Skip to content

Commit

Permalink
Merge pull request #634 from eosnetworkfoundation/yarkin/call_action_new
Browse files Browse the repository at this point in the history
Add call actions
  • Loading branch information
arhag authored Aug 14, 2023
2 parents a7ed7d9 + 37b8ee2 commit f1d9f9f
Show file tree
Hide file tree
Showing 7 changed files with 643 additions and 19 deletions.
6 changes: 6 additions & 0 deletions include/evm_runtime/evm_contract.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ class [[eosio::contract]] evm_contract : public contract
/// @return true if all garbage has been collected
[[eosio::action]] bool gc(uint32_t max);


[[eosio::action]] void call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit);
[[eosio::action]] void admincall(const bytes& from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit);

#ifdef WITH_TEST_ACTIONS
[[eosio::action]] void testtx(const std::optional<bytes>& orlptx, const evm_runtime::test::block_info& bi);
[[eosio::action]] void
Expand Down Expand Up @@ -140,6 +144,8 @@ class [[eosio::contract]] evm_contract : public contract
void handle_account_transfer(const eosio::asset& quantity, const std::string& memo);
void handle_evm_transfer(eosio::asset quantity, const std::string& memo);

void call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce);

// to allow sending through a Bytes (basic_string<uint8_t>) w/o copying over to a std::vector<char>
void pushtx_bytes(eosio::name miner, const std::basic_string<uint8_t>& rlptx);
using pushtx_action = eosio::action_wrapper<"pushtx"_n, &evm_contract::pushtx_bytes>;
Expand Down
115 changes: 97 additions & 18 deletions src/actions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ namespace silkworm {
namespace evm_runtime {

static constexpr uint32_t hundred_percent = 100'000;
static constexpr char err_msg_invalid_addr[] = "invalid address";

using namespace silkworm;

Expand Down Expand Up @@ -219,32 +220,40 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction&
inevm.emplace(get_self(), get_self().value);
};

bool is_special_signature = silkworm::is_special_signature(tx.r, tx.s);

tx.from.reset();
tx.recover_sender();
eosio::check(tx.from.has_value(), "unable to recover sender");
LOGTIME("EVM RECOVER SENDER");

if(from_self) {
check(is_reserved_address(*tx.from), "actions from self without a reserved from address are unexpected");
const name ingress_account(*extract_reserved_address(*tx.from));
// 1 For regular signature, it's impossible to from reserved address,
// and now we accpet them regardless from self or not, so no special treatment.
// 2 For special signature, we will reject calls not from self
// and process the special balance if the tx is from reserved address.
if (is_special_signature) {
check(from_self, "bridge signature used outside of bridge transaction");
if (is_reserved_address(*tx.from)) {
const name ingress_account(*extract_reserved_address(*tx.from));

const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas;
check(max_gas_cost + tx.value < std::numeric_limits<intx::uint256>::max(), "too much gas");
const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost;
const intx::uint512 max_gas_cost = intx::uint256(tx.gas_limit) * tx.max_fee_per_gas;
check(max_gas_cost + tx.value < std::numeric_limits<intx::uint256>::max(), "too much gas");
const intx::uint256 value_with_max_gas = tx.value + (intx::uint256)max_gas_cost;

populate_bridge_accessors();
balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){
b.balance -= value_with_max_gas;
});
inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer);
populate_bridge_accessors();
balance_table.modify(balance_table.get(ingress_account.value), eosio::same_payer, [&](balance& b){
b.balance -= value_with_max_gas;
});
inevm->set(inevm->get() += value_with_max_gas, eosio::same_payer);

ep.state().set_balance(*tx.from, value_with_max_gas);
ep.state().set_nonce(*tx.from, tx.nonce);
ep.state().set_balance(*tx.from, value_with_max_gas);
ep.state().set_nonce(*tx.from, tx.nonce);
}
}
else if(is_reserved_address(*tx.from))
check(from_self, "bridge signature used outside of bridge transaction");

if(enforce_chain_id && !from_self) {
// A tx from self with regular signature can potentially from external source.
// Therefore, only tx with special signature can waive the chain_id check.
if(enforce_chain_id && !is_special_signature) {
check(tx.chain_id.has_value(), "tx without chain-id");
}

Expand All @@ -269,7 +278,7 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction&
}

if(from_self)
eosio::check(receipt.success, "ingress bridge actions must succeed");
eosio::check(receipt.success, "tx executed inline by contract must succeed");

