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 0000000..44cdf94 Binary files /dev/null and b/antelope_contracts/contracts/eosio.boot/eosio.boot.wasm differ diff --git a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp index 5c0544b..9e08cc5 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/erc20.hpp @@ -11,30 +11,112 @@ using namespace intx; namespace erc20 { +checksum256 make_key(const uint8_t* ptr, size_t len) { + uint8_t buffer[32]={}; + check(len <= sizeof(buffer), "len provided to make_key is too small"); + 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 upgrade(); + + [[eosio::action]] void regtoken(eosio::name eos_contract_name, + 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); + + uint64_t get_next_nonce(); + + struct nextnonce { + name owner; + uint64_t next_nonce = 0; - struct [[eosio::table]] config - { - bytes erc20_addr; + uint64_t primary_key() const { return owner.value; } + EOSLIB_SERIALIZE(nextnonce, (owner)(next_nonce)); + }; + + struct [[eosio::table("implcontract")]] impl_contract_t { + uint64_t id = 0; + bytes address; + + uint64_t primary_key() const { + return id; + } + EOSLIB_SERIALIZE(impl_contract_t, (id)(address)); + }; + 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 token_contract; + bytes address; // <-- proxy contract addr + eosio::asset ingress_fee; + eosio::asset balance; // <-- total amount in EVM side + eosio::asset fee_balance; + uint8_t erc20_precision = 0; - EOSLIB_SERIALIZE(config, (erc20_addr)); + uint64_t primary_key() const { + return id; + } + uint128_t by_contract_symbol() const { + uint128_t v = token_contract.value; + v <<= 64; + v |= ingress_fee.symbol.code().raw(); + return v; + } + checksum256 by_address()const { + return make_key(address); + } + + 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 >, + indexed_by<"by.address"_n, const_mem_fun > + > token_table_t; + + struct [[eosio::table("egresslist")]] allowed_egress_account { + eosio::name account; - private: + 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; - 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); + // 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 4a1b3a9..0659b5c 100644 --- a/antelope_contracts/contracts/erc20/include/erc20/types.hpp +++ b/antelope_contracts/contracts/erc20/include/erc20/types.hpp @@ -12,13 +12,15 @@ typedef std::vector bytes; 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 eosio::symbol token_symbol("USDT", 4u); +constexpr uint64_t evm_init_gaslimit = 10000000; + 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())); -static_assert(evm_precision - token_symbol.precision() <= 14, "dust math may overflow a uint64_t"); +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 49633ba..caeaeb0 100644 --- a/antelope_contracts/contracts/erc20/src/erc20.cpp +++ b/antelope_contracts/contracts/erc20/src/erc20.cpp @@ -8,84 +8,283 @@ #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; +} + template void initialize_data(bytes& output, const unsigned char (&arr)[Size]) { + static_assert(Size > 128); // ensure bytecode is compiled output.resize(Size); std::memcpy(output.data(), arr, Size); } -void erc20::init() { +// 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, std::vector{}); + act.send(erc2o_account, next_nonce); + return 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(get_self().value); + auto reserved_addr = silkworm::make_reserved_address(erc2o_account.value); initialize_data(call_data, solidity::erc20::bytecode); + bytes to = {}; - // Assume 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); + 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, {{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, next_nonce); + + contract_table.emplace(_self, [&](auto &v) { + v.id = id; + v.address.resize(kAddressLength); + memcpy(&(v.address[0]), impl_addr.bytes, kAddressLength); + }); +} - // Assume nonce... - auto deploy_addr = silkworm::create_address(reserved_addr, 0); +[[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"); + + // 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 >= 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; + egress_fee_evm *= minimum_natively_representable; + + uint128_t v = token_contract.value; + v <<= 64; + 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"); + + impl_contract_table_t contract_table(_self, _self.value); + eosio::check(contract_table.begin() != contract_table.end(), "no implementaion contract available"); + auto contract_itr = contract_table.end(); + --contract_itr; + + 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, memory _data) call_data.insert(call_data.end(), 32 - kAddressLength, 0); // padding for address - call_data.insert(call_data.end(), deploy_addr.bytes, deploy_addr.bytes + kAddressLength); - // Assume account opened in evm_runtime - call_act.send(get_self(), to, 0, call_data, evm_init_gaslimit); + call_data.insert(call_data.end(), contract_itr->address.begin(), contract_itr->address.end()); - // Assume nonce... - deploy_addr = silkworm::create_address(reserved_addr, 1); + bytes constructor_data; + // 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_)); - // TODO: Where can we get the addr... - config new_config = { - .erc20_addr = {}, + 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); + 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_)); + } }; - new_config.erc20_addr.resize(kAddressLength); - memcpy(new_config.erc20_addr.data(), deploy_addr.bytes, kAddressLength); + pack_uint32(constructor_data, (uint8_t)erc20_precision); // offset 0 + 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, token_contract.to_string()); // offset 288 + + pack_uint32(call_data, 64); // offset 32 + pack_string(call_data, constructor_data); // offset 64 + + bytes to = {}; + bytes value_zero; + value_zero.resize(32, 0); + + uint64_t next_nonce = get_next_nonce(); - _config.set(new_config, get_self()); + // 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 proxy_contract_addr = silkworm::create_address(reserved_addr, next_nonce); + + token_table.emplace(_self, [&](auto &v) { + 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.ingress_fee = ingress_fee; + v.balance = ingress_fee; + v.balance.amount = 0; + v.fee_balance = v.balance; + 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"); + + const bridge_message_v0 &msg = std::get(message); + check(msg.receiver == erc2o_account, "invalid message receiver"); + + checksum256 addr_key = make_key(msg.sender); + + token_table_t token_table(_self, _self.value); + auto index = token_table.get_index<"by.address"_n>(); + auto itr = index.find(addr_key); + + 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"); - // TODO: this API will change + uint32_t app_type = 0; + memcpy((void *)&app_type, (const void *)&(msg.data[0]), sizeof(app_type)); - 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); + 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"); - check(!!to , "failed to extract destination 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"); + memcpy(buffer, (void *)&(msg.data[offset]), 32); + return intx::be::load(buffer); + }; - eosio::check(amount % minimum_natively_representable == 0_u256, "transfer must not generate dust"); + 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"); - 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")); + 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"); + 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; + if (memo_len > 0) { + memo.assign((const char *)&(msg.data[4 + 32 + 32 + 32 + 32]), memo_len); + } + + eosio::name dest_eos_acct(*dest_acc); + 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"); + } + + 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->ingress_fee.symbol), memo); + + token_table.modify(*itr, _self, [&](auto &v) { + v.balance.amount -= dest_amount; + }); + } else { + eosio::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; - if (memo.size() == 42 && memo[0] == '0' && memo[1] == 'x') - handle_evm_transfer(quantity, memo); - else + 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->ingress_fee.symbol == quantity.symbol, "received unregistered token"); + eosio::check(quantity.amount > itr->ingress_fee.amount, "deposit amount must be greater than 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 += quantity; + v.fee_balance += v.ingress_fee; + }); + } 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); @@ -93,7 +292,7 @@ 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; + value *= intx::exp(10_u256, intx::uint256(token.erc20_precision - quantity.symbol.precision())); uint8_t value_buffer[32] = {}; intx::be::store(value_buffer, value); @@ -105,8 +304,34 @@ void erc20::handle_evm_transfer(eosio::asset quantity, const std::string& memo) 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_act.send(get_self(), *address_bytes, 0, call_data, evm_gaslimit); + 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(erc2o_account /*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/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 48120a2..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()); @@ -62,6 +83,15 @@ 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, "upgrade"_n, erc20_account, mvo()); + + produce_block(); + + 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(); + set_code(evm_account, testing::contracts::evm_stub_wasm()); set_abi(evm_account, testing::contracts::evm_stub_abi().data()); 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()); diff --git a/solidity_contracts/erc20/contract.sol b/solidity_contracts/erc20/contract.sol index e8af68a..0684d72 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,21 +1481,36 @@ 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, + uint256 _egressFee, + 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"; - egressFee = 0.1e18; + precision = _precision; + egressFee = _egressFee; + eos_token_contract = _eos_token_contract; + } - _mint(msg.sender, 1000000000000); + 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; } function _authorizeUpgrade(address) internal virtual override { @@ -1520,11 +1535,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"); - + require(msg.value == egressFee, "incorrect egress bridge fee"); // Call bridgeMessage of EVM Runtime - bytes memory n_bytes = abi.encodePacked(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); @@ -1538,6 +1553,6 @@ contract BridgeERC20 is Initializable, ERC20Upgradeable, UUPSUpgradeable { } function decimals() public view virtual override returns (uint8) { - return 6; + return precision; } } 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) {} }