diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7d6d08b..593e8a0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -11,3 +11,6 @@ configure_file(defertest.wasm . COPYONLY) configure_file(defertest.abi . COPYONLY) configure_file(defertest2.wasm . COPYONLY) configure_file(defertest2.abi . COPYONLY) +configure_file(evmbridge.wasm . COPYONLY) +configure_file(evmbridge.abi . COPYONLY) + diff --git a/tests/evmbridge.abi b/tests/evmbridge.abi new file mode 100644 index 0000000..8866f3d --- /dev/null +++ b/tests/evmbridge.abi @@ -0,0 +1,64 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [ + { + "new_type_name": "bridge_message", + "type": "variant_bridge_message_v0" + } + ], + "structs": [ + { + "name": "bridge_message_v0", + "base": "", + "fields": [ + { + "name": "receiver", + "type": "name" + }, + { + "name": "sender", + "type": "bytes" + }, + { + "name": "timestamp", + "type": "time_point" + }, + { + "name": "value", + "type": "bytes" + }, + { + "name": "data", + "type": "bytes" + } + ] + }, + { + "name": "onbridgemsg", + "base": "", + "fields": [ + { + "name": "message", + "type": "bridge_message" + } + ] + } + ], + "actions": [ + { + "name": "onbridgemsg", + "type": "onbridgemsg", + "ricardian_contract": "" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [ + { + "name": "variant_bridge_message_v0", + "types": ["bridge_message_v0"] + } + ], + "action_results": [] +} \ No newline at end of file diff --git a/tests/evmbridge.cpp b/tests/evmbridge.cpp new file mode 100644 index 0000000..202e7b4 --- /dev/null +++ b/tests/evmbridge.cpp @@ -0,0 +1,69 @@ +#include +#include +#include +#include + +using namespace eosio; +using namespace std; + +extern "C" { +__attribute__((eosio_wasm_import)) +void set_action_return_value(void*, size_t); + +__attribute__((eosio_wasm_import)) +uint64_t get_sender(); +} + +typedef std::vector bytes; + +struct bridge_message_v0 { + name receiver; + bytes sender; + time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); +}; + +using bridge_message = std::variant; + +class [[eosio::contract("evmbridge")]] evmbridge : public contract { +public: + using contract::contract; + [[eosio::action]] void onbridgemsg(const bridge_message& message); + + + class contract_actions { + public: + void call(eosio::name from, const bytes &to, const bytes& value, const bytes &data, uint64_t gas_limit); + void assertnonce(eosio::name account, uint64_t next_nonce); + }; + + using call_action = action_wrapper<"call"_n, &contract_actions::call>; +}; + +void evmbridge::onbridgemsg(const bridge_message& message) { + const bridge_message_v0 &msg = std::get(message); + const char method[4] = {'\x00','\x8f','\xcf','\x3e'}; // function assertdata(uint256) payable + + uint8_t value_buffer[32] = {}; + + if (msg.data.size() >= 32) { + std::copy(msg.data.end() - 32, msg.data.end(), value_buffer); + } + + bytes call_data; + call_data.reserve(4 + 32); + call_data.insert(call_data.end(), method, method + 4); + call_data.insert(call_data.end(), value_buffer, value_buffer + 32); + + call_action call_act("eosio.evm"_n, {{get_self(), "active"_n}}); + + bytes value; + value.resize(32, 0); + value[31] = 100; + + call_act.send(get_self() /*from*/, msg.sender /*to*/, value /*value*/, call_data /*data*/, 100000 /*gas_limit*/); +} + diff --git a/tests/evmbridge.wasm b/tests/evmbridge.wasm new file mode 100755 index 0000000..20d7c92 Binary files /dev/null and b/tests/evmbridge.wasm differ diff --git a/tests/nodeos_eos_evm_test.py b/tests/nodeos_eos_evm_test.py index 0955d5b..47da0f8 100755 --- a/tests/nodeos_eos_evm_test.py +++ b/tests/nodeos_eos_evm_test.py @@ -107,6 +107,18 @@ def prefix_0x(hexstr): evmRPCPOpen = None eosEvmMinerPOpen = None +def assert_contract_exist(contract_addr): + Utils.Print("ensure contract {0} exist".format(contract_addr)) + if contract_addr[:2] == '0x': + contract_addr = contract_addr[2:] + rows=prodNode.getTable(evmAcc.name, evmAcc.name, "account") + for row in rows['rows']: + if (str(contract_addr) == str(row['eth_address'])): + assert row['code_id'] is not None, "contract {0} should exist".format(contract_addr) + return True + Utils.Print("evm account table rows: " + json.dumps(rows)) + assert False, "contract {0} should exist".format(contract_addr) + def interact_with_storage_contract(dest, nonce): for i in range(1, 5): # execute a few Utils.Print("Execute ETH contract") @@ -291,7 +303,7 @@ def toDict(dictToParse): prodNode = cluster.getNode(0) nonProdNode = cluster.getNode(1) - accounts=createAccountKeys(6) + accounts=createAccountKeys(7) if accounts is None: Utils.errorExit("FAILURE - create keys") @@ -303,10 +315,13 @@ def toDict(dictToParse): defertest2Acc = accounts[4] aliceAcc = accounts[5] + accounts[6].name = "evmbridge" + evmbridgeAcc = accounts[6] + testWalletName="test" Print("Creating wallet \"%s\"." % (testWalletName)) - testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount,accounts[0],accounts[1],accounts[2],accounts[3],accounts[4]]) + testWallet=walletMgr.create(testWalletName, [cluster.eosioAccount,accounts[0],accounts[1],accounts[2],accounts[3],accounts[4],accounts[5],accounts[6]]) addys = { "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266":"0x038318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed75,0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" @@ -356,6 +371,15 @@ def toDict(dictToParse): Utils.Print(f"Publish defertest2 contract {contractDir}/{wasmFile} to account {defertest2Acc}") prodNode.publishContract(defertest2Acc, contractDir, wasmFile, abiFile, waitForTransBlock=True) + contractDir=eosEvmBuildRoot + "/tests" + wasmFile="evmbridge.wasm" + abiFile="evmbridge.abi" + Utils.Print(f"Publish evmbridge contract {contractDir}/{wasmFile} to account {evmbridgeAcc}") + prodNode.publishContract(evmbridgeAcc, contractDir, wasmFile, abiFile, waitForTransBlock=True) + # add eosio.code evmbridge + cmd="set account permission evmbridge active --add-code -p evmbridge@active" + prodNode.processCleosCmd(cmd, cmd, silentErrors=True, returnType=ReturnType.raw) + # add eosio.code permission to defertest2 account cmd="set account permission " + defertest2Acc.name + " active --add-code -p " + defertest2Acc.name + "@active" prodNode.processCleosCmd(cmd, cmd, silentErrors=False, returnType=ReturnType.raw) @@ -527,6 +551,7 @@ def toDict(dictToParse): actData = {"miner":minerAcc.name, "rlptx":Web3.to_hex(get_raw_transaction(signed_trx))[2:]} retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p {0}'.format(minerAcc.name), silentErrors=True) assert retValue[0], f"push trx should have succeeded: {retValue}" + assert_contract_exist(makeContractAddress(fromAdd, nonce)) nonce = interact_with_storage_contract(makeContractAddress(fromAdd, nonce), nonce) if genesisJson[0] != '/': genesisJson = os.path.realpath(genesisJson) @@ -820,6 +845,7 @@ def toDict(dictToParse): # Launch eos-evm-node + Utils.Print("===== laucnhing eos-evm-node & eos-evm-rpc =====") dataDir = Utils.DataDir + "eos_evm" nodeStdOutDir = dataDir + "/eos-evm-node.stdout" nodeStdErrDir = dataDir + "/eos-evm-node.stderr" @@ -901,6 +927,69 @@ def get_block(num): nonProdNode.transferFunds(cluster.eosioAccount, evmAcc, "111.0000 EOS", "0xB106D2C286183FFC3D1F0C4A6f0753bB20B407c2", waitForTransBlock=True) time.sleep(2) + ### evmtx event order test + Utils.Print("Test evmtx event order via evmbridge contract") + # // SPDX-License-Identifier: GPL-3.0 + # pragma solidity >=0.7.0 <0.9.0; + # contract BridgeTest { + # uint256 number; + # constructor() { + # number = 41; + # } + # function assertdata(uint256 expect) payable public { + # require(number == expect, "assertdata failed"); + # number = number + 1; + # } + # function sendbridgemsg() payable public { + # number = number + 1; + # bytes memory receiver_msg = abi.encodeWithSignature("test(uint256)", number); + # address evmaddr = 0xbBBBbBbbbBBBBbbbbbbBBbBB5530EA015b900000;//eosio.evm + # (bool success, ) = evmaddr.call{value: msg.value}( + # abi.encodeWithSignature("bridgeMsgV0(string,bool,bytes)", "evmbridge", true, receiver_msg )); + # if(!success) { revert(); } + # } + # } + # + nonce += 1 + evmChainId = 15555 + gasP = getGasPrice() + signed_trx = w3.eth.account.sign_transaction(dict( + nonce=nonce, + gas=5000000, + gasPrice=gasP, + data=Web3.to_bytes(hexstr='608060405234801561001057600080fd5b5060296000819055506105b2806100286000396000f3fe6080604052600436106100285760003560e01c80628fcf3e1461002d57806386bf4eff14610049575b600080fd5b610047600480360381019061004291906102b8565b610053565b005b6100516100af565b005b8060005414610097576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161008e90610342565b60405180910390fd5b60016000546100a69190610391565b60008190555050565b60016000546100be9190610391565b600081905550600080546040516024016100d891906103d4565b6040516020818303038152906040527f29e99f07000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050509050600073bbbbbbbbbbbbbbbbbbbbbbbb5530ea015b900000905060008173ffffffffffffffffffffffffffffffffffffffff163460018560405160240161019e9291906104e6565b6040516020818303038152906040527ff781185b000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516102289190610565565b60006040518083038185875af1925050503d8060008114610265576040519150601f19603f3d011682016040523d82523d6000602084013e61026a565b606091505b505090508061027857600080fd5b505050565b600080fd5b6000819050919050565b61029581610282565b81146102a057600080fd5b50565b6000813590506102b28161028c565b92915050565b6000602082840312156102ce576102cd61027d565b5b60006102dc848285016102a3565b91505092915050565b600082825260208201905092915050565b7f61737365727464617461206661696c6564000000000000000000000000000000600082015250565b600061032c6011836102e5565b9150610337826102f6565b602082019050919050565b6000602082019050818103600083015261035b8161031f565b9050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061039c82610282565b91506103a783610282565b92508282019050808211156103bf576103be610362565b5b92915050565b6103ce81610282565b82525050565b60006020820190506103e960008301846103c5565b92915050565b7f65766d6272696467650000000000000000000000000000000000000000000000600082015250565b60006104256009836102e5565b9150610430826103ef565b602082019050919050565b60008115159050919050565b6104508161043b565b82525050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610490578082015181840152602081019050610475565b60008484015250505050565b6000601f19601f8301169050919050565b60006104b882610456565b6104c28185610461565b93506104d2818560208601610472565b6104db8161049c565b840191505092915050565b600060608201905081810360008301526104ff81610418565b905061050e6020830185610447565b818103604083015261052081846104ad565b90509392505050565b600081905092915050565b600061053f82610456565b6105498185610529565b9350610559818560208601610472565b80840191505092915050565b60006105718284610534565b91508190509291505056fea2646970667358221220ac18e6f72606f415174ea5fa2cf02da58e2ec7af6b59282e166efa50f79aef3164736f6c63430008120033'), + chainId=evmChainId + ), evmSendKey) + actData = {"miner":minerAcc.name, "rlptx":Web3.to_hex(get_raw_transaction(signed_trx))[2:]} + retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p {0}'.format(minerAcc.name), silentErrors=True) + assert retValue[0], f"push trx should have succeeded: {retValue}" + bridgemsgcontractaddr = makeContractAddress("9E126C57330FA71556628e0aabd6B6B6783d99fA", nonce) + assert_contract_exist(bridgemsgcontractaddr) + Utils.Print("bridge msg contract addr is:" + str(bridgemsgcontractaddr)) + row4=prodNode.getTableRow(evmAcc.name, evmAcc.name, "account", 4) # 4th balance of this integration test + Utils.Print("\taccount row4: ", row4) + + Utils.Print("call bridgereg in evm runtime contract for account evmbridge") + prodNode.pushMessage(evmAcc.name, "bridgereg", '["evmbridge","evmbridge","1.0000 EOS"]', '-p {0} -p evmbridge'.format(evmAcc.name), silentErrors=False) + + Utils.Print("push EVM trx to trigger bridgemsg from EVM to notify evmbridge account") + amount=1.0000 + nonce += 1 + signed_trx = w3.eth.account.sign_transaction(dict( + nonce=nonce, + gas=100000, + gasPrice=gasP, + to=Web3.to_checksum_address(bridgemsgcontractaddr), + data=Web3.to_bytes(hexstr='86bf4eff'), #function sendbridgemsg() + value=int(amount*10000*szabo*100), + chainId=evmChainId + ), evmSendKey) + actData = {"miner":minerAcc.name, "rlptx":Web3.to_hex(get_raw_transaction(signed_trx))[2:]} + retValue = prodNode.pushMessage(evmAcc.name, "pushtx", json.dumps(actData), '-p {0}'.format(minerAcc.name), silentErrors=False) + assert retValue[0], f"push trx to bridge msg contract should have succeeded: {retValue}" + row4=prodNode.getTableRow(evmAcc.name, evmAcc.name, "account", 4) # 4th balance of this integration test + Utils.Print("\taccount row4: ", row4) + # update gas parameter Utils.Print("Update gas parameter: ram price = 100 EOS per MB, gas price = 900Gwei") trans = prodNode.pushMessage(evmAcc.name, "updtgasparam", json.dumps({"ram_price_mb":"100.0000 EOS","gas_price":900000000000}), '-p {0}'.format(evmAcc.name), silentErrors=False)