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

Initial implementation of call in test host #9

Merged
merged 2 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/assigner/include/nil/blueprint/assigner.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace nil {
handler_ptr = std::make_shared<handler<BlueprintFieldType>>(desc, assignments);
}

void evaluate(evmc_vm* c_vm, const evmc_host_interface* host, evmc_host_context* ctx,
evmc::Result evaluate(evmc_vm* c_vm, const evmc_host_interface* host, evmc_host_context* ctx,
evmc_revision rev, const evmc_message* msg, const uint8_t* code, size_t code_size) {

auto vm = static_cast<evmone::VM*>(c_vm);
Expand All @@ -40,7 +40,7 @@ namespace nil {
state->set_handler(handler_ptr);

auto res = execute(*vm, msg->gas, *state, code_analysis);
// TODO check res
return evmc::Result{res};
}

private:
Expand Down
4 changes: 3 additions & 1 deletion lib/assigner/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ include(GoogleTest)

find_package(GTest CONFIG REQUIRED)

add_executable(assigner_tests assigner_test.cpp)
add_executable(assigner_tests
assigner_test.cpp
test_host.cpp)

target_link_libraries(
assigner_tests
Expand Down
161 changes: 144 additions & 17 deletions lib/assigner/test/assigner_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
class AssignerTest : public testing::Test
{
public:
using BlueprintFieldType = typename nil::crypto3::algebra::curves::pallas::base_field_type;
using ArithmetizationType = nil::crypto3::zk::snark::plonk_constraint_system<BlueprintFieldType>;
static void SetUpTestSuite()
{
Expand Down Expand Up @@ -54,18 +53,19 @@ class AssignerTest : public testing::Test
.input_data = input,
.input_size = sizeof(input),
.value = value,
.create2_salt = 0,
.create2_salt = {0},
.code_address = code_addr
};


host_interface = &evmc::Host::get_interface();
ctx = example_host_create_context(tx_context);
ctx = test_host_create_context(tx_context, assigner_ptr.get());
}

static void TearDownTestSuite()
{
assigner_ptr.reset();
test_host_destroy_context(ctx);
}

static std::unique_ptr<nil::blueprint::assigner<BlueprintFieldType>> assigner_ptr;
Expand All @@ -77,7 +77,7 @@ class AssignerTest : public testing::Test
static struct evmc_message msg;
};

std::unique_ptr<nil::blueprint::assigner<AssignerTest::BlueprintFieldType>>
std::unique_ptr<nil::blueprint::assigner<BlueprintFieldType>>
AssignerTest::assigner_ptr;
std::vector<nil::blueprint::assignment<AssignerTest::ArithmetizationType>>
AssignerTest::assignments;
Expand All @@ -87,6 +87,8 @@ struct evmc_vm* AssignerTest::vm;
evmc_revision AssignerTest::rev = {};
struct evmc_message AssignerTest::msg;

using intx::operator""_u256;

TEST_F(AssignerTest, conversions)
{
intx::uint256 intx_number;
Expand Down Expand Up @@ -118,7 +120,7 @@ TEST_F(AssignerTest, mul) {

assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(assignments[0].witness(0, 0), 8);
EXPECT_EQ(assignments[0].witness(1, 0), 4);
EXPECT_EQ(assignments[0].witness(0, 1), 4);
}

TEST_F(AssignerTest, callvalue_calldataload)
Expand All @@ -131,9 +133,9 @@ TEST_F(AssignerTest, callvalue_calldataload)
evmone::OP_CALLDATALOAD,
};
assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(nil::blueprint::handler<BlueprintFieldType>::to_uint256(assignments[1].witness(0, 0)),
EXPECT_EQ(nil::blueprint::handler<BlueprintFieldType>::to_uint256(assignments[0].witness(1, 0)),
intx::be::load<intx::uint256>(msg.value));
EXPECT_EQ(assignments[1].witness(0, 2), index);
EXPECT_EQ(assignments[0].witness(1, 1), index);
}

// TODO: test dataload instruction (for now its cost is not defined)
Expand All @@ -145,7 +147,7 @@ TEST_F(AssignerTest, DISABLED_dataload) {
evmone::OP_DATALOAD,
};
assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(assignments[1].witness(0, 1), index);
EXPECT_EQ(assignments[0].witness(1, 2), index);
}

TEST_F(AssignerTest, mstore_load)
Expand All @@ -163,9 +165,9 @@ TEST_F(AssignerTest, mstore_load)
evmone::OP_MLOAD,
};
assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(assignments[2].witness(0, 0), value);
EXPECT_EQ(assignments[2].witness(0, 1), index);
EXPECT_EQ(assignments[2].witness(0, 2), value);
EXPECT_EQ(assignments[0].witness(2, 0), value);
EXPECT_EQ(assignments[0].witness(2, 1), index);
EXPECT_EQ(assignments[0].witness(2, 2), value);
}

