chriseth
10 years ago
9 changed files with 510 additions and 4 deletions
@ -0,0 +1,104 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file GasMeter.cpp
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "GasMeter.h" |
|||
#include <libevmcore/Params.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption const& _other) |
|||
{ |
|||
isInfinite = isInfinite || _other.isInfinite; |
|||
if (isInfinite) |
|||
return *this; |
|||
bigint v = bigint(value) + _other.value; |
|||
if (v > std::numeric_limits<u256>::max()) |
|||
isInfinite = true; |
|||
else |
|||
value = u256(v); |
|||
return *this; |
|||
} |
|||
|
|||
GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) { |
|||
case Push: |
|||
case PushTag: |
|||
return runGas(Instruction::PUSH1); |
|||
case Tag: |
|||
return runGas(Instruction::JUMPDEST); |
|||
case Operation: |
|||
{ |
|||
GasConsumption gas = runGas(_item.instruction()); |
|||
switch (_item.instruction()) |
|||
{ |
|||
case Instruction::SSTORE: |
|||
// @todo logic can be improved
|
|||
gas += c_sstoreSetGas; |
|||
break; |
|||
case Instruction::SLOAD: |
|||
gas += c_sloadGas; |
|||
break; |
|||
case Instruction::MSTORE: |
|||
case Instruction::MSTORE8: |
|||
case Instruction::MLOAD: |
|||
case Instruction::RETURN: |
|||
case Instruction::SHA3: |
|||
case Instruction::CALLDATACOPY: |
|||
case Instruction::CODECOPY: |
|||
case Instruction::EXTCODECOPY: |
|||
case Instruction::LOG0: |
|||
case Instruction::LOG1: |
|||
case Instruction::LOG2: |
|||
case Instruction::LOG3: |
|||
case Instruction::LOG4: |
|||
case Instruction::CALL: |
|||
case Instruction::CALLCODE: |
|||
case Instruction::CREATE: |
|||
case Instruction::EXP: |
|||
// @todo logic can be improved
|
|||
gas = GasConsumption::infinite(); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
return gas; |
|||
break; |
|||
} |
|||
default: |
|||
break; |
|||
} |
|||
|
|||
return GasConsumption::infinite(); |
|||
} |
|||
|
|||
GasMeter::GasConsumption GasMeter::runGas(Instruction _instruction) |
|||
{ |
|||
if (_instruction == Instruction::JUMPDEST) |
|||
return GasConsumption(1); |
|||
|
|||
int tier = instructionInfo(_instruction).gasPriceTier; |
|||
return tier == InvalidTier ? GasConsumption::infinite() : c_tierStepGas[tier]; |
|||
} |
|||
|
|||
|
@ -0,0 +1,67 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file GasMeter.cpp
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <ostream> |
|||
#include <libevmasm/AssemblyItem.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
/**
|
|||
* Class that helps computing the maximum gas consumption for instructions. |
|||
*/ |
|||
class GasMeter |
|||
{ |
|||
public: |
|||
struct GasConsumption |
|||
{ |
|||
GasConsumption(u256 _value = 0, bool _infinite = false): value(_value), isInfinite(_infinite) {} |
|||
static GasConsumption infinite() { return GasConsumption(0, true); } |
|||
|
|||
GasConsumption& operator+=(GasConsumption const& _otherS); |
|||
std::ostream& operator<<(std::ostream& _str) const; |
|||
|
|||
u256 value; |
|||
bool isInfinite; |
|||
}; |
|||
|
|||
/// Returns an upper bound on the gas consumed by the given instruction.
|
|||
GasConsumption estimateMax(AssemblyItem const& _item); |
|||
|
|||
private: |
|||
static GasConsumption runGas(Instruction _instruction); |
|||
}; |
|||
|
|||
inline std::ostream& operator<<(std::ostream& _str, GasMeter::GasConsumption const& _consumption) |
|||
{ |
|||
if (_consumption.isInfinite) |
|||
return _str << "inf"; |
|||
else |
|||
return _str << _consumption.value; |
|||
} |
|||
|
|||
|
|||
} |
|||
} |
@ -0,0 +1,108 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Gas consumption estimator working alongside the AST. |
|||
*/ |
|||
|
|||
#include "StructuralGasEstimator.h" |
|||
#include <map> |
|||
#include <functional> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/ASTVisitor.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::solidity; |
|||
|
|||
map<ASTNode const*, GasMeter::GasConsumption[2]> StructuralGasEstimator::performEstimation( |
|||
AssemblyItems const& _items, |
|||
vector<ASTNode const*> const& _ast |
|||
) |
|||
{ |
|||
map<SourceLocation, GasMeter::GasConsumption> particularCosts; |
|||
GasMeter meter; |
|||
|
|||
for (auto const& item: _items) |
|||
particularCosts[item.getLocation()] += meter.estimateMax(item); |
|||
|
|||
map<ASTNode const*, GasMeter::GasConsumption[2]> gasCosts; |
|||
auto onNode = [&](ASTNode const& _node) |
|||
{ |
|||
gasCosts[&_node][0] = gasCosts[&_node][1] = particularCosts[_node.getLocation()]; |
|||
return true; |
|||
}; |
|||
auto onEdge = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
gasCosts[&_parent][1] += gasCosts[&_child][1]; |
|||
}; |
|||
ASTReduce folder(onNode, onEdge); |
|||
for (ASTNode const* ast: _ast) |
|||
ast->accept(folder); |
|||
|
|||
return gasCosts; |
|||
} |
|||
|
|||
map<ASTNode const*, GasMeter::GasConsumption> StructuralGasEstimator::breakToStatementLevel( |
|||
map<ASTNode const*, GasMeter::GasConsumption[2]> const& _gasCosts, |
|||
vector<ASTNode const*> const& _roots |
|||
) |
|||
{ |
|||
// first pass: statementDepth[node] is the distance from the deepend statement to node
|
|||
// in direction of the tree root (or undefined if not possible)
|
|||
map<ASTNode const*, int> statementDepth; |
|||
auto onNodeFirstPass = [&](ASTNode const& _node) |
|||
{ |
|||
if (dynamic_cast<Statement const*>(&_node)) |
|||
statementDepth[&_node] = 0; |
|||
return true; |
|||
}; |
|||
auto onEdgeFirstPass = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
if (statementDepth.count(&_child)) |
|||
statementDepth[&_parent] = max(statementDepth[&_parent], statementDepth[&_child] + 1); |
|||
}; |
|||
ASTReduce firstPass(onNodeFirstPass, onEdgeFirstPass); |
|||
for (ASTNode const* node: _roots) |
|||
node->accept(firstPass); |
|||
|
|||
// we use the location of a node if
|
|||
// - its statement depth is 0 or
|
|||
// - its statement depth is undefined but the parent's statement depth is at least 1
|
|||
map<ASTNode const*, GasMeter::GasConsumption> gasCosts; |
|||
auto onNodeSecondPass = [&](ASTNode const& _node) |
|||
{ |
|||
return statementDepth.count(&_node); |
|||
}; |
|||
auto onEdgeSecondPass = [&](ASTNode const& _parent, ASTNode const& _child) |
|||
{ |
|||
bool useNode = false; |
|||
if (statementDepth.count(&_child)) |
|||
useNode = statementDepth[&_child] == 0; |
|||
else |
|||
useNode = statementDepth.count(&_parent) && statementDepth.at(&_parent) > 0; |
|||
if (useNode) |
|||
gasCosts[&_child] = _gasCosts.at(&_child)[1]; |
|||
}; |
|||
ASTReduce secondPass(onNodeSecondPass, onEdgeSecondPass); |
|||
for (ASTNode const* node: _roots) |
|||
node->accept(secondPass); |
|||
// gasCosts should only contain non-overlapping locations
|
|||
return gasCosts; |
|||
} |
@ -0,0 +1,59 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Gas consumption estimator working alongside the AST. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <libsolidity/ASTForward.h> |
|||
#include <libevmasm/GasMeter.h> |
|||
#include <libevmasm/Assembly.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
|
|||
class StructuralGasEstimator |
|||
{ |
|||
public: |
|||
using ASTGasConsumption = std::map<ASTNode const*, eth::GasMeter::GasConsumption>; |
|||
|
|||
/// Estimates the gas consumption for every assembly item in the given assembly and stores
|
|||
/// it by source location.
|
|||
/// @returns a mapping from each AST node to a pair of its particular and syntactically accumulated gas costs.
|
|||
std::map<ASTNode const*, eth::GasMeter::GasConsumption[2]> performEstimation( |
|||
eth::AssemblyItems const& _items, |
|||
std::vector<ASTNode const*> const& _ast |
|||
); |
|||
/// @returns a mapping from nodes with non-overlapping source locations to gas consumptions such that
|
|||
/// the following source locations are part of the mapping:
|
|||
/// 1. source locations of statements that do not contain other statements
|
|||
/// 2. maximal source locations that do not overlap locations coming from the first rule
|
|||
ASTGasConsumption breakToStatementLevel( |
|||
std::map<ASTNode const*, eth::GasMeter::GasConsumption[2]> const& _gasCosts, |
|||
std::vector<ASTNode const*> const& _roots |
|||
); |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,98 @@ |
|||
/*
|
|||
This file is part of cpp-ethereum. |
|||
|
|||
cpp-ethereum is free software: you can redistribute it and/or modify |
|||
it under the terms of the GNU General Public License as published by |
|||
the Free Software Foundation, either version 3 of the License, or |
|||
(at your option) any later version. |
|||
|
|||
cpp-ethereum is distributed in the hope that it will be useful, |
|||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
GNU General Public License for more details. |
|||
|
|||
You should have received a copy of the GNU General Public License |
|||
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/**
|
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Unit tests for the gas estimator. |
|||
*/ |
|||
|
|||
#include <test/libsolidity/solidityExecutionFramework.h> |
|||
#include <libsolidity/AST.h> |
|||
#include <libsolidity/StructuralGasEstimator.h> |
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev::eth; |
|||
using namespace dev::solidity; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace solidity |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
class GasMeterTestFramework: public ExecutionFramework |
|||
{ |
|||
public: |
|||
GasMeterTestFramework() { } |
|||
void compile(string const& _sourceCode) |
|||
{ |
|||
m_compiler.setSource(_sourceCode); |
|||
ETH_TEST_REQUIRE_NO_THROW(m_compiler.compile(), "Compiling contract failed"); |
|||
|
|||
StructuralGasEstimator estimator; |
|||
AssemblyItems const* items = m_compiler.getRuntimeAssemblyItems(""); |
|||
ASTNode const& sourceUnit = m_compiler.getAST(); |
|||
BOOST_REQUIRE(items != nullptr); |
|||
m_gasCosts = estimator.breakToStatementLevel( |
|||
estimator.performEstimation(*items, vector<ASTNode const*>({&sourceUnit})), |
|||
{&sourceUnit} |
|||
); |
|||
} |
|||
|
|||
protected: |
|||
dev::solidity::CompilerStack m_compiler; |
|||
map<ASTNode const*, eth::GasMeter::GasConsumption> m_gasCosts; |
|||
}; |
|||
|
|||
BOOST_FIXTURE_TEST_SUITE(GasMeterTests, GasMeterTestFramework) |
|||
|
|||
BOOST_AUTO_TEST_CASE(non_overlapping_filtered_costs) |
|||
{ |
|||
char const* sourceCode = R"( |
|||
contract test { |
|||
bytes x; |
|||
function f(uint a) returns (uint b) { |
|||
x.length = a; |
|||
for (; a < 200; ++a) { |
|||
x[a] = 9; |
|||
b = a * a; |
|||
} |
|||
return f(a - 1); |
|||
} |
|||
} |
|||
)"; |
|||
compile(sourceCode); |
|||
for (auto first = m_gasCosts.cbegin(); first != m_gasCosts.cend(); ++first) |
|||
{ |
|||
auto second = first; |
|||
for (++second; second != m_gasCosts.cend(); ++second) |
|||
if (first->first->getLocation().intersects(second->first->getLocation())) |
|||
{ |
|||
BOOST_CHECK_MESSAGE(false, "Source locations should not overlap!"); |
|||
SourceReferenceFormatter::printSourceLocation(cout, first->first->getLocation(), m_compiler.getScanner()); |
|||
SourceReferenceFormatter::printSourceLocation(cout, second->first->getLocation(), m_compiler.getScanner()); |
|||
} |
|||
} |
|||
} |
|||
|
|||
BOOST_AUTO_TEST_SUITE_END() |
|||
|
|||
} |
|||
} |
|||
} |
Loading…
Reference in new issue