From ea41f756ea1769c2c4a6de6d3f1bd47d09e6f407 Mon Sep 17 00:00:00 2001 From: kayan Date: Fri, 25 Aug 2023 17:39:03 +0800 Subject: [PATCH 01/11] implement EOS->EVM & EVM->EOS transfer in eosio.erc2o contract --- .../contracts/erc20/include/erc20/erc20.hpp | 81 ++++++++- .../contracts/erc20/include/erc20/types.hpp | 4 +- .../contracts/erc20/src/erc20.cpp | 156 ++++++++++++++---- solidity_contracts/erc20/contract.sol | 15 +- 4 files changed, 211 insertions(+), 45 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 867d18a..b9dd13a 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -11,28 +11,97 @@ using namespace intx; namespace erc20 { +checksum256 make_key(const uint8_t* ptr, size_t len) { + uint8_t buffer[32]={0}; + check(len <= sizeof(buffer), "invalida size"); + memcpy(buffer, ptr, len); + return checksum256(buffer); +} + +checksum256 make_key(bytes data){ + return make_key((const uint8_t*)data.data(), data.size()); +} + class [[eosio::contract]] erc20 : public contract { public: using contract::contract; + struct bridge_message_v0 { + eosio::name receiver; + bytes sender; + eosio::time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); + }; + + using bridge_message_t = std::variant; + [[eosio::on_notify("*::transfer")]] void transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo); // evm runtime will call this to notify erc20 about the message from 'from' with 'data'. - [[eosio::action]] void onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data); - [[eosio::action]] void init(); + [[eosio::action]] void onbridgemsg(const bridge_message_t &message); + [[eosio::action]] void init(uint64_t nonce); + + [[eosio::action]] void regtoken(uint64_t nonce, eosio::name eos_contract_name, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision); + + struct [[eosio::table("implcontract")]] impl_contract_t { + uint64_t id = 0; + bytes address; + + uint64_t primary_key() const { + return id; + } + checksum256 by_address()const { + return make_key(address); + } + }; + typedef eosio::multi_index<"implcontract"_n, impl_contract_t, + indexed_by<"by.address"_n, const_mem_fun > + > impl_contract_table_t; + + struct [[eosio::table("tokens")]] token_t { + uint64_t id = 0; + eosio::name eos_contract_name; + bytes address; // <== proxy contract addr + eosio::asset min_deposit; + eosio::asset deposit_fee; + uint64_t balance = 0; + int erc20_precision = 0; + + uint64_t primary_key() const { + return id; + } + uint128_t by_contract_symbol() const { + uint128_t v = eos_contract_name.value; + v <<= 64; + v |= min_deposit.symbol.code().raw(); + return v; + } + checksum256 by_address()const { + return make_key(address); + } + + EOSLIB_SERIALIZE(token_t, (id)(eos_contract_name)(address)(min_deposit)(deposit_fee)(balance)(erc20_precision)); + }; + typedef eosio::multi_index<"tokens"_n, token_t, + indexed_by<"by.symbol"_n, const_mem_fun >, + indexed_by<"by.address"_n, const_mem_fun > + > token_table_t; - struct [[eosio::table]] [[eosio::contract("evm_contract")]] config + struct [[eosio::table]] config { - uint8_t erc20_addr[kAddressLength]; + bytes erc20_addr; EOSLIB_SERIALIZE(config, (erc20_addr)); }; private: eosio::singleton<"config"_n, config> _config{get_self(), get_self().value}; - void handle_evm_transfer(eosio::asset quantity, const std::string &memo); + void handle_erc20_transfer(const token_t &token, eosio::asset quantity, const std::string &memo); - void call(eosio::name from, const bytes &to, uint128_t value, const bytes &data, uint64_t gas_limit); + void call(eosio::name from, const bytes &to, const bytes& value, const bytes &data, uint64_t gas_limit); using call_action = action_wrapper<"call"_n, &erc20::call>; }; diff --git a/antelope_contracts/contracts/erc20/include/erc20/types.hpp b/antelope_contracts/contracts/erc20/include/erc20/types.hpp index 4a1b3a9..71279c0 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/types.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/types.hpp @@ -14,8 +14,8 @@ constexpr size_t kAddressLength{20}; constexpr size_t kHashLength{32}; constexpr unsigned evm_precision = 6; constexpr uint64_t evm_gaslimit = 500000; -constexpr uint64_t evm_init_gaslimit = 5000000; -constexpr eosio::name token_account(eosio::name("tethertether")); +constexpr uint64_t evm_init_gaslimit = 50000000; +//constexpr eosio::name token_account(eosio::name("tethertether")); constexpr eosio::symbol token_symbol("USDT", 4u); constexpr eosio::name evm_account(eosio::name("eosio.evm")); constexpr intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(evm_precision - token_symbol.precision())); diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 16634d2..538294e 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -7,64 +8,150 @@ namespace erc20 { -void erc20::init() { +void erc20::init(uint64_t nonce) { require_auth(get_self()); auto reserved_addr = silkworm::make_reserved_address(get_self().value); auto call_data = from_hex(bytecode); eosio::check(!!call_data, "bytecode should not be void"); bytes to = {}; - // Assumen account opened in evm_runtime + bytes value_zero; + value_zero.resize(32, 0); + + // required account opened in evm_runtime call_action call_act(evm_account, {{get_self(), "active"_n}}); - call_act.send(get_self(), to, 0, *call_data, evm_init_gaslimit); + call_act.send(get_self(), to, value_zero, *call_data, evm_init_gaslimit); + + evmc::address impl_addr = silkworm::create_address(reserved_addr, nonce); + + impl_contract_table_t contract_table(_self, _self.value); + contract_table.emplace(_self, [&](auto &v) { + v.id = contract_table.available_primary_key(); + v.address.resize(kAddressLength); + memcpy(&(v.address[0]), impl_addr.bytes, kAddressLength); + }); +} + +[[eosio::action]] void erc20::regtoken(uint64_t nonce, eosio::name eos_contract_name, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision) { + require_auth(get_self()); + + std::optional impl_address_bytes = from_hex(erc20_impl_address); + eosio::check(!!impl_address_bytes && impl_address_bytes->size() == kAddressLength, "invalid erc20 address"); - // Assume nonce... - auto deploy_addr = silkworm::create_address(reserved_addr, 0); + impl_contract_table_t contract_table(_self, _self.value); + auto index = contract_table.get_index<"by.address"_n>(); + auto itr = index.find(make_key(*impl_address_bytes)); - // TODO: Where can we get the addr... - config new_config = { - .erc20_addr = {}, - }; + eosio::check(itr != index.end(), "implementation contract must be deployed via erc20::init()"); - memcpy(new_config.erc20_addr, deploy_addr.bytes, kAddressLength); + auto reserved_addr = silkworm::make_reserved_address(get_self().value); + auto call_data = from_hex(proxy_bytecode); + + eosio::check(!!call_data, "proxy_bytecode should not be void"); - _config.set(new_config, get_self()); + // constructor(address erc20_impl_contract) + call_data->insert(call_data->end(), 32 - kAddressLength, 0); // padding for address + call_data->insert(call_data->end(), impl_address_bytes->begin(), impl_address_bytes->end()); + + bytes to = {}; + bytes value_zero; + value_zero.resize(32, 0); + + // required account opened in evm_runtime + call_action call_act(evm_account, {{get_self(), "active"_n}}); + call_act.send(get_self(), to, value_zero, *call_data, evm_init_gaslimit); + + evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, nonce); + + token_table_t table(_self, _self.value); + table.emplace(_self, [&](auto &v) { + v.eos_contract_name = eos_contract_name; + v.address.resize(kAddressLength, 0); + memcpy(&(v.address[0]), proxy_contract_addr.bytes, kAddressLength); + v.min_deposit = min_deposit; + v.deposit_fee = deposit_fee; + v.erc20_precision = erc20_precision; + }); } -void erc20::onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data) { - require_auth(evm_account); +void erc20::onbridgemsg(const bridge_message_t &message) { + + check(get_sender() == evm_account, "invalid sender of onbridgemsg"); - // TODO: this API will change + const bridge_message_v0 &msg = std::get(message); + check(msg.receiver == _self, "invalid message receiver"); - check(data.size() == kAddressLength + 32 , "invalid data size"); - // TODO: verify from, decode data - intx::uint256 amount = intx::be::unsafe::load((const uint8_t*)data.data() + kAddressLength); - evmc::address dest = {}; - memcpy(dest.bytes, data.data(), kAddressLength); - auto to = silkworm::extract_reserved_address(dest); + checksum256 addr_key = make_key(msg.sender); - check(!!to , "failed to extract destination address"); + token_table_t token_table(_self, _self.value); + auto index = token_table.get_index<"by.address"_n>(); + auto itr = index.find(addr_key); - eosio::check(amount % minimum_natively_representable == 0_u256, "transfer must not generate dust"); + check(itr != index.end() && itr->address == msg.sender, "ERC-20 token not registerred"); - eosio::token::transfer_action transfer_act(token_account, {{get_self(), "active"_n}}); - transfer_act.send(get_self(), *to, eosio::asset((uint64_t)(amount / minimum_natively_representable), token_symbol), std::string("Transfer from EVM")); + check(msg.data.size() >= 4, "not enough data in bridge_message_v0"); + if (*(uint32_t *)&(msg.data[0]) == 0xe5323365 /* big-endian 0x653332e5 */) { + //sha("bridgeTransferV0(address,uint,string)") = 0xe5323365 + check(msg.data.size() >= 4 + kAddressLength + 32, "not enough data in bridge_message_v0 of application type 0xe5323365"); + + evmc::address dest_addr; + memcpy(dest_addr.bytes, (void *)&(msg.data[4]), kAddressLength); + std::optional dest_acc = silkworm::extract_reserved_address(dest_addr); + check(!!dest_acc, "destination address in bridge_message_v0 must be reserved address"); + + uint8_t amount_bytes[32]={}; + memcpy(amount_bytes, (void *)&(msg.data[4 + kAddressLength]), 32); + + intx::uint256 value{}, zero{0_u256}; + value = intx::be::load(amount_bytes); + + for (int i = itr->erc20_precision - itr->min_deposit.symbol.precision(); i > 0; --i) { + check(value % 10 == zero, "bridge amount can not have dust"); + value /= 10; + } + + uint64_t dest_amount = (uint64_t)value; + check(intx::uint256(dest_amount) == value && dest_amount < (1ull<<62)-1, "bridge amount value overflow"); + check(dest_amount > 0, "bridge amount must be positive"); + + std::string memo; + int memo_len = (int)msg.data.size() - (4 + kAddressLength + 32); + if (memo_len > 0) { + memo.assign((const char *)&(msg.data[4 + kAddressLength + 32]), memo_len); + } + + eosio::token::transfer_action transfer_act(itr->eos_contract_name, {{get_self(), "active"_n}}); + transfer_act.send(get_self(), eosio::name(*dest_acc), eosio::asset(dest_amount, itr->min_deposit.symbol), memo); + } else { + check(false, "unsupported bridge_message version"); + } } void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, std::string memo) { - eosio::check(get_first_receiver() == token_account && quantity.symbol == token_symbol, - "received unexpected token"); if (to != get_self() || from == get_self()) return; + uint128_t v = get_first_receiver().value; + v <<= 64; + v |= quantity.symbol.code().raw(); + + token_table_t token_table(_self, _self.value); + auto index = token_table.get_index<"by.symbol"_n>(); + auto itr = index.find(v); + + eosio::check(itr != index.end() && itr->min_deposit.symbol == quantity.symbol, "received unregistered token"); + eosio::check(quantity.amount >= itr->min_deposit.amount && quantity.amount > itr->deposit_fee.amount, "deposit amount too less"); + + quantity.amount -= itr->deposit_fee.amount; + eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); + if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') - handle_evm_transfer(quantity, memo); + handle_erc20_transfer(*itr, quantity, memo); else eosio::check(false, "memo must be 0x EVM address"); - } -void erc20::handle_evm_transfer(eosio::asset quantity, const std::string& memo) { +void erc20::handle_erc20_transfer(const token_t &token, eosio::asset quantity, const std::string& memo) { const char method[4] = {'\xa9', '\x05', '\x9c', '\xbb'}; // sha3(transfer(address,uint256))[:4] auto address_bytes = from_hex(memo); @@ -72,7 +159,10 @@ void erc20::handle_evm_transfer(eosio::asset quantity, const std::string& memo) eosio::check(address_bytes->size() == kAddressLength, "memo must be valid 0x EVM address"); intx::uint256 value((uint64_t)quantity.amount); - value *= minimum_natively_representable; + + for (int i = token.erc20_precision - quantity.symbol.precision(); i > 0; --i) { + value *= 10; + } uint8_t value_buffer[32] = {}; intx::be::store(value_buffer, value); @@ -85,7 +175,11 @@ void erc20::handle_evm_transfer(eosio::asset quantity, const std::string& memo) call_data.insert(call_data.end(), value_buffer, value_buffer + 32); call_action call_act(evm_account, {{get_self(), "active"_n}}); - call_act.send(get_self(), *address_bytes, 0, call_data, evm_gaslimit); + + bytes value_zero; // value of EVM native token (aka EOS) + value_zero.resize(32, 0); + + call_act.send(get_self() /*from*/, token.address /*to*/, value_zero /*value*/, call_data /*data*/, evm_gaslimit /*gas_limit*/); } } // namespace erc20 \ No newline at end of file diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index e8af68a..6d00ab6 100644 --- a/solidity_contracts/erc20/contract.sol +++ b/solidity_contracts/erc20/contract.sol @@ -1486,16 +1486,18 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { string public linkedEOSAccountName; address public linkedEOSAddress; address public evmAddress; - uint256 public egressFee; + uint8 public precision; + //uint256 public egressFee; function initialize() initializer public { __ERC20_init("Bridged USDT", "USDT"); __UUPSUpgradeable_init(); evmAddress = 0xbBBBbBbbbBBBBbbbbbbBBbBB5530EA015b900000; linkedEOSAddress = 0xbbBbbbBbbBBbBBbBBBbbbBbB5530eA015740a800; linkedEOSAccountName = "eosio.erc2o"; - egressFee = 0.1e18; + precision = 6; + //egressFee = 0.1e18; - _mint(msg.sender, 1000000000000); + //_mint(msg.sender, 1000000000000); } function _authorizeUpgrade(address) internal virtual override { @@ -1520,10 +1522,11 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { // ignore mint and burn if (from == address(0) || to == address(0)) return; if (_isReservedAddress(to)) { - require(msg.value >= egressFee, "Bridge: no enough bridge fee"); // Call bridgeMessage of EVM Runtime - bytes memory n_bytes = abi.encodePacked(to, amount, memo); + // using abi.encodePacked: only the last field should be of variable length + uint32 app_version = 0x653332e5; // sha("bridgeTransferV0(address,uint,string)") + bytes memory n_bytes = abi.encodePacked(app_version, to, amount, memo); (bool success, ) = evmAddress.call{value: msg.value}(abi.encodeWithSignature("bridgeMsgV0(string,bool,bytes)", linkedEOSAccountName, true, n_bytes)); if(!success) { revert(); } @@ -1538,6 +1541,6 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { } function decimals() public view virtual override returns (uint8) { - return 6; + return precision; } } From 644391704418587181a5a8a268f55a134627e499 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 28 Aug 2023 16:26:34 +0800 Subject: [PATCH 02/11] constructor params in solidity impl contract, egresslist --- .../contracts/erc20/include/erc20/erc20.hpp | 20 +++- .../contracts/erc20/src/erc20.cpp | 109 ++++++++++++++++-- solidity_contracts/erc20/contract.sol | 25 ++-- solidity_contracts/erc20/proxy.sol | 2 +- 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index b9dd13a..4b79f45 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -44,7 +44,11 @@ class [[eosio::contract]] erc20 : public contract { [[eosio::action]] void onbridgemsg(const bridge_message_t &message); [[eosio::action]] void init(uint64_t nonce); - [[eosio::action]] void regtoken(uint64_t nonce, eosio::name eos_contract_name, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision); + [[eosio::action]] void regtoken(uint64_t nonce, eosio::name eos_contract_name, + std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision); + + [[eosio::action]] void addegress(const std::vector& accounts); + [[eosio::action]] void removeegress(const std::vector& accounts); struct [[eosio::table("implcontract")]] impl_contract_t { uint64_t id = 0; @@ -63,11 +67,11 @@ class [[eosio::contract]] erc20 : public contract { struct [[eosio::table("tokens")]] token_t { uint64_t id = 0; - eosio::name eos_contract_name; - bytes address; // <== proxy contract addr + eosio::name eos_contract_name; + bytes address; // <-- proxy contract addr eosio::asset min_deposit; eosio::asset deposit_fee; - uint64_t balance = 0; + uint64_t balance = 0; // <-- EVM side's balance int erc20_precision = 0; uint64_t primary_key() const { @@ -90,6 +94,14 @@ class [[eosio::contract]] erc20 : public contract { indexed_by<"by.address"_n, const_mem_fun > > token_table_t; + struct [[eosio::table("egresslist")]] allowed_egress_account { + eosio::name account; + + uint64_t primary_key() const { return account.value; } + EOSLIB_SERIALIZE(allowed_egress_account, (account)); + }; + typedef eosio::multi_index<"egresslist"_n, allowed_egress_account> egresslist_table_t; + struct [[eosio::table]] config { bytes erc20_addr; diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 538294e..33f32c4 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -6,8 +6,27 @@ #include #include +namespace eosio { + namespace internal_use_do_not_use { + extern "C" { + __attribute__((eosio_wasm_import)) + uint32_t get_code_hash(uint64_t account, uint32_t struct_version, char* data, uint32_t size); + } + } +} + namespace erc20 { +checksum256 get_code_hash(name account) { + char buff[64]; + + eosio::check(internal_use_do_not_use::get_code_hash(account.value, 0, buff, sizeof(buff)) <= sizeof(buff), "get_code_hash() too big"); + using start_of_code_hash_return = std::tuple; + const auto& [v, s, code_hash] = unpack(buff, sizeof(buff)); + + return code_hash; +} + void erc20::init(uint64_t nonce) { require_auth(get_self()); auto reserved_addr = silkworm::make_reserved_address(get_self().value); @@ -31,11 +50,21 @@ void erc20::init(uint64_t nonce) { }); } -[[eosio::action]] void erc20::regtoken(uint64_t nonce, eosio::name eos_contract_name, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision) { +[[eosio::action]] void erc20::regtoken(uint64_t nonce, eosio::name eos_contract_name, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision) { require_auth(get_self()); + eosio::check(evm_token_name.length() > 0 && evm_token_name.length() < 32, "invalid evm_token_name"); + eosio::check(evm_token_symbol.length() > 0 && evm_token_symbol.length() < 32, "invalid evm_token_symbol"); + std::optional impl_address_bytes = from_hex(erc20_impl_address); - eosio::check(!!impl_address_bytes && impl_address_bytes->size() == kAddressLength, "invalid erc20 address"); + eosio::check(!!impl_address_bytes && impl_address_bytes->size() == kAddressLength, "invalid erc20 address"); + + uint128_t v = eos_contract_name.value; + v <<= 64; + v |= min_deposit.symbol.code().raw(); + token_table_t token_table(_self, _self.value); + auto index_symbol = token_table.get_index<"by.symbol"_n>(); + check(index_symbol.find(v) == index_symbol.end(), "token already registered"); impl_contract_table_t contract_table(_self, _self.value); auto index = contract_table.get_index<"by.address"_n>(); @@ -52,6 +81,39 @@ void erc20::init(uint64_t nonce) { call_data->insert(call_data->end(), 32 - kAddressLength, 0); // padding for address call_data->insert(call_data->end(), impl_address_bytes->begin(), impl_address_bytes->end()); + bytes constructor_data; + // sha(initialize(uint8 _precision,string memory _name,string memory _symbol,string memory _eos_token_contract)) == 0x0735df57 + uint8_t func_[4] = {0x07,0x35,0xdf,0x57}; + constructor_data.insert(constructor_data.end(), func_, func_ + sizeof(func_)); + + auto pack_uint32 = [&](bytes &ds, uint32_t val) { + uint8_t val_[32] = {}; + val_[28] = (uint8_t)(val >> 24); + val_[29] = (uint8_t)(val >> 16); + val_[30] = (uint8_t)(val >> 8); + val_[31] = (uint8_t)val; + ds.insert(ds.end(), val_, val_ + sizeof(val_)); + }; + auto pack_string = [&](bytes &ds, const auto &str) { + pack_uint32(ds, (uint32_t)str.size()); + for (size_t i = 0; i < (str.size() + 31) / 32 * 32; i += 32) { + uint8_t name_[32] = {}; + memcpy(name_, str.data() + i, i + 32 > str.size() ? str.size() - i : 32); + ds.insert(ds.end(), name_, name_ + sizeof(name_)); + } + }; + + pack_uint32(constructor_data, (uint8_t)erc20_precision); // offset 0 + pack_uint32(constructor_data, 128); // offset 32 + pack_uint32(constructor_data, 192); // offset 64 + pack_uint32(constructor_data, 256); // offset 96 + pack_string(constructor_data, evm_token_name); // offset 128 + pack_string(constructor_data, evm_token_symbol); // offset 192 + pack_string(constructor_data, eos_contract_name.to_string()); // offset 256 + + pack_uint32(*call_data, 64); + pack_string(*call_data, constructor_data); + bytes to = {}; bytes value_zero; value_zero.resize(32, 0); @@ -62,8 +124,7 @@ void erc20::init(uint64_t nonce) { evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, nonce); - token_table_t table(_self, _self.value); - table.emplace(_self, [&](auto &v) { + token_table.emplace(_self, [&](auto &v) { v.eos_contract_name = eos_contract_name; v.address.resize(kAddressLength, 0); memcpy(&(v.address[0]), proxy_contract_addr.bytes, kAddressLength); @@ -119,8 +180,17 @@ void erc20::onbridgemsg(const bridge_message_t &message) { memo.assign((const char *)&(msg.data[4 + kAddressLength + 32]), memo_len); } + eosio::name dest_eos_acct(*dest_acc); + if (get_code_hash(dest_eos_acct) != checksum256()) { + egresslist_table_t(get_self(), get_self().value).get(dest_eos_acct.value, "native accounts containing contract code must be on allow list for egress bridging"); + } + eosio::token::transfer_action transfer_act(itr->eos_contract_name, {{get_self(), "active"_n}}); - transfer_act.send(get_self(), eosio::name(*dest_acc), eosio::asset(dest_amount, itr->min_deposit.symbol), memo); + transfer_act.send(get_self(), dest_eos_acct, eosio::asset(dest_amount, itr->min_deposit.symbol), memo); + + token_table.modify(*itr, _self, [&](auto &v) { + v.balance -= dest_amount; + }); } else { check(false, "unsupported bridge_message version"); } @@ -145,9 +215,12 @@ void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, quantity.amount -= itr->deposit_fee.amount; eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); - if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') + if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') { handle_erc20_transfer(*itr, quantity, memo); - else + token_table.modify(*itr, _self, [&](auto &v) { + v.balance += quantity.amount; + }); + } else eosio::check(false, "memo must be 0x EVM address"); } @@ -182,4 +255,26 @@ void erc20::handle_erc20_transfer(const token_t &token, eosio::asset quantity, c call_act.send(get_self() /*from*/, token.address /*to*/, value_zero /*value*/, call_data /*data*/, evm_gaslimit /*gas_limit*/); } +void erc20::addegress(const std::vector& accounts) { + require_auth(get_self()); + + egresslist_table_t egresslist_table(get_self(), get_self().value); + + for(const name& account : accounts) + if(egresslist_table.find(account.value) == egresslist_table.end()) + egresslist_table.emplace(get_self(), [&](allowed_egress_account& a) { + a.account = account; + }); +} + +void erc20::removeegress(const std::vector& accounts) { + require_auth(get_self()); + + egresslist_table_t egresslist_table(get_self(), get_self().value); + + for(const name& account : accounts) + if(auto it = egresslist_table.find(account.value); it != egresslist_table.end()) + egresslist_table.erase(it); +} + } // namespace erc20 \ No newline at end of file diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index 6d00ab6..429df4e 100644 --- a/solidity_contracts/erc20/contract.sol +++ b/solidity_contracts/erc20/contract.sol @@ -1004,7 +1004,7 @@ interface IERC20Upgradeable { * * Emits a {Transfer} event. */ - function bridgeTransfer(address to, uint256 amount, string memory memo) external returns (bool); + function bridgeTransfer(address to, uint256 amount, string memory memo) external payable returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be @@ -1206,7 +1206,7 @@ contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeabl * - `to` must be reserved address. * - the caller must have a balance of at least `amount`. */ - function bridgeTransfer(address to, uint256 amount, string memory memo) public virtual override returns (bool) { + function bridgeTransfer(address to, uint256 amount, string memory memo) public virtual override payable returns (bool) { address owner = _msgSender(); _transfer(owner, to, amount, memo); return true; @@ -1481,23 +1481,28 @@ contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeabl pragma solidity ^0.8.18; - contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { - string public linkedEOSAccountName; + string eos_token_contract; + string public linkedEOSAccountName; address public linkedEOSAddress; address public evmAddress; uint8 public precision; - //uint256 public egressFee; - function initialize() initializer public { - __ERC20_init("Bridged USDT", "USDT"); + function initialize(uint8 _precision, + string memory _name, + string memory _symbol, + string memory _eos_token_contract + ) initializer public { + __ERC20_init(_name, _symbol); __UUPSUpgradeable_init(); evmAddress = 0xbBBBbBbbbBBBBbbbbbbBBbBB5530EA015b900000; linkedEOSAddress = 0xbbBbbbBbbBBbBBbBBBbbbBbB5530eA015740a800; linkedEOSAccountName = "eosio.erc2o"; - precision = 6; - //egressFee = 0.1e18; + precision = _precision; + eos_token_contract = _eos_token_contract; + } - //_mint(msg.sender, 1000000000000); + function eosTokenContract() public view returns (string memory) { + return eos_token_contract; } function _authorizeUpgrade(address) internal virtual override { diff --git a/solidity_contracts/erc20/proxy.sol b/solidity_contracts/erc20/proxy.sol index a99ec6d..56e4254 100644 --- a/solidity_contracts/erc20/proxy.sol +++ b/solidity_contracts/erc20/proxy.sol @@ -747,5 +747,5 @@ pragma solidity ^0.8.18; contract Erc20Proxy is ERC1967Proxy { - constructor(address _logic) ERC1967Proxy(_logic, abi.encodeWithSignature("initialize()")) {} + constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) {} } From e82b57ec4be59f8afb1c764a03e1016344976f93 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 28 Aug 2023 17:53:11 +0800 Subject: [PATCH 03/11] remove _config --- .../contracts/erc20/include/erc20/erc20.hpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 4b79f45..2ced5af 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -102,15 +102,6 @@ class [[eosio::contract]] erc20 : public contract { }; typedef eosio::multi_index<"egresslist"_n, allowed_egress_account> egresslist_table_t; - struct [[eosio::table]] config - { - bytes erc20_addr; - EOSLIB_SERIALIZE(config, (erc20_addr)); - }; - - private: - - eosio::singleton<"config"_n, config> _config{get_self(), get_self().value}; void handle_erc20_transfer(const token_t &token, eosio::asset quantity, const std::string &memo); void call(eosio::name from, const bytes &to, const bytes& value, const bytes &data, uint64_t gas_limit); From 635d19585ee5daa05561da08ad7d862450c80211 Mon Sep 17 00:00:00 2001 From: kayan Date: Wed, 30 Aug 2023 18:25:50 +0800 Subject: [PATCH 04/11] fix nonce, exp, egress --- .../contracts/erc20/include/erc20/erc20.hpp | 24 +++++-- .../contracts/erc20/src/erc20.cpp | 72 ++++++++++++------- solidity_contracts/erc20/contract.sol | 5 +- 3 files changed, 71 insertions(+), 30 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 2ced5af..5ca7020 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -11,6 +11,10 @@ using namespace intx; namespace erc20 { +constexpr unsigned evm_precision = 18; +constexpr eosio::symbol native_token_symbol("EOS", 4u); +constexpr intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(evm_precision - native_token_symbol.precision())); + checksum256 make_key(const uint8_t* ptr, size_t len) { uint8_t buffer[32]={0}; check(len <= sizeof(buffer), "invalida size"); @@ -42,14 +46,26 @@ class [[eosio::contract]] erc20 : public contract { // evm runtime will call this to notify erc20 about the message from 'from' with 'data'. [[eosio::action]] void onbridgemsg(const bridge_message_t &message); - [[eosio::action]] void init(uint64_t nonce); + [[eosio::action]] void init(); - [[eosio::action]] void regtoken(uint64_t nonce, eosio::name eos_contract_name, - std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision); + [[eosio::action]] void regtoken(eosio::name eos_contract_name, + std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, std::string erc20_impl_address, int erc20_precision); [[eosio::action]] void addegress(const std::vector& accounts); [[eosio::action]] void removeegress(const std::vector& accounts); + uint64_t my_next_nonce(); + + struct [[eosio::table("nextnonces")]] nextnonce { + name owner; + uint64_t next_nonce = 0; + + uint64_t primary_key() const { return owner.value; } + + EOSLIB_SERIALIZE(nextnonce, (owner)(next_nonce)); + }; + typedef eosio::multi_index<"nextnonces"_n, nextnonce> nextnonces_table_t; + struct [[eosio::table("implcontract")]] impl_contract_t { uint64_t id = 0; bytes address; @@ -71,7 +87,7 @@ class [[eosio::contract]] erc20 : public contract { bytes address; // <-- proxy contract addr eosio::asset min_deposit; eosio::asset deposit_fee; - uint64_t balance = 0; // <-- EVM side's balance + uint64_t balance = 0; // <-- total amount in EVM side, using native side precision int erc20_precision = 0; uint64_t primary_key() const { diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 72cee8d..7255747 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -36,7 +36,14 @@ void initialize_data(bytes& output, const unsigned char (&arr)[Size]) { std::memcpy(output.data(), arr, Size); } -void erc20::init(uint64_t nonce) { +uint64_t erc20::my_next_nonce() { // lookup nonce from the multi_index table of evm contract + nextnonces_table_t table(evm_account, evm_account.value); + auto itr = table.find(get_self().value); + if (itr == table.end()) return 0; + else return itr->next_nonce; +} + +void erc20::init() { require_auth(get_self()); bytes call_data; @@ -53,7 +60,7 @@ void erc20::init(uint64_t nonce) { call_act.send(get_self(), to, value_zero, call_data, evm_init_gaslimit); - evmc::address impl_addr = silkworm::create_address(reserved_addr, nonce); + evmc::address impl_addr = silkworm::create_address(reserved_addr, my_next_nonce()); impl_contract_table_t contract_table(_self, _self.value); contract_table.emplace(_self, [&](auto &v) { @@ -63,15 +70,28 @@ void erc20::init(uint64_t nonce) { }); } -[[eosio::action]] void erc20::regtoken(uint64_t nonce, eosio::name eos_contract_name, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, std::string erc20_impl_address, int erc20_precision) { +[[eosio::action]] void erc20::regtoken(eosio::name eos_contract_name, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, std::string erc20_impl_address, int erc20_precision) { require_auth(get_self()); - eosio::check(evm_token_name.length() > 0 && evm_token_name.length() < 32, "invalid evm_token_name"); - eosio::check(evm_token_symbol.length() > 0 && evm_token_symbol.length() < 32, "invalid evm_token_symbol"); + eosio::check(eos_contract_name.value, "invalid eos_contract_name"); + + // keep the name & symbol fit into 32 byte, which is the alignment in EVM + eosio::check(evm_token_name.length() > 0 && evm_token_name.length() < 32, "invalid evm_token_name length"); + eosio::check(evm_token_symbol.length() > 0 && evm_token_symbol.length() < 32, "invalid evm_token_symbol length"); + + // 2^(256-64) = 6.2e+57, so the precision diff is at most 57 + eosio::check(erc20_precision >= min_deposit.symbol.precision() && + erc20_precision <= min_deposit.symbol.precision() + 57, "erc20 precision out of range"); std::optional impl_address_bytes = from_hex(erc20_impl_address); eosio::check(!!impl_address_bytes && impl_address_bytes->size() == kAddressLength, "invalid erc20 address"); + eosio::check(min_deposit.symbol == deposit_fee.symbol, "deposit_fee should have the same symbol of min_deposit"); + + eosio::check(egress_fee.symbol == native_token_symbol, "egress_fee should have native token symbol"); + intx::uint256 egress_fee_evm = egress_fee.amount; + egress_fee_evm *= minimum_natively_representable; + uint128_t v = eos_contract_name.value; v <<= 64; v |= min_deposit.symbol.code().raw(); @@ -95,10 +115,15 @@ void erc20::init(uint64_t nonce) { call_data.insert(call_data.end(), impl_address_bytes->begin(), impl_address_bytes->end()); bytes constructor_data; - // sha(initialize(uint8 _precision,string memory _name,string memory _symbol,string memory _eos_token_contract)) == 0x0735df57 - uint8_t func_[4] = {0x07,0x35,0xdf,0x57}; + // sha(function initialize(uint8 _precision,uint256 _egressFee,string memory _name,string memory _symbol,string memory _eos_token_contract)) == 0xd66d4ac3 + uint8_t func_[4] = {0xd6,0x6d,0x4a,0xc3}; constructor_data.insert(constructor_data.end(), func_, func_ + sizeof(func_)); - + + auto pack_uint256 = [&](bytes &ds, const intx::uint256 &val) { + uint8_t val_[32] = {}; + intx::be::store(val_, val); + ds.insert(ds.end(), val_, val_ + sizeof(val_)); + }; auto pack_uint32 = [&](bytes &ds, uint32_t val) { uint8_t val_[32] = {}; val_[28] = (uint8_t)(val >> 24); @@ -117,15 +142,16 @@ void erc20::init(uint64_t nonce) { }; pack_uint32(constructor_data, (uint8_t)erc20_precision); // offset 0 - pack_uint32(constructor_data, 128); // offset 32 - pack_uint32(constructor_data, 192); // offset 64 - pack_uint32(constructor_data, 256); // offset 96 - pack_string(constructor_data, evm_token_name); // offset 128 - pack_string(constructor_data, evm_token_symbol); // offset 192 - pack_string(constructor_data, eos_contract_name.to_string()); // offset 256 + pack_uint256(constructor_data, egress_fee_evm); // offset 32 + pack_uint32(constructor_data, 160); // offset 64 + pack_uint32(constructor_data, 224); // offset 96 + pack_uint32(constructor_data, 288); // offset 128 + pack_string(constructor_data, evm_token_name); // offset 160 + pack_string(constructor_data, evm_token_symbol); // offset 224 + pack_string(constructor_data, eos_contract_name.to_string()); // offset 288 - pack_uint32(call_data, 64); - pack_string(call_data, constructor_data); + pack_uint32(call_data, 64); // offset 32 + pack_string(call_data, constructor_data); // offset 64 bytes to = {}; bytes value_zero; @@ -135,7 +161,7 @@ void erc20::init(uint64_t nonce) { call_action call_act(evm_account, {{get_self(), "active"_n}}); call_act.send(get_self(), to, value_zero, call_data, evm_init_gaslimit); - evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, nonce); + evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, my_next_nonce()); token_table.emplace(_self, [&](auto &v) { v.eos_contract_name = eos_contract_name; @@ -178,10 +204,9 @@ void erc20::onbridgemsg(const bridge_message_t &message) { intx::uint256 value{}, zero{0_u256}; value = intx::be::load(amount_bytes); - for (int i = itr->erc20_precision - itr->min_deposit.symbol.precision(); i > 0; --i) { - check(value % 10 == zero, "bridge amount can not have dust"); - value /= 10; - } + intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->min_deposit.symbol.precision())); + check(value % mult == zero, "bridge amount can not have dust"); + value /= mult; uint64_t dest_amount = (uint64_t)value; check(intx::uint256(dest_amount) == value && dest_amount < (1ull<<62)-1, "bridge amount value overflow"); @@ -245,10 +270,7 @@ void erc20::handle_erc20_transfer(const token_t &token, eosio::asset quantity, c eosio::check(address_bytes->size() == kAddressLength, "memo must be valid 0x EVM address"); intx::uint256 value((uint64_t)quantity.amount); - - for (int i = token.erc20_precision - quantity.symbol.precision(); i > 0; --i) { - value *= 10; - } + value *= intx::exp(10_u256, intx::uint256(token.erc20_precision - quantity.symbol.precision())); uint8_t value_buffer[32] = {}; intx::be::store(value_buffer, value); diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index 429df4e..947ad1d 100644 --- a/solidity_contracts/erc20/contract.sol +++ b/solidity_contracts/erc20/contract.sol @@ -1487,7 +1487,9 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { address public linkedEOSAddress; address public evmAddress; uint8 public precision; + uint256 public egressFee; function initialize(uint8 _precision, + uint256 _egressFee, string memory _name, string memory _symbol, string memory _eos_token_contract @@ -1498,6 +1500,7 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { linkedEOSAddress = 0xbbBbbbBbbBBbBBbBBBbbbBbB5530eA015740a800; linkedEOSAccountName = "eosio.erc2o"; precision = _precision; + egressFee = _egressFee; eos_token_contract = _eos_token_contract; } @@ -1527,7 +1530,7 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { // ignore mint and burn if (from == address(0) || to == address(0)) return; if (_isReservedAddress(to)) { - + require(msg.value == egressFee, "incorrect egress bridge fee"); // Call bridgeMessage of EVM Runtime // using abi.encodePacked: only the last field should be of variable length uint32 app_version = 0x653332e5; // sha("bridgeTransferV0(address,uint,string)") From d062b9e011936f9ce6b984ec263878ee38964089 Mon Sep 17 00:00:00 2001 From: kayan Date: Fri, 1 Sep 2023 17:02:13 +0800 Subject: [PATCH 05/11] fix account, fee, message type, .. etc --- .../contracts/erc20/include/erc20/erc20.hpp | 43 +++--- .../contracts/erc20/include/erc20/types.hpp | 7 + .../contracts/erc20/src/erc20.cpp | 143 ++++++++++-------- solidity_contracts/erc20/contract.sol | 11 +- 4 files changed, 117 insertions(+), 87 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 5ca7020..0df8975 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -11,13 +11,9 @@ using namespace intx; namespace erc20 { -constexpr unsigned evm_precision = 18; -constexpr eosio::symbol native_token_symbol("EOS", 4u); -constexpr intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(evm_precision - native_token_symbol.precision())); - checksum256 make_key(const uint8_t* ptr, size_t len) { - uint8_t buffer[32]={0}; - check(len <= sizeof(buffer), "invalida size"); + uint8_t buffer[32]={}; + check(len <= sizeof(buffer), "len provided to make_key is too small"); memcpy(buffer, ptr, len); return checksum256(buffer); } @@ -46,25 +42,23 @@ class [[eosio::contract]] erc20 : public contract { // evm runtime will call this to notify erc20 about the message from 'from' with 'data'. [[eosio::action]] void onbridgemsg(const bridge_message_t &message); - [[eosio::action]] void init(); + [[eosio::action]] void upgrade(); [[eosio::action]] void regtoken(eosio::name eos_contract_name, - std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, std::string erc20_impl_address, int erc20_precision); + std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, uint8_t erc20_precision); [[eosio::action]] void addegress(const std::vector& accounts); [[eosio::action]] void removeegress(const std::vector& accounts); - uint64_t my_next_nonce(); + uint64_t get_next_nonce(); - struct [[eosio::table("nextnonces")]] nextnonce { + struct nextnonce { name owner; uint64_t next_nonce = 0; uint64_t primary_key() const { return owner.value; } - EOSLIB_SERIALIZE(nextnonce, (owner)(next_nonce)); }; - typedef eosio::multi_index<"nextnonces"_n, nextnonce> nextnonces_table_t; struct [[eosio::table("implcontract")]] impl_contract_t { uint64_t id = 0; @@ -73,37 +67,33 @@ class [[eosio::contract]] erc20 : public contract { uint64_t primary_key() const { return id; } - checksum256 by_address()const { - return make_key(address); - } + EOSLIB_SERIALIZE(impl_contract_t, (id)(address)); }; - typedef eosio::multi_index<"implcontract"_n, impl_contract_t, - indexed_by<"by.address"_n, const_mem_fun > - > impl_contract_table_t; + typedef eosio::multi_index<"implcontract"_n, impl_contract_t> impl_contract_table_t; struct [[eosio::table("tokens")]] token_t { uint64_t id = 0; - eosio::name eos_contract_name; + eosio::name token_contract; bytes address; // <-- proxy contract addr - eosio::asset min_deposit; eosio::asset deposit_fee; - uint64_t balance = 0; // <-- total amount in EVM side, using native side precision + eosio::asset balance; // <-- total amount in EVM side + eosio::asset fee_balance; int erc20_precision = 0; uint64_t primary_key() const { return id; } uint128_t by_contract_symbol() const { - uint128_t v = eos_contract_name.value; + uint128_t v = token_contract.value; v <<= 64; - v |= min_deposit.symbol.code().raw(); + v |= deposit_fee.symbol.code().raw(); return v; } checksum256 by_address()const { return make_key(address); } - EOSLIB_SERIALIZE(token_t, (id)(eos_contract_name)(address)(min_deposit)(deposit_fee)(balance)(erc20_precision)); + EOSLIB_SERIALIZE(token_t, (id)(token_contract)(address)(deposit_fee)(balance)(fee_balance)(erc20_precision)); }; typedef eosio::multi_index<"tokens"_n, token_t, indexed_by<"by.symbol"_n, const_mem_fun >, @@ -120,8 +110,13 @@ class [[eosio::contract]] erc20 : public contract { void handle_erc20_transfer(const token_t &token, eosio::asset quantity, const std::string &memo); + // actions defined in evm_runtime contract void call(eosio::name from, const bytes &to, const bytes& value, const bytes &data, uint64_t gas_limit); using call_action = action_wrapper<"call"_n, &erc20::call>; + + void assertnonce(eosio::name account, uint64_t next_nonce); + using assertnonce_action = action_wrapper<"assertnonce"_n, &erc20::assertnonce>; + }; } // namespace erc20 \ No newline at end of file diff --git a/antelope_contracts/contracts/erc20/include/erc20/types.hpp b/antelope_contracts/contracts/erc20/include/erc20/types.hpp index a1c4c9c..0659b5c 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/types.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/types.hpp @@ -14,6 +14,13 @@ constexpr size_t kAddressLength{20}; constexpr size_t kHashLength{32}; constexpr uint64_t evm_gaslimit = 500000; constexpr uint64_t evm_init_gaslimit = 10000000; + constexpr eosio::name evm_account(eosio::name("eosio.evm")); +constexpr eosio::name erc2o_account(eosio::name("eosio.erc2o")); + +constexpr unsigned evm_precision = 18; // precision of native token(aka.EOS) in EVM side +constexpr eosio::symbol native_token_symbol("EOS", 4u); +constexpr intx::uint256 minimum_natively_representable = intx::exp(10_u256, intx::uint256(evm_precision - native_token_symbol.precision())); + } // namespace erc20 \ No newline at end of file diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 7255747..e5e4195 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -36,83 +36,86 @@ void initialize_data(bytes& output, const unsigned char (&arr)[Size]) { std::memcpy(output.data(), arr, Size); } -uint64_t erc20::my_next_nonce() { // lookup nonce from the multi_index table of evm contract - nextnonces_table_t table(evm_account, evm_account.value); - auto itr = table.find(get_self().value); - if (itr == table.end()) return 0; - else return itr->next_nonce; +// lookup nonce from the multi_index table of evm contract and assert +uint64_t erc20::get_next_nonce() { + eosio::multi_index<"nextnonces"_n, nextnonce> table(evm_account, evm_account.value); + auto itr = table.find(erc2o_account.value); + uint64_t next_nonce = (itr == table.end() ? 0 : itr->next_nonce); + + assertnonce_action act(evm_account, {{erc2o_account, "active"_n}}); + act.send(erc2o_account, next_nonce); + return next_nonce; } -void erc20::init() { +void erc20::upgrade() { require_auth(get_self()); bytes call_data; - auto reserved_addr = silkworm::make_reserved_address(get_self().value); + auto reserved_addr = silkworm::make_reserved_address(erc2o_account.value); initialize_data(call_data, solidity::erc20::bytecode); bytes to = {}; bytes value_zero; value_zero.resize(32, 0); - // required account opened in evm_runtime - call_action call_act(evm_account, {{get_self(), "active"_n}}); - call_act.send(get_self(), to, value_zero, call_data, evm_init_gaslimit); + uint64_t next_nonce = get_next_nonce(); + // required account opened in evm_runtime + call_action call_act(evm_account, {{erc2o_account, "active"_n}}); + call_act.send(erc2o_account, to, value_zero, call_data, evm_init_gaslimit); - evmc::address impl_addr = silkworm::create_address(reserved_addr, my_next_nonce()); + evmc::address impl_addr = silkworm::create_address(reserved_addr, next_nonce); + uint64_t id = 0; impl_contract_table_t contract_table(_self, _self.value); + + check(contract_table.find(id) == contract_table.end(), "implementation contract already deployed"); + contract_table.emplace(_self, [&](auto &v) { - v.id = contract_table.available_primary_key(); + v.id = id; v.address.resize(kAddressLength); memcpy(&(v.address[0]), impl_addr.bytes, kAddressLength); }); } -[[eosio::action]] void erc20::regtoken(eosio::name eos_contract_name, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& min_deposit, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, std::string erc20_impl_address, int erc20_precision) { +[[eosio::action]] void erc20::regtoken(eosio::name token_contract, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, uint8_t erc20_precision) { require_auth(get_self()); - eosio::check(eos_contract_name.value, "invalid eos_contract_name"); + eosio::check(eosio::is_account(token_contract), "invalid token_contract"); // keep the name & symbol fit into 32 byte, which is the alignment in EVM eosio::check(evm_token_name.length() > 0 && evm_token_name.length() < 32, "invalid evm_token_name length"); eosio::check(evm_token_symbol.length() > 0 && evm_token_symbol.length() < 32, "invalid evm_token_symbol length"); // 2^(256-64) = 6.2e+57, so the precision diff is at most 57 - eosio::check(erc20_precision >= min_deposit.symbol.precision() && - erc20_precision <= min_deposit.symbol.precision() + 57, "erc20 precision out of range"); - - std::optional impl_address_bytes = from_hex(erc20_impl_address); - eosio::check(!!impl_address_bytes && impl_address_bytes->size() == kAddressLength, "invalid erc20 address"); - - eosio::check(min_deposit.symbol == deposit_fee.symbol, "deposit_fee should have the same symbol of min_deposit"); + eosio::check(erc20_precision >= deposit_fee.symbol.precision() && + erc20_precision <= deposit_fee.symbol.precision() + 57, "erc20 precision out of range"); eosio::check(egress_fee.symbol == native_token_symbol, "egress_fee should have native token symbol"); intx::uint256 egress_fee_evm = egress_fee.amount; egress_fee_evm *= minimum_natively_representable; - uint128_t v = eos_contract_name.value; + uint128_t v = token_contract.value; v <<= 64; - v |= min_deposit.symbol.code().raw(); + v |= deposit_fee.symbol.code().raw(); token_table_t token_table(_self, _self.value); auto index_symbol = token_table.get_index<"by.symbol"_n>(); check(index_symbol.find(v) == index_symbol.end(), "token already registered"); impl_contract_table_t contract_table(_self, _self.value); - auto index = contract_table.get_index<"by.address"_n>(); - auto itr = index.find(make_key(*impl_address_bytes)); + eosio::check(contract_table.begin() != contract_table.end(), "no implementaion contract available"); + auto contract_itr = contract_table.end(); + --contract_itr; - eosio::check(itr != index.end(), "implementation contract must be deployed via erc20::init()"); - - auto reserved_addr = silkworm::make_reserved_address(get_self().value); + auto reserved_addr = silkworm::make_reserved_address(erc2o_account.value); bytes call_data; initialize_data(call_data, solidity::proxy::bytecode); - // constructor(address erc20_impl_contract) + // constructor(address erc20_impl_contract, memory _data) call_data.insert(call_data.end(), 32 - kAddressLength, 0); // padding for address - call_data.insert(call_data.end(), impl_address_bytes->begin(), impl_address_bytes->end()); + call_data.insert(call_data.end(), contract_itr->address.begin(), contract_itr->address.end()); bytes constructor_data; // sha(function initialize(uint8 _precision,uint256 _egressFee,string memory _name,string memory _symbol,string memory _eos_token_contract)) == 0xd66d4ac3 @@ -148,7 +151,7 @@ void erc20::init() { pack_uint32(constructor_data, 288); // offset 128 pack_string(constructor_data, evm_token_name); // offset 160 pack_string(constructor_data, evm_token_symbol); // offset 224 - pack_string(constructor_data, eos_contract_name.to_string()); // offset 288 + pack_string(constructor_data, token_contract.to_string()); // offset 288 pack_uint32(call_data, 64); // offset 32 pack_string(call_data, constructor_data); // offset 64 @@ -157,18 +160,23 @@ void erc20::init() { bytes value_zero; value_zero.resize(32, 0); + uint64_t next_nonce = get_next_nonce(); + // required account opened in evm_runtime - call_action call_act(evm_account, {{get_self(), "active"_n}}); - call_act.send(get_self(), to, value_zero, call_data, evm_init_gaslimit); + call_action call_act(evm_account, {{erc2o_account, "active"_n}}); + call_act.send(erc2o_account, to, value_zero, call_data, evm_init_gaslimit); - evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, my_next_nonce()); + evmc::address proxy_contract_addr = silkworm::create_address(reserved_addr, next_nonce); token_table.emplace(_self, [&](auto &v) { - v.eos_contract_name = eos_contract_name; + v.id = token_table.available_primary_key(); + v.token_contract = token_contract; v.address.resize(kAddressLength, 0); memcpy(&(v.address[0]), proxy_contract_addr.bytes, kAddressLength); - v.min_deposit = min_deposit; v.deposit_fee = deposit_fee; + v.balance = deposit_fee; + v.balance.amount = 0; + v.fee_balance = v.balance; v.erc20_precision = erc20_precision; }); } @@ -178,7 +186,7 @@ void erc20::onbridgemsg(const bridge_message_t &message) { check(get_sender() == evm_account, "invalid sender of onbridgemsg"); const bridge_message_v0 &msg = std::get(message); - check(msg.receiver == _self, "invalid message receiver"); + check(msg.receiver == erc2o_account, "invalid message receiver"); checksum256 addr_key = make_key(msg.sender); @@ -189,33 +197,46 @@ void erc20::onbridgemsg(const bridge_message_t &message) { check(itr != index.end() && itr->address == msg.sender, "ERC-20 token not registerred"); check(msg.data.size() >= 4, "not enough data in bridge_message_v0"); - if (*(uint32_t *)&(msg.data[0]) == 0xe5323365 /* big-endian 0x653332e5 */) { - //sha("bridgeTransferV0(address,uint,string)") = 0xe5323365 - check(msg.data.size() >= 4 + kAddressLength + 32, "not enough data in bridge_message_v0 of application type 0xe5323365"); + + uint32_t app_type = 0; + memcpy((void *)&app_type, (const void *)&(msg.data[0]), sizeof(app_type)); + + if (app_type == 0xe5323365 /* big-endian 0x653332e5 */) { + // abi.encodeWithSignature("bridgeTransferV0(address,uint,string)", to, amount, memo); + // sha("bridgeTransferV0(address,uint,string)") = 0x653332e5 + check(msg.data.size() >= 4 + 32 /*to*/ + 32 /*amount*/ + 32 /*memo offset*/ + 32 /*memo len*/, + "not enough data in bridge_message_v0 of application type 0x653332e5"); evmc::address dest_addr; - memcpy(dest_addr.bytes, (void *)&(msg.data[4]), kAddressLength); + memcpy(dest_addr.bytes, (void *)&(msg.data[4 + 32 - kAddressLength]), kAddressLength); std::optional dest_acc = silkworm::extract_reserved_address(dest_addr); check(!!dest_acc, "destination address in bridge_message_v0 must be reserved address"); - uint8_t amount_bytes[32]={}; - memcpy(amount_bytes, (void *)&(msg.data[4 + kAddressLength]), 32); - - intx::uint256 value{}, zero{0_u256}; - value = intx::be::load(amount_bytes); + auto read_uint256 = [&](const auto &msg, size_t offset) -> intx::uint256 { + uint8_t buffer[32]={}; + check(msg.data.size() >= offset + 32, "not enough data in bridge_message_v0 of application type 0x653332e5"); + memcpy(buffer, (void *)&(msg.data[offset]), 32); + return intx::be::load(buffer); + }; - intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->min_deposit.symbol.precision())); - check(value % mult == zero, "bridge amount can not have dust"); + intx::uint256 value = read_uint256(msg, 4 + 32); + intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->deposit_fee.symbol.precision())); + check(value % mult == 0_u256, "bridge amount can not have dust"); value /= mult; uint64_t dest_amount = (uint64_t)value; check(intx::uint256(dest_amount) == value && dest_amount < (1ull<<62)-1, "bridge amount value overflow"); check(dest_amount > 0, "bridge amount must be positive"); + check(read_uint256(msg, 4 + 32 + 32) == 96_u256, "invalid memo offset in bridge_message_v0"); + + intx::uint256 memo_len_ = read_uint256(msg, 4 + 32 + 32 + 32); + size_t memo_len = (uint64_t)(memo_len_); + check(memo_len_ <= 256_u256 && msg.data.size() >= 4 + 32 + 32 + 32 + 32 + memo_len, + "invalid memo length in bridge_message_v0"); std::string memo; - int memo_len = (int)msg.data.size() - (4 + kAddressLength + 32); if (memo_len > 0) { - memo.assign((const char *)&(msg.data[4 + kAddressLength + 32]), memo_len); + memo.assign((const char *)&(msg.data[4 + 32 + 32 + 32 + 32]), memo_len); } eosio::name dest_eos_acct(*dest_acc); @@ -223,14 +244,14 @@ void erc20::onbridgemsg(const bridge_message_t &message) { egresslist_table_t(get_self(), get_self().value).get(dest_eos_acct.value, "native accounts containing contract code must be on allow list for egress bridging"); } - eosio::token::transfer_action transfer_act(itr->eos_contract_name, {{get_self(), "active"_n}}); - transfer_act.send(get_self(), dest_eos_acct, eosio::asset(dest_amount, itr->min_deposit.symbol), memo); + eosio::token::transfer_action transfer_act(itr->token_contract, {{get_self(), "active"_n}}); + transfer_act.send(get_self(), dest_eos_acct, eosio::asset(dest_amount, itr->deposit_fee.symbol), memo); token_table.modify(*itr, _self, [&](auto &v) { - v.balance -= dest_amount; + v.balance.amount -= dest_amount; }); } else { - check(false, "unsupported bridge_message version"); + eosio::check(false, "unsupported bridge_message version"); } } @@ -247,16 +268,18 @@ void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, auto index = token_table.get_index<"by.symbol"_n>(); auto itr = index.find(v); - eosio::check(itr != index.end() && itr->min_deposit.symbol == quantity.symbol, "received unregistered token"); - eosio::check(quantity.amount >= itr->min_deposit.amount && quantity.amount > itr->deposit_fee.amount, "deposit amount too less"); + eosio::check(itr != index.end() && itr->deposit_fee.symbol == quantity.symbol, "received unregistered token"); + eosio::check(quantity.amount > itr->deposit_fee.amount, "deposit amount must be greater than ingress fee"); - quantity.amount -= itr->deposit_fee.amount; + uint64_t deposit_fee = itr->deposit_fee.amount; + quantity.amount -= deposit_fee; eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') { handle_erc20_transfer(*itr, quantity, memo); token_table.modify(*itr, _self, [&](auto &v) { - v.balance += quantity.amount; + v.balance.amount += quantity.amount; + v.fee_balance.amount += deposit_fee; }); } else eosio::check(false, "memo must be 0x EVM address"); @@ -282,12 +305,12 @@ void erc20::handle_erc20_transfer(const token_t &token, eosio::asset quantity, c call_data.insert(call_data.end(), address_bytes->begin(), address_bytes->end()); call_data.insert(call_data.end(), value_buffer, value_buffer + 32); - call_action call_act(evm_account, {{get_self(), "active"_n}}); + call_action call_act(evm_account, {{erc2o_account, "active"_n}}); bytes value_zero; // value of EVM native token (aka EOS) value_zero.resize(32, 0); - call_act.send(get_self() /*from*/, token.address /*to*/, value_zero /*value*/, call_data /*data*/, evm_gaslimit /*gas_limit*/); + call_act.send(erc2o_account /*from*/, token.address /*to*/, value_zero /*value*/, call_data /*data*/, evm_gaslimit /*gas_limit*/); } void erc20::addegress(const std::vector& accounts) { diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index 947ad1d..36c40ab 100644 --- a/solidity_contracts/erc20/contract.sol +++ b/solidity_contracts/erc20/contract.sol @@ -1504,6 +1504,11 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { eos_token_contract = _eos_token_contract; } + function setFee(uint256 _egressFee) public { + require(msg.sender == linkedEOSAddress, "Bridge: only linked EOS address can set fee"); + egressFee = _egressFee; + } + function eosTokenContract() public view returns (string memory) { return eos_token_contract; } @@ -1533,9 +1538,9 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { require(msg.value == egressFee, "incorrect egress bridge fee"); // Call bridgeMessage of EVM Runtime // using abi.encodePacked: only the last field should be of variable length - uint32 app_version = 0x653332e5; // sha("bridgeTransferV0(address,uint,string)") - bytes memory n_bytes = abi.encodePacked(app_version, to, amount, memo); - (bool success, ) = evmAddress.call{value: msg.value}(abi.encodeWithSignature("bridgeMsgV0(string,bool,bytes)", linkedEOSAccountName, true, n_bytes)); + // sha("bridgeTransferV0(address,uint256,string)") = 0x653332e5 + bytes memory receiver_msg = abi.encodeWithSignature("bridgeTransferV0(address,uint256,string)", to, amount, memo); + (bool success, ) = evmAddress.call{value: msg.value}(abi.encodeWithSignature("bridgeMsgV0(string,bool,bytes)", linkedEOSAccountName, true, receiver_msg )); if(!success) { revert(); } _burn(to, amount); From 0d42dec266107a1d7c37c52185fd11d2add614fd Mon Sep 17 00:00:00 2001 From: kayan Date: Fri, 1 Sep 2023 17:21:36 +0800 Subject: [PATCH 06/11] rename deposit_fee to ingress_fee --- .../contracts/erc20/include/erc20/erc20.hpp | 8 ++--- .../contracts/erc20/src/erc20.cpp | 35 +++++++++---------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 0df8975..1b616c3 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -45,7 +45,7 @@ class [[eosio::contract]] erc20 : public contract { [[eosio::action]] void upgrade(); [[eosio::action]] void regtoken(eosio::name eos_contract_name, - std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, uint8_t erc20_precision); + std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& ingress_fee, const eosio::asset &egress_fee, uint8_t erc20_precision); [[eosio::action]] void addegress(const std::vector& accounts); [[eosio::action]] void removeegress(const std::vector& accounts); @@ -75,7 +75,7 @@ class [[eosio::contract]] erc20 : public contract { uint64_t id = 0; eosio::name token_contract; bytes address; // <-- proxy contract addr - eosio::asset deposit_fee; + eosio::asset ingress_fee; eosio::asset balance; // <-- total amount in EVM side eosio::asset fee_balance; int erc20_precision = 0; @@ -86,14 +86,14 @@ class [[eosio::contract]] erc20 : public contract { uint128_t by_contract_symbol() const { uint128_t v = token_contract.value; v <<= 64; - v |= deposit_fee.symbol.code().raw(); + v |= ingress_fee.symbol.code().raw(); return v; } checksum256 by_address()const { return make_key(address); } - EOSLIB_SERIALIZE(token_t, (id)(token_contract)(address)(deposit_fee)(balance)(fee_balance)(erc20_precision)); + EOSLIB_SERIALIZE(token_t, (id)(token_contract)(address)(ingress_fee)(balance)(fee_balance)(erc20_precision)); }; typedef eosio::multi_index<"tokens"_n, token_t, indexed_by<"by.symbol"_n, const_mem_fun >, diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index e5e4195..5933a3a 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -50,6 +50,10 @@ uint64_t erc20::get_next_nonce() { void erc20::upgrade() { require_auth(get_self()); + uint64_t id = 0; + impl_contract_table_t contract_table(_self, _self.value); + check(contract_table.find(id) == contract_table.end(), "implementation contract already deployed"); + bytes call_data; auto reserved_addr = silkworm::make_reserved_address(erc2o_account.value); @@ -67,11 +71,6 @@ void erc20::upgrade() { evmc::address impl_addr = silkworm::create_address(reserved_addr, next_nonce); - uint64_t id = 0; - impl_contract_table_t contract_table(_self, _self.value); - - check(contract_table.find(id) == contract_table.end(), "implementation contract already deployed"); - contract_table.emplace(_self, [&](auto &v) { v.id = id; v.address.resize(kAddressLength); @@ -79,7 +78,7 @@ void erc20::upgrade() { }); } -[[eosio::action]] void erc20::regtoken(eosio::name token_contract, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& deposit_fee, const eosio::asset &egress_fee, uint8_t erc20_precision) { +[[eosio::action]] void erc20::regtoken(eosio::name token_contract, std::string evm_token_name, std::string evm_token_symbol, const eosio::asset& ingress_fee, const eosio::asset &egress_fee, uint8_t erc20_precision) { require_auth(get_self()); eosio::check(eosio::is_account(token_contract), "invalid token_contract"); @@ -89,8 +88,8 @@ void erc20::upgrade() { eosio::check(evm_token_symbol.length() > 0 && evm_token_symbol.length() < 32, "invalid evm_token_symbol length"); // 2^(256-64) = 6.2e+57, so the precision diff is at most 57 - eosio::check(erc20_precision >= deposit_fee.symbol.precision() && - erc20_precision <= deposit_fee.symbol.precision() + 57, "erc20 precision out of range"); + eosio::check(erc20_precision >= ingress_fee.symbol.precision() && + erc20_precision <= ingress_fee.symbol.precision() + 57, "erc20 precision out of range"); eosio::check(egress_fee.symbol == native_token_symbol, "egress_fee should have native token symbol"); intx::uint256 egress_fee_evm = egress_fee.amount; @@ -98,7 +97,7 @@ void erc20::upgrade() { uint128_t v = token_contract.value; v <<= 64; - v |= deposit_fee.symbol.code().raw(); + v |= ingress_fee.symbol.code().raw(); token_table_t token_table(_self, _self.value); auto index_symbol = token_table.get_index<"by.symbol"_n>(); check(index_symbol.find(v) == index_symbol.end(), "token already registered"); @@ -173,8 +172,8 @@ void erc20::upgrade() { v.token_contract = token_contract; v.address.resize(kAddressLength, 0); memcpy(&(v.address[0]), proxy_contract_addr.bytes, kAddressLength); - v.deposit_fee = deposit_fee; - v.balance = deposit_fee; + v.ingress_fee = ingress_fee; + v.balance = ingress_fee; v.balance.amount = 0; v.fee_balance = v.balance; v.erc20_precision = erc20_precision; @@ -220,7 +219,7 @@ void erc20::onbridgemsg(const bridge_message_t &message) { }; intx::uint256 value = read_uint256(msg, 4 + 32); - intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->deposit_fee.symbol.precision())); + intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->ingress_fee.symbol.precision())); check(value % mult == 0_u256, "bridge amount can not have dust"); value /= mult; @@ -245,7 +244,7 @@ void erc20::onbridgemsg(const bridge_message_t &message) { } eosio::token::transfer_action transfer_act(itr->token_contract, {{get_self(), "active"_n}}); - transfer_act.send(get_self(), dest_eos_acct, eosio::asset(dest_amount, itr->deposit_fee.symbol), memo); + transfer_act.send(get_self(), dest_eos_acct, eosio::asset(dest_amount, itr->ingress_fee.symbol), memo); token_table.modify(*itr, _self, [&](auto &v) { v.balance.amount -= dest_amount; @@ -268,18 +267,18 @@ void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, auto index = token_table.get_index<"by.symbol"_n>(); auto itr = index.find(v); - eosio::check(itr != index.end() && itr->deposit_fee.symbol == quantity.symbol, "received unregistered token"); - eosio::check(quantity.amount > itr->deposit_fee.amount, "deposit amount must be greater than ingress fee"); + eosio::check(itr != index.end() && itr->ingress_fee.symbol == quantity.symbol, "received unregistered token"); + eosio::check(quantity.amount > itr->ingress_fee.amount, "deposit amount must be greater than ingress fee"); - uint64_t deposit_fee = itr->deposit_fee.amount; - quantity.amount -= deposit_fee; + uint64_t ingress_fee = itr->ingress_fee.amount; + quantity.amount -= ingress_fee; eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') { handle_erc20_transfer(*itr, quantity, memo); token_table.modify(*itr, _self, [&](auto &v) { v.balance.amount += quantity.amount; - v.fee_balance.amount += deposit_fee; + v.fee_balance.amount += ingress_fee; }); } else eosio::check(false, "memo must be 0x EVM address"); From fa26a92550de680091093cf440bfb54440b238f7 Mon Sep 17 00:00:00 2001 From: kayan Date: Fri, 1 Sep 2023 17:39:30 +0800 Subject: [PATCH 07/11] remote comment --- solidity_contracts/erc20/contract.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index 36c40ab..0684d72 100644 --- a/solidity_contracts/erc20/contract.sol +++ b/solidity_contracts/erc20/contract.sol @@ -1537,7 +1537,6 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { if (_isReservedAddress(to)) { require(msg.value == egressFee, "incorrect egress bridge fee"); // Call bridgeMessage of EVM Runtime - // using abi.encodePacked: only the last field should be of variable length // sha("bridgeTransferV0(address,uint256,string)") = 0x653332e5 bytes memory receiver_msg = abi.encodeWithSignature("bridgeTransferV0(address,uint256,string)", to, amount, memo); (bool success, ) = evmAddress.call{value: msg.value}(abi.encodeWithSignature("bridgeMsgV0(string,bool,bytes)", linkedEOSAccountName, true, receiver_msg )); From ed620fc1ccab6d061725218df8f9760193dfc766 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 4 Sep 2023 11:30:56 +0800 Subject: [PATCH 08/11] fix symbol, nonce, addr check --- .../contracts/erc20/include/erc20/erc20.hpp | 2 +- .../contracts/erc20/src/erc20.cpp | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 1b616c3..6e2fdb5 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -78,7 +78,7 @@ class [[eosio::contract]] erc20 : public contract { eosio::asset ingress_fee; eosio::asset balance; // <-- total amount in EVM side eosio::asset fee_balance; - int erc20_precision = 0; + int8_t erc20_precision = 0; uint64_t primary_key() const { return id; diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 5933a3a..434374c 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -42,7 +42,7 @@ uint64_t erc20::get_next_nonce() { auto itr = table.find(erc2o_account.value); uint64_t next_nonce = (itr == table.end() ? 0 : itr->next_nonce); - assertnonce_action act(evm_account, {{erc2o_account, "active"_n}}); + assertnonce_action act(evm_account, std::vector{}); act.send(erc2o_account, next_nonce); return next_nonce; } @@ -206,11 +206,6 @@ void erc20::onbridgemsg(const bridge_message_t &message) { check(msg.data.size() >= 4 + 32 /*to*/ + 32 /*amount*/ + 32 /*memo offset*/ + 32 /*memo len*/, "not enough data in bridge_message_v0 of application type 0x653332e5"); - evmc::address dest_addr; - memcpy(dest_addr.bytes, (void *)&(msg.data[4 + 32 - kAddressLength]), kAddressLength); - std::optional dest_acc = silkworm::extract_reserved_address(dest_addr); - check(!!dest_acc, "destination address in bridge_message_v0 must be reserved address"); - auto read_uint256 = [&](const auto &msg, size_t offset) -> intx::uint256 { uint8_t buffer[32]={}; check(msg.data.size() >= offset + 32, "not enough data in bridge_message_v0 of application type 0x653332e5"); @@ -218,6 +213,12 @@ void erc20::onbridgemsg(const bridge_message_t &message) { return intx::be::load(buffer); }; + check(read_uint256(msg, 4) <= 0xffffFFFFffffFFFFffffFFFFffffFFFFffffFFFF_u256, "invalid destination address"); + evmc::address dest_addr; + memcpy(dest_addr.bytes, (void *)&(msg.data[4 + 32 - kAddressLength]), kAddressLength); + std::optional dest_acc = silkworm::extract_reserved_address(dest_addr); + check(!!dest_acc, "destination address in bridge_message_v0 must be reserved address"); + intx::uint256 value = read_uint256(msg, 4 + 32); intx::uint256 mult = intx::exp(10_u256, intx::uint256(itr->erc20_precision - itr->ingress_fee.symbol.precision())); check(value % mult == 0_u256, "bridge amount can not have dust"); @@ -271,14 +272,14 @@ void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, eosio::check(quantity.amount > itr->ingress_fee.amount, "deposit amount must be greater than ingress fee"); uint64_t ingress_fee = itr->ingress_fee.amount; - quantity.amount -= ingress_fee; + quantity -= itr->ingress_fee; eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') { handle_erc20_transfer(*itr, quantity, memo); token_table.modify(*itr, _self, [&](auto &v) { - v.balance.amount += quantity.amount; - v.fee_balance.amount += ingress_fee; + v.balance += quantity; + v.fee_balance += v.ingress_fee; }); } else eosio::check(false, "memo must be 0x EVM address"); From 736b4605713e70b2dd06fe7617531840bcc1d012 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 4 Sep 2023 13:42:12 +0800 Subject: [PATCH 09/11] fix precision type --- antelope_contracts/contracts/erc20/include/erc20/erc20.hpp | 2 +- antelope_contracts/contracts/erc20/src/erc20.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 6e2fdb5..9e08cc5 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -78,7 +78,7 @@ class [[eosio::contract]] erc20 : public contract { eosio::asset ingress_fee; eosio::asset balance; // <-- total amount in EVM side eosio::asset fee_balance; - int8_t erc20_precision = 0; + uint8_t erc20_precision = 0; uint64_t primary_key() const { return id; diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index 434374c..c1fb83f 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -271,7 +271,6 @@ void erc20::transfer(eosio::name from, eosio::name to, eosio::asset quantity, eosio::check(itr != index.end() && itr->ingress_fee.symbol == quantity.symbol, "received unregistered token"); eosio::check(quantity.amount > itr->ingress_fee.amount, "deposit amount must be greater than ingress fee"); - uint64_t ingress_fee = itr->ingress_fee.amount; quantity -= itr->ingress_fee; eosio::check(quantity.amount > 0 && quantity.amount < (1ll<<62)-1, "deposit amount overflow"); From f5b8838d699f6fcb07b95f6a18868df9b8cecf08 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 4 Sep 2023 19:40:37 +0800 Subject: [PATCH 10/11] fix testcase --- .../contracts/eosio.boot/eosio.boot.abi | 328 ++++++++++++++++++ .../contracts/eosio.boot/eosio.boot.wasm | Bin 0 -> 4919 bytes .../contracts/stubs/stub_evm_runtime.cpp | 21 +- .../tests/erc20/contracts.hpp.in | 4 + .../tests/erc20/erc20_tester.cpp | 28 +- .../tests/erc20/erc20_tester.hpp | 19 +- .../tests/erc20/transfer_tests.cpp | 52 ++- 7 files changed, 438 insertions(+), 14 deletions(-) create mode 100644 antelope_contracts/contracts/eosio.boot/eosio.boot.abi create mode 100755 antelope_contracts/contracts/eosio.boot/eosio.boot.wasm diff --git a/antelope_contracts/contracts/eosio.boot/eosio.boot.abi b/antelope_contracts/contracts/eosio.boot/eosio.boot.abi new file mode 100644 index 0000000..1971165 --- /dev/null +++ b/antelope_contracts/contracts/eosio.boot/eosio.boot.abi @@ -0,0 +1,328 @@ +{ + "____comment": "This file was generated with eosio-abigen. DO NOT EDIT ", + "version": "eosio::abi/1.2", + "types": [], + "structs": [ + { + "name": "activate", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "authority", + "base": "", + "fields": [ + { + "name": "threshold", + "type": "uint32" + }, + { + "name": "keys", + "type": "key_weight[]" + }, + { + "name": "accounts", + "type": "permission_level_weight[]" + }, + { + "name": "waits", + "type": "wait_weight[]" + } + ] + }, + { + "name": "canceldelay", + "base": "", + "fields": [ + { + "name": "canceling_auth", + "type": "permission_level" + }, + { + "name": "trx_id", + "type": "checksum256" + } + ] + }, + { + "name": "deleteauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "key_weight", + "base": "", + "fields": [ + { + "name": "key", + "type": "public_key" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "linkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + }, + { + "name": "requirement", + "type": "name" + } + ] + }, + { + "name": "newaccount", + "base": "", + "fields": [ + { + "name": "creator", + "type": "name" + }, + { + "name": "name", + "type": "name" + }, + { + "name": "owner", + "type": "authority" + }, + { + "name": "active", + "type": "authority" + } + ] + }, + { + "name": "onerror", + "base": "", + "fields": [ + { + "name": "sender_id", + "type": "uint128" + }, + { + "name": "sent_trx", + "type": "bytes" + } + ] + }, + { + "name": "permission_level", + "base": "", + "fields": [ + { + "name": "actor", + "type": "name" + }, + { + "name": "permission", + "type": "name" + } + ] + }, + { + "name": "permission_level_weight", + "base": "", + "fields": [ + { + "name": "permission", + "type": "permission_level" + }, + { + "name": "weight", + "type": "uint16" + } + ] + }, + { + "name": "reqactivated", + "base": "", + "fields": [ + { + "name": "feature_digest", + "type": "checksum256" + } + ] + }, + { + "name": "setabi", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "abi", + "type": "bytes" + } + ] + }, + { + "name": "setcode", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "vmtype", + "type": "uint8" + }, + { + "name": "vmversion", + "type": "uint8" + }, + { + "name": "code", + "type": "bytes" + } + ] + }, + { + "name": "unlinkauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "code", + "type": "name" + }, + { + "name": "type", + "type": "name" + } + ] + }, + { + "name": "updateauth", + "base": "", + "fields": [ + { + "name": "account", + "type": "name" + }, + { + "name": "permission", + "type": "name" + }, + { + "name": "parent", + "type": "name" + }, + { + "name": "auth", + "type": "authority" + } + ] + }, + { + "name": "wait_weight", + "base": "", + "fields": [ + { + "name": "wait_sec", + "type": "uint32" + }, + { + "name": "weight", + "type": "uint16" + } + ] + } + ], + "actions": [ + { + "name": "activate", + "type": "activate", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Activate Protocol Feature\nsummary: 'Activate protocol feature {{nowrap feature_digest}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\n{{$action.account}} activates the protocol feature with a digest of {{feature_digest}}." + }, + { + "name": "canceldelay", + "type": "canceldelay", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel Delayed Transaction\nsummary: '{{nowrap canceling_auth.actor}} cancels a delayed transaction'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{canceling_auth.actor}} cancels the delayed transaction with id {{trx_id}}." + }, + { + "name": "deleteauth", + "type": "deleteauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Delete Account Permission\nsummary: 'Delete the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDelete the {{permission}} permission of {{account}}." + }, + { + "name": "linkauth", + "type": "linkauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Link Action to Permission\nsummary: '{{nowrap account}} sets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract to {{nowrap requirement}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} sets the minimum required permission for the {{#if type}}{{type}} action of the{{/if}} {{code}} contract to {{requirement}}.\n\n{{#if type}}{{else}}Any links explicitly associated to specific actions of {{code}} will take precedence.{{/if}}" + }, + { + "name": "newaccount", + "type": "newaccount", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create New Account\nsummary: '{{nowrap creator}} creates a new account with the name {{nowrap name}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{creator}} creates a new account with the name {{name}} and the following permissions:\n\nowner permission with authority:\n{{to_json owner}}\n\nactive permission with authority:\n{{to_json active}}" + }, + { + "name": "onerror", + "type": "onerror", + "ricardian_contract": "" + }, + { + "name": "reqactivated", + "type": "reqactivated", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Assert Protocol Feature Activation\nsummary: 'Assert that protocol feature {{nowrap feature_digest}} has been activated'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/admin.png#9bf1cec664863bd6aaac0f814b235f8799fb02c850e9aa5da34e8a004bd6518e\n---\n\nAssert that the protocol feature with a digest of {{feature_digest}} has been activated." + }, + { + "name": "setabi", + "type": "setabi", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract ABI\nsummary: 'Deploy contract ABI on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy the ABI file associated with the contract on account {{account}}." + }, + { + "name": "setcode", + "type": "setcode", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Deploy Contract Code\nsummary: 'Deploy contract code on account {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nDeploy compiled contract code to the account {{account}}." + }, + { + "name": "unlinkauth", + "type": "unlinkauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Unlink Action from Permission\nsummary: '{{nowrap account}} unsets the minimum required permission for the {{#if type}}{{nowrap type}} action of the{{/if}} {{nowrap code}} contract'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\n{{account}} removes the association between the {{#if type}}{{type}} action of the{{/if}} {{code}} contract and its minimum required permission.\n\n{{#if type}}{{else}}This will not remove any links explicitly associated to specific actions of {{code}}.{{/if}}" + }, + { + "name": "updateauth", + "type": "updateauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Modify Account Permission\nsummary: 'Add or update the {{nowrap permission}} permission of {{nowrap account}}'\nicon: http://127.0.0.1/ricardian_assets/eosio.contracts/icons/account.png#3d55a2fc3a5c20b456f5657faf666bc25ffd06f4836c5e8256f741149b0b294f\n---\n\nModify, and create if necessary, the {{permission}} permission of {{account}} to have a parent permission of {{parent}} and the following authority:\n{{to_json auth}}" + } + ], + "tables": [], + "ricardian_clauses": [], + "variants": [], + "action_results": [] +} \ No newline at end of file diff --git a/antelope_contracts/contracts/eosio.boot/eosio.boot.wasm b/antelope_contracts/contracts/eosio.boot/eosio.boot.wasm new file mode 100755 index 0000000000000000000000000000000000000000..44cdf944c2a295c3697b4e2e94e22e2be6e2df83 GIT binary patch literal 4919 zcmeHLU5r~t6+ZKGZSUS)_U5OXO#)=@%>o92v`}d`iYnE2MN6v|@gtCUa~%7o@%66l zweLm=4{JlLDuN*O1u20C1VWSu6{MDj5?(eE55Q9)p%4jBANmlGss`~uL8!}jX6&_F zf>IPJA%R!xIdkUBIcLt_91m5x>w<{HBkwptbVBxebV6+5pU0l)ogn-Ji*MDlgYIo@ zVUclLSlt2ydbPEMZ;3DYXBnuZd1fYQb?dEirQ1z983~K(BpaLcPExLHW~WIqYG1pP zRH|A1OeIUor;7Sthzr^?Xo^jcQGp9(xT-6SiYTx(Su)zg)Fv)rz9D(gv>bh-em zjilyIVV<}fnH4+F+}fR1yVXr<4AeHOU`10bYNoo`=_JjpoYmJ8nr75~W@`_cOi)`^ zg?6Xj%(~?@r@)>`%a*IHT0*`$bdYAWf_;=!Ld?2P&H~yKZ6SoG1c}_d>WR3&O@5E! zm#^&(`6=O5+U>P-v}gJU=AfovzyAkGhJuA4Fl5B1bZNEU|Lct_gUi4DTF*$#86CXS z1Nxa4zw*_uU-|49`>ql8d%yTOjnVIBbpO_~uWVoc@&u0~L+aJgVgu}J-?{P2 z?|$?8wFw@f<h_&wbx36CM6@6NlVmrTi{kd1Z_$mc+{={dfg{2(XtlyuI zkG)^$_b2zp6dd$BLiHpJI!iIl`7yqW@*?JOa3&IIB$qLB*g+{$hSGR0joTX$Ez3a( z8)Y11h92YXfJMsmMgRzfk7ZG!xc4z$ji1`4fPABTN&d1By`zxPh_JFOXRsM^XNr=> z?E8>wteBV{#CWmf-!EhjstH4fLm^8F{DZv_&5D>pFe=L5lp^`itT1v9(G2;%AwLHu z!9L%kAW!}@s0^*BmrAU~HL z8*gxzv&qk-i-{$yjbp+^1c$=E83SNya2qYU!SG;o8hOjI=)z;c`Wumf8t($bsG=-} z9uJG51Ors{UGLi<;rne7`w&1ALGsW04)8u7{vqeX zkO;~1AtD@~^8qU7mZb)BFW#XIME8!^h-CK6fL#;k(@>in($kCkVq=zEb_xiKJc9xG z(TMVVo9RzLMPanbH-becDaRBVf|$^yao*^UTe^{tG&cjRr^z+g+KtEqqg_YYW92yA zbH-ycyF(Zc{e;FPen|=sZHlcK%q6mi){$x$fs>;VfIW^Ac3GA9xQcg*xR*v^mS!Y+ z0dy~X5)Ijg?Gg7LN9VAEPo9%svMI4B z6SwKkMd^+g6C*h^Mi-NZj6@7;TueSPF^AYIrhLPpzq2=7km72wh+$=NLjY&%_{cwebP}|?g6@k_%?Al{i#xV z)K>o2M8X?F!1~@B`aVDD`+_S}&i8re`@)3p@k`?FzV}R?eec0{cJIjdXl~J*4Sk<~ zOTK4$Z}vUndFXpcU_Ux}_wIXcAA>YFyz5E-Rgts5Ed7U1RqAU=&15Z8S;I>TUUf{n z@~vyU&7{+5b&PwyG1W@5*~-kx1kf7qs^M*@n&AzCVl=jXgh!LCRc)=A;lq%r zcMb0tKL^}$NW07MS@AKYvv nXWI~q9ZtJ?)10cG#hcB^b6L_g)m9U%H>+8zgDu#5%MkqoR1>x0 literal 0 HcmV?d00001 diff --git a/antelope_contracts/contracts/stubs/stub_evm_runtime.cpp b/antelope_contracts/contracts/stubs/stub_evm_runtime.cpp index 783f7b4..e1be8ce 100644 --- a/antelope_contracts/contracts/stubs/stub_evm_runtime.cpp +++ b/antelope_contracts/contracts/stubs/stub_evm_runtime.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -7,24 +8,36 @@ namespace stub { typedef std::vector bytes; +struct bridge_message_v0 { + eosio::name receiver; + bytes sender; + eosio::time_point timestamp; + bytes value; + bytes data; + + EOSLIB_SERIALIZE(bridge_message_v0, (receiver)(sender)(timestamp)(value)(data)); +}; + +using bridge_message_t = std::variant; + class [[eosio::contract]] stub_evm_runtime : public contract { using contract::contract; public: [[eosio::action]] void call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit); - [[eosio::action]] void sendbridgemsg(eosio::name receiver, const bytes& sender, const eosio::time_point& timestamp, const bytes& value, const bytes& data); + [[eosio::action]] void sendbridgemsg(const bridge_message_t &message); private: - void onbridgemsg(name receiver, const bytes& sender, const time_point& timestamp, const bytes& value, const bytes& data); + void onbridgemsg(const bridge_message_t &message); using onbridgemsg_action = action_wrapper<"onbridgemsg"_n, &stub_evm_runtime::onbridgemsg>; }; void stub_evm_runtime::call(eosio::name from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit) { } -void stub_evm_runtime::sendbridgemsg(eosio::name receiver, const bytes& sender, const eosio::time_point& timestamp, const bytes& value, const bytes& data) { +void stub_evm_runtime::sendbridgemsg(const bridge_message_t &message) { onbridgemsg_action onbridgemsg_act(eosio::name("eosio.erc2o"), {{get_self(), "active"_n}}); - onbridgemsg_act.send(receiver, sender, timestamp, value, data); + onbridgemsg_act.send(message); } } // namespace stub \ No newline at end of file diff --git a/antelope_contracts/tests/erc20/contracts.hpp.in b/antelope_contracts/tests/erc20/contracts.hpp.in index 7016b20..769ac6e 100644 --- a/antelope_contracts/tests/erc20/contracts.hpp.in +++ b/antelope_contracts/tests/erc20/contracts.hpp.in @@ -20,6 +20,10 @@ namespace eosio { namespace testing { struct contracts { + + static std::vector eosio_boot_wasm() { return read_wasm("${ANTELOPE_CONTRACTS_SOURCE_DIR}/eosio.boot/eosio.boot.wasm"); } + static std::vector eosio_boot_abi() { return read_abi("${ANTELOPE_CONTRACTS_SOURCE_DIR}/eosio.boot/eosio.boot.abi"); } + static std::vector eosio_token_wasm() { return read_wasm("${ANTELOPE_CONTRACTS_SOURCE_DIR}/eosio.token/eosio.token.wasm"); } static std::vector eosio_token_abi() { return read_abi("${ANTELOPE_CONTRACTS_SOURCE_DIR}/eosio.token/eosio.token.abi"); } diff --git a/antelope_contracts/tests/erc20/erc20_tester.cpp b/antelope_contracts/tests/erc20/erc20_tester.cpp index 19fccee..d0a3f3b 100644 --- a/antelope_contracts/tests/erc20/erc20_tester.cpp +++ b/antelope_contracts/tests/erc20/erc20_tester.cpp @@ -18,6 +18,7 @@ using mvo = fc::mutable_variant_object; using intx::operator""_u256; namespace erc20_test { +const eosio::chain::name eos_system_account("eosio"); const eosio::chain::name eos_token_account("eosio.token"); const eosio::chain::symbol eos_token_symbol(4u, "EOS"); const eosio::chain::name token_account("tethertether"); @@ -27,9 +28,29 @@ const eosio::chain::name faucet_account_name("eosio.faucet"); const eosio::chain::name erc20_account("eosio.erc2o"); erc20_tester::erc20_tester(std::string native_symbol_str) : native_symbol(symbol::from_string(native_symbol_str)) { - create_accounts({eos_token_account, evm_account, token_account, faucet_account_name, erc20_account}); + + auto def_conf = default_config(tempdir); + def_conf.first.max_nonprivileged_inline_action_size = 256 * 1024; + cfg = def_conf.first; + init(def_conf.first, def_conf.second); + + const auto& pfm = control->get_protocol_feature_manager(); + + auto preactivate_feature_digest = pfm.get_builtin_digest(builtin_protocol_feature_t::preactivate_feature); + FC_ASSERT( preactivate_feature_digest, "PREACTIVATE_FEATURE not found" ); + schedule_protocol_features_wo_preactivation( { *preactivate_feature_digest } ); + produce_block(); + set_code( "eosio"_n, testing::contracts::eosio_boot_wasm() ); + set_abi( "eosio"_n, testing::contracts::eosio_boot_abi().data() ); + + preactivate_all_builtin_protocol_features(); + + produce_block(); + + create_accounts({eos_token_account, evm_account, token_account, faucet_account_name, erc20_account}); + set_code(eos_token_account, testing::contracts::eosio_token_wasm()); set_abi(eos_token_account, testing::contracts::eosio_token_abi().data()); @@ -63,12 +84,11 @@ erc20_tester::erc20_tester(std::string native_symbol_str) : native_symbol(symbol produce_block(); // ./cleos push action eosio.erc2o init '[0]' -p eosio.erc2o - push_action(erc20_account, "init"_n, erc20_account, mvo()("nonce", 0)); + push_action(erc20_account, "upgrade"_n, erc20_account, mvo()); produce_block(); - // ./cleos push action eosio.erc2o regtoken '[1,"usdt","EVM USDT Token","WUSDT","0.1000 USDT","0.0100 USDT","4ea3b729669bf6c34f7b80e5d6c17db71f89f21f",6]' -p eosio.erc2o - push_action(erc20_account, "regtoken"_n, erc20_account, mvo()("nonce", 1)("eos_contract_name",token_account.to_string())("evm_token_name","EVM USDT V1")("evm_token_symbol","WUSDT")("min_deposit","0.1000 USDT")("deposit_fee","0.0100 USDT")("erc20_impl_address","4ea3b729669bf6c34f7b80e5d6c17db71f89f21f")("erc20_precision",6)); + push_action(erc20_account, "regtoken"_n, erc20_account, mvo()("eos_contract_name",token_account.to_string())("evm_token_name","EVM USDT V1")("evm_token_symbol","WUSDT")("ingress_fee","0.0100 USDT")("egress_fee","0.0100 EOS")("erc20_precision",6)); produce_block(); diff --git a/antelope_contracts/tests/erc20/erc20_tester.hpp b/antelope_contracts/tests/erc20/erc20_tester.hpp index 04ef206..864f375 100644 --- a/antelope_contracts/tests/erc20/erc20_tester.hpp +++ b/antelope_contracts/tests/erc20/erc20_tester.hpp @@ -22,7 +22,9 @@ extern const eosio::chain::name erc20_account; using namespace eosio; using namespace eosio::chain; -class erc20_tester : public eosio::testing::validating_tester { +class erc20_tester : public eosio::testing::base_tester { + +private: public: const eosio::chain::symbol native_symbol; explicit erc20_tester(std::string native_symbol_str = "4,EOS"); @@ -38,6 +40,21 @@ class erc20_tester : public eosio::testing::validating_tester { std::vector data = get_row_by_account(token_addr, act, "accounts"_n, name(target_symbol.to_symbol_code().value)); return data.empty() ? eosio::chain::asset(0, target_symbol) : token_abi_ser.binary_to_variant("account", data, eosio::chain::abi_serializer::create_yield_function(abi_serializer_max_time))["balance"].as(); } + + using base_tester::produce_block; + + signed_block_ptr produce_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { + return _produce_block(skip_time, false); + } + + signed_block_ptr produce_empty_block( fc::microseconds skip_time = fc::milliseconds(config::block_interval_ms) )override { + unapplied_transactions.add_aborted( control->abort_block() ); + return _produce_block(skip_time, true); + } + + signed_block_ptr finish_block()override { + return _finish_block(); + } }; // Hex helper functions: diff --git a/antelope_contracts/tests/erc20/transfer_tests.cpp b/antelope_contracts/tests/erc20/transfer_tests.cpp index 985a059..409d452 100644 --- a/antelope_contracts/tests/erc20/transfer_tests.cpp +++ b/antelope_contracts/tests/erc20/transfer_tests.cpp @@ -44,13 +44,55 @@ struct transfer_tester : erc20_tester { calldata.insert(calldata.end(), value_buffer, value_buffer + 32); // Cannot directly send message to erc20 + + fc::variants arr; + arr.push_back(fc::variant("bridge_message_v0")); + arr.push_back(mvo() + ("receiver", erc20_account.to_string()) + ("sender", "4ea3b729669bf6c34f7b80e5d6c17db71f89f21f") // contract addr at nonce 0 + ("timestamp","2020-01-01T00:00:00.000000") + ("value", "0000000000000000000000000000000000000000000000000000000000000000") + ("data", + "653332e5" //sha("bridgeTransferV0(address,uint256,string)") + "000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbb3d0e000000000000" // bob- dest account + "0000000000000000000000000000000000000000000000000000000000001388" // hex(5000)-ERC-20 val + "0000000000000000000000000000000000000000000000000000000000000060" // hex(96)-memo offset + "0000000000000000000000000000000000000000000000000000000000000004" // memo len + "aabbccdd00000000000000000000000000000000000000000000000000000000" // memo data aligned to 32 bytes + )); + + fc::variants arr_hacker_solidity_contract; + arr_hacker_solidity_contract.push_back(fc::variant("bridge_message_v0")); + arr_hacker_solidity_contract.push_back(mvo() + ("receiver", erc20_account.to_string()) + ("sender", "aaaabbbbccccdddd111122223333444455556666") // hacker's solidity contract + ("timestamp","2020-01-01T00:00:00.000000") + ("value", "0000000000000000000000000000000000000000000000000000000000000000") + ("data", + "653332e5" //sha("bridgeTransferV0(address,uint256,string)") + "000000000000000000000000bbbbbbbbbbbbbbbbbbbbbbbb3d0e000000000000" // bob- dest account + "0000000000000000000000000000000000000000000000000000000000001388" // hex(5000)-ERC-20 val + "0000000000000000000000000000000000000000000000000000000000000060" // hex(96)-memo offset + "0000000000000000000000000000000000000000000000000000000000000004" // memo len + "aabbccdd00000000000000000000000000000000000000000000000000000000" // memo data aligned to 32 bytes + )); + + // hacker can't trigger the bridge from other solidity contract + BOOST_REQUIRE_EXCEPTION( + push_action( + evm_account, "sendbridgemsg"_n, evm_account, mvo()("message", arr_hacker_solidity_contract)), + eosio_assert_message_exception, + eosio_assert_message_is("ERC-20 token not registerred")); + + // only evm_runtime can call onbridgemsg BOOST_REQUIRE_EXCEPTION(push_action( - erc20_account, "onbridgemsg"_n, "alice"_n, mvo()("receiver", erc20_account)("sender", bytes())("timestamp", eosio::chain::time_point())("value", bytes())("data", calldata)), - missing_auth_exception, eosio::testing::fc_exception_message_starts_with("missing authority")); + erc20_account, "onbridgemsg"_n, "alice"_n, mvo()("message", arr)), + eosio_assert_message_exception, + eosio_assert_message_is("invalid sender of onbridgemsg")); // Go through stub push_action( - evm_account, "sendbridgemsg"_n, "alice"_n, mvo()("receiver", erc20_account)("sender", bytes())("timestamp", eosio::chain::time_point())("value", bytes())("data", calldata)); + evm_account, "sendbridgemsg"_n, evm_account, mvo()("message", arr)); } }; @@ -79,7 +121,7 @@ try { BOOST_REQUIRE(trace->action_traces.back().act.name.to_string() == "call"); BOOST_REQUIRE_EXCEPTION(transfer_token(eos_token_account, "alice"_n, erc20_account, make_asset(10000), "0x0000000000000000000000000000000000000000"), - eosio_assert_message_exception, eosio_assert_message_is("received unexpected token")); + eosio_assert_message_exception, eosio_assert_message_is("received unregistered token")); produce_block(); } @@ -103,7 +145,7 @@ try { const char bob[] = "0xbbbbbbbbbbbbbbbbbbbbbbbb3d0e000000000000"; // EVM has precision of 6 - // 5000 /1000000 = 0.005 EOS = 50 + // 5000 /1000000 = 0.005 USDT = 50 gen_bridgemessage(bob, 5000); BOOST_REQUIRE(10000 - 50 == get_balance(erc20_account, token_account, symbol::from_string("4,USDT")).get_amount()); From 6f6695a779fae6569ab9d5ef9f1cadbc26a5db92 Mon Sep 17 00:00:00 2001 From: kayan Date: Mon, 4 Sep 2023 20:15:30 +0800 Subject: [PATCH 11/11] fix CI build error --- antelope_contracts/contracts/erc20/src/erc20.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/antelope_contracts/contracts/erc20/src/erc20.cpp b/antelope_contracts/contracts/erc20/src/erc20.cpp index c1fb83f..caeaeb0 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -240,7 +240,7 @@ void erc20::onbridgemsg(const bridge_message_t &message) { } eosio::name dest_eos_acct(*dest_acc); - if (get_code_hash(dest_eos_acct) != checksum256()) { + if (::erc20::get_code_hash(dest_eos_acct) != checksum256()) { egresslist_table_t(get_self(), get_self().value).get(dest_eos_acct.value, "native accounts containing contract code must be on allow list for egress bridging"); }