mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #11061 from ethereum/evmhost-tracing
EVMHost: Add tracing features needed for fuzzing.
This commit is contained in:
commit
23f03e1b61
111
test/EVMHost.cpp
111
test/EVMHost.cpp
@ -133,6 +133,10 @@ EVMHost::EVMHost(langutil::EVMVersion _evmVersion, evmc::VM& _vm):
|
||||
// Mainnet according to EIP-155
|
||||
tx_context.chain_id = evmc::uint256be{1};
|
||||
|
||||
// Reserve space for recording calls.
|
||||
if (!recorded_calls.capacity())
|
||||
recorded_calls.reserve(max_recorded_calls);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
@ -142,6 +146,8 @@ void EVMHost::reset()
|
||||
m_currentAddress = {};
|
||||
// Clear self destruct records
|
||||
recorded_selfdestructs.clear();
|
||||
// Clear call records
|
||||
recorded_calls.clear();
|
||||
|
||||
// Mark all precompiled contracts as existing. Existing here means to have a balance (as per EIP-161).
|
||||
// NOTE: keep this in sync with `EVMHost::call` below.
|
||||
@ -167,11 +173,18 @@ void EVMHost::selfdestruct(const evmc::address& _addr, const evmc::address& _ben
|
||||
accounts.erase(_addr);
|
||||
accounts[_beneficiary].balance = balance;
|
||||
// Record self destructs
|
||||
recorded_selfdestructs.push_back({_addr, _beneficiary});
|
||||
recorded_selfdestructs.push_back({_addr, _beneficiary, balance});
|
||||
}
|
||||
|
||||
void EVMHost::recordCalls(evmc_message const& _message) noexcept
|
||||
{
|
||||
if (recorded_calls.size() < max_recorded_calls)
|
||||
recorded_calls.emplace_back(_message);
|
||||
}
|
||||
|
||||
evmc::result EVMHost::call(evmc_message const& _message) noexcept
|
||||
{
|
||||
recordCalls(_message);
|
||||
if (_message.destination == 0x0000000000000000000000000000000000000001_address)
|
||||
return precompileECRecover(_message);
|
||||
else if (_message.destination == 0x0000000000000000000000000000000000000002_address)
|
||||
@ -244,12 +257,7 @@ evmc::result EVMHost::call(evmc_message const& _message) noexcept
|
||||
|
||||
code = evmc::bytes(message.input_data, message.input_data + message.input_size);
|
||||
}
|
||||
else if (message.kind == EVMC_DELEGATECALL)
|
||||
{
|
||||
code = accounts[message.destination].code;
|
||||
message.destination = m_currentAddress;
|
||||
}
|
||||
else if (message.kind == EVMC_CALLCODE)
|
||||
else if (message.kind == EVMC_DELEGATECALL || message.kind == EVMC_CALLCODE)
|
||||
{
|
||||
code = accounts[message.destination].code;
|
||||
message.destination = m_currentAddress;
|
||||
@ -761,26 +769,79 @@ evmc::result EVMHost::resultWithGas(
|
||||
return result;
|
||||
}
|
||||
|
||||
void EVMHost::print_all_storage(ostringstream& _os)
|
||||
{
|
||||
for (auto const& [addr, mockedAccount]: accounts)
|
||||
{
|
||||
_os << "Address: " << convertFromEVMC(addr) << endl;
|
||||
for (auto const& [slot, value]: get_address_storage(addr))
|
||||
if (get_storage(addr, slot))
|
||||
_os << convertFromEVMC(slot) << ": " << convertFromEVMC(value.value) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
void EVMHost::print_storage_at(evmc::address const& _addr, ostringstream& _os)
|
||||
{
|
||||
for (auto const& [slot, value]: get_address_storage(_addr))
|
||||
if (get_storage(_addr, slot))
|
||||
_os << convertFromEVMC(slot) << ": " << convertFromEVMC(value.value) << endl;
|
||||
}
|
||||
|
||||
StorageMap const& EVMHost::get_address_storage(evmc::address const& _addr)
|
||||
{
|
||||
assertThrow(account_exists(_addr), Exception, "Account does not exist.");
|
||||
return accounts[_addr].storage;
|
||||
}
|
||||
|
||||
string EVMHostPrinter::state()
|
||||
{
|
||||
// Print state and execution trace.
|
||||
if (m_host.account_exists(m_account))
|
||||
{
|
||||
storage();
|
||||
balance();
|
||||
}
|
||||
else
|
||||
selfdestructRecords();
|
||||
|
||||
callRecords();
|
||||
return m_stateStream.str();
|
||||
}
|
||||
|
||||
void EVMHostPrinter::storage()
|
||||
{
|
||||
for (auto const& [slot, value]: m_host.get_address_storage(m_account))
|
||||
if (m_host.get_storage(m_account, slot))
|
||||
m_stateStream << m_host.convertFromEVMC(slot)
|
||||
<< ": "
|
||||
<< m_host.convertFromEVMC(value.value)
|
||||
<< endl;
|
||||
}
|
||||
|
||||
void EVMHostPrinter::balance()
|
||||
{
|
||||
m_stateStream << "BALANCE "
|
||||
<< m_host.convertFromEVMC(m_host.get_balance(m_account))
|
||||
<< endl;
|
||||
}
|
||||
|
||||
void EVMHostPrinter::selfdestructRecords()
|
||||
{
|
||||
for (auto const& record: m_host.recorded_selfdestructs)
|
||||
m_stateStream << "SELFDESTRUCT"
|
||||
<< " BENEFICIARY "
|
||||
<< m_host.convertFromEVMC(record.beneficiary)
|
||||
<< " BALANCE "
|
||||
<< m_host.convertFromEVMC(record.balance)
|
||||
<< endl;
|
||||
}
|
||||
|
||||
void EVMHostPrinter::callRecords()
|
||||
{
|
||||
static const auto callKind = [](evmc_call_kind _kind) -> string
|
||||
{
|
||||
switch (_kind)
|
||||
{
|
||||
case evmc_call_kind::EVMC_CALL:
|
||||
return "CALL";
|
||||
case evmc_call_kind::EVMC_DELEGATECALL:
|
||||
return "DELEGATECALL";
|
||||
case evmc_call_kind::EVMC_CALLCODE:
|
||||
return "CALLCODE";
|
||||
case evmc_call_kind::EVMC_CREATE:
|
||||
return "CREATE";
|
||||
case evmc_call_kind::EVMC_CREATE2:
|
||||
return "CREATE2";
|
||||
default:
|
||||
assertThrow(false, Exception, "Invalid call kind.");
|
||||
}
|
||||
};
|
||||
|
||||
for (auto const& record: m_host.recorded_calls)
|
||||
m_stateStream << callKind(record.kind)
|
||||
<< " VALUE "
|
||||
<< m_host.convertFromEVMC(record.value)
|
||||
<< endl;
|
||||
}
|
||||
|
@ -61,11 +61,6 @@ public:
|
||||
tx_context.block_timestamp += 15;
|
||||
recorded_logs.clear();
|
||||
}
|
||||
/// Prints contents of storage at all addresses in host to @param _os.
|
||||
void print_all_storage(std::ostringstream& _os);
|
||||
|
||||
/// Prints contents of storage at @param _addr to @param _os.
|
||||
void print_storage_at(evmc::address const& _addr, std::ostringstream& _os);
|
||||
|
||||
/// @returns contents of storage at @param _addr.
|
||||
std::unordered_map<evmc::bytes32, evmc::storage_value> const& get_address_storage(evmc::address const& _addr);
|
||||
@ -95,6 +90,9 @@ public:
|
||||
private:
|
||||
evmc::address m_currentAddress = {};
|
||||
|
||||
/// Records calls made via @param _message.
|
||||
void recordCalls(evmc_message const& _message) noexcept;
|
||||
|
||||
static evmc::result precompileECRecover(evmc_message const& _message) noexcept;
|
||||
static evmc::result precompileSha256(evmc_message const& _message) noexcept;
|
||||
static evmc::result precompileRipeMD160(evmc_message const& _message) noexcept;
|
||||
@ -115,5 +113,29 @@ private:
|
||||
evmc_revision m_evmRevision;
|
||||
};
|
||||
|
||||
class EVMHostPrinter
|
||||
{
|
||||
public:
|
||||
/// Constructs a host printer object for state at @param _address.
|
||||
explicit EVMHostPrinter(EVMHost& _host, evmc::address _address):
|
||||
m_host(_host),
|
||||
m_account(_address)
|
||||
{}
|
||||
/// @returns state at account maintained by host.
|
||||
std::string state();
|
||||
private:
|
||||
/// Outputs storage at account to stateStream.
|
||||
void storage();
|
||||
/// Outputs call records for account to stateStream.
|
||||
void callRecords();
|
||||
/// Outputs balance of account to stateStream.
|
||||
void balance();
|
||||
/// Outputs self-destruct record for account to stateStream.
|
||||
void selfdestructRecords();
|
||||
|
||||
std::ostringstream m_stateStream;
|
||||
EVMHost& m_host;
|
||||
evmc::address m_account;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -91,10 +91,15 @@ public:
|
||||
/// The address of the beneficiary account.
|
||||
address beneficiary;
|
||||
|
||||
/// The balance of the self-destructed account.
|
||||
uint256be balance;
|
||||
|
||||
/// Equal operator.
|
||||
bool operator==(const selfdestuct_record& other) const noexcept
|
||||
{
|
||||
return selfdestructed == other.selfdestructed && beneficiary == other.beneficiary;
|
||||
return selfdestructed == other.selfdestructed &&
|
||||
beneficiary == other.beneficiary &&
|
||||
balance == other.balance;
|
||||
}
|
||||
};
|
||||
|
||||
@ -265,7 +270,7 @@ public:
|
||||
void selfdestruct(const address& addr, const address& beneficiary) noexcept override
|
||||
{
|
||||
record_account_access(addr);
|
||||
recorded_selfdestructs.push_back({addr, beneficiary});
|
||||
recorded_selfdestructs.push_back({addr, beneficiary, get_balance(addr)});
|
||||
}
|
||||
|
||||
/// Call/create other contract (EVMC host method).
|
||||
|
@ -119,11 +119,10 @@ if (OSSFUZZ)
|
||||
target_link_libraries(stack_reuse_codegen_ossfuzz PRIVATE yul
|
||||
evmc
|
||||
evmone-standalone
|
||||
yulInterpreter
|
||||
protobuf-mutator-libfuzzer.a
|
||||
protobuf-mutator.a
|
||||
protobuf.a
|
||||
)
|
||||
)
|
||||
set_target_properties(stack_reuse_codegen_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE})
|
||||
target_compile_options(stack_reuse_codegen_ossfuzz PUBLIC
|
||||
${COMPILE_OPTIONS}
|
||||
|
@ -41,7 +41,6 @@ using namespace solidity;
|
||||
using namespace solidity::test;
|
||||
using namespace solidity::test::fuzzer;
|
||||
using namespace solidity::yul;
|
||||
using namespace solidity::yul::test;
|
||||
using namespace solidity::yul::test::yul_fuzzer;
|
||||
using namespace solidity::langutil;
|
||||
using namespace std;
|
||||
@ -50,6 +49,10 @@ static evmc::VM evmone = evmc::VM{evmc_create_evmone()};
|
||||
|
||||
DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
{
|
||||
// Solidity creates an invalid instruction for subobjects, so we simply
|
||||
// ignore them in this fuzzer.
|
||||
if (_input.has_obj())
|
||||
return;
|
||||
bool filterStatefulInstructions = true;
|
||||
bool filterUnboundedLoops = true;
|
||||
ProtoConverter converter(
|
||||
@ -96,21 +99,28 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
// If the fuzzer synthesized input does not contain the revert opcode which
|
||||
// we lazily check by string find, the EVM call should not revert.
|
||||
bool noRevertInSource = yul_source.find("revert") == string::npos;
|
||||
bool noInvalidInSource = yul_source.find("invalid") == string::npos;
|
||||
if (noInvalidInSource)
|
||||
solAssert(
|
||||
callResult.status_code != EVMC_INVALID_INSTRUCTION,
|
||||
"Invalid instruction."
|
||||
);
|
||||
if (noRevertInSource)
|
||||
solAssert(
|
||||
callResult.status_code != EVMC_REVERT,
|
||||
"SolidityEvmoneInterface: EVM One reverted"
|
||||
);
|
||||
// Out of gas errors are problematic because it is possible that the
|
||||
// optimizer makes them go away, making EVM state impossible to
|
||||
// compare in general.
|
||||
if (callResult.status_code == EVMC_OUT_OF_GAS)
|
||||
// Bail out on serious errors encountered during a call.
|
||||
if (YulEvmoneUtility{}.seriousCallError(callResult.status_code))
|
||||
return;
|
||||
|
||||
if (YulEvmoneUtility{}.checkSelfDestructs(hostContext, deployResult.create_address))
|
||||
return;
|
||||
ostringstream unoptimizedStorage;
|
||||
hostContext.print_storage_at(deployResult.create_address, unoptimizedStorage);
|
||||
solAssert(
|
||||
(callResult.status_code == EVMC_SUCCESS ||
|
||||
(!noRevertInSource && callResult.status_code == EVMC_REVERT) ||
|
||||
(!noInvalidInSource && callResult.status_code == EVMC_INVALID_INSTRUCTION)),
|
||||
"Unoptimised call failed."
|
||||
);
|
||||
ostringstream unoptimizedState;
|
||||
unoptimizedState << EVMHostPrinter{hostContext, deployResult.create_address}.state();
|
||||
|
||||
settings.runYulOptimiser = true;
|
||||
settings.optimizeStackAllocation = true;
|
||||
@ -123,6 +133,9 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset host before running optimised code.
|
||||
hostContext.reset();
|
||||
evmc::result deployResultOpt = YulEvmoneUtility{}.deployCode(optimisedByteCode, hostContext);
|
||||
solAssert(
|
||||
deployResultOpt.status_code == EVMC_SUCCESS,
|
||||
@ -135,12 +148,34 @@ DEFINE_PROTO_FUZZER(Program const& _input)
|
||||
callResultOpt.status_code != EVMC_REVERT,
|
||||
"SolidityEvmoneInterface: EVM One reverted"
|
||||
);
|
||||
if (YulEvmoneUtility{}.checkSelfDestructs(hostContext, deployResultOpt.create_address))
|
||||
return;
|
||||
ostringstream optimizedStorage;
|
||||
hostContext.print_storage_at(deployResultOpt.create_address, optimizedStorage);
|
||||
if (noInvalidInSource)
|
||||
solAssert(
|
||||
callResultOpt.status_code != EVMC_INVALID_INSTRUCTION,
|
||||
"Invalid instruction."
|
||||
);
|
||||
solAssert(
|
||||
unoptimizedStorage.str() == optimizedStorage.str(),
|
||||
"Storage of unoptimised and optimised stack reused code do not match."
|
||||
(callResultOpt.status_code == EVMC_SUCCESS ||
|
||||
(!noRevertInSource && callResultOpt.status_code == EVMC_REVERT) ||
|
||||
(!noInvalidInSource && callResultOpt.status_code == EVMC_INVALID_INSTRUCTION)),
|
||||
"Optimised call failed."
|
||||
);
|
||||
ostringstream optimizedState;
|
||||
optimizedState << EVMHostPrinter{hostContext, deployResultOpt.create_address}.state();
|
||||
|
||||
int64_t constexpr tolerance = 1000;
|
||||
if (callResult.gas_left > callResultOpt.gas_left)
|
||||
if (callResult.gas_left - callResultOpt.gas_left > tolerance)
|
||||
{
|
||||
cout << "Gas differential " << callResult.gas_left - callResultOpt.gas_left << endl;
|
||||
cout << "Unoptimised bytecode" << endl;
|
||||
cout << util::toHex(unoptimisedByteCode) << endl;
|
||||
cout << "Optimised bytecode" << endl;
|
||||
cout << util::toHex(optimisedByteCode) << endl;
|
||||
solAssert(false, "Optimised code consumed more than +1000 gas.");
|
||||
}
|
||||
|
||||
solAssert(
|
||||
unoptimizedState.str() == optimizedState.str(),
|
||||
"State of unoptimised and optimised stack reused code do not match."
|
||||
);
|
||||
}
|
||||
|
@ -79,11 +79,20 @@ evmc_message YulEvmoneUtility::callMessage(evmc_address _address)
|
||||
return call;
|
||||
}
|
||||
|
||||
bool YulEvmoneUtility::checkSelfDestructs(EVMHost& _host, evmc_address _address)
|
||||
bool YulEvmoneUtility::seriousCallError(evmc_status_code _code)
|
||||
{
|
||||
for (auto const& selfDestructRecord: _host.recorded_selfdestructs)
|
||||
if (selfDestructRecord.selfdestructed == _address)
|
||||
return true;
|
||||
return false;
|
||||
if (_code == EVMC_OUT_OF_GAS)
|
||||
return true;
|
||||
else if (_code == EVMC_STACK_OVERFLOW)
|
||||
return true;
|
||||
else if (_code == EVMC_STACK_UNDERFLOW)
|
||||
return true;
|
||||
else if (_code == EVMC_INTERNAL_ERROR)
|
||||
return true;
|
||||
else if (_code == EVMC_UNDEFINED_INSTRUCTION)
|
||||
return true;
|
||||
else if (_code == EVMC_INVALID_MEMORY_ACCESS)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,7 @@ struct YulEvmoneUtility
|
||||
static evmc::result deployCode(solidity::bytes const& _input, EVMHost& _host);
|
||||
/// @returns call message to be sent to @param _address.
|
||||
static evmc_message callMessage(evmc_address _address);
|
||||
/// @returns true if @param _address is in the list of self destructed
|
||||
/// accounts, false otherwise.
|
||||
static bool checkSelfDestructs(EVMHost& _host, evmc_address _address);
|
||||
/// @returns true if call result indicates a serious error, false otherwise.
|
||||
static bool seriousCallError(evmc_status_code _code);
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user