Skip to content

Commit

Permalink
Add call actions. Pending submodule silkworm updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
yarkinwho committed Jul 27, 2023
1 parent d3f5cb9 commit 1e300f2
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 15 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, uint128_t value, bytes& data, uint64_t gas_limit);
[[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, 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 evmc::address& to, intx::uint256 value, 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
92 changes: 77 additions & 15 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,30 +220,37 @@ Receipt evm_contract::execute_tx( eosio::name miner, Block& block, Transaction&
inevm.emplace(get_self(), get_self().value);
};

bool is_special_signature = (tx.r == intx::uint256());

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

// type 1: normal signature (normal address recovered from normal signature), required !from_self
// type 2: special signature (r == 0), reserved address (stored in s), required from_self + reduce special balance
// type 3: special signature (r == 0), normal address (stored in s), required from_self
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));
check(is_special_signature, "actions from self without a special signature are unexpected");
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");
else if(is_special_signature)
check(false, "bridge signature used outside of bridge transaction");

if(enforce_chain_id && !from_self) {
check(tx.chain_id.has_value(), "tx without chain-id");
Expand Down Expand Up @@ -452,7 +460,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 +563,60 @@ bool evm_contract::gc(uint32_t max) {
return state.gc(max);
}

void evm_contract::call_(intx::uint256 s, const evmc::address& to, intx::uint256 value, 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.to = to,
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;

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, uint128_t value, bytes& data, uint64_t gas_limit) {
assert_unfrozen();
require_auth(from);

ByteView bv_to{(const uint8_t*)to.data(), to.size()};

call_(from.value, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, get_and_increment_nonce(from));
}

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

// Prepare s
eosio::check(from.size() == kAddressLength, err_msg_invalid_addr);
intx::uint256 s = intx::be::unsafe::load<intx::uint256>((const uint8_t *)from.data());
// load will put the data in higher bytes, shift them donw.
s >>= 256 - kAddressLength * 8;
// pad with '1's
s |= ((~intx::uint256(0)) << (kAddressLength * 8));

// Prepare to
ByteView bv_to{(const uint8_t*)to.data(), to.size()};

// Prepare nonce
evm_runtime::state state{get_self(), get_self(), true};
auto account = state.read_account(to_address(from));
check(!!account, err_msg_invalid_addr);

call_(s, to_evmc_address(bv_to), intx::uint256(value), data, gas_limit, account->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
28 changes: 28 additions & 0 deletions tests/basic_evm_tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,34 @@ 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, uint128_t 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());

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

void basic_evm_tester::admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t 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(to.size());
memcpy(from_bytes.data(), from.data(), from.size());

push_action(evm_account_name, "admincall"_n, actor, mvo()("from", from_bytes)("to", to_bytes)("value", value)("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, uint128_t value, evmc::bytes& data, uint64_t gas_limit, name actor);
void admincall(const evmc::bytes& from, const evmc::bytes& to, uint128_t 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 1e300f2

Please sign in to comment.