Support metadata via IR.

This commit is contained in:
chriseth 2021-06-08 16:35:37 +02:00
parent ff3eca4ccc
commit 0df8a38e55
55 changed files with 211 additions and 29 deletions

View File

@ -1,6 +1,7 @@
### 0.8.6 (unreleased)
Language Features:
* Yul: Special meaning of ``".metadata"`` data object in Yul object.
Compiler Features:

View File

@ -1076,6 +1076,23 @@ regular strings in native encoding. For code,
Above, ``Block`` refers to ``Block`` in the Yul code grammar explained in the previous chapter.
.. note::
Data objects or sub-objects whose names contain a ``.`` can be defined
but it is not possible to access them through ``datasize``,
``dataoffset`` or ``datacopy`` because ``.`` is used as a separator
to access objects inside another object.
.. note::
The data object called ``".metadata"`` has a special meaning:
It cannot be accessed from code and is always appended to the very end of the
bytecode, regardless of its position in the object.
Other data objects with special significance might be added in the
future, but their names will always start with a ``.``.
An example Yul Object is shown below:
.. code-block:: yul

View File

@ -92,7 +92,7 @@ public:
void pushSubroutineOffset(size_t _subRoutine) { append(AssemblyItem(PushSub, _subRoutine)); }
/// Appends @a _data literally to the very end of the bytecode.
void appendAuxiliaryDataToEnd(bytes const& _data) { m_auxiliaryData += _data; }
void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; }
/// Returns the assembly items.
AssemblyItems const& items() const { return m_items; }

View File

