mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Gas estimation taking known state into account.
This commit is contained in:
parent
3ecd54a835
commit
d015945a1d
@ -431,6 +431,7 @@ bytes Assembly::assemble() const
|
||||
case PushSubSize:
|
||||
{
|
||||
auto s = m_data[i.data()].size();
|
||||
i.setPushedValue(u256(s));
|
||||
byte b = max<unsigned>(1, dev::bytesRequired(s));
|
||||
ret.push_back((byte)Instruction::PUSH1 - 1 + b);
|
||||
ret.resize(ret.size() + b);
|
||||
|
@ -84,11 +84,17 @@ public:
|
||||
JumpType getJumpType() const { return m_jumpType; }
|
||||
std::string getJumpTypeAsString() const;
|
||||
|
||||
void setPushedValue(u256 const& _value) const { m_pushedValue = std::make_shared<u256>(_value); }
|
||||
u256 const* pushedValue() const { return m_pushedValue.get(); }
|
||||
|
||||
private:
|
||||
AssemblyItemType m_type;
|
||||
u256 m_data;
|
||||
SourceLocation m_location;
|
||||
JumpType m_jumpType = JumpType::Ordinary;
|
||||
/// Pushed value for operations with data to be determined during assembly stage,
|
||||
/// e.g. PushSubSize, PushTag, PushSub, etc.
|
||||
mutable std::shared_ptr<u256> m_pushedValue;
|
||||
};
|
||||
|
||||
using AssemblyItems = std::vector<AssemblyItem>;
|
||||
|
134
GasMeter.cpp
134
GasMeter.cpp
@ -20,6 +20,7 @@
|
||||
*/
|
||||
|
||||
#include "GasMeter.h"
|
||||
#include <libevmasm/KnownState.h>
|
||||
#include <libevmcore/Params.h>
|
||||
|
||||
using namespace std;
|
||||
@ -41,55 +42,162 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co
|
||||
|
||||
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item)
|
||||
{
|
||||
switch (_item.type()) {
|
||||
GasConsumption gas;
|
||||
switch (_item.type())
|
||||
{
|
||||
case Push:
|
||||
case PushTag:
|
||||
return runGas(Instruction::PUSH1);
|
||||
case PushData:
|
||||
case PushString:
|
||||
case PushSub:
|
||||
case PushSubSize:
|
||||
case PushProgramSize:
|
||||
gas = runGas(Instruction::PUSH1);
|
||||
break;
|
||||
case Tag:
|
||||
return runGas(Instruction::JUMPDEST);
|
||||
gas = runGas(Instruction::JUMPDEST);
|
||||
break;
|
||||
case Operation:
|
||||
{
|
||||
GasConsumption gas = runGas(_item.instruction());
|
||||
ExpressionClasses& classes = m_state->expressionClasses();
|
||||
gas = runGas(_item.instruction());
|
||||
switch (_item.instruction())
|
||||
{
|
||||
case Instruction::SSTORE:
|
||||
// @todo logic can be improved
|
||||
gas += c_sstoreSetGas;
|
||||
{
|
||||
ExpressionClasses::Id slot = m_state->relativeStackElement(0);
|
||||
ExpressionClasses::Id value = m_state->relativeStackElement(-1);
|
||||
if (classes.knownZero(value) || (
|
||||
m_state->storageContent().count(slot) &&
|
||||
classes.knownNonZero(m_state->storageContent().at(slot))
|
||||
))
|
||||
gas += c_sstoreResetGas; //@todo take refunds into account
|
||||
else
|
||||
gas += c_sstoreSetGas;
|
||||
break;
|
||||
}
|
||||
case Instruction::SLOAD:
|
||||
gas += c_sloadGas;
|
||||
break;
|
||||
case Instruction::MSTORE:
|
||||
case Instruction::MSTORE8:
|
||||
case Instruction::MLOAD:
|
||||
case Instruction::RETURN:
|
||||
gas += memoryGas(0, -1);
|
||||
break;
|
||||
case Instruction::MLOAD:
|
||||
case Instruction::MSTORE:
|
||||
gas += memoryGas(classes.find(eth::Instruction::ADD, {
|
||||
m_state->relativeStackElement(0),
|
||||
classes.find(AssemblyItem(32))
|
||||
}));
|
||||
break;
|
||||
case Instruction::MSTORE8:
|
||||
gas += memoryGas(classes.find(eth::Instruction::ADD, {
|
||||
m_state->relativeStackElement(0),
|
||||
classes.find(AssemblyItem(1))
|
||||
}));
|
||||
break;
|
||||
case Instruction::SHA3:
|
||||
gas = c_sha3Gas;
|
||||
gas += wordGas(c_sha3WordGas, m_state->relativeStackElement(-1));
|
||||
gas += memoryGas(0, -1);
|
||||
break;
|
||||
case Instruction::CALLDATACOPY:
|
||||
case Instruction::CODECOPY:
|
||||
gas += memoryGas(0, -2);
|
||||
gas += wordGas(c_copyGas, m_state->relativeStackElement(-2));
|
||||
break;
|
||||
case Instruction::EXTCODECOPY:
|
||||
gas += memoryGas(-1, -3);
|
||||
gas += wordGas(c_copyGas, m_state->relativeStackElement(-3));
|
||||
break;
|
||||
case Instruction::LOG0:
|
||||
case Instruction::LOG1:
|
||||
case Instruction::LOG2:
|
||||
case Instruction::LOG3:
|
||||
case Instruction::LOG4:
|
||||
{
|
||||
unsigned n = unsigned(_item.instruction()) - unsigned(Instruction::LOG0);
|
||||
gas = c_logGas + c_logTopicGas * n;
|
||||
gas += memoryGas(0, -1);
|
||||
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
|
||||
gas += c_logDataGas * (*value);
|
||||
else
|
||||
gas = GasConsumption::infinite();
|
||||
break;
|
||||
}
|
||||
case Instruction::CALL:
|
||||
case Instruction::CALLCODE:
|
||||
gas = c_callGas;
|
||||
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0)))
|
||||
gas += (*value);
|
||||
else
|
||||
gas = GasConsumption::infinite();
|
||||
if (_item.instruction() != Instruction::CALLCODE)
|
||||
gas += c_callNewAccountGas; // We very rarely know whether the address exists.
|
||||
if (!classes.knownZero(m_state->relativeStackElement(-2)))
|
||||
gas += c_callValueTransferGas;
|
||||
gas += memoryGas(-3, -4);
|
||||
gas += memoryGas(-5, -6);
|
||||
break;
|
||||
case Instruction::CREATE:
|
||||
gas = c_createGas;
|
||||
gas += memoryGas(-1, -2);
|
||||
break;
|
||||
case Instruction::EXP:
|
||||
// @todo logic can be improved
|
||||
gas = GasConsumption::infinite();
|
||||
gas = c_expGas;
|
||||
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
|
||||
gas += c_expByteGas * (32 - (h256(*value).firstBitSet() / 8));
|
||||
else
|
||||
gas = GasConsumption::infinite();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return gas;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
gas = GasConsumption::infinite();
|
||||
break;
|
||||
}
|
||||
|
||||
return GasConsumption::infinite();
|
||||
m_state->feedItem(_item);
|
||||
return gas;
|
||||
}
|
||||
|
||||
GasMeter::GasConsumption GasMeter::wordGas(u256 const& _multiplier, ExpressionClasses::Id _position)
|
||||
{
|
||||
u256 const* value = m_state->expressionClasses().knownConstant(_position);
|
||||
if (!value)
|
||||
return GasConsumption::infinite();
|
||||
return GasConsumption(_multiplier * ((*value + 31) / 32));
|
||||
}
|
||||
|
||||
GasMeter::GasConsumption GasMeter::memoryGas(ExpressionClasses::Id _position)
|
||||
{
|
||||
u256 const* value = m_state->expressionClasses().knownConstant(_position);
|
||||
if (!value)
|
||||
return GasConsumption::infinite();
|
||||
if (*value < m_largestMemoryAccess)
|
||||
return GasConsumption(u256(0));
|
||||
u256 previous = m_largestMemoryAccess;
|
||||
m_largestMemoryAccess = *value;
|
||||
auto memGas = [](u256 const& pos) -> u256
|
||||
{
|
||||
u256 size = (pos + 31) / 32;
|
||||
return c_memoryGas * size + size * size / c_quadCoeffDiv;
|
||||
};
|
||||
return memGas(*value) - memGas(previous);
|
||||
}
|
||||
|
||||
GasMeter::GasConsumption GasMeter::memoryGas(int _stackPosOffset, int _stackPosSize)
|
||||
{
|
||||
ExpressionClasses& classes = m_state->expressionClasses();
|
||||
if (classes.knownZero(m_state->relativeStackElement(_stackPosSize)))
|
||||
return GasConsumption(0);
|
||||
else
|
||||
return memoryGas(classes.find(eth::Instruction::ADD, {
|
||||
m_state->relativeStackElement(_stackPosOffset),
|
||||
m_state->relativeStackElement(_stackPosSize)
|
||||
}));
|
||||
}
|
||||
|
||||
GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction)
|
||||
|
27
GasMeter.h
27
GasMeter.h
@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
#include <libevmasm/ExpressionClasses.h>
|
||||
#include <libevmasm/AssemblyItem.h>
|
||||
|
||||
namespace dev
|
||||
@ -29,8 +30,13 @@ namespace dev
|
||||
namespace eth
|
||||
{
|
||||
|
||||
class KnownState;
|
||||
|
||||
/**
|
||||
* Class that helps computing the maximum gas consumption for instructions.
|
||||
* Has to be initialized with a certain known state that will be automatically updated for
|
||||
* each call to estimateMax. These calls have to supply strictly subsequent AssemblyItems.
|
||||
* A new gas meter has to be constructed (with a new state) for control flow changes.
|
||||
*/
|
||||
class GasMeter
|
||||
{
|
||||
@ -47,11 +53,28 @@ public:
|
||||
bool isInfinite;
|
||||
};
|
||||
|
||||
/// Returns an upper bound on the gas consumed by the given instruction.
|
||||
/// Constructs a new gas meter given the current state.
|
||||
GasMeter(std::shared_ptr<KnownState> const& _state): m_state(_state) {}
|
||||
|
||||
/// @returns an upper bound on the gas consumed by the given instruction and updates
|
||||
/// the state.
|
||||
GasConsumption estimateMax(AssemblyItem const& _item);
|
||||
|
||||
private:
|
||||
/// @returns _multiplier * (_value + 31) / 32, if _value is a known constant and infinite otherwise.
|
||||
GasConsumption wordGas(u256 const& _multiplier, ExpressionClasses::Id _value);
|
||||
/// @returns the gas needed to access the given memory position.
|
||||
/// @todo this assumes that memory was never accessed before and thus over-estimates gas usage.
|
||||
GasConsumption memoryGas(ExpressionClasses::Id _position);
|
||||
/// @returns the memory gas for accessing the memory at a specific offset for a number of bytes
|
||||
/// given as values on the stack at the given relative positions.
|
||||
GasConsumption memoryGas(int _stackPosOffset, int _stackPosSize);
|
||||
|
||||
static GasConsumption runGas(Instruction _instruction);
|
||||
|
||||
std::shared_ptr<KnownState> m_state;
|
||||
/// Largest point where memory was accessed since the creation of this object.
|
||||
u256 m_largestMemoryAccess;
|
||||
};
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption)
|
||||
@ -59,7 +82,7 @@ inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption con
|
||||
if (_consumption.isInfinite)
|
||||
return _str << "inf";
|
||||
else
|
||||
return _str << _consumption.value;
|
||||
return _str << std::dec << _consumption.value;
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,7 +92,11 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
|
||||
else if (_item.type() != Operation)
|
||||
{
|
||||
assertThrow(_item.deposit() == 1, InvalidDeposit, "");
|
||||
setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem));
|
||||
if (_item.pushedValue())
|
||||
// only available after assembly stage, should not be used for optimisation
|
||||
setStackElement(++m_stackHeight, m_expressionClasses->find(*_item.pushedValue()));
|
||||
else
|
||||
setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -230,7 +234,12 @@ ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation
|
||||
return m_stackElements.at(_stackHeight);
|
||||
// Stack element not found (not assigned yet), create new unknown equivalence class.
|
||||
return m_stackElements[_stackHeight] =
|
||||
m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location));
|
||||
m_expressionClasses->find(AssemblyItem(UndefinedItem, _stackHeight, _location));
|
||||
}
|
||||
|
||||
KnownState::Id KnownState::relativeStackElement(int _stackOffset, SourceLocation const& _location)
|
||||
{
|
||||
return stackElement(m_stackHeight + _stackOffset, _location);
|
||||
}
|
||||
|
||||
void KnownState::clearTagUnions()
|
||||
|
@ -111,6 +111,8 @@ public:
|
||||
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
||||
/// one if it does not exist yet).
|
||||
Id stackElement(int _stackHeight, SourceLocation const& _location);
|
||||
/// @returns the stackElement relative to the current stack height.
|
||||
Id relativeStackElement(int _stackOffset, SourceLocation const& _location = SourceLocation());
|
||||
|
||||
/// @returns its set of tags if the given expression class is a known tag union; returns a set
|
||||
/// containing the tag if it is a PushTag expression and the empty set otherwise.
|
||||
@ -123,6 +125,8 @@ public:
|
||||
std::map<int, Id> const& stackElements() const { return m_stackElements; }
|
||||
ExpressionClasses& expressionClasses() const { return *m_expressionClasses; }
|
||||
|
||||
std::map<Id, Id> const& storageContent() const { return m_storageContent; }
|
||||
|
||||
private:
|
||||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
||||
void setStackElement(int _stackHeight, Id _class);
|
||||
|
Loading…
Reference in New Issue
Block a user