Skip to content

Commit

Permalink
Lower contract creation using the new operator to $zk_create intrinsics
Browse files Browse the repository at this point in the history
  • Loading branch information
abinavpp committed Mar 22, 2023
1 parent ff69adb commit e1c3eb8
Show file tree
Hide file tree
Showing 21 changed files with 175 additions and 37 deletions.
2 changes: 2 additions & 0 deletions libevmasm/Assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,12 +579,14 @@ LinkerObject const& Assembly::assemble() const
dataRef.insert(make_pair(h256(i.data()), ret.bytecode.size()));
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
break;
case ZKEVMPushSub:
case PushSub:
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
ret.bytecode.push_back(dataRefPush);
subRef.insert(make_pair(static_cast<size_t>(i.data()), ret.bytecode.size()));
ret.bytecode.resize(ret.bytecode.size() + bytesPerDataRef);
break;
case ZKEVMPushSubSize:
case PushSubSize:
{
assertThrow(i.data() <= numeric_limits<size_t>::max(), AssemblyException, "");
Expand Down
9 changes: 9 additions & 0 deletions libevmasm/Assembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ class Assembly
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
/// on the stack. @returns the pushsub assembly item.
AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; }
/// zkevm version of appendSubroutine():
AssemblyItem zkevmAppendSubroutine(AssemblyPointer const& _assembly)
{
m_subs.push_back(_assembly);
auto sub = AssemblyItem(ZKEVMPushSub, m_subs.size() - 1);
append(AssemblyItem(ZKEVMPushSubSize, size_t(sub.data())));
return sub;
}

