Skip to content

Commit

Permalink
feat: enable library linking. close #13
Browse files Browse the repository at this point in the history
  • Loading branch information
vuilder0 committed Jan 14, 2022
1 parent 2fa0c7e commit c4c2998
Show file tree
Hide file tree
Showing 17 changed files with 1,919 additions and 94 deletions.
10 changes: 5 additions & 5 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ AssemblyItem Assembly::namedTag(string const& _name)

AssemblyItem Assembly::newPushLibraryAddress(string const& _identifier)
{
h256 h(util::blake2b(_identifier)); // Solidity++: keccak256 -> blake2b
h256 h(util::keccak256(_identifier));
m_libraries[h] = _identifier;
return AssemblyItem{PushLibraryAddress, h};
}
Expand Down Expand Up @@ -675,9 +675,9 @@ LinkerObject const& Assembly::assemble() const
break;
}
case PushLibraryAddress:
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH20));
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH21)); // Solidity++: 168-bit address
ret.linkReferences[ret.bytecode.size()] = m_libraries.at(i.data());
ret.bytecode.resize(ret.bytecode.size() + 20);
ret.bytecode.resize(ret.bytecode.size() + 21); // Solidity++: 168-bit address
break;
case PushImmutable:
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH32));
Expand Down Expand Up @@ -711,8 +711,8 @@ LinkerObject const& Assembly::assemble() const
break;
}
case PushDeployTimeAddress:
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH20));
ret.bytecode.resize(ret.bytecode.size() + 20);
ret.bytecode.push_back(static_cast<uint8_t>(Instruction::PUSH21)); // Solidity++: 168-bit address
ret.bytecode.resize(ret.bytecode.size() + 21); // Solidity++: 168-bit address
break;
case Tag:
assertThrow(i.data() != 0, AssemblyException, "Invalid tag position.");
Expand Down
1 change: 1 addition & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include <libsolutil/Common.h>
#include <libsolutil/Assertions.h>
#include <libsolutil/Keccak256.h>
#include <libsolutil/Blake2.h>

