|
|
@@ -1,284 +1,468 @@
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
-
|
|
|
-// Parity Substrate style ABIs/Abi
|
|
|
-use crate::sema::ast;
|
|
|
-use crate::sema::tags::render;
|
|
|
-use contract_metadata::*;
|
|
|
+use contract_metadata::{
|
|
|
+ CodeHash, Compiler, Contract, ContractMetadata, Language, Source, SourceCompiler,
|
|
|
+ SourceLanguage, SourceWasm,
|
|
|
+};
|
|
|
+use ink_metadata::{
|
|
|
+ layout::{FieldLayout, Layout, LayoutKey, LeafLayout, RootLayout, StructLayout},
|
|
|
+ ConstructorSpec, ContractSpec, EventParamSpec, EventSpec, InkProject, MessageParamSpec,
|
|
|
+ MessageSpec, ReturnTypeSpec, TypeSpec,
|
|
|
+};
|
|
|
+
|
|
|
+use itertools::Itertools;
|
|
|
+use serde_json::Value;
|
|
|
+
|
|
|
+use num_bigint::BigInt;
|
|
|
use num_traits::ToPrimitive;
|
|
|
+use scale_info::{
|
|
|
+ form::PortableForm, Field, Path, PortableRegistryBuilder, Type, TypeDef, TypeDefArray,
|
|
|
+ TypeDefComposite, TypeDefPrimitive, TypeDefSequence, TypeDefTuple, TypeDefVariant, Variant,
|
|
|
+};
|
|
|
use semver::Version;
|
|
|
-use serde::{Deserialize, Serialize};
|
|
|
-use serde_json::{Map, Value};
|
|
|
use solang_parser::pt;
|
|
|
-use std::convert::TryInto;
|
|
|
|
|
|
use super::non_unique_function_names;
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-pub struct Abi {
|
|
|
- storage: Storage,
|
|
|
- types: Vec<Type>,
|
|
|
- pub spec: Spec,
|
|
|
-}
|
|
|
-
|
|
|
-impl Abi {
|
|
|
- pub fn get_function(&self, name: &str) -> Option<&Message> {
|
|
|
- self.spec.messages.iter().find(|m| name == m.name)
|
|
|
+use crate::sema::{
|
|
|
+ ast::{self, ArrayLength, EventDecl, Function},
|
|
|
+ tags::render,
|
|
|
+};
|
|
|
+
|
|
|
+macro_rules! path {
|
|
|
+ ($( $segments:expr ),*) => {
|
|
|
+ Path::from_segments_unchecked([$($segments),*].iter().map(ToString::to_string))
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-pub struct ArrayDef {
|
|
|
- array: Array,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-pub struct Array {
|
|
|
- len: usize,
|
|
|
- #[serde(rename = "type")]
|
|
|
- ty: usize,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-pub struct SequenceDef {
|
|
|
- sequence: Sequence,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-pub struct Sequence {
|
|
|
- #[serde(rename = "type")]
|
|
|
- ty: usize,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-#[serde(untagged)]
|
|
|
-enum Type {
|
|
|
- Builtin { def: PrimitiveDef },
|
|
|
- BuiltinArray { def: ArrayDef },
|
|
|
- BuiltinSequence { def: SequenceDef },
|
|
|
- Struct { path: Vec<String>, def: Composite },
|
|
|
- Enum { path: Vec<String>, def: EnumDef },
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct BuiltinType {
|
|
|
- id: String,
|
|
|
- def: String,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct EnumVariant {
|
|
|
- name: String,
|
|
|
- discriminant: usize,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct EnumDef {
|
|
|
- variant: Enum,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct Enum {
|
|
|
- variants: Vec<EnumVariant>,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct Composite {
|
|
|
- composite: StructFields,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct StructFields {
|
|
|
- fields: Vec<StructField>,
|
|
|
-}
|
|
|
-
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct PrimitiveDef {
|
|
|
- primitive: String,
|
|
|
+fn primitive_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
|
|
|
+ match ty {
|
|
|
+ ast::Type::Int(_) | ast::Type::Uint(_) => int_to_ty(ty, registry),
|
|
|
+ ast::Type::Bool => registry.register_type(Type::new(
|
|
|
+ path!("bool"),
|
|
|
+ vec![],
|
|
|
+ TypeDef::Primitive(TypeDefPrimitive::Bool),
|
|
|
+ Default::default(),
|
|
|
+ )),
|
|
|
+ ast::Type::String => registry.register_type(Type::new(
|
|
|
+ path!("string"),
|
|
|
+ vec![],
|
|
|
+ TypeDef::Primitive(TypeDefPrimitive::Str),
|
|
|
+ Default::default(),
|
|
|
+ )),
|
|
|
+ _ => unreachable!("non primitive types"),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-#[derive(Deserialize, Serialize, PartialEq, Eq)]
|
|
|
-struct StructField {
|
|
|
- #[serde(skip_serializing_if = "Option::is_none")]
|
|
|
- name: Option<String>,
|
|
|
- #[serde(rename = "type")]
|
|
|
- ty: usize,
|
|
|
+fn int_to_ty(ty: &ast::Type, registry: &mut PortableRegistryBuilder) -> u32 {
|
|
|
+ let (signed, scalety) = match ty {
|
|
|
+ ast::Type::Uint(n) => ('u', n.next_power_of_two()),
|
|
|
+ ast::Type::Int(n) => ('i', n.next_power_of_two()),
|
|
|
+ _ => unreachable!(),
|
|
|
+ };
|
|
|
+ let def = match (signed, scalety) {
|
|
|
+ ('u', n) => match n {
|
|
|
+ 8 => TypeDefPrimitive::U8,
|
|
|
+ 16 => TypeDefPrimitive::U16,
|
|
|
+ 32 => TypeDefPrimitive::U32,
|
|
|
+ 64 => TypeDefPrimitive::U64,
|
|
|
+ 128 => TypeDefPrimitive::U128,
|
|
|
+ 256 => TypeDefPrimitive::U256,
|
|
|
+ _ => unreachable!(),
|
|
|
+ },
|
|
|
+ ('i', n) => match n {
|
|
|
+ 8 => TypeDefPrimitive::I8,
|
|
|
+ 16 => TypeDefPrimitive::I16,
|
|
|
+ 32 => TypeDefPrimitive::I32,
|
|
|
+ 64 => TypeDefPrimitive::I64,
|
|
|
+ 128 => TypeDefPrimitive::I128,
|
|
|
+ 256 => TypeDefPrimitive::I256,
|
|
|
+
|
|
|
+ _ => unreachable!(),
|
|
|
+ },
|
|
|
+ _ => {
|
|
|
+ unreachable!()
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let path = path!(format!("{signed}{scalety}"));
|
|
|
+ let ty = Type::new(path, vec![], TypeDef::Primitive(def), Default::default());
|
|
|
+ registry.register_type(ty)
|
|
|
}
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-pub struct Constructor {
|
|
|
- pub name: String,
|
|
|
- pub selector: String,
|
|
|
- pub docs: Vec<String>,
|
|
|
- args: Vec<Param>,
|
|
|
-}
|
|
|
+/// Given an `ast::Type`, find and register the `scale_info::Type` definition in the registry
|
|
|
+fn resolve_ast(ty: &ast::Type, ns: &ast::Namespace, registry: &mut PortableRegistryBuilder) -> u32 {
|
|
|
+ match ty {
|
|
|
+ // should reflect address_length for different substrate runtime
|
|
|
+ ast::Type::Address(_) | ast::Type::Contract(_) => {
|
|
|
+ // substituted to [u8; address_length]
|
|
|
+ let address_ty = resolve_ast(
|
|
|
+ &ast::Type::Array(
|
|
|
+ Box::new(ast::Type::Uint(8)),
|
|
|
+ vec![ArrayLength::Fixed(BigInt::from(ns.address_length))],
|
|
|
+ ),
|
|
|
+ ns,
|
|
|
+ registry,
|
|
|
+ );
|
|
|
+ // substituted to struct { AccountId }
|
|
|
+ let field = Field::new(None, address_ty.into(), None, vec![]);
|
|
|
+ let c = TypeDefComposite::new(vec![field]);
|
|
|
+ let path = path!("ink_env", "types", "AccountId");
|
|
|
+ let ty: Type<PortableForm> =
|
|
|
+ Type::new(path, vec![], TypeDef::Composite(c), Default::default());
|
|
|
+ registry.register_type(ty)
|
|
|
+ }
|
|
|
+ // primitive types
|
|
|
+ ast::Type::Bool | ast::Type::Int(_) | ast::Type::Uint(_) | ast::Type::String => {
|
|
|
+ primitive_to_ty(ty, registry)
|
|
|
+ }
|
|
|
+ // resolve from the deepest element to outside
|
|
|
+ // [[A; a: usize]; b: usize] -> Array(A_id, vec![a, b])
|
|
|
+ ast::Type::Array(ty, dims) => {
|
|
|
+ let mut ty = resolve_ast(ty, ns, registry);
|
|
|
+ for d in dims {
|
|
|
+ if let ast::ArrayLength::Fixed(d) = d {
|
|
|
+ let def = TypeDefArray::new(d.to_u32().unwrap(), ty.into());
|
|
|
+
|
|
|
+ // resolve current depth
|
|
|
+ ty = registry.register_type(Type::new(
|
|
|
+ Default::default(),
|
|
|
+ vec![],
|
|
|
+ TypeDef::Array(def),
|
|
|
+ Default::default(),
|
|
|
+ ));
|
|
|
+ } else {
|
|
|
+ let def = TypeDefSequence::new(ty.into());
|
|
|
+
|
|
|
+ // resolve current depth
|
|
|
+ ty = registry.register_type(Type::new(
|
|
|
+ Default::default(),
|
|
|
+ vec![],
|
|
|
+ TypeDef::Sequence(def),
|
|
|
+ Default::default(),
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ty
|
|
|
+ }
|
|
|
+ // substituted to [u8; len]
|
|
|
+ ast::Type::Bytes(n) => resolve_ast(
|
|
|
+ &ast::Type::Array(
|
|
|
+ Box::new(ast::Type::Uint(8)),
|
|
|
+ vec![ArrayLength::Fixed(BigInt::from(*n as i8))],
|
|
|
+ ),
|
|
|
+ ns,
|
|
|
+ registry,
|
|
|
+ ),
|
|
|
+ // substituted to Vec<u8>
|
|
|
+ ast::Type::DynamicBytes => resolve_ast(
|
|
|
+ &ast::Type::Array(Box::new(ast::Type::Uint(8)), vec![ArrayLength::Dynamic]),
|
|
|
+ ns,
|
|
|
+ registry,
|
|
|
+ ),
|
|
|
+ ast::Type::Struct(s) => {
|
|
|
+ let def = s.definition(ns);
|
|
|
+ let fields = def
|
|
|
+ .fields
|
|
|
+ .iter()
|
|
|
+ .map(|f| {
|
|
|
+ let f_ty = resolve_ast(&f.ty, ns, registry);
|
|
|
|
|
|
-impl Constructor {
|
|
|
- /// Build byte string from
|
|
|
- pub fn selector(&self) -> Vec<u8> {
|
|
|
- parse_selector(&self.selector)
|
|
|
+ Field::new(Some(f.name_as_str().to_string()), f_ty.into(), None, vec![])
|
|
|
+ })
|
|
|
+ .collect::<Vec<Field<PortableForm>>>();
|
|
|
+ let c = TypeDefComposite::new(fields);
|
|
|
+ let path = path!(&def.name);
|
|
|
+ let ty = Type::new(path, vec![], TypeDef::Composite(c), Default::default());
|
|
|
+ registry.register_type(ty)
|
|
|
+ }
|
|
|
+ ast::Type::Enum(n) => {
|
|
|
+ let decl = &ns.enums[*n];
|
|
|
+ let mut variants = decl.values.iter().collect_vec();
|
|
|
+ // sort by discriminant
|
|
|
+ variants.sort_by(|a, b| a.1 .1.cmp(&b.1 .1));
|
|
|
+ let variants = variants
|
|
|
+ .into_iter()
|
|
|
+ .map(|(k, v)| Variant {
|
|
|
+ name: k.clone(),
|
|
|
+ fields: Default::default(),
|
|
|
+ index: v.1 as u8,
|
|
|
+ docs: Default::default(),
|
|
|
+ })
|
|
|
+ .collect::<Vec<_>>();
|
|
|
+ let variant = TypeDef::Variant(TypeDefVariant::new(variants));
|
|
|
+ let path = path!(&decl.name);
|
|
|
+ let ty = Type::new(path, vec![], variant, Default::default());
|
|
|
+ registry.register_type(ty)
|
|
|
+ }
|
|
|
+ ast::Type::Ref(ty) => resolve_ast(ty, ns, registry),
|
|
|
+ ast::Type::StorageRef(_, ty) => resolve_ast(ty, ns, registry),
|
|
|
+ ast::Type::InternalFunction { .. } => resolve_ast(&ast::Type::Uint(8), ns, registry),
|
|
|
+ ast::Type::ExternalFunction { .. } => {
|
|
|
+ let fields = [ast::Type::Address(false), ast::Type::Uint(32)]
|
|
|
+ .into_iter()
|
|
|
+ .map(|ty| {
|
|
|
+ let ty = resolve_ast(&ty, ns, registry);
|
|
|
+
|
|
|
+ Field::new(
|
|
|
+ Default::default(),
|
|
|
+ ty.into(),
|
|
|
+ Default::default(),
|
|
|
+ Default::default(),
|
|
|
+ )
|
|
|
+ })
|
|
|
+ .collect::<Vec<_>>();
|
|
|
+ let composite = TypeDef::Composite(TypeDefComposite::new(fields));
|
|
|
+ let path = path!("ExternalFunction");
|
|
|
+ let ty = Type::new(path, vec![], composite, Default::default());
|
|
|
+ registry.register_type(ty)
|
|
|
+ }
|
|
|
+ ast::Type::UserType(no) => {
|
|
|
+ let decl = &ns.user_types[*no];
|
|
|
+ let resolved = resolve_ast(&decl.ty, ns, registry);
|
|
|
+ match (decl.name.as_ref(), decl.loc) {
|
|
|
+ // Builtin Hash type from ink primitives
|
|
|
+ ("Hash", pt::Loc::Builtin) => {
|
|
|
+ let field = Field::new(None, resolved.into(), None, vec![]);
|
|
|
+ let composite = TypeDef::Composite(TypeDefComposite::new([field]));
|
|
|
+ let path = path!("ink_env", "types", "Hash");
|
|
|
+ registry.register_type(Type::new(path, vec![], composite, vec![]))
|
|
|
+ }
|
|
|
+ _ => resolved,
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ast::Type::Mapping(k, v) => {
|
|
|
+ resolve_ast(k, ns, registry);
|
|
|
+ resolve_ast(v, ns, registry)
|
|
|
+ }
|
|
|
+ _ => unreachable!(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-pub struct Message {
|
|
|
- pub name: String,
|
|
|
- pub selector: String,
|
|
|
- pub docs: Vec<String>,
|
|
|
- mutates: bool,
|
|
|
- payable: bool,
|
|
|
- args: Vec<Param>,
|
|
|
- return_type: Option<ParamType>,
|
|
|
-}
|
|
|
-
|
|
|
-impl Message {
|
|
|
- /// Build byte string from
|
|
|
- pub fn selector(&self) -> Vec<u8> {
|
|
|
- parse_selector(&self.selector)
|
|
|
+/// Recursively build the storage layout after all types are registered
|
|
|
+fn type_to_storage_layout(
|
|
|
+ key: u32,
|
|
|
+ root: &LayoutKey,
|
|
|
+ registry: &PortableRegistryBuilder,
|
|
|
+) -> Layout<PortableForm> {
|
|
|
+ let ty = registry.get(key).unwrap();
|
|
|
+ match ty.type_def() {
|
|
|
+ TypeDef::Composite(inner) => Layout::Struct(StructLayout::new(
|
|
|
+ ty.path().ident().unwrap_or_default(),
|
|
|
+ inner.fields().iter().map(|field| {
|
|
|
+ FieldLayout::new(
|
|
|
+ field.name().map(ToString::to_string).unwrap_or_default(),
|
|
|
+ type_to_storage_layout(field.ty().id(), root, registry),
|
|
|
+ )
|
|
|
+ }),
|
|
|
+ )),
|
|
|
+ _ => Layout::Leaf(LeafLayout::new(*root, key.into())),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-pub struct Event {
|
|
|
- docs: Vec<String>,
|
|
|
- name: String,
|
|
|
- args: Vec<ParamIndexed>,
|
|
|
-}
|
|
|
+/// Generate `InkProject` from `ast::Type` and `ast::Namespace`
|
|
|
+pub fn gen_project(contract_no: usize, ns: &ast::Namespace) -> InkProject {
|
|
|
+ let mut registry = PortableRegistryBuilder::new();
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-pub struct Spec {
|
|
|
- pub constructors: Vec<Constructor>,
|
|
|
- pub messages: Vec<Message>,
|
|
|
- pub events: Vec<Event>,
|
|
|
-}
|
|
|
+ // This is only used by off-chain tooling. At the moment there is no such tooling available yet.
|
|
|
+ // So it is not exactly clear yet what this should look like.
|
|
|
+ // For now it just contains all root layouts (you get all storage keys in use).
|
|
|
+ let fields: Vec<FieldLayout<PortableForm>> = ns.contracts[contract_no]
|
|
|
+ .layout
|
|
|
+ .iter()
|
|
|
+ .filter_map(|layout| {
|
|
|
+ let var = &ns.contracts[layout.contract_no].variables[layout.var_no];
|
|
|
+ if let Some(slot) = layout.slot.to_u32() {
|
|
|
+ let ty = resolve_ast(&layout.ty, ns, &mut registry);
|
|
|
+ let layout_key = LayoutKey::new(slot);
|
|
|
+ let root = RootLayout::new(
|
|
|
+ layout_key,
|
|
|
+ type_to_storage_layout(ty, &layout_key, ®istry),
|
|
|
+ );
|
|
|
+ Some(FieldLayout::new(var.name.clone(), root))
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collect();
|
|
|
+ let contract_name = ns.contracts[contract_no].name.clone();
|
|
|
+ let storage = Layout::Struct(StructLayout::new(contract_name, fields));
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct Param {
|
|
|
- name: String,
|
|
|
- #[serde(rename = "type")]
|
|
|
- ty: ParamType,
|
|
|
-}
|
|
|
+ let constructor_spec = |f: &Function| -> ConstructorSpec<PortableForm> {
|
|
|
+ let payable = matches!(f.mutability, ast::Mutability::Payable(_));
|
|
|
+ let args = f
|
|
|
+ .params
|
|
|
+ .iter()
|
|
|
+ .map(|p| {
|
|
|
+ let ty = resolve_ast(&p.ty, ns, &mut registry);
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct ParamIndexed {
|
|
|
- #[serde(flatten)]
|
|
|
- param: Param,
|
|
|
- indexed: bool,
|
|
|
-}
|
|
|
+ let path = registry.get(ty).unwrap().path().clone();
|
|
|
+ let spec = TypeSpec::new(ty.into(), path);
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct ParamType {
|
|
|
- #[serde(rename = "type")]
|
|
|
- ty: usize,
|
|
|
- display_name: Vec<String>,
|
|
|
-}
|
|
|
+ MessageParamSpec::new(p.name_as_str().to_string())
|
|
|
+ .of_type(spec)
|
|
|
+ .done()
|
|
|
+ })
|
|
|
+ .collect::<Vec<MessageParamSpec<PortableForm>>>();
|
|
|
+
|
|
|
+ ConstructorSpec::from_label(if f.name.is_empty() { "new" } else { &f.name }.into())
|
|
|
+ .selector(f.selector().try_into().unwrap())
|
|
|
+ .payable(payable)
|
|
|
+ .args(args)
|
|
|
+ .docs(vec![render(&f.tags).as_str()])
|
|
|
+ .returns(ReturnTypeSpec::new(None))
|
|
|
+ .done()
|
|
|
+ };
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct Storage {
|
|
|
- #[serde(rename = "struct")]
|
|
|
- structs: StorageStruct,
|
|
|
-}
|
|
|
+ let constructors = ns.contracts[contract_no]
|
|
|
+ .functions
|
|
|
+ .iter()
|
|
|
+ .filter_map(|i| {
|
|
|
+ // include functions of type constructor
|
|
|
+ let f = &ns.functions[*i];
|
|
|
+ if f.is_constructor() {
|
|
|
+ Some(f)
|
|
|
+ } else {
|
|
|
+ None
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .chain(
|
|
|
+ // include default constructor if exists
|
|
|
+ ns.contracts[contract_no]
|
|
|
+ .default_constructor
|
|
|
+ .as_ref()
|
|
|
+ .map(|(e, _)| e),
|
|
|
+ )
|
|
|
+ .map(constructor_spec)
|
|
|
+ .collect::<Vec<ConstructorSpec<PortableForm>>>();
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct StorageStruct {
|
|
|
- fields: Vec<StorageLayout>,
|
|
|
-}
|
|
|
+ let conflicting_names = non_unique_function_names(contract_no, ns);
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct StorageLayout {
|
|
|
- name: String,
|
|
|
- layout: LayoutField,
|
|
|
-}
|
|
|
+ let message_spec = |f: &Function| -> MessageSpec<PortableForm> {
|
|
|
+ let payable = matches!(f.mutability, ast::Mutability::Payable(_));
|
|
|
+ let mutates = matches!(
|
|
|
+ f.mutability,
|
|
|
+ ast::Mutability::Payable(_) | ast::Mutability::Nonpayable(_)
|
|
|
+ );
|
|
|
+ let ret_spec: Option<TypeSpec<PortableForm>> = match f.returns.len() {
|
|
|
+ 0 => None,
|
|
|
+ 1 => {
|
|
|
+ let ty = resolve_ast(&f.returns[0].ty, ns, &mut registry);
|
|
|
+ let path = registry.get(ty).unwrap().path().clone();
|
|
|
+ Some(TypeSpec::new(ty.into(), path))
|
|
|
+ }
|
|
|
+ _ => {
|
|
|
+ let fields = f
|
|
|
+ .returns
|
|
|
+ .iter()
|
|
|
+ .map(|r_p| {
|
|
|
+ let ty = resolve_ast(&r_p.ty, ns, &mut registry);
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct LayoutField {
|
|
|
- cell: LayoutFieldCell,
|
|
|
-}
|
|
|
+ ty.into()
|
|
|
+ })
|
|
|
+ .collect::<Vec<_>>();
|
|
|
|
|
|
-#[derive(Deserialize, Serialize)]
|
|
|
-struct LayoutFieldCell {
|
|
|
- key: String,
|
|
|
- ty: usize,
|
|
|
-}
|
|
|
+ let t = TypeDefTuple::new_portable(fields);
|
|
|
|
|
|
-/// Create a new registry and create new entries. Note that the registry is
|
|
|
-/// accessed by number, and the first entry is 1, not 0.
|
|
|
-impl Abi {
|
|
|
- /// Add a type to the list unless already present
|
|
|
- fn register_ty(&mut self, ty: Type) -> usize {
|
|
|
- match self.types.iter().position(|t| *t == ty) {
|
|
|
- Some(i) => i + 1,
|
|
|
- None => {
|
|
|
- self.types.push(ty);
|
|
|
-
|
|
|
- self.types.len()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ let path = path!(&ns.contracts[contract_no].name, &f.name, "return_type");
|
|
|
|
|
|
- /// Returns index to builtin type in registry. Type is added if not already present
|
|
|
- fn builtin_type(&mut self, ty: &str) -> usize {
|
|
|
- self.register_ty(Type::Builtin {
|
|
|
- def: PrimitiveDef {
|
|
|
- primitive: ty.to_owned(),
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
+ let ty = registry.register_type(Type::new(
|
|
|
+ path,
|
|
|
+ vec![],
|
|
|
+ TypeDef::Tuple(t),
|
|
|
+ Default::default(),
|
|
|
+ ));
|
|
|
+ let path = registry.get(ty).unwrap().path().clone();
|
|
|
+ Some(TypeSpec::new(ty.into(), path))
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let ret_type = ReturnTypeSpec::new(ret_spec);
|
|
|
+ let args = f
|
|
|
+ .params
|
|
|
+ .iter()
|
|
|
+ .map(|p| {
|
|
|
+ let ty = resolve_ast(&p.ty, ns, &mut registry);
|
|
|
+ let path = registry.get(ty).unwrap().path().clone();
|
|
|
+ let spec = TypeSpec::new(ty.into(), path);
|
|
|
+
|
|
|
+ MessageParamSpec::new(p.name_as_str().to_string())
|
|
|
+ .of_type(spec)
|
|
|
+ .done()
|
|
|
+ })
|
|
|
+ .collect::<Vec<MessageParamSpec<PortableForm>>>();
|
|
|
+ let label = if conflicting_names.contains(&f.name) {
|
|
|
+ &f.mangled_name
|
|
|
+ } else {
|
|
|
+ &f.name
|
|
|
+ };
|
|
|
+ MessageSpec::from_label(label.into())
|
|
|
+ .selector(f.selector().try_into().unwrap())
|
|
|
+ .mutates(mutates)
|
|
|
+ .payable(payable)
|
|
|
+ .args(args)
|
|
|
+ .returns(ret_type)
|
|
|
+ .docs(vec![render(&f.tags)])
|
|
|
+ .done()
|
|
|
+ };
|
|
|
|
|
|
- /// Returns index to builtin type in registry. Type is added if not already present
|
|
|
- fn builtin_array_type(&mut self, elem: usize, array_len: usize) -> usize {
|
|
|
- self.register_ty(Type::BuiltinArray {
|
|
|
- def: ArrayDef {
|
|
|
- array: Array {
|
|
|
- len: array_len,
|
|
|
- ty: elem,
|
|
|
- },
|
|
|
- },
|
|
|
+ let messages = ns.contracts[contract_no]
|
|
|
+ .all_functions
|
|
|
+ .keys()
|
|
|
+ .filter_map(|function_no| {
|
|
|
+ let func = &ns.functions[*function_no];
|
|
|
+ // libraries are never in the public interface
|
|
|
+ if let Some(base_contract_no) = func.contract_no {
|
|
|
+ if ns.contracts[base_contract_no].is_library() {
|
|
|
+ return None;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Some(func)
|
|
|
})
|
|
|
- }
|
|
|
-
|
|
|
- /// Returns index to builtin type in registry. Type is added if not already present
|
|
|
- fn builtin_slice_type(&mut self, elem: usize) -> usize {
|
|
|
- self.register_ty(Type::BuiltinSequence {
|
|
|
- def: SequenceDef {
|
|
|
- sequence: Sequence { ty: elem },
|
|
|
- },
|
|
|
+ .filter(|f| match f.visibility {
|
|
|
+ pt::Visibility::Public(_) | pt::Visibility::External(_) => matches!(
|
|
|
+ f.ty,
|
|
|
+ pt::FunctionTy::Function | pt::FunctionTy::Fallback | pt::FunctionTy::Receive
|
|
|
+ ),
|
|
|
+ _ => false,
|
|
|
})
|
|
|
- }
|
|
|
+ .map(message_spec)
|
|
|
+ .collect::<Vec<MessageSpec<PortableForm>>>();
|
|
|
|
|
|
- /// Returns index to builtin type in registry. Type is added if not already present
|
|
|
- fn builtin_enum_type(&mut self, e: &ast::EnumDecl) -> usize {
|
|
|
- let mut variants: Vec<EnumVariant> = e
|
|
|
- .values
|
|
|
+ let mut event_spec = |e: &EventDecl| -> EventSpec<PortableForm> {
|
|
|
+ let args = e
|
|
|
+ .fields
|
|
|
.iter()
|
|
|
- .map(|(key, val)| EnumVariant {
|
|
|
- name: key.to_owned(),
|
|
|
- discriminant: val.1,
|
|
|
+ .map(|p| {
|
|
|
+ let ty = resolve_ast(&p.ty, ns, &mut registry);
|
|
|
+ let path = registry.get(ty).unwrap().path().clone();
|
|
|
+ let spec = TypeSpec::new(ty.into(), path);
|
|
|
+ EventParamSpec::new(p.name_as_str().into())
|
|
|
+ .of_type(spec)
|
|
|
+ .indexed(p.indexed)
|
|
|
+ .docs(vec![])
|
|
|
+ .done()
|
|
|
})
|
|
|
- .collect();
|
|
|
-
|
|
|
- variants.sort_by(|a, b| a.discriminant.partial_cmp(&b.discriminant).unwrap());
|
|
|
+ .collect::<Vec<_>>();
|
|
|
+ EventSpec::new(e.name.clone())
|
|
|
+ .args(args)
|
|
|
+ .docs(vec![render(&e.tags)])
|
|
|
+ .done()
|
|
|
+ };
|
|
|
|
|
|
- self.register_ty(Type::Enum {
|
|
|
- path: vec![e.name.to_owned()],
|
|
|
- def: EnumDef {
|
|
|
- variant: Enum { variants },
|
|
|
- },
|
|
|
+ let events = ns.contracts[contract_no]
|
|
|
+ .sends_events
|
|
|
+ .iter()
|
|
|
+ .map(|event_no| {
|
|
|
+ let event = &ns.events[*event_no];
|
|
|
+ event_spec(event)
|
|
|
})
|
|
|
- }
|
|
|
+ .collect::<Vec<EventSpec<PortableForm>>>();
|
|
|
|
|
|
- /// Adds struct type to registry. Does not check for duplication (yet)
|
|
|
- fn struct_type(&mut self, path: Vec<String>, fields: Vec<StructField>) -> usize {
|
|
|
- self.register_ty(Type::Struct {
|
|
|
- path,
|
|
|
- def: Composite {
|
|
|
- composite: StructFields { fields },
|
|
|
- },
|
|
|
- })
|
|
|
- }
|
|
|
-}
|
|
|
+ let spec = ContractSpec::new()
|
|
|
+ .constructors(constructors)
|
|
|
+ .messages(messages)
|
|
|
+ .events(events)
|
|
|
+ .docs(vec![render(&ns.contracts[contract_no].tags)])
|
|
|
+ .done();
|
|
|
|
|
|
-pub fn load(bs: &str) -> Result<Abi, serde_json::error::Error> {
|
|
|
- serde_json::from_str(bs)
|
|
|
+ InkProject::new_portable(storage, spec, registry.finish())
|
|
|
}
|
|
|
|
|
|
fn tags(contract_no: usize, tagname: &str, ns: &ast::Namespace) -> Vec<String> {
|
|
|
@@ -295,7 +479,7 @@ fn tags(contract_no: usize, tagname: &str, ns: &ast::Namespace) -> Vec<String> {
|
|
|
.collect()
|
|
|
}
|
|
|
|
|
|
-/// Generate the metadata for Substrate 2.0
|
|
|
+/// Generate the metadata for Substrate 4.0
|
|
|
pub fn metadata(contract_no: usize, code: &[u8], ns: &ast::Namespace) -> Value {
|
|
|
let hash = blake2_rfc::blake2b::blake2b(32, &[], code);
|
|
|
let version = Version::parse(env!("CARGO_PKG_VERSION")).unwrap();
|
|
|
@@ -303,371 +487,26 @@ pub fn metadata(contract_no: usize, code: &[u8], ns: &ast::Namespace) -> Value {
|
|
|
let compiler = SourceCompiler::new(Compiler::Solang, version);
|
|
|
let code_hash: [u8; 32] = hash.as_bytes().try_into().unwrap();
|
|
|
let source_wasm = SourceWasm::new(code.to_vec());
|
|
|
-
|
|
|
let source = Source::new(Some(source_wasm), CodeHash(code_hash), language, compiler);
|
|
|
- let mut builder = Contract::builder();
|
|
|
|
|
|
- // Add our name and tags
|
|
|
+ let mut builder = Contract::builder();
|
|
|
builder.name(&ns.contracts[contract_no].name);
|
|
|
-
|
|
|
let mut description = tags(contract_no, "title", ns);
|
|
|
-
|
|
|
description.extend(tags(contract_no, "notice", ns));
|
|
|
-
|
|
|
if !description.is_empty() {
|
|
|
builder.description(description.join("\n"));
|
|
|
};
|
|
|
-
|
|
|
let authors = tags(contract_no, "author", ns);
|
|
|
-
|
|
|
if !authors.is_empty() {
|
|
|
builder.authors(authors);
|
|
|
} else {
|
|
|
builder.authors(vec!["unknown"]);
|
|
|
}
|
|
|
-
|
|
|
- // FIXME: contract-metadata wants us to provide a version number, but there is no version in the solidity source
|
|
|
- // code. Since we must provide a valid semver version, we just provide a bogus value.Abi
|
|
|
builder.version(Version::new(0, 0, 1));
|
|
|
-
|
|
|
let contract = builder.build().unwrap();
|
|
|
|
|
|
- // generate the abi for our contract
|
|
|
- let abi = gen_abi(contract_no, ns);
|
|
|
-
|
|
|
- let mut abi_json: Map<String, Value> = Map::new();
|
|
|
- abi_json.insert(
|
|
|
- String::from("types"),
|
|
|
- serde_json::to_value(&abi.types).unwrap(),
|
|
|
- );
|
|
|
- abi_json.insert(
|
|
|
- String::from("spec"),
|
|
|
- serde_json::to_value(&abi.spec).unwrap(),
|
|
|
- );
|
|
|
- abi_json.insert(
|
|
|
- String::from("storage"),
|
|
|
- serde_json::to_value(&abi.storage).unwrap(),
|
|
|
- );
|
|
|
-
|
|
|
- let metadata = ContractMetadata::new(source, contract, None, abi_json);
|
|
|
-
|
|
|
- // serialize to json
|
|
|
- serde_json::to_value(&metadata).unwrap()
|
|
|
-}
|
|
|
-
|
|
|
-fn gen_abi(contract_no: usize, ns: &ast::Namespace) -> Abi {
|
|
|
- let mut abi = Abi {
|
|
|
- types: Vec::new(),
|
|
|
- storage: Storage {
|
|
|
- structs: StorageStruct { fields: Vec::new() },
|
|
|
- },
|
|
|
- spec: Spec {
|
|
|
- constructors: Vec::new(),
|
|
|
- messages: Vec::new(),
|
|
|
- events: Vec::new(),
|
|
|
- },
|
|
|
- };
|
|
|
-
|
|
|
- let fields = ns.contracts[contract_no]
|
|
|
- .layout
|
|
|
- .iter()
|
|
|
- .filter_map(|layout| {
|
|
|
- let var = &ns.contracts[layout.contract_no].variables[layout.var_no];
|
|
|
-
|
|
|
- // mappings and large types cannot be represented
|
|
|
- if !var.ty.contains_mapping(ns) && var.ty.fits_in_memory(ns) {
|
|
|
- Some(StorageLayout {
|
|
|
- name: var.name.to_string(),
|
|
|
- layout: LayoutField {
|
|
|
- cell: LayoutFieldCell {
|
|
|
- key: format!("0x{:064X}", layout.slot),
|
|
|
- ty: ty_to_abi(&var.ty, ns, &mut abi).ty,
|
|
|
- },
|
|
|
- },
|
|
|
- })
|
|
|
- } else {
|
|
|
- None
|
|
|
- }
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- abi.storage.structs.fields = fields;
|
|
|
-
|
|
|
- let conflicting_names = non_unique_function_names(contract_no, ns);
|
|
|
-
|
|
|
- let mut constructors = ns.contracts[contract_no]
|
|
|
- .functions
|
|
|
- .iter()
|
|
|
- .filter_map(|function_no| {
|
|
|
- let f = &ns.functions[*function_no];
|
|
|
- let name = if conflicting_names.contains(&f.name) {
|
|
|
- f.mangled_name.clone()
|
|
|
- } else {
|
|
|
- f.name.clone()
|
|
|
- };
|
|
|
- if f.is_constructor() {
|
|
|
- Some(Constructor {
|
|
|
- name,
|
|
|
- selector: render_selector(f),
|
|
|
- args: f
|
|
|
- .params
|
|
|
- .iter()
|
|
|
- .map(|p| parameter_to_abi(p, ns, &mut abi))
|
|
|
- .collect(),
|
|
|
- docs: vec![render(&f.tags)],
|
|
|
- })
|
|
|
- } else {
|
|
|
- None
|
|
|
- }
|
|
|
- })
|
|
|
- .collect::<Vec<Constructor>>();
|
|
|
-
|
|
|
- if let Some((f, _)) = &ns.contracts[contract_no].default_constructor {
|
|
|
- constructors.push(Constructor {
|
|
|
- name: String::from("new"),
|
|
|
- selector: render_selector(f),
|
|
|
- args: f
|
|
|
- .params
|
|
|
- .iter()
|
|
|
- .map(|p| parameter_to_abi(p, ns, &mut abi))
|
|
|
- .collect(),
|
|
|
- docs: vec![render(&f.tags)],
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- let messages = ns.contracts[contract_no]
|
|
|
- .all_functions
|
|
|
- .keys()
|
|
|
- .filter_map(|function_no| {
|
|
|
- let func = &ns.functions[*function_no];
|
|
|
-
|
|
|
- if let Some(base_contract_no) = func.contract_no {
|
|
|
- if ns.contracts[base_contract_no].is_library() {
|
|
|
- return None;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- Some(func)
|
|
|
- })
|
|
|
- .filter(|f| match f.visibility {
|
|
|
- pt::Visibility::Public(_) | pt::Visibility::External(_) => {
|
|
|
- f.ty == pt::FunctionTy::Function
|
|
|
- }
|
|
|
- _ => false,
|
|
|
- })
|
|
|
- .map(|f| {
|
|
|
- let payable = matches!(f.mutability, ast::Mutability::Payable(_));
|
|
|
-
|
|
|
- Message {
|
|
|
- name: if conflicting_names.contains(&f.name) {
|
|
|
- &f.mangled_name
|
|
|
- } else {
|
|
|
- &f.name
|
|
|
- }
|
|
|
- .into(),
|
|
|
- mutates: matches!(
|
|
|
- f.mutability,
|
|
|
- ast::Mutability::Payable(_) | ast::Mutability::Nonpayable(_)
|
|
|
- ),
|
|
|
- payable,
|
|
|
- return_type: match f.returns.len() {
|
|
|
- 0 => None,
|
|
|
- 1 => Some(ty_to_abi(&f.returns[0].ty, ns, &mut abi)),
|
|
|
- _ => {
|
|
|
- let fields = f
|
|
|
- .returns
|
|
|
- .iter()
|
|
|
- .map(|f| StructField {
|
|
|
- name: f.id.as_ref().map(|id| id.name.to_owned()),
|
|
|
- ty: ty_to_abi(&f.ty, ns, &mut abi).ty,
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- Some(ParamType {
|
|
|
- ty: abi.struct_type(Vec::new(), fields),
|
|
|
- display_name: vec![],
|
|
|
- })
|
|
|
- }
|
|
|
- },
|
|
|
- selector: render_selector(f),
|
|
|
- args: f
|
|
|
- .params
|
|
|
- .iter()
|
|
|
- .map(|p| parameter_to_abi(p, ns, &mut abi))
|
|
|
- .collect(),
|
|
|
- docs: vec![render(&f.tags)],
|
|
|
- }
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- let events = ns.contracts[contract_no]
|
|
|
- .sends_events
|
|
|
- .iter()
|
|
|
- .map(|event_no| {
|
|
|
- let event = &ns.events[*event_no];
|
|
|
-
|
|
|
- let name = event.name.to_owned();
|
|
|
- let args = event
|
|
|
- .fields
|
|
|
- .iter()
|
|
|
- .map(|p| ParamIndexed {
|
|
|
- param: parameter_to_abi(p, ns, &mut abi),
|
|
|
- indexed: p.indexed,
|
|
|
- })
|
|
|
- .collect();
|
|
|
- let docs = vec![render(&event.tags)];
|
|
|
-
|
|
|
- Event { docs, name, args }
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- abi.spec = Spec {
|
|
|
- constructors,
|
|
|
- messages,
|
|
|
- events,
|
|
|
- };
|
|
|
-
|
|
|
- abi
|
|
|
-}
|
|
|
-
|
|
|
-fn ty_to_abi(ty: &ast::Type, ns: &ast::Namespace, registry: &mut Abi) -> ParamType {
|
|
|
- match ty {
|
|
|
- ast::Type::Enum(n) => ParamType {
|
|
|
- ty: registry.builtin_enum_type(&ns.enums[*n]),
|
|
|
- display_name: vec![ns.enums[*n].name.to_owned()],
|
|
|
- },
|
|
|
- ast::Type::Bytes(n) => {
|
|
|
- let elem = registry.builtin_type("u8");
|
|
|
- ParamType {
|
|
|
- ty: registry.builtin_array_type(elem, *n as usize),
|
|
|
- display_name: vec![],
|
|
|
- }
|
|
|
- }
|
|
|
- ast::Type::Mapping(..) => unreachable!(),
|
|
|
- ast::Type::Array(ty, dims) => {
|
|
|
- let mut param_ty = ty_to_abi(ty, ns, registry);
|
|
|
-
|
|
|
- for d in dims {
|
|
|
- if let ast::ArrayLength::Fixed(d) = d {
|
|
|
- param_ty = ParamType {
|
|
|
- ty: registry.builtin_array_type(param_ty.ty, d.to_usize().unwrap()),
|
|
|
- display_name: vec![],
|
|
|
- }
|
|
|
- } else {
|
|
|
- param_ty = ParamType {
|
|
|
- ty: registry.builtin_slice_type(param_ty.ty),
|
|
|
- display_name: vec![],
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- param_ty
|
|
|
- }
|
|
|
- ast::Type::StorageRef(_, ty) => ty_to_abi(ty, ns, registry),
|
|
|
- ast::Type::Ref(ty) => ty_to_abi(ty, ns, registry),
|
|
|
- ast::Type::UserType(no) => ty_to_abi(&ns.user_types[*no].ty, ns, registry),
|
|
|
- ast::Type::Bool | ast::Type::Uint(_) | ast::Type::Int(_) => {
|
|
|
- let scalety = match ty {
|
|
|
- ast::Type::Bool => "bool".into(),
|
|
|
- // Substrate doesn't like primitive types which aren't a power of 2
|
|
|
- // The abi encoder/decoder fixes this automatically
|
|
|
- ast::Type::Uint(n) => format!("u{}", n.next_power_of_two()),
|
|
|
- ast::Type::Int(n) => format!("i{}", n.next_power_of_two()),
|
|
|
- _ => unreachable!(),
|
|
|
- };
|
|
|
-
|
|
|
- ParamType {
|
|
|
- ty: registry.builtin_type(&scalety),
|
|
|
- display_name: vec![scalety.to_string()],
|
|
|
- }
|
|
|
- }
|
|
|
- ast::Type::Address(_) | ast::Type::Contract(_) => {
|
|
|
- let elem = registry.builtin_type("u8");
|
|
|
- let ty = registry.builtin_array_type(elem, 32);
|
|
|
-
|
|
|
- ParamType {
|
|
|
- ty: registry.struct_type(
|
|
|
- vec!["AccountId".to_owned()],
|
|
|
- vec![StructField { name: None, ty }],
|
|
|
- ),
|
|
|
- display_name: vec!["AccountId".to_owned()],
|
|
|
- }
|
|
|
- }
|
|
|
- ast::Type::Struct(struct_type) => {
|
|
|
- let mut display_name = vec![struct_type.definition(ns).name.to_owned()];
|
|
|
-
|
|
|
- if let Some(contract_name) = &struct_type.definition(ns).contract {
|
|
|
- display_name.insert(0, contract_name.to_owned());
|
|
|
- }
|
|
|
-
|
|
|
- let def = struct_type.definition(ns);
|
|
|
- let fields = def
|
|
|
- .fields
|
|
|
- .iter()
|
|
|
- .map(|f| StructField {
|
|
|
- name: Some(f.name_as_str().to_owned()),
|
|
|
- ty: ty_to_abi(&f.ty, ns, registry).ty,
|
|
|
- })
|
|
|
- .collect();
|
|
|
-
|
|
|
- ParamType {
|
|
|
- ty: registry.struct_type(display_name.clone(), fields),
|
|
|
- display_name,
|
|
|
- }
|
|
|
- }
|
|
|
- ast::Type::DynamicBytes => {
|
|
|
- let elem = registry.builtin_type("u8");
|
|
|
-
|
|
|
- ParamType {
|
|
|
- ty: registry.builtin_slice_type(elem),
|
|
|
- display_name: vec![String::from("Vec")],
|
|
|
- }
|
|
|
- }
|
|
|
- ast::Type::String => ParamType {
|
|
|
- ty: registry.builtin_type("str"),
|
|
|
- display_name: vec![String::from("String")],
|
|
|
- },
|
|
|
- ast::Type::InternalFunction { .. } => ParamType {
|
|
|
- ty: registry.builtin_type("u32"),
|
|
|
- display_name: vec![String::from("FunctionSelector")],
|
|
|
- },
|
|
|
- ast::Type::ExternalFunction { .. } => {
|
|
|
- let fields = vec![
|
|
|
- StructField {
|
|
|
- name: None,
|
|
|
- ty: ty_to_abi(&ast::Type::Address(false), ns, registry).ty,
|
|
|
- },
|
|
|
- StructField {
|
|
|
- name: None,
|
|
|
- ty: ty_to_abi(&ast::Type::Uint(32), ns, registry).ty,
|
|
|
- },
|
|
|
- ];
|
|
|
-
|
|
|
- let display_name = vec![String::from("ExternalFunction")];
|
|
|
-
|
|
|
- ParamType {
|
|
|
- ty: registry.struct_type(display_name.clone(), fields),
|
|
|
- display_name,
|
|
|
- }
|
|
|
- }
|
|
|
- _ => unreachable!(),
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-fn parameter_to_abi(param: &ast::Parameter, ns: &ast::Namespace, registry: &mut Abi) -> Param {
|
|
|
- Param {
|
|
|
- name: param.name_as_str().to_owned(),
|
|
|
- ty: ty_to_abi(¶m.ty, ns, registry),
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/// Given an u32 selector, generate a byte string like: 0xF81E7E1A
|
|
|
-fn render_selector(f: &ast::Function) -> String {
|
|
|
- format!("0x{}", hex::encode(f.selector()))
|
|
|
-}
|
|
|
+ let project_json = serde_json::to_value(gen_project(contract_no, ns)).unwrap();
|
|
|
+ let abi = serde_json::from_value(project_json).unwrap();
|
|
|
|
|
|
-/// Given a selector like "0xF81E7E1A", parse the bytes. This function
|
|
|
-/// does not validate the input.
|
|
|
-fn parse_selector(selector: &str) -> Vec<u8> {
|
|
|
- hex::decode(&selector[2..]).unwrap()
|
|
|
+ serde_json::to_value(ContractMetadata::new(source, contract, None, abi)).unwrap()
|
|
|
}
|