void pushSubroutineSize(size_t _subRoutine) { append(newPushSubSize(_subRoutine)); }
/// Pushes the offset of the subroutine.
void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); }
Expand Down
18 changes: 18 additions & 0 deletions libevmasm/AssemblyItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ pair<string, string> AssemblyItem::nameAndData() const
return {"PUSH [$]", toString(util::h256(data()))};
case PushSubSize:
return {"PUSH #[$]", toString(util::h256(data()))};
case ZKEVMPushSub:
return {"$ZK_PUSH [$]", toString(util::h256(data()))};
case ZKEVMPushSubSize:
return {"$ZK_PUSH #[$]", toString(util::h256(data()))};
case PushProgramSize:
return {"PUSHSIZE", ""};
case PushLibraryAddress:
Expand Down Expand Up @@ -126,11 +130,13 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision)
case Push:
return 1 + max<size_t>(1, numberEncodingSize(data()));
case PushSubSize:
case ZKEVMPushSubSize:
case PushProgramSize:
return 1 + 4; // worst case: a 16MB program
case PushTag:
case PushData:
case PushSub:
case ZKEVMPushSub:
return 1 + _addressLength;
case PushLibraryAddress:
case PushDeployTimeAddress:
Expand Down Expand Up @@ -188,6 +194,8 @@ size_t AssemblyItem::returnValues() const
case PushData:
case PushSub:
case PushSubSize:
case ZKEVMPushSub:
case ZKEVMPushSubSize:
case PushProgramSize:
case PushLibraryAddress:
case PushImmutable:
Expand Down Expand Up @@ -216,6 +224,8 @@ bool AssemblyItem::canBeFunctional() const
case PushData:
case PushSub:
case PushSubSize:
case ZKEVMPushSub:
case ZKEVMPushSubSize:
case PushProgramSize:
case PushLibraryAddress:
case PushDeployTimeAddress:
Expand Down Expand Up @@ -277,6 +287,8 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
break;
case PushSub:
case PushSubSize:
case ZKEVMPushSub:
case ZKEVMPushSubSize:
{
vector<string> subPathComponents;
for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast<size_t>(data())))
Expand Down Expand Up @@ -353,9 +365,15 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case PushSub:
_out << " PushSub " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case ZKEVMPushSub:
_out << " ZKEVMPushSub " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case PushSubSize:
_out << " PushSubSize " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case ZKEVMPushSubSize:
_out << " ZKEVMPushSubSize " << hex << static_cast<size_t>(_item.data()) << dec;
break;
case PushProgramSize:
_out << " PushProgramSize";
break;
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/AssemblyItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ enum AssemblyItemType
PushTag,
PushSub,
PushSubSize,
ZKEVMPushSub,
ZKEVMPushSubSize,
PushProgramSize,
Tag,
PushData,
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/GasMeter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _
case PushData:
case PushSub:
case PushSubSize:
case ZKEVMPushSub:
case ZKEVMPushSubSize:
case PushProgramSize:
case PushLibraryAddress:
case PushDeployTimeAddress:
Expand Down
6 changes: 6 additions & 0 deletions libevmasm/Instruction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,9 @@ std::map<std::string, Instruction> const solidity::evmasm::c_instructions =
{ "LOG2", Instruction::LOG2 },
{ "LOG3", Instruction::LOG3 },
{ "LOG4", Instruction::LOG4 },
{ "$ZK_CREATE", Instruction::ZK_CREATE },
{ "$ZK_CREATE2", Instruction::ZK_CREATE2 },
{ "$ZK_DATACOPY", Instruction::ZK_DATACOPY },
{ "CREATE", Instruction::CREATE },
{ "CALL", Instruction::CALL },
{ "CALLCODE", Instruction::CALLCODE },
Expand Down Expand Up @@ -309,6 +312,9 @@ static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{ Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } },
{ Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } },
{ Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } },
{ Instruction::ZK_CREATE, {"$ZK_CREATE", 0, 3, 1, true, Tier::Special } },
{ Instruction::ZK_CREATE2, {"$ZK_CREATE2", 0, 4, 1, true, Tier::Special } },
{ Instruction::ZK_DATACOPY, {"$ZK_DATACOPY", 0, 3, 0, true, Tier::Special } },
{ Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } },
{ Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } },
{ Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
Expand Down
4 changes: 4 additions & 0 deletions libevmasm/Instruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ enum class Instruction: uint8_t
LOG3, ///< Makes a log entry; 3 topics.
LOG4, ///< Makes a log entry; 4 topics.

ZK_CREATE = 0xb0, ///< ZKEVM version of CREATE
ZK_CREATE2, ///< ZKEVM version of CREATE2
ZK_DATACOPY, ///< ZKEVM version of yul's datacopy instruction

CREATE = 0xf0, ///< create a new account with associated code
CALL, ///< message-call into an account
CALLCODE, ///< message-call with another account's code only
Expand Down
5 changes: 5 additions & 0 deletions libsolidity/codegen/CompilerContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,11 @@ class CompilerContext
/// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
/// on the stack. @returns the pushsub assembly item.
evmasm::AssemblyItem addSubroutine(evmasm::AssemblyPointer const& _assembly) { return m_asm->appendSubroutine(_assembly); }
/// zkevm version of addSubroutine():
evmasm::AssemblyItem zkevmAddSubroutine(evmasm::AssemblyPointer const& _assembly)
{
return m_asm->zkevmAppendSubroutine(_assembly);
}
/// Pushes the size of the subroutine.
void pushSubroutineSize(size_t _subRoutine) { m_asm->pushSubroutineSize(_subRoutine); }
/// Pushes the offset of the subroutine.
Expand Down
12 changes: 7 additions & 5 deletions libsolidity/codegen/CompilerUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1507,24 +1507,26 @@ void CompilerUtils::computeHashStatic()
m_context << u256(32) << u256(0) << Instruction::KECCAK256;
}

