From 2d9a0770453244aaa5c237a43d0938b92f55e535 Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 27 Feb 2026 17:40:27 +0800 Subject: [PATCH 01/14] init --- .gitmodules | 3 + contract-tests/Storage.t.sol | 37 +++++ contract-tests/UniversalTimestamps.t.sol | 49 ------ contracts/Constants.sol | 13 ++ contracts/IUniversalTimestamps.sol | 11 -- contracts/L1/IL1AnchoringGateway.sol | 14 ++ contracts/L1/L1AnchoringGateway.sol | 60 +++++++ contracts/L1/L1AnchoringGatewayStorage.sol | 30 ++++ contracts/L2/manager/IL1AnchoringManager.sol | 48 ++++++ contracts/L2/manager/L1AnchoringManager.sol | 122 ++++++++++++++ .../L2/manager/L1AnchoringManagerStorage.sol | 32 ++++ .../L2/manager/L1AnchoringManagerTypes.sol | 11 ++ contracts/L2/oracle/IL1FeeOracle.sol | 39 +++++ contracts/L2/oracle/L1FeeOracle.sol | 85 ++++++++++ contracts/core/IUniversalTimestamps.sol | 22 +++ contracts/core/MerkleTree.sol | 155 ++++++++++++++++++ contracts/{ => core}/UniversalTimestamps.sol | 44 ++--- contracts/core/UniversalTimestampsStorage.sol | 26 +++ contracts/core/UniversalTimestampsTypes.sol | 11 ++ foundry.lock | 6 + foundry.toml | 5 + lib/scroll-contracts | 1 + remappings.txt | 1 + script/Deploy.s.sol | 93 +++++++++++ script/DeployCreate2.s.sol | 35 ---- script/E2E.s.sol | 39 +++++ 26 files changed, 868 insertions(+), 124 deletions(-) create mode 100644 contract-tests/Storage.t.sol delete mode 100644 contract-tests/UniversalTimestamps.t.sol create mode 100644 contracts/Constants.sol delete mode 100644 contracts/IUniversalTimestamps.sol create mode 100644 contracts/L1/IL1AnchoringGateway.sol create mode 100644 contracts/L1/L1AnchoringGateway.sol create mode 100644 contracts/L1/L1AnchoringGatewayStorage.sol create mode 100644 contracts/L2/manager/IL1AnchoringManager.sol create mode 100644 contracts/L2/manager/L1AnchoringManager.sol create mode 100644 contracts/L2/manager/L1AnchoringManagerStorage.sol create mode 100644 contracts/L2/manager/L1AnchoringManagerTypes.sol create mode 100644 contracts/L2/oracle/IL1FeeOracle.sol create mode 100644 contracts/L2/oracle/L1FeeOracle.sol create mode 100644 contracts/core/IUniversalTimestamps.sol create mode 100644 contracts/core/MerkleTree.sol rename contracts/{ => core}/UniversalTimestamps.sol (51%) create mode 100644 contracts/core/UniversalTimestampsStorage.sol create mode 100644 contracts/core/UniversalTimestampsTypes.sol create mode 160000 lib/scroll-contracts create mode 100644 script/Deploy.s.sol delete mode 100644 script/DeployCreate2.s.sol create mode 100644 script/E2E.s.sol diff --git a/.gitmodules b/.gitmodules index 23acfb1..a729b0e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/scroll-contracts"] + path = lib/scroll-contracts + url = https://github.com/scroll-tech/scroll-contracts diff --git a/contract-tests/Storage.t.sol b/contract-tests/Storage.t.sol new file mode 100644 index 0000000..4cce293 --- /dev/null +++ b/contract-tests/Storage.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Test, console} from "forge-std/Test.sol"; +import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; +import {UniversalTimestampsStorage} from "../contracts/core/UniversalTimestampsStorage.sol"; +import {L1AnchoringGatewayStorage} from "../contracts/L1/L1AnchoringGatewayStorage.sol"; +import {L1AnchoringManagerStorage} from "../contracts/L2/manager/L1AnchoringManagerStorage.sol"; + +/** + * @title UniversalTimestampsStorageTest + * @dev Tests to verify the integrity of the storage slot derivation and layout. + */ +contract StorageSlotTest is Test { + using SlotDerivation for string; + + function test_ERC7201_SlotDerivation() public pure { + test(UniversalTimestampsStorage.SLOT, UniversalTimestampsStorage.NAMESPACE); + + test(L1AnchoringGatewayStorage.SLOT, L1AnchoringGatewayStorage.NAMESPACE); + + test(L1AnchoringManagerStorage.SLOT, L1AnchoringManagerStorage.NAMESPACE); + } + + function test(bytes32 hardcodedSlot, string memory namespace) internal pure virtual { + bytes32 expectedSlot = namespace.erc7201Slot(); + assertEq( + hardcodedSlot, + expectedSlot, + "Storage SLOT mismatch: The hardcoded slot does not match the ERC-7201 derivation of the namespace." + ); + + console.log("Namespace:", namespace); + console.log("Hardcoded Slot:", vm.toString(hardcodedSlot)); + console.log("Expected Slot: ", vm.toString(expectedSlot)); + } +} diff --git a/contract-tests/UniversalTimestamps.t.sol b/contract-tests/UniversalTimestamps.t.sol deleted file mode 100644 index 4c70b50..0000000 --- a/contract-tests/UniversalTimestamps.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {Test, console} from "forge-std/Test.sol"; -import {UniversalTimestamps} from "../contracts/UniversalTimestamps.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -contract UniversalTimestampsV2 is UniversalTimestamps { - function version() public pure returns (string memory) { - return "V2"; - } -} - -contract UniversalTimestampsTest is Test { - UniversalTimestamps public proxy; - address owner = address(1); - - function setUp() public { - UniversalTimestamps implementation = new UniversalTimestamps(); - - bytes memory initData = abi.encodeWithSelector(UniversalTimestamps.initialize.selector, owner); - ERC1967Proxy proxyAddress = new ERC1967Proxy(address(implementation), initData); - - proxy = UniversalTimestamps(address(proxyAddress)); - } - - function test_StoragePersistenceAfterUpgrade() public { - bytes32 root = keccak256("test_data"); - - proxy.attest(root); - uint256 timeV1 = proxy.timestamp(root); - assertGt(timeV1, 0); - - vm.startPrank(owner); - - UniversalTimestampsV2 v2Impl = new UniversalTimestampsV2(); - - proxy.upgradeToAndCall(address(v2Impl), ""); - - vm.stopPrank(); - - UniversalTimestampsV2 proxyV2 = UniversalTimestampsV2(address(proxy)); - - assertEq(proxyV2.version(), "V2"); - - assertEq(proxyV2.timestamp(root), timeV1); - console.log("Storage persisted across upgrade at namespaced slot."); - } -} diff --git a/contracts/Constants.sol b/contracts/Constants.sol new file mode 100644 index 0000000..dc707ad --- /dev/null +++ b/contracts/Constants.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {IUniversalTimestamps} from "./core/IUniversalTimestamps.sol"; +import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; + +library Constants { + IUniversalTimestamps public constant UTS = IUniversalTimestamps(0xdf939C24d9c075862837e3c9EC0cc1feD6376D59); + + IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = + IL1GasPriceOracle(0x5300000000000000000000000000000000000002); +} diff --git a/contracts/IUniversalTimestamps.sol b/contracts/IUniversalTimestamps.sol deleted file mode 100644 index b5dfb0b..0000000 --- a/contracts/IUniversalTimestamps.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -interface IUniversalTimestamps { - event Attested(bytes32 indexed root, address indexed sender, uint256 timestamp); - - function attest(bytes32 root) external; - - function timestamp(bytes32 root) external view returns (uint256); -} diff --git a/contracts/L1/IL1AnchoringGateway.sol b/contracts/L1/IL1AnchoringGateway.sol new file mode 100644 index 0000000..9e12eb6 --- /dev/null +++ b/contracts/L1/IL1AnchoringGateway.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +interface IL1AnchoringGateway { + /** + * @notice Submit a SINGLE aggregated Merkle Root to L1 and trigger L2 verification. + * @param merkleRoot The root of the Merkle Tree containing all roots in this batch. + * @param startIndex The queue index of the first root in this batch. + * @param count The number of roots in this batch. + * @dev Caller must send enough ETH to cover L1 Gas + L2 Execution Gas (which is now higher due to loop). + */ + function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count) external payable; +} diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol new file mode 100644 index 0000000..b80a02e --- /dev/null +++ b/contracts/L1/L1AnchoringGateway.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; +import {L1AnchoringGatewayStorage} from "./L1AnchoringGatewayStorage.sol"; +import {IL1AnchoringGateway} from "./IL1AnchoringGateway.sol"; +import {Constants} from "../Constants.sol"; +import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; +import {IL1AnchoringManager} from "../L2/manager/IL1AnchoringManager.sol"; + +contract L1AnchoringGateway is + Initializable, + OwnableUpgradeable, + UUPSUpgradeable, + ReentrancyGuardTransient, + IL1AnchoringGateway +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner, address l1Messenger, address l1AnchoringManagerL2) public initializer { + __Ownable_init(initialOwner); + + require(l1Messenger != address(0), "UTS: Invalid L1ScrollMessenger address"); + require(l1AnchoringManagerL2 != address(0), "UTS: Invalid L1AnchoringManagerL2 address"); + + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + $.l1Messenger = IL1ScrollMessenger(l1Messenger); + $.l1AnchoringManagerL2 = IL1AnchoringManager(l1AnchoringManagerL2); + } + + /// @inheritdoc IL1AnchoringGateway + function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count) external payable nonReentrant { + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + + require(address($.l1Messenger) != address(0), "UTS: L1 Scroll Messenger not set"); + require(address($.l1AnchoringManagerL2) != address(0), "UTS: L1 Anchoring Manager L2 not set"); + + Constants.UTS.attest(merkleRoot); + + bytes memory message = + abi.encodeCall(IL1AnchoringManager.confirmL1AnchoringBatch, (merkleRoot, startIndex, count, block.number)); + + $.l1Messenger.sendMessage{value: msg.value}( + address($.l1AnchoringManagerL2), + 0, + message, + 1_000_000, // TODO: Estimate proper gas limit + msg.sender // refund the caller for the gas cost of L2 execution + ); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} +} diff --git a/contracts/L1/L1AnchoringGatewayStorage.sol b/contracts/L1/L1AnchoringGatewayStorage.sol new file mode 100644 index 0000000..9e587ca --- /dev/null +++ b/contracts/L1/L1AnchoringGatewayStorage.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; +import {IL1AnchoringManager} from "../L2/manager/IL1AnchoringManager.sol"; + +/** + * @dev Library containing the ERC-7201 namespace constant. + * This keeps the implementation detail hidden from the interface. + */ +library L1AnchoringGatewayStorage { + string internal constant NAMESPACE = "uts.storage.L1AnchoringGateway"; + + /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.L1AnchoringGateway")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 internal constant SLOT = 0x8edb9fe689fd9379dceae5cf4dde34cad983b6db894e69fe7b25cb8e53843500; + + /// @custom:storage-location erc7201:uts.storage.L1AnchoringGateway + struct Storage { + /// @notice Reference to the L1 Scroll Messenger contract + IL1ScrollMessenger l1Messenger; + /// @notice Reference to the L1 Anchoring Manager contract on L2 (for address lookup) + IL1AnchoringManager l1AnchoringManagerL2; + } + + function get() internal pure returns (L1AnchoringGatewayStorage.Storage storage $) { + assembly ("memory-safe") { + $.slot := SLOT + } + } +} diff --git a/contracts/L2/manager/IL1AnchoringManager.sol b/contracts/L2/manager/IL1AnchoringManager.sol new file mode 100644 index 0000000..5c2b7b9 --- /dev/null +++ b/contracts/L2/manager/IL1AnchoringManager.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +interface IL1AnchoringManager { + /// @notice Emitted when a user pays to have their root anchored to L1. + event L1AnchoringQueued( + bytes32 indexed root, uint256 queueIndex, uint256 fee, uint256 blockNumber, uint256 timestamp + ); + + /// @notice Emitted when fee parameters are updated. + event FeeParametersUpdated(address indexed feeOracle, address indexed feeCollector); + /// @notice Emitted when fees are withdrawn by the fee collector. + event FeesWithdrawn(address indexed to, uint256 amount); + + /** + * @notice Submit a root for L2 timestamping + L1 anchoring. + * @dev Requires msg.value >= Oracle calculated fee. + */ + function submitForL1Anchoring(bytes32 root) external payable; + + /** + * @notice Check if a root has been confirmed as anchored on L1. + * @param root The Merkle root to check for confirmation. + * @return True if the root has been confirmed as anchored on L1, false otherwise. + */ + function isConfirmed(bytes32 root) external view returns (bool); + + /** + * @notice Called by the L1AnchoringGateway to confirm a batch of anchored roots. + * @param expectedRoot The expected Merkle root of the batch being confirmed. + * @param startIndex The starting index of the batch in the queue. + * @param count The number of items in the batch. + * @param l1BlockNumber The L1 block number at which the batch was anchored. + */ + function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) + external; + + // --- Admin Functions --- + + function setFeeOracle(address oracle) external; + function setFeeCollector(address _collector) external; + /** + * @notice Withdraw accumulated fees to the collector. + * @dev Only callable by the feeCollector or Owner. + */ + function withdrawFees(address to, uint256 amount) external; +} diff --git a/contracts/L2/manager/L1AnchoringManager.sol b/contracts/L2/manager/L1AnchoringManager.sol new file mode 100644 index 0000000..1bb735c --- /dev/null +++ b/contracts/L2/manager/L1AnchoringManager.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; +import {IL1AnchoringManager} from "./IL1AnchoringManager.sol"; +import {L1AnchoringManagerStorage} from "./L1AnchoringManagerStorage.sol"; +import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {L1AnchoringManagerTypes} from "./L1AnchoringManagerTypes.sol"; +import {MerkleTree} from "../../core/MerkleTree.sol"; +import {Constants} from "../../Constants.sol"; + +contract L1AnchoringManager is + Initializable, + OwnableUpgradeable, + UUPSUpgradeable, + ReentrancyGuardTransient, + IL1AnchoringManager +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner, IL1FeeOracle feeOracle) public initializer { + __Ownable_init(initialOwner); + + require(address(feeOracle) != address(0), "UTS: Invalid FeeOracle address"); + + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + $.feeOracle = feeOracle; + $.feeCollector = initialOwner; + } + + /// @inheritdoc IL1AnchoringManager + function submitForL1Anchoring(bytes32 root) external payable nonReentrant { + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + + require(address($.feeOracle) != address(0), "UTS: Oracle not set"); + + uint256 requiredFee = $.feeOracle.getFloorFee(); + require(msg.value >= requiredFee, "UTS: Insufficient fee for L1 anchoring"); + + // Call core contract to record the L2 timestamp. + Constants.UTS.attest(root); + + uint256 currentIndex = $.queueIndex++; + $.items[currentIndex] = L1AnchoringManagerTypes.AnchoringItem({root: root, l1BlockNumber: 0}); + $.roots[root] = currentIndex; + + emit L1AnchoringQueued(root, currentIndex, msg.value, block.number, block.timestamp); + } + + /// @inheritdoc IL1AnchoringManager + function isConfirmed(bytes32 root) external view returns (bool) { + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + uint256 index = $.roots[root]; + return index <= $.confirmedIndex; + } + + /// @inheritdoc IL1AnchoringManager + function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) + external + { + // TODO: constraint on caller (e.g. only L1 Scroll Messenger or a designated relayer) + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + + bytes32[] memory leaves = new bytes32[](count); + for (uint256 i = 0; i < count; i++) { + uint256 index = startIndex + i; + L1AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; + leaves[i] = item.root; + item.l1BlockNumber = l1BlockNumber; + } + + bytes32 computedRoot = MerkleTree.computeRoot(leaves); + require(computedRoot == expectedRoot, "UTS: Invalid Merkle Root"); + + $.confirmedIndex = startIndex + count; + } + + // --- Admin Functions --- + + function setFeeOracle(address _oracle) external onlyOwner { + require(address(_oracle) != address(0), "UTS: Invalid Oracle"); + + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + + $.feeOracle = IL1FeeOracle(_oracle); + emit FeeParametersUpdated(_oracle, $.feeCollector); + } + + function setFeeCollector(address _collector) external onlyOwner { + require(_collector != address(0), "UTS: Invalid Collector"); + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + $.feeCollector = _collector; + emit FeeParametersUpdated(address($.feeOracle), _collector); + } + + /// @inheritdoc IL1AnchoringManager + function withdrawFees(address to, uint256 amount) external nonReentrant { + L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); + + // Security: Allow either Owner or the designated Collector to withdraw + require(msg.sender == owner() || msg.sender == $.feeCollector, "UTS: Unauthorized"); + + require(to != address(0), "UTS: Invalid address"); + require(amount > 0 && amount <= address(this).balance, "UTS: Invalid amount"); + + (bool success,) = payable(to).call{value: amount}(""); + require(success, "UTS: Withdrawal failed"); + + emit FeesWithdrawn(to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + receive() external payable {} +} diff --git a/contracts/L2/manager/L1AnchoringManagerStorage.sol b/contracts/L2/manager/L1AnchoringManagerStorage.sol new file mode 100644 index 0000000..91a083f --- /dev/null +++ b/contracts/L2/manager/L1AnchoringManagerStorage.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {L1AnchoringManagerTypes} from "./L1AnchoringManagerTypes.sol"; + +/** + * @dev Library containing the ERC-7201 namespace constant. + * This keeps the implementation detail hidden from the interface. + */ +library L1AnchoringManagerStorage { + string internal constant NAMESPACE = "uts.storage.L1AnchoringManager"; + + /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.L1AnchoringManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 internal constant SLOT = 0x9831cc7956aa6e272a6b3f7bd193bca727880ec1ca574ef61afc1d64fc9e5000; + + /// @custom:storage-location erc7201:uts.storage.L1AnchoringManager + struct Storage { + IL1FeeOracle feeOracle; + address feeCollector; + uint256 queueIndex; + uint256 confirmedIndex; + mapping(uint256 => L1AnchoringManagerTypes.AnchoringItem) items; + mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup + } + + function get() internal pure returns (L1AnchoringManagerStorage.Storage storage $) { + assembly ("memory-safe") { + $.slot := SLOT + } + } +} diff --git a/contracts/L2/manager/L1AnchoringManagerTypes.sol b/contracts/L2/manager/L1AnchoringManagerTypes.sol new file mode 100644 index 0000000..24b2ff9 --- /dev/null +++ b/contracts/L2/manager/L1AnchoringManagerTypes.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +library L1AnchoringManagerTypes { + /// @notice Attestation struct to hold timestamp and block number for each attested root + struct AnchoringItem { + bytes32 root; + uint256 l1BlockNumber; + } +} diff --git a/contracts/L2/oracle/IL1FeeOracle.sol b/contracts/L2/oracle/IL1FeeOracle.sol new file mode 100644 index 0000000..fe39ace --- /dev/null +++ b/contracts/L2/oracle/IL1FeeOracle.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +interface IL1FeeOracle { + /** + * @notice Emitted when fee parameters are updated. + * @param gasPerAttestation Estimated gas consumed on L1 per attestation (in a batch) + * @param discountRatio The discount ratio applied to the baseline fee (scaled by 1e18) + */ + event ParametersUpdated(uint256 gasPerAttestation, uint256 discountRatio); + + /** + * @notice Return the current L1 Base Fee used for calculation. + */ + function getL1BaseFee() external view returns (uint256); + + /** + * @notice Calculate the theoretical baseline fee WITHOUT aggregation discount. + * @dev Useful for debugging or showing users how much they save. + * @return feePerAttestation The fee if the user submitted independently to L1. + */ + function getFeePerAttestation() external view returns (uint256); + + /** + * @notice Calculate the final fee a user must pay for L1 anchoring. + * @return fee The required fee in Wei, applying the discount ratio. + */ + function getFloorFee() external view returns (uint256); + + /** + * @notice Return the current gas consumed on L1 per attestation. + */ + function gasPerAttestation() external view returns (uint256); + + /** + * @notice Return the current discount ratio applied to the baseline fee. + */ + function discountRatio() external view returns (uint256); +} diff --git a/contracts/L2/oracle/L1FeeOracle.sol b/contracts/L2/oracle/L1FeeOracle.sol new file mode 100644 index 0000000..5e4e7f7 --- /dev/null +++ b/contracts/L2/oracle/L1FeeOracle.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; +import {IL1FeeOracle} from "./IL1FeeOracle.sol"; +import {Constants} from "../../Constants.sol"; + +/** + * @title L1FeeOracle + * @dev Calculates the fee required for L1 anchoring based on dynamic L1 gas prices + * and an aggregation discount ratio (< 1.0). + * + * Formula: UserFee = (L1_Base_Fee * GasPerAttestation * DiscountRatio) / 1e18 + * + * Semantic: + * - _gasPerAttestation: The theoretical gas cost if a user submitted independently to L1 (e.g., 50,000). + * - _discountRatio: The fraction (< 1.0) representing the aggregated share + protocol margin (e.g., 0.005e18). + */ +contract L1FeeOracle is IL1FeeOracle, Ownable { + // Estimated gas consumed on L1 per attestation (in a batch) + uint256 private _gasPerAttestation; + // Discount: The ratio (< 1.0) representing the aggregated share + protocol margin. + // Scaled by 1e18. + // Example: 0.005e18 means user pays 0.5% of the baseline gas cost. + // This value implicitly covers the actual batch share + server profit margin. + uint256 private _discountRatio; + + /** + * @param initialOwner The owner of this oracle contract. + * @param initialGasPerAttestation Initial estimate of gas units consumed on L1 per attestation. + * @param initialDiscountRatio Initial discount ratio (scaled by 1e18) to apply to the baseline fee calculation. + */ + constructor(address initialOwner, uint256 initialGasPerAttestation, uint256 initialDiscountRatio) + Ownable(initialOwner) + { + require(initialDiscountRatio > 0, "L1FeeOracle: Ratio must be positive"); + + _gasPerAttestation = initialGasPerAttestation; + _discountRatio = initialDiscountRatio; + } + + /** + * @notice Update parameters. + * @param newGasPerAttestation Baseline gas for a single independent tx. + * @param newDiscountRatio The aggregation ratio (must be <= 1e18). + */ + function setParameters(uint256 newGasPerAttestation, uint256 newDiscountRatio) external onlyOwner { + require(newGasPerAttestation > 0, "L1FeeOracle: Gas must be positive"); + require(newDiscountRatio > 0 && newDiscountRatio <= 1e18, "L1FeeOracle: Ratio must be between 0 and 1.0"); + + _gasPerAttestation = newGasPerAttestation; + _discountRatio = newDiscountRatio; + + emit ParametersUpdated(newGasPerAttestation, newDiscountRatio); + } + + /// @inheritdoc IL1FeeOracle + function getL1BaseFee() external view returns (uint256) { + return Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + } + + /// @inheritdoc IL1FeeOracle + function getFeePerAttestation() external view returns (uint256) { + uint256 l1BaseFee = Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + return (l1BaseFee * _gasPerAttestation) / 1e18; + } + + /// @inheritdoc IL1FeeOracle + function getFloorFee() external view returns (uint256) { + uint256 l1BaseFee = Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + // Calculate the fee with discount applied + return (l1BaseFee * _gasPerAttestation * _discountRatio) / 1e18; + } + + /// @inheritdoc IL1FeeOracle + function gasPerAttestation() external view returns (uint256) { + return _gasPerAttestation; + } + + /// @inheritdoc IL1FeeOracle + function discountRatio() external view returns (uint256) { + return _discountRatio; + } +} diff --git a/contracts/core/IUniversalTimestamps.sol b/contracts/core/IUniversalTimestamps.sol new file mode 100644 index 0000000..6333159 --- /dev/null +++ b/contracts/core/IUniversalTimestamps.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; + +interface IUniversalTimestamps { + /// @notice Emitted when a new Merkle root is attested with its timestamp and block number + event Attested(bytes32 indexed root, address indexed attester, uint256 timestamp, uint256 blockNumber); + + /// @notice Returns the timestamp associated with a given Merkle root. Reuturns 0 if the root has not been attested. + function timestamp(bytes32 root) external view returns (uint256); + + /// @notice Returns the block number associated with a given Merkle root. Returns 0 if the root has not been attested. + function blockNumberOf(bytes32 root) external view returns (uint256); + + /// @notice Returns the full attestation (timestamp and block number) for a given Merkle root. Returns default values if the root has not been attested. + function getAttestation(bytes32 root) external view returns (UniversalTimestampsTypes.Attestation memory); + + /// @notice Attests a new Merkle root with the current timestamp and block number. + function attest(bytes32 root) external; +} diff --git a/contracts/core/MerkleTree.sol b/contracts/core/MerkleTree.sol new file mode 100644 index 0000000..b8795af --- /dev/null +++ b/contracts/core/MerkleTree.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +/** + * @title MerkleTree + * @dev Verifies Merkle Roots generated by the Rust `MerkleTree` implementation. + * + * Logic Alignment with Rust: + * 1. Leaves are padded to the next power of two with zero hashes (bytes32(0)). + * 2. Internal nodes are computed as: keccak256(abi.encodePacked(0x01, left, right)). + * 3. No sorting is performed on leaves; order is preserved as inserted. + */ +library MerkleTree { + /// @dev Prefix byte to distinguish internal nodes from leaves (matches Rust INNER_NODE_PREFIX) + bytes32 private constant INNER_NODE_PREFIX = 0x0100000000000000000000000000000000000000000000000000000000000000; + + /// @dev Empty leaf used for padding to power of two + bytes32 private constant EMPTY_LEAF = 0x0000000000000000000000000000000000000000000000000000000000000000; + + /** + * @notice Computes the Merkle Root from an array of leaves. + * @dev This function replicates the Rust logic: + * 1. Pads leaves to the next power of two. + * 2. Iteratively hashes pairs from bottom to top. + * @param leaves The array of leaf hashes (roots submitted by users). + * @return root The calculated Merkle Root. + */ + function computeRoot(bytes32[] memory leaves) internal pure returns (bytes32 root) { + uint256 count = leaves.length; + require(count > 0, "Merkle: Cannot compute root of empty set"); + + // If only one leaf, return it directly (Rust logic: tree of size 1 has root = leaf) + if (count == 1) { + return leaves[0]; + } + + // Calculate the next power of two for the tree width + uint256 width = _nextPowerOfTwo(count); + + // We need a buffer to hold the current level of hashes. + // Max width needed is 'width'. We can reuse memory or allocate new. + // For clarity and safety in loops, we allocate a dynamic array for the current level. + bytes32[] memory level = new bytes32[](width); + + // 1. Initialize the leaf level (bottom of the tree) + // Copy leaves to the beginning of the buffer + for (uint256 i = 0; i < count; i++) { + level[i] = leaves[i]; + } + // Pad the rest with EMPTY_LEAF (bytes32(0)) + // Note: Solidity arrays initialize to 0 by default, so explicit loop is optional + // but kept here for clarity if EMPTY_LEAF changes or for explicit intent. + // for (uint256 i = count; i < width; i++) { level[i] = EMPTY_LEAF; } + + // 2. Build the tree bottom-up + // Current number of nodes in this level + uint256 currentSize = width; + + while (currentSize > 1) { + uint256 nextSize = (currentSize + 1) / 2; + + for (uint256 i = 0; i < nextSize; i++) { + uint256 leftIndex = 2 * i; + uint256 rightIndex = 2 * i + 1; + + bytes32 left = level[leftIndex]; + // If right index is out of bounds (shouldn't happen with power-of-two padding), treat as empty + bytes32 right = (rightIndex < currentSize) ? level[rightIndex] : EMPTY_LEAF; + + // Hash: keccak256(0x01 || left || right) + level[i] = _hashNode(left, right); + } + + currentSize = nextSize; + } + + return level[0]; + } + + /** + * @notice Verifies if a set of leaves corresponds to a given root. + * @param leaves The array of leaf hashes. + * @param expectedRoot The expected Merkle Root. + * @return valid True if the calculated root matches the expected root. + */ + function verify(bytes32[] memory leaves, bytes32 expectedRoot) internal pure returns (bool valid) { + return computeRoot(leaves) == expectedRoot; + } + + /** + * @dev Internal function to hash two child nodes into a parent node. + * Matches Rust: Digest::update(&mut hasher, [INNER_NODE_PREFIX]); ... + */ + function _hashNode(bytes32 left, bytes32 right) private pure returns (bytes32) { + assembly ("memory-safe") { + let ptr := mload(0x40) // Free memory pointer + + // Store prefix (0x01) at the start of the 32-byte slot + mstore(ptr, INNER_NODE_PREFIX) + // Store left hash immediately after (offset 32) + mstore(add(ptr, 32), left) + // Store right hash immediately after (offset 64) + mstore(add(ptr, 64), right) + + // Hash 96 bytes (3 words): prefix + left + right + let hash := keccak256(ptr, 96) + + mstore(0x40, add(ptr, 96)) // Update free memory pointer (optional in pure view, but good practice) + + // Return result via stack + mstore(0x00, hash) // Store temporarily to return? No, assembly returns via stack variable + // Actually, in inline assembly within a function returning bytes32, we just assign to the variable + // But here we are in a private pure function returning bytes32. + // The standard way is to let solidity handle the return, or use mstore(0, hash) and load. + // Let's stick to high-level return for safety, but use assembly for the keccak. + // Correction: To return from assembly block, we need to assign to a variable defined outside + // or use `result := ...` syntax if defined in `returns`. + + // Re-writing slightly cleaner: + pop(0) // Placeholder to satisfy compiler if needed, but let's just use standard return logic below + } + // Fallback to high-level if assembly feels too risky for specific compiler versions, + // but the assembly above is standard. Let's provide the clean assembly return version. + + // Clean Assembly Implementation for _hashNode + bytes32 result; + assembly ("memory-safe") { + let ptr := mload(0x40) + mstore(ptr, INNER_NODE_PREFIX) + mstore(add(ptr, 32), left) + mstore(add(ptr, 64), right) + result := keccak256(ptr, 96) + // We don't strictly need to update 0x40 in a pure function that doesn't allocate further, + // but it's safe to do so. + mstore(0x40, add(ptr, 96)) + } + return result; + } + + /** + * @dev Calculates the next power of two greater than or equal to n. + */ + function _nextPowerOfTwo(uint256 n) private pure returns (uint256) { + if (n == 0) return 1; + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n |= n >> 32; + n++; + return n; + } +} diff --git a/contracts/UniversalTimestamps.sol b/contracts/core/UniversalTimestamps.sol similarity index 51% rename from contracts/UniversalTimestamps.sol rename to contracts/core/UniversalTimestamps.sol index 21969d1..0416df5 100644 --- a/contracts/UniversalTimestamps.sol +++ b/contracts/core/UniversalTimestamps.sol @@ -6,28 +6,13 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {IUniversalTimestamps} from "./IUniversalTimestamps.sol"; -import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; +import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; +import {UniversalTimestampsStorage} from "./UniversalTimestampsStorage.sol"; /** * @title UniversalTimestamps - * @dev Records and exposes timestamps for attested Merkle roots using ERC-7201 - * namespaced storage (`uts.storage.UniversalTimestamps`) derived via - * {SlotDerivation}, and is implemented as a UUPS upgradeable contract via - * OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable - * base contracts. Storage is kept in a dedicated namespaced struct to remain - * layout-compatible across upgrades, while upgrades are authorized by the - * contract owner through {_authorizeUpgrade}. */ contract UniversalTimestamps is Initializable, OwnableUpgradeable, UUPSUpgradeable, IUniversalTimestamps { - using SlotDerivation for string; - - string private constant _NAMESPACE = "uts.storage.UniversalTimestamps"; - - /// @custom:storage-location erc7201:uts.storage.UniversalTimestamps - struct UniversalTimestampsStorage { - mapping(bytes32 => uint256) timestamps; - } - /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -37,15 +22,16 @@ contract UniversalTimestamps is Initializable, OwnableUpgradeable, UUPSUpgradeab __Ownable_init(initialOwner); } - function _getUniversalTimestampsStorage() private pure returns (UniversalTimestampsStorage storage $) { - bytes32 slot = _NAMESPACE.erc7201Slot(); - assembly ("memory-safe") { - $.slot := slot - } + function timestamp(bytes32 root) external view returns (uint256) { + return UniversalTimestampsStorage.get().timestamps[root].timestamp; } - function timestamp(bytes32 root) external view returns (uint256) { - return _getUniversalTimestampsStorage().timestamps[root]; + function blockNumberOf(bytes32 root) external view returns (uint256) { + return UniversalTimestampsStorage.get().timestamps[root].blockNumber; + } + + function getAttestation(bytes32 root) external view returns (UniversalTimestampsTypes.Attestation memory) { + return UniversalTimestampsStorage.get().timestamps[root]; } /** @@ -55,11 +41,11 @@ contract UniversalTimestamps is Initializable, OwnableUpgradeable, UUPSUpgradeab function attest(bytes32 root) external { require(root != bytes32(0), "UTS: Root cannot be zero"); - UniversalTimestampsStorage storage $ = _getUniversalTimestampsStorage(); - if ($.timestamps[root] == 0) { - $.timestamps[root] = block.timestamp; - emit Attested(root, msg.sender, block.timestamp); - } + UniversalTimestampsStorage.Storage storage $ = UniversalTimestampsStorage.get(); + require($.timestamps[root].timestamp == 0, "UTS: Root already attested"); + $.timestamps[root] = + UniversalTimestampsTypes.Attestation({timestamp: block.timestamp, blockNumber: block.number}); + emit Attested(root, msg.sender, block.timestamp, block.number); } /** diff --git a/contracts/core/UniversalTimestampsStorage.sol b/contracts/core/UniversalTimestampsStorage.sol new file mode 100644 index 0000000..2c00140 --- /dev/null +++ b/contracts/core/UniversalTimestampsStorage.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; + +/** + * @dev Library containing the ERC-7201 namespace constant. + * This keeps the implementation detail hidden from the interface. + */ +library UniversalTimestampsStorage { + string internal constant NAMESPACE = "uts.storage.UniversalTimestamps"; + + /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.UniversalTimestamps")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 internal constant SLOT = 0x500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b03468100; + + /// @custom:storage-location erc7201:uts.storage.UniversalTimestamps + struct Storage { + mapping(bytes32 => UniversalTimestampsTypes.Attestation) timestamps; + } + + function get() internal pure returns (UniversalTimestampsStorage.Storage storage $) { + assembly ("memory-safe") { + $.slot := SLOT + } + } +} diff --git a/contracts/core/UniversalTimestampsTypes.sol b/contracts/core/UniversalTimestampsTypes.sol new file mode 100644 index 0000000..7ab0be3 --- /dev/null +++ b/contracts/core/UniversalTimestampsTypes.sol @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +library UniversalTimestampsTypes { + /// @notice Attestation struct to hold timestamp and block number for each attested root + struct Attestation { + uint256 timestamp; + uint256 blockNumber; + } +} diff --git a/foundry.lock b/foundry.lock index 1f56371..dcf8f54 100644 --- a/foundry.lock +++ b/foundry.lock @@ -16,5 +16,11 @@ "name": "v0.4.0", "rev": "cbce1e00305e943aa1661d43f41e5ac72c662b07" } + }, + "lib/scroll-contracts": { + "tag": { + "name": "v4.0.0", + "rev": "42de954bee237cfa478a5b443ac0aeb900aca5ad" + } } } \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index eed2c8e..02b37fd 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,6 +6,7 @@ ffi = true fs_permissions = [{ access = "read", path = "target/foundry" }] ignored_error_codes = [ 2018, # Function state mutability can be restricted to pure + 2394, # ransient storage as defined by EIP-1153 can break the composability of smart contracts ] libs = ["lib"] out = "target/foundry" @@ -24,3 +25,7 @@ cbor_metadata = false # If using optimizer, keep settings consistent optimizer = true optimizer_runs = 200 + +[rpc_endpoints] +l1_sepolia = "https://0xrpc.io/sep/" +l2_scroll_sepolia = "https://sepolia-rpc.scroll.io/" diff --git a/lib/scroll-contracts b/lib/scroll-contracts new file mode 160000 index 0000000..42de954 --- /dev/null +++ b/lib/scroll-contracts @@ -0,0 +1 @@ +Subproject commit 42de954bee237cfa478a5b443ac0aeb900aca5ad diff --git a/remappings.txt b/remappings.txt index 9b42f71..0be9806 100644 --- a/remappings.txt +++ b/remappings.txt @@ -6,3 +6,4 @@ halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/ openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/ openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +scroll-contracts/=lib/scroll-contracts/src/ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol new file mode 100644 index 0000000..9592499 --- /dev/null +++ b/script/Deploy.s.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {L1AnchoringManager} from "../contracts/L2/manager/L1AnchoringManager.sol"; +import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; +import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; +import {L1AnchoringGateway} from "../contracts/L1/L1AnchoringGateway.sol"; +import {Constants} from "../contracts/Constants.sol"; + +bytes32 constant SALT = keccak256("universal-timestamps"); + +// Note: all address here are sepolia/scroll-sepolia addresses. + +contract DeployTimestampCreate2 is Script { + function run() public { + address owner = vm.envAddress("OWNER_ADDRESS"); + + vm.startBroadcast(); + UniversalTimestamps implementation = new UniversalTimestamps{salt: SALT}(); + // Implementation deployed at: 0x13889107F758b4c220E6422ef0f00965D5D2b178 + console.log("Implementation deployed at:", address(implementation)); + + bytes memory initData = abi.encodeCall(UniversalTimestamps.initialize, (owner)); + + ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); + vm.stopBroadcast(); + + // Proxy deployed at: 0xdf939C24d9c075862837e3c9EC0cc1feD6376D59 + console.log("Proxy deployed at:", address(proxy)); + } +} + +contract DeployFeeOracle is Script { + function run() public { + address owner = vm.envAddress("OWNER_ADDRESS"); + + vm.startBroadcast(); + L1FeeOracle implementation = new L1FeeOracle( + owner, + 100_000, // initialGasPerAttestation + 0.5e18 // initialDiscountRatio (50%) + ); + vm.stopBroadcast(); + + // Proxy deployed at: 0x36E62Da7f040fC19B857541474D2c5dc114f12af + console.log("FeeOracle deployed at", address(implementation)); + } +} + +contract DeployL1Manager is Script { + function run() public { + address owner = vm.envAddress("OWNER_ADDRESS"); + address l1Messenger = vm.envAddress("L1_MESSENGER_ADDRESS"); + + vm.startBroadcast(); + L1AnchoringManager implementation = new L1AnchoringManager(); + // Implementation deployed at: 0xc496516540367Aa3E5c209E36d68AD326566943B + console.log("Implementation deployed at:", address(implementation)); + + bytes memory initData = abi.encodeCall(L1AnchoringManager.initialize, (owner, IL1FeeOracle(l1Messenger))); + + ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); + vm.stopBroadcast(); + + // Proxy deployed at: 0x5f44B75D6A0D26533EAECaAcf81eDc9A947a39e9 + console.log("Proxy deployed at:", address(proxy)); + } +} + +contract DeployL1Gateway is Script { + function run() public { + address owner = vm.envAddress("OWNER_ADDRESS"); + address l1Messenger = vm.envAddress("L1_MESSENGER_ADDRESS"); + address l1AnchoringManagerL2 = vm.envAddress("L1_ANCHORING_MANAGER_L2_ADDRESS"); + + vm.startBroadcast(); + L1AnchoringGateway implementation = new L1AnchoringGateway(); + // Implementation deployed at: 0x8b28f0D465EC9780459E08827E662e35F24D5197 + console.log("Implementation deployed at:", address(implementation)); + + bytes memory initData = + abi.encodeCall(L1AnchoringGateway.initialize, (owner, l1Messenger, l1AnchoringManagerL2)); + + ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); + vm.stopBroadcast(); + + // Proxy deployed at: 0x91FA317cf93AAefc044B59df5dd463F513Cea516 + console.log("Proxy deployed at:", address(proxy)); + } +} diff --git a/script/DeployCreate2.s.sol b/script/DeployCreate2.s.sol deleted file mode 100644 index 07a73ab..0000000 --- a/script/DeployCreate2.s.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {UniversalTimestamps} from "../contracts/UniversalTimestamps.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; - -interface ICreateX { - function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address); - - function computeCreate2Address(bytes32 salt, bytes32 initCodeHash) external view returns (address); -} - -contract DeployCreate2 is Script { - // CreateX is deployed at the same address on all supported chains - ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); - bytes32 constant SALT = keccak256("universal-timestamps"); - - function run() public { - address owner = vm.envAddress("OWNER_ADDRESS"); - - vm.startBroadcast(); - UniversalTimestamps implementation = new UniversalTimestamps{salt: SALT}(); - // Implementation deployed at: 0x2D806e4ae1c3FDCfecb019B192a53371CAC889A7 - console.log("Implementation deployed at:", address(implementation)); - - bytes memory initData = abi.encodeCall(UniversalTimestamps.initialize, (owner)); - - ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); - vm.stopBroadcast(); - - // Proxy deployed at: 0xceB7a9E77bd00D0391349B9bC989167cAB5e35e7 - console.log("Proxy deployed at:", address(proxy)); - } -} diff --git a/script/E2E.s.sol b/script/E2E.s.sol new file mode 100644 index 0000000..84e4b5c --- /dev/null +++ b/script/E2E.s.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {IL1AnchoringManager} from "../contracts/L2/manager/IL1AnchoringManager.sol"; +import {IL1AnchoringGateway} from "../contracts/L1/IL1AnchoringGateway.sol"; +import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; + +contract SubmitAnchoring is Script { + function run() public { + address l1AnchoringManager = vm.envAddress("L1_ANCHORING_MANAGER"); + address feeOracle = vm.envAddress("FEE_ORACLE"); + + IL1AnchoringManager manager = IL1AnchoringManager(l1AnchoringManager); + IL1FeeOracle oracle = IL1FeeOracle(feeOracle); + + bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + uint256 fee = oracle.getFloorFee(); + console.log("Current floor fee:", fee); + + vm.startBroadcast(); + manager.submitForL1Anchoring{value: fee}(root); + vm.stopBroadcast(); + } +} + +contract ConfirmAnchoring is Script { + function run() public { + address l1AnchoringManager = vm.envAddress("L1_ANCHORING_GATEWAY"); + + IL1AnchoringGateway gateway = IL1AnchoringGateway(l1AnchoringManager); + + bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + + vm.startBroadcast(); + gateway.submitBatch{value: 0.1 ether}(root, 0, 1); + vm.stopBroadcast(); + } +} From cbbb9249c465b0933e0630140b73f8538ec1c443 Mon Sep 17 00:00:00 2001 From: lightsing Date: Fri, 27 Feb 2026 23:36:40 +0800 Subject: [PATCH 02/14] fix --- .env.test | 12 ++ contract-tests/L1AnchoringManager.t.sol | 106 +++++++++++ contract-tests/Storage.t.sol | 6 +- contracts/Constants.sol | 13 -- contracts/L1/IL1AnchoringGateway.sol | 7 +- contracts/L1/L1AnchoringGateway.sol | 36 ++-- contracts/L1/L1AnchoringGatewayStorage.sol | 11 +- ...ingManager.sol => IL2AnchoringManager.sol} | 26 ++- contracts/L2/manager/L1AnchoringManager.sol | 122 ------------- .../L2/manager/L1AnchoringManagerStorage.sol | 32 ---- contracts/L2/manager/L2AnchoringManager.sol | 164 ++++++++++++++++++ .../L2/manager/L2AnchoringManagerStorage.sol | 44 +++++ ...rTypes.sol => L2AnchoringManagerTypes.sol} | 4 +- contracts/L2/oracle/IL1FeeOracle.sol | 2 +- contracts/L2/oracle/L1FeeOracle.sol | 12 +- contracts/core/IUniversalTimestamps.sol | 2 +- contracts/core/MerkleTree.sol | 2 +- contracts/core/UniversalTimestamps.sol | 2 +- contracts/core/UniversalTimestampsStorage.sol | 2 +- contracts/core/UniversalTimestampsTypes.sol | 2 +- crates/contracts/abi/ERC1967Proxy.json | 2 +- .../contracts/abi/IUniversalTimestamps.json | 2 +- crates/contracts/abi/UniversalTimestamps.json | 2 +- foundry.toml | 4 +- script/Deploy.s.sol | 88 +++++++--- script/E2E.s.sol | 39 ++++- 26 files changed, 506 insertions(+), 238 deletions(-) create mode 100644 .env.test create mode 100644 contract-tests/L1AnchoringManager.t.sol delete mode 100644 contracts/Constants.sol rename contracts/L2/manager/{IL1AnchoringManager.sol => IL2AnchoringManager.sol} (65%) delete mode 100644 contracts/L2/manager/L1AnchoringManager.sol delete mode 100644 contracts/L2/manager/L1AnchoringManagerStorage.sol create mode 100644 contracts/L2/manager/L2AnchoringManager.sol create mode 100644 contracts/L2/manager/L2AnchoringManagerStorage.sol rename contracts/L2/manager/{L1AnchoringManagerTypes.sol => L2AnchoringManagerTypes.sol} (78%) diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..4e1d16c --- /dev/null +++ b/.env.test @@ -0,0 +1,12 @@ +L1_MESSENGER=0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A +L2_MESSENGER=0xBa50f5340FB9F3Bd074bD638c9BE13eCB36E603d + +UTS_IMPL=0x9E4D38DfC4484c2E8744c0e32563266aefb3F93C +UTS=0x0D57Edf086949B6Be411D066E448E12eF5D79baE +FEE_ORACLE=0x7f2A06e64Ef079Af08D9083144E0C917D6dFce0d + +ANCHORING_MANAGER_IMPL=0xC0E20e0cd2724A480a8454a544e47C0cbBcdaB5e +ANCHORING_MANAGER=0x30dBB185D59CF8517cAFfE076D2Ea7B83933808D + +ANCHORING_GATEWAY_IMPL=0x97EE9CD89F22E9dD92Bf7CE79B13D6690368fBEe +ANCHORING_GATEWAY=0xBa770953AeF1ED159289Bc587Ac3A60e9a12b2E7 \ No newline at end of file diff --git a/contract-tests/L1AnchoringManager.t.sol b/contract-tests/L1AnchoringManager.t.sol new file mode 100644 index 0000000..b912db6 --- /dev/null +++ b/contract-tests/L1AnchoringManager.t.sol @@ -0,0 +1,106 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +import {Test, console} from "forge-std/Test.sol"; +import {L2AnchoringManager} from "../contracts/L2/manager/L2AnchoringManager.sol"; +import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; +import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; +import {IUniversalTimestamps} from "../contracts/core/IUniversalTimestamps.sol"; +import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; +import {AddressAliasHelper} from "scroll-contracts/libraries/common/AddressAliasHelper.sol"; + +contract MockL1FeeOracle is IL1FeeOracle { + function getL1BaseFee() external view returns (uint256) { + return 0.05 gwei; + } + + function getFeePerAttestation() external view returns (uint256) { + return 0.05 gwei * 51_000; + } + + function getFloorFee() external view returns (uint256) { + return (0.05 gwei * 51_000 * 0.5e18) / 1e18; + } + + function gasPerAttestation() external view returns (uint256) { + return 51_000; + } + + function discountRatio() external view returns (uint256) { + return 0.5e18; // 50% discount + } +} + +contract MockL2ScrollMessenger is IL2ScrollMessenger { + function relayMessage(address from, address to, uint256 value, uint256 nonce, bytes calldata message) external {} + + function xDomainMessageSender() external view returns (address) { + return address(0x456); // Mock L1 sender address + } + + function sendMessage(address target, uint256 value, bytes calldata message, uint256 gasLimit) external payable {} + + function sendMessage(address target, uint256 value, bytes calldata message, uint256 gasLimit, address refundAddress) + external + payable {} +} + +/** + * @title L2AnchoringManagerTest + * @dev Tests to verify the functionality of the L2AnchoringManager contract. + */ +contract L2AnchoringManagerTest is Test { + IUniversalTimestamps uts; + IL1FeeOracle feeOracle; + IL2AnchoringManager manager; + IL2ScrollMessenger l2Messenger; + + address constant L1_MESSENGER = address(0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A); + address constant L1_GATEWAY = address(0x456); + + function setUp() public { + l2Messenger = new MockL2ScrollMessenger(); + UniversalTimestamps utsImpl = new UniversalTimestamps(); + ERC1967Proxy utsProxy = + new ERC1967Proxy(address(utsImpl), abi.encodeCall(UniversalTimestamps.initialize, (address(this)))); + uts = IUniversalTimestamps(address(utsProxy)); + + feeOracle = new MockL1FeeOracle(); + + L2AnchoringManager impl = new L2AnchoringManager(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall( + L2AnchoringManager.initialize, + (address(this), address(uts), address(feeOracle), L1_MESSENGER, address(l2Messenger)) + ) + ); + manager = IL2AnchoringManager(address(proxy)); + manager.setL1Gateway(L1_GATEWAY); + } + + function test() public { + bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + uint256 fee = feeOracle.getFloorFee(); + console.log("Current floor fee:", fee); + + // Simulate submitting a root for L1 anchoring + vm.prank(address(1)); // Simulate a call from an external address + vm.deal(address(1), 100 ether); // Fund the address with some ether to pay for the fee + vm.expectEmit(true, true, true, true); + emit IL2AnchoringManager.L1AnchoringQueued(root, 0, fee, block.number, block.timestamp); + manager.submitForL1Anchoring{value: fee}(root); + + // Verify that the item was added to the queue + bool confirmed = manager.isConfirmed(root); + assertFalse(confirmed, "Root should not be confirmed immediately after submission"); + + // Simulate a call from bridge to confirm the anchoring + vm.prank(AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER)); // Simulate a call from the L1 messenger + vm.expectEmit(true, true, true, true); + emit IL2AnchoringManager.L1AnchoringBatchConfirmed(root, 0, 1, block.number, block.number, block.timestamp); + manager.confirmL1AnchoringBatch(root, 0, 1, block.number); + } +} diff --git a/contract-tests/Storage.t.sol b/contract-tests/Storage.t.sol index 4cce293..556e663 100644 --- a/contract-tests/Storage.t.sol +++ b/contract-tests/Storage.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Test, console} from "forge-std/Test.sol"; import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; import {UniversalTimestampsStorage} from "../contracts/core/UniversalTimestampsStorage.sol"; import {L1AnchoringGatewayStorage} from "../contracts/L1/L1AnchoringGatewayStorage.sol"; -import {L1AnchoringManagerStorage} from "../contracts/L2/manager/L1AnchoringManagerStorage.sol"; +import {L2AnchoringManagerStorage} from "../contracts/L2/manager/L2AnchoringManagerStorage.sol"; /** * @title UniversalTimestampsStorageTest @@ -19,7 +19,7 @@ contract StorageSlotTest is Test { test(L1AnchoringGatewayStorage.SLOT, L1AnchoringGatewayStorage.NAMESPACE); - test(L1AnchoringManagerStorage.SLOT, L1AnchoringManagerStorage.NAMESPACE); + test(L2AnchoringManagerStorage.SLOT, L2AnchoringManagerStorage.NAMESPACE); } function test(bytes32 hardcodedSlot, string memory namespace) internal pure virtual { diff --git a/contracts/Constants.sol b/contracts/Constants.sol deleted file mode 100644 index dc707ad..0000000 --- a/contracts/Constants.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -import {IUniversalTimestamps} from "./core/IUniversalTimestamps.sol"; -import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; - -library Constants { - IUniversalTimestamps public constant UTS = IUniversalTimestamps(0xdf939C24d9c075862837e3c9EC0cc1feD6376D59); - - IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = - IL1GasPriceOracle(0x5300000000000000000000000000000000000002); -} diff --git a/contracts/L1/IL1AnchoringGateway.sol b/contracts/L1/IL1AnchoringGateway.sol index 9e12eb6..d461187 100644 --- a/contracts/L1/IL1AnchoringGateway.sol +++ b/contracts/L1/IL1AnchoringGateway.sol @@ -1,8 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; interface IL1AnchoringGateway { + /// @notice Emitted when a new batch of Merkle roots is submitted to L1 for anchoring. + event BatchSubmitted( + bytes32 indexed merkleRoot, uint256 indexed startIndex, uint256 count, address indexed submitter + ); + /** * @notice Submit a SINGLE aggregated Merkle Root to L1 and trigger L2 verification. * @param merkleRoot The root of the Merkle Tree containing all roots in this batch. diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index b80a02e..d712756 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -8,9 +8,9 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; import {L1AnchoringGatewayStorage} from "./L1AnchoringGatewayStorage.sol"; import {IL1AnchoringGateway} from "./IL1AnchoringGateway.sol"; -import {Constants} from "../Constants.sol"; import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; -import {IL1AnchoringManager} from "../L2/manager/IL1AnchoringManager.sol"; +import {IL2AnchoringManager} from "../L2/manager/IL2AnchoringManager.sol"; +import {IUniversalTimestamps} from "../core/IUniversalTimestamps.sol"; contract L1AnchoringGateway is Initializable, @@ -24,15 +24,20 @@ contract L1AnchoringGateway is _disableInitializers(); } - function initialize(address initialOwner, address l1Messenger, address l1AnchoringManagerL2) public initializer { + function initialize(address initialOwner, address uts, address l1Messenger, address l2AnchoringManager) + public + initializer + { __Ownable_init(initialOwner); + require(uts != address(0), "UTS: Invalid UniversalTimestamps address"); require(l1Messenger != address(0), "UTS: Invalid L1ScrollMessenger address"); - require(l1AnchoringManagerL2 != address(0), "UTS: Invalid L1AnchoringManagerL2 address"); + require(l2AnchoringManager != address(0), "UTS: Invalid L2AnchoringManagerL2 address"); L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + $.uts = IUniversalTimestamps(uts); $.l1Messenger = IL1ScrollMessenger(l1Messenger); - $.l1AnchoringManagerL2 = IL1AnchoringManager(l1AnchoringManagerL2); + $.l2AnchoringManager = IL2AnchoringManager(l2AnchoringManager); } /// @inheritdoc IL1AnchoringGateway @@ -40,20 +45,29 @@ contract L1AnchoringGateway is L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); require(address($.l1Messenger) != address(0), "UTS: L1 Scroll Messenger not set"); - require(address($.l1AnchoringManagerL2) != address(0), "UTS: L1 Anchoring Manager L2 not set"); + require(address($.l2AnchoringManager) != address(0), "UTS: L2 Anchoring Manager not set"); - Constants.UTS.attest(merkleRoot); + uint256 attestedBlockNumber; + try $.uts.attest(merkleRoot) { + attestedBlockNumber = block.number; + } catch { + attestedBlockNumber = $.uts.blockNumberOf(merkleRoot); + require(attestedBlockNumber != 0, "UTS: Merkle root not attested on L1"); + } - bytes memory message = - abi.encodeCall(IL1AnchoringManager.confirmL1AnchoringBatch, (merkleRoot, startIndex, count, block.number)); + bytes memory message = abi.encodeCall( + IL2AnchoringManager.confirmL1AnchoringBatch, (merkleRoot, startIndex, count, attestedBlockNumber) + ); $.l1Messenger.sendMessage{value: msg.value}( - address($.l1AnchoringManagerL2), + address($.l2AnchoringManager), 0, message, 1_000_000, // TODO: Estimate proper gas limit msg.sender // refund the caller for the gas cost of L2 execution ); + + emit BatchSubmitted(merkleRoot, startIndex, count, msg.sender); } function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} diff --git a/contracts/L1/L1AnchoringGatewayStorage.sol b/contracts/L1/L1AnchoringGatewayStorage.sol index 9e587ca..6e0a955 100644 --- a/contracts/L1/L1AnchoringGatewayStorage.sol +++ b/contracts/L1/L1AnchoringGatewayStorage.sol @@ -1,8 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; -import {IL1AnchoringManager} from "../L2/manager/IL1AnchoringManager.sol"; +import {IL2AnchoringManager} from "../L2/manager/IL2AnchoringManager.sol"; +import {IUniversalTimestamps} from "../core/IUniversalTimestamps.sol"; /** * @dev Library containing the ERC-7201 namespace constant. @@ -16,10 +17,12 @@ library L1AnchoringGatewayStorage { /// @custom:storage-location erc7201:uts.storage.L1AnchoringGateway struct Storage { + /// @notice Reference to the Universal Timestamps contract for recording anchoring events + IUniversalTimestamps uts; /// @notice Reference to the L1 Scroll Messenger contract IL1ScrollMessenger l1Messenger; - /// @notice Reference to the L1 Anchoring Manager contract on L2 (for address lookup) - IL1AnchoringManager l1AnchoringManagerL2; + /// @notice Reference to the L2 Anchoring Manager contract + IL2AnchoringManager l2AnchoringManager; } function get() internal pure returns (L1AnchoringGatewayStorage.Storage storage $) { diff --git a/contracts/L2/manager/IL1AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol similarity index 65% rename from contracts/L2/manager/IL1AnchoringManager.sol rename to contracts/L2/manager/IL2AnchoringManager.sol index 5c2b7b9..be22f49 100644 --- a/contracts/L2/manager/IL1AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -1,17 +1,33 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; -interface IL1AnchoringManager { +interface IL2AnchoringManager { /// @notice Emitted when a user pays to have their root anchored to L1. event L1AnchoringQueued( bytes32 indexed root, uint256 queueIndex, uint256 fee, uint256 blockNumber, uint256 timestamp ); + /// @notice Emitted when a batch of roots is confirmed as anchored on L1. + event L1AnchoringBatchConfirmed( + bytes32 indexed aggregateRoot, + uint256 indexed startIndex, + uint256 count, + uint256 l1BlockNumber, + uint256 l2BlockNumber, + uint256 timestamp + ); + /// @notice Emitted when fee parameters are updated. event FeeParametersUpdated(address indexed feeOracle, address indexed feeCollector); /// @notice Emitted when fees are withdrawn by the fee collector. event FeesWithdrawn(address indexed to, uint256 amount); + /// @notice Emitted when the L1 Gateway address is updated. + event L1GatewayUpdated(address indexed l1Gateway); + /// @notice Emitted when the L1 Messenger address is updated. + event L1MessengerUpdated(address indexed l1Messenger); + /// @notice Emitted when the L2 Messenger address is updated. + event L2MessengerUpdated(address indexed l2Messenger); /** * @notice Submit a root for L2 timestamping + L1 anchoring. @@ -39,7 +55,11 @@ interface IL1AnchoringManager { // --- Admin Functions --- function setFeeOracle(address oracle) external; - function setFeeCollector(address _collector) external; + function setFeeCollector(address collector) external; + function setL1Gateway(address l1Gateway) external; + function setL1Messenger(address l1Messenger) external; + function setL2Messenger(address l2Messenger) external; + /** * @notice Withdraw accumulated fees to the collector. * @dev Only callable by the feeCollector or Owner. diff --git a/contracts/L2/manager/L1AnchoringManager.sol b/contracts/L2/manager/L1AnchoringManager.sol deleted file mode 100644 index 1bb735c..0000000 --- a/contracts/L2/manager/L1AnchoringManager.sol +++ /dev/null @@ -1,122 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; - -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; -import {IL1AnchoringManager} from "./IL1AnchoringManager.sol"; -import {L1AnchoringManagerStorage} from "./L1AnchoringManagerStorage.sol"; -import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; -import {L1AnchoringManagerTypes} from "./L1AnchoringManagerTypes.sol"; -import {MerkleTree} from "../../core/MerkleTree.sol"; -import {Constants} from "../../Constants.sol"; - -contract L1AnchoringManager is - Initializable, - OwnableUpgradeable, - UUPSUpgradeable, - ReentrancyGuardTransient, - IL1AnchoringManager -{ - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - function initialize(address initialOwner, IL1FeeOracle feeOracle) public initializer { - __Ownable_init(initialOwner); - - require(address(feeOracle) != address(0), "UTS: Invalid FeeOracle address"); - - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - $.feeOracle = feeOracle; - $.feeCollector = initialOwner; - } - - /// @inheritdoc IL1AnchoringManager - function submitForL1Anchoring(bytes32 root) external payable nonReentrant { - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - - require(address($.feeOracle) != address(0), "UTS: Oracle not set"); - - uint256 requiredFee = $.feeOracle.getFloorFee(); - require(msg.value >= requiredFee, "UTS: Insufficient fee for L1 anchoring"); - - // Call core contract to record the L2 timestamp. - Constants.UTS.attest(root); - - uint256 currentIndex = $.queueIndex++; - $.items[currentIndex] = L1AnchoringManagerTypes.AnchoringItem({root: root, l1BlockNumber: 0}); - $.roots[root] = currentIndex; - - emit L1AnchoringQueued(root, currentIndex, msg.value, block.number, block.timestamp); - } - - /// @inheritdoc IL1AnchoringManager - function isConfirmed(bytes32 root) external view returns (bool) { - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - uint256 index = $.roots[root]; - return index <= $.confirmedIndex; - } - - /// @inheritdoc IL1AnchoringManager - function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) - external - { - // TODO: constraint on caller (e.g. only L1 Scroll Messenger or a designated relayer) - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - - bytes32[] memory leaves = new bytes32[](count); - for (uint256 i = 0; i < count; i++) { - uint256 index = startIndex + i; - L1AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; - leaves[i] = item.root; - item.l1BlockNumber = l1BlockNumber; - } - - bytes32 computedRoot = MerkleTree.computeRoot(leaves); - require(computedRoot == expectedRoot, "UTS: Invalid Merkle Root"); - - $.confirmedIndex = startIndex + count; - } - - // --- Admin Functions --- - - function setFeeOracle(address _oracle) external onlyOwner { - require(address(_oracle) != address(0), "UTS: Invalid Oracle"); - - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - - $.feeOracle = IL1FeeOracle(_oracle); - emit FeeParametersUpdated(_oracle, $.feeCollector); - } - - function setFeeCollector(address _collector) external onlyOwner { - require(_collector != address(0), "UTS: Invalid Collector"); - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - $.feeCollector = _collector; - emit FeeParametersUpdated(address($.feeOracle), _collector); - } - - /// @inheritdoc IL1AnchoringManager - function withdrawFees(address to, uint256 amount) external nonReentrant { - L1AnchoringManagerStorage.Storage storage $ = L1AnchoringManagerStorage.get(); - - // Security: Allow either Owner or the designated Collector to withdraw - require(msg.sender == owner() || msg.sender == $.feeCollector, "UTS: Unauthorized"); - - require(to != address(0), "UTS: Invalid address"); - require(amount > 0 && amount <= address(this).balance, "UTS: Invalid amount"); - - (bool success,) = payable(to).call{value: amount}(""); - require(success, "UTS: Withdrawal failed"); - - emit FeesWithdrawn(to, amount); - } - - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - - receive() external payable {} -} diff --git a/contracts/L2/manager/L1AnchoringManagerStorage.sol b/contracts/L2/manager/L1AnchoringManagerStorage.sol deleted file mode 100644 index 91a083f..0000000 --- a/contracts/L2/manager/L1AnchoringManagerStorage.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; -import {L1AnchoringManagerTypes} from "./L1AnchoringManagerTypes.sol"; - -/** - * @dev Library containing the ERC-7201 namespace constant. - * This keeps the implementation detail hidden from the interface. - */ -library L1AnchoringManagerStorage { - string internal constant NAMESPACE = "uts.storage.L1AnchoringManager"; - - /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.L1AnchoringManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 internal constant SLOT = 0x9831cc7956aa6e272a6b3f7bd193bca727880ec1ca574ef61afc1d64fc9e5000; - - /// @custom:storage-location erc7201:uts.storage.L1AnchoringManager - struct Storage { - IL1FeeOracle feeOracle; - address feeCollector; - uint256 queueIndex; - uint256 confirmedIndex; - mapping(uint256 => L1AnchoringManagerTypes.AnchoringItem) items; - mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup - } - - function get() internal pure returns (L1AnchoringManagerStorage.Storage storage $) { - assembly ("memory-safe") { - $.slot := SLOT - } - } -} diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol new file mode 100644 index 0000000..03307f0 --- /dev/null +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.29; + +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; +import {IL2AnchoringManager} from "./IL2AnchoringManager.sol"; +import {L2AnchoringManagerStorage} from "./L2AnchoringManagerStorage.sol"; +import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {L2AnchoringManagerTypes} from "./L2AnchoringManagerTypes.sol"; +import {MerkleTree} from "../../core/MerkleTree.sol"; +import {IUniversalTimestamps} from "../../core/IUniversalTimestamps.sol"; +import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; +import {AddressAliasHelper} from "scroll-contracts/libraries/common/AddressAliasHelper.sol"; +import {ScrollConstants} from "scroll-contracts/libraries/constants/ScrollConstants.sol"; + +contract L2AnchoringManager is + Initializable, + OwnableUpgradeable, + UUPSUpgradeable, + ReentrancyGuardTransient, + IL2AnchoringManager +{ + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(address initialOwner, address uts, address feeOracle, address l1Messenger, address l2Messenger) + public + initializer + { + __Ownable_init(initialOwner); + + require(feeOracle != address(0), "UTS: Invalid FeeOracle address"); + + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.uts = IUniversalTimestamps(uts); + $.feeOracle = IL1FeeOracle(feeOracle); + $.l1Messenger = l1Messenger; + $.l2Messenger = IL2ScrollMessenger(l2Messenger); + $.feeCollector = initialOwner; + } + + /// @inheritdoc IL2AnchoringManager + function submitForL1Anchoring(bytes32 root) external payable nonReentrant { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + + require(address($.feeOracle) != address(0), "UTS: Oracle not set"); + + uint256 requiredFee = $.feeOracle.getFloorFee(); + require(msg.value >= requiredFee, "UTS: Insufficient fee for L1 anchoring"); + + // Call core contract to record the L2 timestamp. + $.uts.attest(root); + + uint256 currentIndex = $.queueIndex++; + $.items[currentIndex] = L2AnchoringManagerTypes.AnchoringItem({root: root, l1BlockNumber: 0}); + $.roots[root] = currentIndex; + + emit L1AnchoringQueued(root, currentIndex, msg.value, block.number, block.timestamp); + } + + /// @inheritdoc IL2AnchoringManager + function isConfirmed(bytes32 root) external view returns (bool) { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + uint256 index = $.roots[root]; + return index < $.confirmedIndex; + } + + /// @inheritdoc IL2AnchoringManager + function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) + external + { + require(count > 0, "UTS: Count must be greater than zero"); + + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + require(address($.l1Gateway) != address(0), "UTS: L1 Gateway not set"); + + require(msg.sender == address($.l2Messenger), "UTS: Unauthorized caller"); + address l1Sender = $.l2Messenger.xDomainMessageSender(); + require(l1Sender == $.l1Gateway, "UTS: Invalid L1 sender"); + + bytes32[] memory leaves = new bytes32[](count); + for (uint256 i = 0; i < count; i++) { + uint256 index = startIndex + i; + L2AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; + leaves[i] = item.root; + item.l1BlockNumber = l1BlockNumber; + } + + bytes32 computedRoot = MerkleTree.computeRoot(leaves); + require(computedRoot == expectedRoot, "UTS: Invalid Merkle Root"); + + emit L1AnchoringBatchConfirmed(computedRoot, startIndex, count, l1BlockNumber, block.number, block.timestamp); + + $.confirmedIndex = startIndex + count; + } + + // --- Admin Functions --- + + function setFeeOracle(address _oracle) external onlyOwner { + require(address(_oracle) != address(0), "UTS: Invalid Oracle"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.feeOracle = IL1FeeOracle(_oracle); + emit FeeParametersUpdated(_oracle, $.feeCollector); + } + + function setFeeCollector(address collector) external onlyOwner { + require(collector != address(0), "UTS: Invalid Collector"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.feeCollector = collector; + emit FeeParametersUpdated(address($.feeOracle), collector); + } + + function setL1Gateway(address l1Gateway) external onlyOwner { + require(l1Gateway != address(0), "UTS: Invalid L1 Gateway address"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.l1Gateway = l1Gateway; + emit L1GatewayUpdated(l1Gateway); + } + + function setL1Messenger(address l1Messenger) external onlyOwner { + require(l1Messenger != address(0), "UTS: Invalid L1 Messenger address"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.l1Messenger = l1Messenger; + emit L1MessengerUpdated(l1Messenger); + } + + function setL2Messenger(address l2Messenger) external onlyOwner { + require(l2Messenger != address(0), "UTS: Invalid L2 Messenger address"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + IL2ScrollMessenger messenger = IL2ScrollMessenger(l2Messenger); + // Sanity check to ensure it's a valid messenger + require( + messenger.xDomainMessageSender() == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, + "UTS: Invalid L2 Messenger" + ); + $.l2Messenger = messenger; + emit L2MessengerUpdated(l2Messenger); + } + + /// @inheritdoc IL2AnchoringManager + function withdrawFees(address to, uint256 amount) external nonReentrant { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + + // Security: Allow either Owner or the designated Collector to withdraw + require(msg.sender == owner() || msg.sender == $.feeCollector, "UTS: Unauthorized"); + + require(to != address(0), "UTS: Invalid address"); + require(amount > 0 && amount <= address(this).balance, "UTS: Invalid amount"); + + (bool success,) = payable(to).call{value: amount}(""); + require(success, "UTS: Withdrawal failed"); + + emit FeesWithdrawn(to, amount); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + + receive() external payable {} +} diff --git a/contracts/L2/manager/L2AnchoringManagerStorage.sol b/contracts/L2/manager/L2AnchoringManagerStorage.sol new file mode 100644 index 0000000..b67f4c9 --- /dev/null +++ b/contracts/L2/manager/L2AnchoringManagerStorage.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {L2AnchoringManagerTypes} from "./L2AnchoringManagerTypes.sol"; +import {IUniversalTimestamps} from "../../core/IUniversalTimestamps.sol"; +import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; + +/** + * @dev Library containing the ERC-7201 namespace constant. + * This keeps the implementation detail hidden from the interface. + */ +library L2AnchoringManagerStorage { + string internal constant NAMESPACE = "uts.storage.L2AnchoringManager"; + + /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.L2AnchoringManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 internal constant SLOT = 0x9831cc7956aa6e272a6b3f7bd193bca727880ec1ca574ef61afc1d64fc9e5000; + + /// @custom:storage-location erc7201:uts.storage.L2AnchoringManager + struct Storage { + IUniversalTimestamps uts; + IL1FeeOracle feeOracle; + /// @notice L1 contract that sends messages to this manager on L2 + address l1Messenger; + /// @notice Executor for L1 -> L2 messages + IL2ScrollMessenger l2Messenger; + /// @notice L1 sender address + address l1Gateway; + /// @notice Address that collects the fees + address feeCollector; + /// @notice Queue index for the next anchoring item to be added + uint256 queueIndex; + /// @notice Next index of the anchoring item to be confirmed + uint256 confirmedIndex; + mapping(uint256 => L2AnchoringManagerTypes.AnchoringItem) items; + mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup + } + + function get() internal pure returns (L2AnchoringManagerStorage.Storage storage $) { + assembly ("memory-safe") { + $.slot := SLOT + } + } +} diff --git a/contracts/L2/manager/L1AnchoringManagerTypes.sol b/contracts/L2/manager/L2AnchoringManagerTypes.sol similarity index 78% rename from contracts/L2/manager/L1AnchoringManagerTypes.sol rename to contracts/L2/manager/L2AnchoringManagerTypes.sol index 24b2ff9..c205c1e 100644 --- a/contracts/L2/manager/L1AnchoringManagerTypes.sol +++ b/contracts/L2/manager/L2AnchoringManagerTypes.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; -library L1AnchoringManagerTypes { +library L2AnchoringManagerTypes { /// @notice Attestation struct to hold timestamp and block number for each attested root struct AnchoringItem { bytes32 root; diff --git a/contracts/L2/oracle/IL1FeeOracle.sol b/contracts/L2/oracle/IL1FeeOracle.sol index fe39ace..ec67b0a 100644 --- a/contracts/L2/oracle/IL1FeeOracle.sol +++ b/contracts/L2/oracle/IL1FeeOracle.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; interface IL1FeeOracle { /** diff --git a/contracts/L2/oracle/L1FeeOracle.sol b/contracts/L2/oracle/L1FeeOracle.sol index 5e4e7f7..9db42b3 100644 --- a/contracts/L2/oracle/L1FeeOracle.sol +++ b/contracts/L2/oracle/L1FeeOracle.sol @@ -1,10 +1,9 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; import {IL1FeeOracle} from "./IL1FeeOracle.sol"; -import {Constants} from "../../Constants.sol"; /** * @title L1FeeOracle @@ -18,6 +17,9 @@ import {Constants} from "../../Constants.sol"; * - _discountRatio: The fraction (< 1.0) representing the aggregated share + protocol margin (e.g., 0.005e18). */ contract L1FeeOracle is IL1FeeOracle, Ownable { + IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = + IL1GasPriceOracle(0x5300000000000000000000000000000000000002); + // Estimated gas consumed on L1 per attestation (in a batch) uint256 private _gasPerAttestation; // Discount: The ratio (< 1.0) representing the aggregated share + protocol margin. @@ -57,18 +59,18 @@ contract L1FeeOracle is IL1FeeOracle, Ownable { /// @inheritdoc IL1FeeOracle function getL1BaseFee() external view returns (uint256) { - return Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + return L1_GAS_PRICE_ORACLE.l1BaseFee(); } /// @inheritdoc IL1FeeOracle function getFeePerAttestation() external view returns (uint256) { - uint256 l1BaseFee = Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); return (l1BaseFee * _gasPerAttestation) / 1e18; } /// @inheritdoc IL1FeeOracle function getFloorFee() external view returns (uint256) { - uint256 l1BaseFee = Constants.L1_GAS_PRICE_ORACLE.l1BaseFee(); + uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); // Calculate the fee with discount applied return (l1BaseFee * _gasPerAttestation * _discountRatio) / 1e18; } diff --git a/contracts/core/IUniversalTimestamps.sol b/contracts/core/IUniversalTimestamps.sol index 6333159..75fd00b 100644 --- a/contracts/core/IUniversalTimestamps.sol +++ b/contracts/core/IUniversalTimestamps.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; diff --git a/contracts/core/MerkleTree.sol b/contracts/core/MerkleTree.sol index b8795af..8277485 100644 --- a/contracts/core/MerkleTree.sol +++ b/contracts/core/MerkleTree.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; /** * @title MerkleTree diff --git a/contracts/core/UniversalTimestamps.sol b/contracts/core/UniversalTimestamps.sol index 0416df5..251e405 100644 --- a/contracts/core/UniversalTimestamps.sol +++ b/contracts/core/UniversalTimestamps.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; diff --git a/contracts/core/UniversalTimestampsStorage.sol b/contracts/core/UniversalTimestampsStorage.sol index 2c00140..04ef088 100644 --- a/contracts/core/UniversalTimestampsStorage.sol +++ b/contracts/core/UniversalTimestampsStorage.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; diff --git a/contracts/core/UniversalTimestampsTypes.sol b/contracts/core/UniversalTimestampsTypes.sol index 7ab0be3..759e914 100644 --- a/contracts/core/UniversalTimestampsTypes.sol +++ b/contracts/core/UniversalTimestampsTypes.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; library UniversalTimestampsTypes { /// @notice Attestation struct to hold timestamp and block number for each attested root diff --git a/crates/contracts/abi/ERC1967Proxy.json b/crates/contracts/abi/ERC1967Proxy.json index 0650ce4..eccaeda 100644 --- a/crates/contracts/abi/ERC1967Proxy.json +++ b/crates/contracts/abi/ERC1967Proxy.json @@ -1 +1 @@ -{"abi":[{"type":"constructor","inputs":[{"name":"implementation","type":"address","internalType":"address"},{"name":"_data","type":"bytes","internalType":"bytes"}],"stateMutability":"payable"},{"type":"fallback","stateMutability":"payable"},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]}],"bytecode":{"object":"0x608060405260405161037338038061037383398101604081905261002291610219565b61002c8282610033565b50506102fa565b61003c82610091565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561008557610080828261010c565b505050565b61008d6101ad565b5050565b806001600160a01b03163b5f036100cb57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61011984846101ce565b905080801561013a57505f3d118061013a57505f846001600160a01b03163b115b1561014f576101476101e1565b9150506101a7565b801561017957604051639996b31560e01b81526001600160a01b03851660048201526024016100c2565b3d1561018c576101876101fa565b6101a5565b60405163d6bda27560e01b815260040160405180910390fd5b505b92915050565b34156101cc5760405163b398979f60e01b815260040160405180910390fd5b565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561022a575f80fd5b82516001600160a01b0381168114610240575f80fd5b602084810151919350906001600160401b038082111561025e575f80fd5b818601915086601f830112610271575f80fd5b81518181111561028357610283610205565b604051601f8201601f19908116603f011681019083821181831017156102ab576102ab610205565b8160405282815289868487010111156102c2575f80fd5b5f93505b828410156102e357848401860151818501870152928501926102c6565b5f8684830101528096505050505050509250929050565b606d806103065f395ff3fe6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;1081:133;;;;;;;;;;;;;;;;;;:::i;:::-;1155:52;1185:14;1201:5;1155:29;:52::i;:::-;1081:133;;600:1117;;2264:344:29;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;:::-;;2264:344;;:::o;2454:148::-;2573:18;:16;:18::i;:::-;2264:344;;:::o;1671:281::-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;1523:32:39;;1805:47:29;;;1505:51:39;1478:18;;1805:47:29;;;;;;;;1744:119;811:66;1872:73;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;1523:32:39;;5045:24:34;;;1505:51:39;1478:18;;5045:24:34;1359:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;6159:70;6113:122::o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:127:39;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:1208;234:6;242;295:2;283:9;274:7;270:23;266:32;263:52;;;311:1;308;301:12;263:52;337:16;;-1:-1:-1;;;;;382:31:39;;372:42;;362:70;;428:1;425;418:12;362:70;475:2;506:18;;;500:25;451:5;;-1:-1:-1;475:2:39;-1:-1:-1;;;;;574:14:39;;;571:34;;;601:1;598;591:12;571:34;639:6;628:9;624:22;614:32;;684:7;677:4;673:2;669:13;665:27;655:55;;706:1;703;696:12;655:55;735:2;729:9;757:2;753;750:10;747:36;;;763:18;;:::i;:::-;838:2;832:9;806:2;892:13;;-1:-1:-1;;888:22:39;;;912:2;884:31;880:40;868:53;;;936:18;;;956:22;;;933:46;930:72;;;982:18;;:::i;:::-;1022:10;1018:2;1011:22;1057:2;1049:6;1042:18;1097:7;1092:2;1087;1083;1079:11;1075:20;1072:33;1069:53;;;1118:1;1115;1108:12;1069:53;1140:1;1131:10;;1150:129;1164:2;1161:1;1158:9;1150:129;;;1252:10;;;1248:19;;1242:26;1221:14;;;1217:23;;1210:59;1175:10;;;;1150:129;;;1321:1;1316:2;1311;1303:6;1299:15;1295:24;1288:35;1342:6;1332:16;;;;;;;;146:1208;;;;;:::o;1359:203::-;600:1117:28;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;2676:11:30;:9;:11::i;:::-;600:1117:28;2350:83:30;2398:28;2408:17;:15;:17::i;:::-;2398:9;:28::i;:::-;2350:83::o;1583:132:28:-;1650:7;1676:32;811:66:29;1519:53;-1:-1:-1;;;;;1519:53:29;;1441:138;1676:32:28;1669:39;;1583:132;:::o;949:922:30:-;1293:14;1287:4;1281;1268:40;1513:4;1507;1491:14;1485:4;1469:14;1462:5;1449:69;1598:16;1592:4;1586;1571:44;1636:6;1703:69;;;;1824:16;1818:4;1811:30;1703:69;1741:16;1735:4;1728:30","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"}],\"devdoc\":{\"details\":\"This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an implementation address that can be changed. This address is stored in storage in the location specified by https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the implementation behind the proxy.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}]},\"events\":{\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":\"ERC1967Proxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":{\"keccak256\":\"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049\",\"dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol\":{\"keccak256\":\"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5\",\"dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"payable","type":"fallback"}],"devdoc":{"kind":"dev","methods":{"constructor":{"details":"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":"ERC1967Proxy"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":{"keccak256":"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e","urls":["bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049","dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol":{"keccak256":"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f","urls":["bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5","dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol","id":40451,"exportedSymbols":{"ERC1967Proxy":[40450],"ERC1967Utils":[40744],"Proxy":[40780]},"nodeType":"SourceUnit","src":"114:1604:28","nodes":[{"id":40414,"nodeType":"PragmaDirective","src":"114:24:28","nodes":[],"literals":["solidity","^","0.8",".22"]},{"id":40416,"nodeType":"ImportDirective","src":"140:35:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol","file":"../Proxy.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40781,"symbolAliases":[{"foreign":{"id":40415,"name":"Proxy","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40780,"src":"148:5:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40418,"nodeType":"ImportDirective","src":"176:48:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol","file":"./ERC1967Utils.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40745,"symbolAliases":[{"foreign":{"id":40417,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"184:12:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40450,"nodeType":"ContractDefinition","src":"600:1117:28","nodes":[{"id":40437,"nodeType":"FunctionDefinition","src":"1081:133:28","nodes":[],"body":{"id":40436,"nodeType":"Block","src":"1145:69:28","nodes":[],"statements":[{"expression":{"arguments":[{"id":40432,"name":"implementation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40424,"src":"1185:14:28","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":40433,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40426,"src":"1201:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":40429,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1155:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40431,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1168:16:28","memberName":"upgradeToAndCall","nodeType":"MemberAccess","referencedDeclaration":40559,"src":"1155:29:28","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory)"}},"id":40434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1155:52:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":40435,"nodeType":"ExpressionStatement","src":"1155:52:28"}]},"documentation":{"id":40422,"nodeType":"StructuredDocumentation","src":"637:439:28","text":" @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.\n If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an\n encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.\n Requirements:\n - If `data` is empty, `msg.value` must be zero."},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":40427,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40424,"mutability":"mutable","name":"implementation","nameLocation":"1101:14:28","nodeType":"VariableDeclaration","scope":40437,"src":"1093:22:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40423,"name":"address","nodeType":"ElementaryTypeName","src":"1093:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":40426,"mutability":"mutable","name":"_data","nameLocation":"1130:5:28","nodeType":"VariableDeclaration","scope":40437,"src":"1117:18:28","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":40425,"name":"bytes","nodeType":"ElementaryTypeName","src":"1117:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1092:44:28"},"returnParameters":{"id":40428,"nodeType":"ParameterList","parameters":[],"src":"1145:0:28"},"scope":40450,"stateMutability":"payable","virtual":false,"visibility":"public"},{"id":40449,"nodeType":"FunctionDefinition","src":"1583:132:28","nodes":[],"body":{"id":40448,"nodeType":"Block","src":"1659:56:28","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":40444,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1676:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40445,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1689:17:28","memberName":"getImplementation","nodeType":"MemberAccess","referencedDeclaration":40496,"src":"1676:30:28","typeDescriptions":{"typeIdentifier":"t_function_internal_view$__$returns$_t_address_$","typeString":"function () view returns (address)"}},"id":40446,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1676:32:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"functionReturnParameters":40443,"id":40447,"nodeType":"Return","src":"1669:39:28"}]},"baseFunctions":[40761],"documentation":{"id":40438,"nodeType":"StructuredDocumentation","src":"1220:358:28","text":" @dev Returns the current implementation address.\n TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using\n the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`"},"implemented":true,"kind":"function","modifiers":[],"name":"_implementation","nameLocation":"1592:15:28","overrides":{"id":40440,"nodeType":"OverrideSpecifier","overrides":[],"src":"1632:8:28"},"parameters":{"id":40439,"nodeType":"ParameterList","parameters":[],"src":"1607:2:28"},"returnParameters":{"id":40443,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40442,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":40449,"src":"1650:7:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40441,"name":"address","nodeType":"ElementaryTypeName","src":"1650:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1649:9:28"},"scope":40450,"stateMutability":"view","virtual":true,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":40420,"name":"Proxy","nameLocations":["625:5:28"],"nodeType":"IdentifierPath","referencedDeclaration":40780,"src":"625:5:28"},"id":40421,"nodeType":"InheritanceSpecifier","src":"625:5:28"}],"canonicalName":"ERC1967Proxy","contractDependencies":[],"contractKind":"contract","documentation":{"id":40419,"nodeType":"StructuredDocumentation","src":"226:373:28","text":" @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n implementation address that can be changed. This address is stored in storage in the location specified by\n https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the\n implementation behind the proxy."},"fullyImplemented":true,"linearizedBaseContracts":[40450,40780],"name":"ERC1967Proxy","nameLocation":"609:12:28","scope":40451,"usedErrors":[40470,40483,41236,41627],"usedEvents":[40389]}],"license":"MIT"},"id":28} \ No newline at end of file +{"abi":[{"type":"constructor","inputs":[{"name":"implementation","type":"address","internalType":"address"},{"name":"_data","type":"bytes","internalType":"bytes"}],"stateMutability":"payable"},{"type":"fallback","stateMutability":"payable"},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]}],"bytecode":{"object":"0x608060405260405161037338038061037383398101604081905261002291610219565b61002c8282610033565b50506102fa565b61003c82610091565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561008557610080828261010c565b505050565b61008d6101ad565b5050565b806001600160a01b03163b5f036100cb57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61011984846101ce565b905080801561013a57505f3d118061013a57505f846001600160a01b03163b115b1561014f576101476101e1565b9150506101a7565b801561017957604051639996b31560e01b81526001600160a01b03851660048201526024016100c2565b3d1561018c576101876101fa565b6101a5565b60405163d6bda27560e01b815260040160405180910390fd5b505b92915050565b34156101cc5760405163b398979f60e01b815260040160405180910390fd5b565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b634e487b7160e01b5f52604160045260245ffd5b5f806040838503121561022a575f80fd5b82516001600160a01b0381168114610240575f80fd5b602084810151919350906001600160401b038082111561025e575f80fd5b818601915086601f830112610271575f80fd5b81518181111561028357610283610205565b604051601f8201601f19908116603f011681019083821181831017156102ab576102ab610205565b8160405282815289868487010111156102c2575f80fd5b5f93505b828410156102e357848401860151818501870152928501926102c6565b5f8684830101528096505050505050509250929050565b606d806103065f395ff3fe6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;1081:133;;;;;;;;;;;;;;;;;;:::i;:::-;1155:52;1185:14;1201:5;1155:29;:52::i;:::-;1081:133;;600:1117;;2264:344:29;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;:::-;;2264:344;;:::o;2454:148::-;2573:18;:16;:18::i;:::-;2264:344;;:::o;1671:281::-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;1523:32:39;;1805:47:29;;;1505:51:39;1478:18;;1805:47:29;;;;;;;;1744:119;811:66;1872:73;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;1523:32:39;;5045:24:34;;;1505:51:39;1478:18;;5045:24:34;1359:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;6159:70;6113:122::o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:127:39;75:10;70:3;66:20;63:1;56:31;106:4;103:1;96:15;130:4;127:1;120:15;146:1208;234:6;242;295:2;283:9;274:7;270:23;266:32;263:52;;;311:1;308;301:12;263:52;337:16;;-1:-1:-1;;;;;382:31:39;;372:42;;362:70;;428:1;425;418:12;362:70;475:2;506:18;;;500:25;451:5;;-1:-1:-1;475:2:39;-1:-1:-1;;;;;574:14:39;;;571:34;;;601:1;598;591:12;571:34;639:6;628:9;624:22;614:32;;684:7;677:4;673:2;669:13;665:27;655:55;;706:1;703;696:12;655:55;735:2;729:9;757:2;753;750:10;747:36;;;763:18;;:::i;:::-;838:2;832:9;806:2;892:13;;-1:-1:-1;;888:22:39;;;912:2;884:31;880:40;868:53;;;936:18;;;956:22;;;933:46;930:72;;;982:18;;:::i;:::-;1022:10;1018:2;1011:22;1057:2;1049:6;1042:18;1097:7;1092:2;1087;1083;1079:11;1075:20;1072:33;1069:53;;;1118:1;1115;1108:12;1069:53;1140:1;1131:10;;1150:129;1164:2;1161:1;1158:9;1150:129;;;1252:10;;;1248:19;;1242:26;1221:14;;;1217:23;;1210:59;1175:10;;;;1150:129;;;1321:1;1316:2;1311;1303:6;1299:15;1295:24;1288:35;1342:6;1332:16;;;;;;;;146:1208;;;;;:::o;1359:203::-;600:1117:28;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffd","sourceMap":"600:1117:28:-:0;;;2676:11:30;:9;:11::i;:::-;600:1117:28;2350:83:30;2398:28;2408:17;:15;:17::i;:::-;2398:9;:28::i;:::-;2350:83::o;1583:132:28:-;1650:7;1676:32;811:66:29;1519:53;-1:-1:-1;;;;;1519:53:29;;1441:138;1676:32:28;1669:39;;1583:132;:::o;949:922:30:-;1293:14;1287:4;1281;1268:40;1513:4;1507;1491:14;1485:4;1469:14;1462:5;1449:69;1598:16;1592:4;1586;1571:44;1636:6;1703:69;;;;1824:16;1818:4;1811:30;1703:69;1741:16;1735:4;1728:30","linkReferences":{}},"methodIdentifiers":{},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.29+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"}],\"devdoc\":{\"details\":\"This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an implementation address that can be changed. This address is stored in storage in the location specified by https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the implementation behind the proxy.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}]},\"events\":{\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":\"ERC1967Proxy\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol\":{\"keccak256\":\"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049\",\"dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol\":{\"keccak256\":\"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5\",\"dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.29+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"address","name":"implementation","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"stateMutability":"payable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"payable","type":"fallback"}],"devdoc":{"kind":"dev","methods":{"constructor":{"details":"Initializes the upgradeable proxy with an initial implementation specified by `implementation`. If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. Requirements: - If `data` is empty, `msg.value` must be zero."}},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":"ERC1967Proxy"},"evmVersion":"cancun","libraries":{}},"sources":{"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol":{"keccak256":"0xa3066ff86b94128a9d3956a63a0511fa1aae41bd455772ab587b32ff322acb2e","urls":["bzz-raw://bf7b192fd82acf6187970c80548f624b1b9c80425b62fa49e7fdb538a52de049","dweb:/ipfs/QmWXG1YCde1tqDYTbNwjkZDWVgPEjzaQGSDqWkyKLzaNua"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol":{"keccak256":"0x80935e4fae2c414f4e7789e13a820d06901182a5733ab006f8d68b5b09db993f","urls":["bzz-raw://752d991d6ca1087587b48103bc623f74888054f58581ff29166d90889c4765c5","dweb:/ipfs/QmRBsa6K2ChKxVWYY54YiyYhDBPbmY5HyKCtij5LoWh56o"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol","id":40451,"exportedSymbols":{"ERC1967Proxy":[40450],"ERC1967Utils":[40744],"Proxy":[40780]},"nodeType":"SourceUnit","src":"114:1604:28","nodes":[{"id":40414,"nodeType":"PragmaDirective","src":"114:24:28","nodes":[],"literals":["solidity","^","0.8",".22"]},{"id":40416,"nodeType":"ImportDirective","src":"140:35:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/Proxy.sol","file":"../Proxy.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40781,"symbolAliases":[{"foreign":{"id":40415,"name":"Proxy","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40780,"src":"148:5:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40418,"nodeType":"ImportDirective","src":"176:48:28","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol","file":"./ERC1967Utils.sol","nameLocation":"-1:-1:-1","scope":40451,"sourceUnit":40745,"symbolAliases":[{"foreign":{"id":40417,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"184:12:28","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":40450,"nodeType":"ContractDefinition","src":"600:1117:28","nodes":[{"id":40437,"nodeType":"FunctionDefinition","src":"1081:133:28","nodes":[],"body":{"id":40436,"nodeType":"Block","src":"1145:69:28","nodes":[],"statements":[{"expression":{"arguments":[{"id":40432,"name":"implementation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40424,"src":"1185:14:28","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"id":40433,"name":"_data","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40426,"src":"1201:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes memory"}],"expression":{"id":40429,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1155:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40431,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1168:16:28","memberName":"upgradeToAndCall","nodeType":"MemberAccess","referencedDeclaration":40559,"src":"1155:29:28","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$_t_bytes_memory_ptr_$returns$__$","typeString":"function (address,bytes memory)"}},"id":40434,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1155:52:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":40435,"nodeType":"ExpressionStatement","src":"1155:52:28"}]},"documentation":{"id":40422,"nodeType":"StructuredDocumentation","src":"637:439:28","text":" @dev Initializes the upgradeable proxy with an initial implementation specified by `implementation`.\n If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an\n encoded function call, and allows initializing the storage of the proxy like a Solidity constructor.\n Requirements:\n - If `data` is empty, `msg.value` must be zero."},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":40427,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40424,"mutability":"mutable","name":"implementation","nameLocation":"1101:14:28","nodeType":"VariableDeclaration","scope":40437,"src":"1093:22:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40423,"name":"address","nodeType":"ElementaryTypeName","src":"1093:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":40426,"mutability":"mutable","name":"_data","nameLocation":"1130:5:28","nodeType":"VariableDeclaration","scope":40437,"src":"1117:18:28","stateVariable":false,"storageLocation":"memory","typeDescriptions":{"typeIdentifier":"t_bytes_memory_ptr","typeString":"bytes"},"typeName":{"id":40425,"name":"bytes","nodeType":"ElementaryTypeName","src":"1117:5:28","typeDescriptions":{"typeIdentifier":"t_bytes_storage_ptr","typeString":"bytes"}},"visibility":"internal"}],"src":"1092:44:28"},"returnParameters":{"id":40428,"nodeType":"ParameterList","parameters":[],"src":"1145:0:28"},"scope":40450,"stateMutability":"payable","virtual":false,"visibility":"public"},{"id":40449,"nodeType":"FunctionDefinition","src":"1583:132:28","nodes":[],"body":{"id":40448,"nodeType":"Block","src":"1659:56:28","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":40444,"name":"ERC1967Utils","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40744,"src":"1676:12:28","typeDescriptions":{"typeIdentifier":"t_type$_t_contract$_ERC1967Utils_$40744_$","typeString":"type(library ERC1967Utils)"}},"id":40445,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1689:17:28","memberName":"getImplementation","nodeType":"MemberAccess","referencedDeclaration":40496,"src":"1676:30:28","typeDescriptions":{"typeIdentifier":"t_function_internal_view$__$returns$_t_address_$","typeString":"function () view returns (address)"}},"id":40446,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1676:32:28","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"functionReturnParameters":40443,"id":40447,"nodeType":"Return","src":"1669:39:28"}]},"baseFunctions":[40761],"documentation":{"id":40438,"nodeType":"StructuredDocumentation","src":"1220:358:28","text":" @dev Returns the current implementation address.\n TIP: To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using\n the https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.\n `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`"},"implemented":true,"kind":"function","modifiers":[],"name":"_implementation","nameLocation":"1592:15:28","overrides":{"id":40440,"nodeType":"OverrideSpecifier","overrides":[],"src":"1632:8:28"},"parameters":{"id":40439,"nodeType":"ParameterList","parameters":[],"src":"1607:2:28"},"returnParameters":{"id":40443,"nodeType":"ParameterList","parameters":[{"constant":false,"id":40442,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":40449,"src":"1650:7:28","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":40441,"name":"address","nodeType":"ElementaryTypeName","src":"1650:7:28","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1649:9:28"},"scope":40450,"stateMutability":"view","virtual":true,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":40420,"name":"Proxy","nameLocations":["625:5:28"],"nodeType":"IdentifierPath","referencedDeclaration":40780,"src":"625:5:28"},"id":40421,"nodeType":"InheritanceSpecifier","src":"625:5:28"}],"canonicalName":"ERC1967Proxy","contractDependencies":[],"contractKind":"contract","documentation":{"id":40419,"nodeType":"StructuredDocumentation","src":"226:373:28","text":" @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an\n implementation address that can be changed. This address is stored in storage in the location specified by\n https://eips.ethereum.org/EIPS/eip-1967[ERC-1967], so that it doesn't conflict with the storage layout of the\n implementation behind the proxy."},"fullyImplemented":true,"linearizedBaseContracts":[40450,40780],"name":"ERC1967Proxy","nameLocation":"609:12:28","scope":40451,"usedErrors":[40470,40483,41236,41627],"usedEvents":[40389]}],"license":"MIT"},"id":28} \ No newline at end of file diff --git a/crates/contracts/abi/IUniversalTimestamps.json b/crates/contracts/abi/IUniversalTimestamps.json index 9a80366..68bffa5 100644 --- a/crates/contracts/abi/IUniversalTimestamps.json +++ b/crates/contracts/abi/IUniversalTimestamps.json @@ -1 +1 @@ -{"abi":[{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"attest(bytes32)":"23c3617f","timestamp(bytes32)":"4d003070"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/IUniversalTimestamps.sol\":\"IUniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/IUniversalTimestamps.sol":"IUniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/IUniversalTimestamps.sol","id":187,"exportedSymbols":{"IUniversalTimestamps":[186]},"nodeType":"SourceUnit","src":"33:262:1","nodes":[{"id":165,"nodeType":"PragmaDirective","src":"33:24:1","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":186,"nodeType":"ContractDefinition","src":"59:235:1","nodes":[{"id":173,"nodeType":"EventDefinition","src":"96:80:1","nodes":[],"anonymous":false,"eventSelector":"61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a","name":"Attested","nameLocation":"102:8:1","parameters":{"id":172,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"indexed":true,"mutability":"mutable","name":"root","nameLocation":"127:4:1","nodeType":"VariableDeclaration","scope":173,"src":"111:20:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":166,"name":"bytes32","nodeType":"ElementaryTypeName","src":"111:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"},{"constant":false,"id":169,"indexed":true,"mutability":"mutable","name":"sender","nameLocation":"149:6:1","nodeType":"VariableDeclaration","scope":173,"src":"133:22:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":168,"name":"address","nodeType":"ElementaryTypeName","src":"133:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":171,"indexed":false,"mutability":"mutable","name":"timestamp","nameLocation":"165:9:1","nodeType":"VariableDeclaration","scope":173,"src":"157:17:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":170,"name":"uint256","nodeType":"ElementaryTypeName","src":"157:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"110:65:1"}},{"id":178,"nodeType":"FunctionDefinition","src":"182:39:1","nodes":[],"functionSelector":"23c3617f","implemented":false,"kind":"function","modifiers":[],"name":"attest","nameLocation":"191:6:1","parameters":{"id":176,"nodeType":"ParameterList","parameters":[{"constant":false,"id":175,"mutability":"mutable","name":"root","nameLocation":"206:4:1","nodeType":"VariableDeclaration","scope":178,"src":"198:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":174,"name":"bytes32","nodeType":"ElementaryTypeName","src":"198:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"197:14:1"},"returnParameters":{"id":177,"nodeType":"ParameterList","parameters":[],"src":"220:0:1"},"scope":186,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":185,"nodeType":"FunctionDefinition","src":"227:65:1","nodes":[],"functionSelector":"4d003070","implemented":false,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"236:9:1","parameters":{"id":181,"nodeType":"ParameterList","parameters":[{"constant":false,"id":180,"mutability":"mutable","name":"root","nameLocation":"254:4:1","nodeType":"VariableDeclaration","scope":185,"src":"246:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":179,"name":"bytes32","nodeType":"ElementaryTypeName","src":"246:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"245:14:1"},"returnParameters":{"id":184,"nodeType":"ParameterList","parameters":[{"constant":false,"id":183,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":185,"src":"283:7:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":182,"name":"uint256","nodeType":"ElementaryTypeName","src":"283:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"282:9:1"},"scope":186,"stateMutability":"view","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"IUniversalTimestamps","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[186],"name":"IUniversalTimestamps","nameLocation":"69:20:1","scope":187,"usedErrors":[],"usedEvents":[173]}],"license":"MIT"},"id":1} \ No newline at end of file +{"abi":[{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false}],"bytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"deployedBytecode":{"object":"0x","sourceMap":"","linkReferences":{}},"methodIdentifiers":{"attest(bytes32)":"23c3617f","timestamp(bytes32)":"4d003070"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.29+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/IUniversalTimestamps.sol\":\"IUniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.29+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/IUniversalTimestamps.sol":"IUniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/IUniversalTimestamps.sol","id":187,"exportedSymbols":{"IUniversalTimestamps":[186]},"nodeType":"SourceUnit","src":"33:262:1","nodes":[{"id":165,"nodeType":"PragmaDirective","src":"33:24:1","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":186,"nodeType":"ContractDefinition","src":"59:235:1","nodes":[{"id":173,"nodeType":"EventDefinition","src":"96:80:1","nodes":[],"anonymous":false,"eventSelector":"61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a","name":"Attested","nameLocation":"102:8:1","parameters":{"id":172,"nodeType":"ParameterList","parameters":[{"constant":false,"id":167,"indexed":true,"mutability":"mutable","name":"root","nameLocation":"127:4:1","nodeType":"VariableDeclaration","scope":173,"src":"111:20:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":166,"name":"bytes32","nodeType":"ElementaryTypeName","src":"111:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"},{"constant":false,"id":169,"indexed":true,"mutability":"mutable","name":"sender","nameLocation":"149:6:1","nodeType":"VariableDeclaration","scope":173,"src":"133:22:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":168,"name":"address","nodeType":"ElementaryTypeName","src":"133:7:1","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"},{"constant":false,"id":171,"indexed":false,"mutability":"mutable","name":"timestamp","nameLocation":"165:9:1","nodeType":"VariableDeclaration","scope":173,"src":"157:17:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":170,"name":"uint256","nodeType":"ElementaryTypeName","src":"157:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"110:65:1"}},{"id":178,"nodeType":"FunctionDefinition","src":"182:39:1","nodes":[],"functionSelector":"23c3617f","implemented":false,"kind":"function","modifiers":[],"name":"attest","nameLocation":"191:6:1","parameters":{"id":176,"nodeType":"ParameterList","parameters":[{"constant":false,"id":175,"mutability":"mutable","name":"root","nameLocation":"206:4:1","nodeType":"VariableDeclaration","scope":178,"src":"198:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":174,"name":"bytes32","nodeType":"ElementaryTypeName","src":"198:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"197:14:1"},"returnParameters":{"id":177,"nodeType":"ParameterList","parameters":[],"src":"220:0:1"},"scope":186,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":185,"nodeType":"FunctionDefinition","src":"227:65:1","nodes":[],"functionSelector":"4d003070","implemented":false,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"236:9:1","parameters":{"id":181,"nodeType":"ParameterList","parameters":[{"constant":false,"id":180,"mutability":"mutable","name":"root","nameLocation":"254:4:1","nodeType":"VariableDeclaration","scope":185,"src":"246:12:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":179,"name":"bytes32","nodeType":"ElementaryTypeName","src":"246:7:1","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"245:14:1"},"returnParameters":{"id":184,"nodeType":"ParameterList","parameters":[{"constant":false,"id":183,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":185,"src":"283:7:1","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":182,"name":"uint256","nodeType":"ElementaryTypeName","src":"283:7:1","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"282:9:1"},"scope":186,"stateMutability":"view","virtual":false,"visibility":"external"}],"abstract":false,"baseContracts":[],"canonicalName":"IUniversalTimestamps","contractDependencies":[],"contractKind":"interface","fullyImplemented":false,"linearizedBaseContracts":[186],"name":"IUniversalTimestamps","nameLocation":"69:20:1","scope":187,"usedErrors":[],"usedEvents":[173]}],"license":"MIT"},"id":1} \ No newline at end of file diff --git a/crates/contracts/abi/UniversalTimestamps.json b/crates/contracts/abi/UniversalTimestamps.json index 1110a7d..9e8d59a 100644 --- a/crates/contracts/abi/UniversalTimestamps.json +++ b/crates/contracts/abi/UniversalTimestamps.json @@ -1 +1 @@ -{"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"UPGRADE_INTERFACE_VERSION","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"initialize","inputs":[{"name":"initialOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"UUPSUnauthorizedCallContext","inputs":[]},{"type":"error","name":"UUPSUnsupportedProxiableUUID","inputs":[{"name":"slot","type":"bytes32","internalType":"bytes32"}]}],"bytecode":{"object":"0x60a060405230608052348015610013575f80fd5b5061001c610021565b6100d3565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff16156100715760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100d05780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b608051610b046100f95f395f81816104bb015281816104e401526106280152610b045ff3fe608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;1084:4:33;1041:48;;1489:53:2;;;;;;;;;-1:-1:-1;1513:22:2;:20;:22::i;:::-;1042:1794;;7709:422:32;3147:66;7898:15;;;;;;;7894:76;;;7936:23;;-1:-1:-1;;;7936:23:32;;;;;;;;;;;7894:76;7983:14;;-1:-1:-1;;;;;7983:14:32;;;:34;7979:146;;8033:33;;-1:-1:-1;;;;;;8033:33:32;-1:-1:-1;;;;;8033:33:32;;;;;8085:29;;158:50:39;;;8085:29:32;;146:2:39;131:18;8085:29:32;;;;;;;7979:146;7758:373;7709:422::o;14:200:39:-;1042:1794:2;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2140:354;;;;;;;;;;-1:-1:-1;2140:354:2;;;;;:::i;:::-;;:::i;:::-;;1896:138;;;;;;;;;;-1:-1:-1;1896:138:2;;;;;:::i;:::-;;:::i;:::-;;;345:25:39;;;333:2;318:18;1896:138:2;;;;;;;;3911:214:33;;;;;;:::i;:::-;;:::i;3466:126::-;;;;;;;;;;;;;:::i;3176:101:22:-;;;;;;;;;;;;;:::i;2462:144::-;;;;;;;;;;-1:-1:-1;1334:22:22;2591:8;2462:144;;-1:-1:-1;;;;;2591:8:22;;;2019:51:39;;2007:2;1992:18;2462:144:22;1873:203:39;1732:58:33;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1732:58:33;;;;;;;;;;;;:::i;1548:106:2:-;;;;;;;;;;-1:-1:-1;1548:106:2;;;;;:::i;:::-;;:::i;3426:215:22:-;;;;;;;;;;-1:-1:-1;3426:215:22;;;;;:::i;:::-;;:::i;2140:354:2:-;2197:4;2189:55;;;;-1:-1:-1;;;2189:55:2;;3027:2:39;2189:55:2;;;3009:21:39;3066:2;3046:18;;;3039:30;3105:26;3085:18;;;3078:54;3149:18;;2189:55:2;;;;;;;;;2255:36;2294:32;:30;:32::i;:::-;2340:12;:18;;;;;;;;;;;2255:71;;-1:-1:-1;2340:23:2;2336:152;;2379:12;:18;;;;;;;;;;;;2400:15;2379:36;;;;2434:43;;345:25:39;;;2449:10:2;;2379:18;;2434:43;;318:18:39;2434:43:2;;;;;;;2336:152;2179:315;2140:354;:::o;1896:138::-;1952:7;1978:32;:30;:32::i;:::-;:43;:49;;;;;-1:-1:-1;1978:49:2;;;;;1896:138::o;3911:214:33:-;2568:13;:11;:13::i;:::-;4026:36:::1;4044:17;4026;:36::i;:::-;4072:46;4094:17;4113:4;4072:21;:46::i;3466:126::-:0;3527:7;2839:20;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;;3466:126:33;:::o;3176:101:22:-;2355:13;:11;:13::i;:::-;3240:30:::1;3267:1;3240:18;:30::i;:::-;3176:101::o:0;1548:106:2:-;4158:30:32;4191:26;:24;:26::i;:::-;4302:15;;4158:59;;-1:-1:-1;4302:15:32;-1:-1:-1;;;4302:15:32;;;4301:16;;4348:14;;4279:19;4724:16;;:34;;;;;4744:14;4724:34;4704:54;;4768:17;4788:11;:16;;4803:1;4788:16;:50;;;;-1:-1:-1;4816:4:32;4808:25;:30;4788:50;4768:70;;4854:12;4853:13;:30;;;;;4871:12;4870:13;4853:30;4849:91;;;4906:23;;-1:-1:-1;;;4906:23:32;;;;;;;;;;;4849:91;4949:18;;-1:-1:-1;;4949:18:32;4966:1;4949:18;;;4977:67;;;;5011:22;;-1:-1:-1;;;;5011:22:32;-1:-1:-1;;;5011:22:32;;;4977:67;1619:28:2::1;1634:12;1619:14;:28::i;:::-;5068:14:32::0;5064:101;;;5098:23;;-1:-1:-1;;;;5098:23:32;;;5140:14;;-1:-1:-1;3331:50:39;;5140:14:32;;3319:2:39;3304:18;5140:14:32;;;;;;;5064:101;4092:1079;;;;;1548:106:2;:::o;3426:215:22:-;2355:13;:11;:13::i;:::-;-1:-1:-1;;;;;3510:22:22;::::1;3506:91;;3555:31;::::0;-1:-1:-1;;;3555:31:22;;3583:1:::1;3555:31;::::0;::::1;2019:51:39::0;1992:18;;3555:31:22::1;1873:203:39::0;3506:91:22::1;3606:28;3625:8;3606:18;:28::i;:::-;3426:215:::0;:::o;1660:230:2:-;1787:10;;;;;;;;;;;;;;;;;;1846:57:37;1724:36:2;1833:71:37;;;1724:36:2;1925:37:37;1787:24:2;1772:39;1660:230;-1:-1:-1;;1660:230:2:o;4328:312:33:-;4408:4;-1:-1:-1;;;;;4417:6:33;4400:23;;;:120;;;4514:6;-1:-1:-1;;;;;4478:42:33;:32;-1:-1:-1;;;;;;;;;;;1519:53:29;-1:-1:-1;;;;;1519:53:29;;1441:138;4478:32:33;-1:-1:-1;;;;;4478:42:33;;;4400:120;4383:251;;;4594:29;;-1:-1:-1;;;4594:29:33;;;;;;;;;;;2750:84:2;2355:13:22;:11;:13::i;5782:538:33:-;5899:17;-1:-1:-1;;;;;5881:50:33;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5881:52:33;;;;;;;;-1:-1:-1;;5881:52:33;;;;;;;;;;;;:::i;:::-;;;5877:437;;6243:60;;-1:-1:-1;;;6243:60:33;;-1:-1:-1;;;;;2037:32:39;;6243:60:33;;;2019:51:39;1992:18;;6243:60:33;1873:203:39;5877:437:33;-1:-1:-1;;;;;;;;;;;5975:40:33;;5971:120;;6042:34;;-1:-1:-1;;;6042:34:33;;;;;345:25:39;;;318:18;;6042:34:33;199:177:39;5971:120:33;6104:54;6134:17;6153:4;6104:29;:54::i;:::-;5934:235;5782:538;;:::o;4757:213::-;4831:4;-1:-1:-1;;;;;4840:6:33;4823:23;;4819:145;;4924:29;;-1:-1:-1;;;4924:29:33;;;;;;;;;;;2679:162:22;987:10:25;2738:7:22;1334:22;2591:8;-1:-1:-1;;;;;2591:8:22;;2462:144;2738:7;-1:-1:-1;;;;;2738:23:22;;2734:101;;2784:40;;-1:-1:-1;;;2784:40:22;;987:10:25;2784:40:22;;;2019:51:39;1992:18;;2784:40:22;1873:203:39;3795:248:22;1334:22;3944:8;;-1:-1:-1;;;;;;3962:19:22;;-1:-1:-1;;;;;3962:19:22;;;;;;;;3996:40;;3944:8;;;;;3996:40;;3868:24;;3996:40;3858:185;;3795:248;:::o;9071:205:32:-;9129:30;;3147:66;9186:27;8819:122;1868:127:22;6929:20:32;:18;:20::i;:::-;1950:38:22::1;1975:12;1950:24;:38::i;2264:344:29:-:0;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7082:141:32:-;7149:17;:15;:17::i;:::-;7144:73;;7189:17;;-1:-1:-1;;;7189:17:32;;;;;;;;;;;2001:235:22;6929:20:32;:18;:20::i;1671:281:29:-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;2037:32:39;;1805:47:29;;;2019:51:39;1992:18;;1805:47:29;1873:203:39;1744:119:29;-1:-1:-1;;;;;;;;;;;1872:73:29;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;2037:32:39;;5045:24:34;;;2019:51:39;1992:18;;5045:24:34;1873:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;8485:120:32;8535:4;8558:26;:24;:26::i;:::-;:40;-1:-1:-1;;;8558:40:32;;;;;;-1:-1:-1;8485:120:32:o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:180:39;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:39;;14:180;-1:-1:-1;14:180:39:o;381:173::-;449:20;;-1:-1:-1;;;;;498:31:39;;488:42;;478:70;;544:1;541;534:12;478:70;381:173;;;:::o;559:127::-;620:10;615:3;611:20;608:1;601:31;651:4;648:1;641:15;675:4;672:1;665:15;691:995;768:6;776;829:2;817:9;808:7;804:23;800:32;797:52;;;845:1;842;835:12;797:52;868:29;887:9;868:29;:::i;:::-;858:39;;948:2;937:9;933:18;920:32;971:18;1012:2;1004:6;1001:14;998:34;;;1028:1;1025;1018:12;998:34;1066:6;1055:9;1051:22;1041:32;;1111:7;1104:4;1100:2;1096:13;1092:27;1082:55;;1133:1;1130;1123:12;1082:55;1169:2;1156:16;1191:2;1187;1184:10;1181:36;;;1197:18;;:::i;:::-;1272:2;1266:9;1240:2;1326:13;;-1:-1:-1;;1322:22:39;;;1346:2;1318:31;1314:40;1302:53;;;1370:18;;;1390:22;;;1367:46;1364:72;;;1416:18;;:::i;:::-;1456:10;1452:2;1445:22;1491:2;1483:6;1476:18;1531:7;1526:2;1521;1517;1513:11;1509:20;1506:33;1503:53;;;1552:1;1549;1542:12;1503:53;1608:2;1603;1599;1595:11;1590:2;1582:6;1578:15;1565:46;1653:1;1648:2;1643;1635:6;1631:15;1627:24;1620:35;1674:6;1664:16;;;;;;;691:995;;;;;:::o;2081:548::-;2193:4;2222:2;2251;2240:9;2233:21;2283:6;2277:13;2326:6;2321:2;2310:9;2306:18;2299:34;2351:1;2361:140;2375:6;2372:1;2369:13;2361:140;;;2470:14;;;2466:23;;2460:30;2436:17;;;2455:2;2432:26;2425:66;2390:10;;2361:140;;;2365:3;2550:1;2545:2;2536:6;2525:9;2521:22;2517:31;2510:42;2620:2;2613;2609:7;2604:2;2596:6;2592:15;2588:29;2577:9;2573:45;2569:54;2561:62;;;;2081:548;;;;:::o;2634:186::-;2693:6;2746:2;2734:9;2725:7;2721:23;2717:32;2714:52;;;2762:1;2759;2752:12;2714:52;2785:29;2804:9;2785:29;:::i;:::-;2775:39;2634:186;-1:-1:-1;;;2634:186:39:o;3392:184::-;3462:6;3515:2;3503:9;3494:7;3490:23;3486:32;3483:52;;;3531:1;3528;3521:12;3483:52;-1:-1:-1;3554:16:39;;3392:184;-1:-1:-1;3392:184:39:o","linkReferences":{},"immutableReferences":{"41074":[{"start":1211,"length":32},{"start":1252,"length":32},{"start":1576,"length":32}]}},"methodIdentifiers":{"UPGRADE_INTERFACE_VERSION()":"ad3cb1cc","attest(bytes32)":"23c3617f","initialize(address)":"c4d66de8","owner()":"8da5cb5b","proxiableUUID()":"52d1902d","renounceOwnership()":"715018a6","timestamp(bytes32)":"4d003070","transferOwnership(address)":"f2fde38b","upgradeToAndCall(address,bytes)":"4f1ef286"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Records and exposes timestamps for attested Merkle roots using ERC-7201 namespaced storage (`uts.storage.UniversalTimestamps`) derived via {SlotDerivation}, and is implemented as a UUPS upgradeable contract via OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable base contracts. Storage is kept in a dedicated namespaced struct to remain layout-compatible across upgrades, while upgrades are authorized by the contract owner through {_authorizeUpgrade}.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"attest(bytes32)\":{\"params\":{\"root\":\"The Merkle Root to be attested\"}},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"renounceOwnership()\":{\"details\":\"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.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"}},\"title\":\"UniversalTimestamps\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"attest(bytes32)\":{\"notice\":\"Attest Merkle Root\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/UniversalTimestamps.sol\":\"UniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]},\"contracts/UniversalTimestamps.sol\":{\"keccak256\":\"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa\",\"dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed\",\"dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d\",\"dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e\",\"dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455\",\"dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422\",\"dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08\",\"dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34\",\"dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol\":{\"keccak256\":\"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337\",\"dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.24+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"OwnableInvalidOwner"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"type":"error","name":"OwnableUnauthorizedAccount"},{"inputs":[],"type":"error","name":"UUPSUnauthorizedCallContext"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"type":"error","name":"UUPSUnsupportedProxiableUUID"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"address","name":"initialOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"}],"devdoc":{"kind":"dev","methods":{"attest(bytes32)":{"params":{"root":"The Merkle Root to be attested"}},"constructor":{"custom:oz-upgrades-unsafe-allow":"constructor"},"owner()":{"details":"Returns the address of the current owner."},"proxiableUUID()":{"details":"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"renounceOwnership()":{"details":"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."},"transferOwnership(address)":{"details":"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."}},"version":1},"userdoc":{"kind":"user","methods":{"attest(bytes32)":{"notice":"Attest Merkle Root"}},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/UniversalTimestamps.sol":"UniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"},"contracts/UniversalTimestamps.sol":{"keccak256":"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b","urls":["bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa","dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol":{"keccak256":"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14","urls":["bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed","dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16","urls":["bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d","dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf","urls":["bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e","dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d","urls":["bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455","dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b","urls":["bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422","dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol":{"keccak256":"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05","urls":["bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08","dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a","urls":["bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34","dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol":{"keccak256":"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f","urls":["bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337","dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/UniversalTimestamps.sol","id":327,"exportedSymbols":{"IUniversalTimestamps":[186],"Initializable":[41058],"OwnableUpgradeable":[40327],"SlotDerivation":[41925],"UUPSUpgradeable":[41224],"UniversalTimestamps":[326]},"nodeType":"SourceUnit","src":"33:2804:2","nodes":[{"id":188,"nodeType":"PragmaDirective","src":"33:24:2","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":190,"nodeType":"ImportDirective","src":"59:96:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40332,"symbolAliases":[{"foreign":{"id":189,"name":"Initializable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41058,"src":"67:13:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":192,"nodeType":"ImportDirective","src":"156:101:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40328,"symbolAliases":[{"foreign":{"id":191,"name":"OwnableUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40327,"src":"164:18:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":194,"nodeType":"ImportDirective","src":"258:100:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40336,"symbolAliases":[{"foreign":{"id":193,"name":"UUPSUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41224,"src":"266:15:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":196,"nodeType":"ImportDirective","src":"359:64:2","nodes":[],"absolutePath":"contracts/IUniversalTimestamps.sol","file":"./IUniversalTimestamps.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":187,"symbolAliases":[{"foreign":{"id":195,"name":"IUniversalTimestamps","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"367:20:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":198,"nodeType":"ImportDirective","src":"424:80:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol","file":"@openzeppelin/contracts/utils/SlotDerivation.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":41926,"symbolAliases":[{"foreign":{"id":197,"name":"SlotDerivation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41925,"src":"432:14:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":326,"nodeType":"ContractDefinition","src":"1042:1794:2","nodes":[{"id":210,"nodeType":"UsingForDirective","src":"1153:32:2","nodes":[],"global":false,"libraryName":{"id":208,"name":"SlotDerivation","nameLocations":["1159:14:2"],"nodeType":"IdentifierPath","referencedDeclaration":41925,"src":"1159:14:2"},"typeName":{"id":209,"name":"string","nodeType":"ElementaryTypeName","src":"1178:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}}},{"id":213,"nodeType":"VariableDeclaration","src":"1191:70:2","nodes":[],"constant":true,"mutability":"constant","name":"_NAMESPACE","nameLocation":"1215:10:2","scope":326,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":211,"name":"string","nodeType":"ElementaryTypeName","src":"1191:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"value":{"hexValue":"7574732e73746f726167652e556e6976657273616c54696d657374616d7073","id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1228:33:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517b","typeString":"literal_string \"uts.storage.UniversalTimestamps\""},"value":"uts.storage.UniversalTimestamps"},"visibility":"private"},{"id":219,"nodeType":"StructDefinition","src":"1341:89:2","nodes":[],"canonicalName":"UniversalTimestamps.UniversalTimestampsStorage","documentation":{"id":214,"nodeType":"StructuredDocumentation","src":"1268:68:2","text":"@custom:storage-location erc7201:uts.storage.UniversalTimestamps"},"members":[{"constant":false,"id":218,"mutability":"mutable","name":"timestamps","nameLocation":"1413:10:2","nodeType":"VariableDeclaration","scope":219,"src":"1385:38:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"typeName":{"id":217,"keyName":"","keyNameLocation":"-1:-1:-1","keyType":{"id":215,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1393:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"Mapping","src":"1385:27:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"valueName":"","valueNameLocation":"-1:-1:-1","valueType":{"id":216,"name":"uint256","nodeType":"ElementaryTypeName","src":"1404:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}},"visibility":"internal"}],"name":"UniversalTimestampsStorage","nameLocation":"1348:26:2","scope":326,"visibility":"public"},{"id":227,"nodeType":"FunctionDefinition","src":"1489:53:2","nodes":[],"body":{"id":226,"nodeType":"Block","src":"1503:39:2","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":223,"name":"_disableInitializers","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41012,"src":"1513:20:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$__$returns$__$","typeString":"function ()"}},"id":224,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1513:22:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":225,"nodeType":"ExpressionStatement","src":"1513:22:2"}]},"documentation":{"id":220,"nodeType":"StructuredDocumentation","src":"1436:48:2","text":"@custom:oz-upgrades-unsafe-allow constructor"},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":221,"nodeType":"ParameterList","parameters":[],"src":"1500:2:2"},"returnParameters":{"id":222,"nodeType":"ParameterList","parameters":[],"src":"1503:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":239,"nodeType":"FunctionDefinition","src":"1548:106:2","nodes":[],"body":{"id":238,"nodeType":"Block","src":"1609:45:2","nodes":[],"statements":[{"expression":{"arguments":[{"id":235,"name":"initialOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":229,"src":"1634:12:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":234,"name":"__Ownable_init","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40187,"src":"1619:14:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$returns$__$","typeString":"function (address)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1619:28:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":237,"nodeType":"ExpressionStatement","src":"1619:28:2"}]},"functionSelector":"c4d66de8","implemented":true,"kind":"function","modifiers":[{"id":232,"kind":"modifierInvocation","modifierName":{"id":231,"name":"initializer","nameLocations":["1597:11:2"],"nodeType":"IdentifierPath","referencedDeclaration":40898,"src":"1597:11:2"},"nodeType":"ModifierInvocation","src":"1597:11:2"}],"name":"initialize","nameLocation":"1557:10:2","parameters":{"id":230,"nodeType":"ParameterList","parameters":[{"constant":false,"id":229,"mutability":"mutable","name":"initialOwner","nameLocation":"1576:12:2","nodeType":"VariableDeclaration","scope":239,"src":"1568:20:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":228,"name":"address","nodeType":"ElementaryTypeName","src":"1568:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1567:22:2"},"returnParameters":{"id":233,"nodeType":"ParameterList","parameters":[],"src":"1609:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":253,"nodeType":"FunctionDefinition","src":"1660:230:2","nodes":[],"body":{"id":252,"nodeType":"Block","src":"1762:128:2","nodes":[],"statements":[{"assignments":[246],"declarations":[{"constant":false,"id":246,"mutability":"mutable","name":"slot","nameLocation":"1780:4:2","nodeType":"VariableDeclaration","scope":252,"src":"1772:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":245,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1772:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"id":250,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":247,"name":"_NAMESPACE","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"1787:10:2","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},"id":248,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1798:11:2","memberName":"erc7201Slot","nodeType":"MemberAccess","referencedDeclaration":41808,"src":"1787:22:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$_t_bytes32_$attached_to$_t_string_memory_ptr_$","typeString":"function (string memory) pure returns (bytes32)"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1787:24:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"VariableDeclarationStatement","src":"1772:39:2"},{"AST":{"nativeSrc":"1846:38:2","nodeType":"YulBlock","src":"1846:38:2","statements":[{"nativeSrc":"1860:14:2","nodeType":"YulAssignment","src":"1860:14:2","value":{"name":"slot","nativeSrc":"1870:4:2","nodeType":"YulIdentifier","src":"1870:4:2"},"variableNames":[{"name":"$.slot","nativeSrc":"1860:6:2","nodeType":"YulIdentifier","src":"1860:6:2"}]}]},"evmVersion":"cancun","externalReferences":[{"declaration":243,"isOffset":false,"isSlot":true,"src":"1860:6:2","suffix":"slot","valueSize":1},{"declaration":246,"isOffset":false,"isSlot":false,"src":"1870:4:2","valueSize":1}],"flags":["memory-safe"],"id":251,"nodeType":"InlineAssembly","src":"1821:63:2"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_getUniversalTimestampsStorage","nameLocation":"1669:30:2","parameters":{"id":240,"nodeType":"ParameterList","parameters":[],"src":"1699:2:2"},"returnParameters":{"id":244,"nodeType":"ParameterList","parameters":[{"constant":false,"id":243,"mutability":"mutable","name":"$","nameLocation":"1759:1:2","nodeType":"VariableDeclaration","scope":253,"src":"1724:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":242,"nodeType":"UserDefinedTypeName","pathNode":{"id":241,"name":"UniversalTimestampsStorage","nameLocations":["1724:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"1724:26:2"},"referencedDeclaration":219,"src":"1724:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"src":"1723:38:2"},"scope":326,"stateMutability":"pure","virtual":false,"visibility":"private"},{"id":267,"nodeType":"FunctionDefinition","src":"1896:138:2","nodes":[],"body":{"id":266,"nodeType":"Block","src":"1961:73:2","nodes":[],"statements":[{"expression":{"baseExpression":{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":260,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"1978:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":261,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1978:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":262,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2011:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"1978:43:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":264,"indexExpression":{"id":263,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":255,"src":"2022:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"1978:49:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":259,"id":265,"nodeType":"Return","src":"1971:56:2"}]},"baseFunctions":[185],"functionSelector":"4d003070","implemented":true,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"1905:9:2","parameters":{"id":256,"nodeType":"ParameterList","parameters":[{"constant":false,"id":255,"mutability":"mutable","name":"root","nameLocation":"1923:4:2","nodeType":"VariableDeclaration","scope":267,"src":"1915:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":254,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1915:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"1914:14:2"},"returnParameters":{"id":259,"nodeType":"ParameterList","parameters":[{"constant":false,"id":258,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":267,"src":"1952:7:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":257,"name":"uint256","nodeType":"ElementaryTypeName","src":"1952:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1951:9:2"},"scope":326,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":315,"nodeType":"FunctionDefinition","src":"2140:354:2","nodes":[],"body":{"id":314,"nodeType":"Block","src":"2179:315:2","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":274,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2197:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":277,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2213:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":276,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2205:7:2","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":275,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2205:7:2","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2205:10:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"src":"2197:18:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"5554533a20526f6f742063616e6e6f74206265207a65726f","id":280,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2217:26:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""},"value":"UTS: Root cannot be zero"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""}],"id":273,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"2189:7:2","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2189:55:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":282,"nodeType":"ExpressionStatement","src":"2189:55:2"},{"assignments":[285],"declarations":[{"constant":false,"id":285,"mutability":"mutable","name":"$","nameLocation":"2290:1:2","nodeType":"VariableDeclaration","scope":314,"src":"2255:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":284,"nodeType":"UserDefinedTypeName","pathNode":{"id":283,"name":"UniversalTimestampsStorage","nameLocations":["2255:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"2255:26:2"},"referencedDeclaration":219,"src":"2255:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"id":288,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"id":286,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"2294:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2294:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"nodeType":"VariableDeclarationStatement","src":"2255:71:2"},{"condition":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"baseExpression":{"expression":{"id":289,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2340:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":290,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2342:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2340:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":292,"indexExpression":{"id":291,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2353:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"2340:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":293,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2362:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"2340:23:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":313,"nodeType":"IfStatement","src":"2336:152:2","trueBody":{"id":312,"nodeType":"Block","src":"2365:123:2","statements":[{"expression":{"id":302,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"baseExpression":{"expression":{"id":295,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2379:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":298,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2381:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2379:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":299,"indexExpression":{"id":297,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2392:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"nodeType":"IndexAccess","src":"2379:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"expression":{"id":300,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2400:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2406:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2400:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"2379:36:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":303,"nodeType":"ExpressionStatement","src":"2379:36:2"},{"eventCall":{"arguments":[{"id":305,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2443:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},{"expression":{"id":306,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"2449:3:2","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2453:6:2","memberName":"sender","nodeType":"MemberAccess","src":"2449:10:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"expression":{"id":308,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2461:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":309,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2467:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2461:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":304,"name":"Attested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2434:8:2","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes32_$_t_address_$_t_uint256_$returns$__$","typeString":"function (bytes32,address,uint256)"}},"id":310,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2434:43:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":311,"nodeType":"EmitStatement","src":"2429:48:2"}]}}]},"baseFunctions":[178],"documentation":{"id":268,"nodeType":"StructuredDocumentation","src":"2040:95:2","text":" @notice Attest Merkle Root\n @param root The Merkle Root to be attested"},"functionSelector":"23c3617f","implemented":true,"kind":"function","modifiers":[],"name":"attest","nameLocation":"2149:6:2","parameters":{"id":271,"nodeType":"ParameterList","parameters":[{"constant":false,"id":270,"mutability":"mutable","name":"root","nameLocation":"2164:4:2","nodeType":"VariableDeclaration","scope":315,"src":"2156:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":269,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2156:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"2155:14:2"},"returnParameters":{"id":272,"nodeType":"ParameterList","parameters":[],"src":"2179:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":325,"nodeType":"FunctionDefinition","src":"2750:84:2","nodes":[],"body":{"id":324,"nodeType":"Block","src":"2832:2:2","nodes":[],"statements":[]},"baseFunctions":[41178],"documentation":{"id":316,"nodeType":"StructuredDocumentation","src":"2500:245:2","text":" @dev Authorizes an upgrade to `newImplementation`.\n This function is restricted to the contract owner via the {onlyOwner} modifier,\n ensuring that only the owner can authorize upgrades to the implementation."},"implemented":true,"kind":"function","modifiers":[{"id":322,"kind":"modifierInvocation","modifierName":{"id":321,"name":"onlyOwner","nameLocations":["2822:9:2"],"nodeType":"IdentifierPath","referencedDeclaration":40222,"src":"2822:9:2"},"nodeType":"ModifierInvocation","src":"2822:9:2"}],"name":"_authorizeUpgrade","nameLocation":"2759:17:2","overrides":{"id":320,"nodeType":"OverrideSpecifier","overrides":[],"src":"2813:8:2"},"parameters":{"id":319,"nodeType":"ParameterList","parameters":[{"constant":false,"id":318,"mutability":"mutable","name":"newImplementation","nameLocation":"2785:17:2","nodeType":"VariableDeclaration","scope":325,"src":"2777:25:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":317,"name":"address","nodeType":"ElementaryTypeName","src":"2777:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2776:27:2"},"returnParameters":{"id":323,"nodeType":"ParameterList","parameters":[],"src":"2832:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":200,"name":"Initializable","nameLocations":["1074:13:2"],"nodeType":"IdentifierPath","referencedDeclaration":41058,"src":"1074:13:2"},"id":201,"nodeType":"InheritanceSpecifier","src":"1074:13:2"},{"baseName":{"id":202,"name":"OwnableUpgradeable","nameLocations":["1089:18:2"],"nodeType":"IdentifierPath","referencedDeclaration":40327,"src":"1089:18:2"},"id":203,"nodeType":"InheritanceSpecifier","src":"1089:18:2"},{"baseName":{"id":204,"name":"UUPSUpgradeable","nameLocations":["1109:15:2"],"nodeType":"IdentifierPath","referencedDeclaration":41224,"src":"1109:15:2"},"id":205,"nodeType":"InheritanceSpecifier","src":"1109:15:2"},{"baseName":{"id":206,"name":"IUniversalTimestamps","nameLocations":["1126:20:2"],"nodeType":"IdentifierPath","referencedDeclaration":186,"src":"1126:20:2"},"id":207,"nodeType":"InheritanceSpecifier","src":"1126:20:2"}],"canonicalName":"UniversalTimestamps","contractDependencies":[],"contractKind":"contract","documentation":{"id":199,"nodeType":"StructuredDocumentation","src":"506:535:2","text":" @title UniversalTimestamps\n @dev Records and exposes timestamps for attested Merkle roots using ERC-7201\n namespaced storage (`uts.storage.UniversalTimestamps`) derived via\n {SlotDerivation}, and is implemented as a UUPS upgradeable contract via\n OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable\n base contracts. Storage is kept in a dedicated namespaced struct to remain\n layout-compatible across upgrades, while upgrades are authorized by the\n contract owner through {_authorizeUpgrade}."},"fullyImplemented":true,"linearizedBaseContracts":[326,186,41224,40412,40327,40381,41058],"name":"UniversalTimestamps","nameLocation":"1051:19:2","scope":327,"usedErrors":[40163,40168,40470,40483,40807,40810,41081,41086,41236,41627],"usedEvents":[173,40174,40389,40815]}],"license":"MIT"},"id":2} \ No newline at end of file +{"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"UPGRADE_INTERFACE_VERSION","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"type":"function","name":"attest","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"initialize","inputs":[{"name":"initialOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"owner","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"type":"function","name":"proxiableUUID","inputs":[],"outputs":[{"name":"","type":"bytes32","internalType":"bytes32"}],"stateMutability":"view"},{"type":"function","name":"renounceOwnership","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"timestamp","inputs":[{"name":"root","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"type":"function","name":"transferOwnership","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"upgradeToAndCall","inputs":[{"name":"newImplementation","type":"address","internalType":"address"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"payable"},{"type":"event","name":"Attested","inputs":[{"name":"root","type":"bytes32","indexed":true,"internalType":"bytes32"},{"name":"sender","type":"address","indexed":true,"internalType":"address"},{"name":"timestamp","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"type":"event","name":"Initialized","inputs":[{"name":"version","type":"uint64","indexed":false,"internalType":"uint64"}],"anonymous":false},{"type":"event","name":"OwnershipTransferred","inputs":[{"name":"previousOwner","type":"address","indexed":true,"internalType":"address"},{"name":"newOwner","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"event","name":"Upgraded","inputs":[{"name":"implementation","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"type":"error","name":"AddressEmptyCode","inputs":[{"name":"target","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967InvalidImplementation","inputs":[{"name":"implementation","type":"address","internalType":"address"}]},{"type":"error","name":"ERC1967NonPayable","inputs":[]},{"type":"error","name":"FailedCall","inputs":[]},{"type":"error","name":"InvalidInitialization","inputs":[]},{"type":"error","name":"NotInitializing","inputs":[]},{"type":"error","name":"OwnableInvalidOwner","inputs":[{"name":"owner","type":"address","internalType":"address"}]},{"type":"error","name":"OwnableUnauthorizedAccount","inputs":[{"name":"account","type":"address","internalType":"address"}]},{"type":"error","name":"UUPSUnauthorizedCallContext","inputs":[]},{"type":"error","name":"UUPSUnsupportedProxiableUUID","inputs":[{"name":"slot","type":"bytes32","internalType":"bytes32"}]}],"bytecode":{"object":"0x60a060405230608052348015610013575f80fd5b5061001c610021565b6100d3565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff16156100715760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b03908116146100d05780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b608051610b046100f95f395f81816104bb015281816104e401526106280152610b045ff3fe608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;1084:4:33;1041:48;;1489:53:2;;;;;;;;;-1:-1:-1;1513:22:2;:20;:22::i;:::-;1042:1794;;7709:422:32;3147:66;7898:15;;;;;;;7894:76;;;7936:23;;-1:-1:-1;;;7936:23:32;;;;;;;;;;;7894:76;7983:14;;-1:-1:-1;;;;;7983:14:32;;;:34;7979:146;;8033:33;;-1:-1:-1;;;;;;8033:33:32;-1:-1:-1;;;;;8033:33:32;;;;;8085:29;;158:50:39;;;8085:29:32;;146:2:39;131:18;8085:29:32;;;;;;;7979:146;7758:373;7709:422::o;14:200:39:-;1042:1794:2;;;;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405260043610610084575f3560e01c8063715018a611610057578063715018a6146101025780638da5cb5b14610116578063ad3cb1cc1461015c578063c4d66de814610199578063f2fde38b146101b8575f80fd5b806323c3617f146100885780634d003070146100a95780634f1ef286146100db57806352d1902d146100ee575b5f80fd5b348015610093575f80fd5b506100a76100a236600461095e565b6101d7565b005b3480156100b4575f80fd5b506100c86100c336600461095e565b610295565b6040519081526020015b60405180910390f35b6100a76100e93660046109a4565b6102ae565b3480156100f9575f80fd5b506100c86102c9565b34801561010d575f80fd5b506100a76102e4565b348015610121575f80fd5b507f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546040516001600160a01b0390911681526020016100d2565b348015610167575f80fd5b5061018c604051806040016040528060058152602001640352e302e360dc1b81525081565b6040516100d29190610a60565b3480156101a4575f80fd5b506100a76101b3366004610aac565b6102f7565b3480156101c3575f80fd5b506100a76101d2366004610aac565b6103f0565b806102295760405162461bcd60e51b815260206004820152601860248201527f5554533a20526f6f742063616e6e6f74206265207a65726f000000000000000060448201526064015b60405180910390fd5b5f61023261042d565b5f8381526020829052604081205491925003610291575f828152602082815260409182902042908190559151918252339184917f61cae4201bb8c0117495b22a70f5202410666b349c27302dac280dc054b60f2a910160405180910390a35b5050565b5f61029e61042d565b5f92835260205250604090205490565b6102b66104b0565b6102bf82610554565b610291828261055c565b5f6102d261061d565b505f80516020610ae483398151915290565b6102ec610666565b6102f55f6106c1565b565b5f610300610731565b805490915060ff600160401b820416159067ffffffffffffffff165f811580156103275750825b90505f8267ffffffffffffffff1660011480156103435750303b155b905081158015610351575080155b1561036f5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561039957845460ff60401b1916600160401b1785555b6103a286610759565b83156103e857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b505050505050565b6103f8610666565b6001600160a01b03811661042157604051631e4fbdf760e01b81525f6004820152602401610220565b61042a816106c1565b50565b60408051808201909152601f81527f7574732e73746f726167652e556e6976657273616c54696d657374616d7073006020909101527f6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517a5f908152807f500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b034681005b92915050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016148061053657507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031661052a5f80516020610ae4833981519152546001600160a01b031690565b6001600160a01b031614155b156102f55760405163703e46dd60e11b815260040160405180910390fd5b61042a610666565b816001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa9250505080156105b6575060408051601f3d908101601f191682019092526105b391810190610acc565b60015b6105de57604051634c9c8ce360e01b81526001600160a01b0383166004820152602401610220565b5f80516020610ae4833981519152811461060e57604051632a87526960e21b815260048101829052602401610220565b610618838361076a565b505050565b306001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146102f55760405163703e46dd60e11b815260040160405180910390fd5b336106987f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300546001600160a01b031690565b6001600160a01b0316146102f55760405163118cdaa760e01b8152336004820152602401610220565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f807ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a006104aa565b6107616107bf565b61042a816107e4565b610773826107ec565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a28051156107b757610618828261084f565b6102916108ef565b6107c761090e565b6102f557604051631afcd79f60e31b815260040160405180910390fd5b6103f86107bf565b806001600160a01b03163b5f0361082157604051634c9c8ce360e01b81526001600160a01b0382166004820152602401610220565b5f80516020610ae483398151915280546001600160a01b0319166001600160a01b0392909216919091179055565b60605f61085c8484610927565b905080801561087d57505f3d118061087d57505f846001600160a01b03163b115b156108925761088a61093a565b9150506104aa565b80156108bc57604051639996b31560e01b81526001600160a01b0385166004820152602401610220565b3d156108cf576108ca610953565b6108e8565b60405163d6bda27560e01b815260040160405180910390fd5b5092915050565b34156102f55760405163b398979f60e01b815260040160405180910390fd5b5f610917610731565b54600160401b900460ff16919050565b5f805f835160208501865af49392505050565b6040513d81523d5f602083013e3d602001810160405290565b6040513d5f823e3d81fd5b5f6020828403121561096e575f80fd5b5035919050565b80356001600160a01b038116811461098b575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b5f80604083850312156109b5575f80fd5b6109be83610975565b9150602083013567ffffffffffffffff808211156109da575f80fd5b818501915085601f8301126109ed575f80fd5b8135818111156109ff576109ff610990565b604051601f8201601f19908116603f01168101908382118183101715610a2757610a27610990565b81604052828152886020848701011115610a3f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f602080835283518060208501525f5b81811015610a8c57858101830151858201604001528201610a70565b505f604082860101526040601f19601f8301168501019250505092915050565b5f60208284031215610abc575f80fd5b610ac582610975565b9392505050565b5f60208284031215610adc575f80fd5b505191905056fe360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc","sourceMap":"1042:1794:2:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2140:354;;;;;;;;;;-1:-1:-1;2140:354:2;;;;;:::i;:::-;;:::i;:::-;;1896:138;;;;;;;;;;-1:-1:-1;1896:138:2;;;;;:::i;:::-;;:::i;:::-;;;345:25:39;;;333:2;318:18;1896:138:2;;;;;;;;3911:214:33;;;;;;:::i;:::-;;:::i;3466:126::-;;;;;;;;;;;;;:::i;3176:101:22:-;;;;;;;;;;;;;:::i;2462:144::-;;;;;;;;;;-1:-1:-1;1334:22:22;2591:8;2462:144;;-1:-1:-1;;;;;2591:8:22;;;2019:51:39;;2007:2;1992:18;2462:144:22;1873:203:39;1732:58:33;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1732:58:33;;;;;;;;;;;;:::i;1548:106:2:-;;;;;;;;;;-1:-1:-1;1548:106:2;;;;;:::i;:::-;;:::i;3426:215:22:-;;;;;;;;;;-1:-1:-1;3426:215:22;;;;;:::i;:::-;;:::i;2140:354:2:-;2197:4;2189:55;;;;-1:-1:-1;;;2189:55:2;;3027:2:39;2189:55:2;;;3009:21:39;3066:2;3046:18;;;3039:30;3105:26;3085:18;;;3078:54;3149:18;;2189:55:2;;;;;;;;;2255:36;2294:32;:30;:32::i;:::-;2340:12;:18;;;;;;;;;;;2255:71;;-1:-1:-1;2340:23:2;2336:152;;2379:12;:18;;;;;;;;;;;;2400:15;2379:36;;;;2434:43;;345:25:39;;;2449:10:2;;2379:18;;2434:43;;318:18:39;2434:43:2;;;;;;;2336:152;2179:315;2140:354;:::o;1896:138::-;1952:7;1978:32;:30;:32::i;:::-;:43;:49;;;;;-1:-1:-1;1978:49:2;;;;;1896:138::o;3911:214:33:-;2568:13;:11;:13::i;:::-;4026:36:::1;4044:17;4026;:36::i;:::-;4072:46;4094:17;4113:4;4072:21;:46::i;3466:126::-:0;3527:7;2839:20;:18;:20::i;:::-;-1:-1:-1;;;;;;;;;;;;3466:126:33;:::o;3176:101:22:-;2355:13;:11;:13::i;:::-;3240:30:::1;3267:1;3240:18;:30::i;:::-;3176:101::o:0;1548:106:2:-;4158:30:32;4191:26;:24;:26::i;:::-;4302:15;;4158:59;;-1:-1:-1;4302:15:32;-1:-1:-1;;;4302:15:32;;;4301:16;;4348:14;;4279:19;4724:16;;:34;;;;;4744:14;4724:34;4704:54;;4768:17;4788:11;:16;;4803:1;4788:16;:50;;;;-1:-1:-1;4816:4:32;4808:25;:30;4788:50;4768:70;;4854:12;4853:13;:30;;;;;4871:12;4870:13;4853:30;4849:91;;;4906:23;;-1:-1:-1;;;4906:23:32;;;;;;;;;;;4849:91;4949:18;;-1:-1:-1;;4949:18:32;4966:1;4949:18;;;4977:67;;;;5011:22;;-1:-1:-1;;;;5011:22:32;-1:-1:-1;;;5011:22:32;;;4977:67;1619:28:2::1;1634:12;1619:14;:28::i;:::-;5068:14:32::0;5064:101;;;5098:23;;-1:-1:-1;;;;5098:23:32;;;5140:14;;-1:-1:-1;3331:50:39;;5140:14:32;;3319:2:39;3304:18;5140:14:32;;;;;;;5064:101;4092:1079;;;;;1548:106:2;:::o;3426:215:22:-;2355:13;:11;:13::i;:::-;-1:-1:-1;;;;;3510:22:22;::::1;3506:91;;3555:31;::::0;-1:-1:-1;;;3555:31:22;;3583:1:::1;3555:31;::::0;::::1;2019:51:39::0;1992:18;;3555:31:22::1;1873:203:39::0;3506:91:22::1;3606:28;3625:8;3606:18;:28::i;:::-;3426:215:::0;:::o;1660:230:2:-;1787:10;;;;;;;;;;;;;;;;;;1846:57:37;1724:36:2;1833:71:37;;;1724:36:2;1925:37:37;1787:24:2;1772:39;1660:230;-1:-1:-1;;1660:230:2:o;4328:312:33:-;4408:4;-1:-1:-1;;;;;4417:6:33;4400:23;;;:120;;;4514:6;-1:-1:-1;;;;;4478:42:33;:32;-1:-1:-1;;;;;;;;;;;1519:53:29;-1:-1:-1;;;;;1519:53:29;;1441:138;4478:32:33;-1:-1:-1;;;;;4478:42:33;;;4400:120;4383:251;;;4594:29;;-1:-1:-1;;;4594:29:33;;;;;;;;;;;2750:84:2;2355:13:22;:11;:13::i;5782:538:33:-;5899:17;-1:-1:-1;;;;;5881:50:33;;:52;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5881:52:33;;;;;;;;-1:-1:-1;;5881:52:33;;;;;;;;;;;;:::i;:::-;;;5877:437;;6243:60;;-1:-1:-1;;;6243:60:33;;-1:-1:-1;;;;;2037:32:39;;6243:60:33;;;2019:51:39;1992:18;;6243:60:33;1873:203:39;5877:437:33;-1:-1:-1;;;;;;;;;;;5975:40:33;;5971:120;;6042:34;;-1:-1:-1;;;6042:34:33;;;;;345:25:39;;;318:18;;6042:34:33;199:177:39;5971:120:33;6104:54;6134:17;6153:4;6104:29;:54::i;:::-;5934:235;5782:538;;:::o;4757:213::-;4831:4;-1:-1:-1;;;;;4840:6:33;4823:23;;4819:145;;4924:29;;-1:-1:-1;;;4924:29:33;;;;;;;;;;;2679:162:22;987:10:25;2738:7:22;1334:22;2591:8;-1:-1:-1;;;;;2591:8:22;;2462:144;2738:7;-1:-1:-1;;;;;2738:23:22;;2734:101;;2784:40;;-1:-1:-1;;;2784:40:22;;987:10:25;2784:40:22;;;2019:51:39;1992:18;;2784:40:22;1873:203:39;3795:248:22;1334:22;3944:8;;-1:-1:-1;;;;;;3962:19:22;;-1:-1:-1;;;;;3962:19:22;;;;;;;;3996:40;;3944:8;;;;;3996:40;;3868:24;;3996:40;3858:185;;3795:248;:::o;9071:205:32:-;9129:30;;3147:66;9186:27;8819:122;1868:127:22;6929:20:32;:18;:20::i;:::-;1950:38:22::1;1975:12;1950:24;:38::i;2264:344:29:-:0;2355:37;2374:17;2355:18;:37::i;:::-;2407:36;;-1:-1:-1;;;;;2407:36:29;;;;;;;;2458:11;;:15;2454:148;;2489:53;2518:17;2537:4;2489:28;:53::i;2454:148::-;2573:18;:16;:18::i;7082:141:32:-;7149:17;:15;:17::i;:::-;7144:73;;7189:17;;-1:-1:-1;;;7189:17:32;;;;;;;;;;;2001:235:22;6929:20:32;:18;:20::i;1671:281:29:-;1748:17;-1:-1:-1;;;;;1748:29:29;;1781:1;1748:34;1744:119;;1805:47;;-1:-1:-1;;;1805:47:29;;-1:-1:-1;;;;;2037:32:39;;1805:47:29;;;2019:51:39;1992:18;;1805:47:29;1873:203:39;1744:119:29;-1:-1:-1;;;;;;;;;;;1872:73:29;;-1:-1:-1;;;;;;1872:73:29;-1:-1:-1;;;;;1872:73:29;;;;;;;;;;1671:281::o;4691:549:34:-;4774:12;4798;4813:47;4847:6;4855:4;4813:33;:47::i;:::-;4798:62;;4874:7;:72;;;;-1:-1:-1;4918:1:34;4583:16:36;4886:33:34;:59;;;;4944:1;4923:6;-1:-1:-1;;;;;4923:18:34;;:22;4886:59;4870:364;;;4969:25;:23;:25::i;:::-;4962:32;;;;;4870:364;5015:7;5011:223;;;5045:24;;-1:-1:-1;;;5045:24:34;;-1:-1:-1;;;;;2037:32:39;;5045:24:34;;;2019:51:39;1992:18;;5045:24:34;1873:203:39;5011:223:34;4583:16:36;5090:33:34;5086:148;;5139:27;:25;:27::i;:::-;5086:148;;;5204:19;;-1:-1:-1;;;5204:19:34;;;;;;;;;;;5086:148;4788:452;4691:549;;;;:::o;6113:122:29:-;6163:9;:13;6159:70;;6199:19;;-1:-1:-1;;;6199:19:29;;;;;;;;;;;8485:120:32;8535:4;8558:26;:24;:26::i;:::-;:40;-1:-1:-1;;;8558:40:32;;;;;;-1:-1:-1;8485:120:32:o;3383:242:36:-;3466:12;3604:4;3598;3591;3585:11;3578:4;3572;3568:15;3560:6;3553:5;3540:69;3529:80;3383:242;-1:-1:-1;;;3383:242:36:o;4698:334::-;4829:4;4823:11;4862:16;4847:32;;4932:16;4926:4;4919;4907:17;;4892:57;4997:16;4991:4;4987:27;4979:6;4975:40;4969:4;4962:54;4698:334;:::o;5099:223::-;5203:4;5197:11;5247:16;5241:4;5236:3;5221:43;5289:16;5284:3;5277:29;14:180:39;73:6;126:2;114:9;105:7;101:23;97:32;94:52;;;142:1;139;132:12;94:52;-1:-1:-1;165:23:39;;14:180;-1:-1:-1;14:180:39:o;381:173::-;449:20;;-1:-1:-1;;;;;498:31:39;;488:42;;478:70;;544:1;541;534:12;478:70;381:173;;;:::o;559:127::-;620:10;615:3;611:20;608:1;601:31;651:4;648:1;641:15;675:4;672:1;665:15;691:995;768:6;776;829:2;817:9;808:7;804:23;800:32;797:52;;;845:1;842;835:12;797:52;868:29;887:9;868:29;:::i;:::-;858:39;;948:2;937:9;933:18;920:32;971:18;1012:2;1004:6;1001:14;998:34;;;1028:1;1025;1018:12;998:34;1066:6;1055:9;1051:22;1041:32;;1111:7;1104:4;1100:2;1096:13;1092:27;1082:55;;1133:1;1130;1123:12;1082:55;1169:2;1156:16;1191:2;1187;1184:10;1181:36;;;1197:18;;:::i;:::-;1272:2;1266:9;1240:2;1326:13;;-1:-1:-1;;1322:22:39;;;1346:2;1318:31;1314:40;1302:53;;;1370:18;;;1390:22;;;1367:46;1364:72;;;1416:18;;:::i;:::-;1456:10;1452:2;1445:22;1491:2;1483:6;1476:18;1531:7;1526:2;1521;1517;1513:11;1509:20;1506:33;1503:53;;;1552:1;1549;1542:12;1503:53;1608:2;1603;1599;1595:11;1590:2;1582:6;1578:15;1565:46;1653:1;1648:2;1643;1635:6;1631:15;1627:24;1620:35;1674:6;1664:16;;;;;;;691:995;;;;;:::o;2081:548::-;2193:4;2222:2;2251;2240:9;2233:21;2283:6;2277:13;2326:6;2321:2;2310:9;2306:18;2299:34;2351:1;2361:140;2375:6;2372:1;2369:13;2361:140;;;2470:14;;;2466:23;;2460:30;2436:17;;;2455:2;2432:26;2425:66;2390:10;;2361:140;;;2365:3;2550:1;2545:2;2536:6;2525:9;2521:22;2517:31;2510:42;2620:2;2613;2609:7;2604:2;2596:6;2592:15;2588:29;2577:9;2573:45;2569:54;2561:62;;;;2081:548;;;;:::o;2634:186::-;2693:6;2746:2;2734:9;2725:7;2721:23;2717:32;2714:52;;;2762:1;2759;2752:12;2714:52;2785:29;2804:9;2785:29;:::i;:::-;2775:39;2634:186;-1:-1:-1;;;2634:186:39:o;3392:184::-;3462:6;3515:2;3503:9;3494:7;3490:23;3486:32;3483:52;;;3531:1;3528;3521:12;3483:52;-1:-1:-1;3554:16:39;;3392:184;-1:-1:-1;3392:184:39:o","linkReferences":{},"immutableReferences":{"41074":[{"start":1211,"length":32},{"start":1252,"length":32},{"start":1576,"length":32}]}},"methodIdentifiers":{"UPGRADE_INTERFACE_VERSION()":"ad3cb1cc","attest(bytes32)":"23c3617f","initialize(address)":"c4d66de8","owner()":"8da5cb5b","proxiableUUID()":"52d1902d","renounceOwnership()":"715018a6","timestamp(bytes32)":"4d003070","transferOwnership(address)":"f2fde38b","upgradeToAndCall(address,bytes)":"4f1ef286"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.29+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"}],\"name\":\"AddressEmptyCode\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"ERC1967InvalidImplementation\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ERC1967NonPayable\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FailedCall\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidInitialization\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotInitializing\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"OwnableInvalidOwner\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"OwnableUnauthorizedAccount\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"UUPSUnauthorizedCallContext\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"slot\",\"type\":\"bytes32\"}],\"name\":\"UUPSUnsupportedProxiableUUID\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"Attested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"version\",\"type\":\"uint64\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"UPGRADE_INTERFACE_VERSION\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"attest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxiableUUID\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"root\",\"type\":\"bytes32\"}],\"name\":\"timestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"upgradeToAndCall\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Records and exposes timestamps for attested Merkle roots using ERC-7201 namespaced storage (`uts.storage.UniversalTimestamps`) derived via {SlotDerivation}, and is implemented as a UUPS upgradeable contract via OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable base contracts. Storage is kept in a dedicated namespaced struct to remain layout-compatible across upgrades, while upgrades are authorized by the contract owner through {_authorizeUpgrade}.\",\"errors\":{\"AddressEmptyCode(address)\":[{\"details\":\"There's no code at `target` (it is not a contract).\"}],\"ERC1967InvalidImplementation(address)\":[{\"details\":\"The `implementation` of the proxy is invalid.\"}],\"ERC1967NonPayable()\":[{\"details\":\"An upgrade function sees `msg.value > 0` that may be lost.\"}],\"FailedCall()\":[{\"details\":\"A call to an address target failed. The target may have reverted.\"}],\"InvalidInitialization()\":[{\"details\":\"The contract is already initialized.\"}],\"NotInitializing()\":[{\"details\":\"The contract is not initializing.\"}],\"OwnableInvalidOwner(address)\":[{\"details\":\"The owner is not a valid owner account. (eg. `address(0)`)\"}],\"OwnableUnauthorizedAccount(address)\":[{\"details\":\"The caller account is not authorized to perform an operation.\"}],\"UUPSUnauthorizedCallContext()\":[{\"details\":\"The call is from an unauthorized context.\"}],\"UUPSUnsupportedProxiableUUID(bytes32)\":[{\"details\":\"The storage `slot` is unsupported as a UUID.\"}]},\"events\":{\"Initialized(uint64)\":{\"details\":\"Triggered when the contract has been initialized or reinitialized.\"},\"Upgraded(address)\":{\"details\":\"Emitted when the implementation is upgraded.\"}},\"kind\":\"dev\",\"methods\":{\"attest(bytes32)\":{\"params\":{\"root\":\"The Merkle Root to be attested\"}},\"constructor\":{\"custom:oz-upgrades-unsafe-allow\":\"constructor\"},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"proxiableUUID()\":{\"details\":\"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.\"},\"renounceOwnership()\":{\"details\":\"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.\"},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"upgradeToAndCall(address,bytes)\":{\"custom:oz-upgrades-unsafe-allow-reachable\":\"delegatecall\",\"details\":\"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event.\"}},\"title\":\"UniversalTimestamps\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"attest(bytes32)\":{\"notice\":\"Attest Merkle Root\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/UniversalTimestamps.sol\":\"UniversalTimestamps\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"appendCBOR\":false,\"bytecodeHash\":\"none\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/\",\":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/\",\":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/\",\":openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/\"]},\"sources\":{\"contracts/IUniversalTimestamps.sol\":{\"keccak256\":\"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf\",\"dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC\"]},\"contracts/UniversalTimestamps.sol\":{\"keccak256\":\"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa\",\"dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol\":{\"keccak256\":\"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed\",\"dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d\",\"dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e\",\"dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt\"]},\"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol\":{\"keccak256\":\"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455\",\"dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol\":{\"keccak256\":\"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c\",\"dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol\":{\"keccak256\":\"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422\",\"dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol\":{\"keccak256\":\"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a\",\"dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol\":{\"keccak256\":\"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d\",\"dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol\":{\"keccak256\":\"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08\",\"dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol\":{\"keccak256\":\"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34\",\"dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710\",\"dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol\":{\"keccak256\":\"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf\",\"dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol\":{\"keccak256\":\"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56\",\"dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol\":{\"keccak256\":\"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337\",\"dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv\"]},\"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol\":{\"keccak256\":\"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b\",\"dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.29+commit.e11b9ed9"},"language":"Solidity","output":{"abi":[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"type":"error","name":"AddressEmptyCode"},{"inputs":[{"internalType":"address","name":"implementation","type":"address"}],"type":"error","name":"ERC1967InvalidImplementation"},{"inputs":[],"type":"error","name":"ERC1967NonPayable"},{"inputs":[],"type":"error","name":"FailedCall"},{"inputs":[],"type":"error","name":"InvalidInitialization"},{"inputs":[],"type":"error","name":"NotInitializing"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"type":"error","name":"OwnableInvalidOwner"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"type":"error","name":"OwnableUnauthorizedAccount"},{"inputs":[],"type":"error","name":"UUPSUnauthorizedCallContext"},{"inputs":[{"internalType":"bytes32","name":"slot","type":"bytes32"}],"type":"error","name":"UUPSUnsupportedProxiableUUID"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32","indexed":true},{"internalType":"address","name":"sender","type":"address","indexed":true},{"internalType":"uint256","name":"timestamp","type":"uint256","indexed":false}],"type":"event","name":"Attested","anonymous":false},{"inputs":[{"internalType":"uint64","name":"version","type":"uint64","indexed":false}],"type":"event","name":"Initialized","anonymous":false},{"inputs":[{"internalType":"address","name":"previousOwner","type":"address","indexed":true},{"internalType":"address","name":"newOwner","type":"address","indexed":true}],"type":"event","name":"OwnershipTransferred","anonymous":false},{"inputs":[{"internalType":"address","name":"implementation","type":"address","indexed":true}],"type":"event","name":"Upgraded","anonymous":false},{"inputs":[],"stateMutability":"view","type":"function","name":"UPGRADE_INTERFACE_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}]},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"nonpayable","type":"function","name":"attest"},{"inputs":[{"internalType":"address","name":"initialOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"initialize"},{"inputs":[],"stateMutability":"view","type":"function","name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}]},{"inputs":[],"stateMutability":"view","type":"function","name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}]},{"inputs":[],"stateMutability":"nonpayable","type":"function","name":"renounceOwnership"},{"inputs":[{"internalType":"bytes32","name":"root","type":"bytes32"}],"stateMutability":"view","type":"function","name":"timestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}]},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"transferOwnership"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"stateMutability":"payable","type":"function","name":"upgradeToAndCall"}],"devdoc":{"kind":"dev","methods":{"attest(bytes32)":{"params":{"root":"The Merkle Root to be attested"}},"constructor":{"custom:oz-upgrades-unsafe-allow":"constructor"},"owner()":{"details":"Returns the address of the current owner."},"proxiableUUID()":{"details":"Implementation of the ERC-1822 {proxiableUUID} function. This returns the storage slot used by the implementation. It is used to validate the implementation's compatibility when performing an upgrade. IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier."},"renounceOwnership()":{"details":"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."},"transferOwnership(address)":{"details":"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner."},"upgradeToAndCall(address,bytes)":{"custom:oz-upgrades-unsafe-allow-reachable":"delegatecall","details":"Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call encoded in `data`. Calls {_authorizeUpgrade}. Emits an {Upgraded} event."}},"version":1},"userdoc":{"kind":"user","methods":{"attest(bytes32)":{"notice":"Attest Merkle Root"}},"version":1}},"settings":{"remappings":["@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/","erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/","forge-std/=lib/forge-std/src/","halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/","openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/","openzeppelin-contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/","openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"none","appendCBOR":false},"compilationTarget":{"contracts/UniversalTimestamps.sol":"UniversalTimestamps"},"evmVersion":"cancun","libraries":{}},"sources":{"contracts/IUniversalTimestamps.sol":{"keccak256":"0xfa9490d2704cebe76fa78d15f51ed3bf13577ffaf782fe0337db402872571df0","urls":["bzz-raw://c42b87e7040e6d72f5b49a2bff481191678e15df1c19793e9c55800e8f5276cf","dweb:/ipfs/QmNr4ET95fihTnh3YF6WjjKUsCFU7KkoPkvKAizb83pyCC"],"license":"MIT"},"contracts/UniversalTimestamps.sol":{"keccak256":"0xb1596ca55406c833dc01c604dd4d6fb3791a9f9cc0f4cad6292757abc52acb4b","urls":["bzz-raw://e8c53b928701b425781b8bce6e51fc3414d33d9d32e3b192777b6c5157396bfa","dweb:/ipfs/QmQHq55s3BHW4YEhBe8bRGmxFvSc6xhWF4pUub8is9oSkf"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol":{"keccak256":"0x85c3b9bac35a90dce9ed9b31532c3739cae432359d8d7ff59cb6712f21c7ed14","urls":["bzz-raw://a084d32ad4ad5b1d4494124d7695334dbeff81c2d1846a01ef1215153dd38eed","dweb:/ipfs/QmbzDrfeogDd3n65mADjLuy97oAMgh2CtiUxKKEpM3WB8b"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol":{"keccak256":"0x30d125b8417684dbfea3e8d57284b353a86b22077237b4aaf098c0b54b153e16","urls":["bzz-raw://2813775a6326190e75dfa9005c1abbdb1e541c195c0bf5656dd4199e8c66fd8d","dweb:/ipfs/QmYDKANBezQXNrEDyJ69RVXkgypW1hWj7MAvjfdNHTZY8L"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x4918e374e9ce84e9b196486bafbd46851d5e72ab315e31f0b1d7c443dcfea5bf","urls":["bzz-raw://2ced247afc54a93a13922ebbd63add61130abe483ab5b5b78e7e991d564d150e","dweb:/ipfs/QmTfxjcTgfekiguegjvYMyfqhyRNffui17f8xi86BCZNVt"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/contracts/utils/ContextUpgradeable.sol":{"keccak256":"0xad316bdc3ee64a0e29773256245045dc57b92660799ff14f668f7c0da9456a9d","urls":["bzz-raw://66463434d266816fca2a3a2734ceee88544e61b7cc3899c50333b46e8e771455","dweb:/ipfs/QmPYCzHjki1HQLvBub3uUqoUKGrwdgR3xP9Zpya14YTdXS"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol":{"keccak256":"0xbf2aefe54b76d7f7bcd4f6da1080b7b1662611937d870b880db584d09cea56b5","urls":["bzz-raw://f5e7e2f12e0feec75296e57f51f82fdaa8bd1551f4b8cc6560442c0bf60f818c","dweb:/ipfs/QmcW9wDMaQ8RbQibMarfp17a3bABzY5KraWe2YDwuUrUoz"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol":{"keccak256":"0x82f757819bf2429a0d4db141b99a4bbe5039e4ef86dfb94e2e6d40577ed5b28b","urls":["bzz-raw://37c30ed931e19fb71fdb806bb504cfdb9913b7127545001b64d4487783374422","dweb:/ipfs/QmUBHpv4hm3ZmwJ4GH8BeVzK4mv41Q6vBbWXxn8HExPXza"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Utils.sol":{"keccak256":"0xa1ad192cd45317c788618bef5cb1fb3ca4ce8b230f6433ac68cc1d850fb81618","urls":["bzz-raw://b43447bb85a53679d269a403c693b9d88d6c74177dfb35eddca63abaf7cf110a","dweb:/ipfs/QmXSDmpd4bNZj1PDgegr6C4w1jDaWHXCconC3rYiw9TSkQ"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol":{"keccak256":"0x20462ddb2665e9521372c76b001d0ce196e59dbbd989de9af5576cad0bd5628b","urls":["bzz-raw://f417fd12aeec8fbfaceaa30e3a08a0724c0bc39de363e2acf6773c897abbaf6d","dweb:/ipfs/QmU4Hko6sApdweVM92CsiuLKkCk8HfyBeutF89PCTz5Tye"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol":{"keccak256":"0xdb4d24ee2c087c391d587cd17adfe5b3f9d93b3110b1388c2ab6c7c0ad1dcd05","urls":["bzz-raw://ab7b6d5b9e2b88176312967fe0f0e78f3d9a1422fa5e4b64e2440c35869b5d08","dweb:/ipfs/QmXKYWWyzcLg1B2k7Sb1qkEXgLCYfXecR9wYW5obRzWP1Q"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol":{"keccak256":"0x1a26353563a2c63b4120ea0b94727253eeff84fe2241d42c1452308b9080e66a","urls":["bzz-raw://49a95e36d267828b4357186a79917002d616d8634e25d1f9818e2354cd2e7d34","dweb:/ipfs/QmWDkqE4KkyLAS2UkLsRgXE1FGB1qfEgBC3zMXBVsVWfdk"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Address.sol":{"keccak256":"0x0fa9e0d3a859900b5a46f70a03c73adf259603d5e05027a37fe0b45529d85346","urls":["bzz-raw://c2add4da0240c9f2ce47649c8bb6b11b40e98cf6f88b8bdc76b2704e89391710","dweb:/ipfs/QmNQTwF2uVzu4CRtNxr8bxyP9XuW6VsZuo2Nr4KR2bZr3d"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/Errors.sol":{"keccak256":"0x6afa713bfd42cf0f7656efa91201007ac465e42049d7de1d50753a373648c123","urls":["bzz-raw://ba1d02f4847670a1b83dec9f7d37f0b0418d6043447b69f3a29a5f9efc547fcf","dweb:/ipfs/QmQ7iH2keLNUKgq2xSWcRmuBE5eZ3F5whYAkAGzCNNoEWB"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/LowLevelCall.sol":{"keccak256":"0x5b4802a4352474792df3107e961d1cc593e47b820c14f69d3505cb28f5a6a583","urls":["bzz-raw://a6f86fd01f829499fe0545ff5dda07d4521988e88bfe0bf801fc15650921ed56","dweb:/ipfs/QmUUKu4ZDffHAmfkf3asuQfmLTyfpuy2Amdncc3SqfzKPG"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol":{"keccak256":"0x94045fd4f268edf2b2d01ef119268548c320366d6f5294ad30c1b8f9d4f5225f","urls":["bzz-raw://edfda81f426f8948b3834115c21e83c48180e6db0d2a8cd2debb2185ed349337","dweb:/ipfs/QmdYZneFyDAux1BuWQxLAdqtABrGS2k9WYCa7C9dvpKkWv"],"license":"MIT"},"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol":{"keccak256":"0xcf74f855663ce2ae00ed8352666b7935f6cddea2932fdf2c3ecd30a9b1cd0e97","urls":["bzz-raw://9f660b1f351b757dfe01438e59888f31f33ded3afcf5cb5b0d9bf9aa6f320a8b","dweb:/ipfs/QmarDJ5hZEgBtCmmrVzEZWjub9769eD686jmzb2XpSU1cM"],"license":"MIT"}},"version":1},"storageLayout":{"storage":[],"types":{}},"ast":{"absolutePath":"contracts/UniversalTimestamps.sol","id":327,"exportedSymbols":{"IUniversalTimestamps":[186],"Initializable":[41058],"OwnableUpgradeable":[40327],"SlotDerivation":[41925],"UUPSUpgradeable":[41224],"UniversalTimestamps":[326]},"nodeType":"SourceUnit","src":"33:2804:2","nodes":[{"id":188,"nodeType":"PragmaDirective","src":"33:24:2","nodes":[],"literals":["solidity","^","0.8",".24"]},{"id":190,"nodeType":"ImportDirective","src":"59:96:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40332,"symbolAliases":[{"foreign":{"id":189,"name":"Initializable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41058,"src":"67:13:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":192,"nodeType":"ImportDirective","src":"156:101:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40328,"symbolAliases":[{"foreign":{"id":191,"name":"OwnableUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40327,"src":"164:18:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":194,"nodeType":"ImportDirective","src":"258:100:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol","file":"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":40336,"symbolAliases":[{"foreign":{"id":193,"name":"UUPSUpgradeable","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41224,"src":"266:15:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":196,"nodeType":"ImportDirective","src":"359:64:2","nodes":[],"absolutePath":"contracts/IUniversalTimestamps.sol","file":"./IUniversalTimestamps.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":187,"symbolAliases":[{"foreign":{"id":195,"name":"IUniversalTimestamps","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":186,"src":"367:20:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":198,"nodeType":"ImportDirective","src":"424:80:2","nodes":[],"absolutePath":"lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/utils/SlotDerivation.sol","file":"@openzeppelin/contracts/utils/SlotDerivation.sol","nameLocation":"-1:-1:-1","scope":327,"sourceUnit":41926,"symbolAliases":[{"foreign":{"id":197,"name":"SlotDerivation","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41925,"src":"432:14:2","typeDescriptions":{}},"nameLocation":"-1:-1:-1"}],"unitAlias":""},{"id":326,"nodeType":"ContractDefinition","src":"1042:1794:2","nodes":[{"id":210,"nodeType":"UsingForDirective","src":"1153:32:2","nodes":[],"global":false,"libraryName":{"id":208,"name":"SlotDerivation","nameLocations":["1159:14:2"],"nodeType":"IdentifierPath","referencedDeclaration":41925,"src":"1159:14:2"},"typeName":{"id":209,"name":"string","nodeType":"ElementaryTypeName","src":"1178:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}}},{"id":213,"nodeType":"VariableDeclaration","src":"1191:70:2","nodes":[],"constant":true,"mutability":"constant","name":"_NAMESPACE","nameLocation":"1215:10:2","scope":326,"stateVariable":true,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string"},"typeName":{"id":211,"name":"string","nodeType":"ElementaryTypeName","src":"1191:6:2","typeDescriptions":{"typeIdentifier":"t_string_storage_ptr","typeString":"string"}},"value":{"hexValue":"7574732e73746f726167652e556e6976657273616c54696d657374616d7073","id":212,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"1228:33:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_6191da0f5f254a176c2a5b8e81a37f62349600d58cdbf87518a33cdde24d517b","typeString":"literal_string \"uts.storage.UniversalTimestamps\""},"value":"uts.storage.UniversalTimestamps"},"visibility":"private"},{"id":219,"nodeType":"StructDefinition","src":"1341:89:2","nodes":[],"canonicalName":"UniversalTimestamps.UniversalTimestampsStorage","documentation":{"id":214,"nodeType":"StructuredDocumentation","src":"1268:68:2","text":"@custom:storage-location erc7201:uts.storage.UniversalTimestamps"},"members":[{"constant":false,"id":218,"mutability":"mutable","name":"timestamps","nameLocation":"1413:10:2","nodeType":"VariableDeclaration","scope":219,"src":"1385:38:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"typeName":{"id":217,"keyName":"","keyNameLocation":"-1:-1:-1","keyType":{"id":215,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1393:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"Mapping","src":"1385:27:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"},"valueName":"","valueNameLocation":"-1:-1:-1","valueType":{"id":216,"name":"uint256","nodeType":"ElementaryTypeName","src":"1404:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}},"visibility":"internal"}],"name":"UniversalTimestampsStorage","nameLocation":"1348:26:2","scope":326,"visibility":"public"},{"id":227,"nodeType":"FunctionDefinition","src":"1489:53:2","nodes":[],"body":{"id":226,"nodeType":"Block","src":"1503:39:2","nodes":[],"statements":[{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":223,"name":"_disableInitializers","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":41012,"src":"1513:20:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$__$returns$__$","typeString":"function ()"}},"id":224,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1513:22:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":225,"nodeType":"ExpressionStatement","src":"1513:22:2"}]},"documentation":{"id":220,"nodeType":"StructuredDocumentation","src":"1436:48:2","text":"@custom:oz-upgrades-unsafe-allow constructor"},"implemented":true,"kind":"constructor","modifiers":[],"name":"","nameLocation":"-1:-1:-1","parameters":{"id":221,"nodeType":"ParameterList","parameters":[],"src":"1500:2:2"},"returnParameters":{"id":222,"nodeType":"ParameterList","parameters":[],"src":"1503:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":239,"nodeType":"FunctionDefinition","src":"1548:106:2","nodes":[],"body":{"id":238,"nodeType":"Block","src":"1609:45:2","nodes":[],"statements":[{"expression":{"arguments":[{"id":235,"name":"initialOwner","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":229,"src":"1634:12:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_address","typeString":"address"}],"id":234,"name":"__Ownable_init","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":40187,"src":"1619:14:2","typeDescriptions":{"typeIdentifier":"t_function_internal_nonpayable$_t_address_$returns$__$","typeString":"function (address)"}},"id":236,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1619:28:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":237,"nodeType":"ExpressionStatement","src":"1619:28:2"}]},"functionSelector":"c4d66de8","implemented":true,"kind":"function","modifiers":[{"id":232,"kind":"modifierInvocation","modifierName":{"id":231,"name":"initializer","nameLocations":["1597:11:2"],"nodeType":"IdentifierPath","referencedDeclaration":40898,"src":"1597:11:2"},"nodeType":"ModifierInvocation","src":"1597:11:2"}],"name":"initialize","nameLocation":"1557:10:2","parameters":{"id":230,"nodeType":"ParameterList","parameters":[{"constant":false,"id":229,"mutability":"mutable","name":"initialOwner","nameLocation":"1576:12:2","nodeType":"VariableDeclaration","scope":239,"src":"1568:20:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":228,"name":"address","nodeType":"ElementaryTypeName","src":"1568:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"1567:22:2"},"returnParameters":{"id":233,"nodeType":"ParameterList","parameters":[],"src":"1609:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"public"},{"id":253,"nodeType":"FunctionDefinition","src":"1660:230:2","nodes":[],"body":{"id":252,"nodeType":"Block","src":"1762:128:2","nodes":[],"statements":[{"assignments":[246],"declarations":[{"constant":false,"id":246,"mutability":"mutable","name":"slot","nameLocation":"1780:4:2","nodeType":"VariableDeclaration","scope":252,"src":"1772:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":245,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1772:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"id":250,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"expression":{"id":247,"name":"_NAMESPACE","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":213,"src":"1787:10:2","typeDescriptions":{"typeIdentifier":"t_string_memory_ptr","typeString":"string memory"}},"id":248,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"1798:11:2","memberName":"erc7201Slot","nodeType":"MemberAccess","referencedDeclaration":41808,"src":"1787:22:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$_t_string_memory_ptr_$returns$_t_bytes32_$attached_to$_t_string_memory_ptr_$","typeString":"function (string memory) pure returns (bytes32)"}},"id":249,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1787:24:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"VariableDeclarationStatement","src":"1772:39:2"},{"AST":{"nativeSrc":"1846:38:2","nodeType":"YulBlock","src":"1846:38:2","statements":[{"nativeSrc":"1860:14:2","nodeType":"YulAssignment","src":"1860:14:2","value":{"name":"slot","nativeSrc":"1870:4:2","nodeType":"YulIdentifier","src":"1870:4:2"},"variableNames":[{"name":"$.slot","nativeSrc":"1860:6:2","nodeType":"YulIdentifier","src":"1860:6:2"}]}]},"evmVersion":"cancun","externalReferences":[{"declaration":243,"isOffset":false,"isSlot":true,"src":"1860:6:2","suffix":"slot","valueSize":1},{"declaration":246,"isOffset":false,"isSlot":false,"src":"1870:4:2","valueSize":1}],"flags":["memory-safe"],"id":251,"nodeType":"InlineAssembly","src":"1821:63:2"}]},"implemented":true,"kind":"function","modifiers":[],"name":"_getUniversalTimestampsStorage","nameLocation":"1669:30:2","parameters":{"id":240,"nodeType":"ParameterList","parameters":[],"src":"1699:2:2"},"returnParameters":{"id":244,"nodeType":"ParameterList","parameters":[{"constant":false,"id":243,"mutability":"mutable","name":"$","nameLocation":"1759:1:2","nodeType":"VariableDeclaration","scope":253,"src":"1724:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":242,"nodeType":"UserDefinedTypeName","pathNode":{"id":241,"name":"UniversalTimestampsStorage","nameLocations":["1724:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"1724:26:2"},"referencedDeclaration":219,"src":"1724:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"src":"1723:38:2"},"scope":326,"stateMutability":"pure","virtual":false,"visibility":"private"},{"id":267,"nodeType":"FunctionDefinition","src":"1896:138:2","nodes":[],"body":{"id":266,"nodeType":"Block","src":"1961:73:2","nodes":[],"statements":[{"expression":{"baseExpression":{"expression":{"arguments":[],"expression":{"argumentTypes":[],"id":260,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"1978:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":261,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"1978:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":262,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2011:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"1978:43:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":264,"indexExpression":{"id":263,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":255,"src":"2022:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"1978:49:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"functionReturnParameters":259,"id":265,"nodeType":"Return","src":"1971:56:2"}]},"baseFunctions":[185],"functionSelector":"4d003070","implemented":true,"kind":"function","modifiers":[],"name":"timestamp","nameLocation":"1905:9:2","parameters":{"id":256,"nodeType":"ParameterList","parameters":[{"constant":false,"id":255,"mutability":"mutable","name":"root","nameLocation":"1923:4:2","nodeType":"VariableDeclaration","scope":267,"src":"1915:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":254,"name":"bytes32","nodeType":"ElementaryTypeName","src":"1915:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"1914:14:2"},"returnParameters":{"id":259,"nodeType":"ParameterList","parameters":[{"constant":false,"id":258,"mutability":"mutable","name":"","nameLocation":"-1:-1:-1","nodeType":"VariableDeclaration","scope":267,"src":"1952:7:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"},"typeName":{"id":257,"name":"uint256","nodeType":"ElementaryTypeName","src":"1952:7:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"visibility":"internal"}],"src":"1951:9:2"},"scope":326,"stateMutability":"view","virtual":false,"visibility":"external"},{"id":315,"nodeType":"FunctionDefinition","src":"2140:354:2","nodes":[],"body":{"id":314,"nodeType":"Block","src":"2179:315:2","nodes":[],"statements":[{"expression":{"arguments":[{"commonType":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"id":279,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"id":274,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2197:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"nodeType":"BinaryOperation","operator":"!=","rightExpression":{"arguments":[{"hexValue":"30","id":277,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2213:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"}],"id":276,"isConstant":false,"isLValue":false,"isPure":true,"lValueRequested":false,"nodeType":"ElementaryTypeNameExpression","src":"2205:7:2","typeDescriptions":{"typeIdentifier":"t_type$_t_bytes32_$","typeString":"type(bytes32)"},"typeName":{"id":275,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2205:7:2","typeDescriptions":{}}},"id":278,"isConstant":false,"isLValue":false,"isPure":true,"kind":"typeConversion","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2205:10:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"src":"2197:18:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},{"hexValue":"5554533a20526f6f742063616e6e6f74206265207a65726f","id":280,"isConstant":false,"isLValue":false,"isPure":true,"kind":"string","lValueRequested":false,"nodeType":"Literal","src":"2217:26:2","typeDescriptions":{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""},"value":"UTS: Root cannot be zero"}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bool","typeString":"bool"},{"typeIdentifier":"t_stringliteral_2804b13209a936ca289456f44fff96ae78a8d5be97dfafdb6227532f3504fdd2","typeString":"literal_string \"UTS: Root cannot be zero\""}],"id":273,"name":"require","nodeType":"Identifier","overloadedDeclarations":[-18,-18],"referencedDeclaration":-18,"src":"2189:7:2","typeDescriptions":{"typeIdentifier":"t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$","typeString":"function (bool,string memory) pure"}},"id":281,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2189:55:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":282,"nodeType":"ExpressionStatement","src":"2189:55:2"},{"assignments":[285],"declarations":[{"constant":false,"id":285,"mutability":"mutable","name":"$","nameLocation":"2290:1:2","nodeType":"VariableDeclaration","scope":314,"src":"2255:36:2","stateVariable":false,"storageLocation":"storage","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"},"typeName":{"id":284,"nodeType":"UserDefinedTypeName","pathNode":{"id":283,"name":"UniversalTimestampsStorage","nameLocations":["2255:26:2"],"nodeType":"IdentifierPath","referencedDeclaration":219,"src":"2255:26:2"},"referencedDeclaration":219,"src":"2255:26:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage"}},"visibility":"internal"}],"id":288,"initialValue":{"arguments":[],"expression":{"argumentTypes":[],"id":286,"name":"_getUniversalTimestampsStorage","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":253,"src":"2294:30:2","typeDescriptions":{"typeIdentifier":"t_function_internal_pure$__$returns$_t_struct$_UniversalTimestampsStorage_$219_storage_ptr_$","typeString":"function () pure returns (struct UniversalTimestamps.UniversalTimestampsStorage storage pointer)"}},"id":287,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2294:32:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"nodeType":"VariableDeclarationStatement","src":"2255:71:2"},{"condition":{"commonType":{"typeIdentifier":"t_uint256","typeString":"uint256"},"id":294,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftExpression":{"baseExpression":{"expression":{"id":289,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2340:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":290,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2342:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2340:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":292,"indexExpression":{"id":291,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2353:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"nodeType":"IndexAccess","src":"2340:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"BinaryOperation","operator":"==","rightExpression":{"hexValue":"30","id":293,"isConstant":false,"isLValue":false,"isPure":true,"kind":"number","lValueRequested":false,"nodeType":"Literal","src":"2362:1:2","typeDescriptions":{"typeIdentifier":"t_rational_0_by_1","typeString":"int_const 0"},"value":"0"},"src":"2340:23:2","typeDescriptions":{"typeIdentifier":"t_bool","typeString":"bool"}},"id":313,"nodeType":"IfStatement","src":"2336:152:2","trueBody":{"id":312,"nodeType":"Block","src":"2365:123:2","statements":[{"expression":{"id":302,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"leftHandSide":{"baseExpression":{"expression":{"id":295,"name":"$","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":285,"src":"2379:1:2","typeDescriptions":{"typeIdentifier":"t_struct$_UniversalTimestampsStorage_$219_storage_ptr","typeString":"struct UniversalTimestamps.UniversalTimestampsStorage storage pointer"}},"id":298,"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":false,"memberLocation":"2381:10:2","memberName":"timestamps","nodeType":"MemberAccess","referencedDeclaration":218,"src":"2379:12:2","typeDescriptions":{"typeIdentifier":"t_mapping$_t_bytes32_$_t_uint256_$","typeString":"mapping(bytes32 => uint256)"}},"id":299,"indexExpression":{"id":297,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2392:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"isConstant":false,"isLValue":true,"isPure":false,"lValueRequested":true,"nodeType":"IndexAccess","src":"2379:18:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"nodeType":"Assignment","operator":"=","rightHandSide":{"expression":{"id":300,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2400:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":301,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2406:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2400:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"src":"2379:36:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}},"id":303,"nodeType":"ExpressionStatement","src":"2379:36:2"},{"eventCall":{"arguments":[{"id":305,"name":"root","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":270,"src":"2443:4:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},{"expression":{"id":306,"name":"msg","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-15,"src":"2449:3:2","typeDescriptions":{"typeIdentifier":"t_magic_message","typeString":"msg"}},"id":307,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2453:6:2","memberName":"sender","nodeType":"MemberAccess","src":"2449:10:2","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},{"expression":{"id":308,"name":"block","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":-4,"src":"2461:5:2","typeDescriptions":{"typeIdentifier":"t_magic_block","typeString":"block"}},"id":309,"isConstant":false,"isLValue":false,"isPure":false,"lValueRequested":false,"memberLocation":"2467:9:2","memberName":"timestamp","nodeType":"MemberAccess","src":"2461:15:2","typeDescriptions":{"typeIdentifier":"t_uint256","typeString":"uint256"}}],"expression":{"argumentTypes":[{"typeIdentifier":"t_bytes32","typeString":"bytes32"},{"typeIdentifier":"t_address","typeString":"address"},{"typeIdentifier":"t_uint256","typeString":"uint256"}],"id":304,"name":"Attested","nodeType":"Identifier","overloadedDeclarations":[],"referencedDeclaration":173,"src":"2434:8:2","typeDescriptions":{"typeIdentifier":"t_function_event_nonpayable$_t_bytes32_$_t_address_$_t_uint256_$returns$__$","typeString":"function (bytes32,address,uint256)"}},"id":310,"isConstant":false,"isLValue":false,"isPure":false,"kind":"functionCall","lValueRequested":false,"nameLocations":[],"names":[],"nodeType":"FunctionCall","src":"2434:43:2","tryCall":false,"typeDescriptions":{"typeIdentifier":"t_tuple$__$","typeString":"tuple()"}},"id":311,"nodeType":"EmitStatement","src":"2429:48:2"}]}}]},"baseFunctions":[178],"documentation":{"id":268,"nodeType":"StructuredDocumentation","src":"2040:95:2","text":" @notice Attest Merkle Root\n @param root The Merkle Root to be attested"},"functionSelector":"23c3617f","implemented":true,"kind":"function","modifiers":[],"name":"attest","nameLocation":"2149:6:2","parameters":{"id":271,"nodeType":"ParameterList","parameters":[{"constant":false,"id":270,"mutability":"mutable","name":"root","nameLocation":"2164:4:2","nodeType":"VariableDeclaration","scope":315,"src":"2156:12:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"},"typeName":{"id":269,"name":"bytes32","nodeType":"ElementaryTypeName","src":"2156:7:2","typeDescriptions":{"typeIdentifier":"t_bytes32","typeString":"bytes32"}},"visibility":"internal"}],"src":"2155:14:2"},"returnParameters":{"id":272,"nodeType":"ParameterList","parameters":[],"src":"2179:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"external"},{"id":325,"nodeType":"FunctionDefinition","src":"2750:84:2","nodes":[],"body":{"id":324,"nodeType":"Block","src":"2832:2:2","nodes":[],"statements":[]},"baseFunctions":[41178],"documentation":{"id":316,"nodeType":"StructuredDocumentation","src":"2500:245:2","text":" @dev Authorizes an upgrade to `newImplementation`.\n This function is restricted to the contract owner via the {onlyOwner} modifier,\n ensuring that only the owner can authorize upgrades to the implementation."},"implemented":true,"kind":"function","modifiers":[{"id":322,"kind":"modifierInvocation","modifierName":{"id":321,"name":"onlyOwner","nameLocations":["2822:9:2"],"nodeType":"IdentifierPath","referencedDeclaration":40222,"src":"2822:9:2"},"nodeType":"ModifierInvocation","src":"2822:9:2"}],"name":"_authorizeUpgrade","nameLocation":"2759:17:2","overrides":{"id":320,"nodeType":"OverrideSpecifier","overrides":[],"src":"2813:8:2"},"parameters":{"id":319,"nodeType":"ParameterList","parameters":[{"constant":false,"id":318,"mutability":"mutable","name":"newImplementation","nameLocation":"2785:17:2","nodeType":"VariableDeclaration","scope":325,"src":"2777:25:2","stateVariable":false,"storageLocation":"default","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"},"typeName":{"id":317,"name":"address","nodeType":"ElementaryTypeName","src":"2777:7:2","stateMutability":"nonpayable","typeDescriptions":{"typeIdentifier":"t_address","typeString":"address"}},"visibility":"internal"}],"src":"2776:27:2"},"returnParameters":{"id":323,"nodeType":"ParameterList","parameters":[],"src":"2832:0:2"},"scope":326,"stateMutability":"nonpayable","virtual":false,"visibility":"internal"}],"abstract":false,"baseContracts":[{"baseName":{"id":200,"name":"Initializable","nameLocations":["1074:13:2"],"nodeType":"IdentifierPath","referencedDeclaration":41058,"src":"1074:13:2"},"id":201,"nodeType":"InheritanceSpecifier","src":"1074:13:2"},{"baseName":{"id":202,"name":"OwnableUpgradeable","nameLocations":["1089:18:2"],"nodeType":"IdentifierPath","referencedDeclaration":40327,"src":"1089:18:2"},"id":203,"nodeType":"InheritanceSpecifier","src":"1089:18:2"},{"baseName":{"id":204,"name":"UUPSUpgradeable","nameLocations":["1109:15:2"],"nodeType":"IdentifierPath","referencedDeclaration":41224,"src":"1109:15:2"},"id":205,"nodeType":"InheritanceSpecifier","src":"1109:15:2"},{"baseName":{"id":206,"name":"IUniversalTimestamps","nameLocations":["1126:20:2"],"nodeType":"IdentifierPath","referencedDeclaration":186,"src":"1126:20:2"},"id":207,"nodeType":"InheritanceSpecifier","src":"1126:20:2"}],"canonicalName":"UniversalTimestamps","contractDependencies":[],"contractKind":"contract","documentation":{"id":199,"nodeType":"StructuredDocumentation","src":"506:535:2","text":" @title UniversalTimestamps\n @dev Records and exposes timestamps for attested Merkle roots using ERC-7201\n namespaced storage (`uts.storage.UniversalTimestamps`) derived via\n {SlotDerivation}, and is implemented as a UUPS upgradeable contract via\n OpenZeppelin's Initializable, OwnableUpgradeable, and UUPSUpgradeable\n base contracts. Storage is kept in a dedicated namespaced struct to remain\n layout-compatible across upgrades, while upgrades are authorized by the\n contract owner through {_authorizeUpgrade}."},"fullyImplemented":true,"linearizedBaseContracts":[326,186,41224,40412,40327,40381,41058],"name":"UniversalTimestamps","nameLocation":"1051:19:2","scope":327,"usedErrors":[40163,40168,40470,40483,40807,40810,41081,41086,41236,41627],"usedEvents":[173,40174,40389,40815]}],"license":"MIT"},"id":2} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 02b37fd..3d9257b 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,12 +6,12 @@ ffi = true fs_permissions = [{ access = "read", path = "target/foundry" }] ignored_error_codes = [ 2018, # Function state mutability can be restricted to pure - 2394, # ransient storage as defined by EIP-1153 can break the composability of smart contracts + 2394, # Transient storage as defined by EIP-1153 can break the composability of smart contracts ] libs = ["lib"] out = "target/foundry" script = "contract-scripts" -solc_version = "0.8.24" +solc_version = "0.8.29" src = "contracts" test = "contract-tests" diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 9592499..3707d3c 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,18 +1,16 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Script, console} from "forge-std/Script.sol"; import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {L1AnchoringManager} from "../contracts/L2/manager/L1AnchoringManager.sol"; +import {L2AnchoringManager} from "../contracts/L2/manager/L2AnchoringManager.sol"; +import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; import {L1AnchoringGateway} from "../contracts/L1/L1AnchoringGateway.sol"; -import {Constants} from "../contracts/Constants.sol"; -bytes32 constant SALT = keccak256("universal-timestamps"); - -// Note: all address here are sepolia/scroll-sepolia addresses. +bytes32 constant SALT = keccak256("UniversalTimestamps"); contract DeployTimestampCreate2 is Script { function run() public { @@ -20,7 +18,6 @@ contract DeployTimestampCreate2 is Script { vm.startBroadcast(); UniversalTimestamps implementation = new UniversalTimestamps{salt: SALT}(); - // Implementation deployed at: 0x13889107F758b4c220E6422ef0f00965D5D2b178 console.log("Implementation deployed at:", address(implementation)); bytes memory initData = abi.encodeCall(UniversalTimestamps.initialize, (owner)); @@ -28,7 +25,6 @@ contract DeployTimestampCreate2 is Script { ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); vm.stopBroadcast(); - // Proxy deployed at: 0xdf939C24d9c075862837e3c9EC0cc1feD6376D59 console.log("Proxy deployed at:", address(proxy)); } } @@ -45,49 +41,95 @@ contract DeployFeeOracle is Script { ); vm.stopBroadcast(); - // Proxy deployed at: 0x36E62Da7f040fC19B857541474D2c5dc114f12af console.log("FeeOracle deployed at", address(implementation)); } } -contract DeployL1Manager is Script { +contract DeployManager is Script { function run() public { address owner = vm.envAddress("OWNER_ADDRESS"); - address l1Messenger = vm.envAddress("L1_MESSENGER_ADDRESS"); + address uts = vm.envAddress("UTS"); + address feeOracle = vm.envAddress("FEE_ORACLE"); + address l1Messenger = vm.envAddress("L1_MESSENGER"); + address l2Messenger = vm.envAddress("L2_MESSENGER"); vm.startBroadcast(); - L1AnchoringManager implementation = new L1AnchoringManager(); - // Implementation deployed at: 0xc496516540367Aa3E5c209E36d68AD326566943B + L2AnchoringManager implementation = new L2AnchoringManager(); console.log("Implementation deployed at:", address(implementation)); - - bytes memory initData = abi.encodeCall(L1AnchoringManager.initialize, (owner, IL1FeeOracle(l1Messenger))); + // function initialize(address initialOwner, address uts, address feeOracle, address l1Messenger, address l2Messenger) + bytes memory initData = + abi.encodeCall(L2AnchoringManager.initialize, (owner, uts, feeOracle, l1Messenger, l2Messenger)); ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); vm.stopBroadcast(); - // Proxy deployed at: 0x5f44B75D6A0D26533EAECaAcf81eDc9A947a39e9 console.log("Proxy deployed at:", address(proxy)); } } -contract DeployL1Gateway is Script { +contract DeployGateway is Script { function run() public { address owner = vm.envAddress("OWNER_ADDRESS"); - address l1Messenger = vm.envAddress("L1_MESSENGER_ADDRESS"); - address l1AnchoringManagerL2 = vm.envAddress("L1_ANCHORING_MANAGER_L2_ADDRESS"); + address uts = vm.envAddress("UTS"); + address l1Messenger = vm.envAddress("L1_MESSENGER"); + address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); vm.startBroadcast(); L1AnchoringGateway implementation = new L1AnchoringGateway(); - // Implementation deployed at: 0x8b28f0D465EC9780459E08827E662e35F24D5197 console.log("Implementation deployed at:", address(implementation)); - + // function initialize(address initialOwner, address uts, address l1Messenger, address l2AnchoringManager) bytes memory initData = - abi.encodeCall(L1AnchoringGateway.initialize, (owner, l1Messenger, l1AnchoringManagerL2)); + abi.encodeCall(L1AnchoringGateway.initialize, (owner, uts, l1Messenger, anchoringManager)); ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); vm.stopBroadcast(); - // Proxy deployed at: 0x91FA317cf93AAefc044B59df5dd463F513Cea516 console.log("Proxy deployed at:", address(proxy)); } } + +contract SetGateway is Script { + function run() public { + address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); + address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + + IL2AnchoringManager manager = IL2AnchoringManager(anchoringManager); + vm.startBroadcast(); + manager.setL1Gateway(l1Gateway); + vm.stopBroadcast(); + } +} + +contract UpgradeManager is Script { + function run() public { + address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + + vm.startBroadcast(); + L2AnchoringManager newImplementation = new L2AnchoringManager(); + console.log("New implementation deployed at:", address(newImplementation)); + + L2AnchoringManager manager = L2AnchoringManager(payable(anchoringManager)); + + manager.upgradeToAndCall(address(newImplementation), ""); + vm.stopBroadcast(); + + console.log("Manager upgraded to new implementation at:", address(newImplementation)); + } +} + +contract UpgradeGateway is Script { + function run() public { + address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); + + vm.startBroadcast(); + L1AnchoringGateway newImplementation = new L1AnchoringGateway(); + console.log("New implementation deployed at:", address(newImplementation)); + + L1AnchoringGateway gateway = L1AnchoringGateway(payable(l1Gateway)); + + gateway.upgradeToAndCall(address(newImplementation), ""); + vm.stopBroadcast(); + + console.log("Gateway upgraded to new implementation at:", address(newImplementation)); + } +} diff --git a/script/E2E.s.sol b/script/E2E.s.sol index 84e4b5c..be95df6 100644 --- a/script/E2E.s.sol +++ b/script/E2E.s.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.29; import {Script, console} from "forge-std/Script.sol"; -import {IL1AnchoringManager} from "../contracts/L2/manager/IL1AnchoringManager.sol"; +import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; import {IL1AnchoringGateway} from "../contracts/L1/IL1AnchoringGateway.sol"; import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; +import {MerkleTree} from "../contracts/core/MerkleTree.sol"; contract SubmitAnchoring is Script { function run() public { - address l1AnchoringManager = vm.envAddress("L1_ANCHORING_MANAGER"); + address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); address feeOracle = vm.envAddress("FEE_ORACLE"); - IL1AnchoringManager manager = IL1AnchoringManager(l1AnchoringManager); + IL2AnchoringManager manager = IL2AnchoringManager(anchoringManager); IL1FeeOracle oracle = IL1FeeOracle(feeOracle); bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; @@ -24,16 +25,38 @@ contract SubmitAnchoring is Script { } } +contract SubmitAnchoring2 is Script { + function run() public { + address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + address feeOracle = vm.envAddress("FEE_ORACLE"); + + IL2AnchoringManager manager = IL2AnchoringManager(anchoringManager); + IL1FeeOracle oracle = IL1FeeOracle(feeOracle); + + bytes32 root = 0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd; + uint256 fee = oracle.getFloorFee(); + console.log("Current floor fee:", fee); + + vm.startBroadcast(); + manager.submitForL1Anchoring{value: fee}(root); + vm.stopBroadcast(); + } +} + contract ConfirmAnchoring is Script { function run() public { - address l1AnchoringManager = vm.envAddress("L1_ANCHORING_GATEWAY"); + address anchoringGateway = vm.envAddress("ANCHORING_GATEWAY"); - IL1AnchoringGateway gateway = IL1AnchoringGateway(l1AnchoringManager); + IL1AnchoringGateway gateway = IL1AnchoringGateway(anchoringGateway); - bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + bytes32[] memory roots = new bytes32[](2); + roots[0] = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; + roots[1] = 0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd; + + bytes32 root = MerkleTree.computeRoot(roots); vm.startBroadcast(); - gateway.submitBatch{value: 0.1 ether}(root, 0, 1); + gateway.submitBatch{value: 0.1 ether}(root, 0, 2); vm.stopBroadcast(); } } From a2b5a93bd3460bb8593bf08de511d94053f239d9 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 11:56:55 +0800 Subject: [PATCH 03/14] remove sort in MerkleTree --- crates/bmt/benches/tree_construction.rs | 4 +-- crates/bmt/src/lib.rs | 37 ++++++++++--------------- crates/calendar/src/routes/ots.rs | 4 +-- crates/cli/src/commands/stamp.rs | 4 +-- crates/stamper/src/lib.rs | 10 +++---- 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/crates/bmt/benches/tree_construction.rs b/crates/bmt/benches/tree_construction.rs index dadc595..6c89570 100644 --- a/crates/bmt/benches/tree_construction.rs +++ b/crates/bmt/benches/tree_construction.rs @@ -8,7 +8,7 @@ use digest::{Digest, FixedOutputReset, Output}; use sha2::Sha256; use sha3::Keccak256; use std::hint::black_box; -use uts_bmt::UnorderedMerkleTree; +use uts_bmt::MerkleTree; const INPUT_SIZES: &[usize] = &[8, 1024, 65536, 1_048_576]; @@ -34,7 +34,7 @@ where group.bench_function(BenchmarkId::new(id, size), move |b| { // Tree construction is the operation under test. b.iter(|| { - let tree = UnorderedMerkleTree::::new(black_box(leaves.as_slice())); + let tree = MerkleTree::::new(black_box(leaves.as_slice())); black_box(tree); }); }); diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index e9e385c..f35c147 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -12,10 +12,8 @@ pub const INNER_NODE_PREFIX: u8 = 0x01; /// Flat, Fixed-Size, Read only Merkle Tree /// /// Expects the length of leaves to be equal or near(less) to a power of two. -/// -/// Leaves are **sorted** starting at index `len`. #[derive(Debug, Clone, Default)] -pub struct UnorderedMerkleTree { +pub struct MerkleTree { /// Index 0 is not used, leaves start at index `len`. nodes: Box<[Output]>, len: usize, @@ -23,12 +21,12 @@ pub struct UnorderedMerkleTree { /// Merkle Tree without hashing the leaves #[derive(Debug, Clone)] -pub struct UnhashedFlatMerkleTree { +pub struct UnhashedMerkleTree { buffer: Vec>, len: usize, } -impl UnorderedMerkleTree +impl MerkleTree where Output: Pod + Copy, { @@ -38,7 +36,7 @@ where } /// Constructs a new Merkle tree from the given hash leaves, without hashing internal nodes. - pub fn new_unhashed(data: &[Output]) -> UnhashedFlatMerkleTree { + pub fn new_unhashed(data: &[Output]) -> UnhashedMerkleTree { let raw_len = data.len(); assert_ne!(raw_len, 0, "Cannot create Merkle tree with zero leaves"); @@ -67,14 +65,9 @@ where maybe_uninit .get_unchecked_mut(len + raw_len..) .write_filled(Output::::default()); - - maybe_uninit - .get_unchecked_mut(len..) - .assume_init_mut() - .sort_unstable(); } - UnhashedFlatMerkleTree { buffer: nodes, len } + UnhashedMerkleTree { buffer: nodes, len } } /// Returns the root hash of the Merkle tree @@ -93,12 +86,12 @@ where /// Checks if the given leaf is contained in the Merkle tree #[inline] pub fn contains(&self, leaf: &Output) -> bool { - self.leaves().binary_search(leaf).is_ok() + self.leaves().contains(leaf) } /// Get proof for a given leaf pub fn get_proof_iter(&self, leaf: &Output) -> Option> { - let leaf_index_in_slice = self.leaves().binary_search(leaf).ok()?; + let leaf_index_in_slice = self.leaves().iter().position(|a| a == leaf)?; Some(SiblingIter { nodes: &self.nodes, current: self.len + leaf_index_in_slice, @@ -123,12 +116,12 @@ where } } -impl UnhashedFlatMerkleTree +impl UnhashedMerkleTree where Output: Pod + Copy, { /// Finalizes the Merkle tree by hashing internal nodes - pub fn finalize(self) -> UnorderedMerkleTree { + pub fn finalize(self) -> MerkleTree { let mut nodes = self.buffer; let len = self.len; unsafe { @@ -152,7 +145,7 @@ where // SAFETY: initialized all elements. nodes.set_len(2 * len); } - UnorderedMerkleTree { + MerkleTree { nodes: nodes.into_boxed_slice(), len, } @@ -226,15 +219,14 @@ mod tests { where Output: Pod + Copy, { - let mut leaves = vec![ + let leaves = vec![ D::digest(b"leaf1"), D::digest(b"leaf2"), D::digest(b"leaf3"), D::digest(b"leaf4"), ]; - leaves.sort_unstable(); - let tree = UnorderedMerkleTree::::new(&leaves); + let tree = MerkleTree::::new(&leaves); // Manually compute the expected root let mut hasher = D::new(); @@ -260,15 +252,14 @@ mod tests { where Output: Pod + Copy, { - let mut leaves = vec![ + let leaves = vec![ D::digest(b"apple"), D::digest(b"banana"), D::digest(b"cherry"), D::digest(b"date"), ]; - leaves.sort_unstable(); - let tree = UnorderedMerkleTree::::new(&leaves); + let tree = MerkleTree::::new(&leaves); for leaf in &leaves { let mut iter = tree diff --git a/crates/calendar/src/routes/ots.rs b/crates/calendar/src/routes/ots.rs index 4a2ae98..59b427f 100644 --- a/crates/calendar/src/routes/ots.rs +++ b/crates/calendar/src/routes/ots.rs @@ -12,7 +12,7 @@ use bytes::BytesMut; use digest::Digest; use sha3::Keccak256; use std::{cell::RefCell, sync::Arc}; -use uts_bmt::UnorderedMerkleTree; +use uts_bmt::MerkleTree; use uts_core::{ codec::{ Encode, @@ -154,7 +154,7 @@ pub async fn get_timestamp( .load_entry(root) .expect("DB error") .expect("bug: entry not found"); - let trie: UnorderedMerkleTree = entry.trie(); + let trie: MerkleTree = entry.trie(); let proof = trie .get_proof_iter(bytemuck::cast_ref(&*commitment)) diff --git a/crates/cli/src/commands/stamp.rs b/crates/cli/src/commands/stamp.rs index e747ced..1b3ccbf 100644 --- a/crates/cli/src/commands/stamp.rs +++ b/crates/cli/src/commands/stamp.rs @@ -6,7 +6,7 @@ use futures::TryFutureExt; use std::{collections::HashMap, future::ready, io, path::PathBuf, sync::LazyLock, time::Duration}; use tokio::{fs, io::AsyncWriteExt}; use url::Url; -use uts_bmt::UnorderedMerkleTree; +use uts_bmt::MerkleTree; use uts_core::{ codec::{ Decode, Encode, VersionedProof, @@ -97,7 +97,7 @@ impl Stamp { }) .collect::>(); - let internal_tire = UnorderedMerkleTree::::new(&nonced_digest); + let internal_tire = MerkleTree::::new(&nonced_digest); let root = internal_tire.root(); eprintln!("Internal Merkle root: {}", Hexed(root)); diff --git a/crates/stamper/src/lib.rs b/crates/stamper/src/lib.rs index c66f0d2..247500a 100644 --- a/crates/stamper/src/lib.rs +++ b/crates/stamper/src/lib.rs @@ -20,7 +20,7 @@ use std::{ time::Duration, }; use tokio::time::{Interval, MissedTickBehavior}; -use uts_bmt::UnorderedMerkleTree; +use uts_bmt::MerkleTree; use uts_contracts::uts::UniversalTimestamps; use uts_core::utils::Hexed; use uts_journal::reader::JournalReader; @@ -42,7 +42,7 @@ pub struct Stamper { /// Storage for merkle trees and leaf->root mappings storage: Arc, /// FIFO cache of recent merkle trees - cache: VecDeque>, + cache: VecDeque>, /// FIFO cache index of recent merkle trees cache_index: HashMap, /// The contract @@ -81,13 +81,13 @@ pub struct MerkleEntry<'a> { impl MerkleEntry<'_> { /// Get the Merkle tree from the entry - pub fn trie(&self) -> UnorderedMerkleTree + pub fn trie(&self) -> MerkleTree where D: Digest + FixedOutputReset, Output: Pod + Copy, { // SAFETY: We trust that the data in the database is valid, and that the trie was serialized correctly. - unsafe { UnorderedMerkleTree::from_raw_bytes(&self.trie) } + unsafe { MerkleTree::from_raw_bytes(&self.trie) } } } @@ -237,7 +237,7 @@ where } debug_assert_eq!(buffer.len(), target_size); - let merkle_tree = UnorderedMerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); + let merkle_tree = MerkleTree::::new_unhashed(bytemuck::cast_slice(&buffer)); let storage = self.storage.clone(); let merkle_tree = tokio::task::spawn_blocking(move || { From 139599b5cbdc5447d26b46cccb4450a6ad5cc03d Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 13:07:04 +0800 Subject: [PATCH 04/14] minimize uts --- contract-tests/L1AnchoringManager.t.sol | 19 ++---- contract-tests/Storage.t.sol | 3 - contract-tests/Timestamps.t.sol | 36 +++++++++++ contracts/L1/IL1AnchoringGateway.sol | 15 ++++- contracts/L1/L1AnchoringGateway.sol | 52 +++++++++++++--- contracts/L2/manager/IL2AnchoringManager.sol | 10 +--- contracts/L2/manager/L2AnchoringManager.sol | 59 +++++++------------ .../L2/manager/L2AnchoringManagerStorage.sol | 6 +- contracts/L2/oracle/IL1FeeOracle.sol | 4 +- contracts/L2/oracle/L1FeeOracle.sol | 51 ++++++++-------- contracts/core/IUniversalTimestamps.sol | 33 ++++++++--- contracts/core/UniversalTimestamps.sol | 49 +++++---------- contracts/core/UniversalTimestampsStorage.sol | 26 -------- contracts/core/UniversalTimestampsTypes.sol | 11 ---- script/Deploy.s.sol | 10 +--- 15 files changed, 196 insertions(+), 188 deletions(-) create mode 100644 contract-tests/Timestamps.t.sol delete mode 100644 contracts/core/UniversalTimestampsStorage.sol delete mode 100644 contracts/core/UniversalTimestampsTypes.sol diff --git a/contract-tests/L1AnchoringManager.t.sol b/contract-tests/L1AnchoringManager.t.sol index b912db6..9dca168 100644 --- a/contract-tests/L1AnchoringManager.t.sol +++ b/contract-tests/L1AnchoringManager.t.sol @@ -9,7 +9,6 @@ import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.s import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; import {IUniversalTimestamps} from "../contracts/core/IUniversalTimestamps.sol"; import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; -import {AddressAliasHelper} from "scroll-contracts/libraries/common/AddressAliasHelper.sol"; contract MockL1FeeOracle is IL1FeeOracle { function getL1BaseFee() external view returns (uint256) { @@ -24,11 +23,11 @@ contract MockL1FeeOracle is IL1FeeOracle { return (0.05 gwei * 51_000 * 0.5e18) / 1e18; } - function gasPerAttestation() external view returns (uint256) { + function getGasPerAttestation() external view returns (uint256) { return 51_000; } - function discountRatio() external view returns (uint256) { + function getDiscountRatio() external view returns (uint256) { return 0.5e18; // 50% discount } } @@ -57,24 +56,18 @@ contract L2AnchoringManagerTest is Test { IL2AnchoringManager manager; IL2ScrollMessenger l2Messenger; - address constant L1_MESSENGER = address(0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A); address constant L1_GATEWAY = address(0x456); function setUp() public { - l2Messenger = new MockL2ScrollMessenger(); - UniversalTimestamps utsImpl = new UniversalTimestamps(); - ERC1967Proxy utsProxy = - new ERC1967Proxy(address(utsImpl), abi.encodeCall(UniversalTimestamps.initialize, (address(this)))); - uts = IUniversalTimestamps(address(utsProxy)); - + uts = new UniversalTimestamps(); feeOracle = new MockL1FeeOracle(); + l2Messenger = new MockL2ScrollMessenger(); L2AnchoringManager impl = new L2AnchoringManager(); ERC1967Proxy proxy = new ERC1967Proxy( address(impl), abi.encodeCall( - L2AnchoringManager.initialize, - (address(this), address(uts), address(feeOracle), L1_MESSENGER, address(l2Messenger)) + L2AnchoringManager.initialize, (address(this), address(uts), address(feeOracle), address(l2Messenger)) ) ); manager = IL2AnchoringManager(address(proxy)); @@ -98,7 +91,7 @@ contract L2AnchoringManagerTest is Test { assertFalse(confirmed, "Root should not be confirmed immediately after submission"); // Simulate a call from bridge to confirm the anchoring - vm.prank(AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER)); // Simulate a call from the L1 messenger + vm.prank(address(l2Messenger)); // Simulate a call from the L2 messenger vm.expectEmit(true, true, true, true); emit IL2AnchoringManager.L1AnchoringBatchConfirmed(root, 0, 1, block.number, block.number, block.timestamp); manager.confirmL1AnchoringBatch(root, 0, 1, block.number); diff --git a/contract-tests/Storage.t.sol b/contract-tests/Storage.t.sol index 556e663..78178b4 100644 --- a/contract-tests/Storage.t.sol +++ b/contract-tests/Storage.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.29; import {Test, console} from "forge-std/Test.sol"; import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; -import {UniversalTimestampsStorage} from "../contracts/core/UniversalTimestampsStorage.sol"; import {L1AnchoringGatewayStorage} from "../contracts/L1/L1AnchoringGatewayStorage.sol"; import {L2AnchoringManagerStorage} from "../contracts/L2/manager/L2AnchoringManagerStorage.sol"; @@ -15,8 +14,6 @@ contract StorageSlotTest is Test { using SlotDerivation for string; function test_ERC7201_SlotDerivation() public pure { - test(UniversalTimestampsStorage.SLOT, UniversalTimestampsStorage.NAMESPACE); - test(L1AnchoringGatewayStorage.SLOT, L1AnchoringGatewayStorage.NAMESPACE); test(L2AnchoringManagerStorage.SLOT, L2AnchoringManagerStorage.NAMESPACE); diff --git a/contract-tests/Timestamps.t.sol b/contract-tests/Timestamps.t.sol new file mode 100644 index 0000000..142c0a4 --- /dev/null +++ b/contract-tests/Timestamps.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +import {Test, console} from "forge-std/Test.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; +import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; + +/** + * @title UniversalTimestampsTest + * @dev Tests to verify the functionality of the UniversalTimestamps contract. + */ +contract UniversalTimestampsTest is Test { + UniversalTimestamps uts; + + function setUp() public { + uts = new UniversalTimestamps(); + console.log("UniversalTimestamps deployed at:", address(uts)); + } + + function test_AttestGasCost() public { + bytes32 data = keccak256("Test data for attestation"); + uint256 startGas = gasleft(); + uts.attest(data); + uint256 endGas = gasleft(); + // Adding base transaction cost + uint256 gasUsed = startGas - endGas + 21000; + console.log("Gas used for attest:", gasUsed); + + // Assert the L1FeeOracle default fee is roughly same. + L1FeeOracle feeOracle = new L1FeeOracle(address(this)); + uint256 gas = feeOracle.gasPerAttestation(); + console.log("Current gas per attestation from L1FeeOracle:", gas); + assertApproxEqAbs(gasUsed, gas, 5000); // Allow a margin of error of 5000 gas units + } +} diff --git a/contracts/L1/IL1AnchoringGateway.sol b/contracts/L1/IL1AnchoringGateway.sol index d461187..ba7bf9b 100644 --- a/contracts/L1/IL1AnchoringGateway.sol +++ b/contracts/L1/IL1AnchoringGateway.sol @@ -8,12 +8,25 @@ interface IL1AnchoringGateway { bytes32 indexed merkleRoot, uint256 indexed startIndex, uint256 count, address indexed submitter ); + /// @notice Emitted when the UniversalTimestamps contract address is updated. + event UTSUpdated(address indexed oldUts, address indexed newUts); + /// @notice Emitted when the L1 Scroll Messenger contract address is updated. + event L1ScrollMessengerUpdated(address indexed oldMessenger, address indexed newMessenger); + /// @notice Emitted when the L2 Anchoring Manager contract address is updated. + event L2AnchoringManagerUpdated(address indexed oldManager, address indexed newManager); + /** * @notice Submit a SINGLE aggregated Merkle Root to L1 and trigger L2 verification. * @param merkleRoot The root of the Merkle Tree containing all roots in this batch. * @param startIndex The queue index of the first root in this batch. * @param count The number of roots in this batch. + * @param gasLimit The gas limit for L2 execution of this batch. Caller should estimate the gas cost based on the batch size and current L2 gas price, and provide enough ETH to cover both L1 Gas and L2 Execution Gas. * @dev Caller must send enough ETH to cover L1 Gas + L2 Execution Gas (which is now higher due to loop). */ - function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count) external payable; + function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count, uint256 gasLimit) external payable; + + // -- Admin functions -- + function setUts(address newUts) external; + function setL1ScrollMessenger(address newMessenger) external; + function setL2AnchoringManager(address newManager) external; } diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index d712756..da07a64 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.29; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; import {L1AnchoringGatewayStorage} from "./L1AnchoringGatewayStorage.sol"; @@ -11,14 +10,19 @@ import {IL1AnchoringGateway} from "./IL1AnchoringGateway.sol"; import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; import {IL2AnchoringManager} from "../L2/manager/IL2AnchoringManager.sol"; import {IUniversalTimestamps} from "../core/IUniversalTimestamps.sol"; +import { + AccessControlDefaultAdminRulesUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; contract L1AnchoringGateway is Initializable, - OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardTransient, + AccessControlDefaultAdminRulesUpgradeable, IL1AnchoringGateway { + bytes32 public constant SUBMITTER_ROLE = keccak256("SUBMITTER_ROLE"); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -28,7 +32,7 @@ contract L1AnchoringGateway is public initializer { - __Ownable_init(initialOwner); + __AccessControlDefaultAdminRules_init(3 days, initialOwner); require(uts != address(0), "UTS: Invalid UniversalTimestamps address"); require(l1Messenger != address(0), "UTS: Invalid L1ScrollMessenger address"); @@ -38,10 +42,19 @@ contract L1AnchoringGateway is $.uts = IUniversalTimestamps(uts); $.l1Messenger = IL1ScrollMessenger(l1Messenger); $.l2AnchoringManager = IL2AnchoringManager(l2AnchoringManager); + + // Set up roles + grantRole(SUBMITTER_ROLE, initialOwner); + _setRoleAdmin(SUBMITTER_ROLE, DEFAULT_ADMIN_ROLE); } /// @inheritdoc IL1AnchoringGateway - function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count) external payable nonReentrant { + function submitBatch(bytes32 merkleRoot, uint256 startIndex, uint256 count, uint256 gasLimit) + external + payable + nonReentrant + onlyRole(SUBMITTER_ROLE) + { L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); require(address($.l1Messenger) != address(0), "UTS: L1 Scroll Messenger not set"); @@ -51,7 +64,7 @@ contract L1AnchoringGateway is try $.uts.attest(merkleRoot) { attestedBlockNumber = block.number; } catch { - attestedBlockNumber = $.uts.blockNumberOf(merkleRoot); + (attestedBlockNumber,) = $.uts.blockNumberOf(merkleRoot); require(attestedBlockNumber != 0, "UTS: Merkle root not attested on L1"); } @@ -63,12 +76,37 @@ contract L1AnchoringGateway is address($.l2AnchoringManager), 0, message, - 1_000_000, // TODO: Estimate proper gas limit + gasLimit, msg.sender // refund the caller for the gas cost of L2 execution ); emit BatchSubmitted(merkleRoot, startIndex, count, msg.sender); } - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + // -- Admin functions -- + function setUts(address newUts) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(newUts != address(0), "UTS: Invalid UniversalTimestamps address"); + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + address oldUts = address($.uts); + $.uts = IUniversalTimestamps(newUts); + emit UTSUpdated(oldUts, newUts); + } + + function setL1ScrollMessenger(address newMessenger) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(newMessenger != address(0), "UTS: Invalid L1 Scroll Messenger address"); + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + address oldMessenger = address($.l1Messenger); + $.l1Messenger = IL1ScrollMessenger(newMessenger); + emit L1ScrollMessengerUpdated(oldMessenger, newMessenger); + } + + function setL2AnchoringManager(address newManager) external onlyRole(DEFAULT_ADMIN_ROLE) { + require(newManager != address(0), "UTS: Invalid L2 Anchoring Manager address"); + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + address oldManager = address($.l2AnchoringManager); + $.l2AnchoringManager = IL2AnchoringManager(newManager); + emit L2AnchoringManagerUpdated(oldManager, newManager); + } + + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} } diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index be22f49..024f0a3 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -19,15 +19,13 @@ interface IL2AnchoringManager { ); /// @notice Emitted when fee parameters are updated. - event FeeParametersUpdated(address indexed feeOracle, address indexed feeCollector); + event FeeOracleUpdated(address indexed oldOracle, address indexed newOracle); /// @notice Emitted when fees are withdrawn by the fee collector. event FeesWithdrawn(address indexed to, uint256 amount); /// @notice Emitted when the L1 Gateway address is updated. - event L1GatewayUpdated(address indexed l1Gateway); - /// @notice Emitted when the L1 Messenger address is updated. - event L1MessengerUpdated(address indexed l1Messenger); + event L1GatewayUpdated(address indexed oldGateway, address indexed newGateway); /// @notice Emitted when the L2 Messenger address is updated. - event L2MessengerUpdated(address indexed l2Messenger); + event L2MessengerUpdated(address indexed oldMessenger, address indexed newMessenger); /** * @notice Submit a root for L2 timestamping + L1 anchoring. @@ -55,9 +53,7 @@ interface IL2AnchoringManager { // --- Admin Functions --- function setFeeOracle(address oracle) external; - function setFeeCollector(address collector) external; function setL1Gateway(address l1Gateway) external; - function setL1Messenger(address l1Messenger) external; function setL2Messenger(address l2Messenger) external; /** diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 03307f0..660a5e5 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.29; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; import {IL2AnchoringManager} from "./IL2AnchoringManager.sol"; @@ -13,35 +12,38 @@ import {L2AnchoringManagerTypes} from "./L2AnchoringManagerTypes.sol"; import {MerkleTree} from "../../core/MerkleTree.sol"; import {IUniversalTimestamps} from "../../core/IUniversalTimestamps.sol"; import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; -import {AddressAliasHelper} from "scroll-contracts/libraries/common/AddressAliasHelper.sol"; import {ScrollConstants} from "scroll-contracts/libraries/constants/ScrollConstants.sol"; +import { + AccessControlDefaultAdminRulesUpgradeable +} from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; contract L2AnchoringManager is Initializable, - OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardTransient, + AccessControlDefaultAdminRulesUpgradeable, IL2AnchoringManager { + bytes32 public constant FEE_COLLECTOR_ROLE = keccak256("FEE_COLLECTOR_ROLE"); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } - function initialize(address initialOwner, address uts, address feeOracle, address l1Messenger, address l2Messenger) - public - initializer - { - __Ownable_init(initialOwner); + function initialize(address initialOwner, address uts, address feeOracle, address l2Messenger) public initializer { + __AccessControlDefaultAdminRules_init(3 days, initialOwner); require(feeOracle != address(0), "UTS: Invalid FeeOracle address"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); $.uts = IUniversalTimestamps(uts); $.feeOracle = IL1FeeOracle(feeOracle); - $.l1Messenger = l1Messenger; $.l2Messenger = IL2ScrollMessenger(l2Messenger); - $.feeCollector = initialOwner; + + // Set up roles + grantRole(FEE_COLLECTOR_ROLE, initialOwner); + _setRoleAdmin(FEE_COLLECTOR_ROLE, DEFAULT_ADMIN_ROLE); } /// @inheritdoc IL2AnchoringManager @@ -101,35 +103,23 @@ contract L2AnchoringManager is // --- Admin Functions --- - function setFeeOracle(address _oracle) external onlyOwner { + function setFeeOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { require(address(_oracle) != address(0), "UTS: Invalid Oracle"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + address oldOracle = address($.feeOracle); $.feeOracle = IL1FeeOracle(_oracle); - emit FeeParametersUpdated(_oracle, $.feeCollector); - } - - function setFeeCollector(address collector) external onlyOwner { - require(collector != address(0), "UTS: Invalid Collector"); - L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); - $.feeCollector = collector; - emit FeeParametersUpdated(address($.feeOracle), collector); + emit FeeOracleUpdated(oldOracle, _oracle); } - function setL1Gateway(address l1Gateway) external onlyOwner { + function setL1Gateway(address l1Gateway) external onlyRole(DEFAULT_ADMIN_ROLE) { require(l1Gateway != address(0), "UTS: Invalid L1 Gateway address"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + address oldGateway = $.l1Gateway; $.l1Gateway = l1Gateway; - emit L1GatewayUpdated(l1Gateway); + emit L1GatewayUpdated(oldGateway, l1Gateway); } - function setL1Messenger(address l1Messenger) external onlyOwner { - require(l1Messenger != address(0), "UTS: Invalid L1 Messenger address"); - L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); - $.l1Messenger = l1Messenger; - emit L1MessengerUpdated(l1Messenger); - } - - function setL2Messenger(address l2Messenger) external onlyOwner { + function setL2Messenger(address l2Messenger) external onlyRole(DEFAULT_ADMIN_ROLE) { require(l2Messenger != address(0), "UTS: Invalid L2 Messenger address"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); IL2ScrollMessenger messenger = IL2ScrollMessenger(l2Messenger); @@ -139,16 +129,11 @@ contract L2AnchoringManager is "UTS: Invalid L2 Messenger" ); $.l2Messenger = messenger; - emit L2MessengerUpdated(l2Messenger); + emit L2MessengerUpdated(address($.l2Messenger), l2Messenger); } /// @inheritdoc IL2AnchoringManager - function withdrawFees(address to, uint256 amount) external nonReentrant { - L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); - - // Security: Allow either Owner or the designated Collector to withdraw - require(msg.sender == owner() || msg.sender == $.feeCollector, "UTS: Unauthorized"); - + function withdrawFees(address to, uint256 amount) external nonReentrant onlyRole(FEE_COLLECTOR_ROLE) { require(to != address(0), "UTS: Invalid address"); require(amount > 0 && amount <= address(this).balance, "UTS: Invalid amount"); @@ -158,7 +143,7 @@ contract L2AnchoringManager is emit FeesWithdrawn(to, amount); } - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} receive() external payable {} } diff --git a/contracts/L2/manager/L2AnchoringManagerStorage.sol b/contracts/L2/manager/L2AnchoringManagerStorage.sol index b67f4c9..e24039c 100644 --- a/contracts/L2/manager/L2AnchoringManagerStorage.sol +++ b/contracts/L2/manager/L2AnchoringManagerStorage.sol @@ -14,20 +14,16 @@ library L2AnchoringManagerStorage { string internal constant NAMESPACE = "uts.storage.L2AnchoringManager"; /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.L2AnchoringManager")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 internal constant SLOT = 0x9831cc7956aa6e272a6b3f7bd193bca727880ec1ca574ef61afc1d64fc9e5000; + bytes32 internal constant SLOT = 0x5accfd2b2bcf275f7d10bb4569421f50f846511017720654fefc7e6d91daf100; /// @custom:storage-location erc7201:uts.storage.L2AnchoringManager struct Storage { IUniversalTimestamps uts; IL1FeeOracle feeOracle; - /// @notice L1 contract that sends messages to this manager on L2 - address l1Messenger; /// @notice Executor for L1 -> L2 messages IL2ScrollMessenger l2Messenger; /// @notice L1 sender address address l1Gateway; - /// @notice Address that collects the fees - address feeCollector; /// @notice Queue index for the next anchoring item to be added uint256 queueIndex; /// @notice Next index of the anchoring item to be confirmed diff --git a/contracts/L2/oracle/IL1FeeOracle.sol b/contracts/L2/oracle/IL1FeeOracle.sol index ec67b0a..5795460 100644 --- a/contracts/L2/oracle/IL1FeeOracle.sol +++ b/contracts/L2/oracle/IL1FeeOracle.sol @@ -30,10 +30,10 @@ interface IL1FeeOracle { /** * @notice Return the current gas consumed on L1 per attestation. */ - function gasPerAttestation() external view returns (uint256); + function getGasPerAttestation() external view returns (uint256); /** * @notice Return the current discount ratio applied to the baseline fee. */ - function discountRatio() external view returns (uint256); + function getDiscountRatio() external view returns (uint256); } diff --git a/contracts/L2/oracle/L1FeeOracle.sol b/contracts/L2/oracle/L1FeeOracle.sol index 9db42b3..4346306 100644 --- a/contracts/L2/oracle/L1FeeOracle.sol +++ b/contracts/L2/oracle/L1FeeOracle.sol @@ -1,45 +1,44 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; import {IL1FeeOracle} from "./IL1FeeOracle.sol"; +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; /** * @title L1FeeOracle * @dev Calculates the fee required for L1 anchoring based on dynamic L1 gas prices - * and an aggregation discount ratio (< 1.0). + * and an aggregation discount ratio (<= 1.0). * * Formula: UserFee = (L1_Base_Fee * GasPerAttestation * DiscountRatio) / 1e18 * * Semantic: * - _gasPerAttestation: The theoretical gas cost if a user submitted independently to L1 (e.g., 50,000). - * - _discountRatio: The fraction (< 1.0) representing the aggregated share + protocol margin (e.g., 0.005e18). + * - _discountRatio: The fraction (<= 1.0) representing the aggregated share + protocol margin (e.g., 0.005e18). */ -contract L1FeeOracle is IL1FeeOracle, Ownable { +contract L1FeeOracle is IL1FeeOracle, AccessControlDefaultAdminRules { + // Predeploy contract on Scroll that provides the current L1 base fee. IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = IL1GasPriceOracle(0x5300000000000000000000000000000000000002); - // Estimated gas consumed on L1 per attestation (in a batch) - uint256 private _gasPerAttestation; - // Discount: The ratio (< 1.0) representing the aggregated share + protocol margin. + bytes32 public constant UPDATER_ROLE = keccak256("UPDATER_ROLE"); + + // Estimated gas consumed on L1 per attestation if submitted independently (without aggregation). + uint256 public gasPerAttestation = 75_000; + // Discount: The ratio (<= 1.0) representing the aggregated share + protocol margin. // Scaled by 1e18. // Example: 0.005e18 means user pays 0.5% of the baseline gas cost. // This value implicitly covers the actual batch share + server profit margin. - uint256 private _discountRatio; + uint256 public discountRatio = 1e18; // Default to no discount /** * @param initialOwner The owner of this oracle contract. - * @param initialGasPerAttestation Initial estimate of gas units consumed on L1 per attestation. - * @param initialDiscountRatio Initial discount ratio (scaled by 1e18) to apply to the baseline fee calculation. */ - constructor(address initialOwner, uint256 initialGasPerAttestation, uint256 initialDiscountRatio) - Ownable(initialOwner) - { - require(initialDiscountRatio > 0, "L1FeeOracle: Ratio must be positive"); - - _gasPerAttestation = initialGasPerAttestation; - _discountRatio = initialDiscountRatio; + constructor(address initialOwner) AccessControlDefaultAdminRules(3 days, initialOwner) { + _setRoleAdmin(UPDATER_ROLE, DEFAULT_ADMIN_ROLE); + grantRole(UPDATER_ROLE, initialOwner); } /** @@ -47,12 +46,12 @@ contract L1FeeOracle is IL1FeeOracle, Ownable { * @param newGasPerAttestation Baseline gas for a single independent tx. * @param newDiscountRatio The aggregation ratio (must be <= 1e18). */ - function setParameters(uint256 newGasPerAttestation, uint256 newDiscountRatio) external onlyOwner { + function setParameters(uint256 newGasPerAttestation, uint256 newDiscountRatio) external onlyRole(UPDATER_ROLE) { require(newGasPerAttestation > 0, "L1FeeOracle: Gas must be positive"); require(newDiscountRatio > 0 && newDiscountRatio <= 1e18, "L1FeeOracle: Ratio must be between 0 and 1.0"); - _gasPerAttestation = newGasPerAttestation; - _discountRatio = newDiscountRatio; + gasPerAttestation = newGasPerAttestation; + discountRatio = newDiscountRatio; emit ParametersUpdated(newGasPerAttestation, newDiscountRatio); } @@ -65,23 +64,23 @@ contract L1FeeOracle is IL1FeeOracle, Ownable { /// @inheritdoc IL1FeeOracle function getFeePerAttestation() external view returns (uint256) { uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); - return (l1BaseFee * _gasPerAttestation) / 1e18; + return (l1BaseFee * gasPerAttestation) / 1e18; } /// @inheritdoc IL1FeeOracle function getFloorFee() external view returns (uint256) { uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); // Calculate the fee with discount applied - return (l1BaseFee * _gasPerAttestation * _discountRatio) / 1e18; + return (l1BaseFee * gasPerAttestation * discountRatio) / 1e18; } /// @inheritdoc IL1FeeOracle - function gasPerAttestation() external view returns (uint256) { - return _gasPerAttestation; + function getGasPerAttestation() external view returns (uint256) { + return gasPerAttestation; } /// @inheritdoc IL1FeeOracle - function discountRatio() external view returns (uint256) { - return _discountRatio; + function getDiscountRatio() external view returns (uint256) { + return discountRatio; } } diff --git a/contracts/core/IUniversalTimestamps.sol b/contracts/core/IUniversalTimestamps.sol index 75fd00b..3ba67a7 100644 --- a/contracts/core/IUniversalTimestamps.sol +++ b/contracts/core/IUniversalTimestamps.sol @@ -2,21 +2,36 @@ pragma solidity ^0.8.29; -import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; - interface IUniversalTimestamps { + /// @notice Attestation struct to hold timestamp and block number for each attested root + struct Attestation { + uint256 timestamp; + uint256 blockNumber; + } + /// @notice Emitted when a new Merkle root is attested with its timestamp and block number event Attested(bytes32 indexed root, address indexed attester, uint256 timestamp, uint256 blockNumber); - /// @notice Returns the timestamp associated with a given Merkle root. Reuturns 0 if the root has not been attested. - function timestamp(bytes32 root) external view returns (uint256); + /// @notice Returns the timestamp associated with a given Merkle root. + /// @param root The Merkle root for which to retrieve the timestamp. + /// @return The timestamp at which the root was attested, or 0 if the root has not been attested. + /// @return A boolean indicating whether the root has been attested. + function timestamp(bytes32 root) external view returns (uint256, bool); - /// @notice Returns the block number associated with a given Merkle root. Returns 0 if the root has not been attested. - function blockNumberOf(bytes32 root) external view returns (uint256); + /// @notice Returns the block number associated with a given Merkle root. + /// @param root The Merkle root for which to retrieve the block number. + /// @return The block number at which the root was attested, or 0 if the root has not been attested. + /// @return A boolean indicating whether the root has been attested. + function blockNumberOf(bytes32 root) external view returns (uint256, bool); - /// @notice Returns the full attestation (timestamp and block number) for a given Merkle root. Returns default values if the root has not been attested. - function getAttestation(bytes32 root) external view returns (UniversalTimestampsTypes.Attestation memory); + /// @notice Returns the full attestation (timestamp and block number) for a given Merkle root. + /// @param root The Merkle root for which to retrieve the attestation. + /// @return The attestation for the given root, or default values if the root has not been attested. + /// @return A boolean indicating whether the root has been attested. + function getAttestation(bytes32 root) external view returns (Attestation memory, bool); /// @notice Attests a new Merkle root with the current timestamp and block number. - function attest(bytes32 root) external; + /// @param root The Merkle root to be attested. + /// @return The attestation for the newly attested root. + function attest(bytes32 root) external returns (Attestation memory); } diff --git a/contracts/core/UniversalTimestamps.sol b/contracts/core/UniversalTimestamps.sol index 251e405..e2bac58 100644 --- a/contracts/core/UniversalTimestamps.sol +++ b/contracts/core/UniversalTimestamps.sol @@ -2,57 +2,40 @@ pragma solidity ^0.8.29; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {IUniversalTimestamps} from "./IUniversalTimestamps.sol"; -import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; -import {UniversalTimestampsStorage} from "./UniversalTimestampsStorage.sol"; /** * @title UniversalTimestamps */ -contract UniversalTimestamps is Initializable, OwnableUpgradeable, UUPSUpgradeable, IUniversalTimestamps { - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - function initialize(address initialOwner) public initializer { - __Ownable_init(initialOwner); - } +contract UniversalTimestamps is IUniversalTimestamps { + mapping(bytes32 => Attestation) timestamps; - function timestamp(bytes32 root) external view returns (uint256) { - return UniversalTimestampsStorage.get().timestamps[root].timestamp; + function timestamp(bytes32 root) external view returns (uint256, bool) { + Attestation memory attestation = timestamps[root]; + return (attestation.timestamp, attestation.timestamp != 0); } - function blockNumberOf(bytes32 root) external view returns (uint256) { - return UniversalTimestampsStorage.get().timestamps[root].blockNumber; + function blockNumberOf(bytes32 root) external view returns (uint256, bool) { + Attestation memory attestation = timestamps[root]; + return (attestation.blockNumber, attestation.timestamp != 0); } - function getAttestation(bytes32 root) external view returns (UniversalTimestampsTypes.Attestation memory) { - return UniversalTimestampsStorage.get().timestamps[root]; + function getAttestation(bytes32 root) external view returns (Attestation memory, bool) { + Attestation memory attestation = timestamps[root]; + return (attestation, attestation.timestamp != 0); } /** * @notice Attest Merkle Root * @param root The Merkle Root to be attested + * @return The attestation for the newly attested root. */ - function attest(bytes32 root) external { + function attest(bytes32 root) external returns (Attestation memory) { require(root != bytes32(0), "UTS: Root cannot be zero"); + require(timestamps[root].timestamp == 0, "UTS: Root already attested"); - UniversalTimestampsStorage.Storage storage $ = UniversalTimestampsStorage.get(); - require($.timestamps[root].timestamp == 0, "UTS: Root already attested"); - $.timestamps[root] = - UniversalTimestampsTypes.Attestation({timestamp: block.timestamp, blockNumber: block.number}); + timestamps[root] = Attestation({timestamp: block.timestamp, blockNumber: block.number}); emit Attested(root, msg.sender, block.timestamp, block.number); + return timestamps[root]; } - - /** - * @dev Authorizes an upgrade to `newImplementation`. - * - * This function is restricted to the contract owner via the {onlyOwner} modifier, - * ensuring that only the owner can authorize upgrades to the implementation. - */ - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} } diff --git a/contracts/core/UniversalTimestampsStorage.sol b/contracts/core/UniversalTimestampsStorage.sol deleted file mode 100644 index 04ef088..0000000 --- a/contracts/core/UniversalTimestampsStorage.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.29; - -import {UniversalTimestampsTypes} from "./UniversalTimestampsTypes.sol"; - -/** - * @dev Library containing the ERC-7201 namespace constant. - * This keeps the implementation detail hidden from the interface. - */ -library UniversalTimestampsStorage { - string internal constant NAMESPACE = "uts.storage.UniversalTimestamps"; - - /// @dev keccak256(abi.encode(uint256(keccak256("uts.storage.UniversalTimestamps")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 internal constant SLOT = 0x500a69046951d8ea21a7dabf6fe6e1792e3ffa4dc61a276ae65b0e2b03468100; - - /// @custom:storage-location erc7201:uts.storage.UniversalTimestamps - struct Storage { - mapping(bytes32 => UniversalTimestampsTypes.Attestation) timestamps; - } - - function get() internal pure returns (UniversalTimestampsStorage.Storage storage $) { - assembly ("memory-safe") { - $.slot := SLOT - } - } -} diff --git a/contracts/core/UniversalTimestampsTypes.sol b/contracts/core/UniversalTimestampsTypes.sol deleted file mode 100644 index 759e914..0000000 --- a/contracts/core/UniversalTimestampsTypes.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.29; - -library UniversalTimestampsTypes { - /// @notice Attestation struct to hold timestamp and block number for each attested root - struct Attestation { - uint256 timestamp; - uint256 blockNumber; - } -} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 3707d3c..49bb463 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -17,15 +17,9 @@ contract DeployTimestampCreate2 is Script { address owner = vm.envAddress("OWNER_ADDRESS"); vm.startBroadcast(); - UniversalTimestamps implementation = new UniversalTimestamps{salt: SALT}(); - console.log("Implementation deployed at:", address(implementation)); - - bytes memory initData = abi.encodeCall(UniversalTimestamps.initialize, (owner)); - - ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); + UniversalTimestamps uts = new UniversalTimestamps{salt: SALT}(); vm.stopBroadcast(); - - console.log("Proxy deployed at:", address(proxy)); + console.log("UTS deployed at:", address(uts)); } } From 1d316ab5235328d374d2892d20c13e8d3f18901e Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 13:21:36 +0800 Subject: [PATCH 05/14] add refund --- contract-tests/L1AnchoringManager.t.sol | 2 +- contract-tests/Timestamps.t.sol | 1 - contracts/L2/manager/IL2AnchoringManager.sol | 4 +++- contracts/L2/manager/L2AnchoringManager.sol | 11 ++++++++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/contract-tests/L1AnchoringManager.t.sol b/contract-tests/L1AnchoringManager.t.sol index 9dca168..d06cb44 100644 --- a/contract-tests/L1AnchoringManager.t.sol +++ b/contract-tests/L1AnchoringManager.t.sol @@ -84,7 +84,7 @@ contract L2AnchoringManagerTest is Test { vm.deal(address(1), 100 ether); // Fund the address with some ether to pay for the fee vm.expectEmit(true, true, true, true); emit IL2AnchoringManager.L1AnchoringQueued(root, 0, fee, block.number, block.timestamp); - manager.submitForL1Anchoring{value: fee}(root); + manager.submitForL1Anchoring{value: fee}(root, address(1)); // Verify that the item was added to the queue bool confirmed = manager.isConfirmed(root); diff --git a/contract-tests/Timestamps.t.sol b/contract-tests/Timestamps.t.sol index 142c0a4..dc74bfa 100644 --- a/contract-tests/Timestamps.t.sol +++ b/contract-tests/Timestamps.t.sol @@ -2,7 +2,6 @@ pragma solidity ^0.8.29; import {Test, console} from "forge-std/Test.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index 024f0a3..15c9d63 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -29,9 +29,11 @@ interface IL2AnchoringManager { /** * @notice Submit a root for L2 timestamping + L1 anchoring. + * @param root The Merkle root to be anchored on L1. + * @param refundAddress The address to refund any excess fee after covering the required fee for L1 anchoring. This allows users to get a refund if they overpay. * @dev Requires msg.value >= Oracle calculated fee. */ - function submitForL1Anchoring(bytes32 root) external payable; + function submitForL1Anchoring(bytes32 root, address refundAddress) external payable; /** * @notice Check if a root has been confirmed as anchored on L1. diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 660a5e5..99dbe37 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -47,7 +47,7 @@ contract L2AnchoringManager is } /// @inheritdoc IL2AnchoringManager - function submitForL1Anchoring(bytes32 root) external payable nonReentrant { + function submitForL1Anchoring(bytes32 root, address refundAddress) external payable nonReentrant { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); require(address($.feeOracle) != address(0), "UTS: Oracle not set"); @@ -63,6 +63,15 @@ contract L2AnchoringManager is $.roots[root] = currentIndex; emit L1AnchoringQueued(root, currentIndex, msg.value, block.number, block.timestamp); + + // refund fee to `refundAddress` + unchecked { + uint256 _refund = msg.value - requiredFee; + if (_refund > 0) { + (bool _success,) = refundAddress.call{value: _refund}(""); + require(_success, "Failed to refund the fee"); + } + } } /// @inheritdoc IL2AnchoringManager From 0131a54d34d11b2248fea39c98a93693deafdfbe Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 13:31:18 +0800 Subject: [PATCH 06/14] apply reviews and fix --- ...Manager.t.sol => L2AnchoringManager.t.sol} | 0 contracts/L2/manager/L2AnchoringManager.sol | 4 +- contracts/core/MerkleTree.sol | 43 +++---------------- 3 files changed, 8 insertions(+), 39 deletions(-) rename contract-tests/{L1AnchoringManager.t.sol => L2AnchoringManager.t.sol} (100%) diff --git a/contract-tests/L1AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol similarity index 100% rename from contract-tests/L1AnchoringManager.t.sol rename to contract-tests/L2AnchoringManager.t.sol diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 99dbe37..41042fd 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -39,6 +39,8 @@ contract L2AnchoringManager is L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); $.uts = IUniversalTimestamps(uts); $.feeOracle = IL1FeeOracle(feeOracle); + // Start from 1 to use 0 as a sentinel value + $.queueIndex = 1; $.l2Messenger = IL2ScrollMessenger(l2Messenger); // Set up roles @@ -78,7 +80,7 @@ contract L2AnchoringManager is function isConfirmed(bytes32 root) external view returns (bool) { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); uint256 index = $.roots[root]; - return index < $.confirmedIndex; + return index != 0 && index < $.confirmedIndex; } /// @inheritdoc IL2AnchoringManager diff --git a/contracts/core/MerkleTree.sol b/contracts/core/MerkleTree.sol index 8277485..7613e5e 100644 --- a/contracts/core/MerkleTree.sol +++ b/contracts/core/MerkleTree.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.29; */ library MerkleTree { /// @dev Prefix byte to distinguish internal nodes from leaves (matches Rust INNER_NODE_PREFIX) - bytes32 private constant INNER_NODE_PREFIX = 0x0100000000000000000000000000000000000000000000000000000000000000; + bytes1 private constant INNER_NODE_PREFIX = 0x01; /// @dev Empty leaf used for padding to power of two bytes32 private constant EMPTY_LEAF = 0x0000000000000000000000000000000000000000000000000000000000000000; @@ -92,47 +92,14 @@ library MerkleTree { * Matches Rust: Digest::update(&mut hasher, [INNER_NODE_PREFIX]); ... */ function _hashNode(bytes32 left, bytes32 right) private pure returns (bytes32) { - assembly ("memory-safe") { - let ptr := mload(0x40) // Free memory pointer - - // Store prefix (0x01) at the start of the 32-byte slot - mstore(ptr, INNER_NODE_PREFIX) - // Store left hash immediately after (offset 32) - mstore(add(ptr, 32), left) - // Store right hash immediately after (offset 64) - mstore(add(ptr, 64), right) - - // Hash 96 bytes (3 words): prefix + left + right - let hash := keccak256(ptr, 96) - - mstore(0x40, add(ptr, 96)) // Update free memory pointer (optional in pure view, but good practice) - - // Return result via stack - mstore(0x00, hash) // Store temporarily to return? No, assembly returns via stack variable - // Actually, in inline assembly within a function returning bytes32, we just assign to the variable - // But here we are in a private pure function returning bytes32. - // The standard way is to let solidity handle the return, or use mstore(0, hash) and load. - // Let's stick to high-level return for safety, but use assembly for the keccak. - // Correction: To return from assembly block, we need to assign to a variable defined outside - // or use `result := ...` syntax if defined in `returns`. - - // Re-writing slightly cleaner: - pop(0) // Placeholder to satisfy compiler if needed, but let's just use standard return logic below - } - // Fallback to high-level if assembly feels too risky for specific compiler versions, - // but the assembly above is standard. Let's provide the clean assembly return version. - // Clean Assembly Implementation for _hashNode bytes32 result; assembly ("memory-safe") { let ptr := mload(0x40) - mstore(ptr, INNER_NODE_PREFIX) - mstore(add(ptr, 32), left) - mstore(add(ptr, 64), right) - result := keccak256(ptr, 96) - // We don't strictly need to update 0x40 in a pure function that doesn't allocate further, - // but it's safe to do so. - mstore(0x40, add(ptr, 96)) + mstore8(ptr, INNER_NODE_PREFIX) + mstore(add(ptr, 1), left) + mstore(add(ptr, 33), right) + result := keccak256(ptr, 65) } return result; } From cf47bbb0979b162a4a0e1ddaa571364d7d8e3859 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 15:32:33 +0800 Subject: [PATCH 07/14] add tests and fix --- Cargo.lock | 2 + contract-tests/L2AnchoringManager.t.sol | 111 +++++++++++++++++++- contract-tests/MerkleTree.sol | 47 +++++++++ contracts/L2/manager/L2AnchoringManager.sol | 1 + contracts/core/MerkleTree.sol | 66 +++++------- crates/bmt/Cargo.toml | 2 + crates/bmt/src/lib.rs | 30 +++++- 7 files changed, 214 insertions(+), 45 deletions(-) create mode 100644 contract-tests/MerkleTree.sol diff --git a/Cargo.lock b/Cargo.lock index 3a81e53..ba3b4a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6265,6 +6265,8 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" name = "uts-bmt" version = "0.1.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "bytemuck", "commonware-cryptography", "commonware-storage", diff --git a/contract-tests/L2AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol index d06cb44..c4f7626 100644 --- a/contract-tests/L2AnchoringManager.t.sol +++ b/contract-tests/L2AnchoringManager.t.sol @@ -74,7 +74,7 @@ contract L2AnchoringManagerTest is Test { manager.setL1Gateway(L1_GATEWAY); } - function test() public { + function test_Basic() public { bytes32 root = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; uint256 fee = feeOracle.getFloorFee(); console.log("Current floor fee:", fee); @@ -83,7 +83,7 @@ contract L2AnchoringManagerTest is Test { vm.prank(address(1)); // Simulate a call from an external address vm.deal(address(1), 100 ether); // Fund the address with some ether to pay for the fee vm.expectEmit(true, true, true, true); - emit IL2AnchoringManager.L1AnchoringQueued(root, 0, fee, block.number, block.timestamp); + emit IL2AnchoringManager.L1AnchoringQueued(root, 1, fee, block.number, block.timestamp); manager.submitForL1Anchoring{value: fee}(root, address(1)); // Verify that the item was added to the queue @@ -93,7 +93,110 @@ contract L2AnchoringManagerTest is Test { // Simulate a call from bridge to confirm the anchoring vm.prank(address(l2Messenger)); // Simulate a call from the L2 messenger vm.expectEmit(true, true, true, true); - emit IL2AnchoringManager.L1AnchoringBatchConfirmed(root, 0, 1, block.number, block.number, block.timestamp); - manager.confirmL1AnchoringBatch(root, 0, 1, block.number); + emit IL2AnchoringManager.L1AnchoringBatchConfirmed(root, 1, 1, block.number, block.number, block.timestamp); + manager.confirmL1AnchoringBatch(root, 1, 1, block.number); + } + + function test_NonExistentRoot() public view { + bytes32 root = 0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcd; + bool confirmed = manager.isConfirmed(root); + assertFalse(confirmed, "Non-existent root should not be confirmed"); + } +} + +contract L2AnchoringManagerGasTest is Test { + IUniversalTimestamps uts; + IL1FeeOracle feeOracle; + IL2AnchoringManager manager; + IL2ScrollMessenger l2Messenger; + + address constant L1_GATEWAY = address(0x456); + + function setUp() public { + uts = new UniversalTimestamps(); + feeOracle = new MockL1FeeOracle(); + l2Messenger = new MockL2ScrollMessenger(); + + L2AnchoringManager impl = new L2AnchoringManager(); + ERC1967Proxy proxy = new ERC1967Proxy( + address(impl), + abi.encodeCall( + L2AnchoringManager.initialize, (address(this), address(uts), address(feeOracle), address(l2Messenger)) + ) + ); + manager = IL2AnchoringManager(address(proxy)); + manager.setL1Gateway(L1_GATEWAY); + + // fill 1024 roots + vm.deal(address(1), 100 ether); + uint256 fee = feeOracle.getFloorFee(); + for (uint256 i = 0; i < 1024; i++) { + bytes32 root = keccak256(abi.encodePacked(i)); + vm.prank(address(1)); + manager.submitForL1Anchoring{value: fee}(root, address(1)); + } + } + + function confirmBatchGas(bytes32 expectedRoot, uint256 startIndex, uint256 count) private { + vm.prank(address(l2Messenger)); + uint256 startGas = gasleft(); + manager.confirmL1AnchoringBatch(expectedRoot, startIndex, count, block.number); + uint256 gasUsed = startGas - gasleft(); + console.log("Gas used for confirming a batch of", count, "roots:", gasUsed); + } + + function test_ConfirmBatchGas_1() public { + bytes32 expectedRoot = bytes32(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563); + confirmBatchGas(expectedRoot, 1, 1); + } + + function test_ConfirmBatchGas_2() public { + bytes32 expectedRoot = bytes32(0x05cca086ac1292d712fa72c1b3f12ca115644d2961f946ba815d8d731f9e5059); + confirmBatchGas(expectedRoot, 1, 2); + } + + function test_ConfirmBatchGas_4() public { + bytes32 expectedRoot = bytes32(0x0a9951a6344d06e27cd299dad49803dfb69d0009bca6dd3aa073a6e9dcfd7aa7); + confirmBatchGas(expectedRoot, 1, 4); + } + + function test_ConfirmBatchGas_8() public { + bytes32 expectedRoot = bytes32(0xc1df1e1138de013d39e21ddf1b1ab0dd91028c8d002c83be7b1fcdfc68035b6d); + confirmBatchGas(expectedRoot, 1, 8); + } + + function test_ConfirmBatchGas_16() public { + bytes32 expectedRoot = bytes32(0xf1c740406f2ba80a76a186c2c0e76282812958712624855b1c9411dfc9a6792c); + confirmBatchGas(expectedRoot, 1, 16); + } + + function test_ConfirmBatchGas_32() public { + bytes32 expectedRoot = bytes32(0xee50fb68d594b2edce57c20f37f15319dbe5726b7a8ca77397e0ea34222460f3); + confirmBatchGas(expectedRoot, 1, 32); + } + + function test_ConfirmBatchGas_64() public { + bytes32 expectedRoot = bytes32(0xf74fe0dade4345ea10ff784b4ff5989fc98d352bc2c771c75532915a3fe4088c); + confirmBatchGas(expectedRoot, 1, 64); + } + + function test_ConfirmBatchGas_128() public { + bytes32 expectedRoot = bytes32(0x679299f8934b50bb98d4801909db25cb0328bc553ecf89be4203e4268822c892); + confirmBatchGas(expectedRoot, 1, 128); + } + + function test_ConfirmBatchGas_256() public { + bytes32 expectedRoot = bytes32(0x21ba7c62f00c063ebb82e15aa3f706dc15371e1716390f820adc4a15d58358de); + confirmBatchGas(expectedRoot, 1, 256); + } + + function test_ConfirmBatchGas_512() public { + bytes32 expectedRoot = bytes32(0xc8f2d024989f2ec0438755b10bed2cd22998585c2a87f16661b73ea62e604d4e); + confirmBatchGas(expectedRoot, 1, 512); + } + + function test_ConfirmBatchGas_1024() public { + bytes32 expectedRoot = bytes32(0x168852131b9462b8137b1deadf05872035b6046281ecf234278545aef52ae47b); + confirmBatchGas(expectedRoot, 1, 1024); } } diff --git a/contract-tests/MerkleTree.sol b/contract-tests/MerkleTree.sol new file mode 100644 index 0000000..2e1d540 --- /dev/null +++ b/contract-tests/MerkleTree.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +import {Test, console} from "forge-std/Test.sol"; +import {MerkleTree} from "../contracts/core/MerkleTree.sol"; + +/** + * @title MerkleTreeTest + * @dev Tests to verify the correctness of the MerkleTree library functions + */ +contract MerkleTreeTest is Test { + function test_nextPowerOfTwo() public pure { + assertEq(MerkleTree.nextPowerOfTwo(0), 1); + assertEq(MerkleTree.nextPowerOfTwo(1), 1); + assertEq(MerkleTree.nextPowerOfTwo(2), 2); + assertEq(MerkleTree.nextPowerOfTwo(3), 4); + assertEq(MerkleTree.nextPowerOfTwo(4), 4); + assertEq(MerkleTree.nextPowerOfTwo(5), 8); + assertEq(MerkleTree.nextPowerOfTwo(6), 8); + assertEq(MerkleTree.nextPowerOfTwo(7), 8); + assertEq(MerkleTree.nextPowerOfTwo(8), 8); + assertEq(MerkleTree.nextPowerOfTwo(9), 16); + assertEq(MerkleTree.nextPowerOfTwo(15), 16); + assertEq(MerkleTree.nextPowerOfTwo(16), 16); + assertEq(MerkleTree.nextPowerOfTwo(17), 32); + } + + function test_hashNode() public pure { + // Test with known values + bytes32 left = keccak256(abi.encodePacked(uint256(0))); + assertEq(left, bytes32(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563)); + bytes32 right = keccak256(abi.encodePacked(uint256(1))); + assertEq(right, bytes32(0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6)); + bytes32 expectedHash = bytes32(0x05cca086ac1292d712fa72c1b3f12ca115644d2961f946ba815d8d731f9e5059); + assertEq(MerkleTree.hashNode(left, right), expectedHash); + } + + function test_Max1024Leaves() public pure { + bytes32[] memory leaves = new bytes32[](1024); + for (uint256 i = 0; i < 1024; i++) { + leaves[i] = keccak256(abi.encodePacked(i)); + } + bytes32 root = MerkleTree.computeRoot(leaves); + bytes32 expected = bytes32(0x168852131b9462b8137b1deadf05872035b6046281ecf234278545aef52ae47b); + assertEq(root, expected, "Root mismatch for 1024 leaves"); + } +} diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 41042fd..31ac6eb 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -41,6 +41,7 @@ contract L2AnchoringManager is $.feeOracle = IL1FeeOracle(feeOracle); // Start from 1 to use 0 as a sentinel value $.queueIndex = 1; + $.confirmedIndex = 1; $.l2Messenger = IL2ScrollMessenger(l2Messenger); // Set up roles diff --git a/contracts/core/MerkleTree.sol b/contracts/core/MerkleTree.sol index 7613e5e..155a1cc 100644 --- a/contracts/core/MerkleTree.sol +++ b/contracts/core/MerkleTree.sol @@ -12,7 +12,7 @@ pragma solidity ^0.8.29; */ library MerkleTree { /// @dev Prefix byte to distinguish internal nodes from leaves (matches Rust INNER_NODE_PREFIX) - bytes1 private constant INNER_NODE_PREFIX = 0x01; + uint8 private constant INNER_NODE_PREFIX = 0x01; /// @dev Empty leaf used for padding to power of two bytes32 private constant EMPTY_LEAF = 0x0000000000000000000000000000000000000000000000000000000000000000; @@ -29,52 +29,44 @@ library MerkleTree { uint256 count = leaves.length; require(count > 0, "Merkle: Cannot compute root of empty set"); - // If only one leaf, return it directly (Rust logic: tree of size 1 has root = leaf) if (count == 1) { return leaves[0]; } - // Calculate the next power of two for the tree width - uint256 width = _nextPowerOfTwo(count); + uint256 width = nextPowerOfTwo(count); + uint256 halfWidth = width / 2; - // We need a buffer to hold the current level of hashes. - // Max width needed is 'width'. We can reuse memory or allocate new. - // For clarity and safety in loops, we allocate a dynamic array for the current level. - bytes32[] memory level = new bytes32[](width); + bytes32[] memory buffer = new bytes32[](halfWidth); - // 1. Initialize the leaf level (bottom of the tree) - // Copy leaves to the beginning of the buffer - for (uint256 i = 0; i < count; i++) { - level[i] = leaves[i]; - } - // Pad the rest with EMPTY_LEAF (bytes32(0)) - // Note: Solidity arrays initialize to 0 by default, so explicit loop is optional - // but kept here for clarity if EMPTY_LEAF changes or for explicit intent. - // for (uint256 i = count; i < width; i++) { level[i] = EMPTY_LEAF; } - - // 2. Build the tree bottom-up - // Current number of nodes in this level - uint256 currentSize = width; - - while (currentSize > 1) { - uint256 nextSize = (currentSize + 1) / 2; - - for (uint256 i = 0; i < nextSize; i++) { - uint256 leftIndex = 2 * i; - uint256 rightIndex = 2 * i + 1; + uint256 fullPairs = count / 2; - bytes32 left = level[leftIndex]; - // If right index is out of bounds (shouldn't happen with power-of-two padding), treat as empty - bytes32 right = (rightIndex < currentSize) ? level[rightIndex] : EMPTY_LEAF; + // 1. Handle full pairs of leaves + for (uint256 i = 0; i < fullPairs; i++) { + buffer[i] = hashNode(leaves[i * 2], leaves[i * 2 + 1]); + } - // Hash: keccak256(0x01 || left || right) - level[i] = _hashNode(left, right); + // 2. May have an odd leaf left, which pairs with an empty leaf + if (count % 2 != 0) { + buffer[fullPairs] = hashNode(leaves[count - 1], bytes32(0)); + for (uint256 i = fullPairs + 1; i < halfWidth; i++) { + buffer[i] = hashNode(bytes32(0), bytes32(0)); + } + } else { + for (uint256 i = fullPairs; i < halfWidth; i++) { + buffer[i] = hashNode(bytes32(0), bytes32(0)); } + } - currentSize = nextSize; + // 3. Iteratively compute upper levels in-place within the buffer + uint256 currentLevelSize = halfWidth; + while (currentLevelSize > 1) { + currentLevelSize /= 2; + for (uint256 i = 0; i < currentLevelSize; i++) { + buffer[i] = hashNode(buffer[i * 2], buffer[i * 2 + 1]); + } } - return level[0]; + return buffer[0]; } /** @@ -91,7 +83,7 @@ library MerkleTree { * @dev Internal function to hash two child nodes into a parent node. * Matches Rust: Digest::update(&mut hasher, [INNER_NODE_PREFIX]); ... */ - function _hashNode(bytes32 left, bytes32 right) private pure returns (bytes32) { + function hashNode(bytes32 left, bytes32 right) public pure returns (bytes32) { // Clean Assembly Implementation for _hashNode bytes32 result; assembly ("memory-safe") { @@ -107,7 +99,7 @@ library MerkleTree { /** * @dev Calculates the next power of two greater than or equal to n. */ - function _nextPowerOfTwo(uint256 n) private pure returns (uint256) { + function nextPowerOfTwo(uint256 n) public pure returns (uint256) { if (n == 0) return 1; n--; n |= n >> 1; diff --git a/crates/bmt/Cargo.toml b/crates/bmt/Cargo.toml index 565c54c..b559044 100644 --- a/crates/bmt/Cargo.toml +++ b/crates/bmt/Cargo.toml @@ -15,6 +15,8 @@ digest.workspace = true hybrid-array = { workspace = true, features = ["bytemuck"] } [dev-dependencies] +alloy-primitives.workspace = true +alloy-sol-types.workspace = true commonware-cryptography = "0.0.63" # for benchmarks commonware-storage = "0.0.63" # for benchmarks criterion.workspace = true diff --git a/crates/bmt/src/lib.rs b/crates/bmt/src/lib.rs index f35c147..4575c29 100644 --- a/crates/bmt/src/lib.rs +++ b/crates/bmt/src/lib.rs @@ -202,17 +202,21 @@ impl ExactSizeIterator for SiblingIter<'_, D> { #[cfg(test)] mod tests { use super::*; + use alloy_primitives::{B256, U256}; + use alloy_sol_types::SolValue; + use sha2::Sha256; + use sha3::Keccak256; #[test] fn basic() { - test_merkle_tree::(); - test_merkle_tree::(); + test_merkle_tree::(); + test_merkle_tree::(); } #[test] fn proof() { - test_proof::(); - test_proof::(); + test_proof::(); + test_proof::(); } fn test_merkle_tree() @@ -287,4 +291,22 @@ mod tests { assert_eq!(current_hash.as_slice(), tree.root().as_slice()); } } + + #[ignore] + #[test] + fn generate_sol_test() { + let mut leaves = Vec::with_capacity(1024); + for i in 0..1024 { + let mut hasher = Keccak256::new(); + let value = U256::from(i).abi_encode_packed(); + hasher.update(&value); + leaves.push(hasher.finalize()); + } + + for i in 0..=10u32 { + let tree = MerkleTree::::new(&leaves[..2usize.pow(i)]); + let root = B256::new(tree.root().0); + println!("bytes32({}),", root); + } + } } From f54ba48ced2e84ae902443e8417f8fde739b9d0b Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 15:58:09 +0800 Subject: [PATCH 08/14] seperate notify and finalize --- contract-tests/L2AnchoringManager.t.sol | 21 +++++-- contracts/L1/L1AnchoringGateway.sol | 5 +- contracts/L2/manager/IL2AnchoringManager.sol | 56 ++++++++++++++++--- contracts/L2/manager/L2AnchoringManager.sol | 47 ++++++++++++---- .../L2/manager/L2AnchoringManagerStorage.sol | 4 ++ .../L2/manager/L2AnchoringManagerTypes.sol | 8 +++ 6 files changed, 116 insertions(+), 25 deletions(-) diff --git a/contract-tests/L2AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol index c4f7626..eac62cc 100644 --- a/contract-tests/L2AnchoringManager.t.sol +++ b/contract-tests/L2AnchoringManager.t.sol @@ -93,8 +93,13 @@ contract L2AnchoringManagerTest is Test { // Simulate a call from bridge to confirm the anchoring vm.prank(address(l2Messenger)); // Simulate a call from the L2 messenger vm.expectEmit(true, true, true, true); - emit IL2AnchoringManager.L1AnchoringBatchConfirmed(root, 1, 1, block.number, block.number, block.timestamp); - manager.confirmL1AnchoringBatch(root, 1, 1, block.number); + emit IL2AnchoringManager.L1BatchArrived(root, 1, 1, block.number, block.number, block.timestamp); + manager.notifyAnchored(root, 1, 1, block.number); + + vm.prank(address(1)); + vm.expectEmit(true, true, true, true); + emit IL2AnchoringManager.L1BatchFinalized(root, 1, 1, block.number, block.number, block.timestamp); + manager.finalizeBatch(); } function test_NonExistentRoot() public view { @@ -140,9 +145,15 @@ contract L2AnchoringManagerGasTest is Test { function confirmBatchGas(bytes32 expectedRoot, uint256 startIndex, uint256 count) private { vm.prank(address(l2Messenger)); uint256 startGas = gasleft(); - manager.confirmL1AnchoringBatch(expectedRoot, startIndex, count, block.number); - uint256 gasUsed = startGas - gasleft(); - console.log("Gas used for confirming a batch of", count, "roots:", gasUsed); + manager.notifyAnchored(expectedRoot, startIndex, count, block.number); + uint256 l1L2GasUsed = startGas - gasleft(); + + vm.prank(address(1)); + startGas = gasleft(); + manager.finalizeBatch(); + uint256 l2GasUsed = startGas - gasleft(); + + console.log("Gas used for confirming batch with count", count, l1L2GasUsed, l2GasUsed); } function test_ConfirmBatchGas_1() public { diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index da07a64..1e8d04e 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -68,9 +68,8 @@ contract L1AnchoringGateway is require(attestedBlockNumber != 0, "UTS: Merkle root not attested on L1"); } - bytes memory message = abi.encodeCall( - IL2AnchoringManager.confirmL1AnchoringBatch, (merkleRoot, startIndex, count, attestedBlockNumber) - ); + bytes memory message = + abi.encodeCall(IL2AnchoringManager.notifyAnchored, (merkleRoot, startIndex, count, attestedBlockNumber)); $.l1Messenger.sendMessage{value: msg.value}( address($.l2AnchoringManager), diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index 15c9d63..32f1255 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -8,9 +8,35 @@ interface IL2AnchoringManager { bytes32 indexed root, uint256 queueIndex, uint256 fee, uint256 blockNumber, uint256 timestamp ); - /// @notice Emitted when a batch of roots is confirmed as anchored on L1. - event L1AnchoringBatchConfirmed( - bytes32 indexed aggregateRoot, + /** + * Emitted when L1 notifies that a batch of roots has been anchored on L1. + * @param root The Merkle root of the batch being confirmed. + * @param startIndex The starting index of the batch in the queue. + * @param count The number of items in the batch. + * @param l1BlockNumber The L1 block number at which the batch was anchored. + * @param l2BlockNumber The L2 block number at which the notification is received. + * @param timestamp The timestamp when the notification is received. + */ + event L1BatchArrived( + bytes32 indexed root, + uint256 indexed startIndex, + uint256 count, + uint256 l1BlockNumber, + uint256 l2BlockNumber, + uint256 timestamp + ); + + /** + * Emitted when a batch of roots is finalized after L1 confirmation. + * @param root The Merkle root of the batch being confirmed. + * @param startIndex The starting index of the batch in the queue. + * @param count The number of items in the batch. + * @param l1BlockNumber The L1 block number at which the batch was anchored. + * @param l2BlockNumber The L2 block number at which the batch is finalized. + * @param timestamp The timestamp when the batch is finalized. + */ + event L1BatchFinalized( + bytes32 indexed root, uint256 indexed startIndex, uint256 count, uint256 l1BlockNumber, @@ -30,11 +56,19 @@ interface IL2AnchoringManager { /** * @notice Submit a root for L2 timestamping + L1 anchoring. * @param root The Merkle root to be anchored on L1. - * @param refundAddress The address to refund any excess fee after covering the required fee for L1 anchoring. This allows users to get a refund if they overpay. + * @param refundAddress The address to refund any excess fee after covering the required fee for L1 anchoring. + * This allows users to get a refund if they overpay. * @dev Requires msg.value >= Oracle calculated fee. */ function submitForL1Anchoring(bytes32 root, address refundAddress) external payable; + /** + * @notice Finalize the batch confirmation after receiving the L1 notification. This will verify the Merkle root + * and update the confirmed index. This can be called by anyone after the notification is received to save the + * cost of L2 execution since the cross chain gas price is higher than L2 execution. + */ + function finalizeBatch() external; + /** * @notice Check if a root has been confirmed as anchored on L1. * @param root The Merkle root to check for confirmation. @@ -43,14 +77,17 @@ interface IL2AnchoringManager { function isConfirmed(bytes32 root) external view returns (bool); /** - * @notice Called by the L1AnchoringGateway to confirm a batch of anchored roots. + * @notice Called by the L1AnchoringGateway to notify that a batch of roots has been anchored on L1. + * The cross chain gas price is higher than l2 execution, so we separate the notification and finalization into + * two steps to save cost. The batch details will be stored when notifyAnchored is called, and the actual + * confirmation will be done in finalizeBatch which can be called by anyone after the notification. + * * @param expectedRoot The expected Merkle root of the batch being confirmed. * @param startIndex The starting index of the batch in the queue. * @param count The number of items in the batch. * @param l1BlockNumber The L1 block number at which the batch was anchored. */ - function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) - external; + function notifyAnchored(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) external; // --- Admin Functions --- @@ -58,6 +95,11 @@ interface IL2AnchoringManager { function setL1Gateway(address l1Gateway) external; function setL2Messenger(address l2Messenger) external; + /** + * @notice Clear the pending batch. This can be used by the admin to reset the state in case of an emergency or if the batch finalization fails due to unforeseen issues. It allows the contract to accept new batches without being stuck on a pending batch. + */ + function clearBatch() external; + /** * @notice Withdraw accumulated fees to the collector. * @dev Only callable by the feeCollector or Owner. diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 31ac6eb..bfe2fe6 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -85,9 +85,7 @@ contract L2AnchoringManager is } /// @inheritdoc IL2AnchoringManager - function confirmL1AnchoringBatch(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) - external - { + function notifyAnchored(bytes32 expectedRoot, uint256 startIndex, uint256 count, uint256 l1BlockNumber) external { require(count > 0, "UTS: Count must be greater than zero"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); @@ -97,20 +95,43 @@ contract L2AnchoringManager is address l1Sender = $.l2Messenger.xDomainMessageSender(); require(l1Sender == $.l1Gateway, "UTS: Invalid L1 sender"); - bytes32[] memory leaves = new bytes32[](count); - for (uint256 i = 0; i < count; i++) { - uint256 index = startIndex + i; + /// Require there's no pending batch to prevent overlapping batches which can cause state inconsistency + require($.pendingBatch.count == 0, "UTS: Pending batch already exists"); + + // Store the batch details for later finalization + $.pendingBatch = L2AnchoringManagerTypes.L1Batch({ + expectedRoot: expectedRoot, startIndex: startIndex, count: count, l1BlockNumber: l1BlockNumber + }); + + emit L1BatchArrived(expectedRoot, startIndex, count, l1BlockNumber, block.number, block.timestamp); + } + + /// @inheritdoc IL2AnchoringManager + function finalizeBatch() external { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + L2AnchoringManagerTypes.L1Batch memory batch = $.pendingBatch; + + require(batch.count > 0, "UTS: No pending batch"); + + bytes32[] memory leaves = new bytes32[](batch.count); + for (uint256 i = 0; i < batch.count; i++) { + uint256 index = batch.startIndex + i; L2AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; leaves[i] = item.root; - item.l1BlockNumber = l1BlockNumber; + item.l1BlockNumber = batch.l1BlockNumber; } bytes32 computedRoot = MerkleTree.computeRoot(leaves); - require(computedRoot == expectedRoot, "UTS: Invalid Merkle Root"); + require(computedRoot == batch.expectedRoot, "UTS: Invalid Merkle Root"); + + $.confirmedIndex = batch.startIndex + batch.count; - emit L1AnchoringBatchConfirmed(computedRoot, startIndex, count, l1BlockNumber, block.number, block.timestamp); + emit L1BatchFinalized( + batch.expectedRoot, batch.startIndex, batch.count, batch.l1BlockNumber, block.number, block.timestamp + ); - $.confirmedIndex = startIndex + count; + // Clear the pending batch + delete $.pendingBatch; } // --- Admin Functions --- @@ -144,6 +165,12 @@ contract L2AnchoringManager is emit L2MessengerUpdated(address($.l2Messenger), l2Messenger); } + /// @inheritdoc IL2AnchoringManager + function clearBatch() external onlyRole(DEFAULT_ADMIN_ROLE) { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + delete $.pendingBatch; + } + /// @inheritdoc IL2AnchoringManager function withdrawFees(address to, uint256 amount) external nonReentrant onlyRole(FEE_COLLECTOR_ROLE) { require(to != address(0), "UTS: Invalid address"); diff --git a/contracts/L2/manager/L2AnchoringManagerStorage.sol b/contracts/L2/manager/L2AnchoringManagerStorage.sol index e24039c..f602161 100644 --- a/contracts/L2/manager/L2AnchoringManagerStorage.sol +++ b/contracts/L2/manager/L2AnchoringManagerStorage.sol @@ -26,8 +26,12 @@ library L2AnchoringManagerStorage { address l1Gateway; /// @notice Queue index for the next anchoring item to be added uint256 queueIndex; + + /// @notice Storage for pending L1 batch confirmation + L2AnchoringManagerTypes.L1Batch pendingBatch; /// @notice Next index of the anchoring item to be confirmed uint256 confirmedIndex; + mapping(uint256 => L2AnchoringManagerTypes.AnchoringItem) items; mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup } diff --git a/contracts/L2/manager/L2AnchoringManagerTypes.sol b/contracts/L2/manager/L2AnchoringManagerTypes.sol index c205c1e..61a5ce9 100644 --- a/contracts/L2/manager/L2AnchoringManagerTypes.sol +++ b/contracts/L2/manager/L2AnchoringManagerTypes.sol @@ -8,4 +8,12 @@ library L2AnchoringManagerTypes { bytes32 root; uint256 l1BlockNumber; } + + /// @notice Struct to hold L1 notification details for batch confirmation + struct L1Batch { + bytes32 expectedRoot; + uint256 startIndex; + uint256 count; + uint256 l1BlockNumber; + } } From 9ce7d26f7f745c7300b986d3500f6b6b32cd34c5 Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Sat, 28 Feb 2026 16:11:29 +0800 Subject: [PATCH 09/14] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- contracts/L1/L1AnchoringGateway.sol | 2 +- contracts/L2/manager/L2AnchoringManager.sol | 2 +- contracts/L2/oracle/L1FeeOracle.sol | 2 +- contracts/core/MerkleTree.sol | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index 1e8d04e..d241430 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -36,7 +36,7 @@ contract L1AnchoringGateway is require(uts != address(0), "UTS: Invalid UniversalTimestamps address"); require(l1Messenger != address(0), "UTS: Invalid L1ScrollMessenger address"); - require(l2AnchoringManager != address(0), "UTS: Invalid L2AnchoringManagerL2 address"); + require(l2AnchoringManager != address(0), "UTS: Invalid L2AnchoringManager address"); L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); $.uts = IUniversalTimestamps(uts); diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index bfe2fe6..52b16ae 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -65,7 +65,7 @@ contract L2AnchoringManager is $.items[currentIndex] = L2AnchoringManagerTypes.AnchoringItem({root: root, l1BlockNumber: 0}); $.roots[root] = currentIndex; - emit L1AnchoringQueued(root, currentIndex, msg.value, block.number, block.timestamp); + emit L1AnchoringQueued(root, currentIndex, requiredFee, block.number, block.timestamp); // refund fee to `refundAddress` unchecked { diff --git a/contracts/L2/oracle/L1FeeOracle.sol b/contracts/L2/oracle/L1FeeOracle.sol index 4346306..bb04128 100644 --- a/contracts/L2/oracle/L1FeeOracle.sol +++ b/contracts/L2/oracle/L1FeeOracle.sol @@ -64,7 +64,7 @@ contract L1FeeOracle is IL1FeeOracle, AccessControlDefaultAdminRules { /// @inheritdoc IL1FeeOracle function getFeePerAttestation() external view returns (uint256) { uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); - return (l1BaseFee * gasPerAttestation) / 1e18; + return l1BaseFee * gasPerAttestation; } /// @inheritdoc IL1FeeOracle diff --git a/contracts/core/MerkleTree.sol b/contracts/core/MerkleTree.sol index 155a1cc..1f08653 100644 --- a/contracts/core/MerkleTree.sol +++ b/contracts/core/MerkleTree.sol @@ -108,6 +108,8 @@ library MerkleTree { n |= n >> 8; n |= n >> 16; n |= n >> 32; + n |= n >> 64; + n |= n >> 128; n++; return n; } From 08db9dc7aaf6846e3c300b80813d07d28d20ac26 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 17:39:51 +0800 Subject: [PATCH 10/14] add submitter and use _msgSender --- contract-tests/MerkleTree.sol | 2 +- contracts/L1/L1AnchoringGateway.sol | 4 ++-- contracts/L2/manager/L2AnchoringManager.sol | 3 ++- contracts/L2/manager/L2AnchoringManagerTypes.sol | 1 + contracts/core/UniversalTimestamps.sol | 5 +++-- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contract-tests/MerkleTree.sol b/contract-tests/MerkleTree.sol index 2e1d540..a9f6ce1 100644 --- a/contract-tests/MerkleTree.sol +++ b/contract-tests/MerkleTree.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; -import {Test, console} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {MerkleTree} from "../contracts/core/MerkleTree.sol"; /** diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index d241430..9b3aad1 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -76,10 +76,10 @@ contract L1AnchoringGateway is 0, message, gasLimit, - msg.sender // refund the caller for the gas cost of L2 execution + _msgSender() // refund the caller for the gas cost of L2 execution ); - emit BatchSubmitted(merkleRoot, startIndex, count, msg.sender); + emit BatchSubmitted(merkleRoot, startIndex, count, _msgSender()); } // -- Admin functions -- diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 52b16ae..135e120 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -62,7 +62,8 @@ contract L2AnchoringManager is $.uts.attest(root); uint256 currentIndex = $.queueIndex++; - $.items[currentIndex] = L2AnchoringManagerTypes.AnchoringItem({root: root, l1BlockNumber: 0}); + $.items[currentIndex] = + L2AnchoringManagerTypes.AnchoringItem({root: root, submitter: _msgSender(), l1BlockNumber: 0}); $.roots[root] = currentIndex; emit L1AnchoringQueued(root, currentIndex, requiredFee, block.number, block.timestamp); diff --git a/contracts/L2/manager/L2AnchoringManagerTypes.sol b/contracts/L2/manager/L2AnchoringManagerTypes.sol index 61a5ce9..c311ed0 100644 --- a/contracts/L2/manager/L2AnchoringManagerTypes.sol +++ b/contracts/L2/manager/L2AnchoringManagerTypes.sol @@ -6,6 +6,7 @@ library L2AnchoringManagerTypes { /// @notice Attestation struct to hold timestamp and block number for each attested root struct AnchoringItem { bytes32 root; + address submitter; uint256 l1BlockNumber; } diff --git a/contracts/core/UniversalTimestamps.sol b/contracts/core/UniversalTimestamps.sol index e2bac58..3f874e5 100644 --- a/contracts/core/UniversalTimestamps.sol +++ b/contracts/core/UniversalTimestamps.sol @@ -3,11 +3,12 @@ pragma solidity ^0.8.29; import {IUniversalTimestamps} from "./IUniversalTimestamps.sol"; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; /** * @title UniversalTimestamps */ -contract UniversalTimestamps is IUniversalTimestamps { +contract UniversalTimestamps is Context, IUniversalTimestamps { mapping(bytes32 => Attestation) timestamps; function timestamp(bytes32 root) external view returns (uint256, bool) { @@ -35,7 +36,7 @@ contract UniversalTimestamps is IUniversalTimestamps { require(timestamps[root].timestamp == 0, "UTS: Root already attested"); timestamps[root] = Attestation({timestamp: block.timestamp, blockNumber: block.number}); - emit Attested(root, msg.sender, block.timestamp, block.number); + emit Attested(root, _msgSender(), block.timestamp, block.number); return timestamps[root]; } } From f42ba6147722bfa802f51217cff2029d54d798c9 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sat, 28 Feb 2026 18:09:19 +0800 Subject: [PATCH 11/14] add token --- contract-tests/L2AnchoringManager.t.sol | 6 +- contracts/L2/manager/IL2AnchoringManager.sol | 14 ++++ contracts/L2/manager/L2AnchoringManager.sol | 67 ++++++++++++++++++- .../L2/manager/L2AnchoringManagerStorage.sol | 3 + 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/contract-tests/L2AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol index eac62cc..e1ab1c2 100644 --- a/contract-tests/L2AnchoringManager.t.sol +++ b/contract-tests/L2AnchoringManager.t.sol @@ -67,7 +67,8 @@ contract L2AnchoringManagerTest is Test { ERC1967Proxy proxy = new ERC1967Proxy( address(impl), abi.encodeCall( - L2AnchoringManager.initialize, (address(this), address(uts), address(feeOracle), address(l2Messenger)) + L2AnchoringManager.initialize, + (address(this), address(uts), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/") ) ); manager = IL2AnchoringManager(address(proxy)); @@ -126,7 +127,8 @@ contract L2AnchoringManagerGasTest is Test { ERC1967Proxy proxy = new ERC1967Proxy( address(impl), abi.encodeCall( - L2AnchoringManager.initialize, (address(this), address(uts), address(feeOracle), address(l2Messenger)) + L2AnchoringManager.initialize, + (address(this), address(uts), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/") ) ); manager = IL2AnchoringManager(address(proxy)); diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index 32f1255..07a4c3a 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -44,6 +44,9 @@ interface IL2AnchoringManager { uint256 timestamp ); + /// @notice Emitted when a user claims their NFT after batch confirmation. + event NFTClaimed(address indexed submitter, uint256 indexed tokenId, bytes32 indexed root, uint256 timestamp); + /// @notice Emitted when fee parameters are updated. event FeeOracleUpdated(address indexed oldOracle, address indexed newOracle); /// @notice Emitted when fees are withdrawn by the fee collector. @@ -76,6 +79,17 @@ interface IL2AnchoringManager { */ function isConfirmed(bytes32 root) external view returns (bool); + /// @notice Claim the NFT for a confirmed root by providing the root directly. This is a convenience function + /// that looks up the index from the root and calls claimNFT(index). + function claimNFT(bytes32 root) external; + + /// @notice Claim the NFT for a confirmed root by providing the index of the root in the queue. This can be + /// used if the user already knows the index or wants to save gas by avoiding the root lookup. + function claimNFT(uint256 index) external; + + /// @notice Returns the current base URI for token metadata + function getBaseURI() external view returns (string memory); + /** * @notice Called by the L1AnchoringGateway to notify that a batch of roots has been anchored on L1. * The cross chain gas price is higher than l2 execution, so we separate the notification and finalization into diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 135e120..4fc2ce8 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -16,12 +16,15 @@ import {ScrollConstants} from "scroll-contracts/libraries/constants/ScrollConsta import { AccessControlDefaultAdminRulesUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; +import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; contract L2AnchoringManager is Initializable, UUPSUpgradeable, ReentrancyGuardTransient, AccessControlDefaultAdminRulesUpgradeable, + ERC721Upgradeable, IL2AnchoringManager { bytes32 public constant FEE_COLLECTOR_ROLE = keccak256("FEE_COLLECTOR_ROLE"); @@ -31,8 +34,15 @@ contract L2AnchoringManager is _disableInitializers(); } - function initialize(address initialOwner, address uts, address feeOracle, address l2Messenger) public initializer { + function initialize( + address initialOwner, + address uts, + address feeOracle, + address l2Messenger, + string memory baseTokenURI + ) public initializer { __AccessControlDefaultAdminRules_init(3 days, initialOwner); + __ERC721_init("UTS Anchoring Certificate", "UTS"); require(feeOracle != address(0), "UTS: Invalid FeeOracle address"); @@ -43,7 +53,7 @@ contract L2AnchoringManager is $.queueIndex = 1; $.confirmedIndex = 1; $.l2Messenger = IL2ScrollMessenger(l2Messenger); - + $.baseTokenURI = baseTokenURI; // Set up roles grantRole(FEE_COLLECTOR_ROLE, initialOwner); _setRoleAdmin(FEE_COLLECTOR_ROLE, DEFAULT_ADMIN_ROLE); @@ -79,7 +89,7 @@ contract L2AnchoringManager is } /// @inheritdoc IL2AnchoringManager - function isConfirmed(bytes32 root) external view returns (bool) { + function isConfirmed(bytes32 root) public view returns (bool) { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); uint256 index = $.roots[root]; return index != 0 && index < $.confirmedIndex; @@ -135,6 +145,38 @@ contract L2AnchoringManager is delete $.pendingBatch; } + /// @inheritdoc IL2AnchoringManager + function claimNFT(bytes32 root) external { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + uint256 index = $.roots[root]; + claimNFT(index); + } + + /// @inheritdoc IL2AnchoringManager + function claimNFT(uint256 index) public nonReentrant { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + + require(index != 0 && index < $.confirmedIndex, "UTS: Invalid or unconfirmed index"); + require(!$.nftClaimed[index], "UTS: NFT already claimed"); + + L2AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; + address submitter = item.submitter; + + require(_msgSender() == submitter, "UTS: Only submitter can claim"); + + $.nftClaimed[index] = true; + + _safeMint(submitter, index, bytes.concat(item.root)); + + emit NFTClaimed(submitter, index, item.root, block.timestamp); + } + + /// @inheritdoc IL2AnchoringManager + function getBaseURI() external view returns (string memory) { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + return $.baseTokenURI; + } + // --- Admin Functions --- function setFeeOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { @@ -183,7 +225,26 @@ contract L2AnchoringManager is emit FeesWithdrawn(to, amount); } + // --- Others --- + + function _baseURI() internal view virtual override returns (string memory) { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + return $.baseTokenURI; + } + function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {} + /// @dev See {IERC165-supportsInterface}. + function supportsInterface(bytes4 interfaceId) + public + view + virtual + override(AccessControlDefaultAdminRulesUpgradeable, ERC721Upgradeable) + returns (bool) + { + return AccessControlDefaultAdminRulesUpgradeable.supportsInterface(interfaceId) + || ERC721Upgradeable.supportsInterface(interfaceId); + } + receive() external payable {} } diff --git a/contracts/L2/manager/L2AnchoringManagerStorage.sol b/contracts/L2/manager/L2AnchoringManagerStorage.sol index f602161..2ffcbab 100644 --- a/contracts/L2/manager/L2AnchoringManagerStorage.sol +++ b/contracts/L2/manager/L2AnchoringManagerStorage.sol @@ -34,6 +34,9 @@ library L2AnchoringManagerStorage { mapping(uint256 => L2AnchoringManagerTypes.AnchoringItem) items; mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup + + string baseTokenURI; + mapping(uint256 => bool) nftClaimed; } function get() internal pure returns (L2AnchoringManagerStorage.Storage storage $) { From e14a1d3a8887b74da13d80ec8105ab418f0562e5 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sun, 1 Mar 2026 16:17:16 +0800 Subject: [PATCH 12/14] add constants --- .env | 2 + .env.test | 3 +- contract-tests/L2AnchoringManager.t.sol | 66 +++--- contracts/L1/L1AnchoringGateway.sol | 57 +++-- contracts/L2/manager/IL2AnchoringManager.sol | 4 + contracts/L2/manager/L2AnchoringManager.sol | 67 ++++-- .../core/UniversalTimestampsConstants.sol | 7 + script/Deploy.s.sol | 210 +++++++++--------- 8 files changed, 245 insertions(+), 171 deletions(-) create mode 100644 contracts/core/UniversalTimestampsConstants.sol diff --git a/.env b/.env index ebc5ed4..96df61d 100644 --- a/.env +++ b/.env @@ -1 +1,3 @@ FOUNDRY_OUT="target/foundry" + +UTS=0xF4D4Fd14Ff7A51aaf113f254fCeC6005598B2bc2 \ No newline at end of file diff --git a/.env.test b/.env.test index 4e1d16c..d1a6574 100644 --- a/.env.test +++ b/.env.test @@ -1,8 +1,7 @@ L1_MESSENGER=0x50c7d3e7f7c656493D1D76aaa1a836CedfCBB16A L2_MESSENGER=0xBa50f5340FB9F3Bd074bD638c9BE13eCB36E603d -UTS_IMPL=0x9E4D38DfC4484c2E8744c0e32563266aefb3F93C -UTS=0x0D57Edf086949B6Be411D066E448E12eF5D79baE +UTS=0xF4D4Fd14Ff7A51aaf113f254fCeC6005598B2bc2 FEE_ORACLE=0x7f2A06e64Ef079Af08D9083144E0C917D6dFce0d ANCHORING_MANAGER_IMPL=0xC0E20e0cd2724A480a8454a544e47C0cbBcdaB5e diff --git a/contract-tests/L2AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol index e1ab1c2..73cda81 100644 --- a/contract-tests/L2AnchoringManager.t.sol +++ b/contract-tests/L2AnchoringManager.t.sol @@ -5,38 +5,45 @@ import {Test, console} from "forge-std/Test.sol"; import {L2AnchoringManager} from "../contracts/L2/manager/L2AnchoringManager.sol"; import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; +import {L1AnchoringGateway} from "../contracts/L1/L1AnchoringGateway.sol"; +import {IL1AnchoringGateway} from "../contracts/L1/IL1AnchoringGateway.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; import {IUniversalTimestamps} from "../contracts/core/IUniversalTimestamps.sol"; import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; +import {ScrollConstants} from "scroll-contracts/libraries/constants/ScrollConstants.sol"; +import {UniversalTimestampsConstants} from "../contracts/core/UniversalTimestampsConstants.sol"; +import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; contract MockL1FeeOracle is IL1FeeOracle { - function getL1BaseFee() external view returns (uint256) { + function getL1BaseFee() external pure returns (uint256) { return 0.05 gwei; } - function getFeePerAttestation() external view returns (uint256) { + function getFeePerAttestation() external pure returns (uint256) { return 0.05 gwei * 51_000; } - function getFloorFee() external view returns (uint256) { + function getFloorFee() external pure returns (uint256) { return (0.05 gwei * 51_000 * 0.5e18) / 1e18; } - function getGasPerAttestation() external view returns (uint256) { + function getGasPerAttestation() external pure returns (uint256) { return 51_000; } - function getDiscountRatio() external view returns (uint256) { + function getDiscountRatio() external pure returns (uint256) { return 0.5e18; // 50% discount } } contract MockL2ScrollMessenger is IL2ScrollMessenger { + address sender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER; + function relayMessage(address from, address to, uint256 value, uint256 nonce, bytes calldata message) external {} function xDomainMessageSender() external view returns (address) { - return address(0x456); // Mock L1 sender address + return sender; // Mock L1 sender address } function sendMessage(address target, uint256 value, bytes calldata message, uint256 gasLimit) external payable {} @@ -44,6 +51,10 @@ contract MockL2ScrollMessenger is IL2ScrollMessenger { function sendMessage(address target, uint256 value, bytes calldata message, uint256 gasLimit, address refundAddress) external payable {} + + function setSender(address _sender) external { + sender = _sender; + } } /** @@ -51,27 +62,27 @@ contract MockL2ScrollMessenger is IL2ScrollMessenger { * @dev Tests to verify the functionality of the L2AnchoringManager contract. */ contract L2AnchoringManagerTest is Test { - IUniversalTimestamps uts; IL1FeeOracle feeOracle; IL2AnchoringManager manager; - IL2ScrollMessenger l2Messenger; + MockL2ScrollMessenger l2Messenger; - address constant L1_GATEWAY = address(0x456); + address L1_GATEWAY = address(0x123); function setUp() public { - uts = new UniversalTimestamps(); + vm.etch(UniversalTimestampsConstants.UTS, address(new UniversalTimestamps()).code); feeOracle = new MockL1FeeOracle(); l2Messenger = new MockL2ScrollMessenger(); L2AnchoringManager impl = new L2AnchoringManager(); - ERC1967Proxy proxy = new ERC1967Proxy( - address(impl), - abi.encodeCall( - L2AnchoringManager.initialize, - (address(this), address(uts), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/") - ) + address proxy = address(new ERC1967Proxy(address(impl), abi.encodeCall(L2AnchoringManager.initialize, ()))); + L2AnchoringManager proxyInstance = L2AnchoringManager(payable(proxy)); + proxyInstance.lateInitialize( + address(this), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/" ); - manager = IL2AnchoringManager(address(proxy)); + vm.warp(block.timestamp + 1); + proxyInstance.completeInitialization(); + + manager = IL2AnchoringManager(proxy); manager.setL1Gateway(L1_GATEWAY); } @@ -92,6 +103,7 @@ contract L2AnchoringManagerTest is Test { assertFalse(confirmed, "Root should not be confirmed immediately after submission"); // Simulate a call from bridge to confirm the anchoring + l2Messenger.setSender(L1_GATEWAY); // Simulate a call from the L1 gateway vm.prank(address(l2Messenger)); // Simulate a call from the L2 messenger vm.expectEmit(true, true, true, true); emit IL2AnchoringManager.L1BatchArrived(root, 1, 1, block.number, block.number, block.timestamp); @@ -111,26 +123,25 @@ contract L2AnchoringManagerTest is Test { } contract L2AnchoringManagerGasTest is Test { - IUniversalTimestamps uts; IL1FeeOracle feeOracle; IL2AnchoringManager manager; - IL2ScrollMessenger l2Messenger; + MockL2ScrollMessenger l2Messenger; - address constant L1_GATEWAY = address(0x456); + address L1_GATEWAY = address(0x456); function setUp() public { - uts = new UniversalTimestamps(); + vm.etch(UniversalTimestampsConstants.UTS, address(new UniversalTimestamps()).code); feeOracle = new MockL1FeeOracle(); l2Messenger = new MockL2ScrollMessenger(); L2AnchoringManager impl = new L2AnchoringManager(); - ERC1967Proxy proxy = new ERC1967Proxy( - address(impl), - abi.encodeCall( - L2AnchoringManager.initialize, - (address(this), address(uts), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/") - ) + address proxy = address(new ERC1967Proxy(address(impl), abi.encodeCall(L2AnchoringManager.initialize, ()))); + L2AnchoringManager proxyInstance = L2AnchoringManager(payable(proxy)); + proxyInstance.lateInitialize( + address(this), address(feeOracle), address(l2Messenger), "https://timestamps.now/token/" ); + vm.warp(block.timestamp + 1); + proxyInstance.completeInitialization(); manager = IL2AnchoringManager(address(proxy)); manager.setL1Gateway(L1_GATEWAY); @@ -145,6 +156,7 @@ contract L2AnchoringManagerGasTest is Test { } function confirmBatchGas(bytes32 expectedRoot, uint256 startIndex, uint256 count) private { + l2Messenger.setSender(L1_GATEWAY); vm.prank(address(l2Messenger)); uint256 startGas = gasleft(); manager.notifyAnchored(expectedRoot, startIndex, count, block.number); diff --git a/contracts/L1/L1AnchoringGateway.sol b/contracts/L1/L1AnchoringGateway.sol index 9b3aad1..1c2efdf 100644 --- a/contracts/L1/L1AnchoringGateway.sol +++ b/contracts/L1/L1AnchoringGateway.sol @@ -13,6 +13,7 @@ import {IUniversalTimestamps} from "../core/IUniversalTimestamps.sol"; import { AccessControlDefaultAdminRulesUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; +import {UniversalTimestampsConstants} from "../core/UniversalTimestampsConstants.sol"; contract L1AnchoringGateway is Initializable, @@ -23,29 +24,48 @@ contract L1AnchoringGateway is { bytes32 public constant SUBMITTER_ROLE = keccak256("SUBMITTER_ROLE"); + /// finalize a 512 entry batch requires 12.9M Gas, 1024 entries requires 25.8M Gas, which exceeds scroll's current block gas limit. + uint256 public constant MAX_BATCH_SIZE = 512; + + /// @notice Safe bounds for gas limit of the L2 transaction to notify the L2 manager of a new batch submission. + /// Avoid accidentally setting a gas limit that is too low causing failed transactions on the L2, or too high costly L1 fees. + uint256 public constant MIN_GAS_LIMIT = 110_000; + uint256 public constant MAX_GAS_LIMIT = 200_000; + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); } - function initialize(address initialOwner, address uts, address l1Messenger, address l2AnchoringManager) - public - initializer + /// @notice For deterministic deployment, we use a separate initialize function instead of the constructor. + function initialize() public initializer { + __AccessControlDefaultAdminRules_init(0, msg.sender); + _setRoleAdmin(SUBMITTER_ROLE, DEFAULT_ADMIN_ROLE); + + L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); + $.uts = IUniversalTimestamps(UniversalTimestampsConstants.UTS); + } + + /// @notice For deterministic deployment, we use a separate lateInitialize function to transfer ownership, + /// and setup any necessary parameters that are not provided at the time of deployment. + function lateInitialize(address newAdmin, address l1Messenger, address l2AnchoringManager) + external + onlyRole(DEFAULT_ADMIN_ROLE) { - __AccessControlDefaultAdminRules_init(3 days, initialOwner); + require(newAdmin != address(0), "UTS: Invalid admin address"); + require(l1Messenger != address(0), "UTS: Invalid L1 Scroll Messenger address"); + require(l2AnchoringManager != address(0), "UTS: Invalid L2 Anchoring Manager address"); - require(uts != address(0), "UTS: Invalid UniversalTimestamps address"); - require(l1Messenger != address(0), "UTS: Invalid L1ScrollMessenger address"); - require(l2AnchoringManager != address(0), "UTS: Invalid L2AnchoringManager address"); + setL1ScrollMessenger(l1Messenger); + setL2AnchoringManager(l2AnchoringManager); - L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); - $.uts = IUniversalTimestamps(uts); - $.l1Messenger = IL1ScrollMessenger(l1Messenger); - $.l2AnchoringManager = IL2AnchoringManager(l2AnchoringManager); + beginDefaultAdminTransfer(newAdmin); + } - // Set up roles - grantRole(SUBMITTER_ROLE, initialOwner); - _setRoleAdmin(SUBMITTER_ROLE, DEFAULT_ADMIN_ROLE); + /// @notice Completes the ownership transfer process and sets a delay for future admin transfers. + function completeInitialization() external { + acceptDefaultAdminTransfer(); + changeDefaultAdminDelay(3 days); } /// @inheritdoc IL1AnchoringGateway @@ -60,6 +80,9 @@ contract L1AnchoringGateway is require(address($.l1Messenger) != address(0), "UTS: L1 Scroll Messenger not set"); require(address($.l2AnchoringManager) != address(0), "UTS: L2 Anchoring Manager not set"); + require(count > 0 && count <= MAX_BATCH_SIZE, "UTS: Invalid batch size"); + require(gasLimit >= MIN_GAS_LIMIT && gasLimit <= MAX_GAS_LIMIT, "UTS: Invalid gas limit"); + uint256 attestedBlockNumber; try $.uts.attest(merkleRoot) { attestedBlockNumber = block.number; @@ -83,7 +106,7 @@ contract L1AnchoringGateway is } // -- Admin functions -- - function setUts(address newUts) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setUts(address newUts) public onlyRole(DEFAULT_ADMIN_ROLE) { require(newUts != address(0), "UTS: Invalid UniversalTimestamps address"); L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); address oldUts = address($.uts); @@ -91,7 +114,7 @@ contract L1AnchoringGateway is emit UTSUpdated(oldUts, newUts); } - function setL1ScrollMessenger(address newMessenger) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setL1ScrollMessenger(address newMessenger) public onlyRole(DEFAULT_ADMIN_ROLE) { require(newMessenger != address(0), "UTS: Invalid L1 Scroll Messenger address"); L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); address oldMessenger = address($.l1Messenger); @@ -99,7 +122,7 @@ contract L1AnchoringGateway is emit L1ScrollMessengerUpdated(oldMessenger, newMessenger); } - function setL2AnchoringManager(address newManager) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setL2AnchoringManager(address newManager) public onlyRole(DEFAULT_ADMIN_ROLE) { require(newManager != address(0), "UTS: Invalid L2 Anchoring Manager address"); L1AnchoringGatewayStorage.Storage storage $ = L1AnchoringGatewayStorage.get(); address oldManager = address($.l2AnchoringManager); diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index 07a4c3a..c6e5e27 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -47,6 +47,8 @@ interface IL2AnchoringManager { /// @notice Emitted when a user claims their NFT after batch confirmation. event NFTClaimed(address indexed submitter, uint256 indexed tokenId, bytes32 indexed root, uint256 timestamp); + /// @notice Emitted when a batch is cleared by the admin. + event UtsUpdated(address indexed oldUts, address indexed newUts); /// @notice Emitted when fee parameters are updated. event FeeOracleUpdated(address indexed oldOracle, address indexed newOracle); /// @notice Emitted when fees are withdrawn by the fee collector. @@ -55,6 +57,8 @@ interface IL2AnchoringManager { event L1GatewayUpdated(address indexed oldGateway, address indexed newGateway); /// @notice Emitted when the L2 Messenger address is updated. event L2MessengerUpdated(address indexed oldMessenger, address indexed newMessenger); + /// @notice Emitted when the base URI for token metadata is updated. + event BaseURIUpdated(string oldBaseURI, string newBaseURI); /** * @notice Submit a root for L2 timestamping + L1 anchoring. diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 4fc2ce8..750c898 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -18,6 +18,7 @@ import { } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {UniversalTimestampsConstants} from "../../core/UniversalTimestampsConstants.sol"; contract L2AnchoringManager is Initializable, @@ -34,31 +35,45 @@ contract L2AnchoringManager is _disableInitializers(); } - function initialize( - address initialOwner, - address uts, - address feeOracle, - address l2Messenger, - string memory baseTokenURI - ) public initializer { - __AccessControlDefaultAdminRules_init(3 days, initialOwner); + /// @notice For deterministic deployment, we use a separate initialize function instead of the constructor. + function initialize() public initializer { + __AccessControlDefaultAdminRules_init(0, msg.sender); __ERC721_init("UTS Anchoring Certificate", "UTS"); - require(feeOracle != address(0), "UTS: Invalid FeeOracle address"); - L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); - $.uts = IUniversalTimestamps(uts); - $.feeOracle = IL1FeeOracle(feeOracle); + $.uts = IUniversalTimestamps(UniversalTimestampsConstants.UTS); // Start from 1 to use 0 as a sentinel value $.queueIndex = 1; $.confirmedIndex = 1; - $.l2Messenger = IL2ScrollMessenger(l2Messenger); - $.baseTokenURI = baseTokenURI; - // Set up roles - grantRole(FEE_COLLECTOR_ROLE, initialOwner); + _setRoleAdmin(FEE_COLLECTOR_ROLE, DEFAULT_ADMIN_ROLE); } + /// @notice For deterministic deployment, we use a separate lateInitialize function to transfer ownership, + /// and setup any necessary parameters that are not provided at the time of deployment. + function lateInitialize(address newAdmin, address feeOracle, address l2Messenger, string memory baseTokenURI) + external + onlyRole(DEFAULT_ADMIN_ROLE) + { + require(newAdmin != address(0), "UTS: Invalid admin address"); + require(feeOracle != address(0), "UTS: Invalid FeeOracle address"); + require(l2Messenger != address(0), "UTS: Invalid L2 Messenger address"); + + setFeeOracle(feeOracle); + setL2Messenger(l2Messenger); + + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.baseTokenURI = baseTokenURI; + + beginDefaultAdminTransfer(newAdmin); + } + + /// @notice Completes the ownership transfer process and sets a delay for future admin transfers. + function completeInitialization() external { + acceptDefaultAdminTransfer(); + changeDefaultAdminDelay(3 days); + } + /// @inheritdoc IL2AnchoringManager function submitForL1Anchoring(bytes32 root, address refundAddress) external payable nonReentrant { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); @@ -179,7 +194,15 @@ contract L2AnchoringManager is // --- Admin Functions --- - function setFeeOracle(address _oracle) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setUts(address newUts) public onlyRole(DEFAULT_ADMIN_ROLE) { + require(newUts != address(0), "UTS: Invalid UniversalTimestamps address"); + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + address oldUts = address($.uts); + $.uts = IUniversalTimestamps(newUts); + emit UtsUpdated(oldUts, newUts); + } + + function setFeeOracle(address _oracle) public onlyRole(DEFAULT_ADMIN_ROLE) { require(address(_oracle) != address(0), "UTS: Invalid Oracle"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); address oldOracle = address($.feeOracle); @@ -187,7 +210,7 @@ contract L2AnchoringManager is emit FeeOracleUpdated(oldOracle, _oracle); } - function setL1Gateway(address l1Gateway) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setL1Gateway(address l1Gateway) public onlyRole(DEFAULT_ADMIN_ROLE) { require(l1Gateway != address(0), "UTS: Invalid L1 Gateway address"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); address oldGateway = $.l1Gateway; @@ -195,7 +218,7 @@ contract L2AnchoringManager is emit L1GatewayUpdated(oldGateway, l1Gateway); } - function setL2Messenger(address l2Messenger) external onlyRole(DEFAULT_ADMIN_ROLE) { + function setL2Messenger(address l2Messenger) public onlyRole(DEFAULT_ADMIN_ROLE) { require(l2Messenger != address(0), "UTS: Invalid L2 Messenger address"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); IL2ScrollMessenger messenger = IL2ScrollMessenger(l2Messenger); @@ -208,6 +231,12 @@ contract L2AnchoringManager is emit L2MessengerUpdated(address($.l2Messenger), l2Messenger); } + function setURI(string memory baseTokenURI) public onlyRole(DEFAULT_ADMIN_ROLE) { + L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); + $.baseTokenURI = baseTokenURI; + emit BaseURIUpdated($.baseTokenURI, baseTokenURI); + } + /// @inheritdoc IL2AnchoringManager function clearBatch() external onlyRole(DEFAULT_ADMIN_ROLE) { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); diff --git a/contracts/core/UniversalTimestampsConstants.sol b/contracts/core/UniversalTimestampsConstants.sol new file mode 100644 index 0000000..368c327 --- /dev/null +++ b/contracts/core/UniversalTimestampsConstants.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.29; + +library UniversalTimestampsConstants { + address constant UTS = 0xF4D4Fd14Ff7A51aaf113f254fCeC6005598B2bc2; +} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 49bb463..68bad73 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -14,8 +14,6 @@ bytes32 constant SALT = keccak256("UniversalTimestamps"); contract DeployTimestampCreate2 is Script { function run() public { - address owner = vm.envAddress("OWNER_ADDRESS"); - vm.startBroadcast(); UniversalTimestamps uts = new UniversalTimestamps{salt: SALT}(); vm.stopBroadcast(); @@ -23,107 +21,107 @@ contract DeployTimestampCreate2 is Script { } } -contract DeployFeeOracle is Script { - function run() public { - address owner = vm.envAddress("OWNER_ADDRESS"); - - vm.startBroadcast(); - L1FeeOracle implementation = new L1FeeOracle( - owner, - 100_000, // initialGasPerAttestation - 0.5e18 // initialDiscountRatio (50%) - ); - vm.stopBroadcast(); - - console.log("FeeOracle deployed at", address(implementation)); - } -} - -contract DeployManager is Script { - function run() public { - address owner = vm.envAddress("OWNER_ADDRESS"); - address uts = vm.envAddress("UTS"); - address feeOracle = vm.envAddress("FEE_ORACLE"); - address l1Messenger = vm.envAddress("L1_MESSENGER"); - address l2Messenger = vm.envAddress("L2_MESSENGER"); - - vm.startBroadcast(); - L2AnchoringManager implementation = new L2AnchoringManager(); - console.log("Implementation deployed at:", address(implementation)); - // function initialize(address initialOwner, address uts, address feeOracle, address l1Messenger, address l2Messenger) - bytes memory initData = - abi.encodeCall(L2AnchoringManager.initialize, (owner, uts, feeOracle, l1Messenger, l2Messenger)); - - ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); - vm.stopBroadcast(); - - console.log("Proxy deployed at:", address(proxy)); - } -} - -contract DeployGateway is Script { - function run() public { - address owner = vm.envAddress("OWNER_ADDRESS"); - address uts = vm.envAddress("UTS"); - address l1Messenger = vm.envAddress("L1_MESSENGER"); - address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); - - vm.startBroadcast(); - L1AnchoringGateway implementation = new L1AnchoringGateway(); - console.log("Implementation deployed at:", address(implementation)); - // function initialize(address initialOwner, address uts, address l1Messenger, address l2AnchoringManager) - bytes memory initData = - abi.encodeCall(L1AnchoringGateway.initialize, (owner, uts, l1Messenger, anchoringManager)); - - ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); - vm.stopBroadcast(); - - console.log("Proxy deployed at:", address(proxy)); - } -} - -contract SetGateway is Script { - function run() public { - address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); - address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); - - IL2AnchoringManager manager = IL2AnchoringManager(anchoringManager); - vm.startBroadcast(); - manager.setL1Gateway(l1Gateway); - vm.stopBroadcast(); - } -} - -contract UpgradeManager is Script { - function run() public { - address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); - - vm.startBroadcast(); - L2AnchoringManager newImplementation = new L2AnchoringManager(); - console.log("New implementation deployed at:", address(newImplementation)); - - L2AnchoringManager manager = L2AnchoringManager(payable(anchoringManager)); - - manager.upgradeToAndCall(address(newImplementation), ""); - vm.stopBroadcast(); - - console.log("Manager upgraded to new implementation at:", address(newImplementation)); - } -} - -contract UpgradeGateway is Script { - function run() public { - address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); - - vm.startBroadcast(); - L1AnchoringGateway newImplementation = new L1AnchoringGateway(); - console.log("New implementation deployed at:", address(newImplementation)); - - L1AnchoringGateway gateway = L1AnchoringGateway(payable(l1Gateway)); - - gateway.upgradeToAndCall(address(newImplementation), ""); - vm.stopBroadcast(); - - console.log("Gateway upgraded to new implementation at:", address(newImplementation)); - } -} +// contract DeployFeeOracle is Script { +// function run() public { +// address owner = vm.envAddress("OWNER_ADDRESS"); + +// vm.startBroadcast(); +// L1FeeOracle implementation = new L1FeeOracle( +// owner, +// 100_000, // initialGasPerAttestation +// 0.5e18 // initialDiscountRatio (50%) +// ); +// vm.stopBroadcast(); + +// console.log("FeeOracle deployed at", address(implementation)); +// } +// } + +// contract DeployManager is Script { +// function run() public { +// address owner = vm.envAddress("OWNER_ADDRESS"); +// address uts = vm.envAddress("UTS"); +// address feeOracle = vm.envAddress("FEE_ORACLE"); +// address l1Messenger = vm.envAddress("L1_MESSENGER"); +// address l2Messenger = vm.envAddress("L2_MESSENGER"); + +// vm.startBroadcast(); +// L2AnchoringManager implementation = new L2AnchoringManager(); +// console.log("Implementation deployed at:", address(implementation)); +// // function initialize(address initialOwner, address uts, address feeOracle, address l1Messenger, address l2Messenger) +// bytes memory initData = +// abi.encodeCall(L2AnchoringManager.initialize, (owner, uts, feeOracle, l1Messenger, l2Messenger)); + +// ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); +// vm.stopBroadcast(); + +// console.log("Proxy deployed at:", address(proxy)); +// } +// } + +// contract DeployGateway is Script { +// function run() public { +// address owner = vm.envAddress("OWNER_ADDRESS"); +// address uts = vm.envAddress("UTS"); +// address l1Messenger = vm.envAddress("L1_MESSENGER"); +// address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + +// vm.startBroadcast(); +// L1AnchoringGateway implementation = new L1AnchoringGateway(); +// console.log("Implementation deployed at:", address(implementation)); +// // function initialize(address initialOwner, address uts, address l1Messenger, address l2AnchoringManager) +// bytes memory initData = +// abi.encodeCall(L1AnchoringGateway.initialize, (owner, uts, l1Messenger, anchoringManager)); + +// ERC1967Proxy proxy = new ERC1967Proxy{salt: SALT}(address(implementation), initData); +// vm.stopBroadcast(); + +// console.log("Proxy deployed at:", address(proxy)); +// } +// } + +// contract SetGateway is Script { +// function run() public { +// address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); +// address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + +// IL2AnchoringManager manager = IL2AnchoringManager(anchoringManager); +// vm.startBroadcast(); +// manager.setL1Gateway(l1Gateway); +// vm.stopBroadcast(); +// } +// } + +// contract UpgradeManager is Script { +// function run() public { +// address anchoringManager = vm.envAddress("ANCHORING_MANAGER"); + +// vm.startBroadcast(); +// L2AnchoringManager newImplementation = new L2AnchoringManager(); +// console.log("New implementation deployed at:", address(newImplementation)); + +// L2AnchoringManager manager = L2AnchoringManager(payable(anchoringManager)); + +// manager.upgradeToAndCall(address(newImplementation), ""); +// vm.stopBroadcast(); + +// console.log("Manager upgraded to new implementation at:", address(newImplementation)); +// } +// } + +// contract UpgradeGateway is Script { +// function run() public { +// address l1Gateway = vm.envAddress("ANCHORING_GATEWAY"); + +// vm.startBroadcast(); +// L1AnchoringGateway newImplementation = new L1AnchoringGateway(); +// console.log("New implementation deployed at:", address(newImplementation)); + +// L1AnchoringGateway gateway = L1AnchoringGateway(payable(l1Gateway)); + +// gateway.upgradeToAndCall(address(newImplementation), ""); +// vm.stopBroadcast(); + +// console.log("Gateway upgraded to new implementation at:", address(newImplementation)); +// } +// } From 18bbecd94bb1bf5b6034862096a3db0510f7e185 Mon Sep 17 00:00:00 2001 From: lightsing Date: Sun, 1 Mar 2026 17:21:07 +0800 Subject: [PATCH 13/14] update fee model --- contract-tests/L2AnchoringManager.t.sol | 20 ++- contract-tests/Timestamps.t.sol | 35 ------ contracts/L2/manager/IL2AnchoringManager.sol | 2 + contracts/L2/manager/L2AnchoringManager.sol | 13 +- .../L2/manager/L2AnchoringManagerStorage.sol | 6 +- .../L2/manager/L2AnchoringManagerTypes.sol | 1 - contracts/L2/oracle/FeeOracle.sol | 118 ++++++++++++++++++ contracts/L2/oracle/IFeeOracle.sol | 27 ++++ contracts/L2/oracle/IL1FeeOracle.sol | 39 ------ contracts/L2/oracle/L1FeeOracle.sol | 86 ------------- script/Deploy.s.sol | 4 +- 11 files changed, 168 insertions(+), 183 deletions(-) delete mode 100644 contract-tests/Timestamps.t.sol create mode 100644 contracts/L2/oracle/FeeOracle.sol create mode 100644 contracts/L2/oracle/IFeeOracle.sol delete mode 100644 contracts/L2/oracle/IL1FeeOracle.sol delete mode 100644 contracts/L2/oracle/L1FeeOracle.sol diff --git a/contract-tests/L2AnchoringManager.t.sol b/contract-tests/L2AnchoringManager.t.sol index 73cda81..1cf53e9 100644 --- a/contract-tests/L2AnchoringManager.t.sol +++ b/contract-tests/L2AnchoringManager.t.sol @@ -4,18 +4,14 @@ pragma solidity ^0.8.29; import {Test, console} from "forge-std/Test.sol"; import {L2AnchoringManager} from "../contracts/L2/manager/L2AnchoringManager.sol"; import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; -import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; -import {L1AnchoringGateway} from "../contracts/L1/L1AnchoringGateway.sol"; -import {IL1AnchoringGateway} from "../contracts/L1/IL1AnchoringGateway.sol"; +import {IFeeOracle} from "../contracts/L2/oracle/IFeeOracle.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; -import {IUniversalTimestamps} from "../contracts/core/IUniversalTimestamps.sol"; import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; import {ScrollConstants} from "scroll-contracts/libraries/constants/ScrollConstants.sol"; import {UniversalTimestampsConstants} from "../contracts/core/UniversalTimestampsConstants.sol"; -import {IL1ScrollMessenger} from "scroll-contracts/L1/IL1ScrollMessenger.sol"; -contract MockL1FeeOracle is IL1FeeOracle { +contract MockFeeOracle is IFeeOracle { function getL1BaseFee() external pure returns (uint256) { return 0.05 gwei; } @@ -62,15 +58,15 @@ contract MockL2ScrollMessenger is IL2ScrollMessenger { * @dev Tests to verify the functionality of the L2AnchoringManager contract. */ contract L2AnchoringManagerTest is Test { - IL1FeeOracle feeOracle; + IFeeOracle feeOracle; IL2AnchoringManager manager; MockL2ScrollMessenger l2Messenger; - address L1_GATEWAY = address(0x123); + address constant L1_GATEWAY = address(0x123); function setUp() public { vm.etch(UniversalTimestampsConstants.UTS, address(new UniversalTimestamps()).code); - feeOracle = new MockL1FeeOracle(); + feeOracle = new MockFeeOracle(); l2Messenger = new MockL2ScrollMessenger(); L2AnchoringManager impl = new L2AnchoringManager(); @@ -123,15 +119,15 @@ contract L2AnchoringManagerTest is Test { } contract L2AnchoringManagerGasTest is Test { - IL1FeeOracle feeOracle; + IFeeOracle feeOracle; IL2AnchoringManager manager; MockL2ScrollMessenger l2Messenger; - address L1_GATEWAY = address(0x456); + address constant L1_GATEWAY = address(0x456); function setUp() public { vm.etch(UniversalTimestampsConstants.UTS, address(new UniversalTimestamps()).code); - feeOracle = new MockL1FeeOracle(); + feeOracle = new MockFeeOracle(); l2Messenger = new MockL2ScrollMessenger(); L2AnchoringManager impl = new L2AnchoringManager(); diff --git a/contract-tests/Timestamps.t.sol b/contract-tests/Timestamps.t.sol deleted file mode 100644 index dc74bfa..0000000 --- a/contract-tests/Timestamps.t.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.29; - -import {Test, console} from "forge-std/Test.sol"; -import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; -import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; - -/** - * @title UniversalTimestampsTest - * @dev Tests to verify the functionality of the UniversalTimestamps contract. - */ -contract UniversalTimestampsTest is Test { - UniversalTimestamps uts; - - function setUp() public { - uts = new UniversalTimestamps(); - console.log("UniversalTimestamps deployed at:", address(uts)); - } - - function test_AttestGasCost() public { - bytes32 data = keccak256("Test data for attestation"); - uint256 startGas = gasleft(); - uts.attest(data); - uint256 endGas = gasleft(); - // Adding base transaction cost - uint256 gasUsed = startGas - endGas + 21000; - console.log("Gas used for attest:", gasUsed); - - // Assert the L1FeeOracle default fee is roughly same. - L1FeeOracle feeOracle = new L1FeeOracle(address(this)); - uint256 gas = feeOracle.gasPerAttestation(); - console.log("Current gas per attestation from L1FeeOracle:", gas); - assertApproxEqAbs(gasUsed, gas, 5000); // Allow a margin of error of 5000 gas units - } -} diff --git a/contracts/L2/manager/IL2AnchoringManager.sol b/contracts/L2/manager/IL2AnchoringManager.sol index c6e5e27..e16d0c2 100644 --- a/contracts/L2/manager/IL2AnchoringManager.sol +++ b/contracts/L2/manager/IL2AnchoringManager.sol @@ -85,10 +85,12 @@ interface IL2AnchoringManager { /// @notice Claim the NFT for a confirmed root by providing the root directly. This is a convenience function /// that looks up the index from the root and calls claimNFT(index). + // forge-lint: disable-next-line(mixed-case-function) function claimNFT(bytes32 root) external; /// @notice Claim the NFT for a confirmed root by providing the index of the root in the queue. This can be /// used if the user already knows the index or wants to save gas by avoiding the root lookup. + // forge-lint: disable-next-line(mixed-case-function) function claimNFT(uint256 index) external; /// @notice Returns the current base URI for token metadata diff --git a/contracts/L2/manager/L2AnchoringManager.sol b/contracts/L2/manager/L2AnchoringManager.sol index 750c898..a63b421 100644 --- a/contracts/L2/manager/L2AnchoringManager.sol +++ b/contracts/L2/manager/L2AnchoringManager.sol @@ -7,7 +7,7 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; import {IL2AnchoringManager} from "./IL2AnchoringManager.sol"; import {L2AnchoringManagerStorage} from "./L2AnchoringManagerStorage.sol"; -import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {IFeeOracle} from "../oracle/IFeeOracle.sol"; import {L2AnchoringManagerTypes} from "./L2AnchoringManagerTypes.sol"; import {MerkleTree} from "../../core/MerkleTree.sol"; import {IUniversalTimestamps} from "../../core/IUniversalTimestamps.sol"; @@ -17,7 +17,6 @@ import { AccessControlDefaultAdminRulesUpgradeable } from "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {UniversalTimestampsConstants} from "../../core/UniversalTimestampsConstants.sol"; contract L2AnchoringManager is @@ -87,8 +86,7 @@ contract L2AnchoringManager is $.uts.attest(root); uint256 currentIndex = $.queueIndex++; - $.items[currentIndex] = - L2AnchoringManagerTypes.AnchoringItem({root: root, submitter: _msgSender(), l1BlockNumber: 0}); + $.items[currentIndex] = L2AnchoringManagerTypes.AnchoringItem({root: root, submitter: _msgSender()}); $.roots[root] = currentIndex; emit L1AnchoringQueued(root, currentIndex, requiredFee, block.number, block.timestamp); @@ -144,13 +142,13 @@ contract L2AnchoringManager is uint256 index = batch.startIndex + i; L2AnchoringManagerTypes.AnchoringItem storage item = $.items[index]; leaves[i] = item.root; - item.l1BlockNumber = batch.l1BlockNumber; } bytes32 computedRoot = MerkleTree.computeRoot(leaves); require(computedRoot == batch.expectedRoot, "UTS: Invalid Merkle Root"); $.confirmedIndex = batch.startIndex + batch.count; + $.batchStartToL1Block[batch.startIndex] = batch.l1BlockNumber; emit L1BatchFinalized( batch.expectedRoot, batch.startIndex, batch.count, batch.l1BlockNumber, block.number, block.timestamp @@ -161,6 +159,7 @@ contract L2AnchoringManager is } /// @inheritdoc IL2AnchoringManager + // forge-lint: disable-next-line(mixed-case-function) function claimNFT(bytes32 root) external { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); uint256 index = $.roots[root]; @@ -168,6 +167,7 @@ contract L2AnchoringManager is } /// @inheritdoc IL2AnchoringManager + // forge-lint: disable-next-line(mixed-case-function) function claimNFT(uint256 index) public nonReentrant { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); @@ -206,7 +206,7 @@ contract L2AnchoringManager is require(address(_oracle) != address(0), "UTS: Invalid Oracle"); L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); address oldOracle = address($.feeOracle); - $.feeOracle = IL1FeeOracle(_oracle); + $.feeOracle = IFeeOracle(_oracle); emit FeeOracleUpdated(oldOracle, _oracle); } @@ -256,6 +256,7 @@ contract L2AnchoringManager is // --- Others --- + // forge-lint: disable-next-line(mixed-case-function) function _baseURI() internal view virtual override returns (string memory) { L2AnchoringManagerStorage.Storage storage $ = L2AnchoringManagerStorage.get(); return $.baseTokenURI; diff --git a/contracts/L2/manager/L2AnchoringManagerStorage.sol b/contracts/L2/manager/L2AnchoringManagerStorage.sol index 2ffcbab..799a633 100644 --- a/contracts/L2/manager/L2AnchoringManagerStorage.sol +++ b/contracts/L2/manager/L2AnchoringManagerStorage.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; -import {IL1FeeOracle} from "../oracle/IL1FeeOracle.sol"; +import {IFeeOracle} from "../oracle/IFeeOracle.sol"; import {L2AnchoringManagerTypes} from "./L2AnchoringManagerTypes.sol"; import {IUniversalTimestamps} from "../../core/IUniversalTimestamps.sol"; import {IL2ScrollMessenger} from "scroll-contracts/L2/IL2ScrollMessenger.sol"; @@ -19,7 +19,7 @@ library L2AnchoringManagerStorage { /// @custom:storage-location erc7201:uts.storage.L2AnchoringManager struct Storage { IUniversalTimestamps uts; - IL1FeeOracle feeOracle; + IFeeOracle feeOracle; /// @notice Executor for L1 -> L2 messages IL2ScrollMessenger l2Messenger; /// @notice L1 sender address @@ -31,6 +31,8 @@ library L2AnchoringManagerStorage { L2AnchoringManagerTypes.L1Batch pendingBatch; /// @notice Next index of the anchoring item to be confirmed uint256 confirmedIndex; + /// @notice Mapping to track the L1 block number for each batch start index + mapping(uint256 => uint256) batchStartToL1Block; mapping(uint256 => L2AnchoringManagerTypes.AnchoringItem) items; mapping(bytes32 => uint256) roots; // Mapping to track submitted roots for quick lookup diff --git a/contracts/L2/manager/L2AnchoringManagerTypes.sol b/contracts/L2/manager/L2AnchoringManagerTypes.sol index c311ed0..aabe90d 100644 --- a/contracts/L2/manager/L2AnchoringManagerTypes.sol +++ b/contracts/L2/manager/L2AnchoringManagerTypes.sol @@ -7,7 +7,6 @@ library L2AnchoringManagerTypes { struct AnchoringItem { bytes32 root; address submitter; - uint256 l1BlockNumber; } /// @notice Struct to hold L1 notification details for batch confirmation diff --git a/contracts/L2/oracle/FeeOracle.sol b/contracts/L2/oracle/FeeOracle.sol new file mode 100644 index 0000000..ce06252 --- /dev/null +++ b/contracts/L2/oracle/FeeOracle.sol @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; +import {IFeeOracle} from "./IFeeOracle.sol"; +import { + AccessControlDefaultAdminRules +} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; + +/** + * @title FeeOracle + */ +contract FeeOracle is IFeeOracle, AccessControlDefaultAdminRules { + // Predeploy contract on Scroll that provides the current L1 base fee. + IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = + IL1GasPriceOracle(0x5300000000000000000000000000000000000002); + + bytes32 public constant UPDATER_ROLE = keccak256("UPDATER_ROLE"); + + uint256 private constant PRECISION = 1e18; + + /// @notice value from SystemConfig contract on L1 + uint256 public l1Overhead = 39_200_000; + + /// @notice value from SystemConfig contract on L1 + uint256 public l1FeeScalar = 17e14; + + /// @notice gas required to attest a batch on L1 + uint256 public l1GasEstimated = 200_000; + + /// @notice gas required for L1->L2 message to notify L2 manager of new batch submission + uint256 public crossDomainGasEstimated = 110_000; + + /// @notice scalar for L2 execution gas estimation for each additional batch item. + uint256 public l2ExecutionScalar = 3500; + + /// @notice overhead for L2 execution gas estimation + uint256 public l2ExecutionOverhead = 33_000; + + /// @notice expected batch size for fee calculation + uint256 public expectedBatchSize = 256; + + /// @notice multiplier to apply on top of the estimated cost to determine the floor fee. + uint256 public feeMultiplier = 15e17; + + /** + * @param initialOwner The owner of this oracle contract. + */ + constructor(address initialOwner) AccessControlDefaultAdminRules(3 days, initialOwner) { + _setRoleAdmin(UPDATER_ROLE, DEFAULT_ADMIN_ROLE); + grantRole(UPDATER_ROLE, initialOwner); + } + + /// @inheritdoc IFeeOracle + function getFloorFee() external view returns (uint256) { + uint256 estimatedCost = _estimateBatchCost(); + return (estimatedCost * feeMultiplier) / expectedBatchSize / PRECISION; + } + + function _estimateBatchCost() internal view returns (uint256) { + uint256 l1 = L1_GAS_PRICE_ORACLE.l1BaseFee() * l1GasEstimated; + uint256 crossDomain = _getCrossDomainGasPrice() * crossDomainGasEstimated; + uint256 l2 = block.basefee * _getL2ExecutionGas(expectedBatchSize); + return l1 + crossDomain + l2; + } + + function _getL2ExecutionGas(uint256 batchSize) internal view returns (uint256) { + return l2ExecutionScalar * batchSize + l2ExecutionOverhead; + } + + /// @notice formula from IL1MessageQueueV2's estimateL2BaseFee function + function _getCrossDomainGasPrice() internal view returns (uint256) { + uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); + return (l1BaseFee * l1FeeScalar) / PRECISION + l1Overhead; + } + + // -- Admin functions to update fee parameters -- // + + function setL1Overhead(uint256 _l1Overhead) external onlyRole(UPDATER_ROLE) { + l1Overhead = _l1Overhead; + emit L1OverheadUpdated(_l1Overhead); + } + + function setL1FeeScalar(uint256 _l1FeeScalar) external onlyRole(UPDATER_ROLE) { + l1FeeScalar = _l1FeeScalar; + emit L1FeeScalarUpdated(_l1FeeScalar); + } + + function setL1GasEstimated(uint256 _l1GasEstimated) external onlyRole(UPDATER_ROLE) { + l1GasEstimated = _l1GasEstimated; + emit L1GasEstimatedUpdated(_l1GasEstimated); + } + + function setCrossDomainGasEstimated(uint256 _crossDomainGasEstimated) external onlyRole(UPDATER_ROLE) { + crossDomainGasEstimated = _crossDomainGasEstimated; + emit CrossDomainGasEstimatedUpdated(_crossDomainGasEstimated); + } + + function setL2ExecutionScalar(uint256 _l2ExecutionScalar) external onlyRole(UPDATER_ROLE) { + l2ExecutionScalar = _l2ExecutionScalar; + emit L2ExecutionScalarUpdated(_l2ExecutionScalar); + } + + function setL2ExecutionOverhead(uint256 _l2ExecutionOverhead) external onlyRole(UPDATER_ROLE) { + l2ExecutionOverhead = _l2ExecutionOverhead; + emit L2ExecutionOverheadUpdated(_l2ExecutionOverhead); + } + + function setExpectedBatchSize(uint256 _expectedBatchSize) external onlyRole(UPDATER_ROLE) { + expectedBatchSize = _expectedBatchSize; + emit ExpectedBatchSizeUpdated(_expectedBatchSize); + } + + function setFeeMultiplier(uint256 _feeMultiplier) external onlyRole(UPDATER_ROLE) { + feeMultiplier = _feeMultiplier; + emit FeeMultiplierUpdated(_feeMultiplier); + } +} diff --git a/contracts/L2/oracle/IFeeOracle.sol b/contracts/L2/oracle/IFeeOracle.sol new file mode 100644 index 0000000..14723af --- /dev/null +++ b/contracts/L2/oracle/IFeeOracle.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.29; + +interface IFeeOracle { + /// @notice Emitted when the L1 fee parameters are updated. + event L1OverheadUpdated(uint256 l1Overhead); + /// @notice Emitted when the L1 fee scalar is updated. + event L1FeeScalarUpdated(uint256 l1FeeScalar); + /// @notice Emitted when the L1 gas estimation is updated. + event L1GasEstimatedUpdated(uint256 l1GasEstimated); + /// @notice Emitted when the cross-domain gas estimation is updated. + event CrossDomainGasEstimatedUpdated(uint256 crossDomainGasEstimated); + /// @notice Emitted when the L2 execution scalar is updated. + event L2ExecutionScalarUpdated(uint256 l2ExecutionScalar); + /// @notice Emitted when the L2 execution overhead is updated. + event L2ExecutionOverheadUpdated(uint256 l2ExecutionOverhead); + /// @notice Emitted when the expected batch size is updated. + event ExpectedBatchSizeUpdated(uint256 expectedBatchSize); + /// @notice Emitted when the fee multiplier is updated. + event FeeMultiplierUpdated(uint256 feeMultiplier); + + /** + * @notice Calculate the final fee a user must pay for L1 anchoring. + * @return fee The required fee in Wei, applying the discount ratio. + */ + function getFloorFee() external view returns (uint256); +} diff --git a/contracts/L2/oracle/IL1FeeOracle.sol b/contracts/L2/oracle/IL1FeeOracle.sol deleted file mode 100644 index 5795460..0000000 --- a/contracts/L2/oracle/IL1FeeOracle.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.29; - -interface IL1FeeOracle { - /** - * @notice Emitted when fee parameters are updated. - * @param gasPerAttestation Estimated gas consumed on L1 per attestation (in a batch) - * @param discountRatio The discount ratio applied to the baseline fee (scaled by 1e18) - */ - event ParametersUpdated(uint256 gasPerAttestation, uint256 discountRatio); - - /** - * @notice Return the current L1 Base Fee used for calculation. - */ - function getL1BaseFee() external view returns (uint256); - - /** - * @notice Calculate the theoretical baseline fee WITHOUT aggregation discount. - * @dev Useful for debugging or showing users how much they save. - * @return feePerAttestation The fee if the user submitted independently to L1. - */ - function getFeePerAttestation() external view returns (uint256); - - /** - * @notice Calculate the final fee a user must pay for L1 anchoring. - * @return fee The required fee in Wei, applying the discount ratio. - */ - function getFloorFee() external view returns (uint256); - - /** - * @notice Return the current gas consumed on L1 per attestation. - */ - function getGasPerAttestation() external view returns (uint256); - - /** - * @notice Return the current discount ratio applied to the baseline fee. - */ - function getDiscountRatio() external view returns (uint256); -} diff --git a/contracts/L2/oracle/L1FeeOracle.sol b/contracts/L2/oracle/L1FeeOracle.sol deleted file mode 100644 index bb04128..0000000 --- a/contracts/L2/oracle/L1FeeOracle.sol +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.29; - -import {IL1GasPriceOracle} from "scroll-contracts/L2/predeploys/IL1GasPriceOracle.sol"; -import {IL1FeeOracle} from "./IL1FeeOracle.sol"; -import { - AccessControlDefaultAdminRules -} from "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; - -/** - * @title L1FeeOracle - * @dev Calculates the fee required for L1 anchoring based on dynamic L1 gas prices - * and an aggregation discount ratio (<= 1.0). - * - * Formula: UserFee = (L1_Base_Fee * GasPerAttestation * DiscountRatio) / 1e18 - * - * Semantic: - * - _gasPerAttestation: The theoretical gas cost if a user submitted independently to L1 (e.g., 50,000). - * - _discountRatio: The fraction (<= 1.0) representing the aggregated share + protocol margin (e.g., 0.005e18). - */ -contract L1FeeOracle is IL1FeeOracle, AccessControlDefaultAdminRules { - // Predeploy contract on Scroll that provides the current L1 base fee. - IL1GasPriceOracle public constant L1_GAS_PRICE_ORACLE = - IL1GasPriceOracle(0x5300000000000000000000000000000000000002); - - bytes32 public constant UPDATER_ROLE = keccak256("UPDATER_ROLE"); - - // Estimated gas consumed on L1 per attestation if submitted independently (without aggregation). - uint256 public gasPerAttestation = 75_000; - // Discount: The ratio (<= 1.0) representing the aggregated share + protocol margin. - // Scaled by 1e18. - // Example: 0.005e18 means user pays 0.5% of the baseline gas cost. - // This value implicitly covers the actual batch share + server profit margin. - uint256 public discountRatio = 1e18; // Default to no discount - - /** - * @param initialOwner The owner of this oracle contract. - */ - constructor(address initialOwner) AccessControlDefaultAdminRules(3 days, initialOwner) { - _setRoleAdmin(UPDATER_ROLE, DEFAULT_ADMIN_ROLE); - grantRole(UPDATER_ROLE, initialOwner); - } - - /** - * @notice Update parameters. - * @param newGasPerAttestation Baseline gas for a single independent tx. - * @param newDiscountRatio The aggregation ratio (must be <= 1e18). - */ - function setParameters(uint256 newGasPerAttestation, uint256 newDiscountRatio) external onlyRole(UPDATER_ROLE) { - require(newGasPerAttestation > 0, "L1FeeOracle: Gas must be positive"); - require(newDiscountRatio > 0 && newDiscountRatio <= 1e18, "L1FeeOracle: Ratio must be between 0 and 1.0"); - - gasPerAttestation = newGasPerAttestation; - discountRatio = newDiscountRatio; - - emit ParametersUpdated(newGasPerAttestation, newDiscountRatio); - } - - /// @inheritdoc IL1FeeOracle - function getL1BaseFee() external view returns (uint256) { - return L1_GAS_PRICE_ORACLE.l1BaseFee(); - } - - /// @inheritdoc IL1FeeOracle - function getFeePerAttestation() external view returns (uint256) { - uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); - return l1BaseFee * gasPerAttestation; - } - - /// @inheritdoc IL1FeeOracle - function getFloorFee() external view returns (uint256) { - uint256 l1BaseFee = L1_GAS_PRICE_ORACLE.l1BaseFee(); - // Calculate the fee with discount applied - return (l1BaseFee * gasPerAttestation * discountRatio) / 1e18; - } - - /// @inheritdoc IL1FeeOracle - function getGasPerAttestation() external view returns (uint256) { - return gasPerAttestation; - } - - /// @inheritdoc IL1FeeOracle - function getDiscountRatio() external view returns (uint256) { - return discountRatio; - } -} diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 68bad73..bf97aa9 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -6,7 +6,7 @@ import {UniversalTimestamps} from "../contracts/core/UniversalTimestamps.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {L2AnchoringManager} from "../contracts/L2/manager/L2AnchoringManager.sol"; import {IL2AnchoringManager} from "../contracts/L2/manager/IL2AnchoringManager.sol"; -import {L1FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; +import {FeeOracle} from "../contracts/L2/oracle/L1FeeOracle.sol"; import {IL1FeeOracle} from "../contracts/L2/oracle/IL1FeeOracle.sol"; import {L1AnchoringGateway} from "../contracts/L1/L1AnchoringGateway.sol"; @@ -26,7 +26,7 @@ contract DeployTimestampCreate2 is Script { // address owner = vm.envAddress("OWNER_ADDRESS"); // vm.startBroadcast(); -// L1FeeOracle implementation = new L1FeeOracle( +// FeeOraclecle implementation = nFeeOraclecle( // owner, // 100_000, // initialGasPerAttestation // 0.5e18 // initialDiscountRatio (50%) From 60f1ab8bd917871a207aeb71e1d57e47b8af68ca Mon Sep 17 00:00:00 2001 From: Akase Haruka Date: Mon, 2 Mar 2026 20:02:36 +0800 Subject: [PATCH 14/14] feat: integrate with EAS (#35) * integrate with EAS * reduce gas cost * add EAS test and adjust l1 gas * use timestamp instead of attest to reduce L1 gas * feat(eas): update rust side (#36) * update contracts * feat(eas): update ts sdk & web (#37) --- .gitmodules | 3 + Cargo.lock | 55 +- Cargo.toml | 4 +- .../components/verify/AttestationDetail.vue | 149 +- .../src/components/verify/MerkleTreeViz.vue | 119 +- apps/web/src/locales/zh.po | 11 +- contract-tests/EAS.t.sol | 37 + contract-tests/L2AnchoringManager.t.sol | 26 +- .../{MerkleTree.sol => MerkleTree.t.sol} | 2 +- contract-tests/Storage.t.sol | 2 +- contracts/L1/IL1AnchoringGateway.sol | 3 +- contracts/L1/L1AnchoringGateway.sol | 37 +- contracts/L1/L1AnchoringGatewayStorage.sol | 8 +- contracts/L2/manager/IL2AnchoringManager.sol | 61 +- contracts/L2/manager/L2AnchoringManager.sol | 102 +- .../L2/manager/L2AnchoringManagerStorage.sol | 14 +- .../L2/manager/L2AnchoringManagerTypes.sol | 5 +- contracts/L2/oracle/FeeOracle.sol | 6 +- contracts/L2/oracle/IFeeOracle.sol | 2 +- contracts/core/EASHelper.sol | 27 + contracts/core/IUniversalTimestamps.sol | 37 - contracts/core/MerkleTree.sol | 2 +- contracts/core/UniversalTimestamps.sol | 42 - .../core/UniversalTimestampsConstants.sol | 7 - crates/calendar/Cargo.toml | 1 + crates/calendar/src/main.rs | 10 +- crates/calendar/src/routes/ots.rs | 11 +- crates/cli/Cargo.toml | 4 +- crates/cli/src/commands/verify.rs | 114 +- crates/contracts/Cargo.toml | 1 + .../contracts/abi/IUniversalTimestamps.json | 1 - crates/contracts/abi/UniversalTimestamps.json | 1 - crates/contracts/src/lib.rs | 218 +- crates/core/Cargo.toml | 4 +- crates/core/src/codec/v1.rs | 4 +- crates/core/src/codec/v1/attestation.rs | 199 +- crates/core/src/verifier.rs | 16 +- crates/core/src/verifier/eas.rs | 125 + crates/core/src/verifier/ethereum_uts.rs | 145 - crates/stamper/src/lib.rs | 8 +- foundry.lock | 6 + foundry.toml | 2 +- lib/eas-contracts | 1 + packages/sdk/package.json | 1 + packages/sdk/src/codec/constants.ts | 7 +- packages/sdk/src/codec/decode.ts | 63 +- packages/sdk/src/codec/encode.ts | 53 +- packages/sdk/src/index.ts | 10 +- packages/sdk/src/sdk.ts | 170 +- packages/sdk/src/types.ts | 14 +- pnpm-lock.yaml | 2354 ++++++++++++++++- remappings.txt | 1 + 52 files changed, 3200 insertions(+), 1105 deletions(-) create mode 100644 contract-tests/EAS.t.sol rename contract-tests/{MerkleTree.sol => MerkleTree.t.sol} (98%) create mode 100644 contracts/core/EASHelper.sol delete mode 100644 contracts/core/IUniversalTimestamps.sol delete mode 100644 contracts/core/UniversalTimestamps.sol delete mode 100644 contracts/core/UniversalTimestampsConstants.sol delete mode 100644 crates/contracts/abi/IUniversalTimestamps.json delete mode 100644 crates/contracts/abi/UniversalTimestamps.json create mode 100644 crates/core/src/verifier/eas.rs delete mode 100644 crates/core/src/verifier/ethereum_uts.rs create mode 160000 lib/eas-contracts diff --git a/.gitmodules b/.gitmodules index a729b0e..9f4208d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/scroll-contracts"] path = lib/scroll-contracts url = https://github.com/scroll-tech/scroll-contracts +[submodule "lib/eas-contracts"] + path = lib/eas-contracts + url = https://github.com/ethereum-attestation-service/eas-contracts diff --git a/Cargo.lock b/Cargo.lock index ba3b4a6..7f02a87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4275,6 +4275,49 @@ dependencies = [ "rustc_version 0.4.1", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -5529,6 +5572,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.11" @@ -6281,6 +6330,7 @@ dependencies = [ name = "uts-calendar" version = "0.1.0" dependencies = [ + "alloy-chains", "alloy-primitives", "alloy-provider", "alloy-signer", @@ -6311,6 +6361,7 @@ dependencies = [ name = "uts-cli" version = "0.1.0" dependencies = [ + "alloy-primitives", "alloy-provider", "bytemuck", "clap", @@ -6329,6 +6380,7 @@ dependencies = [ "tracing", "url", "uts-bmt", + "uts-contracts", "uts-core", ] @@ -6342,6 +6394,7 @@ dependencies = [ "alloy-sol-types", "eyre", "futures", + "phf", "tokio", ] @@ -6350,9 +6403,9 @@ name = "uts-core" version = "0.1.0" dependencies = [ "alloy-chains", + "alloy-contract", "alloy-primitives", "alloy-provider", - "alloy-rpc-types-eth", "alloy-sol-types", "auto_impl", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 3bc242a..2d3270a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,12 +60,12 @@ itoa = "1.0" jiff = "0.2.20" once_cell = { version = "1.21", default-features = false } paste = "1.0" +phf = { version = "0.13.1", default-features = false } rand = "0.10" regex = "1.12" reqwest = { version = "0.13", default-features = false } rocksdb = "0.24" serde = "1.0" -serde-wasm-bindgen = "0.6" serde_json = "1.0" serde_with = "3.16" strum = "0.27" @@ -76,7 +76,7 @@ tower-http = "0.6" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } url = "2.5" -wasm-bindgen = "0.2" + crypto-common = "0.2.0-rc.5" digest = "0.11.0-rc.4" diff --git a/apps/web/src/components/verify/AttestationDetail.vue b/apps/web/src/components/verify/AttestationDetail.vue index 4c85ad0..61d1d59 100644 --- a/apps/web/src/components/verify/AttestationDetail.vue +++ b/apps/web/src/components/verify/AttestationDetail.vue @@ -122,7 +122,7 @@ function formatTimestamp(ts: bigint | number): string { -