Address Contract Verified
Address
0xBcf1065521A7379D6f4AbEF2dEdAf86a77E5eac8
Balance
0 ETH
Nonce
1
Code Size
7367 bytes
Creator
0x6c8B8897...D00E at tx 0xa57d3bb2...0d4e32
Indexed Transactions
0
Contract Bytecode
7367 bytes
0x60806040526004361015610013575b600080fd5b60003560e01c80630b03ee31146101035780632b20e397146100fa57806332ea039a146100f157806340891774146100e8578063715018a6146100df5780638da5cb5b146100d65780639ca33928146100cd578063d6c6e1c8146100c4578063e7c4393e146100bb578063f0560dd8146100b2578063f2fde38b146100a95763faab9d39146100a157600080fd5b61000e610583565b5061000e6104bb565b5061000e610416565b5061000e6103c3565b5061000e61038e565b5061000e610364565b5061000e61033a565b5061000e6102db565b5061000e6102a9565b5061000e61022c565b5061000e610202565b5061000e61014f565b6001600160a01b0381160361000e57565b6004359061012a8261010c565b565b606090600319011261000e576004356101448161010c565b906024359060443590565b503461000e5761015e3661012c565b9061016a828285610e31565b9260009380855260026020526040852054156101cc57600161019786926000526002602052604060002090565b82815501556001600160a01b03167f0b74dca72a77b54ac4bce9f4c01310b4bccfcbfeaf7d1eb961edc90890ba2c778480a480f35b60405162461bcd60e51b815260206004820152600e60248201526d1b9bdd08185d5d1a1bdc9a5e995960921b6044820152606490fd5b503461000e57600036600319011261000e576001546040516001600160a01b039091168152602090f35b503461000e57602036600319011261000e5760043560ff81169081810361000e5761025561062a565b7f1bd81c63e634020e5be2a6a6eb93c8e833597262fb2f5876698fcf3e2de91a3560406001549381519060ff8660a01c1682526020820152a160ff60a01b1990911660a09190911b60ff60a01b1617600155005b503461000e576102c16102bb3661012c565b91610e31565b600052600260205260206040600020541515604051908152f35b503461000e57600080600319360112610337576102f661062a565b80546001600160a01b03198116825581906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a380f35b80fd5b503461000e57600036600319011261000e576000546040516001600160a01b039091168152602090f35b503461000e576103766102bb3661012c565b60005260026020526020604060002054604051908152f35b503461000e576103a06102bb3661012c565b6000526002602052602060018060a01b0360016040600020015416604051908152f35b503461000e57600036600319011261000e57602060ff60015460a01c16604051908152f35b9181601f8401121561000e5782359167ffffffffffffffff831161000e576020838186019501011161000e57565b503461000e5761010036600319011261000e5761043161011d565b67ffffffffffffffff9060443582811161000e576104539036906004016103e8565b9060a43584811161000e5761046c9036906004016103e8565b60c49291923586811161000e576104879036906004016103e8565b93909260e43597881161000e576104a56104b99836906004016103e8565b979096608435926064359260243590610a9b565b005b503461000e57602036600319011261000e576004356104d98161010c565b6104e161062a565b6001600160a01b03908116801561052f57600080546001600160a01b03198116831782559092167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08380a380f35b60405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608490fd5b503461000e57602036600319011261000e576004356105a18161010c565b6105a961062a565b6001600160a01b039081169081156105f757816001549182167f1e21b6e20d93937dbc9ef28854997425fe405db2c44e7dd2873918ca9455f839600080a36001600160a01b03191617600155005b60405162461bcd60e51b815260206004820152600b60248201526a07265676973747261723d360ac1b6044820152606490fd5b6000546001600160a01b0316330361063e57565b606460405162461bcd60e51b815260206004820152602060248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152fd5b1561068957565b60405162461bcd60e51b81526020600482015260116024820152701c9959da5cdd1c985c881b9bdd081cd95d607a1b6044820152606490fd5b156106c957565b60405162461bcd60e51b815260206004820152600c60248201526b66757475726520626c6f636b60a01b6044820152606490fd5b1561070457565b60405162461bcd60e51b81526020600482015260096024820152680e8dede40cce4cae6d60bb1b6044820152606490fd5b1561073c57565b60405162461bcd60e51b8152602060048201526007602482015266195e1c1a5c995960ca1b6044820152606490fd5b50634e487b7160e01b600052604160045260246000fd5b6040810190811067ffffffffffffffff82111761079e57604052565b6107a661076b565b604052565b90601f8019910116810190811067ffffffffffffffff82111761079e57604052565b6040519061012a82610782565b60209067ffffffffffffffff81116107f8575b601f01601f19160190565b61080061076b565b6107ed565b929192610811826107da565b9161081f60405193846107ab565b82948184528183011161000e578281602093846000960137010152565b1561084357565b60405162461bcd60e51b815260206004820152600a6024820152693130b2103432b0b232b960b11b6044820152606490fd5b50634e487b7160e01b600052603260045260246000fd5b60c09080516005101561089d570190565b6108a5610875565b0190565b60809080516003101561089d570190565b60209080511561089d570190565b60409080516001101561089d570190565b60609080516002101561089d570190565b6102209080516010101561089d570190565b6020918151811015610911575b60051b010190565b610919610875565b610909565b602081519101519060208110610932575090565b6000199060200360031b1b1690565b1561094857565b60405162461bcd60e51b81526020600482015260116024820152703130b2103932b1b2b4b83a10383937b7b360791b6044820152606490fd5b9081602091031261000e575167ffffffffffffffff8116810361000e5790565b506040513d6000823e3d90fd5b9081602091031261000e57516109c38161010c565b90565b156109cd57565b60405162461bcd60e51b8152602060048201526009602482015268373790383934b1b2b960b91b6044820152606490fd5b919082604091031261000e5760208251610a178161010c565b92015190565b928060809160409467ffffffffffffffff94989798875260606020880152816060880152838701376000828287010152601f80199101168401019416910152565b15610a6557565b60405162461bcd60e51b815260206004820152600e60248201526d189d5c9b881d1bdbc81cdb585b1b60921b6044820152606490fd5b9596919a90999798939460015498600160a01b6001900398898b169a8b1515610ac390610682565b884311610acf906106c2565b8843039060a01c60ff16811015610ae5906106fd565b6101001015610af390610735565b610afe368683610805565b948551602080970120894014610b139061083c565b3690610b1e92610805565b610b2790611806565b610b30906118a3565b610b399061088c565b51610b4390611971565b610b4c9061091e565b91610b56906112d4565b90610b6236888e610805565b933690610b6e92610805565b90610b78936113bb565b610b8190610941565b60408051633b50e96360e21b8152600481018b9052979092908a90838a602481845afa998a15610e24575b60009a610de7575b50845163326018d760e01b815260048101929092528492918991859082908180602481015b03915afa908115610dda575b600091610dad575b5016610bfa8115156109c6565b610c0536838f610805565b8481519101209c8d610c2c85519c8d9586948594638847fbc960e01b865260048601610a1d565b03915afa928315610da0575b6000978894610d2a575b50610d2594939291610c91610c88888b610c837faae8e8b7cd4b0ea170187352e61958258abf3094ebe47b20f3776d87587d74199d9e610d00973691610805565b610ef0565b94851015610a5e565b610cd2610c9f8d8d8a610e31565b610cc28c610cab6107cd565b8881526001600160a01b0390911694810194909452565b6000526002602052604060002090565b600190825181550190602060018060a01b03910151166bffffffffffffffffffffffff60a01b825416179055565b516001600160a01b039097168752602087015260408601529116929081906060820190565b0390a4565b7faae8e8b7cd4b0ea170187352e61958258abf3094ebe47b20f3776d87587d74199850610d2595945091610c91610c8888610c83610d828897610d0097993d8a11610d99575b610d7a81836107ab565b8101906109fe565b909d50989950959693955092939250610c42915050565b503d610d70565b610da86109a1565b610c38565b610dcd9150853d8711610dd3575b610dc581836107ab565b8101906109ae565b38610bed565b503d610dbb565b610de26109a1565b610be5565b89919a5084869493610e11610bd993833d8511610e1d575b610e0981836107ab565b810190610981565b9c935093945050610bb4565b503d610dff565b610e2c6109a1565b610bac565b916040519160208301936bffffffffffffffffffffffff199060601b16845260348301526054820152605481526080810181811067ffffffffffffffff821117610e7f575b60405251902090565b610e8761076b565b610e76565b50634e487b7160e01b600052601160045260246000fd5b9060018201809211610eb157565b61012a610e8c565b6001019081600111610eb157565b6080019081608011610eb157565b9060208201809211610eb157565b91908201809211610eb157565b929192610f1f610f19610f14610f0f610f0a60009561113e565b611806565b6118a3565b6108a9565b516118a3565b9360005b855181101561104c57610f39610f1982886108fc565b610f4b610f45826108ba565b516119cc565b6001600160a01b0390811686821614610f69575b5050600101610f23565b610f75610f19836108c8565b90600382511015610f87575b50610f5f565b7fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef610fc2610fbd610fb7856108ba565b51611971565b61091e565b03610f8157610ffd610feb610feb610fbd610fb7610ff7610feb610feb610fbd610fb78b6108c8565b6001600160a01b031690565b966108d9565b918116858216149182611041575b5050611019575b8080610f81565b6001919361103461102f610fb761103a946108d9565b611218565b90610ee3565b9290611012565b16159050388061100b565b5050925050565b90602091805182101561106557010190565b61106d610875565b010190565b60f719810191908211610eb157565b60bf19810191908211610eb157565b60b719810191908211610eb157565b607f19810191908211610eb157565b91908203918211610eb157565b604051906110c882610782565b6001825260203681840137565b6040516020810181811067ffffffffffffffff8211176110ff575b60405260008152906000368137565b61110761076b565b6110f0565b90611116826107da565b61112360405191826107ab565b8281528092611134601f19916107da565b0190602036910137565b805115801590816111c4575b506111525790565b90611169825160001981019081116111b75761110c565b600092835b82518110156111b0578061119d61118f611189600194610ea3565b85611053565b516001600160f81b03191690565b861a6111a98286611053565b530161116e565b5090925050565b6111bf610e8c565b61110c565b90506111dd575b607f602082015160f81c11153861114a565b6111e5610875565b6111cb565b908160011b9180830460021490151715610eb157565b600181901b91906001600160ff1b03811603610eb157565b6020808251116112705780820151915181811061123457505090565b8103908111611263575b6001600160fd1b0381168103611256575b60031b1c90565b61125e610e8c565b61124f565b61126b610e8c565b61123e565b6064906040519062461bcd60e51b8252600482015260086024820152676f766572666c6f7760c01b6044820152fd5b604051906112ac82610782565b60018252600160ff1b6020830152565b60019060001981146112cc570190565b6108a5610e8c565b80156113b257806000908282935b61139e57506001831480611393575b61136c576113016111bf84610eb9565b9261132761131761131183610ec7565b60ff1690565b60f81b6001600160f81b03191690565b831a611332856108ba565b5390815b6113405750505090565b60f881901b6001600160f81b031916831a61135b8386611053565b536000199091019060081c81611336565b91506113766110bb565b9160f81b6001600160f81b031916901a61138f826108ba565b5390565b50607f8111156112f1565b926113a8906112bc565b9260081c806112e2565b506109c361129f565b93916113cf610f0f6113d592959395611806565b93611665565b909360009182955b855187101561156f576113f3610fb788886108fc565b805191602092838301200361156257610f0f61140e91611806565b8051601181036114c65750825185146114a15760ff61143961143361118f8887611053565b60f81c90565b1690601082116114935761145091610fb7916108fc565b90815180156114935791816114769261147c941060001461148357815191012094610ea3565b966112bc565b95926113dd565b5061148d9061157b565b94610ea3565b505050505050915050600090565b91509450610fb7919395506114b692506108ea565b8281519101209181519101201490565b600290989297939496989591951460001461155657806110346114fd92866114f86114f3610fb78b6108ba565b611665565b6115c6565b92838151146000146115195750505050610fb76114b6916108c8565b919561152f610fb761147c9396999795996108c8565b9080825110600014611546578151910120936112bc565b506115509061157b565b936112bc565b50505050505050600090565b5050505050915050600090565b50505050915050600090565b602081510361158b576020015190565b60405162461bcd60e51b815260206004820152601360248201527209aa0a87440d2dcecc2d8d2c840d8cadccee8d606b1b6044820152606490fd5b60009281511561165f57925b815181108061163e575b1561162a576001600160f81b03196115f48284611053565b511661161d61161061118f848801808911611631575b87611053565b6001600160f81b03191690565b0361162a576001016115d2565b9250505090565b611639610e8c565b61160a565b50808401808511611652575b8351116115dc565b61165a610e8c565b61164a565b50505090565b908151156117e35761167a6111bf83516111ea565b60005b835181101561170657806116a96113176116a061143361118f611701968a611053565b60041c600f1690565b6116c06116b583611200565b9160001a9185611053565b536116e7600f6116d661143361118f858a611053565b1660f81b6001600160f81b03191690565b6116fb6116b56116f684611200565b610ea3565b536112bc565b61167d565b509091508051156109c35760ff61172261143361118f846108ba565b16801580156117d9575b80156117cf575b80156117c5575b611742575090565b906001918281149081156117ba575b50156117b15760ff825b169161176b6111bf8484516110ae565b926000825b61177c575b5050505090565b84518110156117ac578061179961118f61160a856117a695610ee3565b60001a6116fb8288611053565b82611770565b611775565b60ff600261175b565b600391501438611751565b506003811461173a565b5060028114611733565b506001811461172c565b90506109c36110d5565b604051906117fa82610782565b60006020838281520152565b61180e6117ed565b5060208151916040519261182184610782565b835201602082015290565b60209067ffffffffffffffff8111611846575b60051b0190565b61184e61076b565b61183f565b9061185d8261182c565b61186a60405191826107ab565b828152809261187b601f199161182c565b019060005b82811061188c57505050565b6020906118976117ed565b82828501015201611880565b9060208083019260c084515160001a1061192c576118c090611a85565b906118d86118cd83611853565b945161103481611bb8565b6000905b8382106118e95750505050565b611924816118f8600193611b2f565b906119016107cd565b8281528187820152611913868b6108fc565b5261191e858a6108fc565b50610ee3565b9101906118dc565b60405162461bcd60e51b815260048101839052601d60248201527f524c505265616465723a206974656d206973206e6f742061206c6973740000006044820152606490fd5b9061012a60208301926119848451611bb8565b9051908082039182116119bf575b61199b8261110c565b94519081018091116119b2575b6020850190611c0e565b6119ba610e8c565b6119a8565b6119c7610e8c565b611992565b805160158114908115611a44575b50156119ff5760206119f891016119f18151611bb8565b9051610ee3565b5160601c90565b60405162461bcd60e51b815260206004820152601a60248201527f524c505265616465723a20696e76616c696420616464726573730000000000006044820152606490fd5b601591501180611a55575b386119da565b506014611a656020830151611bb8565b8251908103908111611a78575b14611a4f565b611a80610e8c565b611a72565b6020810160c081515160001a10611af457611abc6000928251611aa781611bb8565b8101809111611ae7575b939251905190610ee3565b915b828110611acb5750905090565b611adb81611034611ae193611b2f565b916112bc565b90611abe565b611aef610e8c565b611ab1565b60405162461bcd60e51b815260206004820152601360248201527214931414995859195c8e881b9bdd081b1a5cdd606a1b6044820152606490fd5b805160001a906080821015611b45575050600190565b60b8821015611b5b57506116f66109c39161109f565b60c0821015611b9057611b8b611b766116f66109c394611090565b916001836020036101000a9101510491610eb9565b610ee3565b60f8821015611ba657506116f66109c391611081565b611b8b611b766116f66109c394611072565b5160001a60808110611c085760b88110611bf35760c08110611bf95760f88110611bf35760f6198101908111611beb5790565b6109c3610e8c565b50600190565b60b6198101908111611beb5790565b50600090565b8215611c8c579190915b6020808310611c695790611c3a91815185528101809111611c5c575b92610ed5565b90601f198101908111611c4f575b9091611c18565b611c57610e8c565b611c48565b611c64610e8c565b611c34565b50908015611c8c57600019906020036101000a0190811990511690825116179052565b50505056fea2646970667358221220b02468bb45325918af957b6243e2f806ffbb0b6012a56f5b6688d270fdcbf2d464736f6c63430008110033
Verified Source Code Full Match
Compiler: v0.8.17+commit.8df45f5f
EVM: london
Optimization: Yes (200 runs)
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
BurnReceiptVerifier.sol 217 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "./lib/RLPReader.sol";
import "./lib/MerklePatriciaProof.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import { ISubdomainPricer } from "./pricers/ISubdomainPricer.sol"; // <-- use the canonical interface
interface IRegistrarView {
function currentDuration(bytes32 parentNode) external view returns (uint64);
function domainPricer(bytes32 parentNode) external view returns (address);
}
/**
* @title BurnReceiptVerifier
* @notice Verifies, on-chain, that a buyer self-burned >= required amount of the correct token
* for a given (parentNode, label). Proof is a Merkle-Patricia receipt proof anchored
* by blockhash(blockNumber); thus, the burn must be recorded within the last 256 blocks.
*
* Flow:
* - User burns token (emits Transfer(buyer -> 0x0, amount))
* - Frontend builds header+receipt+MPT proof client-side and calls recordSelfBurn(...)
* - Verifier:
* * anchors header with blockhash
* * verifies receipt inclusion against receiptsRoot
* * calls registrar.domainPricer(...) and registrar.currentDuration(...)
* * computes expected (token, required) via pricer.price(...)
* * scans logs for Transfer(buyer->0x0, amount) on expected token, requires amount >= required
* * stores {amount, token} for (buyer, parentNode, labelhash)
* - Registrar’s claimAfterSelfBurn(...) re-checks and consumes authorization (one-time).
*/
contract BurnReceiptVerifier is Ownable {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
/// @dev ERC20 Transfer topic = keccak256("Transfer(address,address,uint256)")
bytes32 private constant TRANSFER_TOPIC =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
IRegistrarView public registrar;
uint8 public minConfirmations = 6; // small anti-reorg buffer; adjustable by owner
struct Auth {
uint256 amount; // actual burned (sum of matching logs)
address token; // token address observed in the receipt
}
// key = keccak256(buyer, parentNode, labelhash)
mapping(bytes32 => Auth) private _auth;
event RegistrarUpdated(address indexed oldReg, address indexed newReg);
event MinConfirmationsUpdated(uint8 oldVal, uint8 newVal);
event Authorized(address indexed buyer, bytes32 indexed parentNode, bytes32 indexed labelhash, address token, uint256 amount, uint256 blockNumber);
event Consumed(address indexed buyer, bytes32 indexed parentNode, bytes32 indexed labelhash);
constructor(address registrar_) {
require(registrar_ != address(0), "registrar=0");
registrar = IRegistrarView(registrar_);
emit RegistrarUpdated(address(0), registrar_);
}
function setRegistrar(address registrar_) external onlyOwner {
require(registrar_ != address(0), "registrar=0");
emit RegistrarUpdated(address(registrar), registrar_);
registrar = IRegistrarView(registrar_);
}
function setMinConfirmations(uint8 confs) external onlyOwner {
emit MinConfirmationsUpdated(minConfirmations, confs);
minConfirmations = confs;
}
// --------------------------- Public views for registrar ---------------------------
function isAuthorized(address buyer, bytes32 parentNode, bytes32 labelhash) external view returns (bool) {
return _auth[_key(buyer, parentNode, labelhash)].amount != 0;
}
function authorizedAmount(address buyer, bytes32 parentNode, bytes32 labelhash) external view returns (uint256) {
return _auth[_key(buyer, parentNode, labelhash)].amount;
}
function authorizedToken(address buyer, bytes32 parentNode, bytes32 labelhash) external view returns (address) {
return _auth[_key(buyer, parentNode, labelhash)].token;
}
function consume(address buyer, bytes32 parentNode, bytes32 labelhash) external {
bytes32 k = _key(buyer, parentNode, labelhash);
require(_auth[k].amount != 0, "not authorized");
delete _auth[k];
emit Consumed(buyer, parentNode, labelhash);
}
// --------------------------- Core: record proof of self-burn ---------------------------
/**
* @param buyer The address that executed the burn tx (msg.sender in the burn call)
* @param parentNode The ENS parent node under which the label will be registered
* @param label The human-readable label (used by pricer; labelhash is keccak256(bytes(label)))
* @param blockNumber Block number containing the burn receipt
* @param txIndex Index of the burn transaction within the block
* @param headerRlp Full RLP-encoded block header for @blockNumber
* @param receiptRlp RLP-encoded receipt for the burn transaction (typed or legacy)
* @param rlpParentNodes RLP-encoded list of MPT proof nodes along the receipt path
*/
function recordSelfBurn(
address buyer,
bytes32 parentNode,
string calldata label,
uint256 blockNumber,
uint256 txIndex,
bytes calldata headerRlp,
bytes calldata receiptRlp,
bytes calldata rlpParentNodes
) external {
require(address(registrar) != address(0), "registrar not set");
require(block.number > blockNumber, "future block");
unchecked {
// reorg guard (minConfirmations) + freshness (≤256 blocks for blockhash)
require(block.number - blockNumber >= minConfirmations, "too fresh");
require(block.number - blockNumber <= 256, "expired");
}
// Anchor header by blockhash
require(keccak256(headerRlp) == blockhash(blockNumber), "bad header");
// Extract receiptsRoot from header (field index 5)
RLPReader.RLPItem[] memory h = headerRlp.toRlpItem().toList();
bytes32 receiptsRoot = bytes32(h[5].toBytes());
// Verify receipt inclusion via MPT (key = RLP(txIndex))
bytes memory key = _rlpEncodeUint(txIndex);
bool ok = MerklePatriciaProof.verify(receiptRlp, key, rlpParentNodes, receiptsRoot);
require(ok, "bad receipt proof");
// Compute current price context trustlessly from registrar/pricer
uint64 duration = registrar.currentDuration(parentNode);
address pricerAddr = registrar.domainPricer(parentNode);
require(pricerAddr != address(0), "no pricer");
bytes32 labelhash = keccak256(bytes(label));
(address expectedToken, uint256 required) = ISubdomainPricer(pricerAddr).price(labelhash, label, duration);
// Parse receipt logs → sum matching Transfer(buyer->0x0) for expected token
uint256 burned = _sumBurnedAmount(receiptRlp, expectedToken, buyer);
require(burned >= required, "burn too small");
// Store facts for one-time authorization
bytes32 k = _key(buyer, parentNode, labelhash);
_auth[k] = Auth({ amount: burned, token: expectedToken });
emit Authorized(buyer, parentNode, labelhash, expectedToken, burned, blockNumber);
}
// --------------------------- Internal helpers ---------------------------
function _key(address buyer, bytes32 parentNode, bytes32 labelhash) private pure returns (bytes32) {
return keccak256(abi.encodePacked(buyer, parentNode, labelhash));
}
// Strips receipt type (EIP-2718) if present; returns the RLP list payload
function _normalize(bytes memory receipt) private pure returns (bytes memory) {
if (receipt.length > 0 && uint8(receipt[0]) <= 0x7f) {
bytes memory out = new bytes(receipt.length - 1);
for (uint256 i; i < out.length; ) { out[i] = receipt[i + 1]; unchecked { ++i; } }
return out;
}
return receipt;
}
function _sumBurnedAmount(bytes memory receiptRlp, address token, address buyer) private pure returns (uint256 total) {
bytes memory dec = _normalize(receiptRlp);
RLPReader.RLPItem[] memory fields = dec.toRlpItem().toList();
// fields: [status/postState, cumulativeGasUsed, logsBloom, logs]
RLPReader.RLPItem[] memory logs = fields[3].toList();
for (uint256 i = 0; i < logs.length; ) {
RLPReader.RLPItem[] memory logEntry = logs[i].toList();
address addr = logEntry[0].toAddress();
if (addr == token) {
RLPReader.RLPItem[] memory topics = logEntry[1].toList();
if (topics.length >= 3) {
bytes32 sig = bytes32(topics[0].toBytes());
if (sig == TRANSFER_TOPIC) {
address fromTopic = address(uint160(uint256(bytes32(topics[1].toBytes()))));
address toTopic = address(uint160(uint256(bytes32(topics[2].toBytes()))));
if (fromTopic == buyer && toTopic == address(0)) {
// data encodes uint256(value)
uint256 amount = _toUint(logEntry[2].toBytes());
total += amount;
}
}
}
}
unchecked { ++i; }
}
}
function _toUint(bytes memory b) private pure returns (uint256 x) {
require(b.length <= 32, "overflow");
assembly { x := mload(add(b, 32)) }
if (b.length < 32) x >>= (8 * (32 - b.length));
}
// Minimal RLP encoding of a non-negative integer
function _rlpEncodeUint(uint256 x) private pure returns (bytes memory) {
if (x == 0) return hex"80";
uint256 tmp = x; uint256 len;
while (tmp != 0) { len++; tmp >>= 8; }
if (len == 1 && x <= 0x7f) {
bytes memory b1 = new bytes(1); b1[0] = bytes1(uint8(x)); return b1;
}
bytes memory out = new bytes(1 + len);
out[0] = bytes1(uint8(0x80 + len));
for (uint256 i = len; i > 0; ) { out[i] = bytes1(uint8(x & 0xff)); x >>= 8; unchecked { --i; } }
return out;
}
}
MerklePatriciaProof.sol 115 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import {RLPReader} from "./RLPReader.sol";
/**
* @title MerklePatriciaProof
* @notice Verifies inclusion proofs for Ethereum's Merkle‑Patricia Trie (MPT).
* @dev API and logic adapted for Solidity ^0.8 from Polygon's fx-portal implementation.
*/
library MerklePatriciaProof {
using RLPReader for bytes;
using RLPReader for RLPReader.RLPItem;
/**
* @dev Verifies a Merkle‑Patricia proof.
* @param value RLP-encoded value stored at the leaf (e.g., the receipt RLP).
* @param encodedPath Path key used in the trie (NOT hashed) – for receipts/tx tries this is RLP(txIndex).
* @param rlpParentNodes RLP-encoded list of proof nodes (concatenate the stack of RLP nodes as an RLP list).
* @param root Trie root hash (e.g., receiptsRoot).
*/
function verify(
bytes memory value,
bytes memory encodedPath,
bytes memory rlpParentNodes,
bytes32 root
) internal pure returns (bool) {
RLPReader.RLPItem[] memory parentNodes = rlpParentNodes.toRlpItem().toList();
bytes memory path = _getNibbleArray(encodedPath);
bytes32 nodeKey = root;
uint256 pathPtr = 0;
for (uint256 i = 0; i < parentNodes.length; i++) {
bytes memory currentNode = parentNodes[i].toBytes();
if (keccak256(currentNode) != nodeKey) return false;
RLPReader.RLPItem[] memory node = currentNode.toRlpItem().toList();
if (node.length == 17) {
if (pathPtr == path.length) {
return keccak256(node[16].toBytes()) == keccak256(value);
}
uint8 nib = uint8(path[pathPtr]);
if (nib > 16) return false;
bytes memory next = node[nib].toBytes();
if (next.length == 0) return false;
if (next.length < 32) nodeKey = keccak256(next);
else nodeKey = bytes32(bytes32FromBytes(next));
pathPtr += 1;
} else if (node.length == 2) {
bytes memory partialPath = _getNibbleArray(node[0].toBytes());
uint256 traverse = _nibblesToTraverse(partialPath, path, pathPtr);
pathPtr += traverse;
if (pathPtr == path.length) {
return keccak256(node[1].toBytes()) == keccak256(value);
} else {
bytes memory next = node[1].toBytes();
if (next.length < 32) nodeKey = keccak256(next);
else nodeKey = bytes32(bytes32FromBytes(next));
}
} else {
return false;
}
}
return false;
}
function bytes32FromBytes(bytes memory b) private pure returns (bytes32 out) {
require(b.length == 32, "MPT: invalid length");
assembly { out := mload(add(b, 32)) }
}
function _nibblesToTraverse(
bytes memory partialPath,
bytes memory path,
uint256 pathPtr
) private pure returns (uint256) {
uint256 len = 0;
if (partialPath.length == 0) return 0;
while (len < partialPath.length && pathPtr + len < path.length) {
if (partialPath[len] != path[pathPtr + len]) break;
unchecked { ++len; }
}
return len;
}
// Expand bytes to nibbles and, if input is a compact-encoded node key,
// strip the hex-prefix header.
function _getNibbleArray(bytes memory b) private pure returns (bytes memory) {
if (b.length == 0) return new bytes(0);
bytes memory nibbles = new bytes(b.length * 2);
for (uint256 i = 0; i < b.length; i++) {
nibbles[2 * i] = bytes1(uint8(b[i]) >> 4);
nibbles[2 * i + 1] = bytes1(uint8(b[i]) & 0x0f);
}
if (nibbles.length == 0) return nibbles;
uint8 hpNibble = uint8(nibbles[0]);
if (hpNibble == 0 || hpNibble == 1 || hpNibble == 2 || hpNibble == 3) {
uint256 offset = (hpNibble == 1 || hpNibble == 3) ? 1 : 2;
bytes memory res = new bytes(nibbles.length - offset);
for (uint256 i = 0; i < res.length; i++) { res[i] = nibbles[i + offset]; }
return res;
}
return nibbles;
}
}
RLPReader.sol 108 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
/**
* @title RLPReader
* @dev A lightweight RLP decoding library adapted for Solidity ^0.8.
* Based on the MIT-licensed work of hamdiallam/solidity-rlp and Polygon's fx-portal.
*/
library RLPReader {
uint8 private constant STRING_SHORT_START = 0x80;
uint8 private constant STRING_LONG_START = 0xb8;
uint8 private constant LIST_SHORT_START = 0xc0;
uint8 private constant LIST_LONG_START = 0xf8;
struct RLPItem { uint256 len; uint256 memPtr; }
struct Iterator { RLPItem item; uint256 nextPtr; }
function toRlpItem(bytes memory item) internal pure returns (RLPItem memory) {
uint256 memPtr; assembly { memPtr := add(item, 0x20) } return RLPItem(item.length, memPtr);
}
function iterator(RLPItem memory item) internal pure returns (Iterator memory it) {
uint256 ptr = item.memPtr + _payloadOffset(item.memPtr); it = Iterator(item, ptr);
}
function hasNext(Iterator memory it) internal pure returns (bool) {
return it.nextPtr < it.item.memPtr + it.item.len;
}
function next(Iterator memory it) internal pure returns (RLPItem memory subItem) {
require(hasNext(it), "RLPReader: iterator overflow");
uint256 ptr = it.nextPtr; uint256 itemLen = _itemLength(ptr);
subItem = RLPItem(itemLen, ptr); it.nextPtr = ptr + itemLen;
}
function isList(RLPItem memory item) internal pure returns (bool) {
uint8 b0; uint256 memPtr = item.memPtr; assembly { b0 := byte(0, mload(memPtr)) } return b0 >= LIST_SHORT_START;
}
function toList(RLPItem memory item) internal pure returns (RLPItem[] memory result) {
require(isList(item), "RLPReader: item is not a list");
uint256 items = numItems(item); result = new RLPItem[](items);
uint256 memPtr = item.memPtr + _payloadOffset(item.memPtr);
for (uint256 i; i < items; ) { uint256 len = _itemLength(memPtr); result[i] = RLPItem(len, memPtr); memPtr += len; unchecked { ++i; } }
}
function toBytes(RLPItem memory item) internal pure returns (bytes memory bts) {
uint256 offset = _payloadOffset(item.memPtr); uint256 len = item.len - offset; bts = new bytes(len);
uint256 src = item.memPtr + offset; uint256 dest; assembly { dest := add(bts, 0x20) } _copy(src, dest, len);
}
function toAddress(RLPItem memory item) internal pure returns (address addr) {
require(item.len == 21 || (item.len > 21 && _payloadLen(item) == 20), "RLPReader: invalid address");
uint256 offset = _payloadOffset(item.memPtr); uint256 mem = item.memPtr + offset;
assembly { addr := div(mload(mem), 0x1000000000000000000000000) } // right shift by 12 bytes
}
function toUint(RLPItem memory item) internal pure returns (uint256 result) {
uint256 offset = _payloadOffset(item.memPtr); uint256 len = item.len - offset;
require(len > 0 && len <= 32, "RLPReader: invalid uint"); uint256 mem = item.memPtr + offset; assembly { result := mload(mem) }
if (len < 32) { result >>= (8 * (32 - len)); }
}
function numItems(RLPItem memory item) internal pure returns (uint256) {
require(isList(item), "RLPReader: not list"); uint256 count; uint256 currPtr = item.memPtr + _payloadOffset(item.memPtr); uint256 endPtr = item.memPtr + item.len;
while (currPtr < endPtr) { currPtr += _itemLength(currPtr); count++; } return count;
}
function _payloadLen(RLPItem memory item) private pure returns (uint256) {
uint256 offset = _payloadOffset(item.memPtr); return item.len - offset;
}
function _itemLength(uint256 memPtr) private pure returns (uint256 len) {
uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) }
if (byte0 < STRING_SHORT_START) { len = 1; }
else if (byte0 < STRING_LONG_START) { len = byte0 - STRING_SHORT_START + 1; }
else if (byte0 < LIST_SHORT_START) {
uint256 llength = byte0 - STRING_LONG_START + 1; uint256 dataLen;
assembly { dataLen := div(mload(add(memPtr, 1)), exp(256, sub(32, llength))) }
len = 1 + llength + dataLen;
} else if (byte0 < LIST_LONG_START) { len = byte0 - LIST_SHORT_START + 1; }
else {
uint256 llength = byte0 - LIST_LONG_START + 1; uint256 dataLen;
assembly { dataLen := div(mload(add(memPtr, 1)), exp(256, sub(32, llength))) }
len = 1 + llength + dataLen;
}
}
function _payloadOffset(uint256 memPtr) private pure returns (uint256) {
uint256 byte0; assembly { byte0 := byte(0, mload(memPtr)) }
if (byte0 < STRING_SHORT_START) return 0;
if (byte0 < STRING_LONG_START) return 1;
if (byte0 < LIST_SHORT_START) return byte0 - (STRING_LONG_START - 1);
if (byte0 < LIST_LONG_START) return 1;
return byte0 - (LIST_LONG_START - 1);
}
function _copy(uint256 src, uint256 dest, uint256 len) private pure {
if (len == 0) return;
for (; len >= 32; len -= 32) { assembly { mstore(dest, mload(src)) } src += 32; dest += 32; }
if (len == 0) return;
uint256 mask; assembly {
mask := sub(exp(256, sub(32, len)), 1)
let srcpart := and(mload(src), not(mask))
let destpart := and(mload(dest), mask)
mstore(dest, or(destpart, srcpart))
}
}
}
ISubdomainPricer.sol 10 lines
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface ISubdomainPricer {
function price(
bytes32 parentNode,
string calldata label,
uint256 duration
) external view returns (address token, uint256 price);
}
Read Contract
authorizedAmount 0x9ca33928 → uint256
authorizedToken 0xd6c6e1c8 → address
isAuthorized 0x40891774 → bool
minConfirmations 0xe7c4393e → uint8
owner 0x8da5cb5b → address
registrar 0x2b20e397 → address
Write Contract 6 functions
These functions modify contract state and require a wallet transaction to execute.
consume 0x0b03ee31
address buyer
bytes32 parentNode
bytes32 labelhash
recordSelfBurn 0xf0560dd8
address buyer
bytes32 parentNode
string label
uint256 blockNumber
uint256 txIndex
bytes headerRlp
bytes receiptRlp
bytes rlpParentNodes
renounceOwnership 0x715018a6
No parameters
setMinConfirmations 0x32ea039a
uint8 confs
setRegistrar 0xfaab9d39
address registrar_
transferOwnership 0xf2fde38b
address newOwner
Recent Transactions
No transactions found for this address