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:
|
case PushSubSize:
|
||||||
{
|
{
|
||||||
auto s = m_data[i.data()].size();
|
auto s = m_data[i.data()].size();
|
||||||
|
i.setPushedValue(u256(s));
|
||||||
byte b = max<unsigned>(1, dev::bytesRequired(s));
|
byte b = max<unsigned>(1, dev::bytesRequired(s));
|
||||||
ret.push_back((byte)Instruction::PUSH1 - 1 + b);
|
ret.push_back((byte)Instruction::PUSH1 - 1 + b);
|
||||||
ret.resize(ret.size() + b);
|
ret.resize(ret.size() + b);
|
||||||
|
@ -84,11 +84,17 @@ public:
|
|||||||
JumpType getJumpType() const { return m_jumpType; }
|
JumpType getJumpType() const { return m_jumpType; }
|
||||||
std::string getJumpTypeAsString() const;
|
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:
|
private:
|
||||||
AssemblyItemType m_type;
|
AssemblyItemType m_type;
|
||||||
u256 m_data;
|
u256 m_data;
|
||||||
SourceLocation m_location;
|
SourceLocation m_location;
|
||||||
JumpType m_jumpType = JumpType::Ordinary;
|
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>;
|
using AssemblyItems = std::vector<AssemblyItem>;
|
||||||
|
134
GasMeter.cpp
134
GasMeter.cpp
@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "GasMeter.h"
|
#include "GasMeter.h"
|
||||||
|
#include <libevmasm/KnownState.h>
|
||||||
#include <libevmcore/Params.h>
|
#include <libevmcore/Params.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
@ -41,55 +42,162 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co
|
|||||||
|
|
||||||
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item)
|
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item)
|
||||||
{
|
{
|
||||||
switch (_item.type()) {
|
GasConsumption gas;
|
||||||
|
switch (_item.type())
|
||||||
|
{
|
||||||
case Push:
|
case Push:
|
||||||
case PushTag:
|
case PushTag:
|
||||||
return runGas(Instruction::PUSH1);
|
case PushData:
|
||||||
|
case PushString:
|
||||||
|
case PushSub:
|
||||||
|
case PushSubSize:
|
||||||
|
case PushProgramSize:
|
||||||
|
gas = runGas(Instruction::PUSH1);
|
||||||
|
break;
|
||||||
case Tag:
|
case Tag:
|
||||||
return runGas(Instruction::JUMPDEST);
|
gas = runGas(Instruction::JUMPDEST);
|
||||||
|
break;
|
||||||
case Operation:
|
case Operation:
|
||||||
{
|
{
|
||||||
GasConsumption gas = runGas(_item.instruction());
|
ExpressionClasses& classes = m_state->expressionClasses();
|
||||||
|
gas = runGas(_item.instruction());
|
||||||
switch (_item.instruction())
|
switch (_item.instruction())
|
||||||
{
|
{
|
||||||
case Instruction::SSTORE:
|
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;
|
break;
|
||||||
|
}
|
||||||
case Instruction::SLOAD:
|
case Instruction::SLOAD:
|
||||||
gas += c_sloadGas;
|
gas += c_sloadGas;
|
||||||
break;
|
break;
|
||||||
case Instruction::MSTORE:
|
|
||||||
case Instruction::MSTORE8:
|
|
||||||
case Instruction::MLOAD:
|
|
||||||
case Instruction::RETURN:
|
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:
|
case Instruction::SHA3:
|
||||||
|
gas = c_sha3Gas;
|
||||||
|
gas += wordGas(c_sha3WordGas, m_state->relativeStackElement(-1));
|
||||||
|
gas += memoryGas(0, -1);
|
||||||
|
break;
|
||||||
case Instruction::CALLDATACOPY:
|
case Instruction::CALLDATACOPY:
|
||||||
case Instruction::CODECOPY:
|
case Instruction::CODECOPY:
|
||||||
|
gas += memoryGas(0, -2);
|
||||||
|
gas += wordGas(c_copyGas, m_state->relativeStackElement(-2));
|
||||||
|
break;
|
||||||
case Instruction::EXTCODECOPY:
|
case Instruction::EXTCODECOPY:
|
||||||
|
gas += memoryGas(-1, -3);
|
||||||
|
gas += wordGas(c_copyGas, m_state->relativeStackElement(-3));
|
||||||
|
break;
|
||||||
case Instruction::LOG0:
|
case Instruction::LOG0:
|
||||||
case Instruction::LOG1:
|
case Instruction::LOG1:
|
||||||
case Instruction::LOG2:
|
case Instruction::LOG2:
|
||||||
case Instruction::LOG3:
|
case Instruction::LOG3:
|
||||||
case Instruction::LOG4:
|
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::CALL:
|
||||||
case Instruction::CALLCODE:
|
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:
|
case Instruction::CREATE:
|
||||||
|
gas = c_createGas;
|
||||||
|
gas += memoryGas(-1, -2);
|
||||||
|
break;
|
||||||
case Instruction::EXP:
|
case Instruction::EXP:
|
||||||
// @todo logic can be improved
|
gas = c_expGas;
|
||||||
gas = GasConsumption::infinite();
|
if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1)))
|
||||||
|
gas += c_expByteGas * (32 - (h256(*value).firstBitSet() / 8));
|
||||||
|
else
|
||||||
|
gas = GasConsumption::infinite();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return gas;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
gas = GasConsumption::infinite();
|
||||||
break;
|
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)
|
GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction)
|
||||||
|
27
GasMeter.h
27
GasMeter.h
@ -22,6 +22,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
|
#include <libevmasm/ExpressionClasses.h>
|
||||||
#include <libevmasm/AssemblyItem.h>
|
#include <libevmasm/AssemblyItem.h>
|
||||||
|
|
||||||
namespace dev
|
namespace dev
|
||||||
@ -29,8 +30,13 @@ namespace dev
|
|||||||
namespace eth
|
namespace eth
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class KnownState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class that helps computing the maximum gas consumption for instructions.
|
* 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
|
class GasMeter
|
||||||
{
|
{
|
||||||
@ -47,11 +53,28 @@ public:
|
|||||||
bool isInfinite;
|
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);
|
GasConsumption estimateMax(AssemblyItem const& _item);
|
||||||
|
|
||||||
private:
|
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);
|
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)
|
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)
|
if (_consumption.isInfinite)
|
||||||
return _str << "inf";
|
return _str << "inf";
|
||||||
else
|
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)
|
else if (_item.type() != Operation)
|
||||||
{
|
{
|
||||||
assertThrow(_item.deposit() == 1, InvalidDeposit, "");
|
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
|
else
|
||||||
{
|
{
|
||||||
@ -230,7 +234,12 @@ ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation
|
|||||||
return m_stackElements.at(_stackHeight);
|
return m_stackElements.at(_stackHeight);
|
||||||
// Stack element not found (not assigned yet), create new unknown equivalence class.
|
// Stack element not found (not assigned yet), create new unknown equivalence class.
|
||||||
return m_stackElements[_stackHeight] =
|
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()
|
void KnownState::clearTagUnions()
|
||||||
|
@ -111,6 +111,8 @@ public:
|
|||||||
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
||||||
/// one if it does not exist yet).
|
/// one if it does not exist yet).
|
||||||
Id stackElement(int _stackHeight, SourceLocation const& _location);
|
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
|
/// @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.
|
/// 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; }
|
std::map<int, Id> const& stackElements() const { return m_stackElements; }
|
||||||
ExpressionClasses& expressionClasses() const { return *m_expressionClasses; }
|
ExpressionClasses& expressionClasses() const { return *m_expressionClasses; }
|
||||||
|
|
||||||
|
std::map<Id, Id> const& storageContent() const { return m_storageContent; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
||||||
void setStackElement(int _stackHeight, Id _class);
|
void setStackElement(int _stackHeight, Id _class);
|
||||||
|
Loading…
Reference in New Issue
Block a user