#include <json/json.h>
Expand Down
2 changes: 1 addition & 1 deletion libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
return 1 + _addressLength;
case PushLibraryAddress:
case PushDeployTimeAddress:
return 1 + 20;
return 1 + 21; // Solidity++: 168-bit address
case PushImmutable:
return 1 + 32;
case AssignImmutable:
Expand Down
4 changes: 2 additions & 2 deletions libevmasm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ set(sources
${ORIGINAL_SOURCE_DIR}/JumpdestRemover.h
${ORIGINAL_SOURCE_DIR}/KnownState.cpp
${ORIGINAL_SOURCE_DIR}/KnownState.h
${ORIGINAL_SOURCE_DIR}/LinkerObject.cpp
${ORIGINAL_SOURCE_DIR}/LinkerObject.h
LinkerObject.cpp
LinkerObject.h
${ORIGINAL_SOURCE_DIR}/PathGasMeter.cpp
${ORIGINAL_SOURCE_DIR}/PathGasMeter.h
${ORIGINAL_SOURCE_DIR}/PeepholeOptimiser.cpp
Expand Down
92 changes: 92 additions & 0 deletions libevmasm/LinkerObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/** @file LinkerObject.cpp
* @author Christian R <c@ethdev.com>
* @date 2015
*/

#include <libevmasm/LinkerObject.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/Keccak256.h>

using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::evmasm;

void LinkerObject::append(LinkerObject const& _other)
{
for (auto const& ref: _other.linkReferences)
linkReferences[ref.first + bytecode.size()] = ref.second;
bytecode += _other.bytecode;
}

// Solidity++: 168-bit address
void LinkerObject::link(map<string, h168> const& _libraryAddresses)
{
std::map<size_t, std::string> remainingRefs;
for (auto const& linkRef: linkReferences)
if (h168 const* address = matchLibrary(linkRef.second, _libraryAddresses))
copy(address->data(), address->data() + 20, bytecode.begin() + vector<uint8_t>::difference_type(linkRef.first));
else
remainingRefs.insert(linkRef);
linkReferences.swap(remainingRefs);
}

string LinkerObject::toHex() const
{
string hex = solidity::util::toHex(bytecode);
for (auto const& ref: linkReferences)
{
size_t pos = ref.first * 2;
string hash = libraryPlaceholder(ref.second);
hex[pos] = hex[pos + 1] = hex[pos + 38] = hex[pos + 39] = '_';
// Solidity++: Vite use 168-bit address, placeholder is end with '____'
hex[pos + 40] = hex[pos + 41] = '_';

for (size_t i = 0; i < 36; ++i)
hex[pos + 2 + i] = hash.at(i);
}
return hex;
}

string LinkerObject::libraryPlaceholder(string const& _libraryName)
{
return "$" + keccak256(_libraryName).hex().substr(0, 34) + "$";
}

// Solidity++: 168-bit address
h168 const*
LinkerObject::matchLibrary(
string const& _linkRefName,
map<string, h168> const& _libraryAddresses
)
{
auto it = _libraryAddresses.find(_linkRefName);
if (it != _libraryAddresses.end())
return &it->second;
// If the user did not supply a fully qualified library name,
// try to match only the simple library name
size_t colon = _linkRefName.find(':');
if (colon == string::npos)
return nullptr;
it = _libraryAddresses.find(_linkRefName.substr(colon + 1));
if (it != _libraryAddresses.end())
return &it->second;
return nullptr;
}
71 changes: 71 additions & 0 deletions libevmasm/LinkerObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/** @file Assembly.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/

#pragma once

#include <libsolutil/Common.h>
#include <libsolutil/FixedHash.h>

namespace solidity::evmasm
{

/**
* Binary object that potentially still needs to be linked (i.e. addresses of other contracts
* need to be filled in).
*/
struct LinkerObject
{
/// The bytecode.
bytes bytecode;

/// Map from offsets in bytecode to library identifiers. The addresses starting at those offsets
/// need to be replaced by the actual addresses by the linker.
std::map<size_t, std::string> linkReferences;

/// Map from hashes of the identifiers of immutable variables to the full identifier of the immutable and
/// to a list of offsets into the bytecode that refer to their values.
std::map<u256, std::pair<std::string, std::vector<size_t>>> immutableReferences;

/// Appends the bytecode of @a _other and incorporates its link references.
void append(LinkerObject const& _other);

/// Links the given libraries by replacing their uses in the code and removes them from the references.
void link(std::map<std::string, util::h168> const& _libraryAddresses); // Solidity++: 168-bit address

/// @returns a hex representation of the bytecode of the given object, replacing unlinked
/// addresses by placeholders. This output is lowercase.
std::string toHex() const;

/// @returns a 36 character string that is used as a placeholder for the library
/// address (enclosed by `__` on both sides). The placeholder is the hex representation
/// of the first 18 bytes of the keccak-256 hash of @a _libraryName.
static std::string libraryPlaceholder(std::string const& _libraryName);

private:
// Solidity++: 168-bit address
static util::h168 const* matchLibrary(
std::string const& _linkRefName,
std::map<std::string, util::h168> const& _libraryAddresses
);
};

}
4 changes: 2 additions & 2 deletions libsolidity/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ set(sources
${ORIGINAL_SOURCE_DIR}/interface/Natspec.h
${ORIGINAL_SOURCE_DIR}/interface/OptimiserSettings.h
${ORIGINAL_SOURCE_DIR}/interface/ReadFile.h
${ORIGINAL_SOURCE_DIR}/interface/StandardCompiler.cpp
${ORIGINAL_SOURCE_DIR}/interface/StandardCompiler.h
interface/StandardCompiler.cpp
interface/StandardCompiler.h
${ORIGINAL_SOURCE_DIR}/interface/StorageLayout.cpp
${ORIGINAL_SOURCE_DIR}/interface/StorageLayout.h
interface/Version.cpp
Expand Down
44 changes: 25 additions & 19 deletions libsolidity/codegen/ContractCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,9 @@ void ContractCompiler::compileContract(
debug(" - " + entry.toAssemblyText(m_context.assembly()) + functionType->toString(true));
}


// Solidity++: Will append function selector after compiling await expressions
if (!_contract.isLibrary()) // Solidity++: Libraries don't have function selectors
{
debug("Jump to function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly()));
m_context.appendJumpTo(m_functionSelectorTag);
}
debug("Jump to function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly()));
m_context.appendJumpTo(m_functionSelectorTag);

debug("Compiled.");
}
Expand Down Expand Up @@ -241,6 +237,9 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
appendMissingFunctions();
m_runtimeCompiler->appendMissingFunctions();

// Solidity++: Moved from compileContract()
m_runtimeCompiler->appendFunctionSelector(_contract);

// Solidity++: Moved from appendMissingFunctions()
m_runtimeCompiler->m_context.appendYulUtilityFunctions(m_optimiserSettings);
m_context.appendYulUtilityFunctions(m_optimiserSettings);
Expand All @@ -251,21 +250,25 @@ size_t ContractCompiler::deployLibrary(ContractDefinition const& _contract)
m_context.pushSubroutineSize(m_context.runtimeSub());
m_context.pushSubroutineOffset(m_context.runtimeSub());
// This code replaces the address added by appendDeployTimeAddress().
// Solidity++: use 168-bit address
m_context.appendInlineAssembly(
Whiskers(R"(
Whiskers(R"(
{
// If code starts at 11, an mstore(0) writes to the full PUSH20 plus data
// If code starts at 10, an mstore(0) writes to the full PUSH21 plus data
// without the need for a shift.
let codepos := 11
let codepos := 10
codecopy(codepos, subOffset, subSize)
// Check that the first opcode is a PUSH20
if iszero(eq(0x73, byte(0, mload(codepos)))) {
// Check that the first opcode is a PUSH21
if iszero(eq(0x74, byte(0, mload(codepos)))) {
mstore(0, <panicSig>)
mstore(4, 0)
revert(0, 0x24)
}
// code in memory: [10 bytes] [PUSH21] [00..... 21 bytes]
mstore(0, address())
mstore8(codepos, 0x73)
// code in memory: [10 bytes] [00] [address 21 bytes]
mstore8(codepos, 0x74)
// code in memory: [10 bytes] [PUSH21] [address 21 bytes]
return(codepos, subSize)
}
)")("panicSig", util::selectorFromSignature("Panic(uint256)").str()).render(),
Expand Down Expand Up @@ -330,7 +333,7 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor)
void ContractCompiler::appendDelegatecallCheck()
{
// Special constant that will be replaced by the address at deploy time.
// At compilation time, this is just "PUSH20 00...000".
// At compilation time, this is just "PUSH21 00...000".
m_context.appendDeployTimeAddress();
m_context << Instruction::ADDRESS << Instruction::EQ;
// The result on the stack is
Expand Down Expand Up @@ -422,11 +425,9 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
solAssert(m_context.stackHeight() == 1, "CALL / DELEGATECALL flag expected.");
}

if (!_contract.isLibrary()) // Solidity++: Libraries don't have function selectors
{
debug("Append function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly()));
m_context << m_functionSelectorTag;
}
// Solidity++: Add tag
debug("Append function selector " + m_functionSelectorTag.toAssemblyText(m_context.assembly()));
m_context << m_functionSelectorTag;

FunctionDefinition const* fallback = _contract.fallbackFunction();
solAssert(!_contract.isLibrary() || !fallback, "Libraries can't have fallback functions");
Expand Down Expand Up @@ -528,6 +529,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
// Calldata unpacker of interface functions
for (auto const& it: interfaceFunctions)
{
m_context.setStackOffset(1);
FunctionTypePointer const& functionType = it.second;
solAssert(functionType->hasDeclaration(), "");
CompilerContext::LocationSetter locationSetter(m_context, functionType->declaration());
Expand All @@ -542,7 +544,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
m_context << dupInstruction(2);
m_context.appendConditionalRevert(false, "Non-view function of library called without DELEGATECALL");
}

m_context.setStackOffset(0);
// We have to allow this for libraries, because value of the previous
// call is still visible in the delegatecall.
if (!functionType->isPayable() && !_contract.isLibrary() && needToAddCallvalueCheck)
Expand Down Expand Up @@ -1446,6 +1448,8 @@ void ContractCompiler::endVisit(Block const& _block)
void ContractCompiler::appendMissingFunctions()
{
debug("Append missing functions");
// Solidity++: save original stack height
auto stackHeight = m_context.stackHeight();
while (Declaration const* function = m_context.nextFunctionToCompile())
{
debug(" - " + function->name());
Expand All @@ -1456,6 +1460,8 @@ void ContractCompiler::appendMissingFunctions()
m_context.appendMissingLowLevelFunctions();
// Solidity++: append Yul functions after appending function selector
// m_context.appendYulUtilityFunctions(m_optimiserSettings);
// Solidity++: restore original stack height
m_context.setStackOffset((int)stackHeight);
}

void ContractCompiler::appendModifierOrFunctionCode()
Expand Down
Loading

0 comments on commit c4c2998

Please sign in to comment.