void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation)
void CompilerUtils::copyContractCodeToMemory(ContractDefinition const& contract, bool _creation, bool _zkevm)
{
string which = _creation ? "Creation" : "Runtime";
m_context.callLowLevelFunction(
"$copyContract" + which + "CodeToMemory_" + contract.type()->identifier(),
1,
1,
[&contract, _creation](CompilerContext& _context)
[&contract, _creation, _zkevm](CompilerContext& _context)
{
// copy the contract's code into memory
shared_ptr<evmasm::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;
if (_zkevm)
_context << Instruction::DUP1 << _context.zkevmAddSubroutine(assembly);
else
_context << Instruction::DUP1 << _context.addSubroutine(assembly);
_context << Instruction::DUP4 << (_zkevm ? Instruction::ZK_DATACOPY : Instruction::CODECOPY);
_context << Instruction::ADD;
}
);
Expand Down
3 changes: 2 additions & 1 deletion libsolidity/codegen/CompilerUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ class CompilerUtils
/// Stack pre: Memory position
/// Stack post: Updated memory position
/// @param creation if true, copies creation code, if false copies runtime code.
/// @param _zkevem enables zkevm specific lowering
/// @note the contract has to be compiled already, so beware of cyclic dependencies!
void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode);
void copyContractCodeToMemory(ContractDefinition const& contract, bool _creationCode, bool _zkevm = false);

/// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier.
Expand Down
6 changes: 3 additions & 3 deletions libsolidity/codegen/ExpressionCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
ContractDefinition const* contract =
&dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
utils().fetchFreeMemoryPointer();
utils().copyContractCodeToMemory(*contract, true);
utils().copyContractCodeToMemory(*contract, true, /*zkevm=*/true);
utils().abiEncode(argumentTypes, function.parameterTypes());
// now on stack: [salt], [value], memory_end_ptr
// need: [salt], size, offset, value
Expand All @@ -730,9 +730,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)

// now: [salt], [value], [salt], size, offset, value
if (function.saltSet())
m_context << Instruction::CREATE2;
m_context << Instruction::ZK_CREATE2;
else
m_context << Instruction::CREATE;
m_context << Instruction::ZK_CREATE;

// now: [salt], [value], address

Expand Down
8 changes: 4 additions & 4 deletions libsolidity/codegen/ir/IRGeneratorForStatements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1483,14 +1483,14 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
m_context.subObjectsCreated().insert(contract);

Whiskers t(R"(let <memPos> := <allocateUnbounded>()
let <memEnd> := add(<memPos>, datasize("<object>"))
let <memEnd> := add(<memPos>, $zk_datasize("<object>"))
if or(gt(<memEnd>, 0xffffffffffffffff), lt(<memEnd>, <memPos>)) { <panic>() }
datacopy(<memPos>, dataoffset("<object>"), datasize("<object>"))
$zk_datacopy(<memPos>, $zk_dataoffset("<object>"), $zk_datasize("<object>"))
<memEnd> := <abiEncode>(<memEnd><constructorParams>)
<?saltSet>
let <address> := create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
let <address> := $zk_create2(<value>, <memPos>, sub(<memEnd>, <memPos>), <salt>)
<!saltSet>
let <address> := create(<value>, <memPos>, sub(<memEnd>, <memPos>))
let <address> := $zk_create(<value>, <memPos>, sub(<memEnd>, <memPos>))
</saltSet>
<?isTryCall>
let <success> := iszero(iszero(<address>))
Expand Down
15 changes: 15 additions & 0 deletions libyul/backends/evm/AbstractAssembly.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#pragma once

#include "liblangutil/Exceptions.h"
#include <libyul/ASTForward.h>

#include <libsolutil/Common.h>
Expand Down Expand Up @@ -116,6 +117,20 @@ class AbstractAssembly

/// Mark this assembly as invalid. Any attempt to request bytecode from it should throw.
virtual void markAsInvalid() = 0;

/// zkevm version of appendDataOffset()
virtual void appendZKEVMDataOffset(std::vector<SubID> const& _subPath)
{
(void) _subPath;
solUnimplemented("ZKEVM dataoffset lowering not implemented");
}

/// zkevm version of appendDataSize()
virtual void appendZKEVMDataSize(std::vector<SubID> const& _subPath)
{
(void) _subPath;
solUnimplemented("ZKEVM datasize lowering not implemented");
}
};

enum class IdentifierContext { LValue, RValue, VariableDeclaration, NonExternal };
Expand Down
43 changes: 43 additions & 0 deletions libyul/backends/evm/EVMDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,49 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
})
);

