From b7d5c07f75f3675b9fee54b3697770105b4f2762 Mon Sep 17 00:00:00 2001 From: fountainchen Date: Tue, 2 Nov 2021 10:24:33 +0800 Subject: [PATCH 1/2] [gen]update golang gen code --- Cargo.lock | 20 +-- etc/starcoin_types.yml | 56 +++++++ .../examples/java/StdlibDemo.java | 56 ------- .../src/common.rs | 46 +++++- vm/transaction-builder-generator/src/cpp.rs | 57 ++++++- .../src/csharp.rs | 122 ++++++++++++-- vm/transaction-builder-generator/src/dart.rs | 2 +- .../src/generate.rs | 74 +++++++-- .../src/golang.rs | 145 +++++++++++++++-- vm/transaction-builder-generator/src/java.rs | 154 ++++++++++++++---- vm/transaction-builder-generator/src/lib.rs | 26 ++- .../src/python3.rs | 147 +++++++++++++++-- vm/transaction-builder-generator/src/rust.rs | 116 ++++++++++--- .../tests/generation.rs | 65 +++++++- 14 files changed, 879 insertions(+), 207 deletions(-) delete mode 100644 vm/transaction-builder-generator/examples/java/StdlibDemo.java diff --git a/Cargo.lock b/Cargo.lock index 8a51a3dca1..2946a2b8b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,11 +691,10 @@ dependencies = [ [[package]] name = "bincode" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder 1.4.3", "serde", ] @@ -7693,7 +7692,7 @@ dependencies = [ "include_dir", "maplit", "serde", - "serde-reflection 0.3.2 (git+https://github.com/starcoinorg/serde-reflection?rev=694048797338ff7385006d968e786b6d9dbdeb8b)", + "serde-reflection 0.3.2", "serde_bytes", "serde_yaml", "structopt 0.3.25", @@ -7734,8 +7733,7 @@ dependencies = [ [[package]] name = "serde-reflection" version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "695ca48311bdeac687d09bbd558de507f0874a401c56cce52ee5665705f817f3" +source = "git+https://github.com/starcoinorg/serde-reflection?rev=694048797338ff7385006d968e786b6d9dbdeb8b#694048797338ff7385006d968e786b6d9dbdeb8b" dependencies = [ "serde", "thiserror", @@ -7743,9 +7741,11 @@ dependencies = [ [[package]] name = "serde-reflection" -version = "0.3.2" -source = "git+https://github.com/starcoinorg/serde-reflection?rev=694048797338ff7385006d968e786b6d9dbdeb8b#694048797338ff7385006d968e786b6d9dbdeb8b" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167450ba550f903a2b35a81ba3ca387585189e2430e3df6b94b95f3bec2f26bd" dependencies = [ + "once_cell", "serde", "thiserror", ] @@ -8623,7 +8623,7 @@ dependencies = [ "anyhow", "bcs-ext", "serde", - "serde-reflection 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-reflection 0.3.5", "serde_yaml", "starcoin-crypto", "starcoin-types", @@ -10938,7 +10938,7 @@ dependencies = [ "move-core-types", "regex", "serde-generate", - "serde-reflection 0.3.2 (git+https://github.com/starcoinorg/serde-reflection?rev=694048797338ff7385006d968e786b6d9dbdeb8b)", + "serde-reflection 0.3.2", "serde_yaml", "starcoin-vm-types", "structopt 0.3.25", diff --git a/etc/starcoin_types.yml b/etc/starcoin_types.yml index b132021fac..0fb06b624e 100644 --- a/etc/starcoin_types.yml +++ b/etc/starcoin_types.yml @@ -32,6 +32,40 @@ ArgumentABI: TYPENAME: TypeTag AuthenticationKey: NEWTYPESTRUCT: BYTES +BlockHeader: + STRUCT: + - parent_hash: + TYPENAME: HashValue + - timestamp: U64 + - number: U64 + - author: + TYPENAME: AccountAddress + - author_auth_key: + OPTION: + TYPENAME: AuthenticationKey + - txn_accumulator_root: + TYPENAME: HashValue + - block_accumulator_root: + TYPENAME: HashValue + - state_root: + TYPENAME: HashValue + - gas_used: U64 + - difficulty: + TUPLEARRAY: + CONTENT: U8 + SIZE: 32 + - body_hash: + TYPENAME: HashValue + - chain_id: + TYPENAME: ChainId + - nonce: U32 + - extra: + TYPENAME: BlockHeaderExtra +BlockHeaderExtra: + NEWTYPESTRUCT: + TUPLEARRAY: + CONTENT: U8 + SIZE: 4 BlockMetadata: STRUCT: - parent_hash: @@ -241,6 +275,18 @@ TransactionArgument: 5: Bool: NEWTYPE: BOOL + 6: + VecU128: + NEWTYPE: + TYPENAME: VecU128 + 7: + VecAccountAddress: + NEWTYPE: + TYPENAME: VecAccountAddress + 8: + VecBytes: + NEWTYPE: + TYPENAME: VecBytes TransactionAuthenticator: ENUM: 0: @@ -311,6 +357,16 @@ WithdrawCapabilityResource: STRUCT: - account_address: TYPENAME: AccountAddress +VecBytes: + NEWTYPESTRUCT: + SEQ: BYTES +VecAccountAddress: + NEWTYPESTRUCT: + SEQ: + TYPENAME: AccountAddress +VecU128: + NEWTYPESTRUCT: + SEQ: U128 WriteOp: ENUM: 0: diff --git a/vm/transaction-builder-generator/examples/java/StdlibDemo.java b/vm/transaction-builder-generator/examples/java/StdlibDemo.java deleted file mode 100644 index c7af946568..0000000000 --- a/vm/transaction-builder-generator/examples/java/StdlibDemo.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) The Diem Core Contributors -// SPDX-License-Identifier: Apache-2.0 - -import java.util.Arrays; -import java.util.ArrayList; - -import com.facebook.serde.Bytes; -import com.facebook.serde.Serializer; -import com.facebook.serde.Unsigned; // used as documentation. -import com.facebook.lcs.LcsSerializer; -import org.starcoin.stdlib.Stdlib; -import org.starcoin.types.AccountAddress; -import org.starcoin.types.Identifier; -import org.starcoin.types.Script; -import org.starcoin.types.StructTag; -import org.starcoin.types.TypeTag; - -public class StdlibDemo { - - static AccountAddress make_address(byte[] values) { - assert values.length == 16; - Byte[] address = new Byte[16]; - for (int i = 0; i < 16; i++) { - address[i] = Byte.valueOf(values[i]); - } - return new AccountAddress(address); - } - - public static void main(String[] args) throws Exception { - StructTag.Builder builder = new StructTag.Builder(); - builder.address = make_address(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); - builder.module = new Identifier("LBR"); - builder.name = new Identifier("LBR"); - builder.type_params = new ArrayList(); - StructTag tag = builder.build(); - - TypeTag token = new TypeTag.Struct(tag); - - AccountAddress payee = make_address( - new byte[]{0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}); - - @Unsigned Long amount = Long.valueOf(1234567); - Script script = - Stdlib.encode_peer_to_peer_with_metadata_script(token, payee, new Bytes(new byte[]{}),java.math.BigInteger.valueOf(amount), new Bytes(new byte[]{})); - - Serializer serializer = new LcsSerializer(); - script.serialize(serializer); - byte[] output = serializer.get_bytes(); - - for (byte o : output) { - System.out.print(((int) o & 0xFF) + " "); - }; - System.out.println(); - } - -} diff --git a/vm/transaction-builder-generator/src/common.rs b/vm/transaction-builder-generator/src/common.rs index 90db85661c..8a598e2549 100644 --- a/vm/transaction-builder-generator/src/common.rs +++ b/vm/transaction-builder-generator/src/common.rs @@ -32,9 +32,14 @@ fn quote_type_as_format(type_tag: &TypeTag) -> Format { Address => Format::TypeName("AccountAddress".into()), Vector(type_tag) => match type_tag.as_ref() { U8 => Format::Bytes, + U128 => Format::TypeName("VecU128".into()), + Address => Format::TypeName("VecAccountAddress".into()), + Vector(type_tag) => match type_tag.as_ref() { + U8 => Format::TypeName("VecBytes".into()), + _ => type_not_allowed(type_tag), + }, _ => type_not_allowed(type_tag), }, - Struct(_) | Signer => type_not_allowed(type_tag), } } @@ -79,15 +84,23 @@ pub(crate) fn mangle_type(type_tag: &TypeTag) -> String { use TypeTag::*; match type_tag { Bool => "bool".into(), - U8 => "st.uint8".into(), - U64 => "st.uint64".into(), - U128 => "st.uint128".into(), - Address => "starcoin_types.AccountAddress".into(), + U8 => "u8".into(), + U64 => "u64".into(), + U128 => "u128".into(), + Address => "address".into(), Vector(type_tag) => match type_tag.as_ref() { - U8 => "bytes".into(), + U8 => "u8vector".into(), + Address => "vecaccountaddress".into(), + U128 => "vecu128".into(), + Vector(type_tag) => { + if type_tag.as_ref() == &U8 { + "vecbytes".into() + } else { + type_not_allowed(type_tag) + } + } _ => type_not_allowed(type_tag), }, - Struct(_) | Signer => type_not_allowed(type_tag), } } @@ -95,7 +108,15 @@ pub(crate) fn mangle_type(type_tag: &TypeTag) -> String { pub(crate) fn get_external_definitions(diem_types: &str) -> serde_generate::ExternalDefinitions { let definitions = vec![( diem_types, - vec!["AccountAddress", "TypeTag", "Script", "TransactionArgument"], + vec![ + "AccountAddress", + "TypeTag", + "Script", + "TransactionArgument", + "VecBytes", + "VecU128", + "VecAccountAddress", + ], )]; definitions .into_iter() @@ -108,7 +129,7 @@ pub(crate) fn get_external_definitions(diem_types: &str) -> serde_generate::Exte .collect() } -pub(crate) fn get_required_decoding_helper_types(abis: &[ScriptABI]) -> BTreeSet<&TypeTag> { +pub(crate) fn get_required_helper_types(abis: &[ScriptABI]) -> BTreeSet<&TypeTag> { let mut required_types = BTreeSet::new(); for abi in abis { for arg in abi.args() { @@ -119,6 +140,13 @@ pub(crate) fn get_required_decoding_helper_types(abis: &[ScriptABI]) -> BTreeSet required_types } +pub(crate) fn filter_transaction_scripts(abis: &[ScriptABI]) -> Vec { + abis.iter() + .cloned() + .filter(|abi| abi.is_transaction_script_abi()) + .collect() +} + pub(crate) fn transaction_script_abis(abis: &[ScriptABI]) -> Vec { abis.iter() .cloned() diff --git a/vm/transaction-builder-generator/src/cpp.rs b/vm/transaction-builder-generator/src/cpp.rs index 82d78f3793..e61646214c 100644 --- a/vm/transaction-builder-generator/src/cpp.rs +++ b/vm/transaction-builder-generator/src/cpp.rs @@ -31,7 +31,9 @@ pub fn output(out: &mut dyn Write, abis: &[ScriptABI], namespace: Option<&str>) ScriptABI::TransactionScript(abi) => { emitter.output_transaction_script_builder_definition(abi)? } - ScriptABI::ScriptFunction(abi) => emitter.output_script_fun_builder_definition(abi)?, + ScriptABI::ScriptFunction(abi) => { + emitter.output_script_function_builder_definition(abi)? + } }; } emitter.output_close_namespace() @@ -77,7 +79,9 @@ pub fn output_library_body( ScriptABI::TransactionScript(abi) => { emitter.output_transaction_script_builder_definition(abi)? } - ScriptABI::ScriptFunction(abi) => emitter.output_script_fun_builder_definition(abi)?, + ScriptABI::ScriptFunction(abi) => { + emitter.output_script_function_builder_definition(abi)? + } }; } emitter.output_close_namespace() @@ -188,13 +192,13 @@ using namespace diem_types; }};"#, Self::quote_code(abi.code()), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; writeln!(self.out, "}}")?; Ok(()) } - fn output_script_fun_builder_definition(&mut self, abi: &ScriptFunctionABI) -> Result<()> { + fn output_script_function_builder_definition(&mut self, abi: &ScriptFunctionABI) -> Result<()> { if self.inlined_definitions { self.output_doc(abi.doc())?; } @@ -221,7 +225,7 @@ using namespace diem_types; {}, {}, std::vector {{{}}}, - std::vector {{{}}}, + std::vector> {{{}}}, }} }};"#, Self::quote_module_id(abi.module_name()), @@ -301,6 +305,13 @@ using namespace diem_types; .join(", ") } + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + fn quote_type(type_tag: &TypeTag) -> String { use TypeTag::*; match type_tag { @@ -311,6 +322,7 @@ using namespace diem_types; Address => "AccountAddress".into(), Vector(type_tag) => match type_tag.as_ref() { U8 => "std::vector".into(), + Vector(type_tag) if type_tag.as_ref() == &U8 => "VecBytes".into(), _ => common::type_not_allowed(type_tag), }, Struct(_) | Signer => common::type_not_allowed(type_tag), @@ -318,6 +330,20 @@ using namespace diem_types; } fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + match Self::bcs_primitive_type_name(type_tag) { + None => format!("{}.bcsSerialize()", name), + Some(type_name) => format!( + r#"({{ + auto s = BcsSerializer(); + Serializable<{}>::serialize({}, s); + std::move(s).bytes(); + }})"#, + type_name, name + ), + } + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { Bool => format!("{{TransactionArgument::Bool {{{}}} }}", name), @@ -334,6 +360,27 @@ using namespace diem_types; Struct(_) | Signer => common::type_not_allowed(type_tag), } } + + // - if a `type_tag` is a primitive type in BCS, we can call + // `Serializable::serialize(arg, &s)` and `Deserializable::deserialize(arg, &d)` + // to convert into and from `std::vector`. + // - otherwise, we can use `.bcsSerialize()`, `.bcsDeserialize()` to do the work. + fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> { + use TypeTag::*; + match type_tag { + Bool => Some("bool"), + U8 => Some("uint8_t"), + U64 => Some("uint64_t"), + U128 => Some("uint128_t"), + Address => None, + Vector(type_tag) => match type_tag.as_ref() { + U8 => Some("std::vector"), + Vector(type_tag) if type_tag.as_ref() == &U8 => None, + _ => common::type_not_allowed(type_tag), + }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } } pub struct Installer { diff --git a/vm/transaction-builder-generator/src/csharp.rs b/vm/transaction-builder-generator/src/csharp.rs index d5771fd04c..4bb9eeb49c 100644 --- a/vm/transaction-builder-generator/src/csharp.rs +++ b/vm/transaction-builder-generator/src/csharp.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::common; -use starcoin_vm_types::transaction::{ - ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI, -}; use move_core_types::{ account_address::AccountAddress, language_storage::{ModuleId, TypeTag}, @@ -14,6 +11,9 @@ use serde_generate::{ indent::{IndentConfig, IndentedWriter}, CodeGeneratorConfig, }; +use starcoin_vm_types::transaction::{ + ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI, +}; use heck::{CamelCase, ShoutySnakeCase}; use std::{ @@ -69,12 +69,13 @@ fn write_helper_file( emitter.output_transaction_script_encoder_map(&common::transaction_script_abis(abis))?; emitter.output_script_function_encoder_map(&common::script_function_abis(abis))?; for abi in &common::transaction_script_abis(abis) { - emitter.output_code_constant(&abi)?; + emitter.output_code_constant(abi)?; } // Must be defined after the constants. emitter.output_decoder_maps(abis)?; - emitter.output_decoding_helpers(abis)?; + emitter.output_encoding_helpers(abis)?; + emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?; emitter.out.unindent(); writeln!(emitter.out, "\n}}\n")?; // class @@ -176,11 +177,12 @@ where self.out, r#" using System; // For ArgumentException and IndexOutOfRangeException -using System.Numerics; // For BigInteger -using Diem.Types; // For Script, TransactionArgument, TypeTag, TransactionPayload, ScriptFunction -using Serde; // For ValueArray (e.g., ValueArray) using System.Collections; using System.Collections.Generic; // For List, Dictonary +using System.Numerics; // For BigInteger +using Bcs; +using Diem.Types; // For Script, TransactionArgument, VecBytes, TypeTag, TransactionPayload, ScriptFunction +using Serde; // For ValueArray (e.g., ValueArray) "#, )?; writeln!(self.out, "namespace {}\n{{", self.namespace_name)?; @@ -344,7 +346,7 @@ return new Script( new ValueArray(ta) );"#, Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), abi.name().to_shouty_snake_case(), )?; self.out.unindent(); @@ -374,13 +376,13 @@ return new Script( writeln!( self.out, "TypeTag[] tt = new TypeTag[] {{{}}}; -TransactionArgument[] ta = new TransactionArgument[] {{{}}}; +ValueArray[] ta = new ValueArray[] {{{}}}; return new TransactionPayload.ScriptFunction( new ScriptFunction( {}, {}, new ValueArray(tt), - new ValueArray(ta) + new ValueArray>(ta) ) );", Self::quote_type_arguments(abi.ty_args()), @@ -473,11 +475,18 @@ return new TransactionPayload.ScriptFunction( )); } for (index, arg) in abi.args().iter().enumerate() { - params.push_str(&format!( - " Helpers.decode_{}_argument(script.args[{}]),\n", - common::mangle_type(arg.type_tag()), - index, - )); + let decoding = match Self::bcs_primitive_type_name(arg.type_tag()) { + None => format!( + "{}.BcsDeserialize(script.args[{}].ToArray())", + Self::quote_type(arg.type_tag()), + index + ), + Some(type_name) => format!( + "new BcsDeserializer(script.args[{}].ToArray()).deserialize_{}()", + index, type_name + ), + }; + params.push_str(&format!(" {},\n", decoding,)); } params.pop(); // removes last newline params.pop(); // removes last trailing comma @@ -636,8 +645,51 @@ private static System.Collections.Generic.Dictionary Result<()> { + let required_types = common::get_required_helper_types(abis); + for required_type in required_types { + self.output_encoding_helper(required_type)?; + } + Ok(()) + } + + fn output_encoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> { + let function_body = match Self::bcs_primitive_type_name(type_tag) { + None => r#" + return new ValueArray(arg.BcsSerialize()); + "# + .into(), + Some(type_name) => { + format!( + r#" + BcsSerializer s = new BcsSerializer(); + s.serialize_{}(arg); + return new ValueArray(s.get_bytes()); + "#, + type_name + ) + } + }; + writeln!( + self.out, + r#" +private static ValueArray encode_{}_argument({} arg) {{ + try {{ +{} + }} catch (SerializationException e) {{ + throw new ArgumentException("Unable to serialize argument of type {}"); + }} +}} +"#, + common::mangle_type(type_tag), + Self::quote_type(type_tag), + function_body, + common::mangle_type(type_tag) + ) + } + fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { - let required_types = common::get_required_decoding_helper_types(abis); + let required_types = common::get_required_helper_types(abis); for required_type in required_types { self.output_decoding_helper(required_type)?; } @@ -719,6 +771,13 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ .join(", ") } + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + fn quote_identifier(ident: &str) -> String { format!("new Identifier(\"{}\")", ident) } @@ -753,6 +812,7 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ Address => "AccountAddress".into(), Vector(type_tag) => match type_tag.as_ref() { U8 => "ValueArray".into(), + Vector(type_tag) if type_tag.as_ref() == &U8 => "VecBytes".into(), _ => common::type_not_allowed(type_tag), }, @@ -761,6 +821,14 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ } fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "Helpers.encode_{}_argument({})", + common::mangle_type(type_tag), + name + ) + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { Bool => format!("new TransactionArgument.Bool({})", name), @@ -772,7 +840,27 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ U8 => format!("new TransactionArgument.U8Vector({})", name), _ => common::type_not_allowed(type_tag), }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + // - if a `type_tag` is a primitive type in BCS, we can call + // `.serialize_(arg)` and `.deserialize_(arg)` + // to convert into and from `byte[]`. + // - otherwise, we can use `.BcsSerialize()`, `.BcsDeserialize()` to do the work. + fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> { + use TypeTag::*; + match type_tag { + Bool => Some("bool"), + U8 => Some("u8"), + U64 => Some("u64"), + U128 => Some("u128"), + Address => None, + Vector(type_tag) => match type_tag.as_ref() { + U8 => Some("bytes"), + Vector(type_tag) if type_tag.as_ref() == &U8 => None, + _ => common::type_not_allowed(type_tag), + }, Struct(_) | Signer => common::type_not_allowed(type_tag), } } diff --git a/vm/transaction-builder-generator/src/dart.rs b/vm/transaction-builder-generator/src/dart.rs index 18d4bfd4d2..dac18e0e0c 100644 --- a/vm/transaction-builder-generator/src/dart.rs +++ b/vm/transaction-builder-generator/src/dart.rs @@ -655,7 +655,7 @@ typedef ScriptEncodingHelper = Script Function( } fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { - let required_types = common::get_required_decoding_helper_types(abis); + let required_types = common::get_required_helper_types(abis); for required_type in required_types { self.output_decoding_helper(required_type)?; } diff --git a/vm/transaction-builder-generator/src/generate.rs b/vm/transaction-builder-generator/src/generate.rs index 34b774c49c..5d534d6c18 100644 --- a/vm/transaction-builder-generator/src/generate.rs +++ b/vm/transaction-builder-generator/src/generate.rs @@ -21,7 +21,10 @@ enum Language { Rust, Cpp, Java, - Dart, + Csharp, + Go, + TypeScript, + // Dart } } @@ -32,7 +35,7 @@ enum Language { )] struct Options { /// Path to the directory containing ABI files in BCS encoding. - abi_directory: PathBuf, + abi_directories: Vec, /// Language for code generation. #[structopt(long, possible_values = &Language::variants(), case_insensitive = true, default_value = "Python3")] @@ -76,7 +79,7 @@ struct Options { fn main() { let options = Options::from_args(); let abis = - buildgen::read_abis(&options.abi_directory).expect("Failed to read ABI in directory"); + buildgen::read_abis(&options.abi_directories).expect("Failed to read ABI in directory"); let abis = abis .into_iter() .filter(is_supported_abi) @@ -103,17 +106,32 @@ fn main() { Language::Java => { panic!("Code generation in Java requires --target_source_dir"); } - Language::Dart => { - // let module_name = options.module_name.as_deref().unwrap_or("Helpers"); - // let parts = module_name.rsplitn(2, '.').collect::>(); - // let (_, class_name) = if parts.len() > 1 { - // (Some(parts[1]), parts[0]) - // } else { - // (None, parts[0]) - // }; - // buildgen::dart::output(&mut out, &abis, class_name).unwrap() - panic!("Code generation in dart requires --target_source_dir"); + Language::Go => { + buildgen::golang::output( + &mut out, + options.serde_package_name.clone(), + options.diem_package_name.clone(), + options.module_name.as_deref().unwrap_or("main").to_string(), + &abis, + ) + .unwrap(); } + Language::TypeScript => { + buildgen::typescript::output(&mut out, &abis).unwrap(); + } + Language::Csharp => { + panic!("Code generation in C# requires --target_source_dir"); + } // Language::Dart => { + // // let module_name = options.module_name.as_deref().unwrap_or("Helpers"); + // // let parts = module_name.rsplitn(2, '.').collect::>(); + // // let (_, class_name) = if parts.len() > 1 { + // // (Some(parts[1]), parts[0]) + // // } else { + // // (None, parts[0]) + // // }; + // // buildgen::dart::output(&mut out, &abis, class_name).unwrap() + // panic!("Code generation in dart requires --target_source_dir"); + // } } return; } @@ -131,11 +149,20 @@ fn main() { Language::Rust => Box::new(serdegen::rust::Installer::new(install_dir.clone())), Language::Cpp => Box::new(serdegen::cpp::Installer::new(install_dir.clone())), Language::Java => Box::new(serdegen::java::Installer::new(install_dir.clone())), - Language::Dart => Box::new(serdegen::dart::Installer::new(install_dir.clone())), + Language::Csharp => Box::new(serdegen::csharp::Installer::new(install_dir.clone())), + Language::TypeScript => { + Box::new(serdegen::typescript::Installer::new(install_dir.clone())) + } + Language::Go => Box::new(serdegen::golang::Installer::new( + install_dir.clone(), + options.serde_package_name.clone(), + )), + // Language::Dart => Box::new(serdegen::dart::Installer::new(install_dir.clone())), }; match options.language { - Language::Rust => (), // In Rust, runtimes are deployed as crates. + // In Rust and Go, runtimes are deployed using a global package manager. + Language::Rust | Language::Go => (), _ => { installer.install_serde_runtime().unwrap(); installer.install_bcs_runtime().unwrap(); @@ -143,7 +170,10 @@ fn main() { } let content = std::fs::read_to_string(registry_file).expect("registry file must be readable"); - let registry = serde_yaml::from_str::(content.as_str()).unwrap(); + let mut registry = serde_yaml::from_str::(content.as_str()).unwrap(); + if let Language::TypeScript = options.language { + buildgen::typescript::replace_keywords(&mut registry); + }; let (diem_package_name, diem_package_path) = match options.language { Language::Rust => ( if options.diem_version_number == "0.1.0" { @@ -157,6 +187,9 @@ fn main() { "org.starcoin.types".to_string(), vec!["org", "starcoin", "types"], ), + Language::Csharp => ("Starcoin.Types".to_string(), vec!["Starcoin", "Types"]), + Language::Go => ("types".to_string(), vec!["types"]), + Language::TypeScript => ("starcoinTypes".to_string(), vec!["starcoinTypes"]), _ => ("starcoin_types".to_string(), vec!["starcoin_types"]), }; let custom_diem_code = buildgen::read_custom_code_from_paths( @@ -177,13 +210,20 @@ fn main() { options.serde_package_name, options.diem_package_name, )), + Language::TypeScript => Box::new(buildgen::typescript::Installer::new(install_dir)), Language::Rust => Box::new(buildgen::rust::Installer::new( install_dir, options.diem_version_number, )), Language::Cpp => Box::new(buildgen::cpp::Installer::new(install_dir)), Language::Java => Box::new(buildgen::java::Installer::new(install_dir)), - Language::Dart => Box::new(buildgen::dart::Installer::new(install_dir)), + Language::Csharp => Box::new(buildgen::csharp::Installer::new(install_dir)), + Language::Go => Box::new(buildgen::golang::Installer::new( + install_dir, + options.serde_package_name, + options.diem_package_name, + )), + // Language::Dart => Box::new(buildgen::dart::Installer::new(install_dir)), }; if let Some(name) = options.module_name { diff --git a/vm/transaction-builder-generator/src/golang.rs b/vm/transaction-builder-generator/src/golang.rs index a9dcb2bb46..0918d949db 100644 --- a/vm/transaction-builder-generator/src/golang.rs +++ b/vm/transaction-builder-generator/src/golang.rs @@ -2,9 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 use crate::common; -use starcoin_vm_types::transaction::{ - ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI, -}; use move_core_types::{ account_address::AccountAddress, language_storage::{ModuleId, TypeTag}, @@ -14,6 +11,9 @@ use serde_generate::{ indent::{IndentConfig, IndentedWriter}, CodeGeneratorConfig, }; +use starcoin_vm_types::transaction::{ + ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI, +}; use heck::CamelCase; use std::{ @@ -54,7 +54,9 @@ pub fn output( } for abi in abis { match abi { - ScriptABI::TransactionScript(abi) => emitter.output_script_decoder_function(abi)?, + ScriptABI::TransactionScript(abi) => { + emitter.output_transaction_script_decoder_function(abi)? + } ScriptABI::ScriptFunction(abi) => { emitter.output_script_function_decoder_function(abi)? } @@ -67,7 +69,8 @@ pub fn output( emitter.output_transaction_script_decoder_map(&common::transaction_script_abis(abis))?; emitter.output_script_function_decoder_map(&common::script_function_abis(abis))?; - emitter.output_decoding_helpers(abis)?; + emitter.output_encoding_helpers(abis)?; + emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?; Ok(()) } @@ -96,6 +99,12 @@ where None => "diemtypes".into(), }; let mut external_definitions = crate::common::get_external_definitions(&diem_types_package); + // We need BCS for argument encoding and decoding + external_definitions.insert( + "github.com/novifinancial/serde-reflection/serde-generate/runtime/golang/bcs" + .to_string(), + Vec::new(), + ); // Add standard imports external_definitions.insert("fmt".to_string(), Vec::new()); @@ -300,7 +309,7 @@ func DecodeScriptFunctionPayload(script diemtypes.TransactionPayload) (ScriptFun }}"#, abi.name(), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; self.out.unindent(); writeln!(self.out, "}}") @@ -327,7 +336,7 @@ func DecodeScriptFunctionPayload(script diemtypes.TransactionPayload) (ScriptFun Module: {}, Function: {}, TyArgs: []diemtypes.TypeTag{{{}}}, - Args: []diemtypes.TransactionArgument{{{}}}, + Args: [][]byte{{{}}}, }}, }}"#, Self::quote_module_id(abi.module_name()), @@ -339,7 +348,10 @@ func DecodeScriptFunctionPayload(script diemtypes.TransactionPayload) (ScriptFun writeln!(self.out, "}}") } - fn output_script_decoder_function(&mut self, abi: &TransactionScriptABI) -> Result<()> { + fn output_transaction_script_decoder_function( + &mut self, + abi: &TransactionScriptABI, + ) -> Result<()> { writeln!( self.out, "\nfunc decode_{}_script(script *diemtypes.Script) (ScriptCall, error) {{", @@ -427,16 +439,30 @@ func DecodeScriptFunctionPayload(script diemtypes.TransactionPayload) (ScriptFun )?; } for (index, arg) in abi.args().iter().enumerate() { + let decoding = match Self::bcs_primitive_type_name(arg.type_tag()) { + None => { + let quoted_type = Self::quote_type(arg.type_tag()); + let splits: Vec<_> = quoted_type.rsplitn(2, '.').collect(); + format!( + "{}.BcsDeserialize{}(script.Value.Args[{}])", + splits[1], splits[0], index + ) + } + Some(type_name) => format!( + "bcs.NewDeserializer(script.Value.Args[{}]).Deserialize{}()", + index, type_name + ), + }; writeln!( self.out, - r#"if val, err := decode_{}_argument(script.Value.Args[{}]); err == nil {{ + r#" +if val, err := {}; err == nil {{ call.{} = val }} else {{ return nil, err }} "#, - common::mangle_type(arg.type_tag()), - index, + decoding, arg.name().to_camel_case(), )?; } @@ -496,8 +522,51 @@ var script_function_decoder_map = map[string]func(diemtypes.TransactionPayload) writeln!(self.out, "}}") } + fn output_encoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { + let required_types = common::get_required_helper_types(abis); + for required_type in required_types { + self.output_encoding_helper(required_type)?; + } + Ok(()) + } + + fn output_encoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> { + let encoding = match Self::bcs_primitive_type_name(type_tag) { + None => r#" + if val, err := arg.BcsSerialize(); err == nil {{ + return val; + }} + "# + .into(), + Some(type_name) => { + format!( + r#" + s := bcs.NewSerializer(); + if err := s.Serialize{}(arg); err == nil {{ + return s.GetBytes(); + }} + "#, + type_name + ) + } + }; + writeln!( + self.out, + r#" +func encode_{}_argument(arg {}) []byte {{ + {} + panic("Unable to serialize argument of type {}"); +}} +"#, + common::mangle_type(type_tag), + Self::quote_type(type_tag), + encoding, + common::mangle_type(type_tag) + ) + } + fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { - let required_types = common::get_required_decoding_helper_types(abis); + let required_types = common::get_required_helper_types(abis); for required_type in required_types { self.output_decoding_helper(required_type)?; } @@ -515,6 +584,9 @@ var script_function_decoder_map = map[string]func(diemtypes.TransactionPayload) Address => ("Address", "value = arg.Value".into()), Vector(type_tag) => match type_tag.as_ref() { U8 => ("U8Vector", default_stmt), + U128 => ("VecU128", default_stmt), + Address => ("VecAccountAddress", "value = arg.Value".into()), + Vector(type_tag) if type_tag.as_ref() == &U8 => ("VecBytes", default_stmt), _ => common::type_not_allowed(type_tag), }, Struct(_) | Signer => common::type_not_allowed(type_tag), @@ -611,6 +683,13 @@ func decode_{0}_argument(arg diemtypes.TransactionArgument) (value {1}, err erro .join(", ") } + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + fn quote_type(type_tag: &TypeTag) -> String { use TypeTag::*; match type_tag { @@ -621,14 +700,24 @@ func decode_{0}_argument(arg diemtypes.TransactionArgument) (value {1}, err erro Address => "diemtypes.AccountAddress".into(), Vector(type_tag) => match type_tag.as_ref() { U8 => "[]byte".into(), + U128 => "diemtypes.VecU128".into(), + Address => "diemtypes.VecAccountAddress".into(), + Vector(type_tag) if type_tag.as_ref() == &U8 => "diemtypes.VecBytes".into(), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), } } fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "encode_{}_argument({})", + common::mangle_type(type_tag), + name + ) + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { Bool => format!("(*diemtypes.TransactionArgument__Bool)(&{})", name), @@ -638,9 +727,39 @@ func decode_{0}_argument(arg diemtypes.TransactionArgument) (value {1}, err erro Address => format!("&diemtypes.TransactionArgument__Address{{{}}}", name), Vector(type_tag) => match type_tag.as_ref() { U8 => format!("(*diemtypes.TransactionArgument__U8Vector)(&{})", name), + U128 => format!("(*diemtypes.TransactionArgument__VecU128)(&{})", name), + Address => format!( + "&diemtypes.TransactionArgument__VecAccountAddress{{{}}}", + name + ), + Vector(type_tag_inner) => match type_tag_inner.as_ref() { + U8 => format!("(*diemtypes.TransactionArgument__VecBytes)({})", name), + _ => common::type_not_allowed(type_tag_inner), + }, _ => common::type_not_allowed(type_tag), }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + // - if a `type_tag` is a primitive type in BCS, we can call + // `NewSerializer().Serialize(arg)` and `NewDeserializer().Deserialize(arg)` + // to convert into and from `[]byte`. + // - otherwise, we can use `.BcsSerialize()`, `.BcsDeserialize()` to do the work. + fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> { + use TypeTag::*; + match type_tag { + Bool => Some("Bool"), + U8 => Some("U8"), + U64 => Some("U64"), + U128 => Some("U128"), + Address => None, + Vector(type_tag) => match type_tag.as_ref() { + U8 => Some("Bytes"), + U128 | Address => None, + Vector(type_tag) if type_tag.as_ref() == &U8 => None, + _ => common::type_not_allowed(type_tag), + }, Struct(_) | Signer => common::type_not_allowed(type_tag), } } diff --git a/vm/transaction-builder-generator/src/java.rs b/vm/transaction-builder-generator/src/java.rs index 09b35c0d4f..73d248e43e 100644 --- a/vm/transaction-builder-generator/src/java.rs +++ b/vm/transaction-builder-generator/src/java.rs @@ -77,7 +77,8 @@ fn write_helper_file( emitter.output_transaction_script_decoder_map(&common::transaction_script_abis(abis))?; emitter.output_script_function_decoder_map(&common::script_function_abis(abis))?; - emitter.output_decoding_helpers(abis)?; + emitter.output_encoding_helpers(abis)?; + emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?; emitter.out.unindent(); writeln!(emitter.out, "\n}}\n") @@ -88,7 +89,7 @@ fn write_script_call_files( package_name: &str, abis: &[ScriptABI], ) -> Result<()> { - let external_definitions = crate::common::get_external_definitions("com.diem.types"); + let external_definitions = crate::common::get_external_definitions("org.starcoin.types"); let (transaction_script_abis, script_fun_abis): (Vec<_>, Vec<_>) = abis .iter() .cloned() @@ -185,17 +186,22 @@ where import java.math.BigInteger; import java.lang.IllegalArgumentException; import java.lang.IndexOutOfBoundsException; -import com.diem.types.AccountAddress; -import com.diem.types.Script; -import com.diem.types.ScriptFunction; -import com.diem.types.TransactionPayload; -import com.diem.types.Identifier; -import com.diem.types.ModuleId; -import com.diem.types.TransactionArgument; -import com.diem.types.TypeTag; +import org.starcoin.types.AccountAddress; +import org.starcoin.types.Script; +import org.starcoin.types.ScriptFunction; +import org.starcoin.types.TransactionPayload; +import org.starcoin.types.Identifier; +import org.starcoin.types.ModuleId; +import org.starcoin.types.TransactionArgument; +import org.starcoin.types.VecBytes; +import org.starcoin.types.TypeTag; +import com.novi.bcs.BcsDeserializer; +import com.novi.bcs.BcsSerializer; +import com.novi.serde.Bytes; import com.novi.serde.Int128; import com.novi.serde.Unsigned; -import com.novi.serde.Bytes; +import com.novi.serde.DeserializationError; +import com.novi.serde.SerializationError; "#, )?; Ok(()) @@ -206,7 +212,7 @@ import com.novi.serde.Bytes; self.out, r#" /** - * Build a Diem {{@link com.diem.types.Script}} from a structured value {{@link ScriptCall}}. + * Build a Diem {{@link org.starcoin.types.Script}} from a structured value {{@link ScriptCall}}. * * @param call {{@link ScriptCall}} value to encode. * @return Encoded script. @@ -220,7 +226,7 @@ public static Script encode_script(ScriptCall call) {{ self.out, r#" /** - * Build a Diem {{@link com.diem.types.TransactionPayload}} from a structured value {{@link ScriptFunctionCall}}. + * Build a Diem {{@link org.starcoin.types.TransactionPayload}} from a structured value {{@link ScriptFunctionCall}}. * * @param call {{@link ScriptFunctionCall}} value to encode. * @return Encoded TransactionPayload. @@ -237,9 +243,9 @@ public static TransactionPayload encode_script_function(ScriptFunctionCall call) self.out, r#" /** - * Try to recognize a Diem {{@link com.diem.types.Script}} and convert it into a structured value {{@code ScriptCall}}. + * Try to recognize a Diem {{@link org.starcoin.types.Script}} and convert it into a structured value {{@code ScriptCall}}. * - * @param script {{@link com.diem.types.Script}} values to decode. + * @param script {{@link org.starcoin.types.Script}} values to decode. * @return Decoded {{@link ScriptCall}} value. */ public static ScriptCall decode_script(Script script) throws IllegalArgumentException, IndexOutOfBoundsException {{ @@ -256,12 +262,12 @@ public static ScriptCall decode_script(Script script) throws IllegalArgumentExce self.out, r#" /** - * Try to recognize a Diem {{@link com.diem.types.TransactionPayload}} and convert it into a structured value {{@code ScriptFunctionCall}}. + * Try to recognize a Diem {{@link org.starcoin.types.TransactionPayload}} and convert it into a structured value {{@code ScriptFunctionCall}}. * - * @param payload {{@link com.diem.types.TransactionPayload}} values to decode. + * @param payload {{@link org.starcoin.types.TransactionPayload}} values to decode. * @return Decoded {{@link ScriptFunctionCall}} value. */ -public static ScriptFunctionCall decode_script_function_payload(TransactionPayload payload) throws IllegalArgumentException, IndexOutOfBoundsException {{ +public static ScriptFunctionCall decode_script_function_payload(TransactionPayload payload) throws DeserializationError, IllegalArgumentException, IndexOutOfBoundsException {{ if (payload instanceof TransactionPayload.ScriptFunction) {{ ScriptFunction script = ((TransactionPayload.ScriptFunction)payload).value; ScriptFunctionDecodingHelper helper = SCRIPT_FUNCTION_DECODER_MAP.get(script.module.name.value + script.function.value); @@ -291,7 +297,7 @@ public static ScriptFunctionCall decode_script_function_payload(TransactionPaylo Self::quote_doc( abi.doc(), [quoted_type_params_doc, quoted_params_doc].concat(), - "Encoded {@link com.diem.types.Script} value.", + "Encoded {@link org.starcoin.types.Script} value.", ), abi.name(), [quoted_type_params, quoted_params].concat().join(", ") @@ -306,7 +312,7 @@ builder.args = java.util.Arrays.asList({}); return builder.build();"#, abi.name().to_shouty_snake_case(), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; self.out.unindent(); writeln!(self.out, "}}") @@ -326,7 +332,7 @@ return builder.build();"#, Self::quote_doc( abi.doc(), [quoted_type_params_doc, quoted_params_doc].concat(), - "Encoded {@link com.diem.types.TransactionPayload} value.", + "Encoded {@link org.starcoin.types.TransactionPayload} value.", ), abi.name(), [quoted_type_params, quoted_params].concat().join(", ") @@ -445,7 +451,7 @@ return builder.build();"#, // `payload` is always used, so don't need to add `"_"` prefix writeln!( self.out, - "\nprivate static ScriptFunctionCall decode_{}_script_function(TransactionPayload payload) throws IllegalArgumentException, IndexOutOfBoundsException {{", + "\nprivate static ScriptFunctionCall decode_{}_script_function(TransactionPayload payload) throws DeserializationError, IllegalArgumentException, IndexOutOfBoundsException {{", abi.name(), )?; self.out.indent(); @@ -479,13 +485,18 @@ return builder.build();"#, )?; } for (index, arg) in abi.args().iter().enumerate() { - writeln!( - self.out, - "builder.{} = Helpers.decode_{}_argument(script.args.get({}));", - arg.name(), - common::mangle_type(arg.type_tag()), - index, - )?; + let decoding = match Self::bcs_primitive_type_name(arg.type_tag()) { + None => format!( + "{}.bcsDeserialize(script.args.get({}).content())", + Self::quote_type(arg.type_tag()), + index + ), + Some(type_name) => format!( + "new BcsDeserializer(script.args.get({}).content()).deserialize_{}()", + index, type_name + ), + }; + writeln!(self.out, "builder.{} = {};", arg.name(), decoding)?; } writeln!(self.out, "return builder.build();")?; self.out.unindent(); @@ -614,7 +625,7 @@ private static java.util.Map initTransac self.out, r#" interface ScriptFunctionDecodingHelper {{ - public ScriptFunctionCall decode(TransactionPayload payload); + public ScriptFunctionCall decode(TransactionPayload payload) throws DeserializationError; }} private static final java.util.Map SCRIPT_FUNCTION_DECODER_MAP = initDecoderMap(); @@ -639,8 +650,51 @@ private static java.util.Map initDecoderMa writeln!(self.out, "}}") } + fn output_encoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { + let required_types = common::get_required_helper_types(abis); + for required_type in required_types { + self.output_encoding_helper(required_type)?; + } + Ok(()) + } + + fn output_encoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> { + let function_body = match Self::bcs_primitive_type_name(type_tag) { + None => r#" + return Bytes.valueOf(arg.bcsSerialize()); + "# + .into(), + Some(type_name) => { + format!( + r#" + BcsSerializer s = new BcsSerializer(); + s.serialize_{}(arg); + return Bytes.valueOf(s.get_bytes()); + "#, + type_name + ) + } + }; + writeln!( + self.out, + r#" +private static Bytes encode_{}_argument({} arg) {{ + try {{ +{} + }} catch (SerializationError e) {{ + throw new IllegalArgumentException("Unable to serialize argument of type {}"); + }} +}} +"#, + common::mangle_type(type_tag), + Self::quote_type(type_tag), + function_body, + common::mangle_type(type_tag) + ) + } + fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { - let required_types = common::get_required_decoding_helper_types(abis); + let required_types = common::get_required_helper_types(abis); for required_type in required_types { self.output_decoding_helper(required_type)?; } @@ -746,6 +800,13 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ .join(", ") } + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + fn quote_type(type_tag: &TypeTag) -> String { use TypeTag::*; match type_tag { @@ -756,14 +817,22 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ Address => "AccountAddress".into(), Vector(type_tag) => match type_tag.as_ref() { U8 => "Bytes".into(), + Vector(type_tag) if type_tag.as_ref() == &U8 => "VecBytes".into(), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), } } fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "Helpers.encode_{}_argument({})", + common::mangle_type(type_tag), + name + ) + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { Bool => format!("new TransactionArgument.Bool({})", name), @@ -779,6 +848,27 @@ private static {} decode_{}_argument(TransactionArgument arg) {{ Struct(_) | Signer => common::type_not_allowed(type_tag), } } + + // - if a `type_tag` is a primitive type in BCS, we can call + // `.serialize_(arg)` and `.deserialize_(arg)` + // to convert into and from `byte[]`. + // - otherwise, we can use `.bcsSerialize()`, `.bcsDeserialize()` to do the work. + fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> { + use TypeTag::*; + match type_tag { + Bool => Some("bool"), + U8 => Some("u8"), + U64 => Some("u64"), + U128 => Some("u128"), + Address => None, + Vector(type_tag) => match type_tag.as_ref() { + U8 => Some("bytes"), + Vector(type_tag) if type_tag.as_ref() == &U8 => None, + _ => common::type_not_allowed(type_tag), + }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } } pub struct Installer { diff --git a/vm/transaction-builder-generator/src/lib.rs b/vm/transaction-builder-generator/src/lib.rs index 943fc4c62a..e95210da6a 100644 --- a/vm/transaction-builder-generator/src/lib.rs +++ b/vm/transaction-builder-generator/src/lib.rs @@ -8,14 +8,20 @@ use std::{ffi::OsStr, fs, io::Read, path::Path}; /// Support for code-generation in C++17. pub mod cpp; +/// Support for code-generation in C# +pub mod csharp; /// Support for code-generation in Dart. -pub mod dart; +// pub mod dart; +/// Support for code-generation in Go >= 1.13. +pub mod golang; /// Support for code-generation in Java 8. pub mod java; /// Support for code-generation in Python 3. pub mod python3; /// Support for code-generation in Rust. pub mod rust; +/// Support for code-generation in TypeScript. +pub mod typescript; /// Internals shared between languages. mod common; @@ -36,14 +42,16 @@ fn get_abi_paths(dir: &Path) -> std::io::Result> { Ok(abi_paths) } -/// Read all ABI files in a directory. This supports both new and old `ScriptABI`s. -pub fn read_abis(dir_path: &Path) -> anyhow::Result> { +/// Read all ABI files the specified directories. This supports both new and old `ScriptABI`s. +pub fn read_abis(dir_paths: &[impl AsRef]) -> anyhow::Result> { let mut abis = Vec::::new(); - for path in get_abi_paths(dir_path)? { - let mut buffer = Vec::new(); - let mut f = std::fs::File::open(path)?; - f.read_to_end(&mut buffer)?; - abis.push(bcs::from_bytes(&buffer)?); + for dir in dir_paths.iter() { + for path in get_abi_paths(dir.as_ref())? { + let mut buffer = Vec::new(); + let mut f = std::fs::File::open(path)?; + f.read_to_end(&mut buffer)?; + abis.push(bcs::from_bytes(&buffer)?); + } } // Sort scripts by alphabetical order. #[allow(clippy::unnecessary_sort_by)] @@ -97,7 +105,7 @@ pub fn is_supported_abi(abi: &ScriptABI) -> bool { for arg in abi.args() { if let TypeTag::Vector(type_tag) = arg.type_tag() { match type_tag.as_ref() { - TypeTag::U8 => continue, + TypeTag::U8 | TypeTag::Address | TypeTag::Vector(_) | TypeTag::U128 => continue, _ => { eprintln!( "{} function's argument {:?}, the generator do not support, skip it.", diff --git a/vm/transaction-builder-generator/src/python3.rs b/vm/transaction-builder-generator/src/python3.rs index b01aa9be5b..2b8db0cd21 100644 --- a/vm/transaction-builder-generator/src/python3.rs +++ b/vm/transaction-builder-generator/src/python3.rs @@ -54,6 +54,9 @@ pub fn output( emitter .output_transaction_script_decoder_map(common::transaction_script_abis(abis).as_slice())?; emitter.output_script_function_decoder_map(common::script_function_abis(abis).as_slice())?; + + emitter.output_encoding_helpers(abis)?; + emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?; Ok(()) } @@ -72,15 +75,16 @@ where T: Write, { fn output_additional_imports(&mut self) -> Result<()> { - writeln!(self.out, r#"from starcoin import bcs"#)?; + let diem_pkg_root = match &self.diem_package_name { + None => "".into(), + Some(package) => package.clone() + ".", + }; writeln!( self.out, r#" -from {}starcoin_types import (Script, ScriptFunction, TransactionPayload, TransactionPayload__ScriptFunction, Identifier, ModuleId, TypeTag, AccountAddress, TransactionArgument, TransactionArgument__Bool, TransactionArgument__U8, TransactionArgument__U64, TransactionArgument__U128, TransactionArgument__Address, TransactionArgument__U8Vector)"#, - match &self.diem_package_name { - None => "".into(), - Some(package) => package.clone() + ".", - }, +from {}bcs import (deserialize as bcs_deserialize, serialize as bcs_serialize) +from {}diem_types import (Script, ScriptFunction, TransactionPayload, TransactionPayload__ScriptFunction, Identifier, ModuleId, TypeTag, AccountAddress, TransactionArgument, VecBytes, TransactionArgument__Bool, TransactionArgument__U8, TransactionArgument__U64, TransactionArgument__U128, TransactionArgument__Address, TransactionArgument__U8Vector)"#, + diem_pkg_root, diem_pkg_root ) } @@ -228,7 +232,7 @@ def decode_script_function_payload(payload: TransactionPayload) -> ScriptFunctio "#, abi.name().to_shouty_snake_case(), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; self.out.unindent(); Ok(()) @@ -307,10 +311,10 @@ def decode_script_function_payload(payload: TransactionPayload) -> ScriptFunctio for (index, arg) in abi.args().iter().enumerate() { writeln!( self.out, - "{}=bcs.deserialize(script.args[{}],{}),", + "{}=decode_{}_argument(script.args[{}]),", arg.name(), - index, common::mangle_type(arg.type_tag()), + index, )?; } self.out.unindent(); @@ -345,10 +349,10 @@ def decode_script_function_payload(payload: TransactionPayload) -> ScriptFunctio for (index, arg) in abi.args().iter().enumerate() { writeln!( self.out, - "{}=bcs.deserialize(script.args[{}],{}),", + "{}=bcs_deserialize(script.args[{}], {})[0],", arg.name(), index, - common::mangle_type(arg.type_tag()), + Self::quote_type(arg.type_tag()) )?; } self.out.unindent(); @@ -459,6 +463,77 @@ SCRIPT_FUNCTION_ENCODER_MAP: typing.Dict[typing.Type[ScriptFunctionCall], typing writeln!(self.out, "}}\n") } + fn output_encoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { + let required_types = common::get_required_helper_types(abis); + for required_type in required_types { + self.output_encoding_helper(required_type)?; + } + Ok(()) + } + + fn output_encoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> { + let encoding = match Self::bcs_primitive_type_name(type_tag) { + None => "arg.bcs_serialize()".into(), + Some(type_name) => { + format!("bcs_serialize(arg, {})", type_name) + } + }; + writeln!( + self.out, + r#" +def encode_{}_argument(arg: {}) -> bytes: + return {} +"#, + common::mangle_type(type_tag), + Self::quote_type(type_tag), + encoding, + ) + } + + fn output_decoding_helpers(&mut self, abis: &[ScriptABI]) -> Result<()> { + let required_types = common::get_required_helper_types(abis); + for required_type in required_types { + self.output_decoding_helper(required_type)?; + } + Ok(()) + } + + fn output_decoding_helper(&mut self, type_tag: &TypeTag) -> Result<()> { + use TypeTag::*; + let (constructor, expr) = match type_tag { + Bool => ("Bool", "arg.value".into()), + U8 => ("U8", "arg.value".into()), + U64 => ("U64", "arg.value".into()), + U128 => ("U128", "arg.value".into()), + Address => ("Address", "arg.value".into()), + Vector(type_tag) => match type_tag.as_ref() { + U8 => ("U8Vector", "arg.value".into()), + inner_type_tag => ( + "Vector", + format!( + "[decode_{}_argument(x) for x in arg.value]", + common::mangle_type(inner_type_tag) + ), + ), + }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + }; + writeln!( + self.out, + r#" +def decode_{}_argument(arg: TransactionArgument) -> {}: + if not isinstance(arg, TransactionArgument__{}): + raise ValueError("Was expecting a {} argument") + return {} +"#, + common::mangle_type(type_tag), + Self::quote_type(type_tag), + constructor, + constructor, + expr, + ) + } + fn prepare_doc_string(doc: &str) -> String { let doc = crate::common::prepare_doc_string(doc); let s: Vec<_> = doc.splitn(2, |c| c == '.').collect(); @@ -513,6 +588,13 @@ SCRIPT_FUNCTION_ENCODER_MAP: typing.Dict[typing.Type[ScriptFunctionCall], typing .join(", ") } + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + fn quote_type(type_tag: &TypeTag) -> String { use TypeTag::*; match type_tag { @@ -523,29 +605,58 @@ SCRIPT_FUNCTION_ENCODER_MAP: typing.Dict[typing.Type[ScriptFunctionCall], typing Address => "AccountAddress".into(), Vector(type_tag) => match type_tag.as_ref() { U8 => "bytes".into(), + Vector(type_tag) if type_tag.as_ref() == &U8 => "VecBytes".into(), _ => common::type_not_allowed(type_tag), }, - Struct(_) | Signer => common::type_not_allowed(type_tag), } } fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "encode_{}_argument({})", + common::mangle_type(type_tag), + name + ) + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { - Bool => format!("bcs.serialize({}, st.bool)", name), - U8 => format!("bcs.serialize({}, st.uint8)", name), - U64 => format!("bcs.serialize({}, st.uint64)", name), - U128 => format!("bcs.serialize({}, st.uint128)", name), - Address => format!("bcs.serialize({}, starcoin_types.AccountAddress)", name), + Bool => format!("TransactionArgument__Bool(value={})", name), + U8 => format!("TransactionArgument__U8(value={})", name), + U64 => format!("TransactionArgument__U64(value={})", name), + U128 => format!("TransactionArgument__U128(value={})", name), + Address => format!("TransactionArgument__Address(value={})", name), Vector(type_tag) => match type_tag.as_ref() { - U8 => format!("bcs.serialize({}, bytes)", name), + U8 => format!("TransactionArgument__U8Vector(value={})", name), _ => common::type_not_allowed(type_tag), }, Struct(_) | Signer => common::type_not_allowed(type_tag), } } + + // - if a `type_tag` is a primitive type in BCS, we can call + // `bcs_serialize(arg, )` and `bcs_deserialize(arg, )` + // to convert into and from `bytes`. + // - otherwise, we can use `.bcs_serialize()`, `.bcs_deserialize()` to do the work. + fn bcs_primitive_type_name(type_tag: &TypeTag) -> Option<&'static str> { + use TypeTag::*; + match type_tag { + Bool => Some("bool"), + U8 => Some("st.uint8"), + U64 => Some("st.uint64"), + U128 => Some("st.uint128"), + Address => None, + Vector(type_tag) => match type_tag.as_ref() { + U8 => Some("bytes"), + Vector(type_tag) if type_tag.as_ref() == &U8 => None, + _ => common::type_not_allowed(type_tag), + }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } } pub struct Installer { diff --git a/vm/transaction-builder-generator/src/rust.rs b/vm/transaction-builder-generator/src/rust.rs index 86a3cba99f..2fe153c0de 100644 --- a/vm/transaction-builder-generator/src/rust.rs +++ b/vm/transaction-builder-generator/src/rust.rs @@ -25,6 +25,9 @@ use std::{ /// If `local_types` is true, we generate a file suitable for the Diem codebase itself /// rather than using serde-generated, standalone definitions. pub fn output(out: &mut dyn Write, abis: &[ScriptABI], local_types: bool) -> Result<()> { + if abis.is_empty() { + return Ok(()); + } let mut emitter = RustEmitter { out: IndentedWriter::new(out, IndentConfig::Space(4)), local_types, @@ -33,8 +36,15 @@ pub fn output(out: &mut dyn Write, abis: &[ScriptABI], local_types: bool) -> Res emitter.output_preamble()?; emitter.output_script_call_enum_with_imports(abis)?; - emitter.output_transaction_script_impl(&common::transaction_script_abis(abis))?; - emitter.output_script_function_impl(&common::script_function_abis(abis))?; + let tx_script_abis = common::transaction_script_abis(abis); + let script_function_abis = common::script_function_abis(abis); + + if !tx_script_abis.is_empty() { + emitter.output_transaction_script_impl(&tx_script_abis)?; + } + if !script_function_abis.is_empty() { + emitter.output_script_function_impl(&script_function_abis)?; + } for abi in abis { emitter.output_script_encoder_function(abi)?; @@ -44,11 +54,15 @@ pub fn output(out: &mut dyn Write, abis: &[ScriptABI], local_types: bool) -> Res emitter.output_script_decoder_function(abi)?; } - emitter.output_transaction_script_decoder_map(&common::transaction_script_abis(abis))?; - emitter.output_script_function_decoder_map(&common::script_function_abis(abis))?; - emitter.output_decoding_helpers(abis)?; + if !tx_script_abis.is_empty() { + emitter.output_transaction_script_decoder_map(&tx_script_abis)?; + } + if !script_function_abis.is_empty() { + emitter.output_script_function_decoder_map(&script_function_abis)?; + } + emitter.output_decoding_helpers(&common::filter_transaction_scripts(abis))?; - for abi in &common::transaction_script_abis(abis) { + for abi in &tx_script_abis { emitter.output_code_constant(abi)?; } Ok(()) @@ -74,6 +88,7 @@ where self.out.indent(); self.output_transaction_script_encode_method(transaction_script_abis)?; self.output_transaction_script_decode_method()?; + self.output_transaction_script_name_method(transaction_script_abis)?; self.out.unindent(); writeln!(self.out, "\n}}") } @@ -205,7 +220,7 @@ impl ScriptFunctionCall { "starcoin_types::language_storage", vec!["TypeTag", "ModuleId"], ), - ("starcoin_types::identifier", vec!["Identifier"]), + ("starcoin_types::identifier", vec!["ident_str"]), ( "starcoin_types::transaction", vec![ @@ -213,6 +228,7 @@ impl ScriptFunctionCall { "TransactionArgument", "TransactionPayload", "ScriptFunction", + "VecBytes", ], ), ("starcoin_types::account_address", vec!["AccountAddress"]), @@ -226,6 +242,7 @@ impl ScriptFunctionCall { "Script", "ScriptFunction", "TransactionArgument", + "VecBytes", "TransactionPayload", "ModuleId", "Identifier", @@ -347,6 +364,33 @@ pub fn decode(payload: &TransactionPayload) -> Option {{ ) } + fn output_transaction_script_name_method( + &mut self, + abis: &[TransactionScriptABI], + ) -> Result<()> { + writeln!( + self.out, + r#" +/// Return the name of a Diem `Script` from a structured object `ScriptCall`. +pub fn name(&self) -> &'static str {{"# + )?; + self.out.indent(); + writeln!(self.out, "use ScriptCall::*;\nmatch self {{")?; + self.out.indent(); + for abi in abis { + writeln!( + self.out, + "{} {{ .. }} => \"{}\",", + abi.name().to_camel_case(), + abi.name(), + )?; + } + self.out.unindent(); + writeln!(self.out, "}}")?; + self.out.unindent(); + writeln!(self.out, "}}\n") + } + fn output_comment(&mut self, indentation: usize, doc: &str) -> std::io::Result<()> { let prefix = " ".repeat(indentation) + "/// "; let empty_line = "\n".to_string() + &" ".repeat(indentation) + "///\n"; @@ -381,7 +425,7 @@ Script::new( )"#, abi.name().to_shouty_snake_case(), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; } else { writeln!( @@ -394,7 +438,7 @@ Script {{ }}"#, abi.name().to_shouty_snake_case(), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments_for_script(abi.args()), )?; } self.out.unindent(); @@ -428,7 +472,7 @@ TransactionPayload::ScriptFunction(ScriptFunction::new( self.quote_module_id(abi.module_name()), self.quote_identifier(abi.name()), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments(abi.args(), /* local_types */ true), )?; } else { writeln!( @@ -443,7 +487,7 @@ TransactionPayload::ScriptFunction(ScriptFunction {{ self.quote_module_id(abi.module_name()), self.quote_identifier(abi.name()), Self::quote_type_arguments(abi.ty_args()), - Self::quote_arguments(abi.args()), + Self::quote_arguments(abi.args(), /* local_types */ false), )?; } self.out.unindent(); @@ -503,9 +547,8 @@ TransactionPayload::ScriptFunction(ScriptFunction {{ for (index, arg) in abi.args().iter().enumerate() { writeln!( self.out, - "{} : decode_{}_argument(script.args{}.get({})?.clone())?,", + "{} : bcs::from_bytes(script.args{}.get({})?).ok()?,", arg.name(), - common::mangle_type(arg.type_tag()), if self.local_types { "()" } else { "" }, index, )?; @@ -584,7 +627,7 @@ static TRANSACTION_SCRIPT_DECODER_MAP: once_cell::sync::Lazy Result<()> { - let required_types = common::get_required_decoding_helper_types(abis); + let required_types = common::get_required_helper_types(abis); for required_type in required_types { self.output_decoding_helper(required_type)?; } @@ -681,7 +724,7 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ fn quote_identifier(&self, ident: &str) -> String { if self.local_types { - format!("Identifier::new(\"{}\").unwrap()", ident) + format!("ident_str!(\"{}\").to_owned()", ident) } else { format!("Identifier(\"{}\".to_string())", ident) } @@ -753,9 +796,16 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ .join(", ") } - fn quote_arguments(args: &[ArgumentABI]) -> String { + fn quote_arguments(args: &[ArgumentABI], local_types: bool) -> String { args.iter() - .map(|arg| Self::quote_transaction_argument(arg.type_tag(), arg.name())) + .map(|arg| Self::quote_transaction_argument(arg.type_tag(), arg.name(), local_types)) + .collect::>() + .join(", ") + } + + fn quote_arguments_for_script(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument_for_script(arg.type_tag(), arg.name())) .collect::>() .join(", ") } @@ -776,6 +826,7 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ "Bytes".into() } } + Vector(type_tag) if type_tag.as_ref() == &U8 => "VecBytes".into(), _ => common::type_not_allowed(type_tag), }, @@ -783,7 +834,34 @@ fn decode_{}_argument(arg: TransactionArgument) -> Option<{}> {{ } } - fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + fn quote_transaction_argument(type_tag: &TypeTag, name: &str, local_types: bool) -> String { + // NOTE: this check is not necessary as with BCS-encoding, argument of + // any valid Move type is possible, including Struct and Vector of types + // other than U8. However, to be consistent with the restrictions on + // transaction script arguments, we still check the TypeTag here. + use TypeTag::*; + match type_tag { + Bool | U8 | U64 | U128 | Address => {} + Vector(type_tag) => match type_tag.as_ref() { + U8 => {} + Vector(type_tag) => { + if type_tag.as_ref() != &U8 { + common::type_not_allowed(type_tag) + } + } + _ => common::type_not_allowed(type_tag), + }, + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + let conversion = format!("bcs::to_bytes(&{}).unwrap()", name); + if local_types { + conversion + } else { + format!("serde_bytes::ByteBuf::from({})", conversion) + } + } + + fn quote_transaction_argument_for_script(type_tag: &TypeTag, name: &str) -> String { use TypeTag::*; match type_tag { Bool => format!("TransactionArgument::Bool({})", name), diff --git a/vm/transaction-builder-generator/tests/generation.rs b/vm/transaction-builder-generator/tests/generation.rs index e53767533c..e8d841351d 100644 --- a/vm/transaction-builder-generator/tests/generation.rs +++ b/vm/transaction-builder-generator/tests/generation.rs @@ -18,7 +18,7 @@ fn get_starcoin_registry() -> Registry { fn get_stdlib_script_abis() -> Vec { let path = Path::new("../stdlib/compiled/latest/transaction_scripts/abi"); - buildgen::read_abis(path) + buildgen::read_abis(&[path]) .expect("reading ABI files should not fail") .into_iter() .filter(is_supported_abi) @@ -287,3 +287,66 @@ fn test_that_java_code_compiles_and_demo_runs() { assert!(output.status.success()); assert_eq!(std::str::from_utf8(&output.stdout).unwrap(), OUTPUT); } + +#[ignore] +#[test] +fn test_that_golang_code_compiles_and_demo_runs() { + let registry = get_starcoin_registry(); + let abis = get_stdlib_script_abis(); + let dir = tempdir().unwrap(); + + let config = serdegen::CodeGeneratorConfig::new("diemtypes".to_string()) + .with_encodings(vec![serdegen::Encoding::Bcs]); + let bcs_installer = serdegen::golang::Installer::new( + dir.path().to_path_buf(), + /* default Serde module */ None, + ); + bcs_installer.install_module(&config, ®istry).unwrap(); + + let abi_installer = buildgen::golang::Installer::new( + dir.path().to_path_buf(), + /* default Serde module */ None, + Some("testing".to_string()), + ); + abi_installer + .install_transaction_builders("diemstdlib", &abis) + .unwrap(); + + std::fs::copy( + "examples/golang/stdlib_demo.go", + dir.path().join("stdlib_demo.go"), + ) + .unwrap(); + + let status = Command::new("go") + .current_dir(dir.path()) + .arg("mod") + .arg("init") + .arg("testing") + .status() + .unwrap(); + assert!(status.success()); + + let status = Command::new("go") + .current_dir(dir.path()) + .arg("mod") + .arg("edit") + .arg("-replace") + .arg(format!("testing={}", dir.path().to_string_lossy(),)) + .status() + .unwrap(); + assert!(status.success()); + + let output = Command::new("go") + .current_dir(dir.path()) + .arg("run") + .arg(dir.path().join("stdlib_demo.go")) + .output() + .unwrap(); + eprintln!("{}", std::str::from_utf8(&output.stderr).unwrap()); + assert_eq!( + std::str::from_utf8(&output.stdout).unwrap(), + EXPECTED_OUTPUT + ); + assert!(output.status.success()); +} From 7e8531dbbd82c1cf29570383285c576da5351bc4 Mon Sep 17 00:00:00 2001 From: fountainchen Date: Tue, 2 Nov 2021 11:05:55 +0800 Subject: [PATCH 2/2] [gen]add example code of java and golang --- .../examples/golang/stdlib_demo.go | 87 ++ .../examples/java/StdlibDemo.java | 56 ++ .../java/custom_diem_code/AccountAddress.java | 24 + .../src/typescript.rs | 773 ++++++++++++++++++ 4 files changed, 940 insertions(+) create mode 100644 vm/transaction-builder-generator/examples/golang/stdlib_demo.go create mode 100644 vm/transaction-builder-generator/examples/java/StdlibDemo.java create mode 100644 vm/transaction-builder-generator/examples/java/custom_diem_code/AccountAddress.java create mode 100644 vm/transaction-builder-generator/src/typescript.rs diff --git a/vm/transaction-builder-generator/examples/golang/stdlib_demo.go b/vm/transaction-builder-generator/examples/golang/stdlib_demo.go new file mode 100644 index 0000000000..3b3ce114c5 --- /dev/null +++ b/vm/transaction-builder-generator/examples/golang/stdlib_demo.go @@ -0,0 +1,87 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +package main + +import ( + "fmt" + stdlib "testing/diemstdlib" + diem "testing/diemtypes" +) + +func demo_p2p_script() { + token := &diem.TypeTag__Struct{ + Value: diem.StructTag{ + Address: diem.AccountAddress( + [16]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + ), + Module: diem.Identifier("XDX"), + Name: diem.Identifier("XDX"), + TypeParams: []diem.TypeTag{}, + }, + } + payee := diem.AccountAddress( + [16]uint8{0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, + ) + amount := uint64(1_234_567) + script := stdlib.EncodePeerToPeerWithMetadataScript(token, payee, amount, []uint8{}, []uint8{}) + + call, err := stdlib.DecodeScript(&script) + if err != nil { + panic(fmt.Sprintf("failed to decode script: %v", err)) + } + payment := call.(*stdlib.ScriptCall__PeerToPeerWithMetadata) + if payment.Amount != amount || payment.Payee != payee { + panic("wrong script content") + } + + bytes, err := script.BcsSerialize() + if err != nil { + panic("failed to serialize") + } + for _, b := range bytes { + fmt.Printf("%d ", b) + } + fmt.Printf("\n") +} + +func demo_p2p_script_function() { + token := &diem.TypeTag__Struct{ + Value: diem.StructTag{ + Address: diem.AccountAddress( + [16]uint8{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + ), + Module: diem.Identifier("XDX"), + Name: diem.Identifier("XDX"), + TypeParams: []diem.TypeTag{}, + }, + } + payee := diem.AccountAddress( + [16]uint8{0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}, + ) + amount := uint64(1_234_567) + payload := stdlib.EncodePeerToPeerWithMetadataScriptFunction(token, payee, amount, []uint8{}, []uint8{}) + + call, err := stdlib.DecodeScriptFunctionPayload(payload) + if err != nil { + panic(fmt.Sprintf("failed to decode script function: %v", err)) + } + payment := call.(*stdlib.ScriptFunctionCall__PeerToPeerWithMetadata) + if payment.Amount != amount || payment.Payee != payee { + panic("wrong script content") + } + + bytes, err := payload.BcsSerialize() + if err != nil { + panic("failed to serialize") + } + for _, b := range bytes { + fmt.Printf("%d ", b) + } + fmt.Printf("\n") +} + +func main() { + demo_p2p_script() + demo_p2p_script_function() +} diff --git a/vm/transaction-builder-generator/examples/java/StdlibDemo.java b/vm/transaction-builder-generator/examples/java/StdlibDemo.java new file mode 100644 index 0000000000..c7af946568 --- /dev/null +++ b/vm/transaction-builder-generator/examples/java/StdlibDemo.java @@ -0,0 +1,56 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +import java.util.Arrays; +import java.util.ArrayList; + +import com.facebook.serde.Bytes; +import com.facebook.serde.Serializer; +import com.facebook.serde.Unsigned; // used as documentation. +import com.facebook.lcs.LcsSerializer; +import org.starcoin.stdlib.Stdlib; +import org.starcoin.types.AccountAddress; +import org.starcoin.types.Identifier; +import org.starcoin.types.Script; +import org.starcoin.types.StructTag; +import org.starcoin.types.TypeTag; + +public class StdlibDemo { + + static AccountAddress make_address(byte[] values) { + assert values.length == 16; + Byte[] address = new Byte[16]; + for (int i = 0; i < 16; i++) { + address[i] = Byte.valueOf(values[i]); + } + return new AccountAddress(address); + } + + public static void main(String[] args) throws Exception { + StructTag.Builder builder = new StructTag.Builder(); + builder.address = make_address(new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); + builder.module = new Identifier("LBR"); + builder.name = new Identifier("LBR"); + builder.type_params = new ArrayList(); + StructTag tag = builder.build(); + + TypeTag token = new TypeTag.Struct(tag); + + AccountAddress payee = make_address( + new byte[]{0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22}); + + @Unsigned Long amount = Long.valueOf(1234567); + Script script = + Stdlib.encode_peer_to_peer_with_metadata_script(token, payee, new Bytes(new byte[]{}),java.math.BigInteger.valueOf(amount), new Bytes(new byte[]{})); + + Serializer serializer = new LcsSerializer(); + script.serialize(serializer); + byte[] output = serializer.get_bytes(); + + for (byte o : output) { + System.out.print(((int) o & 0xFF) + " "); + }; + System.out.println(); + } + +} diff --git a/vm/transaction-builder-generator/examples/java/custom_diem_code/AccountAddress.java b/vm/transaction-builder-generator/examples/java/custom_diem_code/AccountAddress.java new file mode 100644 index 0000000000..a8f0a35fb9 --- /dev/null +++ b/vm/transaction-builder-generator/examples/java/custom_diem_code/AccountAddress.java @@ -0,0 +1,24 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +static final int LENGTH = 16; + +public static AccountAddress valueOf(byte[] values) { + if (values.length != LENGTH) { + throw new java.lang.IllegalArgumentException("Invalid length for AccountAddress"); + } + java.util.List address = new java.util.ArrayList(LENGTH); + for (int i = 0; i < LENGTH; i++) { + address.add(Byte.valueOf(values[i])); + } + return new AccountAddress(address); +} + +public byte[] toBytes() { + byte[] bytes = new byte[LENGTH]; + int i = 0; + for (Byte item : value) { + bytes[i++] = item.byteValue(); + } + return bytes; +} diff --git a/vm/transaction-builder-generator/src/typescript.rs b/vm/transaction-builder-generator/src/typescript.rs new file mode 100644 index 0000000000..b1e861d75c --- /dev/null +++ b/vm/transaction-builder-generator/src/typescript.rs @@ -0,0 +1,773 @@ +// Copyright (c) The Diem Core Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::common; +use move_core_types::{ + account_address::AccountAddress, + language_storage::{ModuleId, TypeTag}, +}; +use serde_generate::{ + indent::{IndentConfig, IndentedWriter}, + typescript, CodeGeneratorConfig, +}; +use serde_reflection::ContainerFormat; +use starcoin_vm_types::transaction::{ + ArgumentABI, ScriptABI, ScriptFunctionABI, TransactionScriptABI, TypeArgumentABI, +}; + +use heck::{CamelCase, MixedCase, ShoutySnakeCase}; +use std::{ + collections::BTreeMap, + io::{Result, Write}, + path::PathBuf, +}; +/// Output transaction builders and decoders in TypeScript for the given ABIs. +pub fn output(out: &mut dyn Write, abis: &[ScriptABI]) -> Result<()> { + write_script_calls(out, abis)?; + write_helpers(out, abis) +} + +fn write_stdlib_helper_interfaces(emitter: &mut TypeScriptEmitter<&mut dyn Write>) -> Result<()> { + writeln!( + emitter.out, + r#" +export interface TypeTagDef {{ + type: Types; + arrayType?: TypeTagDef; + name?: string; + moduleName?: string; + address?: string; + typeParams?: TypeTagDef[]; +}} + +export interface ArgDef {{ + readonly name: string; + readonly type: TypeTagDef; + readonly choices?: string[]; + readonly mandatory?: boolean; +}} + +export interface ScriptDef {{ + readonly stdlibEncodeFunction: (...args: any[]) => DiemTypes.Script; + readonly stdlibDecodeFunction: (script: DiemTypes.Script) => ScriptCall; + readonly codeName: string; + readonly description: string; + readonly typeArgs: string[]; + readonly args: ArgDef[]; +}} + +export interface ScriptFunctionDef {{ + readonly stdlibEncodeFunction: (...args: any[]) => DiemTypes.TransactionPayload; + readonly description: string; + readonly typeArgs: string[]; + readonly args: ArgDef[]; +}} + +export enum Types {{ + Boolean, + U8, + U64, + U128, + Address, + Array, + Struct +}} +"# + )?; + + Ok(()) +} + +/// Output transaction helper functions for the given ABIs. +fn write_helpers(out: &mut dyn Write, abis: &[ScriptABI]) -> Result<()> { + let mut emitter = TypeScriptEmitter { + out: IndentedWriter::new(out, IndentConfig::Space(2)), + }; + let txn_script_abis = common::transaction_script_abis(abis); + let script_fun_abis = common::script_function_abis(abis); + emitter.output_preamble()?; + write_stdlib_helper_interfaces(&mut emitter)?; + writeln!(emitter.out, "\nexport class Stdlib {{")?; + emitter.out.indent(); + writeln!(emitter.out, "private static fromHexString(hexString: string): Uint8Array {{ return new Uint8Array(hexString.match(/.{{1,2}}/g)!.map((byte) => parseInt(byte, 16)));}}")?; + + for abi in &txn_script_abis { + emitter.output_script_encoder_function(abi)?; + } + for abi in &txn_script_abis { + emitter.output_script_decoder_function(abi)?; + } + for abi in &script_fun_abis { + emitter.output_script_function_encoder_function(abi)?; + } + + for abi in &script_fun_abis { + emitter.output_script_function_decoder_function(abi)?; + } + + for abi in &txn_script_abis { + emitter.output_code_constant(abi)?; + } + writeln!( + emitter.out, + "\nstatic ScriptArgs: {{[name: string]: ScriptDef}} = {{" + )?; + emitter.out.indent(); + for abi in &txn_script_abis { + emitter.output_script_args_definition(abi)?; + } + emitter.out.unindent(); + writeln!(emitter.out, "}}")?; + + writeln!( + emitter.out, + "\nstatic ScriptFunctionArgs: {{[name: string]: ScriptFunctionDef}} = {{" + )?; + emitter.out.indent(); + for abi in &script_fun_abis { + emitter.output_script_fun_args_definition(abi)?; + } + emitter.out.unindent(); + writeln!(emitter.out, "}}")?; + + emitter.out.unindent(); + writeln!(emitter.out, "\n}}\n")?; + + writeln!(emitter.out, "\nexport type ScriptDecoders = {{")?; + emitter.out.indent(); + writeln!(emitter.out, "User: {{")?; + emitter.out.indent(); + for abi in &txn_script_abis { + emitter.output_script_args_callbacks(abi)?; + } + writeln!( + emitter.out, + "default: (type: keyof ScriptDecoders['User']) => void;" + )?; + emitter.out.unindent(); + writeln!(emitter.out, "}};")?; + emitter.out.unindent(); + writeln!(emitter.out, "}};") +} + +fn write_script_calls(out: &mut dyn Write, abis: &[ScriptABI]) -> Result<()> { + let txn_script_abis = common::transaction_script_abis(abis); + let script_fun_abis = common::script_function_abis(abis); + let external_definitions = crate::common::get_external_definitions("diemTypes"); + let script_registry: BTreeMap<_, _> = vec![ + ( + "ScriptCall".to_string(), + common::make_abi_enum_container( + txn_script_abis + .iter() + .cloned() + .map(ScriptABI::TransactionScript) + .collect::>() + .as_slice(), + ), + ), + ( + "ScriptFunctionCall".to_string(), + common::make_abi_enum_container( + script_fun_abis + .iter() + .cloned() + .map(ScriptABI::ScriptFunction) + .collect::>() + .as_slice(), + ), + ), + ] + .into_iter() + .collect(); + let mut comments: BTreeMap<_, _> = txn_script_abis + .iter() + .map(|abi| { + let paths = vec!["ScriptCall".to_string(), abi.name().to_camel_case()]; + (paths, crate::common::prepare_doc_string(abi.doc())) + }) + .chain(script_fun_abis.iter().map(|abi| { + let paths = vec!["ScriptFunctionCall".to_string(), abi.name().to_camel_case()]; + (paths, crate::common::prepare_doc_string(abi.doc())) + })) + .collect(); + comments.insert( + vec!["ScriptCall".to_string()], + "Structured representation of a call into a known Move script.".into(), + ); + comments.insert( + vec!["ScriptFunctionCall".to_string()], + "Structured representation of a call into a known Move script function.".into(), + ); + + let config = CodeGeneratorConfig::new("StdLib".to_string()) + .with_comments(comments) + .with_external_definitions(external_definitions) + .with_serialization(false); + typescript::CodeGenerator::new(&config) + .output(out, &script_registry) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, format!("{}", err)))?; + Ok(()) +} + +/// Shared state for the TypeScript code generator. +struct TypeScriptEmitter { + /// Writer. + out: IndentedWriter, +} + +impl TypeScriptEmitter +where + T: Write, +{ + fn output_preamble(&mut self) -> Result<()> { + Ok(()) + } + + fn output_script_function_encoder_function(&mut self, abi: &ScriptFunctionABI) -> Result<()> { + writeln!( + self.out, + "\n{}static encode{}ScriptFunction({}): DiemTypes.TransactionPayload {{", + Self::quote_doc(abi.doc()), + abi.name().to_camel_case(), + [ + Self::quote_type_parameters(abi.ty_args()), + Self::quote_parameters(abi.args()), + ] + .concat() + .join(", ") + )?; + self.out.indent(); + writeln!( + self.out, + r#"const tyArgs: Seq = [{}]; +{}const args: Seq = [{}]; +const module_id: DiemTypes.ModuleId = {}; +const function_name: DiemTypes.Identifier = {}; +const script = new DiemTypes.ScriptFunction(module_id, function_name, tyArgs, args); +return new DiemTypes.TransactionPayloadVariantScriptFunction(script);"#, + Self::quote_type_arguments(abi.ty_args()), + Self::quote_serialize_arguments(abi.args()), + abi.args() + .iter() + .map(|arg| format!("{}_serialized", arg.name())) + .collect::>() + .join(", "), + Self::quote_module_id(abi.module_name()), + Self::quote_identifier(abi.name()), + )?; + self.out.unindent(); + writeln!(self.out, "}}") + } + + fn quote_serialize_arguments(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_serialize_transaction_argument(arg.type_tag(), arg.name())) + .collect::>() + .join("") + } + + fn quote_module_id(module_id: &ModuleId) -> String { + format!( + "new DiemTypes.ModuleId({}, {})", + Self::quote_address(module_id.address()), + Self::quote_identifier(module_id.name().as_str()) + ) + } + + fn quote_address(address: &AccountAddress) -> String { + format!( + "new DiemTypes.AccountAddress([{}])", + address + .to_vec() + .iter() + .map(|x| format!("[{}]", x)) + .collect::>() + .join(", ") + ) + } + + fn quote_identifier(ident: &str) -> String { + format!("new DiemTypes.Identifier(\"{}\")", ident) + } + + fn output_script_encoder_function(&mut self, abi: &TransactionScriptABI) -> Result<()> { + writeln!( + self.out, + "\n{}static encode{}Script({}): DiemTypes.Script {{", + Self::quote_doc(abi.doc()), + abi.name().to_camel_case(), + [ + Self::quote_type_parameters(abi.ty_args()), + Self::quote_parameters(abi.args()), + ] + .concat() + .join(", ") + )?; + self.out.indent(); + writeln!( + self.out, + r#"const code = Stdlib.{}_CODE; +const tyArgs: Seq = [{}]; +const args: Seq = [{}]; +return new DiemTypes.Script(code, tyArgs, args);"#, + abi.name().to_shouty_snake_case(), + Self::quote_type_arguments(abi.ty_args()), + Self::quote_arguments(abi.args()), + )?; + self.out.unindent(); + writeln!(self.out, "}}") + } + + fn output_script_function_decoder_function(&mut self, abi: &ScriptFunctionABI) -> Result<()> { + let arg_name = format!( + "{}script_fun", + if abi.ty_args().is_empty() && abi.args().is_empty() { + "_" + } else { + "" + } + ); + writeln!( + self.out, + "\nstatic decode{}ScriptFunction({}: DiemTypes.TransactionPayload): ScriptFunctionCallVariant{0} {{", + abi.name().to_camel_case(), + // prevent warning "unused variable" + arg_name, + )?; + + writeln!( + self.out, + "if ({} instanceof DiemTypes.TransactionPayloadVariantScriptFunction) {{", + arg_name + )?; + self.out.indent(); + + let mut all_args: Vec = Vec::new(); + all_args.extend( + abi.ty_args() + .iter() + .enumerate() + .map(|(idx, _)| format!("script_fun.value.ty_args[{}]", idx)) + .collect::>(), + ); + self.out.indent(); + for (idx, arg) in abi.args().iter().enumerate() { + writeln!( + self.out, + "{}", + Self::quote_deserialize_transaction_argument( + arg.type_tag(), + arg.name(), + &format!("script_fun.value.args[{}]", idx) + ) + )?; + all_args.push(arg.name().to_string()) + } + writeln!( + self.out, + "return new ScriptFunctionCallVariant{}(", + abi.name().to_camel_case() + )?; + self.out.indent(); + writeln!(self.out, "{}", all_args.join(",\n"))?; + self.out.unindent(); + writeln!(self.out, ");",)?; + self.out.unindent(); + + writeln!(self.out, "}} else {{")?; + self.out.indent(); + writeln!( + self.out, + "throw new Error(\"Transaction payload not a script function payload\")" + )?; + self.out.unindent(); + writeln!(self.out, "}}")?; + + self.out.unindent(); + writeln!(self.out, "}}")?; + Ok(()) + } + + fn output_script_decoder_function(&mut self, abi: &TransactionScriptABI) -> Result<()> { + writeln!( + self.out, + "\nstatic decode{}Script({}script: DiemTypes.Script): ScriptCallVariant{0} {{", + abi.name().to_camel_case(), + // prevent warning "unused variable" + if abi.ty_args().is_empty() && abi.args().is_empty() { + "_" + } else { + "" + } + )?; + let mut all_args: Vec = Vec::new(); + all_args.extend( + abi.ty_args() + .iter() + .enumerate() + .map(|(idx, _)| format!("script.ty_args[{}]", idx)) + .collect::>(), + ); + all_args.extend( + abi.args() + .iter() + .enumerate() + .map(|(idx, arg)| { + format!( + "(script.args[{}] as {}).value", + idx, + Self::quote_transaction_argument_type(arg.type_tag()) + ) + }) + .collect::>(), + ); + self.out.indent(); + writeln!( + self.out, + "return new ScriptCallVariant{}(", + abi.name().to_camel_case() + )?; + self.out.indent(); + writeln!(self.out, "{}", all_args.join(",\n"))?; + self.out.unindent(); + writeln!(self.out, ");",)?; + self.out.unindent(); + writeln!(self.out, "}}")?; + Ok(()) + } + + fn output_code_constant(&mut self, abi: &TransactionScriptABI) -> Result<()> { + writeln!( + self.out, + "\nstatic {}_CODE = Stdlib.fromHexString('{}');", + abi.name().to_shouty_snake_case(), + abi.code() + .iter() + .map(|x| format!("{:02x}", *x as i8)) + .collect::>() + .join("") + )?; + Ok(()) + } + + fn output_script_fun_args_definition(&mut self, abi: &ScriptFunctionABI) -> Result<()> { + writeln!( + self.out, + r#" + {0}: {{ + stdlibEncodeFunction: Stdlib.encode{0}ScriptFunction, + description: "{1}", + typeArgs: [{2}], + args: [ + {3} + ] +}}, + "#, + abi.name().to_camel_case(), + abi.doc().replace("\"", "\\\"").replace("\n", "\" + \n \""), + abi.ty_args() + .iter() + .map(|ty_arg| format!("\"{}\"", ty_arg.name())) + .collect::>() + .join(", "), + abi.args() + .iter() + .map(|arg| format!( + "{{name: \"{}\", type: {}}}", + arg.name(), + Self::quote_script_arg_type(arg.type_tag()) + )) + .collect::>() + .join(", ") + )?; + Ok(()) + } + + fn output_script_args_definition(&mut self, abi: &TransactionScriptABI) -> Result<()> { + writeln!(self.out, "{}: {{", abi.name().to_camel_case())?; + writeln!( + self.out, + " stdlibEncodeFunction: Stdlib.encode{}Script,", + abi.name().to_camel_case() + )?; + writeln!( + self.out, + " stdlibDecodeFunction: Stdlib.decode{}Script,", + abi.name().to_camel_case() + )?; + writeln!( + self.out, + " codeName: '{}',", + abi.name().to_shouty_snake_case() + )?; + writeln!( + self.out, + " description: \"{}\",", + abi.doc().replace("\"", "\\\"").replace("\n", "\" + \n \"") + )?; + writeln!( + self.out, + " typeArgs: [{}],", + abi.ty_args() + .iter() + .map(|ty_arg| format!("\"{}\"", ty_arg.name())) + .collect::>() + .join(", ") + )?; + writeln!(self.out, " args: [")?; + writeln!( + self.out, + "{}", + abi.args() + .iter() + .map(|arg| format!( + "{{name: \"{}\", type: {}}}", + arg.name(), + Self::quote_script_arg_type(arg.type_tag()) + )) + .collect::>() + .join(", ") + )?; + writeln!(self.out, " ]")?; + writeln!(self.out, "}},")?; + Ok(()) + } + + fn output_script_args_callbacks(&mut self, abi: &TransactionScriptABI) -> Result<()> { + let mut args_with_types = abi + .ty_args() + .iter() + .map(|ty_arg| { + format!( + "{}: DiemTypes.TypeTagVariantStruct", + ty_arg.name().to_mixed_case() + ) + }) + .collect::>(); + args_with_types.extend( + abi.args() + .iter() + .map(|arg| { + format!( + "{}: {}", + arg.name().to_mixed_case(), + Self::quote_transaction_argument_type(arg.type_tag()) + ) + }) + .collect::>(), + ); + writeln!( + self.out, + "{}: (type: string, {}) => void;", + abi.name().to_camel_case(), + args_with_types.join(", ") + )?; + Ok(()) + } + + fn quote_doc(doc: &str) -> String { + let doc = crate::common::prepare_doc_string(doc); + let text = textwrap::indent(&doc, " * ").replace("\n\n", "\n *\n"); + format!("/**\n{}\n */\n", text) + } + + fn quote_type_parameters(ty_args: &[TypeArgumentABI]) -> Vec { + ty_args + .iter() + .map(|ty_arg| format!("{}: DiemTypes.TypeTag", ty_arg.name())) + .collect() + } + + fn quote_parameters(args: &[ArgumentABI]) -> Vec { + args.iter() + .map(|arg| format!("{}: {}", arg.name(), Self::quote_type(arg.type_tag()))) + .collect() + } + + fn quote_type_arguments(ty_args: &[TypeArgumentABI]) -> String { + ty_args + .iter() + .map(|ty_arg| ty_arg.name().to_string()) + .collect::>() + .join(", ") + } + + fn quote_arguments(args: &[ArgumentABI]) -> String { + args.iter() + .map(|arg| Self::quote_transaction_argument(arg.type_tag(), arg.name())) + .collect::>() + .join(", ") + } + + fn quote_type(type_tag: &TypeTag) -> String { + use TypeTag::*; + match type_tag { + Bool => "boolean".into(), + U8 => "number".into(), + U64 => "bigint".into(), + U128 => "bigint".into(), + Address => "DiemTypes.AccountAddress".into(), + Vector(type_tag) => match type_tag.as_ref() { + U8 => "Uint8Array".into(), + _ => common::type_not_allowed(type_tag), + }, + + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + + fn quote_transaction_argument_type(type_tag: &TypeTag) -> String { + use TypeTag::*; + match type_tag { + Bool => "DiemTypes.TransactionArgumentVariantBool".to_string(), + U8 => "DiemTypes.TransactionArgumentVariantU8".to_string(), + U64 => "DiemTypes.TransactionArgumentVariantU64".to_string(), + U128 => "DiemTypes.TransactionArgumentVariantU128".to_string(), + Address => "DiemTypes.TransactionArgumentVariantAddress".to_string(), + Vector(type_tag) => match type_tag.as_ref() { + U8 => "DiemTypes.TransactionArgumentVariantU8Vector".to_string(), + _ => common::type_not_allowed(type_tag), + }, + + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + + fn quote_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "new {}({})", + Self::quote_transaction_argument_type(type_tag), + name + ) + } + + fn quote_deserialize_transaction_argument_type(type_tag: &TypeTag, ser_name: &str) -> String { + use TypeTag::*; + match type_tag { + Bool => format!("{}.deserializeBool()", ser_name), + U8 => format!("{}.deserializeU8()", ser_name), + U64 => format!("{}.deserializeU64()", ser_name), + U128 => format!("{}.deserializeU128()", ser_name), + Address => format!("DiemTypes.AccountAddress.deserialize({})", ser_name), + Vector(type_tag) => match type_tag.as_ref() { + U8 => format!("{}.deserializeBytes()", ser_name), + // TODO: support vec> + _ => common::type_not_allowed(type_tag), + }, + + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + + fn quote_deserialize_transaction_argument( + type_tag: &TypeTag, + name: &str, + data_access: &str, + ) -> String { + format!( + "var deserializer = new BcsDeserializer({});\n\ + const {}: {} = {};\n", + data_access, + name, + Self::quote_type(type_tag), + Self::quote_deserialize_transaction_argument_type(type_tag, "deserializer"), + ) + } + + fn quote_serialize_transaction_argument_type( + type_tag: &TypeTag, + ser_name: &str, + arg_name: &str, + ) -> String { + use TypeTag::*; + match type_tag { + Bool => format!("{}.serializeBool({})", ser_name, arg_name), + U8 => format!("{}.serializeU8({})", ser_name, arg_name), + U64 => format!("{}.serializeU64({})", ser_name, arg_name), + U128 => format!("{}.serializeU128({})", ser_name, arg_name), + Address => format!("{}.serialize({})", arg_name, ser_name), + Vector(type_tag) => match type_tag.as_ref() { + U8 => format!("{}.serializeBytes({})", ser_name, arg_name), + // TODO: support vec> + _ => common::type_not_allowed(type_tag), + }, + + Struct(_) | Signer => common::type_not_allowed(type_tag), + } + } + + fn quote_serialize_transaction_argument(type_tag: &TypeTag, name: &str) -> String { + format!( + "var serializer = new BcsSerializer();\n\ + {};\n\ + const {1}_serialized: bytes = serializer.getBytes();\n", + Self::quote_serialize_transaction_argument_type(type_tag, "serializer", name), + name + ) + } + + fn quote_script_arg_type(type_tag: &TypeTag) -> String { + use TypeTag::*; + match type_tag { + Bool => "{type: Types.Boolean}".to_string(), + U8 => "{type: Types.U8}".to_string(), + U64 => "{type: Types.U64}".to_string(), + U128 => "{type: Types.U128}".to_string(), + Address => "{type: Types.Address}".to_string(), + Vector(type_tag) => format!("{{type: Types.Array, arrayType: {}}}", Self::quote_script_arg_type(type_tag)), + Struct(struct_tag) => format!("{{type: Types.Struct, name: \"{}\", moduleName: \"{}\", address: \"{}\", typeParams: [{}]}}", + struct_tag.name, + struct_tag.module, + struct_tag.address, + struct_tag.type_params.iter().map(|tt| Self::quote_script_arg_type(tt)).collect::>().join(", ")), + Signer => common::type_not_allowed(type_tag), + } + } +} + +pub struct Installer { + install_dir: PathBuf, +} + +impl Installer { + pub fn new(install_dir: PathBuf) -> Self { + Installer { install_dir } + } +} + +impl crate::SourceInstaller for Installer { + type Error = Box; + + fn install_transaction_builders( + &self, + name: &str, + abis: &[ScriptABI], + ) -> std::result::Result<(), Self::Error> { + let dir_path = self.install_dir.join(name); + std::fs::create_dir_all(&dir_path)?; + let mut file = std::fs::File::create(dir_path.join("mod.ts"))?; + output(&mut file, abis)?; + Ok(()) + } +} + +/// Walks through the registry replacing variables known to be named as a +/// javascript keyword, making the resulting codegen invalid. +/// ie: public function: Identifier => public function_name: Identifier +pub fn replace_keywords(registry: &mut BTreeMap) { + swap_keyworded_fields(registry.get_mut("StructTag")); + swap_keyworded_fields(registry.get_mut("ScriptFunction")); +} + +fn swap_keyworded_fields(fields: Option<&mut ContainerFormat>) { + if let Some(ContainerFormat::Struct(fields)) = fields { + for entry in fields.iter_mut() { + match entry.name.as_str() { + "module" => entry.name = String::from("module_name"), + "function" => entry.name = String::from("function_name"), + _ => {} + } + } + } +}