diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt index 135547a4b09..0b0b62d0834 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/CoinAddressDerivationTests.kt @@ -119,7 +119,7 @@ class CoinAddressDerivationTests { NERVOS -> assertEquals("ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3", address) EVERSCALE -> assertEquals("0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04", address) TON -> assertEquals("EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9", address) - APTOS -> assertEquals("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) + APTOS -> assertEquals("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", address) NEBL -> assertEquals("NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7", address) SUI -> assertEquals("0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2", address) HEDERA -> assertEquals("0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5", address) diff --git a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt index 351e7d0aa8e..1cbfd41590e 100644 --- a/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt +++ b/kotlin/wallet-core-kotlin/src/commonTest/kotlin/com/trustwallet/core/test/CoinAddressDerivationTests.kt @@ -115,7 +115,7 @@ class CoinAddressDerivationTests { Nervos -> "ckb1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqdtyq04tvp02wectaumxn0664yw2jd53lqk4mxg3" Everscale -> "0:0c39661089f86ec5926ea7d4ee4223d634ba4ed6dcc2e80c7b6a8e6d59f79b04" TON -> "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9" - Aptos -> "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + Aptos -> "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" Nebl -> "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7" Sui -> "0xada112cfb90b44ba889cc5d39ac2bf46281e4a91f7919c693bcd9b8323e81ed2" Hedera -> "0.0.302a300506032b657003210049eba62f64d0d941045595d9433e65d84ecc46bcdb1421de55e05fcf2d8357d5" diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 035cd54ddad..c6fdc8c55a6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arbitrary" @@ -136,9 +136,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bcs" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b06b4c1f053002b70e7084ac944c77d58d5d92b2110dbc5e852735e00ad3ccc" +checksum = "85b6598a2f5d564fb7855dc6b06fd1c38cff5a72bd8b863a4d021938497b440a" dependencies = [ "serde", "thiserror", @@ -196,16 +196,28 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7774144344a4faa177370406a7ff5f1da24303817368584c6206c8303eb07848" +dependencies = [ + "funty 1.1.0", + "radium 0.6.2", + "tap", + "wyz 0.2.0", +] + [[package]] name = "bitvec" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "funty", - "radium", + "funty 2.0.0", + "radium 0.7.0", "tap", - "wyz", + "wyz 0.5.1", ] [[package]] @@ -521,6 +533,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +[[package]] +name = "ethnum" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8ff382b2fa527fb7fb06eeebfc5bbb3f17e3cc6b9d70b006c41daa8824adac" + [[package]] name = "ff" version = "0.13.0" @@ -531,6 +549,18 @@ dependencies = [ "subtle", ] +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + [[package]] name = "fixed-hash" version = "0.8.0" @@ -544,6 +574,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "funty" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" + [[package]] name = "funty" version = "2.0.0" @@ -665,13 +701,31 @@ dependencies = [ "quick-error", ] +[[package]] +name = "impl-codec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161ebdfec3c8e3b52bf61c4f3550a1eea4f9579d10dc1b936f3171ebdcd6c443" +dependencies = [ + "parity-scale-codec 2.3.1", +] + [[package]] name = "impl-codec" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec", + "parity-scale-codec 3.5.0", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", ] [[package]] @@ -778,16 +832,20 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "move-core-types" version = "0.0.4" -source = "git+https://github.com/move-language/move?rev=f7137eabc2046f76fdad3ded2c51e03a3b1fbd01#f7137eabc2046f76fdad3ded2c51e03a3b1fbd01" +source = "git+https://github.com/move-language/move?rev=ea70797099baea64f05194a918cebd69ed02b285#ea70797099baea64f05194a918cebd69ed02b285" dependencies = [ "anyhow", "bcs", + "ethnum", "hex", + "num", "once_cell", + "primitive-types 0.10.1", "rand", "ref-cast", "serde", "serde_bytes", + "uint", ] [[package]] @@ -800,6 +858,20 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -811,6 +883,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -821,6 +902,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.15" @@ -854,6 +958,20 @@ dependencies = [ "sha2 0.10.6", ] +[[package]] +name = "parity-scale-codec" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373b1a4c1338d9cd3d1fa53b3a11bdab5ab6bd80a20f7f7becd76953ae2be909" +dependencies = [ + "arrayvec", + "bitvec 0.20.4", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive 2.3.1", + "serde", +] + [[package]] name = "parity-scale-codec" version = "3.5.0" @@ -861,13 +979,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" dependencies = [ "arrayvec", - "bitvec", + "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive", + "parity-scale-codec-derive 3.1.4", "serde", ] +[[package]] +name = "parity-scale-codec-derive" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "parity-scale-codec-derive" version = "3.1.4" @@ -923,14 +1053,26 @@ dependencies = [ "elliptic-curve", ] +[[package]] +name = "primitive-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e4722c697a58a99d5d06a08c30821d7c082a4632198de1eaa5a6c22ef42373" +dependencies = [ + "fixed-hash 0.7.0", + "impl-codec 0.5.1", + "impl-serde", + "uint", +] + [[package]] name = "primitive-types" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" dependencies = [ - "fixed-hash", - "impl-codec", + "fixed-hash 0.8.0", + "impl-codec 0.6.0", "uint", ] @@ -977,6 +1119,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "radium" version = "0.7.0" @@ -1166,9 +1314,9 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] @@ -1184,9 +1332,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", @@ -1195,9 +1343,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1446,6 +1594,8 @@ dependencies = [ name = "tw_any_coin" version = "0.1.0" dependencies = [ + "serde", + "serde_json", "tw_any_coin", "tw_coin_entry", "tw_coin_registry", @@ -1457,6 +1607,23 @@ dependencies = [ "tw_proto", ] +[[package]] +name = "tw_aptos" +version = "0.1.0" +dependencies = [ + "move-core-types", + "serde", + "serde_bytes", + "serde_json", + "tw_coin_entry", + "tw_encoding", + "tw_hash", + "tw_keypair", + "tw_memory", + "tw_number", + "tw_proto", +] + [[package]] name = "tw_bitcoin" version = "0.1.0" @@ -1480,6 +1647,7 @@ name = "tw_coin_entry" version = "0.1.0" dependencies = [ "serde_json", + "tw_encoding", "tw_keypair", "tw_memory", "tw_misc", @@ -1494,6 +1662,7 @@ dependencies = [ "lazy_static", "serde", "serde_json", + "tw_aptos", "tw_bitcoin", "tw_coin_entry", "tw_ethereum", @@ -1510,6 +1679,7 @@ name = "tw_encoding" version = "0.1.0" dependencies = [ "arbitrary", + "bcs", "bs58", "ciborium", "data-encoding", @@ -1622,26 +1792,18 @@ version = "0.1.0" name = "tw_misc" version = "0.1.0" dependencies = [ + "serde", + "serde_json", "zeroize", ] -[[package]] -name = "tw_move_parser" -version = "0.1.0" -dependencies = [ - "bcs", - "hex", - "move-core-types", - "tw_memory", -] - [[package]] name = "tw_number" version = "0.1.0" dependencies = [ "arbitrary", "lazy_static", - "primitive-types", + "primitive-types 0.12.1", "serde", "tw_encoding", "tw_hash", @@ -1746,6 +1908,7 @@ version = "0.1.0" dependencies = [ "serde_json", "tw_any_coin", + "tw_aptos", "tw_bitcoin", "tw_coin_entry", "tw_coin_registry", @@ -1755,7 +1918,6 @@ dependencies = [ "tw_keypair", "tw_memory", "tw_misc", - "tw_move_parser", "tw_number", "tw_proto", ] @@ -1876,6 +2038,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "wyz" version = "0.5.1" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e5c065055f1..872986a6a6a 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "tw_any_coin", + "tw_aptos", "tw_bitcoin", "tw_coin_entry", "tw_coin_registry", @@ -12,7 +13,6 @@ members = [ "tw_keypair", "tw_memory", "tw_misc", - "tw_move_parser", "tw_number", "tw_proto", "tw_ronin", diff --git a/rust/coverage.stats b/rust/coverage.stats index 8670b15529f..7d7ab43dc7c 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -91.0 \ No newline at end of file +92.0 \ No newline at end of file diff --git a/rust/tw_any_coin/Cargo.toml b/rust/tw_any_coin/Cargo.toml index 7ea537e73c2..17ba71621f1 100644 --- a/rust/tw_any_coin/Cargo.toml +++ b/rust/tw_any_coin/Cargo.toml @@ -14,9 +14,12 @@ tw_misc = { path = "../tw_misc" } test-utils = [] [dev-dependencies] +serde = { version = "1.0.163", features = ["derive"] } +serde_json = { version = "1.0.96" } tw_any_coin = { path = "./", features = ["test-utils"] } tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair", features = ["test-utils"] } tw_memory = { path = "../tw_memory", features = ["test-utils"] } +tw_misc = { path = "../tw_misc", features = ["test-utils"] } tw_number = { path = "../tw_number" } tw_proto = { path = "../tw_proto" } diff --git a/rust/tw_move_parser/src/lib.rs b/rust/tw_any_coin/tests/chain_tests.rs similarity index 95% rename from rust/tw_move_parser/src/lib.rs rename to rust/tw_any_coin/tests/chain_tests.rs index c9f1c100548..c1b24a82062 100644 --- a/rust/tw_move_parser/src/lib.rs +++ b/rust/tw_any_coin/tests/chain_tests.rs @@ -4,4 +4,4 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod ffi; +mod chains; diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs new file mode 100644 index 00000000000..9f21f68d317 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_compile.rs @@ -0,0 +1,86 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::aptos::test_cases::transfer_b4d62afd::{ + aptos_sign_transfer_input, expected_json, DATA_TO_SIGN, ENCODED, PRIVATE_KEY, RAW_TXN, + SIGNATURE, +}; +use crate::chains::aptos::APTOS_COIN_TYPE; +use tw_any_coin::ffi::tw_transaction_compiler::{ + tw_transaction_compiler_compile, tw_transaction_compiler_pre_image_hashes, +}; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_keypair::ed25519; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_memory::test_utils::tw_data_vector_helper::TWDataVectorHelper; +use tw_misc::assert_eq_json; +use tw_misc::traits::ToBytesVec; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_aptos() { + let input = aptos_sign_transfer_input(); + + // Step 2: Obtain preimage hash + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let preimage_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_pre_image_hashes(APTOS_COIN_TYPE, input_data.ptr()) + }) + .to_vec() + .expect("!tw_transaction_compiler_pre_image_hashes returned nullptr"); + + let preimage: CompilerProto::PreSigningOutput = + deserialize(&preimage_data).expect("Coin entry returned an invalid output"); + + assert_eq!(preimage.error, SigningErrorType::OK); + assert!(preimage.error_message.is_empty()); + assert_eq!(preimage.data.to_hex(), DATA_TO_SIGN); + + // Step 3: Sign the data "externally" + + let private_key = ed25519::sha512::KeyPair::try_from(PRIVATE_KEY).unwrap(); + let public_key = private_key.public().to_vec(); + + let signature = private_key + .sign(preimage.data.to_vec()) + .expect("Error signing data") + .to_vec(); + assert_eq!(signature.to_hex(), SIGNATURE); + + // Step 4: Compile transaction info + + let signatures = TWDataVectorHelper::create([signature]); + let public_keys = TWDataVectorHelper::create([public_key]); + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + let output_data = TWDataHelper::wrap(unsafe { + tw_transaction_compiler_compile( + APTOS_COIN_TYPE, + input_data.ptr(), + signatures.ptr(), + public_keys.ptr(), + ) + }) + .to_vec() + .expect("!tw_transaction_compiler_compile returned nullptr"); + + let output: Proto::SigningOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let authenticator = output.authenticator.unwrap(); + assert_eq!(authenticator.signature.to_hex(), SIGNATURE); + assert_eq!(output.raw_txn.to_hex(), RAW_TXN); + assert_eq!(output.encoded.to_hex(), ENCODED); + + assert_eq_json!(output.json, expected_json()); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs b/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs new file mode 100644 index 00000000000..6021ce8bb92 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/aptos_sign.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::chains::aptos::test_cases::transfer_b4d62afd::{ + aptos_sign_transfer_input, expected_json, ENCODED, PRIVATE_KEY, RAW_TXN, SIGNATURE, +}; +use crate::chains::aptos::APTOS_COIN_TYPE; +use tw_any_coin::ffi::tw_any_signer::tw_any_signer_sign; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_misc::assert_eq_json; +use tw_proto::Aptos::Proto; +use tw_proto::{deserialize, serialize}; + +#[test] +fn test_any_signer_sign_aptos() { + let input = Proto::SigningInput { + private_key: PRIVATE_KEY.decode_hex().unwrap().into(), + ..aptos_sign_transfer_input() + }; + + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output = + TWDataHelper::wrap(unsafe { tw_any_signer_sign(input_data.ptr(), APTOS_COIN_TYPE) }) + .to_vec() + .expect("!tw_any_signer_sign returned nullptr"); + + let output: Proto::SigningOutput = deserialize(&output).unwrap(); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let authenticator = output.authenticator.unwrap(); + assert_eq!(authenticator.signature.to_hex(), SIGNATURE); + assert_eq!(output.raw_txn.to_hex(), RAW_TXN); + assert_eq!(output.encoded.to_hex(), ENCODED); + + assert_eq_json!(output.json, expected_json()); +} diff --git a/rust/tw_any_coin/tests/chains/aptos/mod.rs b/rust/tw_any_coin/tests/chains/aptos/mod.rs new file mode 100644 index 00000000000..0e046226232 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod aptos_compile; +mod aptos_sign; +mod test_cases; + +const APTOS_COIN_TYPE: u32 = 637; diff --git a/rust/tw_any_coin/tests/chains/aptos/test_cases.rs b/rust/tw_any_coin/tests/chains/aptos/test_cases.rs new file mode 100644 index 00000000000..81b166f7247 --- /dev/null +++ b/rust/tw_any_coin/tests/chains/aptos/test_cases.rs @@ -0,0 +1,64 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::{json, Value as Json}; +use tw_proto::Aptos::Proto; +use tw_proto::Aptos::Proto::mod_SigningInput::OneOftransaction_payload as TransactionPayloadEnum; + +pub(super) mod transfer_b4d62afd { + use super::*; + + /// Expected private key. + pub const PRIVATE_KEY: &str = + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec"; + pub const ENCODED: &str = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"; + /// Expected `raw_txn`. + pub const RAW_TXN: &str = "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"; + /// Expected signature. + pub const SIGNATURE: &str = "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"; + /// Expected preimage data to be signed. + pub const DATA_TO_SIGN: &str = "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"; + + pub fn aptos_sign_transfer_input() -> Proto::SigningInput<'static> { + let transfer = Proto::TransferMessage { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".into(), + amount: 1000, + }; + + Proto::SigningInput { + sender: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".into(), + sequence_number: 99, + max_gas_amount: 3296766, + gas_unit_price: 100, + expiration_timestamp_secs: 3664390082, + chain_id: 33, + any_encoded: Default::default(), + transaction_payload: TransactionPayloadEnum::transfer(transfer), + ..Proto::SigningInput::default() + } + } + + pub fn expected_json() -> Json { + json!({ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + }) + } +} diff --git a/rust/tw_any_coin/tests/chains/mod.rs b/rust/tw_any_coin/tests/chains/mod.rs new file mode 100644 index 00000000000..fa5b3a9718c --- /dev/null +++ b/rust/tw_any_coin/tests/chains/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +mod aptos; diff --git a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs index 0449c69ca59..10013b4e6d3 100644 --- a/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs +++ b/rust/tw_any_coin/tests/tw_any_address_ffi_tests.rs @@ -34,6 +34,9 @@ fn test_any_address_derive() { // TODO match `CoinType` when it's generated. let expected_address = match coin.blockchain { + BlockchainType::Aptos => { + "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" + }, // By default, Bitcoin will return a P2PKH address. BlockchainType::Bitcoin => "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", BlockchainType::Ethereum => "0xAc1ec44E4f0ca7D172B7803f6836De87Fb72b309", @@ -62,6 +65,10 @@ fn test_any_address_derive() { fn test_any_address_normalize_eth() { for coin in supported_coin_items() { let (denormalized, expected_normalized) = match coin.blockchain { + BlockchainType::Aptos => ( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + ), BlockchainType::Bitcoin => ( "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", "19cAJn4Ms8jodBBGtroBNNpCZiHAWGAq7X", @@ -101,6 +108,14 @@ fn test_any_address_normalize_eth() { fn test_any_address_is_valid_coin() { for coin in supported_coin_items() { let valid = match coin.blockchain { + BlockchainType::Aptos => vec![ + "0x1", + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", + "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", + "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", + ], BlockchainType::Bitcoin => vec![ "1MrZNGN7mfWZiZNQttrzHjfw72jnJC2JNx", "bc1qunq74p3h8425hr6wllevlvqqr6sezfxj262rff", @@ -136,6 +151,14 @@ fn test_any_address_is_valid_coin() { fn test_any_address_is_valid_coin_invalid() { for coin in supported_coin_items() { let invalid = match coin.blockchain { + BlockchainType::Aptos => { + vec![ + "", // Empty + "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", // Invalid Hex + "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", // Too long + "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", + ] + }, BlockchainType::Bitcoin => { vec!["0xb16db98b365b1f89191996942612b14f1da4bd5f"] }, diff --git a/rust/tw_aptos/Cargo.toml b/rust/tw_aptos/Cargo.toml new file mode 100644 index 00000000000..49e4d120138 --- /dev/null +++ b/rust/tw_aptos/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "tw_aptos" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde_json = "1.0.108" +tw_coin_entry = { path = "../tw_coin_entry" } +tw_encoding = { path = "../tw_encoding" } +tw_keypair = { path = "../tw_keypair" } +tw_proto = { path = "../tw_proto" } +tw_number = { path = "../tw_number" } +tw_hash = { path = "../tw_hash" } +tw_memory = { path = "../tw_memory" } +move-core-types = { git = "https://github.com/move-language/move", rev = "ea70797099baea64f05194a918cebd69ed02b285", features = ["address32"] } +serde = { version = "1.0.189", features = ["derive"] } +serde_bytes = "0.11.12" + +[dev-dependencies] +tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } +tw_encoding = { path = "../tw_encoding" } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/tw_aptos/fuzz/.gitignore b/rust/tw_aptos/fuzz/.gitignore new file mode 100644 index 00000000000..1a45eee7760 --- /dev/null +++ b/rust/tw_aptos/fuzz/.gitignore @@ -0,0 +1,4 @@ +target +corpus +artifacts +coverage diff --git a/rust/tw_aptos/fuzz/Cargo.toml b/rust/tw_aptos/fuzz/Cargo.toml new file mode 100644 index 00000000000..2d3506a0ea6 --- /dev/null +++ b/rust/tw_aptos/fuzz/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "tw_aptos-fuzz" +version = "0.0.0" +publish = false +edition = "2021" + +[package.metadata] +cargo-fuzz = true + +[dependencies] +libfuzzer-sys = "0.4" +tw_number = { path = "../../tw_number" } +tw_proto = { path = "../../tw_proto", features = ["fuzz"] } + +[dependencies.tw_aptos] +path = ".." + +# Prevent this from interfering with workspaces +[workspace] +members = ["."] + +[profile.release] +debug = 1 + +[[bin]] +name = "sign" +path = "fuzz_targets/sign.rs" +test = false +doc = false diff --git a/rust/tw_aptos/fuzz/fuzz_targets/sign.rs b/rust/tw_aptos/fuzz/fuzz_targets/sign.rs new file mode 100644 index 00000000000..5d55e467d14 --- /dev/null +++ b/rust/tw_aptos/fuzz/fuzz_targets/sign.rs @@ -0,0 +1,9 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use tw_aptos::signer::Signer; +use tw_proto::Aptos::Proto; + +fuzz_target!(|input: Proto::SigningInput<'_>| { + let _ = Signer::sign_proto(input); +}); diff --git a/rust/tw_aptos/src/address.rs b/rust/tw_aptos/src/address.rs new file mode 100644 index 00000000000..3f5cf1d3217 --- /dev/null +++ b/rust/tw_aptos/src/address.rs @@ -0,0 +1,139 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use move_core_types::account_address::{AccountAddress, AccountAddressParseError}; +use std::fmt::{Display, Formatter}; +use std::str::FromStr; +use tw_coin_entry::coin_entry::CoinAddress; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_hash::sha3::sha3_256; +use tw_keypair::ed25519; +use tw_memory::Data; + +pub trait AptosAddress: FromStr + Into
{ + /// Tries to parse an address from the string representation. + /// Returns `Ok(None)` if the given `s` string is empty. + #[inline] + fn from_str_optional(s: &str) -> AddressResult> { + if s.is_empty() { + return Ok(None); + } + + Self::from_str(s).map(Some) + } +} + +impl AptosAddress for Address {} + +#[repr(u8)] +pub enum Scheme { + Ed25519 = 0, +} + +#[derive(Clone)] +pub struct Address { + addr: AccountAddress, +} + +impl Address { + pub const LENGTH: usize = AccountAddress::LENGTH; + /// Initializes an address with a `ed25519` public key. + + pub fn with_ed25519_pubkey( + pubkey: &ed25519::sha512::PublicKey, + ) -> Result { + let mut to_hash = pubkey.as_slice().to_vec(); + to_hash.push(Scheme::Ed25519 as u8); + let hashed = sha3_256(to_hash.as_slice()); + let addr = AccountAddress::from_bytes(hashed).map_err(from_account_error)?; + Ok(Address { addr }) + } + + pub fn inner(&self) -> AccountAddress { + self.addr + } +} + +impl Display for Address { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.addr.to_hex_literal()) + } +} + +impl CoinAddress for Address { + #[inline] + fn data(&self) -> Data { + self.addr.to_vec() + } +} + +#[inline] +pub fn from_account_error(_err: AccountAddressParseError) -> AddressError { + AddressError::InvalidInput +} + +impl FromStr for Address { + type Err = AddressError; + + // https://github.com/aptos-labs/aptos-core/blob/261019cbdafe1c514c60c2b74357ea2c77d25e67/types/src/account_address.rs#L44 + fn from_str(s: &str) -> Result { + const NUM_CHARS: usize = AccountAddress::LENGTH * 2; + let mut has_0x = false; + let mut working = s.trim(); + + // Checks if it has a 0x at the beginning, which is okay + if working.starts_with("0x") { + has_0x = true; + working = &working[2..]; + } + + if working.len() > NUM_CHARS || (!has_0x && working.len() < NUM_CHARS) { + return Err(AddressError::InvalidInput); + } + + if !working.chars().all(|c| char::is_ascii_hexdigit(&c)) { + return Err(AddressError::InvalidInput); + } + + let addr = if has_0x { + AccountAddress::from_hex_literal(s.trim()) + } else { + AccountAddress::from_str(s.trim()) + } + .map_err(from_account_error)?; + + Ok(Address { addr }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use tw_keypair::ed25519::sha512::PrivateKey; + + #[test] + fn test_from_public_key() { + let private = PrivateKey::try_from( + "afeefca74d9a325cf1d6b6911d61a65c32afa8e02bd5e78e2e4ac2910bab45f5", + ) + .unwrap(); + let public = private.public(); + let addr = Address::with_ed25519_pubkey(&public); + assert_eq!( + addr.as_ref().unwrap().to_string(), + "0x9006fa46f038224e8004bdda97f2e7a60c2c3d135bce7cb15541e5c0aae907a4" + ); + assert_eq!(addr.unwrap().data().len(), Address::LENGTH); + } + + #[test] + fn test_from_account_error() { + assert_eq!( + from_account_error(AccountAddressParseError {}), + AddressError::InvalidInput + ); + } +} diff --git a/rust/tw_aptos/src/aptos_move_packages.rs b/rust/tw_aptos/src/aptos_move_packages.rs new file mode 100644 index 00000000000..2e8f7aff378 --- /dev/null +++ b/rust/tw_aptos/src/aptos_move_packages.rs @@ -0,0 +1,209 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::account_address::AccountAddress; +use move_core_types::ident_str; +use move_core_types::language_storage::{ModuleId, TypeTag}; +use serde_json::json; +use tw_coin_entry::error::SigningResult; +use tw_encoding::bcs; + +pub fn aptos_account_transfer( + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_create_account(auth_key: AccountAddress) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("create_account").to_owned(), + vec![], + vec![bcs::encode(&auth_key)?], + json!([auth_key.to_hex_literal()]), + ))) +} + +pub fn coin_transfer( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("coin").to_owned(), + ), + ident_str!("transfer").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn aptos_account_transfer_coins( + coin_type: TypeTag, + to: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("aptos_account").to_owned(), + ), + ident_str!("transfer_coins").to_owned(), + vec![coin_type], + vec![bcs::encode(&to)?, bcs::encode(&amount)?], + json!([to.to_hex_literal(), amount.to_string()]), + ))) +} + +pub fn token_transfers_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + bcs::encode(&amount)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string(), + amount.to_string() + ]), + ))) +} + +pub fn token_transfers_cancel_offer_script( + receiver: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("cancel_offer_script").to_owned(), + vec![], + vec![ + bcs::encode(&receiver)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + receiver.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn token_transfers_claim_script( + sender: AccountAddress, + creator: AccountAddress, + collection: Vec, + name: Vec, + property_version: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 3, + ]), + ident_str!("token_transfers").to_owned(), + ), + ident_str!("claim_script").to_owned(), + vec![], + vec![ + bcs::encode(&sender)?, + bcs::encode(&creator)?, + bcs::encode(&collection)?, + bcs::encode(&name)?, + bcs::encode(&property_version)?, + ], + json!([ + sender.to_hex_literal(), + creator.to_hex_literal(), + String::from_utf8_lossy(&collection), + String::from_utf8_lossy(&name), + property_version.to_string() + ]), + ))) +} + +pub fn managed_coin_register(coin_type: TypeTag) -> TransactionPayload { + TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + AccountAddress::new([ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, + ]), + ident_str!("managed_coin").to_owned(), + ), + ident_str!("register").to_owned(), + vec![coin_type], + vec![], + json!([]), + )) +} diff --git a/rust/tw_aptos/src/bcs_encoding.rs b/rust/tw_aptos/src/bcs_encoding.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/rust/tw_aptos/src/compiler.rs b/rust/tw_aptos/src/compiler.rs new file mode 100644 index 00000000000..73954261f19 --- /dev/null +++ b/rust/tw_aptos/src/compiler.rs @@ -0,0 +1,74 @@ +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct Compiler; + +impl Compiler { + #[inline] + pub fn preimage_hashes( + input: Proto::SigningInput<'_>, + ) -> CompilerProto::PreSigningOutput<'static> { + Self::preimage_hashes_impl(input) + .unwrap_or_else(|e| signing_output_error!(CompilerProto::PreSigningOutput, e)) + } + + fn preimage_hashes_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .pre_image()?; + Ok(CompilerProto::PreSigningOutput { + data: signed_tx.into(), + ..CompilerProto::PreSigningOutput::default() + }) + } + + #[inline] + pub fn compile( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Proto::SigningOutput<'static> { + Self::compile_impl(input, signatures, public_keys) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn compile_impl( + input: Proto::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> SigningResult> { + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signature = signatures + .first() + .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; + let public_key = public_keys + .first() + .ok_or(SigningError(SigningErrorType::Error_signatures_count))?; + + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .compile(signature.to_vec(), public_key.to_vec())?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_aptos/src/constants.rs b/rust/tw_aptos/src/constants.rs new file mode 100644 index 00000000000..6c30d3b68bc --- /dev/null +++ b/rust/tw_aptos/src/constants.rs @@ -0,0 +1,9 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub const GAS_UNIT_PRICE: u64 = 100; +pub const MAX_GAS_AMOUNT: u64 = 100_000_000; +pub const APTOS_SALT: &[u8] = b"APTOS::RawTransaction"; diff --git a/rust/tw_aptos/src/entry.rs b/rust/tw_aptos/src/entry.rs new file mode 100644 index 00000000000..4438a15ae2b --- /dev/null +++ b/rust/tw_aptos/src/entry.rs @@ -0,0 +1,94 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::compiler::Compiler; +use crate::signer::Signer; +use std::str::FromStr; +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::coin_entry::{CoinEntry, PublicKeyBytes, SignatureBytes}; +use tw_coin_entry::derivation::Derivation; +use tw_coin_entry::error::{AddressError, AddressResult}; +use tw_coin_entry::modules::json_signer::NoJsonSigner; +use tw_coin_entry::modules::message_signer::NoMessageSigner; +use tw_coin_entry::modules::plan_builder::NoPlanBuilder; +use tw_coin_entry::prefix::NoPrefix; +use tw_keypair::tw::PublicKey; +use tw_proto::Aptos::Proto; +use tw_proto::TxCompiler::Proto as CompilerProto; + +pub struct AptosEntry; + +impl CoinEntry for AptosEntry { + type AddressPrefix = NoPrefix; + type Address = Address; + type SigningInput<'a> = Proto::SigningInput<'a>; + type SigningOutput = Proto::SigningOutput<'static>; + type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + + // Optional modules: + type JsonSigner = NoJsonSigner; + type PlanBuilder = NoPlanBuilder; + type MessageSigner = NoMessageSigner; + + #[inline] + fn parse_address( + &self, + _coin: &dyn CoinContext, + address: &str, + _prefix: Option, + ) -> AddressResult { + Address::from_str(address) + } + + fn derive_address( + &self, + _coin: &dyn CoinContext, + public_key: PublicKey, + _derivation: Derivation, + _prefix: Option, + ) -> AddressResult { + let public_key = public_key + .to_ed25519() + .ok_or(AddressError::PublicKeyTypeMismatch)?; + Address::with_ed25519_pubkey(public_key) + } + + #[inline] + fn sign(&self, _coin: &dyn CoinContext, input: Self::SigningInput<'_>) -> Self::SigningOutput { + Signer::sign_proto(input) + } + + #[inline] + fn preimage_hashes( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + ) -> Self::PreSigningOutput { + Compiler::preimage_hashes(input) + } + + #[inline] + fn compile( + &self, + _coin: &dyn CoinContext, + input: Self::SigningInput<'_>, + signatures: Vec, + public_keys: Vec, + ) -> Self::SigningOutput { + Compiler::compile(input, signatures, public_keys) + } + + #[inline] + fn json_signer(&self) -> Option { + None + } + + #[inline] + fn message_signer(&self) -> Option { + None + } +} diff --git a/rust/tw_aptos/src/lib.rs b/rust/tw_aptos/src/lib.rs new file mode 100644 index 00000000000..b85498f42c2 --- /dev/null +++ b/rust/tw_aptos/src/lib.rs @@ -0,0 +1,20 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod address; +pub mod aptos_move_packages; +pub mod constants; +pub mod entry; +mod serde_helper; + +pub mod nft; + +pub mod compiler; +pub mod liquid_staking; +pub mod signer; +pub mod transaction; +pub mod transaction_builder; +pub mod transaction_payload; diff --git a/rust/tw_aptos/src/liquid_staking.rs b/rust/tw_aptos/src/liquid_staking.rs new file mode 100644 index 00000000000..2ac80692050 --- /dev/null +++ b/rust/tw_aptos/src/liquid_staking.rs @@ -0,0 +1,154 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use crate::transaction_payload::{EntryFunction, TransactionPayload}; +use move_core_types::{account_address::AccountAddress, ident_str, language_storage::ModuleId}; +use serde_json::json; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::bcs; +use tw_proto::{ + Aptos::Proto::mod_LiquidStaking::OneOfliquid_stake_transaction_payload, + Aptos::Proto::{LiquidStaking, TortugaClaim, TortugaStake, TortugaUnstake}, +}; + +pub fn tortuga_stake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("stake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_unstake( + smart_contract_address: AccountAddress, + amount: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("unstake").to_owned(), + vec![], + vec![bcs::encode(&amount)?], + json!([amount.to_string()]), + ))) +} + +pub fn tortuga_claim( + smart_contract_address: AccountAddress, + idx: u64, +) -> SigningResult { + Ok(TransactionPayload::EntryFunction(EntryFunction::new( + ModuleId::new( + smart_contract_address, + ident_str!("stake_router").to_owned(), + ), + ident_str!("claim").to_owned(), + vec![], + vec![bcs::encode(&idx)?], + json!([idx.to_string()]), + ))) +} + +pub struct Stake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Unstake { + pub amount: u64, + pub smart_contract_address: AccountAddress, +} + +pub struct Claim { + pub idx: u64, + pub smart_contract_address: AccountAddress, +} + +pub enum LiquidStakingOperation { + Stake(Stake), + Unstake(Unstake), + Claim(Claim), +} + +impl TryFrom> for LiquidStakingOperation { + type Error = SigningError; + + fn try_from(value: LiquidStaking) -> SigningResult { + match value.liquid_stake_transaction_payload { + OneOfliquid_stake_transaction_payload::stake(stake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Stake(Stake { + amount: stake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::unstake(unstake_msg) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Unstake(Unstake { + amount: unstake_msg.amount, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::claim(claim) => { + let smart_contract_address = + AccountAddress::from_str(&value.smart_contract_address) + .map_err(from_account_error)?; + Ok(LiquidStakingOperation::Claim(Claim { + idx: claim.idx, + smart_contract_address, + })) + }, + OneOfliquid_stake_transaction_payload::None => { + Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + } + } +} + +impl From for LiquidStaking<'_> { + fn from(value: LiquidStakingOperation) -> Self { + match value { + LiquidStakingOperation::Stake(stake) => LiquidStaking { + smart_contract_address: stake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::stake( + TortugaStake { + amount: stake.amount, + }, + ), + }, + LiquidStakingOperation::Unstake(unstake) => LiquidStaking { + smart_contract_address: unstake.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::unstake( + TortugaUnstake { + amount: unstake.amount, + }, + ), + }, + LiquidStakingOperation::Claim(claim) => LiquidStaking { + smart_contract_address: claim.smart_contract_address.to_hex_literal().into(), + liquid_stake_transaction_payload: OneOfliquid_stake_transaction_payload::claim( + TortugaClaim { idx: claim.idx }, + ), + }, + } + } +} diff --git a/rust/tw_aptos/src/nft.rs b/rust/tw_aptos/src/nft.rs new file mode 100644 index 00000000000..fe6f0375146 --- /dev/null +++ b/rust/tw_aptos/src/nft.rs @@ -0,0 +1,165 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use move_core_types::account_address::AccountAddress; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::Aptos::Proto::mod_NftMessage::OneOfnft_transaction_payload; +use tw_proto::Aptos::Proto::{CancelOfferNftMessage, ClaimNftMessage, NftMessage, OfferNftMessage}; + +pub struct Offer { + pub receiver: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, + pub amount: u64, +} + +pub struct Claim { + pub sender: AccountAddress, + pub creator: AccountAddress, + pub collection: Vec, + pub name: Vec, + pub property_version: u64, +} + +pub enum NftOperation { + Claim(Claim), + Offer(Offer), + Cancel(Offer), +} + +impl TryFrom> for NftOperation { + type Error = SigningError; + + fn try_from(value: NftMessage) -> SigningResult { + match value.nft_transaction_payload { + OneOfnft_transaction_payload::offer_nft(msg) => { + Ok(NftOperation::Offer(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::cancel_offer_nft(msg) => { + Ok(NftOperation::Cancel(Offer::try_from(msg)?)) + }, + OneOfnft_transaction_payload::claim_nft(msg) => { + Ok(NftOperation::Claim(Claim::try_from(msg)?)) + }, + OneOfnft_transaction_payload::None => { + Err(SigningError(SigningErrorType::Error_invalid_params)) + }, + } + } +} + +impl From for NftMessage<'_> { + fn from(value: NftOperation) -> Self { + match value { + NftOperation::Claim(claim) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::claim_nft(claim.into()), + }, + NftOperation::Offer(offer) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::offer_nft(offer.into()), + }, + NftOperation::Cancel(cancel) => NftMessage { + nft_transaction_payload: OneOfnft_transaction_payload::cancel_offer_nft( + cancel.into(), + ), + }, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: OfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: value.amount, + }) + } +} + +impl From for OfferNftMessage<'_> { + fn from(value: Offer) -> Self { + OfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(&value.name).to_string().into(), + property_version: value.property_version, + amount: value.amount, + } + } +} + +impl TryFrom> for Offer { + type Error = SigningError; + + fn try_from(value: CancelOfferNftMessage) -> SigningResult { + Ok(Offer { + receiver: AccountAddress::from_str(&value.receiver).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + amount: 0, + }) + } +} + +impl From for CancelOfferNftMessage<'_> { + fn from(value: Offer) -> Self { + CancelOfferNftMessage { + receiver: value.receiver.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} + +impl TryFrom> for Claim { + type Error = SigningError; + + fn try_from(value: ClaimNftMessage) -> SigningResult { + Ok(Claim { + sender: AccountAddress::from_str(&value.sender).map_err(from_account_error)?, + creator: AccountAddress::from_str(&value.creator).map_err(from_account_error)?, + collection: value.collectionName.as_bytes().to_vec(), + name: value.name.as_bytes().to_vec(), + property_version: value.property_version, + }) + } +} + +impl From for ClaimNftMessage<'_> { + fn from(value: Claim) -> Self { + ClaimNftMessage { + sender: value.sender.to_hex_literal().into(), + creator: value.creator.to_hex_literal().into(), + collectionName: String::from_utf8_lossy(value.collection.as_slice()) + .to_string() + .into(), + name: String::from_utf8_lossy(value.name.as_slice()) + .to_string() + .into(), + property_version: value.property_version, + } + } +} diff --git a/rust/tw_aptos/src/serde_helper/mod.rs b/rust/tw_aptos/src/serde_helper/mod.rs new file mode 100644 index 00000000000..876f018225e --- /dev/null +++ b/rust/tw_aptos/src/serde_helper/mod.rs @@ -0,0 +1,5 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod vec_bytes; diff --git a/rust/tw_aptos/src/serde_helper/vec_bytes.rs b/rust/tw_aptos/src/serde_helper/vec_bytes.rs new file mode 100644 index 00000000000..f57311d19dd --- /dev/null +++ b/rust/tw_aptos/src/serde_helper/vec_bytes.rs @@ -0,0 +1,30 @@ +// Copyright © Aptos Foundation +// Parts of the project are originally copyright © Meta Platforms, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use serde::{ + de::Deserializer, + ser::{SerializeSeq, Serializer}, + Deserialize, +}; + +pub fn serialize(data: &[Vec], serializer: S) -> Result +where + S: Serializer, +{ + let mut seq = serializer.serialize_seq(Some(data.len()))?; + for e in data { + seq.serialize_element(serde_bytes::Bytes::new(e.as_slice()))?; + } + seq.end() +} + +pub fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + Ok(>::deserialize(deserializer)? + .into_iter() + .map(serde_bytes::ByteBuf::into_vec) + .collect()) +} diff --git a/rust/tw_aptos/src/signer.rs b/rust/tw_aptos/src/signer.rs new file mode 100644 index 00000000000..91431569c73 --- /dev/null +++ b/rust/tw_aptos/src/signer.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::Address; +use crate::transaction_builder; +use std::str::FromStr; +use tw_coin_entry::error::SigningResult; +use tw_coin_entry::signing_output_error; +use tw_keypair::ed25519; +use tw_proto::Aptos::Proto; + +pub struct Signer; + +impl Signer { + #[inline] + pub fn sign_proto(input: Proto::SigningInput<'_>) -> Proto::SigningOutput<'static> { + Self::sign_proto_impl(input) + .unwrap_or_else(|e| signing_output_error!(Proto::SigningOutput, e)) + } + + fn sign_proto_impl( + input: Proto::SigningInput<'_>, + ) -> SigningResult> { + let key_pair = ed25519::sha512::KeyPair::try_from(input.private_key.as_ref())?; + let builder = transaction_builder::TransactionFactory::new_from_protobuf(input.clone())?; + let sender = Address::from_str(&input.sender)?; + let signed_tx = builder + .sender(sender.inner()) + .sequence_number(input.sequence_number as u64) + .build()? + .sign(key_pair)?; + Ok(Proto::SigningOutput { + raw_txn: signed_tx.raw_txn_bytes().clone().into(), + encoded: signed_tx.encoded().clone().into(), + authenticator: Some((*signed_tx.authenticator()).clone().into()), + json: signed_tx.to_json().to_string().into(), + ..Proto::SigningOutput::default() + }) + } +} diff --git a/rust/tw_aptos/src/transaction.rs b/rust/tw_aptos/src/transaction.rs new file mode 100644 index 00000000000..b48d7950bbc --- /dev/null +++ b/rust/tw_aptos/src/transaction.rs @@ -0,0 +1,220 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::constants::APTOS_SALT; +use crate::transaction_payload::TransactionPayload; +use move_core_types::account_address::AccountAddress; +use serde::Serialize; +use serde_json::{json, Value}; +use std::borrow::Cow; +use tw_coin_entry::error::SigningResult; +use tw_encoding::hex::encode; +use tw_encoding::{bcs, EncodingResult}; +use tw_keypair::ed25519::sha512::KeyPair; +use tw_keypair::traits::{KeyPairTrait, SigningKeyTrait}; +use tw_memory::Data; +use tw_proto::Aptos::Proto; + +#[derive(Clone, Serialize)] +pub enum TransactionAuthenticator { + /// Single Ed25519 signature + Ed25519 { + public_key: Vec, + signature: Vec, + }, +} + +impl From for Proto::TransactionAuthenticator<'_> { + fn from(from: TransactionAuthenticator) -> Self { + Proto::TransactionAuthenticator { + signature: Cow::from(from.get_signature()), + public_key: Cow::from(from.get_public_key()), + } + } +} + +impl TransactionAuthenticator { + pub fn get_signature(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key: _public_key, + signature, + } => signature.clone(), + } + } + + pub fn get_public_key(&self) -> Vec { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature: _signature, + } => public_key.clone(), + } + } + + pub fn to_json(&self) -> Value { + match self { + TransactionAuthenticator::Ed25519 { + public_key, + signature, + } => { + json!({"public_key": encode(public_key, true), + "signature": encode(signature, true), + "type": "ed25519_signature"}) + }, + } + } +} + +/// RawTransaction is the portion of a transaction that a client signs. +#[derive(Clone, Serialize)] +pub struct RawTransaction { + /// Sender's address. + sender: AccountAddress, + + /// Sequence number of this transaction. This must match the sequence number + /// stored in the sender's account at the time the transaction executes. + sequence_number: u64, + + /// The transaction payload, e.g., a script to execute. + payload: TransactionPayload, + + /// Maximal total gas to spend for this transaction. + max_gas_amount: u64, + + /// Price to be paid per gas unit. + gas_unit_price: u64, + + /// Expiration timestamp for this transaction, represented + /// as seconds from the Unix Epoch. If the current blockchain timestamp + /// is greater than or equal to this time, then the transaction has + /// expired and will be discarded. This can be set to a large value far + /// in the future to indicate that a transaction does not expire. + expiration_timestamp_secs: u64, + + /// Chain ID of the Aptos network this transaction is intended for. + chain_id: u8, +} + +impl RawTransaction { + /// Create a new `RawTransaction` with a payload. + /// + /// It can be either to publish a module, to execute a script, or to issue a writeset + /// transaction. + pub fn new( + sender: AccountAddress, + sequence_number: u64, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, + ) -> Self { + RawTransaction { + sender, + sequence_number, + payload, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs, + chain_id, + } + } + + /// Create a new `RawTransaction` with an entry function + fn serialize(&self) -> EncodingResult { + bcs::encode(&self) + } + + fn msg_to_sign(&self) -> SigningResult { + let serialized = self.serialize()?; + let mut preimage = tw_hash::sha3::sha3_256(APTOS_SALT); + preimage.extend_from_slice(serialized.as_slice()); + Ok(preimage) + } + + pub fn pre_image(&self) -> SigningResult> { + self.msg_to_sign() + } + + pub fn compile( + &self, + signature: Vec, + public_key: Vec, + ) -> SigningResult { + let serialized = self.serialize()?; + let auth = TransactionAuthenticator::Ed25519 { + public_key, + signature, + }; + let mut encoded = serialized.clone(); + encoded.extend_from_slice(bcs::encode(&auth)?.as_slice()); + Ok(SignedTransaction { + raw_txn: self.clone(), + authenticator: auth, + raw_txn_bytes: serialized.to_vec(), + encoded, + }) + } + + pub fn sign(self, key_pair: KeyPair) -> SigningResult { + let to_sign = self.pre_image()?; + let signature = key_pair.private().sign(to_sign)?.to_bytes().into_vec(); + let pubkey = key_pair.public().as_slice().to_vec(); + self.compile(signature, pubkey) + } + + pub fn to_json(&self) -> Value { + json!({ + "expiration_timestamp_secs": self.expiration_timestamp_secs.to_string(), + "gas_unit_price": self.gas_unit_price.to_string(), + "max_gas_amount": self.max_gas_amount.to_string(), + "payload": self.payload.to_json(), + "sender": self.sender.to_hex_literal(), + "sequence_number": self.sequence_number.to_string() + }) + } +} + +/// A transaction that has been signed. +/// +/// A `SignedTransaction` is a single transaction that can be atomically executed. Clients submit +/// these to validator nodes, and the validator and executor submits these to the VM. +/// +#[derive(Clone, Serialize)] +pub struct SignedTransaction { + /// The raw transaction + raw_txn: RawTransaction, + + /// Public key and signature to authenticate + authenticator: TransactionAuthenticator, + + #[serde(skip_serializing)] + /// Raw txs bytes + raw_txn_bytes: Vec, + + #[serde(skip_serializing)] + /// Encoded bytes to be broadcast + encoded: Vec, +} + +impl SignedTransaction { + pub fn authenticator(&self) -> &TransactionAuthenticator { + &self.authenticator + } + pub fn raw_txn_bytes(&self) -> &Vec { + &self.raw_txn_bytes + } + pub fn encoded(&self) -> &Vec { + &self.encoded + } + + pub fn to_json(&self) -> Value { + let mut json_value = self.raw_txn.to_json(); + json_value["signature"] = self.authenticator.to_json(); + json_value + } +} diff --git a/rust/tw_aptos/src/transaction_builder.rs b/rust/tw_aptos/src/transaction_builder.rs new file mode 100644 index 00000000000..53f72d32936 --- /dev/null +++ b/rust/tw_aptos/src/transaction_builder.rs @@ -0,0 +1,263 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::address::from_account_error; +use crate::aptos_move_packages::{ + aptos_account_create_account, aptos_account_transfer, aptos_account_transfer_coins, + coin_transfer, managed_coin_register, token_transfers_cancel_offer_script, + token_transfers_claim_script, token_transfers_offer_script, +}; +use crate::constants::{GAS_UNIT_PRICE, MAX_GAS_AMOUNT}; +use crate::liquid_staking::{ + tortuga_claim, tortuga_stake, tortuga_unstake, LiquidStakingOperation, +}; +use crate::nft::NftOperation; +use crate::transaction::RawTransaction; +use crate::transaction_payload::{ + convert_proto_struct_tag_to_type_tag, EntryFunction, TransactionPayload, +}; +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use serde_json::Value; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_proto::Aptos::Proto::mod_SigningInput::OneOftransaction_payload; +use tw_proto::Aptos::Proto::SigningInput; + +pub struct TransactionBuilder { + sender: Option, + sequence_number: Option, + payload: TransactionPayload, + max_gas_amount: u64, + gas_unit_price: u64, + expiration_timestamp_secs: u64, + chain_id: u8, +} + +impl TransactionBuilder { + pub fn sender(mut self, sender: AccountAddress) -> Self { + self.sender = Some(sender); + self + } + + pub fn sequence_number(mut self, sequence_number: u64) -> Self { + self.sequence_number = Some(sequence_number); + self + } + + pub fn build(self) -> SigningResult { + let sender = self + .sender + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + let sequence_number = self + .sequence_number + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + Ok(RawTransaction::new( + sender, + sequence_number, + self.payload, + self.max_gas_amount, + self.gas_unit_price, + self.expiration_timestamp_secs, + self.chain_id, + )) + } +} + +#[derive(Clone, Debug)] +pub struct TransactionFactory { + max_gas_amount: u64, + gas_unit_price: u64, + transaction_expiration_time: u64, + chain_id: u8, +} + +impl TransactionFactory { + pub fn new(chain_id: u8) -> Self { + Self { + max_gas_amount: MAX_GAS_AMOUNT, + gas_unit_price: GAS_UNIT_PRICE, + transaction_expiration_time: 30, + chain_id, + } + } + + pub fn new_from_protobuf(input: SigningInput) -> SigningResult { + let factory = TransactionFactory::new(input.chain_id as u8) + .with_gas_unit_price(input.gas_unit_price) + .with_max_gas_amount(input.max_gas_amount) + .with_transaction_expiration_time(input.expiration_timestamp_secs); + match input.transaction_payload { + OneOftransaction_payload::transfer(transfer) => factory + .implicitly_create_user_account_and_transfer( + AccountAddress::from_str(&transfer.to).map_err(from_account_error)?, + transfer.amount, + ), + OneOftransaction_payload::token_transfer(token_transfer) => { + let func = token_transfer + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + factory.coins_transfer( + AccountAddress::from_str(&token_transfer.to).map_err(from_account_error)?, + token_transfer.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::create_account(create_account) => { + let address = AccountAddress::from_str(&create_account.auth_key) + .map_err(from_account_error)?; + factory.create_user_account(address) + }, + OneOftransaction_payload::nft_message(nft_message) => { + factory.nft_ops(NftOperation::try_from(nft_message)?) + }, + OneOftransaction_payload::register_token(register_token) => { + let function = register_token + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + Ok(factory.register_token(convert_proto_struct_tag_to_type_tag(function)?)) + }, + OneOftransaction_payload::liquid_staking_message(msg) => { + factory.liquid_staking_ops(LiquidStakingOperation::try_from(msg)?) + }, + OneOftransaction_payload::token_transfer_coins(token_transfer_coins) => { + let func = token_transfer_coins + .function + .ok_or(SigningError(SigningErrorType::Error_invalid_params))?; + factory.implicitly_create_user_and_coins_transfer( + AccountAddress::from_str(&token_transfer_coins.to) + .map_err(from_account_error)?, + token_transfer_coins.amount, + convert_proto_struct_tag_to_type_tag(func)?, + ) + }, + OneOftransaction_payload::None => { + let is_blind_sign = !input.any_encoded.is_empty(); + let v = serde_json::from_str::(&input.any_encoded)?; + if is_blind_sign { + let entry_function = EntryFunction::try_from(v)?; + Ok(factory.payload(TransactionPayload::EntryFunction(entry_function))) + } else { + Err(SigningError(SigningErrorType::Error_input_parse)) + } + }, + } + } + + pub fn with_max_gas_amount(mut self, max_gas_amount: u64) -> Self { + self.max_gas_amount = max_gas_amount; + self + } + + pub fn with_gas_unit_price(mut self, gas_unit_price: u64) -> Self { + self.gas_unit_price = gas_unit_price; + self + } + + pub fn with_transaction_expiration_time(mut self, transaction_expiration_time: u64) -> Self { + self.transaction_expiration_time = transaction_expiration_time; + self + } + + pub fn payload(&self, payload: TransactionPayload) -> TransactionBuilder { + self.transaction_builder(payload) + } + + pub fn create_user_account(&self, to: AccountAddress) -> SigningResult { + Ok(self.payload(aptos_account_create_account(to)?)) + } + + pub fn register_token(&self, coin_type: TypeTag) -> TransactionBuilder { + self.payload(managed_coin_register(coin_type)) + } + + pub fn nft_ops(&self, operation: NftOperation) -> SigningResult { + match operation { + NftOperation::Claim(claim) => Ok(self.payload(token_transfers_claim_script( + claim.sender, + claim.creator, + claim.collection, + claim.name, + claim.property_version, + )?)), + NftOperation::Cancel(offer) => Ok(self.payload(token_transfers_cancel_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + )?)), + NftOperation::Offer(offer) => Ok(self.payload(token_transfers_offer_script( + offer.receiver, + offer.creator, + offer.collection, + offer.name, + offer.property_version, + offer.amount, + )?)), + } + } + + pub fn liquid_staking_ops( + &self, + operation: LiquidStakingOperation, + ) -> SigningResult { + match operation { + LiquidStakingOperation::Stake(stake) => { + Ok(self.payload(tortuga_stake(stake.smart_contract_address, stake.amount)?)) + }, + LiquidStakingOperation::Unstake(unstake) => Ok(self.payload(tortuga_unstake( + unstake.smart_contract_address, + unstake.amount, + )?)), + LiquidStakingOperation::Claim(claim) => { + Ok(self.payload(tortuga_claim(claim.smart_contract_address, claim.idx)?)) + }, + } + } + + pub fn implicitly_create_user_account_and_transfer( + &self, + to: AccountAddress, + amount: u64, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer(to, amount)?)) + } + + pub fn coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(coin_transfer(coin_type, to, amount)?)) + } + + pub fn implicitly_create_user_and_coins_transfer( + &self, + to: AccountAddress, + amount: u64, + coin_type: TypeTag, + ) -> SigningResult { + Ok(self.payload(aptos_account_transfer_coins(coin_type, to, amount)?)) + } + + fn transaction_builder(&self, payload: TransactionPayload) -> TransactionBuilder { + TransactionBuilder { + sender: None, + sequence_number: None, + payload, + max_gas_amount: self.max_gas_amount, + gas_unit_price: self.gas_unit_price, + expiration_timestamp_secs: self.expiration_timestamp(), + chain_id: self.chain_id, + } + } + + fn expiration_timestamp(&self) -> u64 { + self.transaction_expiration_time + } +} diff --git a/rust/tw_aptos/src/transaction_payload.rs b/rust/tw_aptos/src/transaction_payload.rs new file mode 100644 index 00000000000..5e33e71d465 --- /dev/null +++ b/rust/tw_aptos/src/transaction_payload.rs @@ -0,0 +1,268 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::serde_helper::vec_bytes; +use move_core_types::identifier::Identifier; +use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; +use move_core_types::parser::parse_transaction_argument; +use move_core_types::transaction_argument::TransactionArgument; +use serde::{Deserialize, Serialize}; +use serde_json::{json, Value}; +use std::default::Default; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_encoding::{bcs, EncodingError, EncodingResult}; +use tw_memory::Data; +use tw_proto::Aptos; + +pub type EntryFunctionResult = Result; + +#[derive(Debug)] +pub enum EntryFunctionError { + MissingFunctionName, + InvalidFunctionName, + MissingArguments, + InvalidArguments, + EncodingError, + MissingTypeArguments, + InvalidTypeArguments, +} + +impl From for EntryFunctionError { + fn from(_error: EncodingError) -> Self { + EntryFunctionError::EncodingError + } +} + +impl From for SigningError { + fn from(_: EntryFunctionError) -> Self { + SigningError(SigningErrorType::Error_invalid_params) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct EntryFunction { + module: ModuleId, + function: Identifier, + ty_args: Vec, + #[serde(with = "vec_bytes")] + args: Vec>, + #[serde(skip_serializing)] + json_args: Value, +} + +impl TryFrom for EntryFunction { + type Error = EntryFunctionError; + + fn try_from(value: Value) -> EntryFunctionResult { + let function_str = value["function"] + .as_str() + .ok_or(EntryFunctionError::MissingFunctionName)?; + let tag = StructTag::from_str(function_str) + .map_err(|_| EntryFunctionError::InvalidFunctionName)?; + + let args = value["arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingArguments)? + .iter() + .map(|element| { + let arg_str = element.to_string(); + let arg = parse_transaction_argument( + arg_str.trim_start_matches('"').trim_end_matches('"'), + ) + .map_err(|_| EntryFunctionError::InvalidArguments)?; + serialize_argument(&arg).map_err(EntryFunctionError::from) + }) + .collect::>>()?; + + let ty_args = value["type_arguments"] + .as_array() + .ok_or(EntryFunctionError::MissingTypeArguments)? + .iter() + .map(|element| { + let ty_arg_str = element + .as_str() + .ok_or(EntryFunctionError::InvalidTypeArguments)?; + TypeTag::from_str(ty_arg_str).map_err(|_| EntryFunctionError::InvalidTypeArguments) + }) + .collect::>>()?; + + Ok(EntryFunction { + module: tag.module_id(), + function: tag.name, + ty_args, + args, + json_args: value["arguments"].clone(), + }) + } +} + +fn serialize_argument(arg: &TransactionArgument) -> EncodingResult { + match arg { + TransactionArgument::U8(v) => bcs::encode(v), + TransactionArgument::U16(v) => bcs::encode(v), + TransactionArgument::U32(v) => bcs::encode(v), + TransactionArgument::U64(v) => bcs::encode(v), + TransactionArgument::U128(v) => bcs::encode(v), + TransactionArgument::U256(v) => bcs::encode(v), + TransactionArgument::U8Vector(v) => bcs::encode(v), + TransactionArgument::Bool(v) => bcs::encode(v), + TransactionArgument::Address(v) => { + let serialized_v = bcs::encode(v)?; + bcs::encode(&serialized_v) + }, + } +} + +pub fn convert_proto_struct_tag_to_type_tag( + struct_tag: Aptos::Proto::StructTag, +) -> SigningResult { + TypeTag::from_str(&format!( + "{}::{}::{}", + struct_tag.account_address, struct_tag.module, struct_tag.name + )) + .map_err(|_| SigningError(SigningErrorType::Error_invalid_params)) +} + +pub fn convert_type_tag_to_struct_tag(type_tag: TypeTag) -> Aptos::Proto::StructTag<'static> { + if let TypeTag::Struct(st) = type_tag { + Aptos::Proto::StructTag { + account_address: st.address.to_hex_literal().into(), + module: st.module.to_string().into(), + name: st.name.to_string().into(), + } + } else { + Aptos::Proto::StructTag::default() + } +} + +impl EntryFunction { + fn to_json(&self) -> Value { + // Create a JSON array from the `ty_args` field by filtering and mapping + // the items that match `TypeTag::Struct` to their string representation. + let type_arguments: Value = self + .ty_args + .iter() + .map(|item| Some(json!(item.to_string()))) + .collect(); + + // Construct the final JSON value + json!({ + "type": "entry_function_payload", + "function": format!("{}::{}", self.module.short_str_lossless(), self.function.clone().into_string()), + "arguments": self.json_args, + "type_arguments": type_arguments + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum TransactionPayload { + Script, + ModuleBundle, + /// A transaction that executes an existing entry function published on-chain. + EntryFunction(EntryFunction), +} + +impl TransactionPayload { + pub fn to_json(&self) -> Value { + match self { + TransactionPayload::Script => Value::default(), + TransactionPayload::ModuleBundle => Value::default(), + TransactionPayload::EntryFunction(entry) => entry.to_json(), + } + } +} + +impl EntryFunction { + pub fn new( + module: ModuleId, + function: Identifier, + ty_args: Vec, + args: Vec>, + json_args: Value, + ) -> Self { + EntryFunction { + module, + function, + ty_args, + args, + json_args, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address::Address; + use move_core_types::account_address::AccountAddress; + use move_core_types::identifier::Identifier; + use move_core_types::language_storage::{ModuleId, TypeTag}; + use serde_json::{json, Value}; + use std::str::FromStr; + use tw_encoding::hex; + + #[test] + fn test_payload_from_json() { + let payload_value: Value = json!({ + "arguments": ["0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"], + "function": "0xc23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d50::IFO::release", + "type": "entry_function_payload", + "type_arguments": [ + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::BUSD", + "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::DAI", + "0x9936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a::uints::U1" + ] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_payload_from_json_with_arg_non_str() { + let payload_value: Value = json!({ + "type":"entry_function_payload", + "function":"0xd11107bdf0d6d7040c6c0bfbdecb6545191fdf13e8d8d259952f53e1713f61b5::ditto_staking::stake_aptos", + "type_arguments":[], + "arguments": [1000000] + }); + + let v = EntryFunction::try_from(payload_value.clone()).unwrap(); + assert_eq!(payload_value, v.to_json()); + } + + #[test] + fn test_basic_payload() { + let addr = + Address::from_str("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b") + .unwrap() + .inner(); + let amount: i64 = 1000; + let args = vec![bcs::encode(&addr).unwrap(), bcs::encode(&amount).unwrap()]; + let module = ModuleId::new(AccountAddress::ONE, Identifier::from_str("coin").unwrap()); + let function = Identifier::from_str("transfer").unwrap(); + let type_tag = vec![TypeTag::from_str("0x1::aptos_coin::AptosCoin").unwrap()]; + let entry = EntryFunction::new( + module, + function, + type_tag, + args, + json!([addr.to_hex_literal(), amount.to_string()]), + ); + let tp = TransactionPayload::EntryFunction(entry); + let serialized = bcs::encode(&tp).unwrap(); + assert_eq!(hex::encode(serialized, false), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); + let payload_value: Value = json!({ + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "arguments": ["0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", "1000"], + "type_arguments": ["0x1::aptos_coin::AptosCoin"] + }); + assert_eq!(tp.to_json(), payload_value); + } +} diff --git a/rust/tw_aptos/tests/signer.rs b/rust/tw_aptos/tests/signer.rs new file mode 100644 index 00000000000..09f80e2e26a --- /dev/null +++ b/rust/tw_aptos/tests/signer.rs @@ -0,0 +1,873 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use move_core_types::account_address::AccountAddress; +use move_core_types::language_storage::TypeTag; +use std::str::FromStr; +use tw_aptos::liquid_staking; +use tw_aptos::liquid_staking::{LiquidStakingOperation, Stake, Unstake}; +use tw_aptos::nft::{Claim, NftOperation, Offer}; +use tw_aptos::signer::Signer; +use tw_aptos::transaction_payload::convert_type_tag_to_struct_tag; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex; +use tw_proto::Aptos::Proto; +use tw_proto::Aptos::Proto::{SigningInput, SigningOutput}; + +pub struct AccountCreation { + to: String, +} + +pub struct Transfer { + to: String, + amount: u64, +} + +pub struct TokenTransfer { + transfer: Transfer, + tag: TypeTag, +} + +pub struct RegisterToken { + coin_type: TypeTag, +} + +pub enum OpsDetails { + RegisterToken(RegisterToken), + LiquidStakingOps(LiquidStakingOperation), + AccountCreation(AccountCreation), + Transfer(Transfer), + TokenTransfer(TokenTransfer), + ImplicitTokenTransfer(TokenTransfer), + NftOps(NftOperation), +} + +fn setup_proto_transaction<'a>( + sender: &'a str, + keypair_str: &'a str, + transaction_type: &'a str, + sequence_number: i64, + chain_id: u32, + max_gas_amount: u64, + timestamp: u64, + gas_unit_price: u64, + any_encoded: &'a str, + ops_details: Option, +) -> SigningInput<'a> { + let private = hex::decode(keypair_str).unwrap(); + + let payload: Proto::mod_SigningInput::OneOftransaction_payload = match transaction_type { + "transfer" => { + if let OpsDetails::Transfer(transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::transfer( + Proto::TransferMessage { + to: transfer.to.into(), + amount: transfer.amount, + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "create_account" => { + if let OpsDetails::AccountCreation(account) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::create_account( + Proto::CreateAccountMessage { + auth_key: account.to.into(), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "coin_transfer" => { + if let OpsDetails::TokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer( + Proto::TokenTransferMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "implicit_coin_transfer" => { + if let OpsDetails::ImplicitTokenTransfer(token_transfer) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::token_transfer_coins( + Proto::TokenTransferCoinsMessage { + to: token_transfer.transfer.to.into(), + amount: token_transfer.transfer.amount, + function: Some(convert_type_tag_to_struct_tag(token_transfer.tag)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "nft_ops" => { + if let OpsDetails::NftOps(nft) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::nft_message(nft.into()) + } else { + panic!("Unsupported arguments") + } + }, + "register_token" => { + if let OpsDetails::RegisterToken(register_token) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::register_token( + Proto::ManagedTokensRegisterMessage { + function: Some(convert_type_tag_to_struct_tag(register_token.coin_type)), + }, + ) + } else { + panic!("Unsupported arguments") + } + }, + "liquid_staking_ops" => { + if let OpsDetails::LiquidStakingOps(liquid_staking_ops) = ops_details.unwrap() { + Proto::mod_SigningInput::OneOftransaction_payload::liquid_staking_message( + liquid_staking_ops.into(), + ) + } else { + panic!("Unsupported arguments") + } + }, + "blind_sign_json" => Proto::mod_SigningInput::OneOftransaction_payload::None, + _ => Proto::mod_SigningInput::OneOftransaction_payload::None, + }; + + let input = SigningInput { + chain_id, + sender: sender.into(), + sequence_number, + max_gas_amount, + gas_unit_price, + expiration_timestamp_secs: timestamp, + private_key: private.into(), + any_encoded: any_encoded.into(), + transaction_payload: payload, + }; + + input +} + +fn test_tx_result( + output: SigningOutput, + expected_raw_txn_bytes_str: &str, + expected_signature_str: &str, + expected_encoded_txn_str: &str, + json_literal: &str, +) { + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + assert_eq!( + hex::encode(output.raw_txn.to_vec(), false), + expected_raw_txn_bytes_str + ); + assert_eq!( + hex::encode(output.authenticator.unwrap().signature.to_vec(), false), + expected_signature_str + ); + assert_eq!( + hex::encode(output.encoded.to_vec(), false), + expected_encoded_txn_str + ); + + let json_value_expected: serde_json::Value = serde_json::from_str(json_literal).unwrap(); + let json_value: serde_json::Value = serde_json::from_str(output.json.as_ref()).unwrap(); + assert_eq!(json_value, json_value_expected); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet +#[test] +fn test_aptos_sign_transaction_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", + "transfer", + 99, + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::Transfer(Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30".to_string(), + amount: 1000, + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021", + "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "function": "0x1::aptos_account::transfer", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "99", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet +#[test] +fn test_aptos_sign_create_account() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "create_account", + 0, // Sequence number + 33, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::AccountCreation(AccountCreation { + to: "0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e".to_string(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021", // Expected raw transaction bytes + "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], + "function": "0x1::aptos_account::create_account", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "0", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet +#[test] +fn test_aptos_sign_coin_transfer() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "coin_transfer", + 24, // Sequence number + 32, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::TokenTransfer(TokenTransfer { + transfer: Transfer { + to: "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30" + .to_string(), + amount: 100000, + }, + tag: TypeTag::from_str( + "0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC", + ) + .unwrap(), + })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020", // Expected raw transaction bytes + "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], + "function": "0x1::coin::transfer", + "type": "entry_function_payload", + "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "24", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet +#[test] +fn test_implicit_aptos_sign_coin_transfer() { + let input = setup_proto_transaction("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", // Sender's address + "e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8", // Keypair + "implicit_coin_transfer", + 2, // Sequence number + 1, + 2000, + 3664390082, + 100, + "", + Some(OpsDetails::ImplicitTokenTransfer(TokenTransfer { transfer: Transfer { to: "0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c".to_string(), amount: 10000 }, tag: TypeTag::from_str("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected signature + "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000", + "payload": { + "arguments": ["0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c","10000"], + "function": "0x1::aptos_account::transfer_coins", + "type": "entry_function_payload", + "type_arguments": ["0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin"] + }, + "sender": "0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", + "sequence_number": "2", + "signature": { + "public_key": "0x62e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca8369", + "signature": "0x30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet +#[test] +fn test_aptos_nft_offer() { + let input = setup_proto_transaction( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", // Sender's address + "7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2", // Keypair + "nft_ops", + 1, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Offer(Offer { + receiver: AccountAddress::from_str( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 1, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected signature + "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], + "function": "0x3::token_transfers::offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "sequence_number": "1", + "signature": { + "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", + "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet +#[test] +fn test_aptos_cancel_nft_offer() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 21, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Cancel(Offer { + receiver: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + amount: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::cancel_offer_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "21", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet +#[test] +fn test_aptos_nft_claim() { + let input = setup_proto_transaction( + "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "nft_ops", + 19, // Sequence number + 2, + 3296766, + 3664390082, + 100, + "", + Some(OpsDetails::NftOps(NftOperation::Claim(Claim { + sender: AccountAddress::from_str( + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + ) + .unwrap(), + creator: AccountAddress::from_str( + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + ) + .unwrap(), + collection: "Topaz Troopers".as_bytes().to_vec(), + name: "Topaz Trooper #20068".as_bytes().to_vec(), + property_version: 0, + }))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "3296766", + "payload": { + "arguments": [ + "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", + "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", + "Topaz Troopers", "Topaz Trooper #20068", "0"], + "function": "0x3::token_transfers::claim_script", + "type": "entry_function_payload", + "type_arguments": [] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "19", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet +#[test] +fn test_aptos_register_token() { + let input = setup_proto_transaction("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "register_token", + 23, // Sequence number + 2, + 2000000, + 3664390082, + 100, + "", + Some(OpsDetails::RegisterToken(RegisterToken { coin_type: TypeTag::from_str("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin").unwrap() })), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002", // Expected raw transaction bytes + "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "2000000", + "payload": { + "arguments": [], + "function": "0x1::managed_coin::register", + "type": "entry_function_payload", + "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "23", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet +#[test] +fn test_aptos_tortuga_stake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 19, // Sequence number + 1, + 5554, + 1670240203, + 100, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Stake( + Stake { + amount: 100000000, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001", // Expected raw transaction bytes + "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "19", + "max_gas_amount": "5554", + "gas_unit_price": "100", + "expiration_timestamp_secs": "1670240203", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet +#[test] +fn test_aptos_tortuga_unstake() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 20, // Sequence number + 1, + 2371, + 1670304949, + 120, + "", + Some(OpsDetails::LiquidStakingOps( + LiquidStakingOperation::Unstake(Unstake { + amount: 99178100, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }), + )), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001", // Expected raw transaction bytes + "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "20", + "max_gas_amount": "2371", + "gas_unit_price": "120", + "expiration_timestamp_secs": "1670304949", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", + "type": "ed25519_signature" + } + }"#); +} + +// // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet +#[test] +fn test_aptos_tortuga_claim() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05", // Keypair + "liquid_staking_ops", + 28, // Sequence number + 1, + 10, + 1682066783, + 148, + "", + Some(OpsDetails::LiquidStakingOps(LiquidStakingOperation::Claim( + liquid_staking::Claim { + idx: 0, + smart_contract_address: AccountAddress::from_str( + "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f", + ) + .unwrap(), + }, + ))), + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001", // Expected raw transaction bytes + "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", // Expected encoded transaction + r#"{ + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "28", + "max_gas_amount": "10", + "gas_unit_price": "148", + "expiration_timestamp_secs": "1682066783", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", + "type_arguments": [], + "arguments": [ + "0" + ], + "type": "entry_function_payload" + }, + "signature": { + "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", + "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet +#[test] +fn test_aptos_blind_sign() { + let input = setup_proto_transaction( + "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 42, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected signature + "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", + "type_arguments": [ + "0x1::aptos_coin::AptosCoin", + "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", + "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" + ], + "arguments": [ + "1000000", + "49329" + ], + "type": "entry_function_payload" + }, + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sequence_number": "42", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload +#[test] +fn test_aptos_blind_sign_staking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 43, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", + "type_arguments": [], + "arguments": [ + "100000000" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "43", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", + "type": "ed25519_signature" + } + }"#); +} + +// Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968/payload +#[test] +fn test_aptos_blind_sign_unstaking() { + let input = setup_proto_transaction( + "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", // Sender's address + "5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec", // Keypair + "blind_sign_json", + 44, // Sequence number + 1, + 100011, + 3664390082, + 100, + r#"{ + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }"#, + None, + ); + let output = Signer::sign_proto(input); + test_tx_result(output, + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada0000000001", // Expected raw transaction bytes + "a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected signature + "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", // Expected encoded transaction + r#"{ + "expiration_timestamp_secs": "3664390082", + "gas_unit_price": "100", + "max_gas_amount": "100011", + "payload": { + "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", + "type_arguments": [], + "arguments": [ + "99178100" + ], + "type": "entry_function_payload" + }, + "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", + "sequence_number": "44", + "signature": { + "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", + "signature": "0xa58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", + "type": "ed25519_signature" + } + }"#); +} diff --git a/rust/tw_coin_entry/Cargo.toml b/rust/tw_coin_entry/Cargo.toml index 8b55efc5c0e..41ab8b48576 100644 --- a/rust/tw_coin_entry/Cargo.toml +++ b/rust/tw_coin_entry/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] serde_json = "1.0.95" +tw_encoding = { path = "../tw_encoding" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_coin_entry/src/error.rs b/rust/tw_coin_entry/src/error.rs index c31b495576b..0e39825ce3b 100644 --- a/rust/tw_coin_entry/src/error.rs +++ b/rust/tw_coin_entry/src/error.rs @@ -6,6 +6,7 @@ use std::fmt; use std::fmt::Formatter; +use tw_encoding::EncodingError; use tw_keypair::KeyPairError; use tw_number::NumberError; use tw_proto::Common::Proto; @@ -26,7 +27,7 @@ macro_rules! signing_output_error { pub type AddressResult = Result; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum AddressError { UnknownCoinType, MissingPrefix, @@ -57,6 +58,18 @@ impl From for SigningError { } } +impl From for SigningError { + fn from(_value: serde_json::Error) -> Self { + SigningError(SigningErrorType::Error_input_parse) + } +} + +impl From for SigningError { + fn from(_e: EncodingError) -> Self { + SigningError(SigningErrorType::Error_input_parse) + } +} + impl From for SigningError { fn from(err: KeyPairError) -> Self { match err { diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index 4ad4e6d87de..b45a573c5a1 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" lazy_static = "1.4.0" serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" +tw_aptos = { path = "../tw_aptos" } tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry" } tw_ethereum = { path = "../tw_ethereum" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 72a4141d1ea..2139a373eb3 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -13,6 +13,7 @@ use std::str::FromStr; /// Extend this enum when adding new blockchains. #[derive(Copy, Clone, Debug)] pub enum BlockchainType { + Aptos, Bitcoin, Ethereum, InternetComputer, @@ -35,6 +36,7 @@ impl FromStr for BlockchainType { fn from_str(s: &str) -> Result { match s { + "Aptos" => Ok(BlockchainType::Aptos), "Bitcoin" => Ok(BlockchainType::Bitcoin), "Ethereum" => Ok(BlockchainType::Ethereum), "InternetComputer" => Ok(BlockchainType::InternetComputer), diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs index e027054e443..68970653371 100644 --- a/rust/tw_coin_registry/src/dispatcher.rs +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -9,6 +9,7 @@ use crate::coin_context::CoinRegistryContext; use crate::coin_type::CoinType; use crate::error::{RegistryError, RegistryResult}; use crate::registry::get_coin_item; +use tw_aptos::entry::AptosEntry; use tw_bitcoin::entry::BitcoinEntry; use tw_coin_entry::coin_entry_ext::CoinEntryExt; use tw_ethereum::entry::EthereumEntry; @@ -19,6 +20,7 @@ use tw_ronin::entry::RoninEntry; pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; +const APTOS: AptosEntry = AptosEntry; const BITCOIN: BitcoinEntry = BitcoinEntry; const ETHEREUM: EthereumEntry = EthereumEntry; const INTERNET_COMPUTER: InternetComputerEntry = InternetComputerEntry; @@ -26,6 +28,7 @@ const RONIN: RoninEntry = RoninEntry; pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { match blockchain { + BlockchainType::Aptos => Ok(&APTOS), BlockchainType::Bitcoin => Ok(&BITCOIN), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Ok(&INTERNET_COMPUTER), @@ -46,6 +49,7 @@ pub fn coin_dispatcher( pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { let item = get_coin_item(coin)?; match item.blockchain { + BlockchainType::Aptos => Err(RegistryError::Unsupported), BlockchainType::Bitcoin => Err(RegistryError::Unsupported), BlockchainType::Ethereum => Ok(ÐEREUM), BlockchainType::InternetComputer => Err(RegistryError::Unsupported), diff --git a/rust/tw_encoding/Cargo.toml b/rust/tw_encoding/Cargo.toml index 2211e12bde9..4a1b666a551 100644 --- a/rust/tw_encoding/Cargo.toml +++ b/rust/tw_encoding/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arbitrary = { version = "1", features = ["derive"], optional = true } +bcs = "0.1.6" bs58 = "0.4.0" ciborium = "0.2.1" data-encoding = "2.3.3" diff --git a/rust/tw_encoding/src/bcs.rs b/rust/tw_encoding/src/bcs.rs new file mode 100644 index 00000000000..8051ce4ac03 --- /dev/null +++ b/rust/tw_encoding/src/bcs.rs @@ -0,0 +1,16 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use crate::{EncodingError, EncodingResult}; +use serde::Serialize; +use tw_memory::Data; + +pub fn encode(value: &T) -> EncodingResult +where + T: ?Sized + Serialize, +{ + bcs::to_bytes(value).map_err(|_| EncodingError::InvalidInput) +} diff --git a/rust/tw_encoding/src/lib.rs b/rust/tw_encoding/src/lib.rs index 3250e9ed4da..f658d9b019f 100644 --- a/rust/tw_encoding/src/lib.rs +++ b/rust/tw_encoding/src/lib.rs @@ -7,6 +7,7 @@ pub mod base32; pub mod base58; pub mod base64; +pub mod bcs; pub mod cbor; pub mod ffi; pub mod hex; diff --git a/rust/tw_evm/fuzz/Cargo.toml b/rust/tw_evm/fuzz/Cargo.toml index 6fad417a7d8..e1ac2e6c65c 100644 --- a/rust/tw_evm/fuzz/Cargo.toml +++ b/rust/tw_evm/fuzz/Cargo.toml @@ -9,7 +9,7 @@ cargo-fuzz = true [dependencies] libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } -serde_json = "1.0.95" +serde_json = "1.0.96" tw_number = { path = "../../tw_number" } tw_proto = { path = "../../tw_proto", features = ["fuzz"] } diff --git a/rust/tw_keypair/src/tw/public.rs b/rust/tw_keypair/src/tw/public.rs index e8a31f0fc20..8b527050944 100644 --- a/rust/tw_keypair/src/tw/public.rs +++ b/rust/tw_keypair/src/tw/public.rs @@ -135,4 +135,11 @@ impl PublicKey { _ => None, } } + + pub fn to_ed25519(&self) -> Option<&ed25519::sha512::PublicKey> { + match self { + PublicKey::Ed25519(ed25519) => Some(ed25519), + _ => None, + } + } } diff --git a/rust/tw_misc/Cargo.toml b/rust/tw_misc/Cargo.toml index ac5867c20dc..60fe9a1033d 100644 --- a/rust/tw_misc/Cargo.toml +++ b/rust/tw_misc/Cargo.toml @@ -3,5 +3,10 @@ name = "tw_misc" version = "0.1.0" edition = "2021" +[features] +test-utils = ["serde", "serde_json"] + [dependencies] +serde = { version = "1.0.163", features = ["derive"], optional = true } +serde_json = { version = "1.0.96", optional = true } zeroize = "1.6.0" diff --git a/rust/tw_misc/src/lib.rs b/rust/tw_misc/src/lib.rs index 1f73752d7a1..314dedda6dc 100644 --- a/rust/tw_misc/src/lib.rs +++ b/rust/tw_misc/src/lib.rs @@ -1,2 +1,4 @@ pub mod macros; +#[cfg(feature = "test-utils")] +pub mod test_utils; pub mod traits; diff --git a/rust/tw_misc/src/test_utils/json.rs b/rust/tw_misc/src/test_utils/json.rs new file mode 100644 index 00000000000..bcab8c3f6d3 --- /dev/null +++ b/rust/tw_misc/src/test_utils/json.rs @@ -0,0 +1,44 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use serde_json::Value as Json; +use std::borrow::Cow; + +pub trait ToJson { + fn to_json(&self) -> Json; +} + +impl ToJson for Json { + #[track_caller] + fn to_json(&self) -> Json { + self.clone() + } +} + +impl<'a> ToJson for Cow<'a, str> { + #[track_caller] + fn to_json(&self) -> Json { + self.as_ref().to_json() + } +} + +impl<'a> ToJson for &'a str { + #[track_caller] + fn to_json(&self) -> Json { + serde_json::from_str(self).expect("Error on deserializing JSON from string") + } +} + +#[macro_export] +macro_rules! assert_eq_json { + ($left:expr, $right:expr) => {{ + use $crate::test_utils::json::ToJson; + + let left = $left.to_json(); + let right = $right.to_json(); + assert_eq!(left, right); + }}; +} diff --git a/rust/tw_misc/src/test_utils/mod.rs b/rust/tw_misc/src/test_utils/mod.rs new file mode 100644 index 00000000000..019606a0fca --- /dev/null +++ b/rust/tw_misc/src/test_utils/mod.rs @@ -0,0 +1,7 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +pub mod json; diff --git a/rust/tw_move_parser/Cargo.toml b/rust/tw_move_parser/Cargo.toml deleted file mode 100644 index b674b9a8908..00000000000 --- a/rust/tw_move_parser/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "tw_move_parser" -version = "0.1.0" -edition = "2021" - -[dependencies] -bcs = "0.1.4" -hex = "0.4.3" -move-core-types = { git = "https://github.com/move-language/move", rev = "f7137eabc2046f76fdad3ded2c51e03a3b1fbd01", features = ["address32"] } -tw_memory = { path = "../tw_memory" } diff --git a/rust/tw_move_parser/src/ffi.rs b/rust/tw_move_parser/src/ffi.rs deleted file mode 100644 index 7552e37f457..00000000000 --- a/rust/tw_move_parser/src/ffi.rs +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#![allow(clippy::missing_safety_doc)] - -use move_core_types::language_storage::TypeTag; -use move_core_types::transaction_argument::TransactionArgument; -use move_core_types::*; -use std::{ffi::c_char, ffi::CStr}; -use tw_memory::ffi::c_result::{CStrResult, ErrorCode}; - -#[repr(C)] -pub enum CMoveParserCode { - Ok = 0, - InvalidInput = 1, - ErrorParsing = 2, -} - -impl From for ErrorCode { - fn from(code: CMoveParserCode) -> Self { - code as ErrorCode - } -} - -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum ETypeTag { - Bool = 1, - U8 = 2, - U64 = 3, - U128 = 4, - Address = 5, - Signer = 6, - Vector = 7, - Struct = 8, - Error = 9, -} - -/// Parses a Move type tag. -/// \param input *non-null* C-compatible, nul-terminated string. -/// \return `ETypeTag` enumeration. -#[no_mangle] -pub unsafe extern "C" fn parse_type_tag(input: *const c_char) -> ETypeTag { - let s = CStr::from_ptr(input).to_str().unwrap(); - let transaction_argument = match parser::parse_type_tag(s) { - Ok(v) => v, - Err(_) => return ETypeTag::Error, - }; - match transaction_argument { - TypeTag::Bool => ETypeTag::Bool, - TypeTag::U8 => ETypeTag::U8, - TypeTag::U64 => ETypeTag::U64, - TypeTag::U128 => ETypeTag::U128, - TypeTag::Address => ETypeTag::Address, - TypeTag::Signer => ETypeTag::Signer, - TypeTag::Vector(_) => ETypeTag::Vector, - TypeTag::Struct(_) => ETypeTag::Struct, - } -} - -/// Parses `input` as a Move function argument. -/// \param input *non-null* C-compatible, nul-terminated string. -/// \return *non-null* C-compatible, nul-terminated string, Binary Canonical Serialization (BCS). -#[no_mangle] -pub unsafe extern "C" fn parse_function_argument_to_bcs(input: *const c_char) -> CStrResult { - let s = match CStr::from_ptr(input).to_str() { - Ok(input) => input, - Err(_) => return CStrResult::error(CMoveParserCode::InvalidInput), - }; - let transaction_argument = match parser::parse_transaction_argument(s) { - Ok(v) => v, - Err(_) => return CStrResult::error(CMoveParserCode::ErrorParsing), - }; - let v = match transaction_argument { - TransactionArgument::U8(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::U64(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::U128(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::Address(v) => { - hex::encode(bcs::to_bytes(&bcs::to_bytes(&v).unwrap()).unwrap()) - }, - TransactionArgument::U8Vector(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - TransactionArgument::Bool(v) => hex::encode(bcs::to_bytes(&v).unwrap()), - }; - CStrResult::ok(tw_memory::c_string_standalone(v)) -} diff --git a/rust/tw_move_parser/tests/move_parser_ffi_tests.rs b/rust/tw_move_parser/tests/move_parser_ffi_tests.rs deleted file mode 100644 index 3cb0d528f5e..00000000000 --- a/rust/tw_move_parser/tests/move_parser_ffi_tests.rs +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -use std::ffi::{c_char, CString}; -use tw_move_parser::ffi::{parse_function_argument_to_bcs, parse_type_tag, ETypeTag}; - -#[test] -fn tests_type_tag() { - let tag = unsafe { parse_type_tag("0x1::aptos_coin::AptosCoin\0".as_ptr() as *const c_char) }; - assert_eq!(tag, ETypeTag::Struct); -} - -#[test] -fn tests_function_argument_to_bcs() { - let str = unsafe { - let input = "10000000\0".as_ptr() as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!(str, "8096980000000000"); - - let str = unsafe { - let input = "5047445908\0".as_ptr() as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - assert_eq!(str, "94e9d92c01000000"); -} - -#[test] -fn tests_function_argument_to_bcs_another() { - let str = unsafe { - let input = "0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6\0".as_ptr() - as *const c_char; - CString::from_raw(parse_function_argument_to_bcs(input).unwrap() as *mut c_char) - .into_string() - .unwrap() - }; - let decoded = hex::decode(str).unwrap(); - let v = vec![decoded]; - let actual = hex::encode(bcs::to_bytes(&v).unwrap()); - let expected = "012120c95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6"; - assert_eq!(actual, expected); -} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 90586221f9a..37b8c878d1d 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -15,6 +15,7 @@ ethereum-rlp = [] [dependencies] tw_any_coin = { path = "../tw_any_coin" } +tw_aptos = { path = "../tw_aptos" } tw_bitcoin = { path = "../tw_bitcoin" } tw_coin_entry = { path = "../tw_coin_entry", features = ["test-utils"] } tw_coin_registry = { path = "../tw_coin_registry" } @@ -23,7 +24,6 @@ tw_ethereum = { path = "../tw_ethereum" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } -tw_move_parser = { path = "../tw_move_parser" } tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto" } diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index 6335fa4a0f4..eefcb93f8c8 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -5,6 +5,7 @@ // file LICENSE at the root of the source code distribution tree. pub extern crate tw_any_coin; +pub extern crate tw_aptos; pub extern crate tw_bitcoin; pub extern crate tw_coin_registry; pub extern crate tw_encoding; @@ -12,7 +13,6 @@ pub extern crate tw_ethereum; pub extern crate tw_hash; pub extern crate tw_keypair; pub extern crate tw_memory; -pub extern crate tw_move_parser; pub extern crate tw_proto; pub mod ffi; diff --git a/src/Aptos/Address.cpp b/src/Aptos/Address.cpp deleted file mode 100644 index 18242423a9e..00000000000 --- a/src/Aptos/Address.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Author: Clement Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Address.h" -#include "HexCoding.h" - -namespace TW::Aptos { - -Address::Address(const std::string& string) : Address::AptosAddress(string) { -} - -Address::Address(const PublicKey& publicKey): Address::AptosAddress(publicKey) { -} - -Data Address::getDigest(const PublicKey& publicKey) { - auto key_data = publicKey.bytes; - append(key_data, 0x00); - return key_data; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Address addr) noexcept { - stream.add_bytes(addr.bytes.begin(), addr.bytes.end()); - return stream; -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Address.h b/src/Aptos/Address.h deleted file mode 100644 index db735283ddf..00000000000 --- a/src/Aptos/Address.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Author: Clement Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "BCS.h" -#include "Data.h" -#include "Move/Address.h" -#include "PublicKey.h" - -#include - -namespace TW::Aptos { - -class Address : public Move::Address { -public: - using AptosAddress = Move::Address; - using AptosAddress::size; - using AptosAddress::bytes; - - /// Initializes an Aptos address with a string representation. - explicit Address(const std::string& string); - - /// Initializes an Aptos address with a public key. - explicit Address(const PublicKey& publicKey); - - /// Constructor that allow factory programming; - Address() noexcept = default; - - Data getDigest(const PublicKey& publicKey); -}; - -constexpr inline bool operator==(const Address& lhs, const Address& rhs) noexcept { - return lhs.bytes == rhs.bytes; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Address) noexcept; - -} // namespace TW::Aptos diff --git a/src/Aptos/Entry.cpp b/src/Aptos/Entry.cpp index 8c7fafced8e..a0d362910ac 100644 --- a/src/Aptos/Entry.cpp +++ b/src/Aptos/Entry.cpp @@ -6,36 +6,26 @@ #include "Entry.h" -#include "Address.h" -#include "Signer.h" - namespace TW::Aptos { -bool Entry::validateAddress([[maybe_unused]] TWCoinType coin, const std::string& address, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address::isValid(address); +bool Entry::validateAddress(TWCoinType coin, const std::string& address, const PrefixVariant& addressPrefix) const { + return validateAddressRust(coin, address, addressPrefix); } -std::string Entry::deriveAddress([[maybe_unused]] TWCoinType coin, const PublicKey& publicKey, [[maybe_unused]] TWDerivation derivation, [[maybe_unused]] const PrefixVariant& addressPrefix) const { - return Address(publicKey).string(); +std::string Entry::deriveAddress(TWCoinType coin, const PublicKey& publicKey, TWDerivation derivation, const PrefixVariant& addressPrefix) const { + return deriveAddressRust(coin, publicKey, derivation, addressPrefix); } void Entry::sign([[maybe_unused]] TWCoinType coin, const TW::Data& dataIn, TW::Data& dataOut) const { - signTemplate(dataIn, dataOut); + signRust(dataIn, coin, dataOut); } Data Entry::preImageHashes([[maybe_unused]] TWCoinType coin, const Data& txInputData) const { - return txCompilerTemplate( - txInputData, [](const auto& input, auto& output) { - output = Signer::preImageHashes(input); - }); + return preImageHashesRust(coin, txInputData); } void Entry::compile([[maybe_unused]] TWCoinType coin, const Data& txInputData, const std::vector& signatures, const std::vector& publicKeys, Data& dataOut) const { - dataOut = txCompilerSingleTemplate( - txInputData, signatures, publicKeys, - [](const auto& input, auto& output, const auto& signature, const auto& publicKey) { - output = Signer::compile(input, signature, publicKey); - }); + compileRust(coin, txInputData, signatures, publicKeys, dataOut); } } // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.cpp b/src/Aptos/MoveTypes.cpp deleted file mode 100644 index ccde47c9439..00000000000 --- a/src/Aptos/MoveTypes.cpp +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include - -namespace TW::Aptos { - -Aptos::ModuleId::ModuleId(Address accountAddress, Identifier name) noexcept - : mAccountAddress(accountAddress), mName(std::move(name)) { -} - -Data ModuleId::accessVector() const noexcept { - BCS::Serializer serializer; - serializer << static_cast(gCodeTag) << mAccountAddress << mName; - return serializer.bytes; -} - -std::string ModuleId::string() const noexcept { - std::stringstream ss; - ss << mAccountAddress.string() << "::" << mName; - return ss.str(); -} - -std::string ModuleId::shortString() const noexcept { - std::stringstream ss; - ss << "0x" << mAccountAddress.shortString() << "::" << mName; - return ss.str(); -} - -Data StructTag::serialize(bool withResourceTag) const noexcept { - BCS::Serializer serializer; - if (withResourceTag) - { - serializer << gResourceTag; - } - serializer << mAccountAddress << mModule << mName << mTypeParams; - return serializer.bytes; -} - -StructTag::StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept - : mAccountAddress(accountAddress), mModule(std::move(module)), mName(std::move(name)), mTypeParams(std::move(typeParams)) { -} -std::string StructTag::string() const noexcept { - std::stringstream ss; - ss << "0x" << mAccountAddress.shortString() << "::" << mModule << "::" << mName; - if (!mTypeParams.empty()) { - ss << "<"; - ss << TypeTagToString(*mTypeParams.begin()); - std::for_each(begin(mTypeParams) + 1, end(mTypeParams), [&ss](auto&& cur) { - ss << ", " << TypeTagToString(cur); - }); - ss << ">"; - } - return ss.str(); -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept { - stream << Bool::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept { - stream << U8::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept { - stream << U64::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept { - stream << U128::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept { - stream << TAddress::value; - return stream; -} -BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept { - stream << TSigner::value; - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept { - auto res = st.serialize(); - stream.add_bytes(begin(res), end(res)); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& st) noexcept { - stream << TStructTag::value; - auto res = st.st.serialize(false); - stream.add_bytes(begin(res), end(res)); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept { - stream << Vector::value; - for (auto&& cur: t.tags) { - stream << cur; - } - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept { - std::visit([&stream](auto&& arg) { stream << arg; }, t.tags); - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept { - stream << module.address() << module.name(); - return stream; -} -std::string TypeTagToString(const TypeTag& typeTag) noexcept { - auto visit_functor = [](const TypeTag::TypeTagVariant& value) -> std::string { - if (std::holds_alternative(value)) { - return "bool"; - } else if (std::holds_alternative(value)) { - return "u8"; - } else if (std::holds_alternative(value)) { - return "u64"; - } else if (std::holds_alternative(value)) { - return "u128"; - } else if (std::holds_alternative(value)) { - return "address"; - } else if (std::holds_alternative(value)) { - return "signer"; - } else if (auto* vectorData = std::get_if(&value); vectorData != nullptr && !vectorData->tags.empty()) { - std::stringstream ss; - ss << "vector<" << TypeTagToString(*vectorData->tags.begin()) << ">"; - return ss.str(); - } else if (auto* structData = std::get_if(&value); structData) { - return structData->string(); - } else if (auto* tStructData = std::get_if(&value); tStructData) { - return tStructData->st.string(); - } else { - return ""; - } - }; - - return std::visit(visit_functor, typeTag.tags); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/MoveTypes.h b/src/Aptos/MoveTypes.h deleted file mode 100644 index 26dfe1b8477..00000000000 --- a/src/Aptos/MoveTypes.h +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Aptos/Address.h" -#include "BCS.h" -#include - -namespace TW::Aptos { - -constexpr std::uint8_t gCodeTag{0}; -constexpr std::uint8_t gResourceTag{1}; -using Identifier = std::string; - -class ModuleId { -public: - ///< Constructor - ModuleId(Address accountAddress, Identifier name) noexcept; - - ///< Getters - [[nodiscard]] const std::string& name() const noexcept { return mName; } - [[nodiscard]] const Address& address() const noexcept { return mAccountAddress; } - [[nodiscard]] Data accessVector() const noexcept; - [[nodiscard]] std::string string() const noexcept; - [[nodiscard]] std::string shortString() const noexcept; - -private: - Address mAccountAddress; - Identifier mName; -}; - -inline ModuleId gAptosAccountModule{Address::one(), "aptos_account"}; -inline ModuleId gAptosCoinModule{Address::one(), "coin"}; -inline ModuleId gAptosManagedCoinsModule{Address::one(), "managed_coin"}; -inline ModuleId gAptosTokenTransfersModule{Address::three(), "token_transfers"}; - -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleId& module) noexcept; - -struct TypeTag; - -struct Bool { - static constexpr std::uint8_t value = 0; -}; -struct U8 { - static constexpr std::uint8_t value = 1; -}; -struct U64 { - static constexpr std::uint8_t value = 2; -}; -struct U128 { - static constexpr std::uint8_t value = 3; -}; -struct TAddress { - static constexpr std::uint8_t value = 4; -}; -struct TSigner { - static constexpr std::uint8_t value = 5; -}; -struct Vector { - static constexpr std::uint8_t value = 6; - std::vector tags; -}; - -class StructTag { -public: - explicit StructTag(Address accountAddress, Identifier module, Identifier name, std::vector typeParams) noexcept; - [[nodiscard]] Data serialize(bool withResourceTag = true) const noexcept; - [[nodiscard]] ModuleId moduleID() const noexcept { return {mAccountAddress, mName}; }; - [[nodiscard]] std::string string() const noexcept; - -private: - Address mAccountAddress; - Identifier mModule; - Identifier mName; - std::vector mTypeParams; -}; - -// C++ limitation, the first StructTag will serialize with ResourceTag, the inner one will use the value 7 instead. Tweaking by wrapping the struct -struct TStructTag { - static constexpr std::uint8_t value = 7; - StructTag st; -}; - -struct TypeTag { - using TypeTagVariant = std::variant; - TypeTagVariant tags; -}; - -std::string TypeTagToString(const TypeTag& typeTag) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const StructTag& st) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, Bool) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U8) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U64) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, U128) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, TAddress) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, TSigner) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const Vector& t) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const TStructTag& t) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const TypeTag& t) noexcept; -static const TypeTag gTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address::one(), "aptos_coin", "AptosCoin", {})})}; -static const TypeTag gOfferNftTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address::three(), "token_transfers", "offer_script", {})})}; - -} // namespace TW::Aptos diff --git a/src/Aptos/Signer.cpp b/src/Aptos/Signer.cpp deleted file mode 100644 index 992d91dd6f6..00000000000 --- a/src/Aptos/Signer.cpp +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Signer.h" -#include "Address.h" -#include "Hash.h" -#include "MoveTypes.h" -#include "TransactionBuilder.h" -#include "TransactionPayload.h" - -namespace { -template -void serializeToArgs(std::vector& args, T&& toSerialize) { - TW::BCS::Serializer serializer; - serializer << std::forward(toSerialize); - args.emplace_back(serializer.bytes); -} -} // namespace - -namespace TW::Aptos { - -template -std::pair, nlohmann::json> commonTransferPayload(const TPayload& input) { - std::vector args; - serializeToArgs(args, Address(input.to())); - serializeToArgs(args, input.amount()); - nlohmann::json argsJson = nlohmann::json::array({input.to(), std::to_string(input.amount())}); - return std::make_pair(args, argsJson); -} - -TransactionPayload transferPayload(const Proto::SigningInput& input) { - auto&& [args, argsJson] = commonTransferPayload(input.transfer()); - TransactionPayload payload = EntryFunction(gAptosAccountModule, "transfer", {}, args, argsJson); - return payload; -} - -TransactionPayload createAccountPayload(const Proto::SigningInput& input) { - std::vector args; - serializeToArgs(args, Address(input.create_account().auth_key())); - nlohmann::json argsJson = nlohmann::json::array({input.create_account().auth_key()}); - TransactionPayload payload = EntryFunction(gAptosAccountModule, "create_account", {}, args, argsJson); - return payload; -} - -TransactionPayload claimNftPayload(const Proto::ClaimNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.sender())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.sender(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "claim_script", {}, args, argsJson); - return payload; -} - -TransactionPayload nftOfferPayload(const Proto::OfferNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.receiver())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.receiver(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - std::to_string(msg.amount()) - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "offer_script", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaClaimPayload(const std::string& smart_contract_address, const Proto::TortugaClaim& msg) { - std::vector args; - serializeToArgs(args, msg.idx()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.idx()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "claim", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaStakePayload(const std::string& smart_contract_address, const Proto::TortugaStake& msg) { - std::vector args; - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.amount()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "stake", {}, args, argsJson); - return payload; -} - -TransactionPayload tortugaUnStakePayload(const std::string& smart_contract_address, const Proto::TortugaUnstake& msg) { - std::vector args; - serializeToArgs(args, msg.amount()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - std::to_string(msg.amount()) - }); - // clang-format on - ModuleId tortugaStakeModule{Address(smart_contract_address), "stake_router"}; - TransactionPayload payload = EntryFunction(tortugaStakeModule, "unstake", {}, args, argsJson); - return payload; -} - -TransactionPayload cancelNftOfferPayload(const Proto::CancelOfferNftMessage& msg) { - std::vector args; - serializeToArgs(args, Address(msg.receiver())); - serializeToArgs(args, Address(msg.creator())); - serializeToArgs(args, msg.collectionname()); - serializeToArgs(args, msg.name()); - serializeToArgs(args, msg.property_version()); - // clang-format off - nlohmann::json argsJson = nlohmann::json::array( - { - msg.receiver(), - msg.creator(), - msg.collectionname(), - msg.name(), - std::to_string(msg.property_version()), - }); - // clang-format on - TransactionPayload payload = EntryFunction(gAptosTokenTransfersModule, "cancel_offer_script", {}, args, argsJson); - return payload; -} - -TransactionPayload tokenTransferPayload(const Proto::SigningInput& input) { - - auto&& [args, argsJson] = commonTransferPayload(input.token_transfer()); - auto& function = input.token_transfer().function(); - TypeTag tokenTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosCoinModule, "transfer", {tokenTransferTag}, args, argsJson); - return payload; -} - -TransactionPayload tokenTransferCoinsPayload(const Proto::SigningInput& input) { - auto&& [args, argsJson] = commonTransferPayload(input.token_transfer_coins()); - auto& function = input.token_transfer_coins().function(); - TypeTag tokenTransferTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosAccountModule, "transfer_coins", {tokenTransferTag}, args, argsJson); - return payload; -} - -TransactionPayload registerTokenPayload(const Proto::SigningInput& input) { - - auto& function = input.register_token().function(); - TypeTag tokenRegisterTag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(Address(function.account_address()), - function.module(), function.name(), {})})}; - TransactionPayload payload = EntryFunction(gAptosManagedCoinsModule, "register", {tokenRegisterTag}, {}); - return payload; -} - -TransactionBasePtr buildBlindTx(const Proto::SigningInput& input) { - if (nlohmann::json j = nlohmann::json::parse(input.any_encoded(), nullptr, false); j.is_discarded()) { - auto blindBuilder = std::make_unique(); - blindBuilder->encodedCallHex(input.any_encoded()); - return blindBuilder; - } else { - auto txBuilder = std::make_unique(); - txBuilder->sender(Address(input.sender())) - .sequenceNumber(input.sequence_number()) - .payload(EntryFunction::from_json(j)) - .maxGasAmount(input.max_gas_amount()) - .gasUnitPrice(input.gas_unit_price()) - .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())); - return txBuilder; - } -} - -TransactionBasePtr buildTx(const Proto::SigningInput& input) { - if (!input.any_encoded().empty()) { - return buildBlindTx(input); - } - - auto nftPayloadFunctor = [](const Proto::NftMessage& nftMessage) { - switch (nftMessage.nft_transaction_payload_case()) { - case Proto::NftMessage::kOfferNft: - return nftOfferPayload(nftMessage.offer_nft()); - case Proto::NftMessage::kCancelOfferNft: - return cancelNftOfferPayload(nftMessage.cancel_offer_nft()); - case Proto::NftMessage::kClaimNft: - return claimNftPayload(nftMessage.claim_nft()); - case Proto::NftMessage::NFT_TRANSACTION_PAYLOAD_NOT_SET: - throw std::runtime_error("Nft message payload not set"); - } - }; - auto liquidStakingFunctor = [](const Proto::LiquidStaking& liquidStakingMessage) { - switch (liquidStakingMessage.liquid_stake_transaction_payload_case()) { - case Proto::LiquidStaking::kStake: - return tortugaStakePayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.stake()); - case Proto::LiquidStaking::kUnstake: - return tortugaUnStakePayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.unstake()); - case Proto::LiquidStaking::kClaim: - return tortugaClaimPayload(liquidStakingMessage.smart_contract_address(), liquidStakingMessage.claim()); - case Proto::LiquidStaking::LIQUID_STAKE_TRANSACTION_PAYLOAD_NOT_SET: - return TransactionPayload(); - } - }; - auto payloadFunctor = [&input, &nftPayloadFunctor, &liquidStakingFunctor]() { - switch (input.transaction_payload_case()) { - case Proto::SigningInput::kTransfer: { - return transferPayload(input); - } - case Proto::SigningInput::kTokenTransfer: { - return tokenTransferPayload(input); - } - case Proto::SigningInput::kNftMessage: { - return nftPayloadFunctor(input.nft_message()); - } - case Proto::SigningInput::kCreateAccount: { - return createAccountPayload(input); - } - case Proto::SigningInput::kRegisterToken: { - return registerTokenPayload(input); - } - case Proto::SigningInput::kLiquidStakingMessage: { - return liquidStakingFunctor(input.liquid_staking_message()); - } - case Proto::SigningInput::kTokenTransferCoins: { - return tokenTransferCoinsPayload(input); - } - case Proto::SigningInput::TRANSACTION_PAYLOAD_NOT_SET: - throw std::runtime_error("Transaction payload should be set"); - } - }; - auto txBuilder = std::make_unique(); - txBuilder->sender(Address(input.sender())) - .sequenceNumber(input.sequence_number()) - .payload(payloadFunctor()) - .maxGasAmount(input.max_gas_amount()) - .gasUnitPrice(input.gas_unit_price()) - .expirationTimestampSecs(input.expiration_timestamp_secs()) - .chainId(static_cast(input.chain_id())); - return txBuilder; -} - -Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) { - return buildTx(input)->sign(input); -} - -TxCompiler::Proto::PreSigningOutput Signer::preImageHashes(const Proto::SigningInput& input) { - return buildTx(input)->preImage(); -} - -Proto::SigningOutput Signer::compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey) { - return buildTx(input)->compile(signature, publicKey); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/Signer.h b/src/Aptos/Signer.h deleted file mode 100644 index e0222515ca0..00000000000 --- a/src/Aptos/Signer.h +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "Data.h" -#include "../PrivateKey.h" -#include "../proto/Aptos.pb.h" -#include "../proto/TransactionCompiler.pb.h" - -namespace TW::Aptos { - -inline const Data gAptosSalt = data("APTOS::RawTransaction"); - -/// Helper class that performs Aptos transaction signing. -class Signer { -public: - /// Hide default constructor - Signer() = delete; - - /// Signs a Proto::SigningInput transaction - static Proto::SigningOutput sign(const Proto::SigningInput& input); - - static TxCompiler::Proto::PreSigningOutput preImageHashes(const Proto::SigningInput& input); - - static Proto::SigningOutput compile(const Proto::SigningInput& input, const Data& signature, const PublicKey& publicKey); -}; - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionBuilder.h b/src/Aptos/TransactionBuilder.h deleted file mode 100644 index 46bf3fbaa12..00000000000 --- a/src/Aptos/TransactionBuilder.h +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include "HexCoding.h" -#include "TransactionPayload.h" - -#include -#include - -namespace TW::Aptos { - -struct TransactionBase; - -using TransactionBasePtr = std::unique_ptr; - -struct TransactionBase { - virtual ~TransactionBase() = default; - - virtual TxCompiler::Proto::PreSigningOutput preImage() noexcept = 0; - - virtual Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) = 0; - - virtual Proto::SigningOutput sign(const Proto::SigningInput& input) = 0; -}; - -class BlindBuilder final : public TransactionBase { -public: - BlindBuilder() noexcept = default; - - BlindBuilder& encodedCallHex(const std::string& encodedCallHex) { - mEncodedCall = parse_hex(encodedCallHex); - return *this; - } - - TxCompiler::Proto::PreSigningOutput preImage() noexcept override { - TxCompiler::Proto::PreSigningOutput output; - // Aptos has no preImageHash. - output.set_data(mEncodedCall.data(), mEncodedCall.size()); - return output; - } - - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) override { - Proto::SigningOutput output; - const auto& pubKeyData = publicKey.bytes; - - BCS::Serializer serializer; - serializer.add_bytes(begin(mEncodedCall), end(mEncodedCall)); - - output.set_raw_txn(mEncodedCall.data(), mEncodedCall.size()); - output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); - output.mutable_authenticator()->set_signature(signature.data(), signature.size()); - serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; - output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); - - // clang-format off - nlohmann::json json = { - {"type", "ed25519_signature"}, - {"public_key", hexEncoded(pubKeyData)}, - {"signature", hexEncoded(signature)} - }; - // clang-format on - output.set_json(json.dump()); - - return output; - } - - Proto::SigningOutput sign(const Proto::SigningInput& input) override { - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - auto signature = privateKey.sign(mEncodedCall, TWCurveED25519); - return compile(signature, publicKey); - } - -private: - Data mEncodedCall; -}; - -// Standard transaction builder. -class TransactionBuilder final : public TransactionBase { -public: - TransactionBuilder() noexcept = default; - - TransactionBuilder& sender(Address sender) noexcept { - mSender = sender; - return *this; - } - - TransactionBuilder& sequenceNumber(std::uint64_t sequenceNumber) noexcept { - mSequenceNumber = sequenceNumber; - return *this; - } - - TransactionBuilder& payload(TransactionPayload payload) noexcept { - mPayload = std::move(payload); - return *this; - } - - TransactionBuilder& maxGasAmount(std::uint64_t maxGasAmount) noexcept { - mMaxGasAmount = maxGasAmount; - return *this; - } - - TransactionBuilder& gasUnitPrice(std::uint64_t gasUnitPrice) noexcept { - mGasUnitPrice = gasUnitPrice; - return *this; - } - - TransactionBuilder& expirationTimestampSecs(std::uint64_t expirationTimestampSecs) noexcept { - mExpirationTimestampSecs = expirationTimestampSecs; - return *this; - } - - TransactionBuilder& chainId(std::uint8_t chainId) noexcept { - mChainId = chainId; - return *this; - } - - BCS::Serializer prepareSerializer() noexcept { - BCS::Serializer serializer; - serializer << mSender << mSequenceNumber << mPayload << mMaxGasAmount << mGasUnitPrice << mExpirationTimestampSecs << mChainId; - return serializer; - } - - Data msgToSign() noexcept { - auto serialized = prepareSerializer().bytes; - auto preImageOutput = TW::Hash::sha3_256(gAptosSalt.data(), gAptosSalt.size()); - append(preImageOutput, serialized); - return preImageOutput; - } - - TxCompiler::Proto::PreSigningOutput preImage() noexcept override { - TxCompiler::Proto::PreSigningOutput output; - auto signingMsg = msgToSign(); - // Aptos has no preImageHash. - output.set_data(signingMsg.data(), signingMsg.size()); - return output; - } - - Proto::SigningOutput compile(const Data& signature, const PublicKey& publicKey) noexcept override { - Proto::SigningOutput output; - const auto& pubKeyData = publicKey.bytes; - - auto serializer = prepareSerializer(); - - output.set_raw_txn(serializer.bytes.data(), serializer.bytes.size()); - output.mutable_authenticator()->set_public_key(pubKeyData.data(), pubKeyData.size()); - output.mutable_authenticator()->set_signature(signature.data(), signature.size()); - - serializer << BCS::uleb128{.value = 0} << pubKeyData << signature; - output.set_encoded(serializer.bytes.data(), serializer.bytes.size()); - - // https://fullnode.devnet.aptoslabs.com/v1/spec#/operations/submit_transaction - // clang-format off - nlohmann::json json = { - {"sender", mSender.string()}, - {"sequence_number", std::to_string(mSequenceNumber)}, - {"max_gas_amount", std::to_string(mMaxGasAmount)}, - {"gas_unit_price", std::to_string(mGasUnitPrice)}, - {"expiration_timestamp_secs", std::to_string(mExpirationTimestampSecs)}, - {"payload", payloadToJson(mPayload)}, - {"signature", { - {"type", "ed25519_signature"}, - {"public_key", hexEncoded(pubKeyData)}, - {"signature", hexEncoded(signature)}} - } - }; - // clang-format on - output.set_json(json.dump()); - return output; - } - - Proto::SigningOutput sign(const Proto::SigningInput& input) noexcept override { - auto signingMsg = msgToSign(); - auto privateKey = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto signature = privateKey.sign(signingMsg, TWCurveED25519); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519); - return compile(signature, publicKey); - } - -private: - Address mSender{}; - std::uint64_t mSequenceNumber{}; - TransactionPayload mPayload{}; - std::uint64_t mMaxGasAmount{}; - std::uint64_t mGasUnitPrice{}; - std::uint64_t mExpirationTimestampSecs{}; - std::uint8_t mChainId{}; -}; - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.cpp b/src/Aptos/TransactionPayload.cpp deleted file mode 100644 index e3bf73bccb7..00000000000 --- a/src/Aptos/TransactionPayload.cpp +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "rust/bindgen/WalletCoreRSBindgen.h" -#include -#include - -namespace TW::Aptos { - -EntryFunction::EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs) noexcept - : mModule(std::move(module)), mFunction(std::move(function)), mTyArgs(std::move(tyArgs)), mArgs(std::move(args)), mJsonArgs(std::move(jsonArgs)) { -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept { - stream << entryFunction.module() << entryFunction.function() << entryFunction.tyArgs() << entryFunction.args(); - return stream; -} - -nlohmann::json payloadToJson(const TransactionPayload& payload) { - auto visit_functor = [](const TransactionPayload& value) -> nlohmann::json { - if (auto* entryFunction = std::get_if(&value); entryFunction) { - return entryFunction->json(); - } else { - return {}; - } - }; - - return std::visit(visit_functor, payload); -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const Script& script) noexcept { - return stream; -} - -BCS::Serializer& operator<<(BCS::Serializer& stream, [[maybe_unused]] const ModuleBundle& moduleBundle) noexcept { - return stream; -} - -nlohmann::json EntryFunction::json() const noexcept { - nlohmann::json tyArgsJson = nlohmann::json::array(); - for (auto&& cur : mTyArgs) { - tyArgsJson.emplace_back(TypeTagToString(cur)); - } - // clang-format off - nlohmann::json out = { - {"type", "entry_function_payload"}, - {"function", mModule.shortString() + "::" + mFunction}, - {"type_arguments", tyArgsJson}, - {"arguments", mJsonArgs.empty() ? nlohmann::json::array() : mJsonArgs} - }; - // clang-format on - return out; -} - -EntryFunction EntryFunction::from_json(const nlohmann::json& payload) noexcept { - auto splitFunctor = [](std::string s, std::string_view delimiter) { - size_t pos_start = 0, pos_end, delim_len = delimiter.size(); - std::string token; - std::vector output; - - while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { - token = s.substr(pos_start, pos_end - pos_start); - pos_start = pos_end + delim_len; - output.emplace_back(token); - } - - output.emplace_back(s.substr(pos_start)); - return output; - }; - auto functionSplitted = splitFunctor(payload.at("function").get(), "::"); - auto moduleId = ModuleId(Address(functionSplitted[0]), functionSplitted[1]); - std::vector args; - for (auto&& cur : payload.at("arguments")) { - auto curStr = cur.get(); - auto res = Rust::parse_function_argument_to_bcs(curStr.c_str()); - if (res.code != Rust::OK_CODE) { - // TODO consider exiting this function. - args.emplace_back(); - continue; - } - args.emplace_back(parse_hex(res.result)); - Rust::free_string(res.result); - } - - std::vector tags; - - for (auto&& cur : payload.at("type_arguments")) { - auto curStr = cur.get(); - switch (Rust::parse_type_tag(curStr.c_str())) { - case Rust::ETypeTag::Bool: - break; - case Rust::ETypeTag::U8: - break; - case Rust::ETypeTag::U64: - break; - case Rust::ETypeTag::U128: - break; - case Rust::ETypeTag::Address: - break; - case Rust::ETypeTag::Signer: - break; - case Rust::ETypeTag::Vector: - break; - case Rust::ETypeTag::Struct: { - auto structSplitted = splitFunctor(curStr, "::"); - auto addr = Address(structSplitted[0]); - TypeTag tag = {TypeTag::TypeTagVariant(TStructTag{.st = StructTag(addr, structSplitted[1], structSplitted[2], {})})}; - tags.emplace_back(tag); - break; - } - case Rust::ETypeTag::Error: - break; - default: - break; - } - } - - return EntryFunction(moduleId, functionSplitted[2], tags, {args}, payload.at("arguments")); -} - -} // namespace TW::Aptos diff --git a/src/Aptos/TransactionPayload.h b/src/Aptos/TransactionPayload.h deleted file mode 100644 index 6041fc0ed8d..00000000000 --- a/src/Aptos/TransactionPayload.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include -#include - -namespace TW::Aptos { - -/// Call a Move entry function. -class EntryFunction { -public: - explicit EntryFunction(ModuleId module, Identifier function, std::vector tyArgs, std::vector args, nlohmann::json jsonArgs = {}) noexcept; - [[nodiscard]] const ModuleId& module() const noexcept { return mModule; } - [[nodiscard]] const Identifier& function() const noexcept { return mFunction; } - [[nodiscard]] const std::vector& tyArgs() const noexcept { return mTyArgs; } - [[nodiscard]] const std::vector& args() const noexcept { return mArgs; } - [[nodiscard]] nlohmann::json json() const noexcept; - static EntryFunction from_json(const nlohmann::json& json) noexcept; - -private: - ModuleId mModule; - Identifier mFunction; - std::vector mTyArgs; - std::vector mArgs; - nlohmann::json mJsonArgs; -}; - - -class Script { -}; - -class ModuleBundle { -}; - -BCS::Serializer& operator<<(BCS::Serializer& stream, const EntryFunction& entryFunction) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const Script& script) noexcept; -BCS::Serializer& operator<<(BCS::Serializer& stream, const ModuleBundle& moduleBundle) noexcept; -using TransactionPayload = std::variant; -nlohmann::json payloadToJson(const TransactionPayload& payload); - -} // namespace TW::Aptos diff --git a/src/BCS.cpp b/src/BCS.cpp deleted file mode 100644 index fb6015cdf1b..00000000000 --- a/src/BCS.cpp +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue - -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BCS.h" - -namespace TW::BCS { - -Serializer& operator<<(Serializer& stream, std::byte b) noexcept { - stream.add_byte(b); - return stream; -} - -Serializer& operator<<(Serializer& stream, uleb128 t) noexcept { - integral auto value = t.value; - - while (value >= 0x80) { - // Add the 7 lowest bits of data and set highest bit to 1 - stream << static_cast((value & 0x7f) | 0x80); - value >>= 7; - } - - // Add the remaining bits of data (highest bit is already 0 at this point) - stream << static_cast(value); - return stream; -} - -Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept { - stream << uleb128{static_cast(sv.size())}; - stream.add_bytes(sv.begin(), sv.end()); - return stream; -} - -Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept { - stream << false; - return stream; -} - -} // namespace TW::BCS diff --git a/src/BCS.h b/src/BCS.h deleted file mode 100644 index 563c204b8cb..00000000000 --- a/src/BCS.h +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue - -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "Data.h" -#include "concepts/tw_concepts.h" - -namespace TW::BCS { - -/// Implementation of BCS encoding (as specified by the Diem project, see github.com/diem/bcs#detailed-specifications) - -struct Serializer { - Data bytes; - - void add_byte(std::byte b) noexcept { - bytes.emplace_back(static_cast(b)); - } - - template - void add_bytes(Iterator first, Iterator last) noexcept { - std::transform(first, last, std::back_inserter(bytes), [](auto&& c) { - return static_cast(c); - }); - } - - void clear() noexcept { - bytes.clear(); - } -}; - -struct uleb128 { - uint32_t value; -}; - -namespace details { - -template -concept aggregate_struct = std::is_class_v> && std::is_aggregate_v>; - -template -concept map_container = requires(T t) { - typename T::key_type; - typename T::mapped_type; - { std::declval().size() } -> std::same_as; - }; - -template - requires integral || - floating_point || - std::same_as || - std::same_as || - std::same_as || - aggregate_struct || - map_container -struct is_serializable { - static constexpr auto value = true; -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = (is_serializable::value && ...); -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value && is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = is_serializable::value; -}; - -template -struct is_serializable> { - static constexpr auto value = (is_serializable::value && ...); -}; - -template -Serializer& serialize_integral_impl(Serializer& stream, T t, std::index_sequence) noexcept { - const char* bytes = reinterpret_cast(&t); - // Add each byte in little-endian order - return (stream << ... << static_cast(bytes[Is])); -} - -template -Serializer& serialize_tuple_impl(Serializer& stream, const T& t, std::index_sequence) noexcept { - return (stream << ... << std::get(t)); -} - -template -struct dependent_false { - static constexpr auto value = false; -}; - -template -constexpr auto to_tuple(T&& t) { - if constexpr (std::is_empty_v) { - return std::make_tuple(); - } else if constexpr (requires { [&t] { auto&& [a0] = t; }; }) { - auto&& [a0] = std::forward(t); - return std::make_tuple(a0); - } else if constexpr (requires { [&t] { auto&& [a0, a1] = t; }; }) { - auto&& [a0, a1] = std::forward(t); - return std::make_tuple(a0, a1); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2] = t; }; }) { - auto&& [a0, a1, a2] = std::forward(t); - return std::make_tuple(a0, a1, a2); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3] = t; }; }) { - auto&& [a0, a1, a2, a3] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4] = t; }; }) { - auto&& [a0, a1, a2, a3, a4] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3, a4); - } else if constexpr (requires { [&t] { auto&& [a0, a1, a2, a3, a4, a5] = t; }; }) { - auto&& [a0, a1, a2, a3, a4, a5] = std::forward(t); - return std::make_tuple(a0, a1, a2, a3, a4, a5); - } else { - static_assert(dependent_false::value, "the structure has more than 6 members"); - } -} - -template -Serializer& serialize_struct_impl(Serializer& stream, const T& t) noexcept { - return stream << to_tuple(t); -} -} // namespace details - -template -concept PrimitiveSerializable = details::is_serializable::value; - -template -concept CustomSerializable = requires(T t) { - { std::declval() << t } -> std::same_as; - }; - -template -concept Serializable = PrimitiveSerializable || CustomSerializable; - -Serializer& operator<<(Serializer& stream, std::byte b) noexcept; - -template -Serializer& operator<<(Serializer& stream, T t) noexcept { - return details::serialize_integral_impl(stream, t, std::make_index_sequence{}); -} - -Serializer& operator<<(Serializer& stream, uleb128 t) noexcept; - -Serializer& operator<<(Serializer& stream, std::string_view sv) noexcept; - -template -Serializer& operator<<(Serializer& stream, const std::optional o) noexcept { - if (o.has_value()) { - stream << true; - stream << o.value(); - } else { - stream << false; - } - return stream; -} - -Serializer& operator<<(Serializer& stream, std::nullopt_t) noexcept; - -template -Serializer& operator<<(Serializer& stream, const std::tuple& t) noexcept { - return details::serialize_tuple_impl(stream, t, std::make_index_sequence{}); -} - -template -Serializer& operator<<(Serializer& stream, const std::pair& t) noexcept { - return details::serialize_tuple_impl(stream, t, std::make_index_sequence<2>{}); -} - -template -Serializer& operator<<(Serializer& stream, const T& t) noexcept { - return details::serialize_struct_impl(stream, t); -} - -template -Serializer& operator<<(Serializer& stream, const std::vector& t) noexcept { - stream << uleb128{static_cast(t.size())}; - for (auto&& cur: t) { - stream << cur; - } - return stream; -} - -template -Serializer& operator<<(Serializer& stream, const std::variant& t) noexcept { - stream << uleb128{static_cast(t.index())}; - std::visit([&stream](auto&& value) { stream << value; }, t); - return stream; -} - -template -Serializer& operator<<(Serializer& stream, const T& t) noexcept { - stream << uleb128{static_cast(t.size())}; - for (auto&& [k, v] : t) { - stream << std::make_tuple(k, v); - } - return stream; -} - -} // namespace TW::BCS diff --git a/swift/Tests/CoinAddressDerivationTests.swift b/swift/Tests/CoinAddressDerivationTests.swift index 468aa0c5fbf..df0a6c09048 100644 --- a/swift/Tests/CoinAddressDerivationTests.swift +++ b/swift/Tests/CoinAddressDerivationTests.swift @@ -306,7 +306,7 @@ class CoinAddressDerivationTests: XCTestCase { let expectedResult = "EQDgEMqToTacHic7SnvnPFmvceG5auFkCcAw0mSCvzvKUfk9"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .aptos: - let expectedResult = "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; + let expectedResult = "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"; assertCoinDerivation(coin, expectedResult, derivedAddress, address) case .nebl: let expectedResult = "NgDVaXAwNgBwb88xLiFKomfBmPkEh9F2d7"; diff --git a/tests/chains/Aptos/AddressTests.cpp b/tests/chains/Aptos/AddressTests.cpp index 8c758428154..fc68f647a88 100644 --- a/tests/chains/Aptos/AddressTests.cpp +++ b/tests/chains/Aptos/AddressTests.cpp @@ -6,7 +6,7 @@ // file LICENSE at the root of the source code distribution tree. #include "HexCoding.h" -#include "Aptos/Address.h" +#include "Aptos/Entry.h" #include "PublicKey.h" #include "PrivateKey.h" #include @@ -15,49 +15,38 @@ namespace TW::Aptos::tests { TEST(AptosAddress, Valid) { - ASSERT_TRUE(Address::isValid("0x1")); - ASSERT_TRUE(Address::isValid(Address::one().string())); - ASSERT_TRUE(Address::isValid(Address::zero().string())); - ASSERT_TRUE(Address::isValid("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); - ASSERT_TRUE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); - ASSERT_TRUE(Address::isValid("19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c")); - ASSERT_TRUE(Address::isValid("777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb")); - ASSERT_TRUE(Address::isValid("0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb")); - ASSERT_TRUE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175")); // too short -> automatically padded + Entry entry; + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x1", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x0", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "19aadeca9388e009d136245b9a67423f3eee242b03142849eb4f81a4a409e59c", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb", std::monostate{})); + ASSERT_TRUE(entry.validateAddress(TWCoinTypeAptos, "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175", std::monostate{})); } TEST(AptosAddress, Invalid) { - ASSERT_FALSE(Address::isValid("Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b")); // Invalid hex character - ASSERT_FALSE(Address::isValid("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb")); // Invalid length: too long + Entry entry; + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "Seff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175bb", std::monostate{})); + ASSERT_FALSE(entry.validateAddress(TWCoinTypeAptos, "0xSeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b", std::monostate{})); } TEST(AptosAddress, FromPrivateKey) { auto privateKey = PrivateKey(parse_hex("088baa019f081d6eab8dff5c447f9ce2f83c1babf3d03686299eaf6a1e89156e")); - auto address = Address(privateKey.getPublicKey(TWPublicKeyTypeED25519)); - ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); + auto pubkey = privateKey.getPublicKey(TWPublicKeyTypeED25519); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, pubkey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); } TEST(AptosAddress, FromPublicKey) { auto publicKey = PublicKey(parse_hex("ad0e293a56c9fc648d1872a00521d97e6b65724519a2676c2c47cb95d131cf5a"), TWPublicKeyTypeED25519); - auto address = Address(publicKey); - ASSERT_EQ(address.string(), "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); -} - -TEST(AptosAddress, FromString) { - auto address = Address("eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - ASSERT_EQ(address.string(), "0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - - - address = Address("0x777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - ASSERT_EQ(address.string(), "0x0000777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - address = Address("777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); - ASSERT_EQ(address.string(), "0x0000777821c78442e17d82c3d7a371f42de7189e4248e529fe6eee6bca40ddbb"); -} - -TEST(AptosAddress, ShortString) { - ASSERT_EQ(Address::one().string(), "0x0000000000000000000000000000000000000000000000000000000000000001"); - ASSERT_EQ(Address::one().shortString(), "1"); + Entry entry; + auto address = entry.deriveAddress(TWCoinTypeAptos, publicKey, TWDerivationDefault, std::monostate{}); + ASSERT_EQ(address, "0xe9c4d0b6fe32a5cc8ebd1e9ad5b54a0276a57f2d081dcb5e30342319963626c3"); } } // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/CompilerTests.cpp b/tests/chains/Aptos/CompilerTests.cpp index 6f731cfbfe5..16d7ae5dc11 100644 --- a/tests/chains/Aptos/CompilerTests.cpp +++ b/tests/chains/Aptos/CompilerTests.cpp @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Aptos/Signer.h" +#include "proto/Aptos.pb.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" @@ -66,12 +66,12 @@ TEST(AptosCompiler, StandardTransaction) { "gas_unit_price": "100", "max_gas_amount": "3296766", "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], "function": "0x1::aptos_account::transfer", "type": "entry_function_payload", "type_arguments": [] }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "99", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", @@ -160,7 +160,7 @@ TEST(AptosCompiler, BlindTransactionJson) { ], "type": "entry_function_payload" }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "42", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", @@ -173,52 +173,4 @@ TEST(AptosCompiler, BlindTransactionJson) { assertJSONEqual(expectedJson, parsedJson); } -TEST(AptosCompiler, BlindTransactionHex) { - // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet - auto anyEncoded = "0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"; - - Proto::SigningInput input; - input.set_any_encoded(anyEncoded); - - auto inputString = input.SerializeAsString(); - auto inputStrData = TW::Data(inputString.begin(), inputString.end()); - - // Pre-hash the transaction. - - auto preImageHashesData = TransactionCompiler::preImageHashes(TWCoinTypeAptos, inputStrData); - TxCompiler::Proto::PreSigningOutput preSigningOutput; - preSigningOutput.ParseFromArray(preImageHashesData.data(), static_cast(preImageHashesData.size())); - auto actualDataToSign = data(preSigningOutput.data()); - - EXPECT_EQ(preSigningOutput.error(), Common::Proto::OK); - EXPECT_EQ(actualDataToSign, parse_hex(anyEncoded)); - - // Sign the pre-hash data. - - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - auto publicKey = privateKey.getPublicKey(TWPublicKeyTypeED25519).bytes; - auto signature = privateKey.sign(actualDataToSign, TWCurveED25519); - EXPECT_EQ(hex(signature), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - - // Compile the transaction. - - auto outputData = TransactionCompiler::compileWithSignatures(TWCoinTypeAptos, inputStrData, {signature}, {publicKey}); - Proto::SigningOutput output; - output.ParseFromArray(outputData.data(), static_cast(outputData.size())); - - EXPECT_EQ(output.error(), Common::Proto::OK); - ASSERT_EQ(hex(output.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(output.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - ASSERT_EQ(hex(output.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - nlohmann::json expectedJson = R"( - { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", - "type": "ed25519_signature" - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(output.json()); - assertJSONEqual(expectedJson, parsedJson); -} - } diff --git a/tests/chains/Aptos/MoveTypesTests.cpp b/tests/chains/Aptos/MoveTypesTests.cpp deleted file mode 100644 index 06ef076991c..00000000000 --- a/tests/chains/Aptos/MoveTypesTests.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include - -namespace TW::Aptos::tests { - -TEST(AptosMoveTypes, ModuleId) { - ModuleId module(Address::one(), "coin"); - ASSERT_EQ(module.address(), Address::one()); - ASSERT_EQ(module.name(), "coin"); - ASSERT_EQ(hex(module.accessVector()), "00000000000000000000000000000000000000000000000000000000000000000104636f696e"); - ASSERT_EQ(module.string(), "0x0000000000000000000000000000000000000000000000000000000000000001::coin"); - ASSERT_EQ(module.shortString(), "0x1::coin"); -} - -TEST(AptosMoveTypes, StructTag) { - auto functorTest = [](T value, const std::string expectedHex) { - TypeTag t{.tags = value}; - StructTag st(Address::one(), "abc", "abc", std::vector{{t}}); - ASSERT_EQ(st.moduleID().name(), "abc"); - ASSERT_EQ(st.moduleID().address(), Address::one()); - ASSERT_EQ(hex(st.serialize()), expectedHex); - }; - functorTest(Bool{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630100"); - functorTest(U8{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630101"); - functorTest(U64{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630102"); - functorTest(U128{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630103"); - functorTest(TAddress{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630104"); - functorTest(TSigner{}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630105"); - functorTest(Vector{.tags = std::vector{{TypeTag{.tags = U8{}}}}}, "0100000000000000000000000000000000000000000000000000000000000000010361626303616263010601"); - StructTag stInner(Address::one(), "foo", "bar", std::vector{{U8{}}}); - functorTest(TStructTag{stInner}, "01000000000000000000000000000000000000000000000000000000000000000103616263036162630107000000000000000000000000000000000000000000000000000000000000000103666f6f036261720101"); -} - -TEST(AptosMoveTypes, TypeTagDisplay) { - auto functorTest = [](const TypeTag &value, const std::string& expected) { - ASSERT_EQ(TypeTagToString(value), expected); - }; - functorTest(TypeTag{.tags = Bool{}}, "bool"); - functorTest(TypeTag{.tags = U8{}}, "u8"); - functorTest(TypeTag{.tags = U64{}}, "u64"); - functorTest(TypeTag{.tags = U128{}}, "u128"); - functorTest(TypeTag{.tags = TAddress{}}, "address"); - functorTest(TypeTag{.tags = TSigner{}}, "signer"); - TypeTag t{.tags = TypeTag::TypeTagVariant(Vector{.tags = {{U8{}}}})}; - functorTest(t, "vector"); - StructTag st(Address::one(), "foo", "bar", std::vector{{U8{}}}); - TypeTag anotherT{.tags = TypeTag::TypeTagVariant(st)}; - functorTest(anotherT, "0x1::foo::bar"); - functorTest(gTransferTag, "0x1::aptos_coin::AptosCoin"); -} - -} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/SignerTests.cpp b/tests/chains/Aptos/SignerTests.cpp deleted file mode 100644 index 65c0bc87089..00000000000 --- a/tests/chains/Aptos/SignerTests.cpp +++ /dev/null @@ -1,730 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "Aptos/Address.h" -#include "Aptos/Signer.h" -#include "HexCoding.h" -#include "PrivateKey.h" -#include "PublicKey.h" -#include "TestUtilities.h" -#include - -#include - -namespace TW::Aptos::tests { - -TEST(AptosSigner, ClaimNftTxSign) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x60b51e15140ec0b7650334e948fb447ce3cb13ae63492260461ebfa9d02e85c4?network=testnet - Proto::SigningInput input; - input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(19); - auto& tf = *input.mutable_nft_message()->mutable_claim_nft(); - tf.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3013000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c636c61696d5f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40ede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0"], - "function": "0x3::token_transfers::claim_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "19", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xede1ffb5f8f663741c2ca9597af44af81c98f7a910261bb4125f758fd0c0ebbf5bacb34f1196ad45153177729eb6d478676b364ab747da17602713f65ca2dd0a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, NftOfferTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x514e473618bd3cb89a2b110b7c473db9a2e10532f98eb42d02d86fb31c00525d?network=testnet - Proto::SigningInput input; - input.set_sender("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - input.set_sequence_number(1); - auto& tf = *input.mutable_nft_message()->mutable_offer_nft(); - tf.set_receiver("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - tf.set_amount(1); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("7bebb6d543d17f6fe4e685cfab239fa37896edd594ff859f1df32f244fb707e2")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); - ASSERT_EQ(hex(result.encoded()), "783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee01000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572730c6f666665725f73637269707400062007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000080100000000000000fe4d3200000000006400000000000000c2276ada00000000020020d1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a411340af5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0", "1"], - "function": "0x3::token_transfers::offer_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "sequence_number": "1", - "signature": { - "public_key": "0xd1d99b67e37b483161a0fa369c46f34a3be4863c20e20fc7cdc669c0826a4113", - "signature": "0xaf5c7357a83c69e3f425beb23eaf232f8bb36dea3b7cad4a7ab8d735cee999c8ec5285005adf69dc85a6c34b042dd0308fe92b76dad5d6ac88c7b9259902c10f", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, CancelNftOfferTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x0b8c64e6847c368e4c6bd2cce0e9eab378971b0ef2e3bc40cbd292910a80201d?network=testnet - Proto::SigningInput input; - input.set_sender("0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(21); - auto& tf = *input.mutable_nft_message()->mutable_cancel_offer_nft(); - tf.set_receiver("0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee"); - tf.set_creator("0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac"); - tf.set_collectionname("Topaz Troopers"); - tf.set_name("Topaz Trooper #20068"); - tf.set_property_version(0); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3015000000000000000200000000000000000000000000000000000000000000000000000000000000030f746f6b656e5f7472616e73666572731363616e63656c5f6f666665725f736372697074000520783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee209125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac0f0e546f70617a2054726f6f706572731514546f70617a2054726f6f70657220233230303638080000000000000000fe4d3200000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": [ - "0x783135e8b00430253a22ba041d860c373d7a1501ccf7ac2d1ad37a8ed2775aee", - "0x9125e4054d884fdc7296b66e12c0d63a7baa0d88c77e8e784987c0a967c670ac", - "Topaz Troopers", "Topaz Trooper #20068", "0"], - "function": "0x3::token_transfers::cancel_offer_script", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "21", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x826722d374e276f618123e77da3ac024c89a3f97db9e09e19aa8ed06c3cdfc57d4a21c7890137f9a7c0447cc303447ba10ca5b1908e889071e0a68f48c0f260a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb4d62afd3862116e060dd6ad9848ccb50c2bc177799819f1d29c059ae2042467?network=devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(99); - auto& tf = *input.mutable_transfer(); - tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_amount(1000); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(33); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3063000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e74087472616e7366657200022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008e803000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c405707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], - "function": "0x1::aptos_account::transfer", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "99", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x5707246db31e2335edc4316a7a656a11691d1d1647f6e864d1ab12f43428aaaf806cf02120d0b608cdd89c5c904af7b137432aacdd60cc53f9fad7bd33578e01", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, CreateAccount) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x477141736de6b0936a6c3734e4d6fd018c7d21f1f28f99028ef0bc6881168602?network=Devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(0); - auto& tf = *input.mutable_create_account(); - tf.set_auth_key("0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(33); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3000000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e6372656174655f6163636f756e740001203aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30efe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40fcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x3aa1672641a4e17b3d913b4c0301e805755a80b12756fc729c5878f12344d30e"], - "function": "0x1::aptos_account::create_account", - "type": "entry_function_payload", - "type_arguments": [] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "0", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xfcba3dfbec76721454ef414955f09f159660a13886b4edd8c579e3c779c29073afe7b25efa3fef9b21c2efb1cf16b4247fc0e5c8f63fdcd1c8d87f5d59f44501", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignFromJson) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x7efd69e7f9462774b932ce500ab51c0d0dcc004cf272e09f8ffd5804c2a84e33?network=mainnet - auto payloadJson = R"( - { - "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", - "type_arguments": [ - "0x1::aptos_coin::AptosCoin", - "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" - ], - "arguments": [ - "1000000", - "49329" - ], - "type": "entry_function_payload" - })"_json; - Proto::SigningInput input; - input.set_sequence_number(42); - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f302a000000000000000216fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c0f416e696d6553776170506f6f6c563127737761705f65786163745f636f696e735f666f725f636f696e735f335f706169725f656e747279040700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e0007881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f404636f696e044d4f4a4f0007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa05617373657404555344540007f22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa056173736574045553444300020840420f000000000008b1c0000000000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c4042cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x16fe2df00ea7dde4a63409201f7f4e536bde7bb7335526a35d05111e68aa322c::AnimeSwapPoolV1::swap_exact_coins_for_coins_3_pair_entry", - "type_arguments": [ - "0x1::aptos_coin::AptosCoin", - "0x881ac202b1f1e6ad4efcff7a1d0579411533f2502417a19211cfc49751ddb5f4::coin::MOJO", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT", - "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC" - ], - "arguments": [ - "1000000", - "49329" - ], - "type": "entry_function_payload" - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "42", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x42cd67406e85afd1e948e7ad7f5f484fb4c60d82b267c6b6b28a92301e228b983206d2b87cd5487cf9acfb0effbd183ab90123570eb2e047cb152d337152210b", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingStake) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/userTxnOverview?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(19); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_stake = *ls.mutable_stake(); - ls_stake.set_amount(100000000); - input.set_max_gas_amount(5554); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(1670240203); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1300000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000b2150000000000006400000000000000cbd78d630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc44022d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "19", - "max_gas_amount": "5554", - "gas_unit_price": "100", - "expiration_timestamp_secs": "1670240203", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0x22d3166c3003f9c24a35fd39c71eb27e0d2bb82541be610822165c9283f56fefe5a9d46421b9caf174995bd8f83141e60ea8cff521ecf4741fe19e6ae9a5680d", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingUnstake) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(20); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_unstake = *ls.mutable_unstake(); - ls_unstake.set_amount(99178100); - input.set_max_gas_amount(2371); - input.set_gas_unit_price(120); - input.set_expiration_timestamp_secs(1670304949); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1400000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e9050000000043090000000000007800000000000000b5d48e630000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4406994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "20", - "max_gas_amount": "2371", - "gas_unit_price": "120", - "expiration_timestamp_secs": "1670304949", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0x6994b917432ad70ae84d2ce1484e6aece589a68aad1b7c6e38c9697f2a012a083a3a755c5e010fd3d0f149a75dd8d257acbd09f10800e890074e5ad384314d0c", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TortugaLiquidStakingClaim) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x9fc874de7a7d3e813d9a1658d896023de270a0096a5e258c196005656ace7d54?network=mainnet - Proto::SigningInput input; - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_sequence_number(28); - auto& ls = *input.mutable_liquid_staking_message(); - ls.set_smart_contract_address("0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f"); - auto& ls_claim = *ls.mutable_claim(); - ls_claim.set_idx(0); - input.set_max_gas_amount(10); - input.set_gas_unit_price(148); - input.set_expiration_timestamp_secs(1682066783); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("786fc7ceca43b4c1da018fea5d96f35dfdf5605f220b1205ff29c5c6d9eccf05")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - EXPECT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001"); - EXPECT_EQ(hex(result.authenticator().signature()), "c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); - EXPECT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc1c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657205636c61696d00010800000000000000000a0000000000000094000000000000005f4d42640000000001002089e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc440c936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03"); - nlohmann::json expectedJson = R"( - { - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "28", - "max_gas_amount": "10", - "gas_unit_price": "148", - "expiration_timestamp_secs": "1682066783", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::claim", - "type_arguments": [], - "arguments": [ - "0" - ], - "type": "entry_function_payload" - }, - "signature": { - "public_key": "0x89e0211d7e19c7d3a8e2030fe16c936a690ca9b95569098c5d2bf1031ff44bc4", - "signature": "0xc936584f89777e1fe2d5dd75cd8d9c514efc445810ba22f462b6fe7229c6ec7fc1c8b25d3e233eafaa8306433b3220235e563498ba647be38cac87ff618e3d03", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignStaking) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x25dca849cb4ebacbff223139f7ad5d24c37c225d9506b8b12a925de70429e685/payload - auto payloadJson = R"( - { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - })"_json; - - Proto::SigningInput input; - input.set_sequence_number(43); - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b"); - ASSERT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2b00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f75746572057374616b6500010800e1f50500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::stake", - "type_arguments": [], - "arguments": [ - "100000000" - ], - "type": "entry_function_payload" - }, - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "43", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xa41b7440a50f36e8491319508734acb55488abc6d88fbc9cb2b37ba23210f01f5d08c856cb7abf18c414cf9302ee144450bd99495a7e21e61f624764db91eb0b", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, BlindSignUnStaking) { - // Successfully broadcasted: https://explorer.aptoslabs.com/txn/0x92edb4f756fe86118e34a0e64746c70260ee02c2ae2cf402b3e39f6a282ce968/payload - auto payloadJson = R"( - { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - })"_json; - Proto::SigningInput input; - input.set_sequence_number(44); - input.set_sender("0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc"); - input.set_gas_unit_price(100); - input.set_max_gas_amount(100011); - input.set_expiration_timestamp_secs(3664390082); - input.set_any_encoded(payloadJson.dump()); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - input.set_chain_id(1); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a"); - ASSERT_EQ(hex(result.encoded()), "f3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc2c00000000000000028f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f0c7374616b655f726f7574657207756e7374616b650001087456e90500000000ab860100000000006400000000000000c2276ada00000000010020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40a58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a"); - nlohmann::json expectedJson = R"( -{ - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "100011", - "payload": { - "function": "0x8f396e4246b2ba87b51c0739ef5ea4f26515a98375308c31ac2ec1e42142a57f::stake_router::unstake", - "type_arguments": [], - "arguments": [ - "99178100" - ], - "type": "entry_function_payload" - }, - "sender": "0xf3d7f364dd7705824a5ebda9c7aab6cb3fc7bb5b58718249f12defec240b36cc", - "sequence_number": "44", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xa58ad5e3331beb8c0212a18a1f932207cb664b78f5aad3cb1fe7435e0e0e053247ce49b38fd67b064bed34ed643eb6a03165d77c681d7d73ac3161ab984a960a", - "type": "ed25519_signature" - } -} - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - - -TEST(AptosSigner, BlindSign) { - // successfully broadcasted https://explorer.aptoslabs.com/txn/0xd95857a9e644528708778a3a0a6e13986751944fca30eaac98853c1655de0422?network=Devnet - // encoded submission with: - // curl --location --request POST 'https://fullnode.devnet.aptoslabs.com/v1/transactions/encode_submission' \ - //--header 'Content-Type: application/json' \ - //--header 'Cookie: AWSALB=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5; AWSALBCORS=0zI2zWypvEr0I3sGM6vnyHSxYO1D0aaMXfyA/2VwhA291aJJ80Yz67Fur50sXPFBI8dKKID4p8DShj1KkEXPY/NGAylpOj1EG2M2Qjuu1B38Q5C+dZW2CHT+IAZ5' \ - //--data-raw '{ - // "expiration_timestamp_secs": "3664390082", - // "gas_unit_price": "100", - // "max_gas_amount": "3296766", - // "payload": { - // "function": "0x4633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b298837::pool::swap_y_to_x", - // "type_arguments": [ - // "0xdeae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e01::devnet_coins::DevnetUSDT", - // "0x1::aptos_coin::AptosCoin" - // ], - // "arguments": [ - // "100000000", - // "0" - // ], - // "type": "entry_function_payload" - // }, - // "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - // "sequence_number": "2" - //}' - Proto::SigningInput input; - input.set_any_encoded("0xb5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada0000000021"); - ASSERT_EQ(hex(result.authenticator().signature()), "9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - ASSERT_EQ(hex(result.encoded()), "b5e97db07fa0bd0e5598aa3643a9bc6f6693bddc1a9fec9e674a461eaa00b19307968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f300200000000000000024633134869a61c41ad42eaca028d71c5b8b4109ffd69e1aa99c35a621b29883704706f6f6c0b737761705f795f746f5f780207deae46f81671e76f444e2ce5a299d9e1ea06a8fa26e81dfd49aa7fa5a5a60e010c6465766e65745f636f696e730a4465766e657455534454000700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e00020800e1f50500000000080000000000000000fe4d3200000000006400000000000000c2276ada00000000210020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c409e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b"); - nlohmann::json expectedJson = R"( - { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x9e81026fdd43986f4d5588afdab875cd18b64dc15b3489fcc00ed46fc361915b27e23e0cefe6d23698ee76a562915fe85e99185dbc1dd29ba720f7fad144af0b", - "type": "ed25519_signature" - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenRegisterTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xe591252daed785641bfbbcf72a5d17864568cf32e04c0cc9129f3a13834d0e8e?network=testnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(23); - auto& tf = *input.mutable_register_token(); - tf.mutable_function()->set_account_address("0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379"); - tf.mutable_function()->set_module("move_coin"); - tf.mutable_function()->set_name("MoveCoin"); - input.set_max_gas_amount(2000000); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(2); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada0000000002"); - ASSERT_EQ(hex(result.authenticator().signature()), "e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3017000000000000000200000000000000000000000000000000000000000000000000000000000000010c6d616e616765645f636f696e0872656769737465720107e4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379096d6f76655f636f696e084d6f7665436f696e000080841e00000000006400000000000000c2276ada00000000020020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c40e230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "2000000", - "payload": { - "arguments": [], - "function": "0x1::managed_coin::register", - "type": "entry_function_payload", - "type_arguments": ["0xe4497a32bf4a9fd5601b27661aa0b933a923191bf403bd08669ab2468d43b379::move_coin::MoveCoin"] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "23", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0xe230b49f552fb85356dbec9df13f0dc56228eb7a9c29a8af3a99f4ae95b86c72bdcaa4ff1e9beb0bd81c298b967b9d97449856ec8bc672a08e2efef345c37100", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenTxSign) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0xb5b383a5c7f99b2edb3bed9533f8169a89051b149d65876a82f4c0b9bf78a15b?network=Devnet - Proto::SigningInput input; - input.set_sender("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - input.set_sequence_number(24); - auto& tf = *input.mutable_token_transfer(); - tf.set_to("0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); - tf.set_amount(100000); - tf.mutable_function()->set_account_address("0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9"); - tf.mutable_function()->set_module("coins"); - tf.mutable_function()->set_name("BTC"); - input.set_max_gas_amount(3296766); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(32); - auto privateKey = PrivateKey(parse_hex("5d996aa76b3212142792d9130796cd2e11e3c445a93118c08414df4f66bc60ec")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - ASSERT_EQ(hex(result.raw_txn()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada0000000020"); - ASSERT_EQ(hex(result.authenticator().signature()), "7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); - ASSERT_EQ(hex(result.encoded()), "07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30180000000000000002000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010743417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b905636f696e730342544300022007968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f3008a086010000000000fe4d3200000000006400000000000000c2276ada00000000200020ea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c407643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "3296766", - "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","100000"], - "function": "0x1::coin::transfer", - "type": "entry_function_payload", - "type_arguments": ["0x43417434fd869edee76cca2a4d2301e528a1551b1d719b75c350c3c97d15b8b9::coins::BTC"] - }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", - "sequence_number": "24", - "signature": { - "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", - "signature": "0x7643ec8aae6198bd13ca6ea2962265859cba5a228e7d181131f6c022700dd02a7a04dc0345ad99a0289e5ab80b130b3864e6404079980bc226f1a13aee7d280a", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -TEST(AptosSigner, TokenTransferCoins) { - // Successfully broadcasted https://explorer.aptoslabs.com/txn/0x197d40ea12e2bfc65a0a913b9f4ca3b0b0208fe0c1514d3d55cef3d5bcf25211?network=mainnet - Proto::SigningInput input; - input.set_sender("0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25"); - input.set_sequence_number(2); - auto& tf = *input.mutable_token_transfer_coins(); - tf.set_to("0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c"); - tf.set_amount(10000); - tf.mutable_function()->set_account_address("0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9"); - tf.mutable_function()->set_module("mee_coin"); - tf.mutable_function()->set_name("MeeCoin"); - input.set_max_gas_amount(2000); - input.set_gas_unit_price(100); - input.set_expiration_timestamp_secs(3664390082); - input.set_chain_id(1); - auto privateKey = PrivateKey(parse_hex("e7f56c77189e03699a75d8ec5c090e41f3d9d4783bc49c33df8a93d915e10de8")); - input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); - auto result = Signer::sign(input); - - ASSERT_EQ(hex(result.raw_txn()), "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001"); - ASSERT_EQ(hex(result.authenticator().signature()), "30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d"); - ASSERT_EQ(hex(result.encoded()), "1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf2502000000000000000200000000000000000000000000000000000000000000000000000000000000010d6170746f735f6163636f756e740e7472616e736665725f636f696e730107e9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9086d65655f636f696e074d6565436f696e000220b7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c081027000000000000d0070000000000006400000000000000c2276ada0000000001002062e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca83694030ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d"); - nlohmann::json expectedJson = R"( - { - "expiration_timestamp_secs": "3664390082", - "gas_unit_price": "100", - "max_gas_amount": "2000", - "payload": { - "arguments": ["0xb7c7d12080209e9dc14498c80200706e760363fb31782247e82cf57d1d6e5d6c","10000"], - "function": "0x1::aptos_account::transfer_coins", - "type": "entry_function_payload", - "type_arguments": ["0xe9c192ff55cffab3963c695cff6dbf9dad6aff2bb5ac19a6415cad26a81860d9::mee_coin::MeeCoin"] - }, - "sender": "0x1869b853768f0ba935d67f837a66b172dd39a60ca2315f8d4e0e669bbd35cf25", - "sequence_number": "2", - "signature": { - "public_key": "0x62e7a6a486553b56a53e89dfae3f780693e537e5b0a7ed33290780e581ca8369", - "signature": "0x30ebd7e95cb464677f411868e2cbfcb22bc01cc63cded36c459dff45e6d2f1354ae4e090e7dfbb509851c0368b343e0e5ecaf6b08e7c1b94c186530b0f7dee0d", - "type": "ed25519_signature" - } - } - )"_json; - nlohmann::json parsedJson = nlohmann::json::parse(result.json()); - assertJSONEqual(expectedJson, parsedJson); -} - -} // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TWAnySignerTests.cpp b/tests/chains/Aptos/TWAnySignerTests.cpp index 28f260970b6..44885c9f872 100644 --- a/tests/chains/Aptos/TWAnySignerTests.cpp +++ b/tests/chains/Aptos/TWAnySignerTests.cpp @@ -4,11 +4,10 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -#include "Aptos/Address.h" -#include "Aptos/Signer.h" #include "HexCoding.h" #include "PrivateKey.h" #include "PublicKey.h" +#include "proto/Aptos.pb.h" #include #include #include "TestUtilities.h" @@ -42,12 +41,12 @@ TEST(TWAnySignerAptos, TxSign) { "gas_unit_price": "100", "max_gas_amount": "3296766", "payload": { - "arguments": ["0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], + "arguments": ["0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30","1000"], "function": "0x1::aptos_account::transfer", "type": "entry_function_payload", "type_arguments": [] }, - "sender": "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", + "sender": "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30", "sequence_number": "99", "signature": { "public_key": "0xea526ba1710343d953461ff68641f1b7df5f23b9042ffa2d2a798d3adb3f3d6c", diff --git a/tests/chains/Aptos/TWAptosAddressTests.cpp b/tests/chains/Aptos/TWAptosAddressTests.cpp index 2b9e3fd8047..ec6612d0960 100644 --- a/tests/chains/Aptos/TWAptosAddressTests.cpp +++ b/tests/chains/Aptos/TWAptosAddressTests.cpp @@ -28,7 +28,7 @@ TEST(TWAptosAddress, HDWallet) { auto address = WRAP(TWAnyAddress, TWAnyAddressCreateWithPublicKey(publicKey.get(), TWCoinTypeAptos)); auto addressStr = WRAPS(TWAnyAddressDescription(address.get())); - assertStringsEqual(addressStr, "0x07968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); + assertStringsEqual(addressStr, "0x7968dab936c1bad187c60ce4082f307d030d780e91e694ae03aef16aba73f30"); } } // namespace TW::Aptos::tests diff --git a/tests/chains/Aptos/TransactionPayloadTests.cpp b/tests/chains/Aptos/TransactionPayloadTests.cpp deleted file mode 100644 index 45b501230c3..00000000000 --- a/tests/chains/Aptos/TransactionPayloadTests.cpp +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include -#include -#include - -namespace TW::Aptos::tests { - -TEST(AptosTransactionPayload, PancakeSwapPayload) { - auto pancakeSwapPayload=R"( - { -"arguments": [ - "0xc95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6" -], -"function": "0xc23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d50::IFO::release", -"type": "entry_function_payload", -"type_arguments": [ - "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::BUSD", - "0x48e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced90517::coins::DAI", - "0x9936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a::uints::U1" -] -} -)"_json; - - TransactionPayload payload = EntryFunction::from_json(pancakeSwapPayload); - BCS::Serializer serializer; - Address sender("0x2ce519d8cd60e0870e874e8000e8cbc87c8172e6acdbec83662b4c8cc3fc3de9"); - std::uint64_t sequenceNumber{75}; - std::uint64_t gasAmount{488130}; - std::uint64_t gasPrice{100}; - std::uint64_t expirationTime{199940521552}; - std::uint8_t chainId{1}; - serializer << sender << sequenceNumber << payload << gasAmount << gasPrice << expirationTime << chainId; - ASSERT_EQ(hex(serializer.bytes), "2ce519d8cd60e0870e874e8000e8cbc87c8172e6acdbec83662b4c8cc3fc3de94b0000000000000002c23c3b70956ce8d88fb18ad9ed3b463fe873cb045db3f6d2e2fb15b9aab71d500349464f0772656c65617365030748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730442555344000748e0e3958d42b8d452c9199d4a221d0d1b15d14655787453dbe77208ced9051705636f696e730344414900079936836587ca33240d3d3f91844651b16cb07802faf5e34514ed6f78580deb0a0575696e747302553100012120c95db29a67a848940829b3df6119b5e67b788ff0248676e4484c7c6f29c0f5e6c2720700000000006400000000000000503e628d2e00000001"); -} - -TEST(AptosTransactionPayload, PayLoadBasis) { - ModuleId module(Address::one(), "coin"); - std::uint64_t amount{1000}; - Address to("0xeeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b"); - BCS::Serializer serializer; - serializer << to; - std::vector args; - args.emplace_back(serializer.bytes); - serializer.clear(); - serializer << amount; - args.emplace_back(serializer.bytes); - TransactionPayload payload = EntryFunction(module, "transfer", {gTransferTag}, args); - ASSERT_EQ(std::get(payload).module().name(), "coin"); - ASSERT_EQ(std::get(payload).module().shortString(), "0x1::coin"); - serializer.clear(); - serializer << payload; - ASSERT_EQ(hex(serializer.bytes), "02000000000000000000000000000000000000000000000000000000000000000104636f696e087472616e73666572010700000000000000000000000000000000000000000000000000000000000000010a6170746f735f636f696e094170746f73436f696e000220eeff357ea5c1a4e7bc11b2b17ff2dc2dcca69750bfef1e1ebcaccf8c8018175b08e803000000000000"); -} - -} // namespace TW::Aptos::tests diff --git a/tests/common/BCSTests.cpp b/tests/common/BCSTests.cpp deleted file mode 100644 index f12f8242612..00000000000 --- a/tests/common/BCSTests.cpp +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright © 2017-2023 Trust Wallet. -// Created by Clément Doumergue -// -// This file is part of Trust. The full Trust copyright notice, including -// terms governing use, modification, and redistribution, is contained in the -// file LICENSE at the root of the source code distribution tree. - -#include "BCS.h" -#include "HexCoding.h" - -#include - -namespace TW::BCS::tests { - -TEST(BCS, Integral) { - Serializer os; - os << uint32_t(0xAABBCCDD); - ASSERT_EQ(os.bytes, parse_hex("0xDDCCBBAA")); - - os.clear(); - os << int32_t(-305419896); - ASSERT_EQ(os.bytes, parse_hex("0x88A9CBED")); -} - -TEST(BCS, ULEB128) { - Serializer os; - os << uleb128{0x00000001}; - ASSERT_EQ(os.bytes, parse_hex("0x01")); - - os.clear(); - os << uleb128{0x00000080}; - ASSERT_EQ(os.bytes, parse_hex("0x8001")); - - os.clear(); - os << uleb128{0x00004000}; - ASSERT_EQ(os.bytes, parse_hex("0x808001")); - - os.clear(); - os << uleb128{0x00200000}; - ASSERT_EQ(os.bytes, parse_hex("0x80808001")); - - os.clear(); - os << uleb128{0x10000000}; - ASSERT_EQ(os.bytes, parse_hex("0x8080808001")); - - os.clear(); - os << uleb128{0x0000250F}; - ASSERT_EQ(os.bytes, parse_hex("0x8F4A")); -} - -TEST(BCS, String) { - Serializer os; - os << std::string_view("abcd"); - ASSERT_EQ(os.bytes, parse_hex("0x0461626364")); - - os.clear(); - os << std::string_view(""); - ASSERT_EQ(os.bytes, parse_hex("0x00")); -} - -TEST(BCS, Optional) { - Serializer os; - os << std::optional{0xBBCCDD}; - ASSERT_EQ(os.bytes, parse_hex("0x01DDCCBB00")); - - os.clear(); - os << std::optional{}; - ASSERT_EQ(os.bytes, parse_hex("0x00")); - - os.clear(); - os << std::nullopt; - ASSERT_EQ(os.bytes, parse_hex("0x00")); -} - -TEST(BCS, Tuple) { - Serializer os; - os << std::tuple{uint16_t(1), 'a'}; - ASSERT_EQ(os.bytes, parse_hex("0x010061")); - - os.clear(); - os << std::tuple{std::optional{123}, std::string_view("abcd"), uint8_t(0x0E)}; - ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); - - os.clear(); - os << std::tuple{}; - ASSERT_EQ(os.bytes, (Data{})); -} - -TEST(BCS, Pair) { - Serializer os; - os << std::pair{uint16_t(1), 'a'}; - ASSERT_EQ(os.bytes, parse_hex("0x010061")); - - os.clear(); - os << std::pair{std::optional{123}, std::string_view("abcd")}; - ASSERT_EQ(os.bytes, parse_hex("0x017b0000000461626364")); -} - -struct my_struct { - std::optional first; - std::string_view second; - uint8_t third; -}; - -TEST(BCS, Struct) { - Serializer os; - os << my_struct{{123}, "abcd", 0x0E}; - ASSERT_EQ(os.bytes, parse_hex("0x017b00000004616263640e")); -} - -TEST(BCS, Variant) { - using V = std::variant; - - Serializer os; - os << V{uint32_t(1)}; - ASSERT_EQ(os.bytes, parse_hex("0x0001000000")); - - os.clear(); - os << V{char('a')}; - ASSERT_EQ(os.bytes, parse_hex("0x0161")); - - os.clear(); - os << V{true}; - ASSERT_EQ(os.bytes, parse_hex("0x0201")); -} - -TEST(BCS, Map) { - Serializer os; - os << std::map{{'a', 0}, {'b', 1}, {'c', 2}}; - ASSERT_EQ(os.bytes, parse_hex("0x03610062016302")); -} - -class my_number { -private: - int value; - -public: - explicit my_number(int value) noexcept - : value(value) { - } - - [[nodiscard]] auto get_value() const { - return value; - } -}; - -Serializer& operator<<(Serializer& stream, my_number n) noexcept { - return stream << n.get_value(); -} - -static_assert(CustomSerializable, "my_number does not model the CustomSerializable concept"); - -TEST(BCS, Custom) { - Serializer os; - os << my_number{0xBBCCDD}; - ASSERT_EQ(os.bytes, parse_hex("0xDDCCBB00")); -} - -TEST(BCS, Vector) { - Serializer os; - os << std::vector{1}; - ASSERT_EQ(os.bytes, parse_hex("0101")); -} - -} diff --git a/tests/common/rust/bindgen/WalletCoreRsTests.cpp b/tests/common/rust/bindgen/WalletCoreRsTests.cpp index 402fbfb6050..bd5cd298a41 100644 --- a/tests/common/rust/bindgen/WalletCoreRsTests.cpp +++ b/tests/common/rust/bindgen/WalletCoreRsTests.cpp @@ -13,15 +13,6 @@ #include "proto/Polkadot.pb.h" #include "uint256.h" -TEST(RustBindgen, MoveParseFunctionArgument) { - using namespace TW; - std::string arg = "10000000"; - auto str_result = Rust::parse_function_argument_to_bcs(arg.c_str()); - ASSERT_EQ(str_result.code, Rust::OK_CODE); - ASSERT_EQ(std::string(str_result.result), "8096980000000000"); - Rust::free_string(str_result.result); -} - TEST(RustBindgen, EthSigningMessageProto) { using namespace TW;