Merge pull request #10031 from ethereum/setImmutableArgument

Add another argument to setimmutable and the AssignImmutable opcode, allowing to modify code at any memory offset.
This commit is contained in:
Daniel Kirchner 2020-10-15 18:37:21 +02:00 committed by GitHub
commit 41000fea31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 71 additions and 39 deletions

View File

@ -1,6 +1,7 @@
### 0.8.0 (unreleased) ### 0.8.0 (unreleased)
Breaking Changes: Breaking Changes:
* Assembler: The artificial ASSIGNIMMUTABLE opcode and the corresponding builtin in the "EVM with object access" dialect of Yul take the base offset of the code to modify as additional argument.
* Type System: Unary negation can only be used on signed integers, not on unsigned integers. * Type System: Unary negation can only be used on signed integers, not on unsigned integers.
* Type System: Disallow explicit conversions from negative literals and literals larger than ``type(uint160).max`` to ``address`` type. * Type System: Disallow explicit conversions from negative literals and literals larger than ``type(uint160).max`` to ``address`` type.
* Parser: Exponentiation is right associative. ``a**b**c`` is parsed as ``a**(b**c)``. * Parser: Exponentiation is right associative. ``a**b**c`` is parsed as ``a**(b**c)``.

View File

@ -920,12 +920,12 @@ For the EVM, the ``datacopy`` function is equivalent to ``codecopy``.
setimmutable, loadimmutable setimmutable, loadimmutable
^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
The functions ``setimmutable("name", value)`` and ``loadimmutable("name")`` are The functions ``setimmutable(offset, "name", value)`` and ``loadimmutable("name")`` are
used for the immutable mechanism in Solidity and do not nicely map to pure Yul. used for the immutable mechanism in Solidity and do not nicely map to pure Yul.
The function ``setimmutable`` assumes that the runtime code of a contract The call to ``setimmutable(offset, "name", value)`` assumes that the runtime code of the contract
is currently copied to memory at offset zero. The call to ``setimmutable("name", value)`` containing the given named immutable was copied to memory at offset ``offset`` and will write ``value`` to all
will store ``value`` at all points in memory that contain a call to positions in memory (relative to ``offset``) that contain the placeholder that was generated for calls
``loadimmutable("name")``. to ``loadimmutable("name")`` in the runtime code.
linkersymbol linkersymbol

View File

@ -672,18 +672,30 @@ LinkerObject const& Assembly::assemble() const
ret.bytecode.resize(ret.bytecode.size() + 32); ret.bytecode.resize(ret.bytecode.size() + 32);
break; break;
case AssignImmutable: case AssignImmutable:
for (auto const& offset: immutableReferencesBySub[i.data()].second) {
auto const& offsets = immutableReferencesBySub[i.data()].second;
for (size_t i = 0; i < offsets.size(); ++i)
{ {
ret.bytecode.push_back(uint8_t(Instruction::DUP1)); if (i != offsets.size() - 1)
{
ret.bytecode.push_back(uint8_t(Instruction::DUP2));
ret.bytecode.push_back(uint8_t(Instruction::DUP2));
}
// TODO: should we make use of the constant optimizer methods for pushing the offsets? // TODO: should we make use of the constant optimizer methods for pushing the offsets?
bytes offsetBytes = toCompactBigEndian(u256(offset)); bytes offsetBytes = toCompactBigEndian(u256(offsets[i]));
ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size()); ret.bytecode.push_back(uint8_t(Instruction::PUSH1) - 1 + offsetBytes.size());
ret.bytecode += offsetBytes; ret.bytecode += offsetBytes;
ret.bytecode.push_back(uint8_t(Instruction::ADD));
ret.bytecode.push_back(uint8_t(Instruction::MSTORE)); ret.bytecode.push_back(uint8_t(Instruction::MSTORE));
} }
if (offsets.empty())
{
ret.bytecode.push_back(uint8_t(Instruction::POP));
ret.bytecode.push_back(uint8_t(Instruction::POP));
}
immutableReferencesBySub.erase(i.data()); immutableReferencesBySub.erase(i.data());
ret.bytecode.push_back(uint8_t(Instruction::POP));
break; break;
}
case PushDeployTimeAddress: case PushDeployTimeAddress:
ret.bytecode.push_back(uint8_t(Instruction::PUSH20)); ret.bytecode.push_back(uint8_t(Instruction::PUSH20));
ret.bytecode.resize(ret.bytecode.size() + 20); ret.bytecode.resize(ret.bytecode.size() + 20);

