diff options
author | chriseth <chris@ethereum.org> | 2019-01-18 07:16:06 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-01-18 07:16:06 +0800 |
commit | 2ec997e697e306dd54165aad365406ee88c534cb (patch) | |
tree | 5e943e23d38e332de3eedd33be11f2cf9df5fd69 | |
parent | 0711873a2f13d7b0f27e268fcd0a7683665f339d (diff) | |
parent | 2a92403690a4998ab097503231ac39f854b9c76c (diff) | |
download | dexon-solidity-2ec997e697e306dd54165aad365406ee88c534cb.tar.gz dexon-solidity-2ec997e697e306dd54165aad365406ee88c534cb.tar.zst dexon-solidity-2ec997e697e306dd54165aad365406ee88c534cb.zip |
Merge pull request #5775 from ethereum/codeAccess
Provide access to code of contract types.
51 files changed, 641 insertions, 72 deletions
diff --git a/Changelog.md b/Changelog.md index dc6beb44..bdfd9746 100644 --- a/Changelog.md +++ b/Changelog.md @@ -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: diff --git a/docs/miscellaneous.rst b/docs/miscellaneous.rst index 5a6f3875..69124c77 100644 --- a/docs/miscellaneous.rst +++ b/docs/miscellaneous.rst @@ -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 diff --git a/docs/types/value-types.rst b/docs/types/value-types.rst index 077587dd..09db1423 100644 --- a/docs/types/value-types.rst +++ b/docs/types/value-types.rst @@ -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 diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index c47f257b..ce7706c1 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -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. diff --git a/liblangutil/Token.h b/liblangutil/Token.h index f832fdf7..b3a1acb1 100644 --- a/liblangutil/Token.h +++ b/liblangutil/Token.h @@ -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) \ diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp index cd5fe07d..2276d783 100644 --- a/libsolidity/analysis/GlobalContext.cpp +++ b/libsolidity/analysis/GlobalContext.cpp @@ -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 + )), }) { } diff --git a/libsolidity/analysis/StaticAnalyzer.cpp b/libsolidity/analysis/StaticAnalyzer.cpp index aaaa4f9f..11ed6a4f 100644 --- a/libsolidity/analysis/StaticAnalyzer.cpp +++ b/libsolidity/analysis/StaticAnalyzer.cpp @@ -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") diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index ff33fa3a..3daf83b3 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -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. diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 7fa9933c..d038233c 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -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; } diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index b60c571a..d5f3645c 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -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 diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp index eb019481..7df7ac17 100644 --- a/libsolidity/analysis/ViewPureChecker.cpp +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -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"} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index f379d758..4cf8b1e8 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -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) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 9ac065ea..cd986050 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -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; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index cc978b4a..081c7fb6 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -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; } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index bee00661..53109de1 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -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; + }; /** diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index a22e6e9d..72efed33 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -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); diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 784d7f8c..c21de96d 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -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). diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index be681b2e..861b1c98 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -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 diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index dedcd95f..e5ddfbc5 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -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). diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index bbc703c7..6cfb0777 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -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 diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 7e4f47ba..6bde2e8b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -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; diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index b051d260..f843e07a 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -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); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 40871f0d..9ab006f6 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -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. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index be2709ae..e6bb163d 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -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; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index f9d889e7..9e4da62d 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -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 diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 81d5009f..79927850 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -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 diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 8a6bc343..35476a76 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -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: { diff --git a/test/libsolidity/ASTJSON/address_payable.json b/test/libsolidity/ASTJSON/address_payable.json index 0f30e8e8..c8b71628 100644 --- a/test/libsolidity/ASTJSON/address_payable.json +++ b/test/libsolidity/ASTJSON/address_payable.json @@ -277,7 +277,7 @@ "name" : "this", "nodeType" : "Identifier", "overloadedDeclarations" : [], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "src" : "217:4:1", "typeDescriptions" : { diff --git a/test/libsolidity/ASTJSON/address_payable_legacy.json b/test/libsolidity/ASTJSON/address_payable_legacy.json index dd8a5582..8abebce0 100644 --- a/test/libsolidity/ASTJSON/address_payable_legacy.json +++ b/test/libsolidity/ASTJSON/address_payable_legacy.json @@ -424,7 +424,7 @@ [ null ], - "referencedDeclaration" : 65, + "referencedDeclaration" : 66, "type" : "contract C", "value" : "this" }, diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index baa9bff1..b5a1797b 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -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(); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8d219d16..38be5ae7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -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() } diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a33c6134..413a8c9c 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -539,7 +539,6 @@ BOOST_AUTO_TEST_CASE(keyword_is_reserved) "supports", "switch", "try", - "type", "typedef", "typeof", "unchecked" diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol new file mode 100644 index 00000000..e90443e1 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccess.sol @@ -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) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol new file mode 100644 index 00000000..bae137c5 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractCreation.sol @@ -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). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol new file mode 100644 index 00000000..186d2714 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessAbstractRuntime.sol @@ -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). diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol new file mode 100644 index 00000000..33dbfd7c --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessBase.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol new file mode 100644 index 00000000..d5723df6 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessCyclic.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol new file mode 100644 index 00000000..cda5d5c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessIsConstant.sol @@ -0,0 +1,7 @@ +contract Test { + bytes constant c = type(B).creationCode; + bytes constant r = type(B).runtimeCode; + +} +contract B { function f() public pure {} } +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol new file mode 100644 index 00000000..f746dc35 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeAccessLibrary.sol @@ -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) {} +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol new file mode 100644 index 00000000..022e4d6f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/codeIsNoLValue.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol new file mode 100644 index 00000000..542aaf53 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/noArgForType.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol new file mode 100644 index 00000000..ec8d9784 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/runtimeCodeWarningAssembly.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol new file mode 100644 index 00000000..61c2b779 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/tooManyArgsForType.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol new file mode 100644 index 00000000..144ca1c3 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierContractName.sol @@ -0,0 +1,3 @@ +contract type { } +// ---- +// ParserError: (9-13): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol new file mode 100644 index 00000000..b7881f15 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierFunction.sol @@ -0,0 +1,6 @@ +contract Test { + function type() public pure { + } +} +// ---- +// ParserError: (29-33): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol new file mode 100644 index 00000000..001ba840 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierParameter.sol @@ -0,0 +1,6 @@ +contract Test { + function f(uint type) public pure { + } +} +// ---- +// ParserError: (36-40): Expected ',' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol new file mode 100644 index 00000000..fa827a33 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierStateVariable.sol @@ -0,0 +1,5 @@ +contract Test { + uint type; +} +// ---- +// ParserError: (25-29): Expected identifier but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol new file mode 100644 index 00000000..fa57698d --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeNotRegularIdentifierVariable.sol @@ -0,0 +1,7 @@ +contract Test { + function f() public pure { + uint type; + } +} +// ---- +// ParserError: (60-64): Expected ';' but got 'type' diff --git a/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol new file mode 100644 index 00000000..036c36e8 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeOfContract.sol @@ -0,0 +1,6 @@ +contract Test { + function f() public pure returns (bytes memory) { + type(Test); + } +} +// ---- diff --git a/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol new file mode 100644 index 00000000..0ce06786 --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/typeRecursive.sol @@ -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. diff --git a/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol new file mode 100644 index 00000000..5c27d42f --- /dev/null +++ b/test/libsolidity/syntaxTests/metaTypes/unsupportedArgForType.sol @@ -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. |