컨트랙트 메타데이터
컨트랙트 메타데이터 (Contract Metadata)
Solidity 컴파일러는 자동으로 JSON 파일을 생성해요. 이 파일은 컴파일된 컨트랙트에 대해 두 종류의 정보를 담아요. 첫째는 컨트랙트와 상호작용하는 방법(ABI와 NatSpec 문서), 둘째는 컴파일을 재현하고 배포된 컨트랙트를 검증하는 방법(컴파일러 버전, 컴파일러 설정, 사용된 소스 파일)이에요. 메타데이터는 컴파일 전체의 지문(fingerprint) 역할을 해요.
출처: 문서
본문
Solidity 컴파일러는 자동으로 JSON 파일을 생성해요. 이 파일은 컴파일된 컨트랙트에 대해 두 종류의 정보를 담아요:
- 컨트랙트와 상호작용하는 방법: ABI, 그리고 NatSpec 문서.
- 컴파일을 재현하고 배포된 컨트랙트를 검증하는 방법: 컴파일러 버전, 컴파일러 설정, 사용된 소스 파일.
컴파일러는 기본으로 각 컨트랙트의 런타임 바이트코드(반드시 생성 바이트코드가 아니라) 끝에 메타데이터 파일의 IPFS 해시를 추가해, 게시하면 중앙 집중식 데이터 제공자에 의존하지 않고 인증된 방식으로 파일을 검색할 수 있게 해요. 다른 사용 가능한 옵션은 Swarm 해시와 메타데이터 해시를 바이트코드에 추가하지 않는 것이에요. 이들은 표준 JSON 인터페이스를 통해 구성할 수 있어요.
메타데이터 파일을 IPFS, Swarm 또는 다른 서비스에 게시해 다른 사람이 접근할 수 있게 해야 해요. 파일은 solc --metadata 명령과 --output-dir 매개변수를 함께 사용해 만들어요. 매개변수 없이 메타데이터는 표준 출력에 쓰여요.
메타데이터는 소스 코드에 대한 IPFS와 Swarm 참조를 포함하므로, 메타데이터 파일에 더해 모든 소스 파일도 업로드해야 해요. IPFS의 경우, ipfs add가 반환하는 CID에 포함된 해시(파일의 직접 sha2-256 해시가 아니라)가 바이트코드에 포함된 것과 일치해야 해요.
메타데이터 파일은 다음 형식을 가져요. 아래 예시는 사람이 읽을 수 있는 방식으로 제시돼요. 제대로 포맷된 메타데이터는 따옴표를 올바르게 사용하고, 공백을 최소로 줄이며, 모든 객체의 키를 알파벳순으로 정렬해 표준 형식(canonical formatting)에 도달해야 해요. 주석은 허용되지 않으며 여기서는 설명 목적으로만 사용돼요.
{
// Required: Details about the compiler, contents are specific
// to the language.
"compiler": {
// Optional: Hash of the compiler binary which produced this output
"keccak256": "0x123...",
// Required for Solidity: Version of the compiler
"version": "0.8.2+commit.661d1103"
},
// Required: Source code language, basically selects a "sub-version"
// of the specification
"language": "Solidity",
// Required: Generated information about the contract.
"output": {
// Required: ABI definition of the contract. See "Contract ABI Specification"
"abi": [/* ... */],
// Required: NatSpec developer documentation of the contract. See https://docs.soliditylang.org/en/latest/natspec-format.html for details.
"devdoc": {
// Contents of the @author NatSpec field of the contract
"author": "John Doe",
// 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",
"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": {
"transfer(address,uint256)": {
// Contents of the @dev NatSpec field of the method
"details": "Returns a boolean value indicating whether the operation succeeded. Must be called by the token holder address",
// Contents of the @param NatSpec fields of the method
"params": {
"_value": "The amount tokens to be transferred",
"_to": "The receiver address"
},
// Contents of the @return NatSpec field.
"returns": {
// Return var name (here "success") if exists. "_0" as key if return var is unnamed
"success": "a boolean value indicating whether the operation succeeded"
}
}
},
"stateVariables": {
"owner": {
// 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"
}
},
// Contents of the @title NatSpec field of the contract
"title": "MyERC20: an example ERC20",
"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",
"methods": {
"transfer(address,uint256)": {
"notice": "Transfers `_value` tokens to address `_to`"
}
},
"version": 1 // NatSpec version
}
},
// Required: Compiler settings.
// Reflects the settings in the JSON input during compilation, except:
// - Different format: "libraries" field
// - Added field in metadata.settings: "compilationTarget"
// - Not in metadata.settings: "stopAfter", "debug.debugInfo", "outputSelection"
// See the standard JSON input's "settings" field docs for the rest.
"settings": {
// Required for Solidity: File path and the name of the contract or library this
// metadata is created for. This field is not present in the standard JSON input settings.
"compilationTarget": {
"myDirectory/myFile.sol": "MyContract"
},
// Optional (false if omitted): Indicates whether experimental mode has been enabled.
// Always matches the value of the `experimental` flag in CBOR metadata.
// Note that experimental mode being enabled does not necessarily mean that any
// experimental features were actually used, or if they were, that those features
// affected the bytecode.
"experimental": true,
// Required for Solidity: Addresses for libraries used.
// Note that metadata has a different format for "libraries" field than the standard JSON input.
// metadata format = { "MyLib.sol:MyLib": "0x123123..." }
// standard JSON input format = { "MyLib.sol": { "MyLib": "0x123123..." } }
"libraries": {
"MyLib.sol:MyLib": "0x123123..."
},
// ...
// ...
// ...
// The rest of the fields and their defaults same as in std JSON input.
},
// Required: Compilation source files/source units, keys are file paths
"sources": {
"settable": {
// Required (unless "url" is used): literal contents of the source file
"content": "contract settable is owned { uint256 private x = 0; function set(uint256 _x) public { if (msg.sender == owner) x = _x; } }",
// 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)
결과 컨트랙트의 바이트코드는 기본으로 메타데이터 해시를 포함하므로, 메타데이터에 대한 어떤 변경도 바이트코드의 변경으로 이어질 수 있어요. 여기에는 파일 이름이나 경로의 변경도 포함되고, 메타데이터는 사용된 모든 소스의 해시를 포함하므로 단일 공백 변경도 다른 메타데이터와 다른 바이트코드를 만들어요.
참고 (Note)
위의 ABI 정의는 고정된 순서가 없어요. 컴파일러 버전에 따라 바뀔 수 있어요. 그러나 Solidity 버전 0.5.12부터 배열은 일정한 순서를 유지해요.
바이트코드에서 메타데이터 해시의 인코딩 (Encoding of the Metadata Hash in the Bytecode)
컴파일러는 현재 기본으로 표준 메타데이터 파일의 IPFS 해시(CID v0)와 컴파일러 버전을 바이트코드 끝에 추가해요. 선택적으로 IPFS 대신 Swarm 해시, 또는 실험적 플래그가 사용돼요. 아래는 가능한 모든 필드예요:
{
// Present if "bytecodeHash" was "ipfs" in compiler settings
"ipfs": "<metadata hash>",
// Present if "bytecodeHash" was "bzzr1" in compiler settings
"bzzr1": "<metadata hash>",
// Previous versions were using "bzzr0" instead of "bzzr1"
"bzzr0": "<metadata hash>",
// Present if experimental mode has been enabled either via "--experimental" flag or
// "settings.experimental" option in Standard JSON
"experimental": true,
"solc": "<compiler version>"
}
미래에 메타데이터 파일을 검색하는 다른 방법을 지원할 수 있기 때문에, 이 정보는 CBOR-인코딩되어 저장돼요. 바이트코드의 마지막 두 바이트는 CBOR 인코딩된 정보의 길이를 나타내요. 이 길이를 보고 바이트코드의 관련 부분을 CBOR 디코더로 디코딩할 수 있어요. 동작을 보려면 Metadata Playground를 확인해요.
solc의 릴리스 빌드는 위와 같이 3바이트 인코딩의 버전(메이저, 마이너, 패치 버전 번호 각각 한 바이트)을 사용하지만, prerelease 빌드는 커밋 해시와 빌드 날짜를 포함한 완전한 버전 문자열을 대신 사용해요.
명령줄 플래그 --no-cbor-metadata를 사용해 메타데이터가 배포된 바이트코드 끝에 추가되는 것을 건너뛸 수 있어요. 동등하게, 표준 JSON 입력의 불리언 필드 settings.metadata.appendCBOR를 false로 설정할 수 있어요.
참고 (Note)
CBOR 매핑은 다른 키도 포함할 수 있으므로, 바이트코드의 끝을 보아 CBOR 길이를 찾고 적절한 CBOR 파서를 사용해 데이터를 완전히 디코딩하는 것이 더 좋아요. 그것이
0xa264또는0xa2 0x64 'i' 'p' 'f' 's'로 시작한다고 의존하지 마세요.
자동 인터페이스 생성과 NatSpec을 위한 사용법 (Usage for Automatic Interface Generation and NatSpec)
메타데이터는 다음과 같이 사용돼요: 컨트랙트와 상호작용하려는 구성 요소(예: 지갑)가 컨트랙트의 코드를 검색해요. 메타데이터 파일의 IPFS/Swarm 해시를 포함하는 CBOR 인코딩된 섹션을 디코딩해요. 그 해시로 메타데이터 파일을 검색해요. 그 파일은 위와 같은 구조로 JSON-디코딩돼요.
구성 요소는 그런 다음 ABI를 사용해 컨트랙트에 대한 기초적인 사용자 인터페이스를 자동으로 생성할 수 있어요. 게다가 지갑은 NatSpec 사용자 문서를 사용해, 사용자가 컨트랙트와 상호작용할 때마다 트랜잭션 서명에 대한 인증을 요청하면서 사람이 읽을 수 있는 확인 메시지를 사용자에게 표시할 수 있어요.
추가 정보는 Ethereum Natural Language Specification (NatSpec) 형식을 참고해요.
소스 코드 검증을 위한 사용법 (Usage for Source Code Verification)
고정/게시되면, IPFS/Swarm에서 컨트랙트의 메타데이터를 검색할 수 있어요. 메타데이터 파일은 소스 파일의 URL이나 IPFS 해시뿐 아니라 컴파일 설정, 즉 컴파일을 재현하는 데 필요한 모든 것도 포함해요. 이 정보로 컴파일을 재현하고 그 컴파일의 바이트코드를 배포된 컨트랙트의 바이트코드와 비교해 컨트랙트의 소스 코드를 검증할 수 있어요.
이것은 그 해시가 바이트코드의 일부이므로 메타데이터를 자동으로 검증하고, 소스의 해시가 메타데이터의 일부이므로 소스 코드도 자동으로 검증해요. 파일이나 설정의 어떤 변경도 다른 메타데이터 해시를 만들어요. 여기서 메타데이터는 컴파일 전체의 지문 역할을 해요.
Sourcify는 "full/perfect verification"에 이 기능을 사용하며, 메타데이터 해시로 접근할 수 있도록 파일을 IPFS에 공개적으로 고정(pinning)하기도 해요.