Update metadata docs

Squashed into single commit

Squashed commits:
Sort metadata JSON, add errors to devdoc & userdoc

Sorts the example metadata in the alphabetical order,
as this is the way output by the compiler.

Add playground.sourcify.dev link

Update metadata documentation content

Fix trailing whitespaces

Add new line after code-block

Fix unexpected indentation

Fix unexpected unindent

Fix unexpected unindent - 2

Suggestions from code review: wording, punctuation

Co-authored-by: Nikola Matić <nikola.matic@ethereum.org>
Fix missing trailing commas

Co-authored-by: Nikola Matić <nikola.matic@ethereum.org>
Order yul settings, fix trailing commas

Explicitly state appended to the runtime bytecode

Break down metadata content into two points

Remove trailing whitespace
This commit is contained in:
Kaan Uzdoğan 2023-03-31 15:27:57 +03:00
parent 0aa85153e5
commit 1a23b7a60a

View File

@ -6,18 +6,19 @@ Contract Metadata
.. index:: metadata, contract verification .. index:: metadata, contract verification
The Solidity compiler automatically generates a JSON file, the contract The Solidity compiler automatically generates a JSON file.
metadata, that contains information about the compiled contract. You can use The file contains two kinds of information about the compiled contract:
this file to query the compiler version, the sources used, the ABI and NatSpec
documentation to more safely interact with the contract and verify its source - How to interact with the contract: ABI, and NatSpec documentation.
code. - How to reproduce the compilation and verify a deployed contract:
compiler version, compiler settings, and source files used.
The compiler appends by default the IPFS hash of the metadata file to the end The compiler appends by default the IPFS hash of the metadata file to the end
of the bytecode (for details, see below) of each contract, so that you can of the runtime bytecode (not necessarily the creation bytecode) of each contract,
retrieve the file in an authenticated way without having to resort to a so that, if published, you can retrieve the file in an authenticated way without
centralized data provider. The other available options are the Swarm hash and having to resort to a centralized data provider. The other available options are
not appending the metadata hash to the bytecode. These can be configured via the Swarm hash and not appending the metadata hash to the bytecode. These can be
the :ref:`Standard JSON Interface<compiler-api>`. configured via the :ref:`Standard JSON Interface<compiler-api>`.
You have to publish the metadata file to IPFS, Swarm, or another service so You have to publish the metadata file to IPFS, Swarm, or another service so
that others can access it. You create the file by using the ``solc --metadata`` that others can access it. You create the file by using the ``solc --metadata``
@ -30,108 +31,50 @@ shall match with the one contained in the bytecode.
The metadata file has the following format. The example below is presented in a The metadata file has the following format. The example below is presented in a
human-readable way. Properly formatted metadata should use quotes correctly, human-readable way. Properly formatted metadata should use quotes correctly,
reduce whitespace to a minimum and sort the keys of all objects to arrive at a reduce whitespace to a minimum, and sort the keys of all objects in alphabetical order
unique formatting. Comments are not permitted and used here only for to arrive at a canonical formatting. Comments are not permitted and are used here only for
explanatory purposes. explanatory purposes.
.. code-block:: javascript .. code-block:: javascript
{ {
// Required: The version of the metadata format
"version": "1",
// Required: Source code language, basically selects a "sub-version"
// of the specification
"language": "Solidity",
// Required: Details about the compiler, contents are specific // Required: Details about the compiler, contents are specific
// to the language. // to the language.
"compiler": { "compiler": {
// Required for Solidity: Version of the compiler
"version": "0.8.2+commit.661d1103",
// Optional: Hash of the compiler binary which produced this output // Optional: Hash of the compiler binary which produced this output
"keccak256": "0x123..." "keccak256": "0x123...",
}, // Required for Solidity: Version of the compiler
// Required: Compilation source files/source units, keys are file paths "version": "0.8.2+commit.661d1103"
"sources":
{
"myDirectory/myFile.sol": {
// Required: keccak256 hash of the source file
"keccak256": "0x123...",
// Required (unless "content" is used, see below): Sorted URL(s)
// to the source file, protocol is more or less arbitrary, but an
// IPFS URL is recommended
"urls": [ "bzz-raw://7d7a...", "dweb:/ipfs/QmN..." ],
// Optional: SPDX license identifier as given in the source file
"license": "MIT"
},
"destructible": {
// Required: keccak256 hash of the source file
"keccak256": "0x234...",
// Required (unless "url" is used): literal contents of the source file
"content": "contract destructible is owned { function destroy() { if (msg.sender == owner) selfdestruct(owner); } }"
}
},
// Required: Compiler settings
"settings":
{
// Required for Solidity: Sorted list of import remappings
"remappings": [ ":g=/dir" ],
// Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated
// and are only given for backwards-compatibility.
"optimizer": {
"enabled": true,
"runs": 500,
"details": {
// peephole defaults to "true"
"peephole": true,
// inliner defaults to "true"
"inliner": true,
// jumpdestRemover defaults to "true"
"jumpdestRemover": true,
"orderLiterals": false,
"deduplicate": false,
"cse": false,
"constantOptimizer": false,
"yul": true,
// Optional: Only present if "yul" is "true"
"yulDetails": {
"stackAllocation": false,
"optimizerSteps": "dhfoDgvulfnTUtnIf..."
}
}
},
"metadata": {
// Reflects the setting used in the input json, defaults to "true"
"appendCBOR": true,
// Reflects the setting used in the input json, defaults to "false"
"useLiteralContent": true,
// Reflects the setting used in the input json, defaults to "ipfs"
"bytecodeHash": "ipfs"
},
// Required for Solidity: File path and the name of the contract or library this
// metadata is created for.
"compilationTarget": {
"myDirectory/myFile.sol": "MyContract"
},
// Required for Solidity: Addresses for libraries used
"libraries": {
"MyLib": "0x123123..."
}
}, },
// Required: Source code language, basically selects a "sub-version"
// of the specification
"language": "Solidity",
// Required: Generated information about the contract. // Required: Generated information about the contract.
"output": "output": {
{
// Required: ABI definition of the contract. See "Contract ABI Specification" // Required: ABI definition of the contract. See "Contract ABI Specification"
"abi": [/* ... */], "abi": [/* ... */],
// Required: NatSpec developer documentation of the contract. // Required: NatSpec developer documentation of the contract. See https://docs.soliditylang.org/en/latest/natspec-format.html for details.
"devdoc": { "devdoc": {
"version": 1 // NatSpec version
"kind": "dev",
// Contents of the @author NatSpec field of the contract // Contents of the @author NatSpec field of the contract
"author": "John Doe", "author": "John Doe",
// Contents of the @title NatSpec field of the contract
"title": "MyERC20: an example ERC20"
// Contents of the @dev NatSpec field of the contract // Contents of the @dev NatSpec field of the contract
"details": "Interface of the ERC20 standard as defined in the EIP. See https://eips.ethereum.org/EIPS/eip-20 for details", "details": "Interface of the ERC20 standard as defined in the EIP. See https://eips.ethereum.org/EIPS/eip-20 for details",
"errors": {
"MintToZeroAddress()" : {
"details": "Cannot mint to zero address"
}
},
"events": {
"Transfer(address,address,uint256)": {
"details": "Emitted when `value` tokens are moved from one account (`from`) toanother (`to`).",
"params": {
"from": "The sender address",
"to": "The receiver address",
"value": "The token amount"
}
}
},
"kind": "dev",
"methods": { "methods": {
"transfer(address,uint256)": { "transfer(address,uint256)": {
// Contents of the @dev NatSpec field of the method // Contents of the @dev NatSpec field of the method
@ -140,7 +83,7 @@ explanatory purposes.
"params": { "params": {
"_value": "The amount tokens to be transferred", "_value": "The amount tokens to be transferred",
"_to": "The receiver address" "_to": "The receiver address"
} },
// Contents of the @return NatSpec field. // Contents of the @return NatSpec field.
"returns": { "returns": {
// Return var name (here "success") if exists. "_0" as key if return var is unnamed // Return var name (here "success") if exists. "_0" as key if return var is unnamed
@ -153,34 +96,104 @@ explanatory purposes.
// Contents of the @dev NatSpec field of the state variable // Contents of the @dev NatSpec field of the state variable
"details": "Must be set during contract creation. Can then only be changed by the owner" "details": "Must be set during contract creation. Can then only be changed by the owner"
} }
} },
"events": { // Contents of the @title NatSpec field of the contract
"Transfer(address,address,uint256)": { "title": "MyERC20: an example ERC20",
"details": "Emitted when `value` tokens are moved from one account (`from`) toanother (`to`)."
"params": {
"from": "The sender address"
"to": "The receiver address"
"value": "The token amount"
}
}
}
},
// Required: NatSpec user documentation of the contract
"userdoc": {
"version": 1 // NatSpec version "version": 1 // NatSpec version
},
// Required: NatSpec user documentation of the contract. See "NatSpec Format"
"userdoc": {
"errors": {
"ApprovalCallerNotOwnerNorApproved()": [
{
"notice": "The caller must own the token or be an approved operator."
}
]
},
"events": {
"Transfer(address,address,uint256)": {
"notice": "`_value` tokens have been moved from `from` to `to`"
}
},
"kind": "user", "kind": "user",
"methods": { "methods": {
"transfer(address,uint256)": { "transfer(address,uint256)": {
"notice": "Transfers `_value` tokens to address `_to`" "notice": "Transfers `_value` tokens to address `_to`"
} }
}, },
"events": { "version": 1 // NatSpec version
"Transfer(address,address,uint256)": {
"notice": "`_value` tokens have been moved from `from` to `to`"
}
}
} }
} },
// Required: Compiler settings. Reflects the settings in the JSON input during compilation.
// Check the documentation of standard JSON input's "settings" field
"settings": {
// Required for Solidity: File path and the name of the contract or library this
// metadata is created for.
"compilationTarget": {
"myDirectory/myFile.sol": "MyContract"
},
// Required for Solidity.
"evmVersion": "london",
// Required for Solidity: Addresses for libraries used.
"libraries": {
"MyLib": "0x123123..."
},
"metadata": {
// Reflects the setting used in the input json, defaults to "true"
"appendCBOR": true,
// Reflects the setting used in the input json, defaults to "ipfs"
"bytecodeHash": "ipfs",
// Reflects the setting used in the input json, defaults to "false"
"useLiteralContent": true
},
// Optional: Optimizer settings. The fields "enabled" and "runs" are deprecated
// and are only given for backwards-compatibility.
"optimizer": {
"details": {
"constantOptimizer": false,
"cse": false,
"deduplicate": false,
// inliner defaults to "true"
"inliner": true,
// jumpdestRemover defaults to "true"
"jumpdestRemover": true,
"orderLiterals": false,
// peephole defaults to "true"
"peephole": true,
"yul": true,
// Optional: Only present if "yul" is "true"
"yulDetails": {
"optimizerSteps": "dhfoDgvulfnTUtnIf...",
"stackAllocation": false
}
},
"enabled": true,
"runs": 500
},
// Required for Solidity: Sorted list of import remappings.
"remappings": [ ":g=/dir" ]
},
// Required: Compilation source files/source units, keys are file paths
"sources": {
"destructible": {
// Required (unless "url" is used): literal contents of the source file
"content": "contract destructible is owned { function destroy() { if (msg.sender == owner) selfdestruct(owner); } }",
// Required: keccak256 hash of the source file
"keccak256": "0x234..."
},
"myDirectory/myFile.sol": {
// Required: keccak256 hash of the source file
"keccak256": "0x123...",
// Optional: SPDX license identifier as given in the source file
"license": "MIT",
// Required (unless "content" is used, see above): Sorted URL(s)
// to the source file, protocol is more or less arbitrary, but an
// IPFS URL is recommended
"urls": [ "bzz-raw://7d7a...", "dweb:/ipfs/QmN..." ]
}
},
// Required: The version of the metadata format
"version": "1"
} }
.. warning:: .. warning::
@ -200,23 +213,32 @@ explanatory purposes.
Encoding of the Metadata Hash in the Bytecode Encoding of the Metadata Hash in the Bytecode
============================================= =============================================
Because we might support other ways to retrieve the metadata file in the future, The compiler currently by default appends the
the mapping ``{"ipfs": <IPFS hash>, "solc": <compiler version>}`` is stored `IPFS hash (in CID v0) <https://docs.ipfs.tech/concepts/content-addressing/#version-0-v0>`_
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. Since the mapping might of the canonical metadata file and the compiler version to the end of the bytecode.
contain more keys (see below) and the beginning of that Optionally, a Swarm hash instead of the IPFS, or an experimental flag is used.
encoding is not easy to find, its length is added in a two-byte big-endian Below are all the possible fields:
encoding. The current version of the Solidity compiler usually adds the following
to the end of the deployed bytecode
.. code-block:: text .. code-block:: javascript
0xa2 {
0x64 'i' 'p' 'f' 's' 0x58 0x22 <34 bytes IPFS hash> "ipfs": "<metadata hash>",
0x64 's' 'o' 'l' 'c' 0x43 <3 byte version encoding> // If "bytecodeHash" was "bzzr1" in compiler settings not "ipfs" but "bzzr1"
0x00 0x33 "bzzr1": "<metadata hash>",
// Previous versions were using "bzzr0" instead of "bzzr1"
"bzzr0": "<metadata hash>",
// If any experimental features that affect code generation are used
"experimental": true,
"solc": "<compiler version>"
}
So in order to retrieve the data, the end of the deployed bytecode can be checked Because we might support other ways to retrieve the
to match that pattern and the IPFS hash can be used to retrieve the file (if pinned/published). metadata file in the future, this information is stored
`CBOR <https://tools.ietf.org/html/rfc7049>`_-encoded. The last two bytes in the bytecode
indicate the length of the CBOR encoded information. By looking at this length, the
relevant part of the bytecode can be decoded with a CBOR decoder.
Check the `Metadata Playground <https://playground.sourcify.dev/>`_ to see it in action.
Whereas release builds of solc use a 3 byte encoding of the version as shown Whereas release builds of solc use a 3 byte encoding of the version as shown
above (one byte each for major, minor and patch version number), prerelease builds above (one byte each for major, minor and patch version number), prerelease builds
@ -228,17 +250,9 @@ boolean field ``settings.metadata.appendCBOR`` in Standard JSON input can be set
.. note:: .. note::
The CBOR mapping can also contain other keys, so it is better to fully The CBOR mapping can also contain other keys, so it is better to fully
decode the data instead of relying on it starting with ``0xa264``. decode the data by looking at the end of the bytecode for the CBOR length,
For example, if any experimental features that affect code generation and to use a proper CBOR parser. Do not rely on it starting with ``0xa264``
are used, the mapping will also contain ``"experimental": true``. or ``0xa2 0x64 'i' 'p' 'f' 's'``.
.. note::
The compiler currently uses the IPFS hash of the metadata by default, but
it may also use the bzzr1 hash or some other hash in the future, so do
not rely on this sequence to start with ``0xa2 0x64 'i' 'p' 'f' 's'``. We
might also add additional data to this CBOR structure, so the best option
is to use a proper CBOR parser.
Usage for Automatic Interface Generation and NatSpec Usage for Automatic Interface Generation and NatSpec
==================================================== ====================================================
@ -252,24 +266,27 @@ is JSON-decoded into a structure like above.
The component can then use the ABI to automatically generate a rudimentary The component can then use the ABI to automatically generate a rudimentary
user interface for the contract. user interface for the contract.
Furthermore, the wallet can use the NatSpec user documentation to display a human-readable confirmation message to the user Furthermore, the wallet can use the NatSpec user documentation to display a
whenever they interact with the contract, together with requesting human-readable confirmation message to the user whenever they interact with
authorization for the transaction signature. the contract, together with requesting authorization for the transaction signature.
For additional information, read :doc:`Ethereum Natural Language Specification (NatSpec) format <natspec-format>`. For additional information, read :doc:`Ethereum Natural Language Specification (NatSpec) format <natspec-format>`.
Usage for Source Code Verification Usage for Source Code Verification
================================== ==================================
In order to verify the compilation, sources can be retrieved from IPFS/Swarm If pinned/published, it is possible to retrieve the metadata of the contract from IPFS/Swarm.
via the link in the metadata file. The metadata file also contains the URLs or the IPFS hashes of the source files, as well as
The compiler of the correct version (which is checked to be part of the "official" compilers) the compilation settings, i.e. everything needed to reproduce a compilation.
is invoked on that input with the specified settings. The resulting
bytecode is compared to the data of the creation transaction or ``CREATE`` opcode data.
This automatically verifies the metadata since its hash is part of the bytecode.
Excess data corresponds to the constructor input data, which should be decoded
according to the interface and presented to the user.
In the repository `sourcify <https://github.com/ethereum/sourcify>`_ With this information it is then possible to verify the source code of a contract by
(`npm package <https://www.npmjs.com/package/source-verify>`_) you can see reproducing the compilation, and comparing the bytecode from the compilation with
example code that shows how to use this feature. the bytecode of the deployed contract.
This automatically verifies the metadata since its hash is part of the bytecode, as well
as the source codes, because their hashes are part of the metadata. Any change in the files
or settings would result in a different metadata hash. The metadata here serves
as a fingerprint of the whole compilation.
`Sourcify <https://sourcify.dev>`_ makes use of this feature for "full/perfect verification",
as well as pinning the files publicly on IPFS to be accessed with the metadata hash.