TEST_F(AssignerTest, sstore_load)
Expand All @@ -183,9 +185,9 @@ TEST_F(AssignerTest, sstore_load)
evmone::OP_SLOAD,
};
assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(assignments[3].witness(0, 0), value);
EXPECT_EQ(assignments[3].witness(0, 1), key);
EXPECT_EQ(assignments[3].witness(0, 2), value);
EXPECT_EQ(assignments[0].witness(3, 0), value);
EXPECT_EQ(assignments[0].witness(3, 1), key);
EXPECT_EQ(assignments[0].witness(3, 2), value);
}

// TODO: test transient storage opcodes (for now their costs are not defined)
Expand All @@ -203,7 +205,132 @@ TEST_F(AssignerTest, DISABLED_tstore_load) {
evmone::OP_TLOAD,
};
assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
EXPECT_EQ(assignments[3].witness(1, 0), value);
EXPECT_EQ(assignments[3].witness(1, 1), key);
EXPECT_EQ(assignments[3].witness(1, 2), value);
EXPECT_EQ(assignments[0].witness(4, 0), value);
EXPECT_EQ(assignments[0].witness(4, 1), key);
EXPECT_EQ(assignments[0].witness(4, 2), value);
}

TEST_F(AssignerTest, create) {

std::vector<uint8_t> code = {
// Create an account with 0 wei and no code
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_CREATE,

// Create an account with 9 wei and no code
evmone::OP_PUSH1,
1,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
9,
evmone::OP_CREATE,

// Create an account with 0 wei and 4 FF as code
evmone::OP_PUSH13,
// code 0x63FFFFFFFF60005260046000F3 will be inserted later

evmone::OP_PUSH1,
0,
evmone::OP_MSTORE,
evmone::OP_PUSH1,
2,
evmone::OP_PUSH1,
13,
evmone::OP_PUSH1,
19,
evmone::OP_PUSH1,
0,
evmone::OP_CREATE,
};

auto push13_it = std::find(code.begin(), code.end(), evmone::OP_PUSH13);
ASSERT_NE(push13_it, code.end());
size_t push13_idx = push13_it - code.begin();

auto contract_code = 0x63FFFFFFFF60005260046000F3_u256;
auto byte_container = intx::be::store<evmc_bytes32>(contract_code);
// Code is in the last 13 bytes of the container
code.insert(code.begin() + push13_idx + 1, &byte_container.bytes[32-13], &byte_container.bytes[32]);

assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
// Check stored witnesses of MSTORE instruction at depth 1
EXPECT_EQ(assignments[1].witness(2, 1), 0);
EXPECT_EQ(assignments[1].witness(2, 0), 0xFFFFFFFF);
}

TEST_F(AssignerTest, call) {

std::vector<uint8_t> code = {
// Create a contract that creates an exception if first word of calldata is 0
evmone::OP_PUSH17,
// code 0x67600035600757FE5B60005260086018F3 will be inserted later
evmone::OP_PUSH1,
0,

evmone::OP_MSTORE,
evmone::OP_PUSH1,
17,
evmone::OP_PUSH1,
15,
evmone::OP_PUSH1,
0,
evmone::OP_CREATE,

// Call with no parameters, return 0
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_DUP6,
evmone::OP_PUSH2,
0xFF,
0xFF,
evmone::OP_CALL,

// Call with non 0 calldata, returns success
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
32,
evmone::OP_PUSH1,
0,
evmone::OP_PUSH1,
0,
evmone::OP_DUP7,
evmone::OP_PUSH2,
0xFF,
0xFF,
evmone::OP_CALL,
};

auto push17_it = std::find(code.begin(), code.end(), evmone::OP_PUSH17);
ASSERT_NE(push17_it, code.end());
size_t push17_idx = push17_it - code.begin();

auto contract_code = 0x67600035600757FE5B60005260086018F3_u256;
auto bytes = intx::be::store<evmc_bytes32>(contract_code);
// Code is in the last 13 bytes of the container
code.insert(code.begin() + push17_idx + 1, &bytes.bytes[32-17], &bytes.bytes[32]);

assigner_ptr->evaluate(vm, host_interface, ctx, rev, &msg, code.data(), code.size());
// Check stored witness of CALLDATALOAD instruction at depth 1
EXPECT_EQ(assignments[1].witness(1, 1), 0);
}
88 changes: 88 additions & 0 deletions lib/assigner/test/test_host.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "test_host.h"