View File

@ -102,7 +102,7 @@ size_t AssemblyItem::arguments() const
if (type() == Operation) if (type() == Operation)
return static_cast<size_t>(instructionInfo(instruction()).args); return static_cast<size_t>(instructionInfo(instruction()).args);
else if (type() == AssignImmutable) else if (type() == AssignImmutable)
return 1; return 2;
else else
return 0; return 0;
} }

View File

@ -93,9 +93,13 @@ KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool
// can be ignored // can be ignored
} }
else if (_item.type() == AssignImmutable) else if (_item.type() == AssignImmutable)
{
// Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which // Since AssignImmutable breaks blocks, it should be fine to only consider its changes to the stack, which
// is the same as POP. // is the same as two POPs.
// Note that the StoreOperation for POP is generic and _copyItem is ignored.
feedItem(AssemblyItem(Instruction::POP), _copyItem);
return feedItem(AssemblyItem(Instruction::POP), _copyItem); return feedItem(AssemblyItem(Instruction::POP), _copyItem);
}
else if (_item.type() != Operation) else if (_item.type() != Operation)
{ {
assertThrow(_item.deposit() == 1, InvalidDeposit, ""); assertThrow(_item.deposit() == 1, InvalidDeposit, "");

View File

@ -203,7 +203,10 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont
{ {
auto slotNames = m_context.immutableVariableSlotNames(*immutable); auto slotNames = m_context.immutableVariableSlotNames(*immutable);
for (auto&& slotName: slotNames | boost::adaptors::reversed) for (auto&& slotName: slotNames | boost::adaptors::reversed)
{
m_context << u256(0);
m_context.appendImmutableAssignment(slotName); m_context.appendImmutableAssignment(slotName);
}
} }
if (!immutables.empty()) if (!immutables.empty())
m_context.pushSubroutineSize(m_context.runtimeSub()); m_context.pushSubroutineSize(m_context.runtimeSub());

View File

@ -558,7 +558,7 @@ string IRGenerator::deployCode(ContractDefinition const& _contract)
codecopy(0, dataoffset("<object>"), datasize("<object>")) codecopy(0, dataoffset("<object>"), datasize("<object>"))
<#storeImmutables> <#storeImmutables>
setimmutable("<immutableName>", <var>) setimmutable(0, "<immutableName>", <var>)
</storeImmutables> </storeImmutables>
return(0, datasize("<object>")) return(0, datasize("<object>"))

View File

@ -221,21 +221,22 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
)); ));
builtins.emplace(createFunction( builtins.emplace(createFunction(
"setimmutable", "setimmutable",
2, 3,
0, 0,
SideEffects{false, false, false, false, true, SideEffects::None, SideEffects::None, SideEffects::Write}, SideEffects{false, false, false, false, true, SideEffects::None, SideEffects::None, SideEffects::Write},
{LiteralKind::String, std::nullopt}, {std::nullopt, LiteralKind::String, std::nullopt},
[]( [](
FunctionCall const& _call, FunctionCall const& _call,
AbstractAssembly& _assembly, AbstractAssembly& _assembly,
BuiltinContext&, BuiltinContext&,
std::function<void(Expression const&)> _visitExpression std::function<void(Expression const&)> _visitExpression
) { ) {
yulAssert(_call.arguments.size() == 2, ""); yulAssert(_call.arguments.size() == 3, "");
_visitExpression(_call.arguments[1]); _visitExpression(_call.arguments[2]);
YulString identifier = std::get<Literal>(_call.arguments[1]).value;
_visitExpression(_call.arguments[0]);
_assembly.setSourceLocation(_call.location); _assembly.setSourceLocation(_call.location);
YulString identifier = std::get<Literal>(_call.arguments.front()).value;
_assembly.appendImmutableAssignment(identifier.str()); _assembly.appendImmutableAssignment(identifier.str());
} }
)); ));

View File