if(!ep.state().reserved_objects().empty()) {
bool non_open_account_sent = false;
Expand Down Expand Up @@ -452,7 +461,7 @@ void evm_contract::close(eosio::name owner) {
uint64_t evm_contract::get_and_increment_nonce(const name owner) {
nextnonces nextnonce_table(get_self(), get_self().value);

const nextnonce& nonce = nextnonce_table.get(owner.value);
const nextnonce& nonce = nextnonce_table.get(owner.value, "caller account has not been opened");
uint64_t ret = nonce.next_nonce;
nextnonce_table.modify(nonce, eosio::same_payer, [](nextnonce& n){
++n.next_nonce;
Expand Down Expand Up @@ -555,6 +564,76 @@ bool evm_contract::gc(uint32_t max) {
return state.gc(max);
}

void evm_contract::call_(intx::uint256 s, const bytes& to, intx::uint256 value, const bytes& data, uint64_t gas_limit, uint64_t nonce) {
const auto& current_config = _config.get();

Transaction txn;
txn.type = TransactionType::kLegacy;
txn.nonce = nonce;
txn.max_priority_fee_per_gas = current_config.gas_price;
txn.max_fee_per_gas = current_config.gas_price;
txn.gas_limit = gas_limit;
txn.value = value;
txn.data = Bytes{(const uint8_t*)data.data(), data.size()};
txn.r = 0u; // r == 0 is pseudo signature that resolves to reserved address range
txn.s = s;

// Allow empty to so that it can support contract creation calls.
if (!to.empty()) {
ByteView bv_to{(const uint8_t*)to.data(), to.size()};
txn.to = to_evmc_address(bv_to);
}

Bytes rlp;
rlp::encode(rlp, txn);
pushtx_action pushtx_act(get_self(), {{get_self(), "active"_n}});
pushtx_act.send(get_self(), rlp);
}

void evm_contract::call(eosio::name from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) {
assert_unfrozen();
require_auth(from);

// Prepare v
eosio::check(value.size() == sizeof(intx::uint256), "invalid value");
intx::uint256 v = intx::be::unsafe::load<intx::uint256>((const uint8_t *)value.data());

call_(from.value, to, v, data, gas_limit, get_and_increment_nonce(from));
}

void evm_contract::admincall(const bytes& from, const bytes& to, const bytes& value, const bytes& data, uint64_t gas_limit) {
assert_unfrozen();
require_auth(get_self());

// Prepare v
eosio::check(value.size() == sizeof(intx::uint256), "invalid value");
intx::uint256 v = intx::be::unsafe::load<intx::uint256>((const uint8_t *)value.data());

// Prepare s
eosio::check(from.size() == kAddressLength, err_msg_invalid_addr);
uint8_t s_buffer[32] = {};
memcpy(s_buffer + 32 - kAddressLength, from.data(), kAddressLength);
intx::uint256 s = intx::be::load<intx::uint256>(s_buffer);
// pad with '1's
s |= ((~intx::uint256(0)) << (kAddressLength * 8));

// Prepare nonce
auto from_addr = to_address(from);
auto eos_acct = extract_reserved_address(from_addr);
uint64_t nonce = 0;
if (eos_acct) {
nonce = get_and_increment_nonce(eosio::name(*eos_acct));
}
else {
evm_runtime::state state{get_self(), get_self(), true};
auto account = state.read_account(from_addr);
check(!!account, err_msg_invalid_addr);
nonce = account->nonce;
}

call_(s, to, v, data, gas_limit, nonce);
}

#ifdef WITH_TEST_ACTIONS
[[eosio::action]] void evm_contract::testtx( const std::optional<bytes>& orlptx, const evm_runtime::test::block_info& bi ) {
assert_unfrozen();
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ add_eosio_test_executable( unit_test
${CMAKE_SOURCE_DIR}/gas_fee_tests.cpp
${CMAKE_SOURCE_DIR}/blockhash_tests.cpp
${CMAKE_SOURCE_DIR}/exec_tests.cpp
${CMAKE_SOURCE_DIR}/call_tests.cpp
${CMAKE_SOURCE_DIR}/chainid_tests.cpp
${CMAKE_SOURCE_DIR}/main.cpp
${CMAKE_SOURCE_DIR}/silkworm/core/silkworm/rlp/encode.cpp
Expand Down
38 changes: 38 additions & 0 deletions tests/basic_evm_tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,44 @@ transaction_trace_ptr basic_evm_tester::exec(const exec_input& input, const std:
return basic_evm_tester::push_action(evm_account_name, "exec"_n, evm_account_name, bytes{binary_data.begin(), binary_data.end()});
}

void basic_evm_tester::call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor)
{
bytes to_bytes;
to_bytes.resize(to.size());
memcpy(to_bytes.data(), to.data(), to.size());

bytes data_bytes;
data_bytes.resize(data.size());
memcpy(data_bytes.data(), data.data(), data.size());

bytes value_bytes;
value_bytes.resize(value.size());
memcpy(value_bytes.data(), value.data(), value.size());

push_action(evm_account_name, "call"_n, actor, mvo()("from", from)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit));
}

void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor)
{
bytes to_bytes;
to_bytes.resize(to.size());
memcpy(to_bytes.data(), to.data(), to.size());

bytes data_bytes;
data_bytes.resize(data.size());
memcpy(data_bytes.data(), data.data(), data.size());

bytes from_bytes;
from_bytes.resize(from.size());
memcpy(from_bytes.data(), from.data(), from.size());

bytes value_bytes;
value_bytes.resize(value.size());
memcpy(value_bytes.data(), value.data(), value.size());

push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value_bytes)("data", data_bytes)("gas_limit", gas_limit));
}

void basic_evm_tester::pushtx(const silkworm::Transaction& trx, name miner)
{
silkworm::Bytes rlp;
Expand Down
2 changes: 2 additions & 0 deletions tests/basic_evm_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ class basic_evm_tester : public testing::validating_tester

transaction_trace_ptr exec(const exec_input& input, const std::optional<exec_callback>& callback);
void pushtx(const silkworm::Transaction& trx, name miner = evm_account_name);
void call(name from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor);
void admincall(const evmc::bytes& from, const evmc::bytes& to, const evmc::bytes& value, evmc::bytes& data, uint64_t gas_limit, name actor);
evmc::address deploy_contract(evm_eoa& eoa, evmc::bytes bytecode);

void addegress(const std::vector<name>& accounts);
Expand Down
Loading

0 comments on commit f1d9f9f

Please sign in to comment.