#include <intx/intx.hpp>
#include <ethash/keccak.hpp>

extern "C" {

evmc_host_context* test_host_create_context(evmc_tx_context tx_context, AssignerType *assigner)
{
auto host = new TestHost{tx_context, assigner};
return host->to_context();
}

void test_host_destroy_context(evmc_host_context* context)
{
delete evmc::Host::from_context<TestHost>(context);
}
}

evmc::Result TestHost::handle_call(const evmc_message& msg)
{
evmc_vm * vm = evmc_create_evmone();
auto account_iter = accounts.find(msg.code_address);
auto &sender_acc = accounts[msg.sender];
if (account_iter == accounts.end())
{
// Create account
accounts[msg.code_address] = {};
}
auto& acc = account_iter->second;
if (msg.kind == EVMC_CALL) {
auto value_to_transfer = intx::be::load<intx::uint256>(msg.value);
auto balance = intx::be::load<intx::uint256>(sender_acc.balance);
// Balance was already checked in evmone, so simply adjust it
sender_acc.balance = intx::be::store<evmc::uint256be>(balance - value_to_transfer);
acc.balance = intx::be::store<evmc::uint256be>(
value_to_transfer + intx::be::load<intx::uint256>(acc.balance));
}
if (acc.code.empty())
{
return evmc::Result{EVMC_SUCCESS, msg.gas, 0, msg.input_data, msg.input_size};
}
// TODO: handle precompiled contracts
auto res = assigner->evaluate(vm, &get_interface(), to_context(),
EVMC_LATEST_STABLE_REVISION, &msg, acc.code.data(), acc.code.size());
return res;
}

evmc::Result TestHost::handle_create(const evmc_message& msg)
{
evmc::address new_contract_address = calculate_address(msg);
accounts[new_contract_address] = {};
if (msg.input_size == 0)
{
return evmc::Result{EVMC_SUCCESS, msg.gas, 0, new_contract_address};
}
evmc::VM vm{evmc_create_evmone()};
evmc_message init_msg(msg);
init_msg.kind = EVMC_CALL;
init_msg.recipient = new_contract_address;
init_msg.sender = msg.sender;
init_msg.input_size = 0;
auto res = assigner->evaluate(vm.get_raw_pointer(), &get_interface(), to_context(),
EVMC_LATEST_STABLE_REVISION, &init_msg, msg.input_data, msg.input_size);

if (res.status_code == EVMC_SUCCESS)
{
accounts[new_contract_address].code =
std::vector<uint8_t>(res.output_data, res.output_data + res.output_size);
}
res.create_address = new_contract_address;
return res;
}

evmc::address TestHost::calculate_address(const evmc_message& msg)
{
// TODO: Implement for CREATE opcode, for now the result is only correct for CREATE2
// CREATE requires rlp encoding
auto seed = intx::be::load<intx::uint256>(msg.create2_salt);
auto hash = intx::be::load<intx::uint256>(ethash::keccak256(msg.input_data, msg.input_size));
auto sender = intx::be::load<intx::uint256>(msg.sender);
auto sum = 0xff + seed + hash + sender;
auto rehash = ethash::keccak256(reinterpret_cast<const uint8_t *>(&sum), sizeof(sum));
// Result address is the last 20 bytes of the hash
evmc::address res;
std::memcpy(res.bytes, rehash.bytes + 12, 20);
return res;
}
Loading
Loading