@ -91,7 +91,7 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
// PushDeployTimeAddress // PushDeployTimeAddress
_assembly.append(PushDeployTimeAddress); _assembly.append(PushDeployTimeAddress);
// AssignImmutable. // AssignImmutable.
// Note that since there is no reference to "someOtherImmutable", this will compile to a simple POP in the hex output. // Note that since there is no reference to "someOtherImmutable", this will just compile to two POPs in the hex output.
_assembly.appendImmutableAssignment("someOtherImmutable"); _assembly.appendImmutableAssignment("someOtherImmutable");
_assembly.append(u256(2)); _assembly.append(u256(2));
_assembly.appendImmutableAssignment("someImmutable"); _assembly.appendImmutableAssignment("someImmutable");
@ -105,8 +105,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
BOOST_CHECK_EQUAL( BOOST_CHECK_EQUAL(
_assembly.assemble().toHex(), _assembly.assemble().toHex(),
"5b6001600220606f73__$bf005014d9d0f534b8fcb268bd84c491a2$__" "5b6001600220606f73__$bf005014d9d0f534b8fcb268bd84c491a2$__"
"60005660676022604573000000000000000000000000000000000000000050" "6000566067602260457300000000000000000000000000000000000000005050"
"60028060015250" "600260010152"
"00fe" "00fe"
"7f0000000000000000000000000000000000000000000000000000000000000000" "7f0000000000000000000000000000000000000000000000000000000000000000"
"fe010203044266eeaa" "fe010203044266eeaa"
@ -184,8 +184,10 @@ BOOST_AUTO_TEST_CASE(immutable)
shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm); shared_ptr<Assembly> _subAsmPtr = make_shared<Assembly>(_subAsm);
_assembly.append(u256(42)); _assembly.append(u256(42));
_assembly.append(u256(0));
_assembly.appendImmutableAssignment("someImmutable"); _assembly.appendImmutableAssignment("someImmutable");
_assembly.append(u256(23)); _assembly.append(u256(23));
_assembly.append(u256(0));
_assembly.appendImmutableAssignment("someOtherImmutable"); _assembly.appendImmutableAssignment("someOtherImmutable");
auto sub = _assembly.appendSubroutine(_subAsmPtr); auto sub = _assembly.appendSubroutine(_subAsmPtr);
@ -198,21 +200,22 @@ BOOST_AUTO_TEST_CASE(immutable)
// root.asm // root.asm
// assign "someImmutable" // assign "someImmutable"
"602a" // PUSH1 42 - value for someImmutable "602a" // PUSH1 42 - value for someImmutable
"80" // DUP1 "6000" // PUSH1 0 - offset of code into which to insert the immutable
"8181" // DUP2 DUP2
"6001" // PUSH1 1 - offset of first someImmutable in sub_0 "6001" // PUSH1 1 - offset of first someImmutable in sub_0
"01" // ADD - add offset of immutable to offset of code
"52" // MSTORE "52" // MSTORE
"80" // DUP1
"6043" // PUSH1 67 - offset of second someImmutable in sub_0 "6043" // PUSH1 67 - offset of second someImmutable in sub_0
"01" // ADD - add offset of immutable to offset of code
"52" // MSTORE "52" // MSTORE
"50" // POP
// assign "someOtherImmutable" // assign "someOtherImmutable"
"6017" // PUSH1 23 - value for someOtherImmutable "6017" // PUSH1 23 - value for someOtherImmutable
"80" // DUP1 "6000" // PUSH1 0 - offset of code into which to insert the immutable
"6022" // PUSH1 34 - offset of someOtherImmutable in sub_0 "6022" // PUSH1 34 - offset of someOtherImmutable in sub_0
"01" // ADD - add offset of immutable to offset of code
"52" // MSTORE "52" // MSTORE
"50" // POP
"6063" // PUSH1 0x63 - dataSize(sub_0) "6063" // PUSH1 0x63 - dataSize(sub_0)
"6017" // PUSH1 0x17 - dataOffset(sub_0) "601b" // PUSH1 0x23 - dataOffset(sub_0)
"fe" // INVALID "fe" // INVALID
// end of root.asm // end of root.asm
// sub.asm // sub.asm
@ -224,8 +227,10 @@ BOOST_AUTO_TEST_CASE(immutable)
_assembly.assemblyString(), _assembly.assemblyString(),
" /* \"root.asm\":1:3 */\n" " /* \"root.asm\":1:3 */\n"
" 0x2a\n" " 0x2a\n"
" 0x00\n"
" assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n" " assignImmutable(\"0x26f2c0195e9d408feff3abd77d83f2971f3c9a18d1e8a9437c7835ae4211fc9f\")\n"
" 0x17\n" " 0x17\n"
" 0x00\n"
" assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n" " assignImmutable(\"0xc3978657661c4d8e32e3d5f42597c009f0d3859e9f9d0d94325268f9799e2bfb\")\n"
" dataSize(sub_0)\n" " dataSize(sub_0)\n"
" dataOffset(sub_0)\n" " dataOffset(sub_0)\n"
@ -242,8 +247,10 @@ BOOST_AUTO_TEST_CASE(immutable)
util::jsonCompactPrint(_assembly.assemblyJSON(indices)), util::jsonCompactPrint(_assembly.assemblyJSON(indices)),
"{\".code\":[" "{\".code\":["
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"2A\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"}," "{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"17\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"17\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH\",\"source\":0,\"value\":\"0\"},"
"{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"}," "{\"begin\":1,\"end\":3,\"name\":\"ASSIGNIMMUTABLE\",\"source\":0,\"value\":\"someOtherImmutable\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}," "{\"begin\":1,\"end\":3,\"name\":\"PUSH #[$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"},"
"{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}" "{\"begin\":1,\"end\":3,\"name\":\"PUSH [$]\",\"source\":0,\"value\":\"0000000000000000000000000000000000000000000000000000000000000000\"}"