@ -38,7 +38,7 @@ void Compiler::compileContract(
{
ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimiserSettings);
runtimeCompiler.compileContract(_contract, _otherCompilers);
m_runtimeContext.appendAuxiliaryData(_metadata);
m_runtimeContext.appendToAuxiliaryData(_metadata);
// This might modify m_runtimeContext because it can access runtime functions at
// creation time.

View File

@ -279,7 +279,7 @@ public:
void optimizeYul(yul::Object& _object, yul::EVMDialect const& _dialect, OptimiserSettings const& _optimiserSetting, std::set<yul::YulString> const& _externalIdentifiers = {});
/// Appends arbitrary data to the end of the bytecode.
void appendAuxiliaryData(bytes const& _data) { m_asm->appendAuxiliaryDataToEnd(_data); }
void appendToAuxiliaryData(bytes const& _data) { m_asm->appendToAuxiliaryData(_data); }
/// Run optimisation step.
void optimise(OptimiserSettings const& _settings) { m_asm->optimise(translateOptimiserSettings(_settings)); }

View File

@ -90,10 +90,11 @@ set<CallableDeclaration const*, ASTNode::CompareByID> collectReachableCallables(
pair<string, string> IRGenerator::run(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
map<ContractDefinition const*, string_view const> const& _otherYulSources
)
{
string const ir = yul::reindent(generate(_contract, _otherYulSources));
string const ir = yul::reindent(generate(_contract, _cborMetadata, _otherYulSources));
yul::AssemblyStack asmStack(m_evmVersion, yul::AssemblyStack::Language::StrictAssembly, m_optimiserSettings);
if (!asmStack.parseAndAnalyze("", ir))
@ -118,6 +119,7 @@ pair<string, string> IRGenerator::run(
string IRGenerator::generate(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
map<ContractDefinition const*, string_view const> const& _otherYulSources
)
{
@ -152,6 +154,7 @@ string IRGenerator::generate(
<deployedFunctions>
}
<deployedSubObjects>
data "<metadataName>" hex"<cborMetadata>"
}
<subObjects>
}
@ -207,6 +210,9 @@ string IRGenerator::generate(
generateInternalDispatchFunctions();
t("deployedFunctions", m_context.functionCollector().requestedFunctions());
t("deployedSubObjects", subObjectSources(m_context.subObjectsCreated()));
t("metadataName", yul::Object::metadataName());
t("cborMetadata", toHex(_cborMetadata));
// This has to be called only after all other code generation for the deployed object is complete.
bool deployedInvolvesAssembly = m_context.inlineAssemblySeen();

View File

@ -54,12 +54,14 @@ public:
/// (or just pretty-printed, depending on the optimizer settings).
std::pair<std::string, std::string> run(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources
);
private:
std::string generate(
ContractDefinition const& _contract,
bytes const& _cborMetadata,
std::map<ContractDefinition const*, std::string_view const> const& _otherYulSources
);
std::string generate(Block const& _block);

View File

@ -1351,7 +1351,11 @@ void CompilerStack::generateIR(ContractDefinition const& _contract)
otherYulSources.emplace(pair.second.contract, pair.second.yulIR);
IRGenerator generator(m_evmVersion, m_revertStrings, m_optimiserSettings);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(_contract, otherYulSources);
tie(compiledContract.yulIR, compiledContract.yulIROptimized) = generator.run(
_contract,
createCBORMetadata(compiledContract),
otherYulSources
);
}
void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
@ -1375,8 +1379,6 @@ void CompilerStack::generateEVMFromIR(ContractDefinition const& _contract)
//cout << yul::AsmPrinter{}(*stack.parserResult()->code) << endl;
// TODO: support passing metadata
string deployedName = IRNames::deployedObject(_contract);
solAssert(!deployedName.empty(), "");
tie(compiledContract.evmAssembly, compiledContract.evmRuntimeAssembly) = stack.assembleEVMWithDeployed(deployedName);

View File

@ -65,10 +65,15 @@ string Object::toString(Dialect const* _dialect) const
set<YulString> Object::qualifiedDataNames() const
{
set<YulString> qualifiedNames = name.empty() ? set<YulString>{} : set<YulString>{name};
set<YulString> qualifiedNames =
name.empty() || contains(name.str(), '.') ?
set<YulString>{} :
set<YulString>{name};
for (shared_ptr<ObjectNode> const& subObjectNode: subObjects)
{
yulAssert(qualifiedNames.count(subObjectNode->name) == 0, "");
if (contains(subObjectNode->name.str(), '.'))
continue;
qualifiedNames.insert(subObjectNode->name);
if (auto const* subObject = dynamic_cast<Object const*>(subObjectNode.get()))
for (YulString const& subSubObj: subObject->qualifiedDataNames())

View File

@ -71,6 +71,7 @@ public:
/// @returns the set of names of data objects accessible from within the code of
/// this object, including the name of object itself
/// Handles all names containing dots as reserved identifiers, not accessible as data.
std::set<YulString> qualifiedDataNames() const;
/// @returns vector of subIDs if possible to reach subobject with @a _qualifiedName, throws otherwise
@ -92,6 +93,9 @@ public:
std::vector<std::shared_ptr<ObjectNode>> subObjects;
std::map<YulString, size_t> subIndexByName;
std::shared_ptr<yul::AsmAnalysisInfo> analysisInfo;
/// @returns the name of the special metadata data object.
static std::string metadataName() { return ".metadata"; }
};
}

View File

@ -110,6 +110,9 @@ public:
/// Appends an assignment to an immutable variable.
virtual void appendImmutableAssignment(std::string const& _identifier) = 0;
/// Appends data to the very end of the bytecode. Repeated calls concatenate.
virtual void appendToAuxiliaryData(bytes const& _data) = 0;
/// Mark this assembly as invalid. Any attempt to request bytecode from it should throw.
virtual void markAsInvalid() = 0;
};

View File

@ -53,7 +53,11 @@ void EVMObjectCompiler::run(Object& _object, bool _optimize)
else
{
Data const& data = dynamic_cast<Data const&>(*subNode);
context.subIDs[data.name] = m_assembly.appendData(data.data);
// Special handling of metadata.
if (data.name.str() == Object::metadataName())
m_assembly.appendToAuxiliaryData(data.data);
else
context.subIDs[data.name] = m_assembly.appendData(data.data);
}
yulAssert(_object.analysisInfo, "No analysis info.");

View File

@ -161,6 +161,11 @@ AbstractAssembly::SubID EthAssemblyAdapter::appendData(bytes const& _data)
return subID;
}
void EthAssemblyAdapter::appendToAuxiliaryData(bytes const& _data)
{
m_assembly.appendToAuxiliaryData(_data);
}
void EthAssemblyAdapter::appendImmutable(std::string const& _identifier)
{
m_assembly.appendImmutable(_identifier);

View File

@ -58,6 +58,8 @@ public:
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
void appendToAuxiliaryData(bytes const& _data) override;
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;

View File

@ -70,6 +70,8 @@ public:
void appendDataSize(std::vector<SubID> const& _subPath) override;
SubID appendData(bytes const& _data) override;
void appendToAuxiliaryData(bytes const&) override {}
void appendImmutable(std::string const& _identifier) override;
void appendImmutableAssignment(std::string const& _identifier) override;

View File

@ -153,6 +153,10 @@ function test_solc_behaviour()
sed -i.bak -E -e 's/(\"object\":\"[^"]+\$__)[0-9a-f]+(\")/\1<BYTECODE REMOVED>\2/g' "$stdout_path"
# shellcheck disable=SC2016
sed -i.bak -E -e 's/([0-9a-f]{34}\$__)[0-9a-f]+(__\$[0-9a-f]{17})/\1<BYTECODE REMOVED>\2/g' "$stdout_path"
# Remove metadata in assembly output (see below about the magic numbers)
sed -i.bak -E -e 's/"[0-9a-f]+64697066735822[0-9a-f]+64736f6c63[0-9a-f]+/"<BYTECODE REMOVED>/g' "$stdout_path"
# Remove hash of text representation in ewasm
sed -i.bak -E -e 's/The Keccak-256 hash of the text representation of .*: [0-9a-f]+/The Keccak-256 hash of the text representation of <REMOVED>/g' "$stdout_path"
# Replace escaped newlines by actual newlines for readability
# shellcheck disable=SC1003

View File

@ -26,5 +26,6 @@ object "C_12" {
stop()
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -331,6 +331,7 @@ object "C_81" {
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -23,6 +23,7 @@ object "C_7" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}
@ -51,5 +52,6 @@ object "D_10" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -23,6 +23,7 @@ object "C_3" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}
@ -93,7 +94,9 @@ object "D_16" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -33,5 +33,6 @@ object "D_12" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -33,5 +33,6 @@ object "D_8" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -28,5 +28,6 @@ object "C_12" {
stop()
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -26,5 +26,6 @@ object "C_7" {
stop()
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1 +1 @@
--optimize --ir-optimized --metadata-hash none
--optimize --ir-optimized

View File

@ -122,5 +122,6 @@ object "C_59" {
revert(0, 0x24)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --ir-optimized --metadata-hash none
--optimize --ir-optimized

View File

@ -60,5 +60,6 @@ object "Arraysum_34" {
revert(0, 0x24)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -1 +1 @@
--optimize --asm --metadata-hash none
--optimize --asm

View File

@ -357,6 +357,7 @@ object "C_15" {
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1,7 +1,7 @@
{"contracts":{"A":{"C":{"ewasm":{"wasm":"0061736d010000000125086000006000017e6000017f60017e017f60017f0060017f017f60027f7f0060037f7f7f0002510408657468657265756d08636f6465436f7079000708657468657265756d06726576657274000608657468657265756d0c67657443616c6c56616c7565000408657468657265756d0666696e6973680006030a090002030202020505010503010001060100071102066d656d6f72790200046d61696e000400b6030c435f335f6465706c6f7965640061736d010000000112046000006000017f60017f017f60027f7f0002130108657468657265756d06726576657274000303060500010102020503010001060100071102066d656d6f72790200046d61696e00010ad20205a10104027f017e017f047e024010022100200041c0006a210120012000490440000b420021022002a7210320031005ad42208621042002422088210520042005a71005ad84210620012006370000200141086a2006370000200141106a2006370000428001a71005ad4220862107200141186a2007428001422088a71005ad8437000020022002200284200284520440000b20022005520440000b1003200310000b0b2b01017f024042004200420084420084520440000b420042c000422088520440000b42c000a721000b20000b4203017f017e017f02404200210120012001200184200184520440000b20012001422088520440000b2001a72102200241c0006a210020002002490440000b0b20000b1f01017f024020004108744180fe0371200041087641ff01717221010b20010b1e01027f02402000100441107421022002200041107610047221010b20010b0ae303099a0102027f047e024010072100200041c0006a210120012000490440000b4200a7100bad422086210220024200422088a7100bad84210320012003370000200141086a2003370000200141106a2003370000200141186a100c37000041001002410829000021044200420084200441002900008484504504401008100510010b42a9032105100942b901100620051006100010092005100610030b0b2f02017f017e02404200210120012001200184200184520440000b20012001422088520440000b2001a721000b20000b2901017f024042004200420084420084520440000b42002000422088520440000b2000a721010b20010b2b01017f024042004200420084420084520440000b420042c000422088520440000b42c000a721000b20000b1e01027f024010052101200141c0006a210020002001490440000b0b20000b3c01027f024042004200420084420084520440000b4200428001422088520440000b428001a72101200141c0006a210020002001490440000b0b20000b1f01017f024020004108744180fe0371200041087641ff01717221010b20010b1e01027f02402000100a411074210220022000411076100a7221010b20010b2401027e0240428001a7100bad42208621012001428001422088a7100bad8421000b20000b","wast":"(module
{"contracts":{"A":{"C":{"ewasm":{"wasm":"<BYTECODE REMOVED>","wast":"(module
;; custom section for sub-module
;; The Keccak-256 hash of the text representation of \"C_3_deployed\": d5523336521d49fa8bd64dba28ece7291aa7d45c646a23eabd038bbeecc2d803
;; (@custom \"C_3_deployed\" \"0061736d010000000112046000006000017f60017f017f60027f7f0002130108657468657265756d06726576657274000303060500010102020503010001060100071102066d656d6f72790200046d61696e00010ad20205a10104027f017e017f047e024010022100200041c0006a210120012000490440000b420021022002a7210320031005ad42208621042002422088210520042005a71005ad84210620012006370000200141086a2006370000200141106a2006370000428001a71005ad4220862107200141186a2007428001422088a71005ad8437000020022002200284200284520440000b20022005520440000b1003200310000b0b2b01017f024042004200420084420084520440000b420042c000422088520440000b42c000a721000b20000b4203017f017e017f02404200210120012001200184200184520440000b20012001422088520440000b2001a72102200241c0006a210020002002490440000b0b20000b1f01017f024020004108744180fe0371200041087641ff01717221010b20010b1e01027f02402000100441107421022002200041107610047221010b20010b\")
;; The Keccak-256 hash of the text representation of <REMOVED>
;; (@custom \"C_3_deployed\" \"<BYTECODE REMOVED>\")
(import \"ethereum\" \"codeCopy\" (func $eth.codeCopy (param i32 i32 i32)))
(import \"ethereum\" \"revert\" (func $eth.revert (param i32 i32)))
(import \"ethereum\" \"getCallValue\" (func $eth.getCallValue (param i32)))

View File

@ -67,6 +67,7 @@ object \"C_7\" {
function shift_right_224_unsigned(value) -> newValue
{ newValue := shr(224, value) }
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}
"}}},"sources":{"A":{"id":0}}}

View File

@ -96,6 +96,7 @@ object \"C_7\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -62,6 +62,7 @@ object \"C_3\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}
@ -250,10 +251,12 @@ object \"D_16\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -117,6 +117,7 @@ object "test_11" {
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -1,9 +1,9 @@
======= viair_subobjects/input.sol:C =======
Binary:
60806040523415600f5760006000fd5b600a80601e608039806080f350fe608060405260006000fd
<BYTECODE REMOVED>
Binary of the runtime part:
608060405260006000fd
<BYTECODE REMOVED>
Optimized IR:
/*******************************************************
* WARNING *
@ -29,15 +29,16 @@ object "C_3" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}
======= viair_subobjects/input.sol:D =======
Binary:
608060405234156100105760006000fd5b60ba80610020608039806080f350fe6080604052600436101515608b576000803560e01c6326121ff0141560895734156027578081fd5b80600319360112156036578081fd5b6028806080016080811067ffffffffffffffff82111715606457634e487b7160e01b83526041600452602483fd5b508061009260803980608083f015156082576040513d83823e3d81fd505b5080604051f35b505b60006000fdfe60806040523415600f5760006000fd5b600a80601e608039806080f350fe608060405260006000fd
<BYTECODE REMOVED>
Binary of the runtime part:
6080604052600436101515608b576000803560e01c6326121ff0141560895734156027578081fd5b80600319360112156036578081fd5b6028806080016080811067ffffffffffffffff82111715606457634e487b7160e01b83526041600452602483fd5b508061009260803980608083f015156082576040513d83823e3d81fd505b5080604051f35b505b60006000fdfe60806040523415600f5760006000fd5b600a80601e608039806080f350fe608060405260006000fd
<BYTECODE REMOVED>
Optimized IR:
/*******************************************************
* WARNING *
@ -105,7 +106,9 @@ object "D_16" {
revert(0, 0)
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -40,5 +40,6 @@ object "C_7" {
function shift_right_unsigned(value) -> newValue
{ newValue := shr(224, value) }
}
data ".metadata" hex"<BYTECODE REMOVED>"
}
}

View File

@ -195,6 +195,7 @@ object \"C_11\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -119,6 +119,7 @@ object \"C_11\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -131,6 +131,7 @@ object \"C_11\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -199,6 +199,7 @@ object \"C_11\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -131,6 +131,7 @@ object \"C_11\" {
}
data \".metadata\" hex\"<BYTECODE REMOVED>\"
}
}

View File

@ -97,8 +97,8 @@ BOOST_AUTO_TEST_CASE(all_assembly_items)
_assembly.appendImmutableAssignment("someImmutable");
// Operation
_assembly.append(Instruction::STOP);
_assembly.appendAuxiliaryDataToEnd(bytes{0x42, 0x66});
_assembly.appendAuxiliaryDataToEnd(bytes{0xee, 0xaa});
_assembly.appendToAuxiliaryData(bytes{0x42, 0x66});
_assembly.appendToAuxiliaryData(bytes{0xee, 0xaa});
checkCompilation(_assembly);

View File

@ -0,0 +1,39 @@
object "A" {
code {
pop(datasize("x"))
pop(datasize("C"))
}
object "B" {
code { pop(dataoffset("other")) }
data ".metadata" "M1"
data "other" "Hello, World2!"
}
data "C" "ABC"
data ".metadata" "M2"
data "x" "Hello, World2!"
}
// ----
// Assembly:
// /* "source":26:44 */
// pop(0x0e)
// /* "source":49:67 */
// pop(0x03)
// stop
// data_211450822d7f8c345093893187e7e1fbebc4ec67af72601920194be14104e336 48656c6c6f2c20576f726c643221
// data_e1629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8 414243
//
// sub_0: assembly {
// /* "source":99:123 */
// pop(data_211450822d7f8c345093893187e7e1fbebc4ec67af72601920194be14104e336)
// stop
// data_211450822d7f8c345093893187e7e1fbebc4ec67af72601920194be14104e336 48656c6c6f2c20576f726c643221
//
// auxdata: 0x4d31
// }
//
// auxdata: 0x4d32
// Bytecode: 600e50600350fe4d32
// Opcodes: PUSH1 0xE POP PUSH1 0x3 POP INVALID 0x4D ORIGIN
// SourceMappings: 26:18:0:-:0;;49;

View File

@ -0,0 +1,32 @@
object "A" {
code {
pop(dataoffset(".other"))
pop(datasize(".other"))
pop(datasize("B..other"))
// "B.other" does not exist.
pop(datasize("B.other"))
// ".metadata" is not accessible by definition
pop(dataoffset(".metadata"))
pop(datasize(".metadata"))
pop(dataoffset("B..metadata"))
pop(datasize("B..metadata"))
}
object "B" {
code {}
data ".metadata" "Hello, World!"
data ".other" "Hello, World2!"
}
data ".metadata" "Hello, World!"
data ".other" "Hello, World2!"
}
// ----
// TypeError 3517: (41-49): Unknown data object ".other".
// TypeError 3517: (69-77): Unknown data object ".other".
// TypeError 3517: (97-107): Unknown data object "B..other".
// TypeError 3517: (160-169): Unknown data object "B.other".
// TypeError 3517: (242-253): Unknown data object ".metadata".
// TypeError 3517: (273-284): Unknown data object ".metadata".
// TypeError 3517: (306-319): Unknown data object "B..metadata".
// TypeError 3517: (339-352): Unknown data object "B..metadata".

View File

@ -0,0 +1,13 @@
object "A" {
code {
sstore(dataoffset("0"), dataoffset("1"))
sstore(0, datasize(".mightbereservedinthefuture"))
}
data "0" "UVW"
data ".metadata" "ABC"
data "1" "XYZ"
data ".mightbereservedinthefuture" "TRS"
}
// ----
// TypeError 3517: (90-119): Unknown data object ".mightbereservedinthefuture".

View File

@ -0,0 +1,12 @@
object "A" {
code {
sstore(dataoffset(".metadata.x"), 0)
}
object ".metadata" {
code {}
data "x" "ABC"
}
}
// ----
// TypeError 3517: (41-54): Unknown data object ".metadata.x".