Merge pull request #5775 from ethereum/codeAccess

Provide access to code of contract types.
This commit is contained in:
chriseth 2019-01-18 00:16:06 +01:00 committed by GitHub
commit 2ec997e697
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 641 additions and 72 deletions

View File

@ -1,6 +1,7 @@
### 0.5.3 (unreleased)
Language Features:
* Provide access to creation and runtime code of contracts via ``type(C).creationCode`` / ``type(C).runtimeCode``.
Compiler Features:

View File

@ -385,6 +385,8 @@ Global Variables
- ``<address>.balance`` (``uint256``): balance of the :ref:`address` in Wei
- ``<address payable>.send(uint256 amount) returns (bool)``: send given amount of Wei to :ref:`address`, returns ``false`` on failure
- ``<address payable>.transfer(uint256 amount)``: send given amount of Wei to :ref:`address`, throws on failure
- ``type(C).creationCode`` (``bytes memory``): creation bytecode of the given contract, see :ref:`Type Information<meta-type>`.
- ``type(C).runtimeCode`` (``bytes memory``): runtime bytecode of the given contract, see :ref:`Type Information<meta-type>`.
.. note::
Do not rely on ``block.timestamp``, ``now`` and ``blockhash`` as a source of randomness,
@ -445,7 +447,7 @@ These keywords are reserved in Solidity. They might become part of the syntax in
``abstract``, ``after``, ``alias``, ``apply``, ``auto``, ``case``, ``catch``, ``copyof``, ``default``,
``define``, ``final``, ``immutable``, ``implements``, ``in``, ``inline``, ``let``, ``macro``, ``match``,
``mutable``, ``null``, ``of``, ``override``, ``partial``, ``promise``, ``reference``, ``relocatable``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``type``, ``typedef``, ``typeof``,
``sealed``, ``sizeof``, ``static``, ``supports``, ``switch``, ``try``, ``typedef``, ``typeof``,
``unchecked``.
Language Grammar

View File

@ -329,6 +329,9 @@ Contracts do not support any operators.
The members of contract types are the external functions of the contract
including public state variables.
For a contract ``C`` you can use ``type(C)`` to access
:ref:`type information<meta-type>` about the contract.
.. index:: byte array, bytes32
Fixed-size byte arrays

View File

@ -244,3 +244,33 @@ Furthermore, all functions of the current contract are callable directly includi
.. note::
Prior to version 0.5.0, there was a function called ``suicide`` with the same
semantics as ``selfdestruct``.
.. index:: type, creationCode, runtimeCode
.. _meta-type:
Type Information
----------------
The expression ``type(X)`` can be used to retrieve information about the
type ``X``. Currently, there is limited support for this feature, but
it might be expanded in the future. The following properties are
available for a conract type ``C``:
``type(C).creationCode``:
Memory byte array that contains the creation bytecode of the contract.
This can be used in inline assembly to build custom creation routines,
especially by using the ``create2`` opcode.
This property can **not** be accessed in the contract itself or any
derived contract. It causes the bytecode to be included in the bytecode
of the call site and thus circular references like that are not possible.
``type(C).runtimeCode``:
Memory byte array that contains the runtime bytecode of the contract.
This is the code that is usually deployed by the constructor of ``C``.
If ``C`` has a constructor that uses inline assembly, this might be
different from the actually deployed bytecode. Also note that libraries
modify their runtime bytecode at time of deployment to guard against
regular calls.
The same restrictions as with ``.creationCode`` also apply for this
property.

View File

@ -180,6 +180,7 @@ namespace langutil
K(CallData, "calldata", 0) \
K(Struct, "struct", 0) \
K(Throw, "throw", 0) \
K(Type, "type", 0) \
K(Using, "using", 0) \
K(Var, "var", 0) \
K(View, "view", 0) \
@ -256,7 +257,6 @@ namespace langutil
K(Supports, "supports", 0) \
K(Switch, "switch", 0) \
K(Try, "try", 0) \
K(Type, "type", 0) \
K(Typedef, "typedef", 0) \
K(TypeOf, "typeof", 0) \
K(Unchecked, "unchecked", 0) \

View File

@ -61,7 +61,14 @@ m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{
make_shared<MagicVariableDeclaration>("sha256", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::SHA256, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("sha3", make_shared<FunctionType>(strings{"bytes memory"}, strings{"bytes32"}, FunctionType::Kind::KECCAK256, false, StateMutability::Pure)),
make_shared<MagicVariableDeclaration>("suicide", make_shared<FunctionType>(strings{"address payable"}, strings{}, FunctionType::Kind::Selfdestruct)),
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction))
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
make_shared<MagicVariableDeclaration>("type", make_shared<FunctionType>(
strings{"address"} /* accepts any contract type, handled by the type checker */,
strings{} /* returns a MagicType, handled by the type checker */,
FunctionType::Kind::MetaType,
false,
StateMutability::Pure
)),
})
{
}

View File

