Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add call actions #634

Merged
merged 15 commits into from
Aug 14, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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, const bytes& data, uint64_t gas_limit);
[[eosio::action]] void admincall(const bytes& from, const bytes& to, uint128_t value, const bytes& data, uint64_t gas_limit);
yarkinwho marked this conversation as resolved.
Show resolved Hide resolved

#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
2 changes: 1 addition & 1 deletion silkworm
102 changes: 86 additions & 16 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)) {
yarkinwho marked this conversation as resolved.
Show resolved Hide resolved
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) {
yarkinwho marked this conversation as resolved.
Show resolved Hide resolved
check(tx.chain_id.has_value(), "tx without chain-id");
Expand All @@ -269,7 +277,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, "inline actions must succeed");
yarkinwho marked this conversation as resolved.
Show resolved Hide resolved

if(!ep.state().reserved_objects().empty()) {
bool non_open_account_sent = false;
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,68 @@ 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, uint128_t value, const bytes& data, uint64_t gas_limit) {
arhag marked this conversation as resolved.
Show resolved Hide resolved
assert_unfrozen();
require_auth(from);

call_(from.value, 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, const bytes& data, uint64_t gas_limit) {
arhag marked this conversation as resolved.
Show resolved Hide resolved
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());
arhag marked this conversation as resolved.
Show resolved Hide resolved
// 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 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, intx::uint256(value), 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
30 changes: 30 additions & 0 deletions tests/basic_evm_tester.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,36 @@ 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(from.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