From 099bb4c351a82a969de9db110a2f30d7227a201f Mon Sep 17 00:00:00 2001 From: Adam R <13562139+catenocrypt@users.noreply.github.com> Date: Tue, 22 Dec 2020 00:39:57 +0100 Subject: [PATCH] [Breaking] Refactor Ethereum token transfer signing input (#1210) * Factor out Transfer from Eth SigningInput. * Make with oneof, Transfer and ContractGeneric. * Remove amount from generic contract. * Special ERC20 contract type (empty so far) * Various factory methods for Transaction class. * Add new ERC20 test. * Add ERC20 handling, building. * ABI comment * Update iOS tests. * Add ERC20 iOS test * Update android tests * Update typescript test. * Special handling for invalid Address case. * Additional low-level signer test. * Small refactor in transaction building. * Support ERC721 * ERC20 kotlin test * Swift and Kotlin tests for ERC721 * iOS test fix * Rename in proto * Comment in proto, expose ERC20 call building * Add optional payload to plain Transfer. * ContractGeneric rename. * Minor exception leak fix in Solana * Payload -> Transaction rename. * Add ERC20 Approve * ERC20 approve iOS test * Rename, remove transfer_ prefix * Typescript test fix Co-authored-by: Catenocrypt --- .../ethereum/TestEthereumTransactionSigner.kt | 69 +++++++- src/Ethereum/Signer.cpp | 123 +++++++++++--- src/Ethereum/Transaction.cpp | 51 ++++++ src/Ethereum/Transaction.h | 34 +++- src/proto/Ethereum.proto | 62 ++++++- swift/Tests/Blockchains/EthereumTests.swift | 85 +++++++++- tests/BinanceSmartChain/SignerTests.cpp | 9 +- tests/Ethereum/SignerTests.cpp | 33 +++- tests/Ethereum/TWAnySignerTests.cpp | 155 +++++++++++++++++- tests/Wanchain/SignerTests.cpp | 5 +- tests/Wanchain/TWAnySignerTests.cpp | 3 +- typescript/tests/index.test.ts | 8 +- 12 files changed, 575 insertions(+), 62 deletions(-) diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt index 10b31bb9502..a42835cc612 100644 --- a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumTransactionSigner.kt @@ -29,7 +29,11 @@ class TestEthereumTransactionSigner { nonce = ByteString.copyFrom("0x9".toHexByteArray()) gasPrice = ByteString.copyFrom("0x04a817c800".toHexByteArray()) gasLimit = ByteString.copyFrom("0x5208".toHexByteArray()) - amount = ByteString.copyFrom("0x0de0b6b3a7640000".toHexByteArray()) + transaction = Ethereum.Transaction.newBuilder().apply { + transfer = Ethereum.Transaction.Transfer.newBuilder().apply { + amount = ByteString.copyFrom("0x0de0b6b3a7640000".toHexByteArray()) + }.build() + }.build() } val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) @@ -42,6 +46,63 @@ class TestEthereumTransactionSigner { assertEquals(Numeric.toHexString(encoded), "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } + @Test + fun testEthereumERC20Signing() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + chainId = ByteString.copyFrom("0x1".toHexByteArray()) + nonce = ByteString.copyFrom("0x0".toHexByteArray()) + gasPrice = ByteString.copyFrom("0x09c7652400".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x0130B9".toHexByteArray()) + transaction = Ethereum.Transaction.newBuilder().apply { + erc20Transfer = Ethereum.Transaction.ERC20Transfer.newBuilder().apply { + to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + amount = ByteString.copyFrom("0x1bc16d674ec80000".toHexByteArray()) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val encoded = AnySigner.encode(signingInput.build(), ETHEREUM) + + assertArrayEquals(output.encoded.toByteArray(), encoded) + assertEquals(Numeric.toHexString(output.v.toByteArray()), "0x25") + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0x724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931") + assertEquals(Numeric.toHexString(encoded), "0xf8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931") + } + + @Test + fun testEthereumERC721Signing() { + val signingInput = Ethereum.SigningInput.newBuilder() + signingInput.apply { + privateKey = ByteString.copyFrom(PrivateKey("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151".toHexByteArray()).data()) + toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + chainId = ByteString.copyFrom("0x1".toHexByteArray()) + nonce = ByteString.copyFrom("0x0".toHexByteArray()) + gasPrice = ByteString.copyFrom("0x09c7652400".toHexByteArray()) + gasLimit = ByteString.copyFrom("0x0130B9".toHexByteArray()) + transaction = Ethereum.Transaction.newBuilder().apply { + erc721Transfer = Ethereum.Transaction.ERC721Transfer.newBuilder().apply { + from = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc" + to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + tokenId = ByteString.copyFrom("0x23c47ee5".toHexByteArray()) + }.build() + }.build() + } + + val output = AnySigner.sign(signingInput.build(), ETHEREUM, SigningOutput.parser()) + val encoded = AnySigner.encode(signingInput.build(), ETHEREUM) + + assertArrayEquals(output.encoded.toByteArray(), encoded) + assertEquals(Numeric.toHexString(output.v.toByteArray()), "0x26") + assertEquals(Numeric.toHexString(output.r.toByteArray()), "0x4f35575c8dc6d0c12fd1ae0007a1395f2baa992d5d498f5ee381cdb7d46ed43c") + assertEquals(Numeric.toHexString(output.s.toByteArray()), "0x0935b9ceb724ab73806e7f43da6a3079e7404e2dc28fe030fef96cd13779ac04") + assertEquals(Numeric.toHexString(encoded), "0xf8b6808509c7652400830130b98080b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a04f35575c8dc6d0c12fd1ae0007a1395f2baa992d5d498f5ee381cdb7d46ed43ca00935b9ceb724ab73806e7f43da6a3079e7404e2dc28fe030fef96cd13779ac04") + } + @Test fun testSignJSON() { val json = """ @@ -50,7 +111,11 @@ class TestEthereumTransactionSigner { "gasPrice": "1pOkAA==", "gasLimit": "Ugg=", "toAddress": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", - "amount": "A0i8paFgAA==" + "transaction": { + "transfer": { + "amount":"A0i8paFgAA==" + } + } } """ val key = "17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543".toHexByteArray() diff --git a/src/Ethereum/Signer.cpp b/src/Ethereum/Signer.cpp index bbeeb7c8157..9f65476c8c9 100644 --- a/src/Ethereum/Signer.cpp +++ b/src/Ethereum/Signer.cpp @@ -12,27 +12,31 @@ using namespace TW; using namespace TW::Ethereum; Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { - auto signer = Signer(load(input.chain_id())); - auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); - auto transaction = Signer::build(input); + try { + auto signer = Signer(load(input.chain_id())); + auto key = PrivateKey(Data(input.private_key().begin(), input.private_key().end())); + auto transaction = Signer::build(input); - signer.sign(key, transaction); + signer.sign(key, transaction); - auto output = Proto::SigningOutput(); + auto output = Proto::SigningOutput(); - auto encoded = RLP::encode(transaction); - output.set_encoded(encoded.data(), encoded.size()); + auto encoded = RLP::encode(transaction); + output.set_encoded(encoded.data(), encoded.size()); - auto v = store(transaction.v); - output.set_v(v.data(), v.size()); + auto v = store(transaction.v); + output.set_v(v.data(), v.size()); - auto r = store(transaction.r); - output.set_r(r.data(), r.size()); + auto r = store(transaction.r); + output.set_r(r.data(), r.size()); - auto s = store(transaction.s); - output.set_s(s.data(), s.size()); + auto s = store(transaction.s); + output.set_s(s.data(), s.size()); - return output; + return output; + } catch (std::exception&) { + return Proto::SigningOutput(); + } } std::string Signer::signJSON(const std::string& json, const Data& key) { @@ -67,21 +71,86 @@ Signer::sign(const uint256_t &chainID, const PrivateKey &privateKey, const Data& return values(chainID, signature); } +// May throw +Data addressStringToData(const std::string& asString) { + if (asString.empty()) { + return {}; + } + auto address = Address(asString); + Data asData; + asData.resize(20); + std::copy(address.bytes.begin(), address.bytes.end(), asData.data()); + return asData; +} + Transaction Signer::build(const Proto::SigningInput &input) { - Data toAddress; - if (!input.to_address().empty()) { - toAddress.resize(20); - auto address = Address(input.to_address()); - std::copy(address.bytes.begin(), address.bytes.end(), toAddress.data()); + Data toAddress = addressStringToData(input.to_address()); + switch (input.transaction().transaction_oneof_case()) { + case Proto::Transaction::kTransfer: + { + auto transaction = Transaction::buildTransfer( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* to: */ toAddress, + /* amount: */ load(input.transaction().transfer().amount()), + /* optionalTransaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); + return transaction; + } + + case Proto::Transaction::kErc20Transfer: + { + Data tokenToAddress = addressStringToData(input.transaction().erc20_transfer().to()); + auto transaction = Transaction::buildERC20Transfer( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* tokenContract: */ toAddress, + /* toAddress */ tokenToAddress, + /* amount: */ load(input.transaction().erc20_transfer().amount())); + return transaction; + } + + case Proto::Transaction::kErc20Approve: + { + Data spenderAddress = addressStringToData(input.transaction().erc20_approve().spender()); + auto transaction = Transaction::buildERC20Approve( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* tokenContract: */ toAddress, + /* toAddress */ spenderAddress, + /* amount: */ load(input.transaction().erc20_transfer().amount())); + return transaction; + } + + case Proto::Transaction::kErc721Transfer: + { + Data tokenToAddress = addressStringToData(input.transaction().erc721_transfer().to()); + Data tokenFromAddress = addressStringToData(input.transaction().erc721_transfer().from()); + auto transaction = Transaction::buildERC721Transfer( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* tokenContract: */ toAddress, + /* fromAddress: */ tokenFromAddress, + /* toAddress */ tokenToAddress, + /* tokenId: */ load(input.transaction().erc721_transfer().token_id())); + return transaction; + } + + case Proto::Transaction::kContractGeneric: + default: + { + auto transaction = Transaction::buildSmartContract( + /* nonce: */ load(input.nonce()), + /* gasPrice: */ load(input.gas_price()), + /* gasLimit: */ load(input.gas_limit()), + /* to: */ toAddress, + /* transaction: */ Data(input.transaction().contract_generic().data().begin(), input.transaction().contract_generic().data().end())); + return transaction; + } } - auto transaction = Transaction( - /* nonce: */ load(input.nonce()), - /* gasPrice: */ load(input.gas_price()), - /* gasLimit: */ load(input.gas_limit()), - /* to: */ toAddress, - /* amount: */ load(input.amount()), - /* payload: */ Data(input.payload().begin(), input.payload().end())); - return transaction; } void Signer::sign(const PrivateKey &privateKey, Transaction &transaction) const noexcept { diff --git a/src/Ethereum/Transaction.cpp b/src/Ethereum/Transaction.cpp index fef3920f1af..50e6818b58d 100644 --- a/src/Ethereum/Transaction.cpp +++ b/src/Ethereum/Transaction.cpp @@ -5,5 +5,56 @@ // file LICENSE at the root of the source code distribution tree. #include "Transaction.h" +#include "ABI/Function.h" +#include "ABI/ParamBase.h" +#include "ABI/ParamAddress.h" +using namespace TW::Ethereum::ABI; +using namespace TW::Ethereum; using namespace TW; + +Transaction Transaction::buildERC20Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& toAddress, uint256_t amount) { + return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20TransferCall(toAddress, amount)); +} + +Transaction Transaction::buildERC20Approve(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& spenderAddress, uint256_t amount) { + return Transaction(nonce, gasPrice, gasLimit, tokenContract, 0, buildERC20ApproveCall(spenderAddress, amount)); +} + +Transaction Transaction::buildERC721Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId) { + return Transaction(nonce, gasPrice, gasLimit, {}, 0, buildERC721TransferFromCall(from, to, tokenId)); +} + +Data Transaction::buildERC20TransferCall(const Data& to, uint256_t amount) { + auto func = Function("transfer", std::vector>{ + std::make_shared(to), + std::make_shared(amount) + }); + Data payload; + func.encode(payload); + return payload; +} + +Data Transaction::buildERC20ApproveCall(const Data& spender, uint256_t amount) { + auto func = Function("approve", std::vector>{ + std::make_shared(spender), + std::make_shared(amount) + }); + Data payload; + func.encode(payload); + return payload; +} + +Data Transaction::buildERC721TransferFromCall(const Data& from, const Data& to, uint256_t tokenId) { + auto func = Function("transferFrom", std::vector>{ + std::make_shared(from), + std::make_shared(to), + std::make_shared(tokenId) + }); + Data payload; + func.encode(payload); + return payload; +} diff --git a/src/Ethereum/Transaction.h b/src/Ethereum/Transaction.h index d5af92b3ed8..a39d05106ed 100644 --- a/src/Ethereum/Transaction.h +++ b/src/Ethereum/Transaction.h @@ -12,7 +12,7 @@ namespace TW::Ethereum { class Transaction { - public: +public: uint256_t nonce; uint256_t gasPrice; uint256_t gasLimit; @@ -26,8 +26,36 @@ class Transaction { uint256_t r = uint256_t(); uint256_t s = uint256_t(); - Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, - Data payload) + // Factory methods + // Create a native coin transfer transaction + static Transaction buildTransfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, const Data& optionalData = {}) { + return Transaction(nonce, gasPrice, gasLimit, to, amount, optionalData); + } + + // Create an ERC20 token transfer transaction + static Transaction buildERC20Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& toAddress, uint256_t amount); + + // Create an ERC20 approve transaction + static Transaction buildERC20Approve(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& spenderAddress, uint256_t amount); + + // Create an ERC721 NFT transfer transaction + static Transaction buildERC721Transfer(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, + const Data& tokenContract, const Data& from, const Data& to, uint256_t tokenId); + + // Create a generic smart contract transaction + static Transaction buildSmartContract(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, const Data& data) { + return Transaction(nonce, gasPrice, gasLimit, to, 0, data); + } + + // Helpers for building contract calls + static Data buildERC20TransferCall(const Data& to, uint256_t amount); + static Data buildERC20ApproveCall(const Data& spender, uint256_t amount); + static Data buildERC721TransferFromCall(const Data& from, const Data& to, uint256_t tokenId); + +private: + Transaction(uint256_t nonce, uint256_t gasPrice, uint256_t gasLimit, const Data& to, uint256_t amount, const Data& payload) : nonce(std::move(nonce)) , gasPrice(std::move(gasPrice)) , gasLimit(std::move(gasLimit)) diff --git a/src/proto/Ethereum.proto b/src/proto/Ethereum.proto index ce84d106d8e..448b3e19932 100644 --- a/src/proto/Ethereum.proto +++ b/src/proto/Ethereum.proto @@ -3,6 +3,58 @@ syntax = "proto3"; package TW.Ethereum.Proto; option java_package = "wallet.core.jni.proto"; +// Transaction (transfer, smart contract call, ...) +message Transaction { + // Native coin transfer transaction + message Transfer { + // Amount to send in wei (256-bit number) + bytes amount = 1; + + // Optional payload data + bytes data = 2; + } + + // ERC20 token transfer transaction + message ERC20Transfer { + string to = 1; + + // Amount to send (256-bit number) + bytes amount = 2; + } + + // ERC20 approve transaction + message ERC20Approve { + string spender = 1; + + // Amount to send (256-bit number) + bytes amount = 2; + } + + // ERC721 NFT transfer transaction + message ERC721Transfer { + string from = 1; + + string to = 2; + + // ID of the token (256-bit number) + bytes token_id = 3; + } + + // Generic smart contract transaction + message ContractGeneric { + // Contract call payload data + bytes data = 1; + } + + oneof transaction_oneof { + Transfer transfer = 1; + ERC20Transfer erc20_transfer = 2; + ERC20Approve erc20_approve = 3; + ERC721Transfer erc721_transfer = 4; + ContractGeneric contract_generic = 5; + } +} + // Input data necessary to create a signed transaction. message SigningInput { // Chain identifier (256-bit number) @@ -20,14 +72,10 @@ message SigningInput { // Recipient's address. string to_address = 5; - // Amount to send in wei (256-bit number) - bytes amount = 6; - - // Optional payload - bytes payload = 7; - // Private key. - bytes private_key = 8; + bytes private_key = 6; + + Transaction transaction = 7; } // Transaction signing output. diff --git a/swift/Tests/Blockchains/EthereumTests.swift b/swift/Tests/Blockchains/EthereumTests.swift index 926c541cf78..51d93f3f3c8 100644 --- a/swift/Tests/Blockchains/EthereumTests.swift +++ b/swift/Tests/Blockchains/EthereumTests.swift @@ -28,8 +28,12 @@ class EthereumTests: XCTestCase { $0.gasPrice = Data(hexString: "04a817c800")! $0.gasLimit = Data(hexString: "5208")! $0.toAddress = "0x3535353535353535353535353535353535353535" - $0.amount = Data(hexString: "0de0b6b3a7640000")! $0.privateKey = Data(hexString: "0x4646464646464646464646464646464646464646464646464646464646464646")! + $0.transaction = EthereumTransaction.with { + $0.transfer = TW_Ethereum_Proto_Transaction.Transfer.with { + $0.amount = Data(hexString: "0de0b6b3a7640000")! + } + } } let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) @@ -42,6 +46,79 @@ class EthereumTests: XCTestCase { XCTAssertEqual(output.encoded.hexString, "f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83") } + func testSignERC20Transfer() { + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "00")! + $0.gasPrice = Data(hexString: "09c7652400")! // 42000000000 + $0.gasLimit = Data(hexString: "0130B9")! // 78009 + $0.toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + $0.privateKey = Data(hexString: "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")! + $0.transaction = EthereumTransaction.with { + $0.erc20Transfer = TW_Ethereum_Proto_Transaction.ERC20Transfer.with { + $0.to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + $0.amount = Data(hexString: "1bc16d674ec80000")! // 2000000000000000000 + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + let encoded = AnySigner.encode(input: input, coin: .ethereum) + + XCTAssertEqual(encoded, output.encoded) + XCTAssertEqual(output.v.hexString, "25") + XCTAssertEqual(output.r.hexString, "724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce") + XCTAssertEqual(output.s.hexString, "032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931") + XCTAssertEqual(output.encoded.hexString, "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931") + } + + func testSignERC20Approve() { + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "00")! + $0.gasPrice = Data(hexString: "09c7652400")! // 42000000000 + $0.gasLimit = Data(hexString: "0130B9")! // 78009 + $0.toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f" // DAI + $0.privateKey = Data(hexString: "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")! + $0.transaction = EthereumTransaction.with { + $0.erc20Approve = TW_Ethereum_Proto_Transaction.ERC20Approve.with { + $0.spender = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + $0.amount = Data(hexString: "1bc16d674ec80000")! // 2000000000000000000 + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + let encoded = AnySigner.encode(input: input, coin: .ethereum) + + XCTAssertEqual(encoded, output.encoded) + XCTAssertEqual(output.encoded.hexString, "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc84000000000000000000000000000000000000000000000000000000000000000026a0406bfe337306699d01f447c23a3bd927c7174ace5c1d9a39b545b49fc6fce005a049f837dd141b8fdf45dc10b415eaf974c66384c87056d8f58882896ee6fd55cb") + } + + func testSignERC721Transfer() { + let input = EthereumSigningInput.with { + $0.chainID = Data(hexString: "01")! + $0.nonce = Data(hexString: "00")! + $0.gasPrice = Data(hexString: "09c7652400")! // 42000000000 + $0.gasLimit = Data(hexString: "0130B9")! // 78009 + $0.toAddress = "0x4e45e92ed38f885d39a733c14f1817217a89d425" // payload + $0.privateKey = Data(hexString: "0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")! + $0.transaction = EthereumTransaction.with { + $0.erc721Transfer = TW_Ethereum_Proto_Transaction.ERC721Transfer.with { + $0.from = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc" + $0.to = "0x5322b34c88ed0691971bf52a7047448f0f4efc84" + $0.tokenID = Data(hexString: "23c47ee5")! + } + } + } + let output: EthereumSigningOutput = AnySigner.sign(input: input, coin: .ethereum) + let encoded = AnySigner.encode(input: input, coin: .ethereum) + + XCTAssertEqual(encoded, output.encoded) + XCTAssertEqual(output.v.hexString, "26") + XCTAssertEqual(output.r.hexString, "4f35575c8dc6d0c12fd1ae0007a1395f2baa992d5d498f5ee381cdb7d46ed43c") + XCTAssertEqual(output.s.hexString, "0935b9ceb724ab73806e7f43da6a3079e7404e2dc28fe030fef96cd13779ac04") + XCTAssertEqual(output.encoded.hexString, "f8b6808509c7652400830130b98080b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a04f35575c8dc6d0c12fd1ae0007a1395f2baa992d5d498f5ee381cdb7d46ed43ca00935b9ceb724ab73806e7f43da6a3079e7404e2dc28fe030fef96cd13779ac04") + } + func testSignJSON() { let json = """ { @@ -49,7 +126,11 @@ class EthereumTests: XCTestCase { "gasPrice": "1pOkAA==", "gasLimit": "Ugg=", "toAddress": "0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1", - "amount": "A0i8paFgAA==" + "transaction": { + "transfer": { + "amount":"A0i8paFgAA==" + } + } } """ let key = Data(hexString: "17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543")! diff --git a/tests/BinanceSmartChain/SignerTests.cpp b/tests/BinanceSmartChain/SignerTests.cpp index 97e19fc6685..f08b8874a4e 100644 --- a/tests/BinanceSmartChain/SignerTests.cpp +++ b/tests/BinanceSmartChain/SignerTests.cpp @@ -33,13 +33,12 @@ TEST(BinanceSmartChain, SignNativeTransfer) { // https://explorer.binance.org/smart-testnet/tx/0x6da28164f7b3bc255d749c3ae562e2a742be54c12bf1858b014cc2fe5700684e auto toAddress = parse_hex("0x31BE00EB1fc8e14A696DBC72f746ec3e95f49683"); - auto transaction = Transaction( + auto transaction = Transaction::buildTransfer( /* nonce: */ 0, /* gasPrice: */ 20000000000, /* gasLimit: */ 21000, /* to: */ toAddress, - /* amount: */ 10000000000000000, // 0.01 - /* payload: */ {} + /* amount: */ 10000000000000000 // 0.01 ); // addr: 0xB9F5771C27664bF2282D98E09D7F50cEc7cB01a7 mnemonic: isolate dismiss ... cruel note @@ -76,9 +75,9 @@ TEST(BinanceSmartChain, SignTokenTransfer) { input.set_gas_price(gasPrice.data(), gasPrice.size()); input.set_gas_limit(gasLimit.data(), gasLimit.size()); input.set_to_address(tokenContractAddress); - input.set_payload(payloadFunction.data(), payloadFunction.size()); - input.set_amount(dummyAmount.data(), dummyAmount.size()); input.set_private_key(privateKey.bytes.data(), privateKey.bytes.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(payloadFunction.data(), payloadFunction.size()); const std::string expected = "f8ab1e8504a817c800830f424094ed24fc36d5ee211ea25a80239fb8c4cfd80f12ee80b844a9059cbb00000000000000000000000031be00eb1fc8e14a696dbc72f746ec3e95f49683000000000000000000000000000000000000000000000000002386f26fc1000081e6a0aa9d5e9a947e96f728fe5d3e6467000cd31a693c00270c33ec64b4abddc29516a00bf1d5646139b2bcca1ad64e6e79f45b7d1255de603b5a3765cbd9544ae148d0"; diff --git a/tests/Ethereum/SignerTests.cpp b/tests/Ethereum/SignerTests.cpp index bbc0daec7f7..b6f49df6e28 100644 --- a/tests/Ethereum/SignerTests.cpp +++ b/tests/Ethereum/SignerTests.cpp @@ -21,15 +21,14 @@ class SignerExposed : public Signer { using Signer::hash; }; -TEST(Signer, Hash) { +TEST(EthereumSigner, Hash) { auto address = parse_hex("0x3535353535353535353535353535353535353535"); - auto transaction = Transaction( + auto transaction = Transaction::buildTransfer( /* nonce: */ 9, /* gasPrice: */ 20000000000, /* gasLimit: */ 21000, /* to: */ address, - /* amount: */ 1000000000000000000, - /* payload: */ {} + /* amount: */ 1000000000000000000 ); auto signer = SignerExposed(1); auto hash = signer.hash(transaction); @@ -37,15 +36,14 @@ TEST(Signer, Hash) { ASSERT_EQ(hex(hash), "daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53"); } -TEST(Signer, Sign) { +TEST(EthereumSigner, Sign) { auto address = parse_hex("0x3535353535353535353535353535353535353535"); - auto transaction = Transaction( + auto transaction = Transaction::buildTransfer( /* nonce: */ 9, /* gasPrice: */ 20000000000, /* gasLimit: */ 21000, /* to: */ address, - /* amount: */ 1000000000000000000, - /* payload: */ {} + /* amount: */ 1000000000000000000 ); auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); @@ -57,4 +55,23 @@ TEST(Signer, Sign) { ASSERT_EQ(transaction.s, uint256_t("46948507304638947509940763649030358759909902576025900602547168820602576006531")); } +TEST(EthereumSigner, SignERC20Transfer) { + auto transaction = Transaction::buildERC20Transfer( + /* nonce: */ 0, + /* gasPrice: */ 42000000000, // 0x09c7652400 + /* gasLimit: */ 78009, // 130B9 + /* tokenContract: */ parse_hex("0x6b175474e89094c44da98b954eedeac495271d0f"), // DAI + /* toAddress: */ parse_hex("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), + /* amount: */ 2000000000000000000 + ); + + auto key = PrivateKey(parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151")); + auto signer = SignerExposed(1); + signer.sign(key, transaction); + + ASSERT_EQ(transaction.v, 37); + ASSERT_EQ(hex(TW::store(transaction.r)), "724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98ce"); + ASSERT_EQ(hex(TW::store(transaction.s)), "032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"); +} + } // namespace TW::Ethereum diff --git a/tests/Ethereum/TWAnySignerTests.cpp b/tests/Ethereum/TWAnySignerTests.cpp index c1a1dace516..0a08d01c97a 100644 --- a/tests/Ethereum/TWAnySignerTests.cpp +++ b/tests/Ethereum/TWAnySignerTests.cpp @@ -55,8 +55,9 @@ TEST(TWAnySignerEthereum, Sign) { input.set_nonce(nonce.data(), nonce.size()); input.set_gas_price(gasPrice.data(), gasPrice.size()); input.set_gas_limit(gasLimit.data(), gasLimit.size()); - input.set_payload(data.data(), data.size()); input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(data.data(), data.size()); std::string expected = "f90a9e0b8504a817c800830f42408080b90a4b60a060405260046060527f48302e31000000000000000000000000000000000000000000000000000000006080526006805460008290527f48302e310000000000000000000000000000000000000000000000000000000882556100b5907ff652222313e28459528d920b65115c16c04f3efc82aaedc97be59f3f377c0d3f602060026001841615610100026000190190931692909204601f01919091048101905b8082111561017957600081556001016100a1565b505060405161094b38038061094b83398101604052808051906020019091908051820191906020018051906020019091908051820191906020015050836000600050600033600160a060020a0316815260200190815260200160002060005081905550836002600050819055508260036000509080519060200190828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061017d57805160ff19168380011785555b506101ad9291506100a1565b5090565b8280016001018555821561016d579182015b8281111561016d57825182600050559160200191906001019061018f565b50506004805460ff19168317905560058054825160008390527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db0602060026001851615610100026000190190941693909304601f90810184900482019386019083901061022d57805160ff19168380011785555b5061025d9291506100a1565b82800160010185558215610221579182015b8281111561022157825182600050559160200191906001019061023f565b5050505050506106da806102716000396000f36060604052361561008d5760e060020a600035046306fdde038114610095578063095ea7b3146100f357806318160ddd1461016857806323b872dd14610171578063313ce5671461025c57806354fd4d501461026857806370a08231146102c657806395d89b41146102f4578063a9059cbb14610352578063cae9ca51146103f7578063dd62ed3e146105be575b6105f2610002565b6040805160038054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03908116600081815260016020908152604080832094871680845294825280832086905580518681529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a35060015b92915050565b6102e260025481565b610662600435602435604435600160a060020a0383166000908152602081905260408120548290108015906101c4575060016020908152604080832033600160a060020a03168452909152812054829010155b80156101d05750600082115b156106bf57600160a060020a0383811660008181526020818152604080832080548801905588851680845281842080548990039055600183528184203390961684529482529182902080548790039055815186815291519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016106c3565b61067660045460ff1681565b6040805160068054602060026001831615610100026000190190921691909104601f81018290048202840182019094528383526105f493908301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b600160a060020a03600435166000908152602081905260409020545b60408051918252519081900360200190f35b6105f46005805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156106b75780601f1061068c576101008083540402835291602001916106b7565b61066260043560243533600160a060020a03166000908152602081905260408120548290108015906103845750600082115b156106ca5733600160a060020a0390811660008181526020818152604080832080548890039055938716808352918490208054870190558351868152935191937fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929081900390910190a3506001610162565b604080516020604435600481810135601f810184900484028501840190955284845261066294813594602480359593946064949293910191819084018382808284375094965050505050505033600160a060020a03908116600081815260016020908152604080832094881680845294825280832087905580518781529051929493927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925929181900390910190a383600160a060020a031660405180807f72656365697665417070726f76616c28616464726573732c75696e743235362c81526020017f616464726573732c627974657329000000000000000000000000000000000000815260200150602e019050604051809103902060e060020a9004338530866040518560e060020a0281526004018085600160a060020a0316815260200184815260200183600160a060020a031681526020018280519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156105965780820380516001836020036101000a031916815260200191505b509450505050506000604051808303816000876161da5a03f19250505015156106d257610002565b6102e2600435602435600160a060020a03828116600090815260016020908152604080832093851683529290522054610162565b005b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600f02600301f150905090810190601f1680156106545780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b604080519115158252519081900360200190f35b6040805160ff9092168252519081900360200190f35b820191906000526020600020905b81548152906001019060200180831161069a57829003601f168201915b505050505081565b5060005b9392505050565b506000610162565b5060016106c35600000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000754204275636b73000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003544f54000000000000000000000000000000000000000000000000000000000026a042556c4f2a3f4e4e639cca524d1da70e60881417d4643e5382ed110a52719eafa0172f591a2a763d0bd6b13d042d8c5eb66e87f129c9dc77ada66b6041012db2b3"; @@ -83,8 +84,158 @@ TEST(TWAnySignerEthereum, Sign) { } } +TEST(TWAnySignerEthereum, SignERC20TransferAsERC20) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(toAddress); + erc20.set_amount(amount.data(), amount.size()); + + // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); +} + +TEST(TWAnySignerEthereum, SignERC20TransferAsGenericContract) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto toAddress = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + // payload: transfer(0x5322b34c88ed0691971bf52a7047448f0f4efc84, 2000000000000000000) + auto data = parse_hex("0xa9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec80000"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(toAddress); + input.set_private_key(key.data(), key.size()); + auto& transfer = *input.mutable_transaction()->mutable_contract_generic(); + transfer.set_data(data.data(), data.size()); + + // https://etherscan.io/tx/0x199a7829fc5149e49b452c2cab76d8fa5a9682fee6e4891b8acb697ac142513e + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000001bc16d674ec8000025a0724c62ad4fbf47346b02de06e603e013f26f26b56fdc0be7ba3d6273401d98cea0032131cae15da7ddcda66963e8bef51ca0d9962bfef0547d3f02597a4a58c931"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); +} + +TEST(TWAnySignerEthereum, SignERC20TransferInvalidAddress) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto invalidAddress = "0xdeadbeef"; + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(invalidAddress); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(invalidAddress); + erc20.set_amount(amount.data(), amount.size()); + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), ""); +} + +TEST(TWAnySignerEthereum, SignERC20Approve) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto spenderAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = store(uint256_t(2000000000000000000)); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_approve(); + erc20.set_spender(spenderAddress); + erc20.set_amount(amount.data(), amount.size()); + + std::string expected = "f8aa808509c7652400830130b9946b175474e89094c44da98b954eedeac495271d0f80b844095ea7b30000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc84000000000000000000000000000000000000000000000000000000000000000026a0406bfe337306699d01f447c23a3bd927c7174ace5c1d9a39b545b49fc6fce005a049f837dd141b8fdf45dc10b415eaf974c66384c87056d8f58882896ee6fd55cb"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); +} + +TEST(TWAnySignerEthereum, SignERC721Transfer) { + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); + auto gasLimit = store(uint256_t(78009)); + auto tokenContract = "0x4e45e92ed38f885d39a733c14f1817217a89d425"; + auto fromAddress = "0x718046867b5b1782379a14eA4fc0c9b724DA94Fc"; + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto tokenId = parse_hex("23c47ee5"); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(tokenContract); + input.set_private_key(key.data(), key.size()); + auto& erc721 = *input.mutable_transaction()->mutable_erc721_transfer(); + erc721.set_from(fromAddress); + erc721.set_to(toAddress); + erc721.set_token_id(tokenId.data(), tokenId.size()); + + std::string expected = "f8b6808509c7652400830130b98080b86423b872dd000000000000000000000000718046867b5b1782379a14ea4fc0c9b724da94fc0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000000000023c47ee526a04f35575c8dc6d0c12fd1ae0007a1395f2baa992d5d498f5ee381cdb7d46ed43ca00935b9ceb724ab73806e7f43da6a3079e7404e2dc28fe030fef96cd13779ac04"; + + // sign test + Proto::SigningOutput output; + ANY_SIGN(input, TWCoinTypeEthereum); + + ASSERT_EQ(hex(output.encoded()), expected); +} + TEST(TWAnySignerEthereum, SignJSON) { - auto json = STRING(R"({"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","amount":"A0i8paFgAA=="})"); + auto json = STRING(R"({"chainId":"AQ==","gasPrice":"1pOkAA==","gasLimit":"Ugg=","toAddress":"0x7d8bf18C7cE84b3E175b339c4Ca93aEd1dD166F1","transaction":{"transfer":{"amount":"A0i8paFgAA=="}}})"); auto key = DATA("17209af590a86462395d5881e60d11c7fa7d482cfb02b5a01b93c2eeef243543"); auto result = WRAPS(TWAnySignerSignJSON(json.get(), key.get(), TWCoinTypeEthereum)); diff --git a/tests/Wanchain/SignerTests.cpp b/tests/Wanchain/SignerTests.cpp index 55f3fd873ff..b59a195ca0c 100644 --- a/tests/Wanchain/SignerTests.cpp +++ b/tests/Wanchain/SignerTests.cpp @@ -23,13 +23,12 @@ class SignerExposed : public Signer { TEST(Signer, Sign) { auto address = parse_hex("0x3535353535353535353535353535353535353535"); - auto transaction = Ethereum::Transaction( + auto transaction = Ethereum::Transaction::buildTransfer( /* nonce: */ 9, /* gasPrice: */ 20000000000, /* gasLimit: */ 21000, /* to: */ address, - /* amount: */ 1000000000000000000, - /* payload: */ {} + /* amount: */ 1000000000000000000 ); auto key = PrivateKey(parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646")); diff --git a/tests/Wanchain/TWAnySignerTests.cpp b/tests/Wanchain/TWAnySignerTests.cpp index e7cec4f9b29..c9dfe05cad6 100644 --- a/tests/Wanchain/TWAnySignerTests.cpp +++ b/tests/Wanchain/TWAnySignerTests.cpp @@ -31,7 +31,8 @@ TEST(TWAnySignerWanchain, Sign) { input.set_to_address("0x3535353535353535353535353535353535353535"); auto amount = store(uint256_t(1000000000000000000)); - input.set_amount(amount.data(), amount.size()); + auto& transfer = *input.mutable_transaction()->mutable_transfer(); + transfer.set_amount(amount.data(), amount.size()); auto key = parse_hex("0x4646464646464646464646464646464646464646464646464646464646464646"); input.set_private_key(key.data(), key.size()); diff --git a/typescript/tests/index.test.ts b/typescript/tests/index.test.ts index 58d33709d7a..d81b957aa58 100644 --- a/typescript/tests/index.test.ts +++ b/typescript/tests/index.test.ts @@ -51,12 +51,16 @@ describe('Wallet Core types tests', () => { nonce: Buffer.from('09', 'hex'), gasPrice: Buffer.from('04a817c800', 'hex'), gasLimit: Buffer.from('5208', 'hex'), - amount: Buffer.from('0de0b6b3a7640000', 'hex'), + transaction: TW.Ethereum.Proto.Transaction.create({ + transfer: TW.Ethereum.Proto.Transaction.Transfer.create({ + amount: Buffer.from('0de0b6b3a7640000', 'hex') + }) + }), privateKey: Buffer.from('4646464646464646464646464646464646464646464646464646464646464646', 'hex') }); const encoded = TW.Ethereum.Proto.SigningInput.encode(input).finish() - expect(Buffer.from(encoded).toString('hex')).to.equal("0a01011201091a0504a817c800220252082a2a30783335333533353335333533353335333533353335333533353335333533353335333533353335333532080de0b6b3a764000042204646464646464646464646464646464646464646464646464646464646464646") + expect(Buffer.from(encoded).toString('hex')).to.equal("0a01011201091a0504a817c800220252082a2a307833353335333533353335333533353335333533353335333533353335333533353335333533353335322046464646464646464646464646464646464646464646464646464646464646463a0c0a0a0a080de0b6b3a7640000") }) it('test Bitcoin / Bitcoin SigningInput', () => {