From e892152ba8b11e0721923167d5155d46eeeb7a3d Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 31 Jul 2015 19:23:31 +0200 Subject: [PATCH] Create and output clone contracts. --- libsolidity/Compiler.cpp | 88 +++++++++++++++++++++++++++-------- libsolidity/Compiler.h | 14 ++++++ libsolidity/CompilerStack.cpp | 11 +++++ libsolidity/CompilerStack.h | 6 +++ solc/CommandLineInterface.cpp | 44 +++++++++++++----- 5 files changed, 132 insertions(+), 31 deletions(-) diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 6ed6480f2..eadfc204a 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -53,31 +54,45 @@ void Compiler::compileContract(ContractDefinition const& _contract, m_context = CompilerContext(); // clear it just in case { CompilerContext::LocationSetter locationSetterRunTime(m_context, _contract); - CompilerUtils(m_context).initialiseFreeMemoryPointer(); initializeContext(_contract, _contracts); appendFunctionSelector(_contract); - set functions = m_context.getFunctionsWithoutCode(); - while (!functions.empty()) - { - for (Declaration const* function: functions) - { - m_context.setStackOffset(0); - function->accept(*this); - } - functions = m_context.getFunctionsWithoutCode(); - } + appendFunctionsWithoutCode(); } // Swap the runtime context with the creation-time context swap(m_context, m_runtimeContext); CompilerContext::LocationSetter locationSetterCreationTime(m_context, _contract); - CompilerUtils(m_context).initialiseFreeMemoryPointer(); initializeContext(_contract, _contracts); packIntoContractCreator(_contract, m_runtimeContext); if (m_optimize) m_context.optimise(m_optimizeRuns); } +void Compiler::compileClone( + ContractDefinition const& _contract, + map const& _contracts +) +{ + m_context = CompilerContext(); // clear it just in case + initializeContext(_contract, _contracts); + + appendInitAndConstructorCode(_contract); + + //@todo determine largest return size of all runtime functions + eth::AssemblyItem runtimeSub = m_context.addSubroutine(getCloneRuntime()); + solAssert(runtimeSub.data() < numeric_limits::max(), ""); + m_runtimeSub = size_t(runtimeSub.data()); + + // stack contains sub size + m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; + m_context << u256(0) << eth::Instruction::RETURN; + + appendFunctionsWithoutCode(); + + if (m_optimize) + m_context.optimise(m_optimizeRuns); +} + eth::AssemblyItem Compiler::getFunctionEntryLabel(FunctionDefinition const& _function) const { return m_runtimeContext.getFunctionEntryLabelIfExists(_function); @@ -86,13 +101,14 @@ eth::AssemblyItem Compiler::getFunctionEntryLabel(FunctionDefinition const& _fun void Compiler::initializeContext(ContractDefinition const& _contract, map const& _contracts) { + CompilerUtils(m_context).initialiseFreeMemoryPointer(); m_context.setCompiledContracts(_contracts); m_context.setInheritanceHierarchy(_contract.getLinearizedBaseContracts()); registerStateVariables(_contract); m_context.resetVisitedNodes(&_contract); } -void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +void Compiler::appendInitAndConstructorCode(ContractDefinition const& _contract) { // Determine the arguments that are used for the base constructors. std::vector const& bases = _contract.getLinearizedBaseContracts(); @@ -126,22 +142,22 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp appendConstructor(*constructor); else if (auto c = m_context.getNextConstructor(_contract)) appendBaseConstructor(*c); +} + +void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +{ + appendInitAndConstructorCode(_contract); eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.getAssembly()); solAssert(runtimeSub.data() < numeric_limits::max(), ""); m_runtimeSub = size_t(runtimeSub.data()); + // stack contains sub size m_context << eth::Instruction::DUP1 << runtimeSub << u256(0) << eth::Instruction::CODECOPY; m_context << u256(0) << eth::Instruction::RETURN; // note that we have to include the functions again because of absolute jump labels - set functions = m_context.getFunctionsWithoutCode(); - while (!functions.empty()) - { - for (Declaration const* function: functions) - function->accept(*this); - functions = m_context.getFunctionsWithoutCode(); - } + appendFunctionsWithoutCode(); } void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -618,6 +634,20 @@ bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) return true; } +void Compiler::appendFunctionsWithoutCode() +{ + set functions = m_context.getFunctionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) + { + m_context.setStackOffset(0); + function->accept(*this); + } + functions = m_context.getFunctionsWithoutCode(); + } +} + void Compiler::appendModifierOrFunctionCode() { solAssert(m_currentFunction, ""); @@ -674,3 +704,21 @@ void Compiler::compileExpression(Expression const& _expression, TypePointer cons if (_targetType) CompilerUtils(m_context).convertType(*_expression.getType(), *_targetType); } + +eth::Assembly Compiler::getCloneRuntime() +{ + eth::Assembly a; + a << eth::Instruction::CALLDATASIZE; + a << u256(0) << eth::Instruction::DUP1 << eth::Instruction::CALLDATACOPY; + //@todo adjust for larger return values, make this dynamic. + a << u256(0x20) << u256(0) << eth::Instruction::CALLDATASIZE; + a << u256(0) << eth::Instruction::DUP1; + // this is the address which has to be substituted by the linker. + //@todo implement as special "marker" AssemblyItem. + a << u256("0xcafecafecafecafecafecafecafecafecafecafe"); + a << u256(eth::c_callGas + 10) << eth::Instruction::GAS << eth::Instruction::SUB; + a << eth::Instruction::CALLCODE; + //@todo adjust for larger return values, make this dynamic. + a << u256(0x20) << u256(0) << eth::Instruction::RETURN; + return a; +} diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index ac794f89e..bec2b0641 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -44,6 +44,12 @@ public: void compileContract(ContractDefinition const& _contract, std::map const& _contracts); + /// Compiles a contract that uses CALLCODE to call into a pre-deployed version of the given + /// contract at runtime, but contains the full creation-time code. + void compileClone( + ContractDefinition const& _contract, + std::map const& _contracts + ); bytes getAssembledBytecode() { return m_context.getAssembledBytecode(); } bytes getRuntimeBytecode() { return m_context.getAssembledRuntimeBytecode(m_runtimeSub); } /// @arg _sourceCodes is the map of input files to source code strings @@ -68,6 +74,8 @@ private: /// 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. void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + /// Appends state variable initialisation and constructor code. + void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); void appendConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); @@ -103,6 +111,9 @@ private: virtual bool visit(ExpressionStatement const& _expressionStatement) override; virtual bool visit(PlaceholderStatement const&) override; + /// Repeatedly visits all function which are referenced but which are not compiled yet. + void appendFunctionsWithoutCode(); + /// Appends one layer of function modifier code of the current function, or the function /// body itself if the last modifier was reached. void appendModifierOrFunctionCode(); @@ -110,6 +121,9 @@ private: void appendStackVariableInitialisation(VariableDeclaration const& _variable); void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); + /// @returns the runtime assembly for clone contracts. + static eth::Assembly getCloneRuntime(); + bool const m_optimize; unsigned const m_optimizeRuns; CompilerContext m_context; diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index f056bb9b1..a85738ebd 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -166,7 +166,13 @@ void CompilerStack::compile(bool _optimize, unsigned _runs) compiledContract.bytecode = compiler->getAssembledBytecode(); compiledContract.runtimeBytecode = compiler->getRuntimeBytecode(); compiledContract.compiler = move(compiler); + compiler = make_shared(_optimize, _runs); + compiler->compileContract(*contract, contractBytecode); contractBytecode[compiledContract.contract] = &compiledContract.bytecode; + + Compiler cloneCompiler(_optimize, _runs); + cloneCompiler.compileClone(*contract, contractBytecode); + compiledContract.cloneBytecode = cloneCompiler.getAssembledBytecode(); } } @@ -199,6 +205,11 @@ bytes const& CompilerStack::getRuntimeBytecode(string const& _contractName) cons return getContract(_contractName).runtimeBytecode; } +bytes const& CompilerStack::getCloneBytecode(string const& _contractName) const +{ + return getContract(_contractName).cloneBytecode; +} + dev::h256 CompilerStack::getContractCodeHash(string const& _contractName) const { return dev::sha3(getRuntimeBytecode(_contractName)); diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index a7c6ea3ba..735c4d156 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -99,6 +99,11 @@ public: bytes const& getBytecode(std::string const& _contractName = "") const; /// @returns the runtime bytecode for the contract, i.e. the code that is returned by the constructor. bytes const& getRuntimeBytecode(std::string const& _contractName = "") const; + /// @returns the bytecode of a contract that uses an already deployed contract via CALLCODE. + /// The returned bytes will contain a sequence of 20 bytes of the format "XXX...XXX" which have to + /// substituted by the actual address. Note that this sequence starts end ends in three X + /// characters but can contain anything in between. + bytes const& getCloneBytecode(std::string const& _contractName = "") const; /// @returns normal contract assembly items eth::AssemblyItems const* getAssemblyItems(std::string const& _contractName = "") const; /// @returns runtime contract assembly items @@ -167,6 +172,7 @@ private: std::shared_ptr compiler; bytes bytecode; bytes runtimeBytecode; + bytes cloneBytecode; std::shared_ptr interfaceHandler; mutable std::unique_ptr interface; mutable std::unique_ptr solidityInterface; diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1b1dbd3eb..e579d8839 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -63,6 +63,7 @@ static string const g_argAsmJsonStr = "asm-json"; static string const g_argAstStr = "ast"; static string const g_argAstJson = "ast-json"; static string const g_argBinaryStr = "binary"; +static string const g_argCloneBinaryStr = "clone-binary"; static string const g_argOpcodesStr = "opcodes"; static string const g_argNatspecDevStr = "natspec-dev"; static string const g_argNatspecUserStr = "natspec-user"; @@ -71,6 +72,7 @@ static string const g_argAddStandard = "add-std"; /// Possible arguments to for --combined-json static set const g_combinedJsonArgs{ "binary", + "clone-binary", "opcodes", "json-abi", "sol-abi", @@ -110,7 +112,8 @@ static bool needsHumanTargetedStdout(po::variables_map const& _args) humanTargetedStdout(_args, g_argAsmStr) || humanTargetedStdout(_args, g_argAsmJsonStr) || humanTargetedStdout(_args, g_argOpcodesStr) || - humanTargetedStdout(_args, g_argBinaryStr); + humanTargetedStdout(_args, g_argBinaryStr) || + humanTargetedStdout(_args, g_argCloneBinaryStr); } static inline bool outputToFile(OutputType type) @@ -140,18 +143,33 @@ static std::istream& operator>>(std::istream& _in, OutputType& io_output) void CommandLineInterface::handleBinary(string const& _contract) { - auto choice = m_args[g_argBinaryStr].as(); - if (outputToStdout(choice)) + if (m_args.count(g_argBinaryStr)) { - cout << "Binary: " << endl; - cout << toHex(m_compiler->getBytecode(_contract)) << endl; + if (outputToStdout(m_args[g_argBinaryStr].as())) + { + cout << "Binary: " << endl; + cout << toHex(m_compiler->getBytecode(_contract)) << endl; + } + if (outputToFile(m_args[g_argBinaryStr].as())) + { + ofstream outFile(_contract + ".binary"); + outFile << toHex(m_compiler->getBytecode(_contract)); + outFile.close(); + } } - - if (outputToFile(choice)) + if (m_args.count(g_argCloneBinaryStr)) { - ofstream outFile(_contract + ".binary"); - outFile << toHex(m_compiler->getBytecode(_contract)); - outFile.close(); + if (outputToStdout(m_args[g_argCloneBinaryStr].as())) + { + cout << "Clone Binary: " << endl; + cout << toHex(m_compiler->getCloneBytecode(_contract)) << endl; + } + if (outputToFile(m_args[g_argCloneBinaryStr].as())) + { + ofstream outFile(_contract + ".clone_binary"); + outFile << toHex(m_compiler->getCloneBytecode(_contract)); + outFile.close(); + } } } @@ -177,7 +195,7 @@ void CommandLineInterface::handleBytecode(string const& _contract) { if (m_args.count(g_argOpcodesStr)) handleOpcode(_contract); - if (m_args.count(g_argBinaryStr)) + if (m_args.count(g_argBinaryStr) || m_args.count(g_argCloneBinaryStr)) handleBinary(_contract); } @@ -329,6 +347,8 @@ bool CommandLineInterface::parseArguments(int argc, char** argv) "Request to output the Opcodes of the contract.") (g_argBinaryStr.c_str(), po::value()->value_name("stdout|file|both"), "Request to output the contract in binary (hexadecimal).") + (g_argCloneBinaryStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the clone contract in binary (hexadecimal).") (g_argAbiStr.c_str(), po::value()->value_name("stdout|file|both"), "Request to output the contract's JSON ABI interface.") (g_argSolAbiStr.c_str(), po::value()->value_name("stdout|file|both"), @@ -490,6 +510,8 @@ void CommandLineInterface::handleCombinedJSON() contractData["json-abi"] = m_compiler->getInterface(contractName); if (requests.count("binary")) contractData["binary"] = toHex(m_compiler->getBytecode(contractName)); + if (requests.count("clone-binary")) + contractData["clone-binary"] = toHex(m_compiler->getCloneBytecode(contractName)); if (requests.count("opcodes")) contractData["opcodes"] = eth::disassemble(m_compiler->getBytecode(contractName)); if (requests.count("asm"))