Skip to content

Commit 7717742

Browse files
axicchfast
authored andcommitted
Implement EXT*CALL instructions (EIP-7069) in EOF (#630)
Implements [EIP-7069](https://eips.ethereum.org/EIPS/eip-7069),as part of the [MegaEOF spec](https://github.com/ipsilon/eof/blob/main/spec/eof.md). Adds implementation of EXTCALL, EXTDELEGATECALL and EXTSTATICCALL instructions.
1 parent 8358ba0 commit 7717742

16 files changed

+1917
-451
lines changed

circle.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ jobs:
535535
~/tests/EIPTests/BlockchainTests/
536536
- download_execution_tests:
537537
repo: ipsilon/tests
538-
rev: eof-deprecate-ops-20240318
538+
rev: eof-calls-20240321
539539
legacy: false
540540
- run:
541541
name: "State tests (EOF)"

lib/evmone/advanced_instructions.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,9 @@ constexpr std::array<instruction_exec_fn, 256> instruction_implementations = [](
278278
table[OP_DATASIZE] = op_undefined;
279279
table[OP_DATACOPY] = op_undefined;
280280
table[OP_RETURNDATALOAD] = op_undefined;
281+
table[OP_EXTCALL] = op_undefined;
282+
table[OP_EXTSTATICCALL] = op_undefined;
283+
table[OP_EXTDELEGATECALL] = op_undefined;
281284
table[OP_JUMPF] = op_undefined;
282285

283286
table[OP_DUPN] = op_undefined;

lib/evmone/baseline_instruction_table.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ constexpr auto legacy_cost_tables = []() noexcept {
3838
tables[EVMC_PRAGUE][OP_SWAPN] = instr::undefined;
3939
tables[EVMC_PRAGUE][OP_EXCHANGE] = instr::undefined;
4040
tables[EVMC_PRAGUE][OP_RETURNDATALOAD] = instr::undefined;
41+
tables[EVMC_PRAGUE][OP_EXTCALL] = instr::undefined;
42+
tables[EVMC_PRAGUE][OP_EXTSTATICCALL] = instr::undefined;
43+
tables[EVMC_PRAGUE][OP_EXTDELEGATECALL] = instr::undefined;
4144
return tables;
4245
}();
4346

@@ -48,6 +51,9 @@ constexpr auto eof_cost_tables = []() noexcept {
4851
tables[EVMC_PRAGUE][OP_PC] = instr::undefined;
4952
tables[EVMC_PRAGUE][OP_CALLCODE] = instr::undefined;
5053
tables[EVMC_PRAGUE][OP_SELFDESTRUCT] = instr::undefined;
54+
tables[EVMC_PRAGUE][OP_CALL] = instr::undefined;
55+
tables[EVMC_PRAGUE][OP_STATICCALL] = instr::undefined;
56+
tables[EVMC_PRAGUE][OP_DELEGATECALL] = instr::undefined;
5157
tables[EVMC_PRAGUE][OP_CREATE] = instr::undefined;
5258
tables[EVMC_PRAGUE][OP_CREATE2] = instr::undefined;
5359
tables[EVMC_PRAGUE][OP_CODESIZE] = instr::undefined;

lib/evmone/instructions.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -1066,6 +1066,12 @@ inline constexpr auto callcode = call_impl<OP_CALLCODE>;
10661066
inline constexpr auto delegatecall = call_impl<OP_DELEGATECALL>;
10671067
inline constexpr auto staticcall = call_impl<OP_STATICCALL>;
10681068

1069+
template <Opcode Op>
1070+
Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
1071+
inline constexpr auto extcall = extcall_impl<OP_EXTCALL>;
1072+
inline constexpr auto extdelegatecall = extcall_impl<OP_EXTDELEGATECALL>;
1073+
inline constexpr auto extstaticcall = extcall_impl<OP_EXTSTATICCALL>;
1074+
10691075
template <Opcode Op>
10701076
Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
10711077
inline constexpr auto create = create_impl<OP_CREATE>;

lib/evmone/instructions_calls.cpp

+114-17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55
#include "eof.hpp"
66
#include "instructions.hpp"
77

8+
constexpr int64_t MIN_RETAINED_GAS = 5000;
9+
constexpr int64_t MIN_CALLEE_GAS = 2300;
10+
constexpr int64_t CALL_VALUE_COST = 9000;
11+
constexpr int64_t ACCOUNT_CREATION_COST = 25000;
12+
13+
constexpr auto EXTCALL_SUCCESS = 0;
14+
constexpr auto EXTCALL_REVERT = 1;
15+
constexpr auto EXTCALL_ABORT = 2;
16+
817
namespace evmone::instr::core
918
{
1019
template <Opcode Op>
@@ -61,15 +70,15 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
6170
msg.input_size = input_size;
6271
}
6372

64-
auto cost = has_value ? 9000 : 0;
73+
auto cost = has_value ? CALL_VALUE_COST : 0;
6574

6675
if constexpr (Op == OP_CALL)
6776
{
6877
if (has_value && state.in_static_mode())
6978
return {EVMC_STATIC_MODE_VIOLATION, gas_left};
7079

7180
if ((has_value || state.rev < EVMC_SPURIOUS_DRAGON) && !state.host.account_exists(dst))
72-
cost += 25000;
81+
cost += ACCOUNT_CREATION_COST;
7382
}
7483

7584
if ((gas_left -= cost) < 0)
@@ -96,21 +105,6 @@ Result call_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexce
96105
if (has_value && intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < value)
97106
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
98107

99-
if constexpr (Op == OP_DELEGATECALL)
100-
{
101-
if (state.rev >= EVMC_PRAGUE && is_eof_container(state.original_code))
102-
{
103-
// The code targeted by DELEGATECALL must also be an EOF.
104-
// This restriction has been added to EIP-3540 in
105-
// https://github.com/ethereum/EIPs/pull/7131
106-
uint8_t target_code_prefix[2];
107-
const auto s = state.host.copy_code(
108-
msg.code_address, 0, target_code_prefix, std::size(target_code_prefix));
109-
if (!is_eof_container({target_code_prefix, s}))
110-
return {EVMC_SUCCESS, gas_left};
111-
}
112-
}
113-
114108
const auto result = state.host.call(msg);
115109
state.return_data.assign(result.output_data, result.output_size);
116110
stack.top() = result.status_code == EVMC_SUCCESS;
@@ -133,6 +127,109 @@ template Result call_impl<OP_DELEGATECALL>(
133127
template Result call_impl<OP_CALLCODE>(
134128
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
135129

130+
template <Opcode Op>
131+
Result extcall_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept
132+
{
133+
static_assert(Op == OP_EXTCALL || Op == OP_EXTDELEGATECALL || Op == OP_EXTSTATICCALL);
134+
135+
const auto dst = intx::be::trunc<evmc::address>(stack.pop());
136+
const auto input_offset_u256 = stack.pop();
137+
const auto input_size_u256 = stack.pop();
138+
const auto value = (Op == OP_EXTSTATICCALL || Op == OP_EXTDELEGATECALL) ? 0 : stack.pop();
139+
const auto has_value = value != 0;
140+
141+
stack.push(EXTCALL_ABORT); // Assume (hard) failure.
142+
state.return_data.clear();
143+
144+
if (state.host.access_account(dst) == EVMC_ACCESS_COLD)
145+
{
146+
if ((gas_left -= instr::additional_cold_account_access_cost) < 0)
147+
return {EVMC_OUT_OF_GAS, gas_left};
148+
}
149+
150+
if (!check_memory(gas_left, state.memory, input_offset_u256, input_size_u256))
151+
return {EVMC_OUT_OF_GAS, gas_left};
152+
153+
const auto input_offset = static_cast<size_t>(input_offset_u256);
154+
const auto input_size = static_cast<size_t>(input_size_u256);
155+
156+
auto msg = evmc_message{};
157+
msg.kind = (Op == OP_EXTDELEGATECALL) ? EVMC_DELEGATECALL : EVMC_CALL;
158+
msg.flags = (Op == OP_EXTSTATICCALL) ? uint32_t{EVMC_STATIC} : state.msg->flags;
159+
msg.depth = state.msg->depth + 1;
160+
msg.recipient = (Op != OP_EXTDELEGATECALL) ? dst : state.msg->recipient;
161+
msg.code_address = dst;
162+
msg.sender = (Op == OP_EXTDELEGATECALL) ? state.msg->sender : state.msg->recipient;
163+
msg.value =
164+
(Op == OP_EXTDELEGATECALL) ? state.msg->value : intx::be::store<evmc::uint256be>(value);
165+
166+
if (input_size > 0)
167+
{
168+
// input_offset may be garbage if input_size == 0.
169+
msg.input_data = &state.memory[input_offset];
170+
msg.input_size = input_size;
171+
}
172+
173+
auto cost = has_value ? CALL_VALUE_COST : 0;
174+
175+
if constexpr (Op == OP_EXTCALL)
176+
{
177+
if (has_value && state.in_static_mode())
178+
return {EVMC_STATIC_MODE_VIOLATION, gas_left};
179+
180+
if (has_value && !state.host.account_exists(dst))
181+
cost += ACCOUNT_CREATION_COST;
182+
}
183+
184+
if ((gas_left -= cost) < 0)
185+
return {EVMC_OUT_OF_GAS, gas_left};
186+
187+
msg.gas = gas_left - std::max(gas_left / 64, MIN_RETAINED_GAS);
188+
189+
if (msg.gas < MIN_CALLEE_GAS || state.msg->depth >= 1024 ||
190+
(has_value &&
191+
intx::be::load<uint256>(state.host.get_balance(state.msg->recipient)) < value))
192+
{
193+
stack.top() = EXTCALL_REVERT;
194+
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
195+
}
196+
197+
if constexpr (Op == OP_EXTDELEGATECALL)
198+
{
199+
// The code targeted by EXTDELEGATECALL must also be an EOF.
200+
// This restriction has been added to EIP-3540 in
201+
// https://github.com/ethereum/EIPs/pull/7131
202+
uint8_t target_code_prefix[2];
203+
const auto s = state.host.copy_code(
204+
msg.code_address, 0, target_code_prefix, std::size(target_code_prefix));
205+
if (!is_eof_container({target_code_prefix, s}))
206+
{
207+
stack.top() = EXTCALL_REVERT;
208+
return {EVMC_SUCCESS, gas_left}; // "Light" failure.
209+
}
210+
}
211+
212+
const auto result = state.host.call(msg);
213+
state.return_data.assign(result.output_data, result.output_size);
214+
if (result.status_code == EVMC_SUCCESS)
215+
stack.top() = EXTCALL_SUCCESS;
216+
else if (result.status_code == EVMC_REVERT)
217+
stack.top() = EXTCALL_REVERT;
218+
else
219+
stack.top() = EXTCALL_ABORT;
220+
221+
const auto gas_used = msg.gas - result.gas_left;
222+
gas_left -= gas_used;
223+
state.gas_refund += result.gas_refund;
224+
return {EVMC_SUCCESS, gas_left};
225+
}
226+
227+
template Result extcall_impl<OP_EXTCALL>(
228+
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
229+
template Result extcall_impl<OP_EXTSTATICCALL>(
230+
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
231+
template Result extcall_impl<OP_EXTDELEGATECALL>(
232+
StackTop stack, int64_t gas_left, ExecutionState& state) noexcept;
136233

137234
template <Opcode Op>
138235
Result create_impl(StackTop stack, int64_t gas_left, ExecutionState& state) noexcept

lib/evmone/instructions_opcodes.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,10 @@ enum Opcode : uint8_t
182182
OP_CREATE2 = 0xf5,
183183
OP_RETURNDATALOAD = 0xf7,
184184

185+
OP_EXTCALL = 0xf8,
186+
OP_EXTDELEGATECALL = 0xf9,
185187
OP_STATICCALL = 0xfa,
188+
OP_EXTSTATICCALL = 0xfb,
186189

187190
OP_REVERT = 0xfd,
188191
OP_INVALID = 0xfe,

lib/evmone/instructions_traits.hpp

+6
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,9 @@ constexpr inline GasCostTable gas_costs = []() noexcept {
185185
table[EVMC_PRAGUE][OP_DATASIZE] = 2;
186186
table[EVMC_PRAGUE][OP_DATACOPY] = 3;
187187
table[EVMC_PRAGUE][OP_RETURNDATALOAD] = 3;
188+
table[EVMC_PRAGUE][OP_EXTCALL] = warm_storage_read_cost;
189+
table[EVMC_PRAGUE][OP_EXTDELEGATECALL] = warm_storage_read_cost;
190+
table[EVMC_PRAGUE][OP_EXTSTATICCALL] = warm_storage_read_cost;
188191

189192
return table;
190193
}();
@@ -405,7 +408,10 @@ constexpr inline std::array<Traits, 256> traits = []() noexcept {
405408
table[OP_DELEGATECALL] = {"DELEGATECALL", 0, false, 6, -5, EVMC_HOMESTEAD};
406409
table[OP_CREATE2] = {"CREATE2", 0, false, 4, -3, EVMC_CONSTANTINOPLE};
407410
table[OP_RETURNDATALOAD] = {"RETURNDATALOAD", 0, false, 1, 0, EVMC_PRAGUE};
411+
table[OP_EXTCALL] = {"EXTCALL", 0, false, 4, -3, EVMC_PRAGUE};
412+
table[OP_EXTDELEGATECALL] = {"EXTDELEGATECALL", 0, false, 3, -2, EVMC_PRAGUE};
408413
table[OP_STATICCALL] = {"STATICCALL", 0, false, 6, -5, EVMC_BYZANTIUM};
414+
table[OP_EXTSTATICCALL] = {"EXTSTATICCALL", 0, false, 3, -2, EVMC_PRAGUE};
409415
table[OP_CALLF] = {"CALLF", 2, false, 0, 0, EVMC_PRAGUE};
410416
table[OP_RETF] = {"RETF", 0, true, 0, 0, EVMC_PRAGUE};
411417
table[OP_JUMPF] = {"JUMPF", 2, true, 0, 0, EVMC_PRAGUE};

0 commit comments

Comments
 (0)