@ -32,6 +32,56 @@ using namespace dev;
using namespace langutil;
using namespace dev::solidity;
/**
* Helper class that determines whether a contract's constructor uses inline assembly.
*/
class dev::solidity::ConstructorUsesAssembly
{
public:
/// @returns true if and only if the contract's or any of its bases' constructors
/// use inline assembly.
bool check(ContractDefinition const& _contract)
{
for (auto const* base: _contract.annotation().linearizedBaseContracts)
if (checkInternal(*base))
return true;
return false;
}
private:
class Checker: public ASTConstVisitor
{
public:
Checker(FunctionDefinition const& _f) { _f.accept(*this); }
bool visit(InlineAssembly const&) override { assemblySeen = true; return false; }
bool assemblySeen = false;
};
bool checkInternal(ContractDefinition const& _contract)
{
if (!m_usesAssembly.count(&_contract))
{
bool usesAssembly = false;
if (_contract.constructor())
usesAssembly = Checker{*_contract.constructor()}.assemblySeen;
m_usesAssembly[&_contract] = usesAssembly;
}
return m_usesAssembly[&_contract];
}
map<ContractDefinition const*, bool> m_usesAssembly;
};
StaticAnalyzer::StaticAnalyzer(ErrorReporter& _errorReporter):
m_errorReporter(_errorReporter)
{
}
StaticAnalyzer::~StaticAnalyzer()
{
}
bool StaticAnalyzer::analyze(SourceUnit const& _sourceUnit)
{
_sourceUnit.accept(*this);
@ -152,6 +202,18 @@ bool StaticAnalyzer::visit(MemberAccess const& _memberAccess)
_memberAccess.location(),
"\"block.blockhash()\" has been deprecated in favor of \"blockhash()\""
);
else if (type->kind() == MagicType::Kind::MetaType && _memberAccess.memberName() == "runtimeCode")
{
if (!m_constructorUsesAssembly)
m_constructorUsesAssembly = make_unique<ConstructorUsesAssembly>();
ContractType const& contract = dynamic_cast<ContractType const&>(*type->typeArgument());
if (m_constructorUsesAssembly->check(contract.contractDefinition()))
m_errorReporter.warning(
_memberAccess.location(),
"The constructor of the contract (or its base) uses inline assembly. "
"Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode."
);
}
}
if (_memberAccess.memberName() == "callcode")

View File

