diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 52bda8bf1..5d4432ca5 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -188,6 +188,21 @@ enum class Instruction: uint8_t SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion }; +/// @returns true if the instruction is of the CALL opcode family +constexpr bool isCallInstruction(Instruction _inst) noexcept +{ + switch (_inst) + { + case Instruction::CALL: + case Instruction::CALLCODE: + case Instruction::DELEGATECALL: + case Instruction::STATICCALL: + return true; + default: + return false; + } +} + /// @returns true if the instruction is a PUSH inline bool isPushInstruction(Instruction _inst) { diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index 34a714404..075d11795 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -112,6 +112,7 @@ string EwasmTranslationTest::interpret() state, WasmDialect{}, *m_object->code, + /*disableExternalCalls=*/true, /*disableMemoryTracing=*/false ); } diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 3c890972e..700874349 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -98,6 +98,7 @@ string YulInterpreterTest::interpret() state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast, + /*disableExternalCalls=*/true, /*disableMemoryTracing=*/false ); } diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index cfbbbaa48..57f930cfe 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -53,7 +53,7 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( TerminationReason reason = TerminationReason::None; try { - Interpreter::run(state, _dialect, *_ast, _disableMemoryTracing); + Interpreter::run(state, _dialect, *_ast, true, _disableMemoryTracing); } catch (StepLimitReached const&) { diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 9fcd08b77..30b2b5775 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -70,6 +70,10 @@ u256 readZeroExtended(bytes const& _data, u256 const& _offset) } } +} + +namespace solidity::yul::test +{ /// Copy @a _size bytes of @a _source at offset @a _sourceOffset to /// @a _target at offset @a _targetOffset. Behaves as if @a _source would /// continue with an infinite sequence of zero bytes beyond its end. @@ -185,7 +189,7 @@ u256 EVMInstructionInterpreter::eval( return u256("0x1234cafe1234cafe1234cafe") + arg[0]; uint64_t offset = uint64_t(arg[0] & uint64_t(-1)); uint64_t size = uint64_t(arg[1] & uint64_t(-1)); - return u256(keccak256(readMemory(offset, size))); + return u256(keccak256(m_state.readMemory(offset, size))); } case Instruction::ADDRESS: return h256(m_state.address, h256::AlignRight); @@ -322,7 +326,6 @@ u256 EVMInstructionInterpreter::eval( return (0xdddddd + arg[1]) & u256("0xffffffffffffffffffffffffffffffffffffffff"); case Instruction::CALL: case Instruction::CALLCODE: - // TODO assign returndata accessMemory(arg[3], arg[4]); accessMemory(arg[5], arg[6]); logTrace(_instruction, arg); @@ -335,11 +338,11 @@ u256 EVMInstructionInterpreter::eval( return 0; case Instruction::RETURN: { - bytes data; + m_state.returndata = {}; if (accessMemory(arg[0], arg[1])) - data = readMemory(arg[0], arg[1]); - logTrace(_instruction, arg, data); - BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); + m_state.returndata = m_state.readMemory(arg[0], arg[1]); + logTrace(_instruction, arg, m_state.returndata); + BOOST_THROW_EXCEPTION(ExplicitlyTerminatedWithReturn()); } case Instruction::REVERT: accessMemory(arg[0], arg[1]); @@ -499,18 +502,9 @@ bool EVMInstructionInterpreter::accessMemory(u256 const& _offset, u256 const& _s return false; } -bytes EVMInstructionInterpreter::readMemory(u256 const& _offset, u256 const& _size) -{ - yulAssert(_size <= 0xffff, "Too large read."); - bytes data(size_t(_size), uint8_t(0)); - for (size_t i = 0; i < data.size(); ++i) - data[i] = m_state.memory[_offset + i]; - return data; -} - u256 EVMInstructionInterpreter::readMemoryWord(u256 const& _offset) { - return u256(h256(readMemory(_offset, 32))); + return u256(h256(m_state.readMemory(_offset, 32))); } void EVMInstructionInterpreter::writeMemoryWord(u256 const& _offset, u256 const& _value) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index c05af8c1a..37bfdb214 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -42,6 +42,14 @@ struct BuiltinFunctionForEVM; namespace solidity::yul::test { +/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to +/// @a _target at offset @a _targetOffset. Behaves as if @a _source would +/// continue with an infinite sequence of zero bytes beyond its end. +void copyZeroExtended( + std::map& _target, bytes const& _source, + size_t _targetOffset, size_t _sourceOffset, size_t _size +); + struct InterpreterState; /** diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 45b39f2ed..f24f8b7f1 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -78,11 +78,12 @@ void Interpreter::run( InterpreterState& _state, Dialect const& _dialect, Block const& _ast, + bool _disableExternalCalls, bool _disableMemoryTrace ) { Scope scope; - Interpreter{_state, _dialect, scope, _disableMemoryTrace}(_ast); + Interpreter{_state, _dialect, scope, _disableExternalCalls, _disableMemoryTrace}(_ast); } void Interpreter::operator()(ExpressionStatement const& _expressionStatement) @@ -217,14 +218,14 @@ void Interpreter::operator()(Block const& _block) u256 Interpreter::evaluate(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); ev.visit(_expression); return ev.value(); } vector Interpreter::evaluateMulti(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); ev.visit(_expression); return ev.values(); } @@ -288,7 +289,17 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) { EVMInstructionInterpreter interpreter(m_state, m_disableMemoryTrace); - setValue(interpreter.evalBuiltin(*fun, _funCall.arguments, values())); + + u256 const value = interpreter.evalBuiltin(*fun, _funCall.arguments, values()); + + if ( + !m_disableExternalCalls && + fun->instruction && + evmasm::isCallInstruction(*fun->instruction) + ) + runExternalCall(*fun->instruction); + + setValue(value); return; } } @@ -316,13 +327,13 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) variables[fun->returnVariables.at(i).name] = 0; m_state.controlFlowState = ControlFlowState::Default; - Interpreter interpreter(m_state, m_dialect, *scope, m_disableMemoryTrace, std::move(variables)); - interpreter(fun->body); + unique_ptr interpreter = makeInterpreterCopy(std::move(variables)); + (*interpreter)(fun->body); m_state.controlFlowState = ControlFlowState::Default; m_values.clear(); for (auto const& retVar: fun->returnVariables) - m_values.emplace_back(interpreter.valueOfVariable(retVar.name)); + m_values.emplace_back(interpreter->valueOfVariable(retVar.name)); } u256 ExpressionEvaluator::value() const @@ -380,3 +391,86 @@ void ExpressionEvaluator::incrementStep() BOOST_THROW_EXCEPTION(ExpressionNestingLimitReached()); } } + +void ExpressionEvaluator::runExternalCall(evmasm::Instruction _instruction) +{ + u256 memOutOffset = 0; + u256 memOutSize = 0; + u256 callvalue = 0; + u256 memInOffset = 0; + u256 memInSize = 0; + + // Setup memOut* values + if ( + _instruction == evmasm::Instruction::CALL || + _instruction == evmasm::Instruction::CALLCODE + ) + { + memOutOffset = values()[5]; + memOutSize = values()[6]; + callvalue = values()[2]; + memInOffset = values()[3]; + memInSize = values()[4]; + } + else if ( + _instruction == evmasm::Instruction::DELEGATECALL || + _instruction == evmasm::Instruction::STATICCALL + ) + { + memOutOffset = values()[4]; + memOutSize = values()[5]; + memInOffset = values()[2]; + memInSize = values()[3]; + } + else + yulAssert(false); + + // Don't execute external call if it isn't our own address + if (values()[1] != util::h160::Arith(m_state.address)) + return; + + Scope tmpScope; + InterpreterState tmpState; + tmpState.calldata = m_state.readMemory(memInOffset, memInSize); + tmpState.callvalue = callvalue; + + // Create new interpreter for the called contract + unique_ptr newInterpreter = makeInterpreterNew(tmpState, tmpScope); + + Scope* abstractRootScope = &m_scope; + Scope* fileScope = nullptr; + Block const* ast = nullptr; + + // Find file scope + while (abstractRootScope->parent) + { + fileScope = abstractRootScope; + abstractRootScope = abstractRootScope->parent; + } + + // Get AST for file scope + for (auto&& [block, scope]: abstractRootScope->subScopes) + if (scope.get() == fileScope) + { + ast = block; + break; + } + + yulAssert(ast); + + try + { + (*newInterpreter)(*ast); + } + catch (ExplicitlyTerminatedWithReturn const&) + { + // Copy return data to our memory + copyZeroExtended( + m_state.memory, + newInterpreter->returnData(), + memOutOffset.convert_to(), + 0, + memOutSize.convert_to() + ); + } +} diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index cdc454401..3eee09caa 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include @@ -47,6 +49,10 @@ class ExplicitlyTerminated: public InterpreterTerminatedGeneric { }; +class ExplicitlyTerminatedWithReturn: public ExplicitlyTerminated +{ +}; + class StepLimitReached: public InterpreterTerminatedGeneric { }; @@ -108,6 +114,15 @@ struct InterpreterState void dumpTraceAndState(std::ostream& _out, bool _disableMemoryTrace) const; /// Prints non-zero storage to @param _out. void dumpStorage(std::ostream& _out) const; + + bytes readMemory(u256 const& _offset, u256 const& _size) + { + yulAssert(_size <= 0xffff, "Too large read."); + bytes data(size_t(_size), uint8_t(0)); + for (size_t i = 0; i < data.size(); ++i) + data[i] = memory[_offset + i]; + return data; + } }; /** @@ -135,6 +150,7 @@ public: InterpreterState& _state, Dialect const& _dialect, Block const& _ast, + bool _disableExternalCalls, bool _disableMemoryTracing ); @@ -142,6 +158,7 @@ public: InterpreterState& _state, Dialect const& _dialect, Scope& _scope, + bool _disableExternalCalls, bool _disableMemoryTracing, std::map _variables = {} ): @@ -149,6 +166,7 @@ public: m_state(_state), m_variables(std::move(_variables)), m_scope(&_scope), + m_disableExternalCalls(_disableExternalCalls), m_disableMemoryTrace(_disableMemoryTracing) { } @@ -165,6 +183,7 @@ public: void operator()(Leave const&) override; void operator()(Block const& _block) override; + bytes returnData() const { return m_state.returndata; } std::vector const& trace() const { return m_state.trace; } u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); } @@ -187,6 +206,7 @@ private: /// Values of variables. std::map m_variables; Scope* m_scope; + bool m_disableExternalCalls; bool m_disableMemoryTrace; }; @@ -201,12 +221,14 @@ public: Dialect const& _dialect, Scope& _scope, std::map const& _variables, + bool _disableExternalCalls, bool _disableMemoryTrace ): m_state(_state), m_dialect(_dialect), m_variables(_variables), m_scope(_scope), + m_disableExternalCalls(_disableExternalCalls), m_disableMemoryTrace(_disableMemoryTrace) {} @@ -220,6 +242,29 @@ public: std::vector values() const { return m_values; } private: + void runExternalCall(evmasm::Instruction _instruction); + virtual std::unique_ptr makeInterpreterCopy(std::map _variables = {}) const + { + return std::make_unique( + m_state, + m_dialect, + m_scope, + m_disableExternalCalls, + m_disableMemoryTrace, + std::move(_variables) + ); + } + virtual std::unique_ptr makeInterpreterNew(InterpreterState& _state, Scope& _scope) const + { + return std::make_unique( + _state, + m_dialect, + _scope, + m_disableExternalCalls, + m_disableMemoryTrace + ); + } + void setValue(u256 _value); /// Evaluates the given expression from right to left and @@ -243,6 +288,7 @@ private: std::vector m_values; /// Current expression nesting level unsigned m_nestingLevel = 0; + bool m_disableExternalCalls; /// Flag to disable memory tracing bool m_disableMemoryTrace; }; diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index fd43d222d..4a273d371 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -74,7 +74,7 @@ pair, shared_ptr> parse(string const& _source } } -void interpret(string const& _source) +void interpret(string const& _source, bool _disableExternalCalls) { shared_ptr ast; shared_ptr analysisInfo; @@ -87,7 +87,7 @@ void interpret(string const& _source) try { Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); - Interpreter::run(state, dialect, *ast, /*disableMemoryTracing=*/false); + Interpreter::run(state, dialect, *ast, _disableExternalCalls, /*disableMemoryTracing=*/false); } catch (InterpreterTerminatedGeneric const&) { @@ -110,6 +110,7 @@ Allowed options)", po::options_description::m_default_line_length - 23); options.add_options() ("help", "Show this help screen.") + ("enable-external-calls", "Enable external calls") ("input-file", po::value>(), "input file"); po::positional_options_description filesPositions; filesPositions.add("input-file", -1); @@ -153,7 +154,7 @@ Allowed options)", else input = readUntilEnd(cin); - interpret(input); + interpret(input, !arguments.count("enable-external-calls")); } return 0;