builtins.emplace(createFunction("$zk_datasize", 1, 1, SideEffects{}, {LiteralKind::String}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context
) {
yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, "");
Expression const& arg = _call.arguments.front();
YulString dataName = std::get<Literal>(arg).value;
if (_context.currentObject->name == dataName)
_assembly.appendAssemblySize();
else
{
vector<size_t> subIdPath =
_context.subIDs.count(dataName) == 0 ?
_context.currentObject->pathToSubObject(dataName) :
vector<size_t>{_context.subIDs.at(dataName)};
yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">.");
_assembly.appendZKEVMDataSize(subIdPath);
}
}));
builtins.emplace(createFunction("$zk_dataoffset", 1, 1, SideEffects{}, {LiteralKind::String}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext& _context
) {
yulAssert(_context.currentObject, "No object available.");
yulAssert(_call.arguments.size() == 1, "");
Expression const& arg = _call.arguments.front();
YulString dataName = std::get<Literal>(arg).value;
if (_context.currentObject->name == dataName)
_assembly.appendConstant(0);
else
{
vector<size_t> subIdPath =
_context.subIDs.count(dataName) == 0 ?
_context.currentObject->pathToSubObject(dataName) :
vector<size_t>{_context.subIDs.at(dataName)};
yulAssert(!subIdPath.empty(), "Could not find assembly object <" + dataName.str() + ">.");
_assembly.appendZKEVMDataOffset(subIdPath);
}
}));

builtins.emplace(createFunction("datasize", 1, 1, SideEffects{}, {LiteralKind::String}, [](
FunctionCall const& _call,
AbstractAssembly& _assembly,
Expand Down
24 changes: 24 additions & 0 deletions libyul/backends/evm/EthAssemblyAdapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,18 @@ void EthAssemblyAdapter::appendDataOffset(vector<AbstractAssembly::SubID> const&
m_assembly.pushSubroutineOffset(m_assembly.encodeSubPath(_subPath));
}

void EthAssemblyAdapter::appendZKEVMDataOffset(vector<AbstractAssembly::SubID> const& _subPath)
{
if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
{
yulAssert(_subPath.size() == 1, "");
m_assembly << evmasm::AssemblyItem(evmasm::PushData, it->second);
return;
}

m_assembly.append(evmasm::AssemblyItem(evmasm::ZKEVMPushSub, m_assembly.encodeSubPath(_subPath)));
}

void EthAssemblyAdapter::appendDataSize(vector<AbstractAssembly::SubID> const& _subPath)
{
if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
Expand All @@ -153,6 +165,18 @@ void EthAssemblyAdapter::appendDataSize(vector<AbstractAssembly::SubID> const& _
m_assembly.pushSubroutineSize(m_assembly.encodeSubPath(_subPath));
}

void EthAssemblyAdapter::appendZKEVMDataSize(vector<AbstractAssembly::SubID> const& _subPath)
{
if (auto it = m_dataHashBySubId.find(_subPath[0]); it != m_dataHashBySubId.end())
{
yulAssert(_subPath.size() == 1, "");
m_assembly << u256(m_assembly.data(h256(it->second)).size());
return;
}

m_assembly.append(evmasm::AssemblyItem(evmasm::ZKEVMPushSubSize, m_assembly.encodeSubPath(_subPath)));
}

AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data)
{
evmasm::AssemblyItem pushData = m_assembly.newData(_data);
Expand Down
2 changes: 2 additions & 0 deletions libyul/backends/evm/EthAssemblyAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class EthAssemblyAdapter: public AbstractAssembly
std::pair<std::shared_ptr<AbstractAssembly>, SubID> createSubAssembly(bool _creation, std::string _name = {}) override;
void appendDataOffset(std::vector<SubID> const& _subPath) override;
void appendDataSize(std::vector<SubID> const& _subPath) override;
void appendZKEVMDataOffset(std::vector<SubID> const& _subPath) override;
void appendZKEVMDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;

void appendToAuxiliaryData(bytes const& _data) override;
Expand Down
Loading

0 comments on commit e1c3eb8

Please sign in to comment.