View File

@ -1,6 +1,7 @@
object "a" { object "a" {
code { code {
setimmutable( setimmutable(
0,
"long___name___that___definitely___exceeds___the___thirty___two___byte___limit", "long___name___that___definitely___exceeds___the___thirty___two___byte___limit",
0x1234567890123456789012345678901234567890 0x1234567890123456789012345678901234567890
) )
@ -8,10 +9,12 @@ object "a" {
} }
// ---- // ----
// Assembly: // Assembly:
// /* "source":152:194 */ // /* "source":167:209 */
// 0x1234567890123456789012345678901234567890 // 0x1234567890123456789012345678901234567890
// /* "source":32:204 */ // /* "source":58:59 */
// 0x00
// /* "source":32:219 */
// assignImmutable("0x85a5b1db611c82c46f5fa18e39ae218397536256c451e5de155a86de843a9ad6") // assignImmutable("0x85a5b1db611c82c46f5fa18e39ae218397536256c451e5de155a86de843a9ad6")
// Bytecode: 73123456789012345678901234567890123456789050 // Bytecode: 73123456789012345678901234567890123456789060005050
// Opcodes: PUSH20 0x1234567890123456789012345678901234567890 POP // Opcodes: PUSH20 0x1234567890123456789012345678901234567890 PUSH1 0x0 POP POP
// SourceMappings: 152:42:0:-:0;32:172 // SourceMappings: 167:42:0:-:0;58:1;32:187

View File

@ -1,5 +1,5 @@
{ {
setimmutable(loadimmutable("abc"), "abc") setimmutable(0, loadimmutable("abc"), "abc")
} }
// ==== // ====
// dialect: evm // dialect: evm

View File

@ -1,5 +1,5 @@
{ {
setimmutable("address", 0x1234567890123456789012345678901234567890) setimmutable(0, "address", 0x1234567890123456789012345678901234567890)
} }
// ==== // ====
// dialect: evm // dialect: evm

View File

@ -1,11 +1,11 @@
{ {
setimmutable(0, 0x1234567890123456789012345678901234567890) setimmutable(0, 0, 0x1234567890123456789012345678901234567890)
setimmutable(true, 0x1234567890123456789012345678901234567890) setimmutable(0, true, 0x1234567890123456789012345678901234567890)
setimmutable(false, 0x1234567890123456789012345678901234567890) setimmutable(0, false, 0x1234567890123456789012345678901234567890)
} }
// ==== // ====
// dialect: evm // dialect: evm
// ---- // ----
// TypeError 5859: (19-20): Function expects string literal. // TypeError 5859: (22-23): Function expects string literal.
// TypeError 5859: (83-87): Function expects string literal. // TypeError 5859: (89-93): Function expects string literal.
// TypeError 5859: (150-155): Function expects string literal. // TypeError 5859: (159-164): Function expects string literal.

View File

@ -1,5 +1,6 @@
{ {
setimmutable( setimmutable(
0,
"long___name___that___definitely___exceeds___the___thirty___two___byte___limit", "long___name___that___definitely___exceeds___the___thirty___two___byte___limit",
0x1234567890123456789012345678901234567890 0x1234567890123456789012345678901234567890
) )