@ -38,6 +38,8 @@ namespace dev
namespace solidity
{
class ConstructorUsesAssembly;
/**
* The module that performs static analysis on the AST.
@ -49,7 +51,8 @@ class StaticAnalyzer: private ASTConstVisitor
{
public:
/// @param _errorReporter provides the error logging functionality.
explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
explicit StaticAnalyzer(langutil::ErrorReporter& _errorReporter);
~StaticAnalyzer();
/// Performs static analysis on the given source unit and all of its sub-nodes.
/// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings
@ -85,6 +88,10 @@ private:
/// when traversing.
std::map<std::pair<size_t, VariableDeclaration const*>, int> m_localVarUseCount;
/// Cache that holds information about whether a contract's constructor
/// uses inline assembly.
std::unique_ptr<ConstructorUsesAssembly> m_constructorUsesAssembly;
FunctionDefinition const* m_currentFunction = nullptr;
/// Flag that indicates a constructor.

View File

@ -199,6 +199,38 @@ TypePointers TypeChecker::typeCheckABIDecodeAndRetrieveReturnType(FunctionCall c
return components;
}
TypePointers TypeChecker::typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall)
{
vector<ASTPointer<Expression const>> arguments = _functionCall.arguments();
if (arguments.size() != 1)
{
m_errorReporter.typeError(
_functionCall.location(),
"This function takes one argument, but " +
toString(arguments.size()) +
" were provided."
);
return {};
}
TypePointer firstArgType = type(*arguments.front());
if (
firstArgType->category() != Type::Category::TypeType ||
dynamic_cast<TypeType const&>(*firstArgType).actualType()->category() != TypeType::Category::Contract
)
{
m_errorReporter.typeError(
arguments.front()->location(),
"Invalid type for argument in function call. "
"Contract type required, but " +
type(*arguments.front())->toString(true) +
" provided."
);
return {};
}
return {MagicType::metaType(dynamic_cast<TypeType const&>(*firstArgType).actualType())};
}
void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance)
{
auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name()));
@ -1831,6 +1863,9 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
returnTypes = functionType->returnParameterTypes();
break;
}
case FunctionType::Kind::MetaType:
returnTypes = typeCheckMetaTypeFunctionAndRetrieveReturnType(_functionCall);
break;
default:
{
typeCheckFunctionCall(_functionCall, functionType);
@ -2071,8 +2106,24 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (tt->actualType()->category() == Type::Category::Enum)
annotation.isPure = true;
if (auto magicType = dynamic_cast<MagicType const*>(exprType.get()))
{
if (magicType->kind() == MagicType::Kind::ABI)
annotation.isPure = true;
else if (magicType->kind() == MagicType::Kind::MetaType && (
memberName == "creationCode" || memberName == "runtimeCode"
))
{
annotation.isPure = true;
m_scope->annotation().contractDependencies.insert(
&dynamic_cast<ContractType const&>(*magicType->typeArgument()).contractDefinition()
);
if (contractDependenciesAreCyclic(*m_scope))
m_errorReporter.typeError(
_memberAccess.location(),
"Circular reference for contract code access."
);
}
}
return false;
}

View File

@ -81,6 +81,8 @@ private:
bool _abiEncoderV2
);
TypePointers typeCheckMetaTypeFunctionAndRetrieveReturnType(FunctionCall const& _functionCall);
/// Performs type checks and determines result types for type conversion FunctionCall nodes.
TypePointer typeCheckTypeConversionAndRetrieveReturnType(
FunctionCall const& _functionCall

View File

@ -338,7 +338,9 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{MagicType::Kind::ABI, "encodeWithSignature"},
{MagicType::Kind::Block, "blockhash"},
{MagicType::Kind::Message, "data"},
{MagicType::Kind::Message, "sig"}
{MagicType::Kind::Message, "sig"},
{MagicType::Kind::MetaType, "creationCode"},
{MagicType::Kind::MetaType, "runtimeCode"}
};
set<MagicMember> static const payableMembers{
{MagicType::Kind::Message, "value"}

View File

@ -138,6 +138,11 @@ bool ContractDefinition::constructorIsPublic() const
return !f || f->isPublic();
}
bool ContractDefinition::canBeDeployed() const
{
return constructorIsPublic() && annotation().unimplementedFunctions.empty();
}
FunctionDefinition const* ContractDefinition::fallbackFunction() const
{
for (ContractDefinition const* contract: annotation().linearizedBaseContracts)

View File

@ -408,6 +408,10 @@ public:
FunctionDefinition const* constructor() const;
/// @returns true iff the constructor of this contract is public (or non-existing).
bool constructorIsPublic() const;
/// @returns true iff the contract can be deployed, i.e. is not abstract and has a
/// public constructor.
/// Should only be called after the type checker has run.
bool canBeDeployed() const;
/// Returns the fallback function or nullptr if no fallback function was specified.
FunctionDefinition const* fallbackFunction() const;

View File

@ -2626,6 +2626,7 @@ string FunctionType::richIdentifier() const
case Kind::ABIEncodeWithSelector: id += "abiencodewithselector"; break;
case Kind::ABIEncodeWithSignature: id += "abiencodewithsignature"; break;
case Kind::ABIDecode: id += "abidecode"; break;
case Kind::MetaType: id += "metatype"; break;
}
id += "_" + stateMutabilityToString(m_stateMutability);
id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
@ -3037,7 +3038,8 @@ bool FunctionType::isPure() const
m_kind == Kind::ABIEncodePacked ||
m_kind == Kind::ABIEncodeWithSelector ||
m_kind == Kind::ABIEncodeWithSignature ||
m_kind == Kind::ABIDecode;
m_kind == Kind::ABIDecode ||
m_kind == Kind::MetaType;
}
TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
@ -3305,6 +3307,14 @@ string ModuleType::toString(bool) const
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
}
shared_ptr<MagicType> MagicType::metaType(TypePointer _type)
{
solAssert(_type && _type->category() == Type::Category::Contract, "Only contracts supported for now.");
auto t = make_shared<MagicType>(Kind::MetaType);
t->m_typeArgument = std::move(_type);
return t;
}
string MagicType::richIdentifier() const
{
switch (m_kind)
@ -3317,6 +3327,9 @@ string MagicType::richIdentifier() const
return "t_magic_transaction";
case Kind::ABI:
return "t_magic_abi";
case Kind::MetaType:
solAssert(m_typeArgument, "");
return "t_magic_meta_type_" + m_typeArgument->richIdentifier();
}
return "";
}
@ -3403,12 +3416,27 @@ MemberList::MemberMap MagicType::nativeMembers(ContractDefinition const*) const
StateMutability::Pure
)}
});
default:
solAssert(false, "Unknown kind of magic.");
case Kind::MetaType:
{
solAssert(
m_typeArgument && m_typeArgument->category() == Type::Category::Contract,
"Only contracts supported for now"
);
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*m_typeArgument).contractDefinition();
if (contract.canBeDeployed())
return MemberList::MemberMap({
{"creationCode", std::make_shared<ArrayType>(DataLocation::Memory)},
{"runtimeCode", std::make_shared<ArrayType>(DataLocation::Memory)}
});
else
return {};
}
}
solAssert(false, "Unknown kind of magic.");
return {};
}
string MagicType::toString(bool) const
string MagicType::toString(bool _short) const
{
switch (m_kind)
{
@ -3420,7 +3448,17 @@ string MagicType::toString(bool) const
return "tx";
case Kind::ABI:
return "abi";
default:
solAssert(false, "Unknown kind of magic.");
case Kind::MetaType:
solAssert(m_typeArgument, "");
return "type(" + m_typeArgument->toString(_short) + ")";
}
solAssert(false, "Unknown kind of magic.");
return {};
}
TypePointer MagicType::typeArgument() const
{
solAssert(m_kind == Kind::MetaType, "");
solAssert(m_typeArgument, "");
return m_typeArgument;
}

View File

@ -989,6 +989,7 @@ public:
ABIEncodeWithSignature,
ABIDecode,
GasLeft, ///< gasleft()
MetaType ///< type(...)
};
Category category() const override { return Category::Function; }
@ -1299,16 +1300,23 @@ private:
};
/**
* Special type for magic variables (block, msg, tx), similar to a struct but without any reference
* (it always references a global singleton by name).
* Special type for magic variables (block, msg, tx, type(...)), similar to a struct but without any reference.
*/
class MagicType: public Type
{
public:
enum class Kind { Block, Message, Transaction, ABI };
enum class Kind {
Block, ///< "block"
Message, ///< "msg"
Transaction, ///< "tx"
ABI, ///< "abi"
MetaType ///< "type(...)"
};
Category category() const override { return Category::Magic; }
explicit MagicType(Kind _kind): m_kind(_kind) {}
/// Factory function for meta type
static std::shared_ptr<MagicType> metaType(TypePointer _type);
TypeResult binaryOperatorResult(Token, TypePointer const&) const override
{
@ -1327,8 +1335,13 @@ public:
Kind kind() const { return m_kind; }
TypePointer typeArgument() const;
private:
Kind m_kind;
/// Contract type used for contract metadata magic.
TypePointer m_typeArgument;
};
/**

View File

@ -31,22 +31,28 @@ using namespace dev::solidity;
void Compiler::compileContract(
ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts,
std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers,
bytes const& _metadata
)
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize, m_optimizeRuns);
runtimeCompiler.compileContract(_contract, _contracts);
runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata);
// This might modify m_runtimeContext because it can access runtime functions at
// creation time.
ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize, 1);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts);
m_runtimeSub = creationCompiler.compileConstructor(_contract, _otherCompilers);
m_context.optimise(m_optimize, m_optimizeRuns);
}
std::shared_ptr<eth::Assembly> Compiler::runtimeAssemblyPtr() const
{
solAssert(m_context.runtimeContext(), "");
return m_context.runtimeContext()->assemblyPtr();
}
eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const
{
return m_runtimeContext.functionEntryLabelIfExists(_function);

View File

@ -45,11 +45,15 @@ public:
/// @arg _metadata contains the to be injected metadata CBOR
void compileContract(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts,
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers,
bytes const& _metadata
);
/// @returns Entire assembly.
eth::Assembly const& assembly() const { return m_context.assembly(); }
/// @returns Entire assembly as a shared pointer to non-const.
std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_context.assemblyPtr(); }
/// @returns Runtime assembly.
std::shared_ptr<eth::Assembly> runtimeAssemblyPtr() const;
/// @returns The entire assembled object (with constructor).
eth::LinkerObject assembledObject() const { return m_context.assembledObject(); }
/// @returns Only the runtime object (without constructor).

View File

@ -167,11 +167,18 @@ unsigned CompilerContext::numberOfLocalVariables() const
return m_localVariables.size();
}
eth::Assembly const& CompilerContext::compiledContract(const ContractDefinition& _contract) const
shared_ptr<eth::Assembly> CompilerContext::compiledContract(ContractDefinition const& _contract) const
{
auto ret = m_compiledContracts.find(&_contract);
solAssert(ret != m_compiledContracts.end(), "Compiled contract not found.");
return *ret->second;
auto ret = m_otherCompilers.find(&_contract);
solAssert(ret != m_otherCompilers.end(), "Compiled contract not found.");
return ret->second->assemblyPtr();
}
shared_ptr<eth::Assembly> CompilerContext::compiledContractRuntime(ContractDefinition const& _contract) const
{
auto ret = m_otherCompilers.find(&_contract);
solAssert(ret != m_otherCompilers.end(), "Compiled contract not found.");
return ret->second->runtimeAssemblyPtr();
}
bool CompilerContext::isLocalVariable(Declaration const* _declaration) const

View File

@ -41,6 +41,7 @@
namespace dev {
namespace solidity {
class Compiler;
/**
* Context to be shared by all units that compile the same contract.
@ -74,8 +75,9 @@ public:
/// Returns the number of currently allocated local variables.
unsigned numberOfLocalVariables() const;
void setCompiledContracts(std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts) { m_compiledContracts = _contracts; }
eth::Assembly const& compiledContract(ContractDefinition const& _contract) const;
void setOtherCompilers(std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers) { m_otherCompilers = _otherCompilers; }
std::shared_ptr<eth::Assembly> compiledContract(ContractDefinition const& _contract) const;
std::shared_ptr<eth::Assembly> compiledContractRuntime(ContractDefinition const& _contract) const;
void setStackOffset(int _offset) { m_asm->setDeposit(_offset); }
void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); }
@ -222,15 +224,15 @@ public:
void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, m_evmVersion, true, _runs); }
/// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise.
CompilerContext* runtimeContext() { return m_runtimeContext; }
CompilerContext* runtimeContext() const { return m_runtimeContext; }
/// @returns the identifier of the runtime subroutine.
size_t runtimeSub() const { return m_runtimeSub; }
/// @returns a const reference to the underlying assembly.
eth::Assembly const& assembly() const { return *m_asm; }
/// @returns non-const reference to the underlying assembly. Should be avoided in favour of
/// wrappers in this class.
eth::Assembly& nonConstAssembly() { return *m_asm; }
/// @returns a shared pointer to the assembly.
/// Should be avoided except when adding sub-assemblies.
std::shared_ptr<eth::Assembly> assemblyPtr() const { return m_asm; }
/// @arg _sourceCodes is the map of input files to source code strings
std::string assemblyString(StringMap const& _sourceCodes = StringMap()) const
@ -307,7 +309,7 @@ private:
/// Activated experimental features.
std::set<ExperimentalFeature> m_experimentalFeatures;
/// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, eth::Assembly const*> m_compiledContracts;
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> m_otherCompilers;
/// Storage offsets of state variables
std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base).

View File

@ -1199,6 +1199,29 @@ void CompilerUtils::computeHashStatic()
m_context << u256(32) << u256(0) << Instruction::KECCAK256;
}
void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation)
{
string which = _creation ? "Creation" : "Runtime";
m_context.callLowLevelFunction(
"$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(),
1,
1,
[&contract, _creation](CompilerContext& _context)
{
// copy the contract's code into memory
shared_ptr<eth::Assembly> assembly =
_creation ?
_context.compiledContract(contract) :
_context.compiledContractRuntime(contract);
// pushes size
auto subroutine = _context.addSubroutine(assembly);
_context << Instruction::DUP1 << subroutine;
_context << Instruction::DUP4 << Instruction::CODECOPY;
_context << Instruction::ADD;
}
);
}
void CompilerUtils::storeStringData(bytesConstRef _data)
{
//@todo provide both alternatives to the optimiser

View File

@ -263,6 +263,13 @@ public:
/// Appends code that computes the Keccak-256 hash of the topmost stack element of 32 byte type.
void computeHashStatic();
/// Apppends code that copies the code of the given contract to memory.
/// Stack pre: Memory position
/// Stack post: Updated memory position
/// @param creation if true, copies creation code, if false copies runtime code.
/// @note the contract has to be compiled already, so beware of cyclic dependencies!
void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode);
/// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier.
static const unsigned dataStartOffset;

View File

@ -60,7 +60,7 @@ private:
void ContractCompiler::compileContract(
ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
)
{
CompilerContext::LocationSetter locationSetter(m_context, _contract);
@ -70,7 +70,7 @@ void ContractCompiler::compileContract(
// This has to be the first code in the contract.
appendDelegatecallCheck();
initializeContext(_contract, _contracts);
initializeContext(_contract, _otherCompilers);
// This generates the dispatch function for externally visible functions
// and adds the function to the compilation queue. Additionally internal functions,
// which are referenced directly or indirectly will be added.
@ -81,7 +81,7 @@ void ContractCompiler::compileContract(
size_t ContractCompiler::compileConstructor(
ContractDefinition const& _contract,
std::map<const ContractDefinition*, eth::Assembly const*> const& _contracts
std::map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
)
{
CompilerContext::LocationSetter locationSetter(m_context, _contract);
@ -89,18 +89,18 @@ size_t ContractCompiler::compileConstructor(
return deployLibrary(_contract);
else
{
initializeContext(_contract, _contracts);
initializeContext(_contract, _otherCompilers);
return packIntoContractCreator(_contract);
}
}
void ContractCompiler::initializeContext(
ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts
map<ContractDefinition const*, shared_ptr<Compiler const>> const& _otherCompilers
)
{
m_context.setExperimentalFeatures(_contract.sourceUnit().annotation().experimentalFeatures);
m_context.setCompiledContracts(_compiledContracts);
m_context.setOtherCompilers(_otherCompilers);
m_context.setInheritanceHierarchy(_contract.annotation().linearizedBaseContracts);
CompilerUtils(m_context).initialiseFreeMemoryPointer();
registerStateVariables(_contract);
@ -716,7 +716,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
CodeGenerator::assemble(
_inlineAssembly.operations(),
*_inlineAssembly.annotation().analysisInfo,
m_context.nonConstAssembly(),
*m_context.assemblyPtr(),
identifierAccess
);
m_context.setStackOffset(startStackHeight);

View File

@ -49,13 +49,13 @@ public:
void compileContract(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
);
/// Compiles the constructor part of the contract.
/// @returns the identifier of the runtime sub-assembly.
size_t compileConstructor(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _contracts
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
);
private:
@ -63,7 +63,7 @@ private:
/// information about the contract like the AST annotations.
void initializeContext(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*> const& _compiledContracts
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>> const& _otherCompilers
);
/// Adds the code that is run at creation time. Should be run after exchanging the run-time context
/// with a new and initialized context. Adds the constructor code.

View File

@ -594,22 +594,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
m_context.callLowLevelFunction(
"$copyContractCreationCodeToMemory_" + contract->type()->identifier(),
0,
1,
[contract](CompilerContext& _context)
{
// copy the contract's code into memory
eth::Assembly const& assembly = _context.compiledContract(*contract);
CompilerUtils(_context).fetchFreeMemoryPointer();
// pushes size
auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
_context << Instruction::DUP1 << subroutine;
_context << Instruction::DUP4 << Instruction::CODECOPY;
_context << Instruction::ADD;
}
);
utils().fetchFreeMemoryPointer();
utils().copyContractCodeToMemory(*contract, true);
utils().abiEncode(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr
// need: size, offset, endowment
@ -1107,6 +1093,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::GasLeft:
m_context << Instruction::GAS;
break;
case FunctionType::Kind::MetaType:
// No code to generate.
break;
}
}
return false;
@ -1348,6 +1337,23 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(false, "Gas has been removed.");
else if (member == "blockhash")
solAssert(false, "Blockhash has been removed.");
else if (member == "creationCode" || member == "runtimeCode")
{
TypePointer arg = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type).typeArgument();
ContractDefinition const& contract = dynamic_cast<ContractType const&>(*arg).contractDefinition();
utils().fetchFreeMemoryPointer();
m_context << Instruction::DUP1 << u256(32) << Instruction::ADD;
utils().copyContractCodeToMemory(contract, member == "creationCode");
// Stack: start end
m_context.appendInlineAssembly(
Whiskers(R"({
mstore(start, sub(end, add(start, 0x20)))
mstore(<free>, end)
})")("free", to_string(CompilerUtils::freeMemoryPointer)).render(),
{"start", "end"}
);
m_context << Instruction::POP;
}
else
solAssert(false, "Unknown magic member.");
break;

View File

@ -343,12 +343,12 @@ bool CompilerStack::compile()
return false;
// Only compile contracts individually which have been requested.
map<ContractDefinition const*, eth::Assembly const*> compiledContracts;
map<ContractDefinition const*, shared_ptr<Compiler const>> otherCompilers;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
if (isRequestedContract(*contract))
compileContract(*contract, compiledContracts);
compileContract(*contract, otherCompilers);
m_stackState = CompilationSuccessful;
this->link();
return true;
@ -795,19 +795,15 @@ bool onlySafeExperimentalFeaturesActivated(set<ExperimentalFeature> const& featu
void CompilerStack::compileContract(
ContractDefinition const& _contract,
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
map<ContractDefinition const*, shared_ptr<Compiler const>>& _otherCompilers
)
{
solAssert(m_stackState >= AnalysisSuccessful, "");
if (
_compiledContracts.count(&_contract) ||
!_contract.annotation().unimplementedFunctions.empty() ||
!_contract.constructorIsPublic()
)
if (_otherCompilers.count(&_contract) || !_contract.canBeDeployed())
return;
for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts);
compileContract(*dependency, _otherCompilers);
Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
@ -825,7 +821,7 @@ void CompilerStack::compileContract(
try
{
// Run optimiser and compile the contract.
compiler->compileContract(_contract, _compiledContracts, cborEncodedMetadata);
compiler->compileContract(_contract, _otherCompilers, cborEncodedMetadata);
}
catch(eth::OptimizerException const&)
{
@ -852,7 +848,7 @@ void CompilerStack::compileContract(
solAssert(false, "Assembly exception for deployed bytecode");
}
_compiledContracts[compiledContract.contract] = &compiler->assembly();
_otherCompilers[compiledContract.contract] = compiler;
}
CompilerStack::Contract const& CompilerStack::contract(string const& _contractName) const

View File

@ -293,10 +293,12 @@ private:
/// @returns true if the contract is requested to be compiled.
bool isRequestedContract(ContractDefinition const& _contract) const;
/// Compile a single contract and put the result in @a _compiledContracts.
/// Compile a single contract.
/// @param _otherCompilers provides access to compilers of other contracts, to get
/// their bytecode if needed. Only filled after they have been compiled.
void compileContract(
ContractDefinition const& _contract,
std::map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
std::map<ContractDefinition const*, std::shared_ptr<Compiler const>>& _otherCompilers
);
/// Links all the known library addresses in the available objects. Any unknown

View File

@ -1551,6 +1551,12 @@ ASTPointer<Expression> Parser::parsePrimaryExpression()
nodeFactory.markEndPosition();
expression = nodeFactory.createNode<Identifier>(getLiteralAndAdvance());
break;
case Token::Type:
// Inside expressions "type" is the name of a special, globally-available function.
nodeFactory.markEndPosition();
m_scanner->next();
expression = nodeFactory.createNode<Identifier>(make_shared<ASTString>("type"));
break;
case Token::LParen:
case Token::LBrack:
{

View File

@ -277,7 +277,7 @@
"name" : "this",
"nodeType" : "Identifier",
"overloadedDeclarations" : [],
"referencedDeclaration" : 65,
"referencedDeclaration" : 66,
"src" : "217:4:1",
"typeDescriptions" :
{

View File

@ -424,7 +424,7 @@
[
null
],
"referencedDeclaration" : 65,
"referencedDeclaration" : 66,
"type" : "contract C",
"value" : "this"
},

View File

@ -46,6 +46,7 @@ namespace dev
{
namespace solidity
{
class Contract;
namespace test
{
@ -84,7 +85,7 @@ eth::AssemblyItems compileContract(std::shared_ptr<CharStream> _sourceCode)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
Compiler compiler(dev::test::Options::get().evmVersion());
compiler.compileContract(*contract, map<ContractDefinition const*, Assembly const*>{}, bytes());
compiler.compileContract(*contract, map<ContractDefinition const*, shared_ptr<Compiler const>>{}, bytes());
return compiler.runtimeAssemblyItems();
}

View File

@ -14234,6 +14234,113 @@ BOOST_AUTO_TEST_CASE(base_access_to_function_type_variables)
ABI_CHECK(callContractFunction("h()"), encodeArgs(2));
}
BOOST_AUTO_TEST_CASE(code_access)
{
char const* sourceCode = R"(
contract C {
function lengths() public pure returns (bool) {
uint crLen = type(D).creationCode.length;
uint runLen = type(D).runtimeCode.length;
require(runLen < crLen);
require(crLen >= 0x20);
require(runLen >= 0x20);
return true;
}
function creation() public pure returns (bytes memory) {
return type(D).creationCode;
}
function runtime() public pure returns (bytes memory) {
return type(D).runtimeCode;
}
function runtimeAllocCheck() public pure returns (bytes memory) {
uint[] memory a = new uint[](2);
bytes memory c = type(D).runtimeCode;
uint[] memory b = new uint[](2);
a[0] = 0x1111;
a[1] = 0x2222;
b[0] = 0x3333;
b[1] = 0x4444;
return c;
}
}
contract D {
function f() public pure returns (uint) { return 7; }
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("lengths()"), encodeArgs(true));
bytes codeCreation = callContractFunction("creation()");
bytes codeRuntime1 = callContractFunction("runtime()");
bytes codeRuntime2 = callContractFunction("runtimeAllocCheck()");
ABI_CHECK(codeRuntime1, codeRuntime2);
}
BOOST_AUTO_TEST_CASE(code_access_create)
{
char const* sourceCode = R"(
contract C {
function test() public returns (uint) {
bytes memory c = type(D).creationCode;
D d;
assembly {
d := create(0, add(c, 0x20), mload(c))
}
return d.f();
}
}
contract D {
uint x;
constructor() public { x = 7; }
function f() public view returns (uint) { return x; }
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("test()"), encodeArgs(7));
}
BOOST_AUTO_TEST_CASE(code_access_content)
{
char const* sourceCode = R"(
contract C {
function testRuntime() public returns (bool) {
D d = new D();
bytes32 runtimeHash = keccak256(type(D).runtimeCode);
bytes32 otherHash;
uint size;
assembly {
size := extcodesize(d)
extcodecopy(d, mload(0x40), 0, size)
otherHash := keccak256(mload(0x40), size)
}
require(size == type(D).runtimeCode.length);
require(runtimeHash == otherHash);
return true;
}
function testCreation() public returns (bool) {
D d = new D();
bytes32 creationHash = keccak256(type(D).creationCode);
require(creationHash == d.x());
return true;
}
}
contract D {
bytes32 public x;
constructor() public {
bytes32 codeHash;
assembly {
let size := codesize()
codecopy(mload(0x40), 0, size)
codeHash := keccak256(mload(0x40), size)
}
x = codeHash;
}
}
)";
compileAndRun(sourceCode, 0, "C");
ABI_CHECK(callContractFunction("testRuntime()"), encodeArgs(true));
ABI_CHECK(callContractFunction("testCreation()"), encodeArgs(true));
}
BOOST_AUTO_TEST_SUITE_END()
}

View File

@ -539,7 +539,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved)
"supports",
"switch",
"try",
"type",
"typedef",
"typeof",
"unchecked"

View File

@ -0,0 +1,12 @@
contract Test {
function creationOther() public pure returns (bytes memory) {
return type(Other).creationCode;
}
function runtimeOther() public pure returns (bytes memory) {
return type(Other).runtimeCode;
}
}
contract Other {
function f(uint) public pure returns (uint) {}
}
// ----

View File

@ -0,0 +1,10 @@
contract Test {
function creationOther() public pure returns (bytes memory) {
return type(Other).creationCode;
}
}
contract Other {
function f(uint) public returns (uint);
}
// ----
// TypeError: (97-121): Member "creationCode" not found or not visible after argument-dependent lookup in type(contract Other).

View File

@ -0,0 +1,10 @@
contract Test {
function runtime() public pure returns (bytes memory) {
return type(Other).runtimeCode;
}
}
contract Other {
function f(uint) public returns (uint);
}
// ----
// TypeError: (91-114): Member "runtimeCode" not found or not visible after argument-dependent lookup in type(contract Other).

View File

@ -0,0 +1,22 @@
contract Base {
function f() public pure returns (uint) {}
}
contract Test is Base {
function creation() public pure returns (bytes memory) {
return type(Test).creationCode;
}
function runtime() public pure returns (bytes memory) {
return type(Test).runtimeCode;
}
function creationBase() public pure returns (bytes memory) {
return type(Base).creationCode;
}
function runtimeBase() public pure returns (bytes memory) {
return type(Base).runtimeCode;
}
}
// ----
// TypeError: (165-188): Circular reference for contract code access.
// TypeError: (271-293): Circular reference for contract code access.
// TypeError: (381-404): Circular reference for contract code access.
// TypeError: (491-513): Circular reference for contract code access.

View File

@ -0,0 +1,12 @@
contract A {
function f() public pure {
type(B).runtimeCode;
}
}
contract B {
function f() public pure {
type(A).runtimeCode;
}
}
// ----
// TypeError: (133-152): Circular reference for contract code access.

View File

@ -0,0 +1,7 @@
contract Test {
bytes constant c = type(B).creationCode;
bytes constant r = type(B).runtimeCode;
}
contract B { function f() public pure {} }
// ----

View File

@ -0,0 +1,12 @@
contract Test {
function creationOther() public pure returns (bytes memory) {
return type(Library).creationCode;
}
function runtime() public pure returns (bytes memory) {
return type(Library).runtimeCode;
}
}
contract Library {
function f(uint) public pure returns (uint) {}
}
// ----

View File

@ -0,0 +1,10 @@
contract Test {
function f() public pure {
type(C).creationCode = new bytes(6);
type(C).runtimeCode = new bytes(6);
}
}
contract C {}
// ----
// TypeError: (55-75): Expression has to be an lvalue.
// TypeError: (100-119): Expression has to be an lvalue.

View File

@ -0,0 +1,7 @@
contract Test {
function creation() public pure returns (bytes memory) {
type();
}
}
// ----
// TypeError: (85-91): This function takes one argument, but 0 were provided.

View File

@ -0,0 +1,17 @@
contract Test {
function f() public pure returns (uint) {
return type(C).runtimeCode.length +
type(D).runtimeCode.length +
type(C).creationCode.length +
type(D).creationCode.length;
}
}
contract C {
constructor() public { assembly {} }
}
contract D is C {
constructor() public {}
}
// ----
// Warning: (77-96): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode.
// Warning: (118-137): The constructor of the contract (or its base) uses inline assembly. Because of that, it might be that the deployed bytecode is different from type(...).runtimeCode.

View File

@ -0,0 +1,7 @@
contract Test {
function creation() public pure returns (bytes memory) {
type(1, 2);
}
}
// ----
// TypeError: (85-95): This function takes one argument, but 2 were provided.

View File

@ -0,0 +1,3 @@
contract type { }
// ----
// ParserError: (9-13): Expected identifier but got 'type'

View File

@ -0,0 +1,6 @@
contract Test {
function type() public pure {
}
}
// ----
// ParserError: (29-33): Expected identifier but got 'type'

View File

@ -0,0 +1,6 @@
contract Test {
function f(uint type) public pure {
}
}
// ----
// ParserError: (36-40): Expected ',' but got 'type'

View File

@ -0,0 +1,5 @@
contract Test {
uint type;
}
// ----
// ParserError: (25-29): Expected identifier but got 'type'

View File

@ -0,0 +1,7 @@
contract Test {
function f() public pure {
uint type;
}
}
// ----
// ParserError: (60-64): Expected ';' but got 'type'

View File

@ -0,0 +1,6 @@
contract Test {
function f() public pure returns (bytes memory) {
type(Test);
}
}
// ----

View File

@ -0,0 +1,8 @@
contract Test {
function f() public pure {
type(type(type(Test)));
}
}
// ----
// TypeError: (65-75): Invalid type for argument in function call. Contract type required, but type(contract Test) provided.
// TypeError: (60-76): Invalid type for argument in function call. Contract type required, but tuple() provided.

View File

@ -0,0 +1,9 @@
contract Test {
struct S { uint x; }
function f() public pure {
// Unsupported for now, but might be supported in the future
type(S);
}
}
// ----
// TypeError: (154-155): Invalid type for argument in function call. Contract type required, but type(struct Test.S) provided.