mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Prevent libraries from being called.
This commit is contained in:
@@ -51,6 +51,7 @@ void Compiler::compileClone(
|
||||
map<ContractDefinition const*, eth::Assembly const*> const& _contracts
|
||||
)
|
||||
{
|
||||
solAssert(!_contract.isLibrary(), "");
|
||||
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize);
|
||||
ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize);
|
||||
m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts);
|
||||
|
||||
@@ -174,6 +174,9 @@ public:
|
||||
eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); }
|
||||
/// Appends the address (virtual, will be filled in by linker) of a library.
|
||||
void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); }
|
||||
/// Appends a zero-address that can be replaced by something else at deploy time (if the
|
||||
/// position in bytecode is known).
|
||||
void appendDeployTimeAddress() { m_asm->append(eth::PushDeployTimeAddress); }
|
||||
/// Resets the stack of visited nodes with a new stack having only @c _node
|
||||
void resetVisitedNodes(ASTNode const* _node);
|
||||
/// Pops the stack of visited nodes
|
||||
|
||||
@@ -64,6 +64,12 @@ void ContractCompiler::compileContract(
|
||||
)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||
|
||||
if (_contract.isLibrary())
|
||||
// Check whether this is a call (true) or a delegatecall (false).
|
||||
// This has to be the first code in the contract.
|
||||
appendDelegatecallCheck();
|
||||
|
||||
initializeContext(_contract, _contracts);
|
||||
appendFunctionSelector(_contract);
|
||||
appendMissingFunctions();
|
||||
@@ -75,8 +81,13 @@ size_t ContractCompiler::compileConstructor(
|
||||
)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||
initializeContext(_contract, _contracts);
|
||||
return packIntoContractCreator(_contract);
|
||||
if (_contract.isLibrary())
|
||||
return deployLibrary(_contract);
|
||||
else
|
||||
{
|
||||
initializeContext(_contract, _contracts);
|
||||
return packIntoContractCreator(_contract);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ContractCompiler::compileClone(
|
||||
@@ -122,6 +133,7 @@ void ContractCompiler::appendCallValueCheck()
|
||||
|
||||
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(!_contract.isLibrary(), "Tried to initialize library.");
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||
// Determine the arguments that are used for the base constructors.
|
||||
std::vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts;
|
||||
@@ -163,6 +175,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c
|
||||
size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(!!m_runtimeCompiler, "");
|
||||
solAssert(!_contract.isLibrary(), "Tried to use contract creator or library.");
|
||||
|
||||
appendInitAndConstructorCode(_contract);
|
||||
|
||||
@@ -188,6 +201,34 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
|
||||
return m_context.runtimeSub();
|
||||
}
|
||||
|
||||
size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(!!m_runtimeCompiler, "");
|
||||
solAssert(_contract.isLibrary(), "Tried to deploy contract as library.");
|
||||
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _contract);
|
||||
|
||||
solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered");
|
||||
m_context.pushSubroutineSize(m_context.runtimeSub());
|
||||
m_context.pushSubroutineOffset(m_context.runtimeSub());
|
||||
m_context.appendInlineAssembly(R"(
|
||||
{
|
||||
// If code starts at 11, an mstore(0) writes to the full PUSH20 plus data
|
||||
// without the need for a shift.
|
||||
let codepos := 11
|
||||
codecopy(codepos, subOffset, subSize)
|
||||
// Check that the first opcode is a PUSH20
|
||||
switch eq(0x73, byte(0, mload(codepos)))
|
||||
case 0 { invalid() }
|
||||
mstore(0, address())
|
||||
mstore8(codepos, 0x73)
|
||||
return(codepos, subSize)
|
||||
}
|
||||
)", {"subSize", "subOffset"});
|
||||
|
||||
return m_context.runtimeSub();
|
||||
}
|
||||
|
||||
void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor)
|
||||
{
|
||||
CompilerContext::LocationSetter locationSetter(m_context, _constructor);
|
||||
@@ -244,11 +285,26 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
|
||||
_constructor.accept(*this);
|
||||
}
|
||||
|
||||
void ContractCompiler::appendDelegatecallCheck()
|
||||
{
|
||||
// Special constant that will be replaced by the address at deploy time.
|
||||
// At compilation time, this is just "PUSH20 00...000".
|
||||
m_context.appendDeployTimeAddress();
|
||||
m_context << Instruction::ADDRESS << Instruction::EQ;
|
||||
// The result on the stack is
|
||||
// "We have not been called via DELEGATECALL".
|
||||
}
|
||||
|
||||
void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contract)
|
||||
{
|
||||
map<FixedHash<4>, FunctionTypePointer> interfaceFunctions = _contract.interfaceFunctions();
|
||||
map<FixedHash<4>, const eth::AssemblyItem> callDataUnpackerEntryPoints;
|
||||
|
||||
if (_contract.isLibrary())
|
||||
{
|
||||
solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected.");
|
||||
}
|
||||
|
||||
FunctionDefinition const* fallback = _contract.fallbackFunction();
|
||||
eth::AssemblyItem notFound = m_context.newTag();
|
||||
// directly jump to fallback if the data is too short to contain a function selector
|
||||
@@ -260,7 +316,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
if (!interfaceFunctions.empty())
|
||||
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);
|
||||
|
||||
// stack now is: 1 0 <funhash>
|
||||
// stack now is: <can-call-non-view-functions>? <funhash>
|
||||
for (auto const& it: interfaceFunctions)
|
||||
{
|
||||
callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag()));
|
||||
@@ -272,6 +328,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
m_context << notFound;
|
||||
if (fallback)
|
||||
{
|
||||
solAssert(!_contract.isLibrary(), "");
|
||||
if (!fallback->isPayable())
|
||||
appendCallValueCheck();
|
||||
|
||||
@@ -291,6 +348,13 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
|
||||
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
|
||||
|
||||
m_context << callDataUnpackerEntryPoints.at(it.first);
|
||||
if (_contract.isLibrary() && functionType->stateMutability() > StateMutability::View)
|
||||
{
|
||||
// If the function is not a view function and is called without DELEGATECALL,
|
||||
// we revert.
|
||||
m_context << dupInstruction(2);
|
||||
m_context.appendConditionalRevert();
|
||||
}
|
||||
m_context.setStackOffset(0);
|
||||
// We have to allow this for libraries, because value of the previous
|
||||
// call is still visible in the delegatecall.
|
||||
@@ -441,6 +505,7 @@ void ContractCompiler::registerStateVariables(ContractDefinition const& _contrac
|
||||
|
||||
void ContractCompiler::initializeStateVariables(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(!_contract.isLibrary(), "Tried to initialize state variables of library.");
|
||||
for (VariableDeclaration const* variable: _contract.stateVariables())
|
||||
if (variable->value() && !variable->isConstant())
|
||||
ExpressionCompiler(m_context, m_optimise).appendStateVariableInitialization(*variable);
|
||||
|
||||
@@ -75,10 +75,19 @@ private:
|
||||
/// with a new and initialized context. Adds the constructor code.
|
||||
/// @returns the identifier of the runtime sub assembly
|
||||
size_t packIntoContractCreator(ContractDefinition const& _contract);
|
||||
/// Appends code that deploys the given contract as a library.
|
||||
/// Will also add code that modifies the contract in memory by injecting the current address
|
||||
/// for the call protector.
|
||||
size_t deployLibrary(ContractDefinition const& _contract);
|
||||
/// Appends state variable initialisation and constructor code.
|
||||
void appendInitAndConstructorCode(ContractDefinition const& _contract);
|
||||
void appendBaseConstructor(FunctionDefinition const& _constructor);
|
||||
void appendConstructor(FunctionDefinition const& _constructor);
|
||||
/// Appends code that returns a boolean flag on the stack that tells whether
|
||||
/// the contract has been called via delegatecall (false) or regular call (true).
|
||||
/// This is done by inserting a specific push constant as the first instruction
|
||||
/// whose data will be modified in memory at deploy time.
|
||||
void appendDelegatecallCheck();
|
||||
void appendFunctionSelector(ContractDefinition const& _contract);
|
||||
void appendCallValueCheck();
|
||||
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
|
||||
|
||||
Reference in New Issue
Block a user