diff --git a/packages/protocol/.env_sample b/packages/protocol/.env_sample new file mode 100644 index 000000000000..596455caafc0 --- /dev/null +++ b/packages/protocol/.env_sample @@ -0,0 +1,3 @@ +L2_GENESIS_HASH=0xdf90a9c4daa571aa308e967c9a6b4bf21ba8842d95d73d28be112b6fe0618e8c +PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +MAINNET_CONTRACT_OWNER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 #It shall belong to the PK above. I got this example ADDR-PK from local anvil. \ No newline at end of file diff --git a/packages/protocol/contracts/L1/BasedOperator.sol b/packages/protocol/contracts/L1/BasedOperator.sol index 4df89933a81d..699f4d220e8d 100644 --- a/packages/protocol/contracts/L1/BasedOperator.sol +++ b/packages/protocol/contracts/L1/BasedOperator.sol @@ -10,14 +10,16 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "../common/AddressResolver.sol"; import "../common/EssentialContract.sol"; import "../libs/LibAddress.sol"; +import "./preconfs/ISequencerRegistry.sol"; import "./TaikoL1.sol"; import "./TaikoData.sol"; +import "./TaikoErrors.sol"; import "./VerifierRegistry.sol"; import "./verifiers/IVerifier.sol"; /// @title BasedOperator /// @notice A based operator for Taiko. -contract BasedOperator is EssentialContract { +contract BasedOperator is EssentialContract, TaikoErrors { using LibAddress for address; struct Block { @@ -38,22 +40,27 @@ contract BasedOperator is EssentialContract { /// @dev Struct representing transition to be proven. struct ProofBatch { - TaikoData.BlockMetadata _block; + TaikoData.BlockMetadata blockMetadata; TaikoData.Transition transition; ProofData[] proofs; address prover; } - uint public constant PROVER_BOND = 1 ether / 10; - uint public constant MAX_GAS_PROVER_PAYMENT = 50_000; - uint public constant MAX_BLOCKS_TO_VERIFY = 5; - uint public constant PROVING_WINDOW = 1 hours; + uint256 public constant PROVER_BOND = 1 ether / 10; + uint256 public constant MAX_GAS_PROVER_PAYMENT = 50_000; + uint256 public constant MAX_BLOCKS_TO_VERIFY = 5; + uint256 public constant PROVING_WINDOW = 1 hours; - TaikoL1 public taiko; - VerifierRegistry public verifierRegistry; - address public treasury; + address public treasury; // (?) - mapping(uint => Block) public blocks; + mapping(uint256 => Block) public blocks; + + function init(address _owner, address _addressManager) external initializer { + if (_addressManager == address(0)) { + revert L1_INVALID_ADDRESS(); + } + __Essential_init(_owner, _addressManager); + } /// @dev Proposes a Taiko L2 block. function proposeBlock( @@ -69,46 +76,54 @@ contract BasedOperator is EssentialContract { { require(msg.value == PROVER_BOND, "Prover bond not expected"); - _block = taiko.proposeBlock(params, txList); + _block = TaikoL1(resolve("taiko", false)).proposeBlock(params, txList); // Check if we have whitelisted proposers - require(_isProposerPermitted(_block), "proposer not allowed"); + if (!_isProposerPermitted(_block)) { + revert L1_INVALID_PROPOSER(); + } // Store who paid for proving the block - blocks[_block.id] = Block({ - assignedProver: prover, - bond: uint96(PROVER_BOND) - }); + blocks[_block.l2BlockNumber] = Block({ assignedProver: prover, bond: uint96(PROVER_BOND) }); // Verify some blocks _verifyBlocks(MAX_BLOCKS_TO_VERIFY); } /// @dev Proposes a Taiko L2 block. - function proveBlock(bytes calldata data) - external - nonReentrant - whenNotPaused - { + function proveBlock(bytes calldata data) external nonReentrant whenNotPaused { // Decode the block data ProofBatch memory proofBatch = abi.decode(data, (ProofBatch)); // Check who can prove the block - TaikoData.Block memory taikoBlock = taiko.getBlock(proofBatch._block.id); - if (block.timestamp < taikoBlock.proposedAt + PROVING_WINDOW) { - require(proofBatch.prover == blocks[proofBatch._block.id].assignedProver, "assigned prover not the prover"); + TaikoData.Block memory taikoBlock = + TaikoL1(resolve("taiko", false)).getBlock(proofBatch.blockMetadata.l2BlockNumber); + if (block.timestamp < taikoBlock.timestamp + PROVING_WINDOW) { + require( + proofBatch.prover == blocks[proofBatch.blockMetadata.l2BlockNumber].assignedProver, + "assigned prover not the prover" + ); } + VerifierRegistry verifierRegistry = VerifierRegistry(resolve("verifier_registry", false)); + TaikoL1 taiko = TaikoL1(resolve("taiko", false)); // Verify the proofs uint160 prevVerifier = uint160(0); - for (uint i = 0; i < proofBatch.proofs.length; i++) { + for (uint256 i = 0; i < proofBatch.proofs.length; i++) { IVerifier verifier = proofBatch.proofs[i].verifier; // Make sure each verifier is unique - require(prevVerifier >= uint160(address(verifier)), "duplicated verifier"); + if (prevVerifier >= uint160(address(verifier))) { + revert L1_INVALID_OR_DUPLICATE_VERIFIER(); + } // Make sure it's a valid verifier require(verifierRegistry.isVerifier(address(verifier)), "invalid verifier"); // Verify the proof - verifier.verifyProof(proofBatch._block, proofBatch.transition, proofBatch.prover, proofBatch.proofs[i].proof); + verifier.verifyProof( + proofBatch.transition, + keccak256(abi.encode(proofBatch.blockMetadata)), + proofBatch.prover, + proofBatch.proofs[i].proof + ); prevVerifier = uint160(address(verifier)); } @@ -116,34 +131,48 @@ contract BasedOperator is EssentialContract { // Can use some custom logic here. but let's keep it simple require(proofBatch.proofs.length >= 3, "insufficient number of proofs"); - // Only allow an already proven block to be overwritten when the verifiers used are now invalid + // Only allow an already proven block to be overwritten when the verifiers used are now + // invalid // Get the currently stored transition - TaikoData.TransitionState memory storedTransition = taiko.getTransition(proofBatch._block.id, proofBatch.transition.parentHash); - if (storedTransition.blockHash != proofBatch.transition.blockHash) { - // TODO(Brecht): Check that one of the verifiers is now poissoned - } else { + TaikoData.TransitionState memory storedTransition = taiko.getTransition( + proofBatch.blockMetadata.l2BlockNumber, proofBatch.transition.parentBlockHash + ); + + // Somehow we need to check if this is proven already and IF YES and transition is trying to + // prove the same, then revert with "block already proven". + if ( + storedTransition.isProven == true + && storedTransition.blockHash == proofBatch.transition.blockHash + ) { revert("block already proven"); + } else { + // TODO(Brecht): Check that one of the verifiers is now poissoned } // Prove the block - taiko.proveBlock(proofBatch._block, proofBatch.transition, proofBatch.prover); + taiko.proveBlock(proofBatch.blockMetadata, proofBatch.transition, proofBatch.prover); // Verify some blocks _verifyBlocks(MAX_BLOCKS_TO_VERIFY); } - function verifyBlocks(uint maxBlocksToVerify) external nonReentrant whenNotPaused { + function verifyBlocks(uint256 maxBlocksToVerify) external nonReentrant whenNotPaused { _verifyBlocks(maxBlocksToVerify); } - function _verifyBlocks(uint maxBlocksToVerify) internal { - uint lastVerifiedBlockIdBefore = taiko.getLastVerifiedBlockId(); + function _verifyBlocks(uint256 maxBlocksToVerify) internal { + TaikoL1 taiko = TaikoL1(resolve("taiko", false)); + uint256 lastVerifiedBlockIdBefore = taiko.getLastVerifiedBlockId(); // Verify the blocks taiko.verifyBlocks(maxBlocksToVerify); - uint lastVerifiedBlockIdAfter = taiko.getLastVerifiedBlockId(); + uint256 lastVerifiedBlockIdAfter = taiko.getLastVerifiedBlockId(); // So some additional checks on top of the standard checks done in the rollup contract - for (uint blockId = lastVerifiedBlockIdBefore + 1; blockId <= lastVerifiedBlockIdAfter; blockId++) { + for ( + uint256 blockId = lastVerifiedBlockIdBefore + 1; + blockId <= lastVerifiedBlockIdAfter; + blockId++ + ) { Block storage blk = blocks[blockId]; // TODO(Brecht): Verify that all the verifers used to prove the block are still valid @@ -156,21 +185,15 @@ contract BasedOperator is EssentialContract { uint256 bondToReturn = blk.bond; if (prover != blk.assignedProver) { bondToReturn >>= 1; - treasury.sendEther(bondToReturn, MAX_GAS_PROVER_PAYMENT); + treasury.sendEtherAndVerify(bondToReturn, MAX_GAS_PROVER_PAYMENT); } - prover.sendEther(bondToReturn, MAX_GAS_PROVER_PAYMENT); + prover.sendEtherAndVerify(bondToReturn, MAX_GAS_PROVER_PAYMENT); } } // Additinal proposer rules - function _isProposerPermitted( - TaikoData.BlockMetadata memory _block - ) - private - view - returns (bool) - { - if (_block.id == 1) { + function _isProposerPermitted(TaikoData.BlockMetadata memory _block) private returns (bool) { + if (_block.l2BlockNumber == 1) { // Only proposer_one can propose the first block after genesis address proposerOne = resolve("proposer_one", true); if (proposerOne != address(0) && msg.sender != proposerOne) { @@ -178,7 +201,15 @@ contract BasedOperator is EssentialContract { } } - address proposer = resolve("proposer", true); - return proposer == address(0) || msg.sender == proposer; + // If there's a sequencer registry, check if the block can be proposed by the current + // proposer + ISequencerRegistry sequencerRegistry = + ISequencerRegistry(resolve("sequencer_registry", true)); + if (sequencerRegistry != ISequencerRegistry(address(0))) { + if (!sequencerRegistry.isEligibleSigner(msg.sender)) { + return false; + } + } + return true; } } diff --git a/packages/protocol/contracts/L1/ITaikoL1.sol b/packages/protocol/contracts/L1/ITaikoL1.sol new file mode 100644 index 000000000000..ca343a5ba571 --- /dev/null +++ b/packages/protocol/contracts/L1/ITaikoL1.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoData.sol"; + +/// @title ITaikoL1 +/// @custom:security-contact security@taiko.xyz +interface ITaikoL1 { + /// @notice Proposes a Taiko L2 block. + /// @param _params Block parameters, currently an encoded BlockParams object. + /// @param _txList txList data if calldata is used for DA. + /// @return meta_ The metadata of the proposed L2 block. + function proposeBlock( + bytes calldata _params, + bytes calldata _txList + ) + external + payable + returns (TaikoData.BlockMetadata memory meta_); + + /// @notice Proves or contests a block transition. + /// @param _blockId The index of the block to prove. This is also used to + /// select the right implementation version. + /// @param _input An abi-encoded (TaikoData.BlockMetadata, TaikoData.Transition, + /// TaikoData.TierProof) tuple. + function proveBlock(uint64 _blockId, bytes calldata _input) external; + + /// @notice Verifies up to a certain number of blocks. + /// @param _maxBlocksToVerify Max number of blocks to verify. + function verifyBlocks(uint64 _maxBlocksToVerify) external; + + /// @notice Pause block proving. + /// @param _pause True if paused. + function pauseProving(bool _pause) external; + + /// @notice Deposits Taiko token to be used as bonds. + /// @param _amount The amount of Taiko token to deposit. + function depositBond(uint256 _amount) external; + + /// @notice Withdraws Taiko token. + /// @param _amount The amount of Taiko token to withdraw. + function withdrawBond(uint256 _amount) external; + + // /// @notice Gets the prover that actually proved a verified block. + // /// @param _blockId The index of the block. + // /// @return The prover's address. If the block is not verified yet, address(0) will be + // returned. + // function getVerifiedBlockProver(uint64 _blockId) external view returns (address); + + /// @notice Gets the configuration of the TaikoL1 contract. + /// @return Config struct containing configuration parameters. + function getConfig() external pure returns (TaikoData.Config memory); +} diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index f2051fe1d499..be787595fa6f 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -23,16 +23,17 @@ library TaikoData { /// @dev Struct containing data only required for proving a block struct BlockMetadata { bytes32 blockHash; + bytes32 parentBlockHash; bytes32 parentMetaHash; bytes32 l1Hash; - uint difficulty; + uint256 difficulty; bytes32 blobHash; bytes32 extraData; address coinbase; - uint64 id; + uint64 l2BlockNumber; uint32 gasLimit; + uint32 l1StateBlockNumber; uint64 timestamp; - uint64 l1Height; uint24 txListByteOffset; uint24 txListByteSize; bool blobUsed; @@ -40,16 +41,17 @@ library TaikoData { /// @dev Struct representing transition to be proven. struct Transition { - bytes32 parentHash; + bytes32 parentBlockHash; bytes32 blockHash; } /// @dev Struct representing state transition data. struct TransitionState { - bytes32 blockHash; + bytes32 blockHash; //Might be removed.. uint64 timestamp; address prover; uint64 verifiableAfter; + bool isProven; } /// @dev Struct containing data required for verifying a block. @@ -57,23 +59,20 @@ library TaikoData { bytes32 blockHash; bytes32 metaHash; uint64 blockId; - uint64 proposedAt; - uint64 proposedIn; + uint64 timestamp; + uint32 l1StateBlockNumber; } /// @dev Struct holding the state variables for the {TaikoL1} contract. struct State { - mapping(uint blockId => Block) blocks; - mapping(uint blockId => mapping(bytes32 parentBlockHash => TransitionState)) transitions; - + mapping(uint256 blockId => Block) blocks; + mapping(uint256 blockId => mapping(bytes32 parentBlockHash => TransitionState)) transitions; uint64 genesisHeight; uint64 genesisTimestamp; - uint64 numBlocks; uint64 lastVerifiedBlockId; bool provingPaused; uint64 lastUnpausedAt; - uint256[143] __gap; } } diff --git a/packages/protocol/contracts/L1/TaikoErrors.sol b/packages/protocol/contracts/L1/TaikoErrors.sol index 58c155dc4f23..545a9843ab41 100644 --- a/packages/protocol/contracts/L1/TaikoErrors.sol +++ b/packages/protocol/contracts/L1/TaikoErrors.sol @@ -20,17 +20,22 @@ abstract contract TaikoErrors { error L1_BLOB_NOT_FOUND(); error L1_BLOB_NOT_REUSEABLE(); error L1_BLOCK_MISMATCH(); + error L1_INCORRECT_BLOCK(); error L1_INSUFFICIENT_TOKEN(); error L1_INVALID_ADDRESS(); error L1_INVALID_AMOUNT(); error L1_INVALID_BLOCK_ID(); error L1_INVALID_CONFIG(); error L1_INVALID_ETH_DEPOSIT(); + error L1_INVALID_L1_STATE_BLOCK(); + error L1_INVALID_OR_DUPLICATE_VERIFIER(); error L1_INVALID_PARAM(); error L1_INVALID_PAUSE_STATUS(); error L1_INVALID_PROOF(); + error L1_INVALID_PROPOSER(); error L1_INVALID_PROVER(); error L1_INVALID_TIER(); + error L1_INVALID_TIMESTAMP(); error L1_INVALID_TRANSITION(); error L1_LIVENESS_BOND_NOT_RECEIVED(); error L1_NOT_ASSIGNED_PROVER(); diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index 97cc63128151..06f77c1cad5f 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -19,22 +19,12 @@ abstract contract TaikoEvents { /// @param blockId The ID of the proposed block. /// @param meta The block metadata containing information about the proposed /// block. - event BlockProposed( - uint256 indexed blockId, - TaikoData.BlockMetadata meta - ); + event BlockProposed(uint256 indexed blockId, TaikoData.BlockMetadata meta); /// @dev Emitted when a block is verified. /// @param blockId The ID of the verified block. /// @param blockHash The hash of the verified block. - event BlockVerified( - uint256 indexed blockId, - bytes32 blockHash - ); + event BlockVerified(uint256 indexed blockId, bytes32 blockHash); /// @dev Emitted when a block transition is proved or re-proved. - event TransitionProved( - uint256 indexed blockId, - TaikoData.Transition tran, - address prover - ); + event TransitionProved(uint256 indexed blockId, TaikoData.Transition tran, address prover); } diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index c09e49288015..90603360f8c8 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -12,10 +12,9 @@ import "./TaikoEvents.sol"; /// @title TaikoL1 contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { - event ProvingPaused(bool paused); - uint public constant SECURITY_DELAY_AFTER_PROVEN = 8 hours; + uint256 public constant SECURITY_DELAY_AFTER_PROVEN = 8 hours; // According to EIP4844, each blob has up to 4096 field elements, and each // field element has 32 bytes. @@ -27,8 +26,15 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { /// @notice Initializes the rollup. /// @param _addressManager The {AddressManager} address. /// @param _genesisBlockHash The block hash of the genesis block. - function init(address _addressManager, bytes32 _genesisBlockHash) external initializer { - __Essential_init(_addressManager); + function init( + address _owner, + address _addressManager, + bytes32 _genesisBlockHash + ) + external + initializer + { + __Essential_init(_owner, _addressManager); TaikoData.Config memory config = getConfig(); require(isConfigValid(config), "invalid config"); @@ -41,12 +47,9 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { // Init the genesis block TaikoData.Block storage blk = state.blocks[0]; blk.blockHash = _genesisBlockHash; - blk.proposedAt = uint64(block.timestamp); + blk.timestamp = uint64(block.timestamp); - emit BlockVerified({ - blockId: 0, - blockHash: _genesisBlockHash - }); + emit BlockVerified({ blockId: 0, blockHash: _genesisBlockHash }); } /// Proposes a Taiko L2 block. @@ -61,8 +64,10 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { payable nonReentrant whenNotPaused - onlyFromNamed("operator") - returns (TaikoData.BlockMetadata memory _block) + returns ( + //onlyFromNamed("operator") + TaikoData.BlockMetadata memory _block + ) { TaikoData.Config memory config = getConfig(); @@ -71,17 +76,20 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { // Verify L1 data // TODO(Brecht): needs to be more configurable for preconfirmations - require(_block.l1Height == uint64(block.number - 1), "INVALID_L1_HEIGHT"); - require(_block.l1Hash == blockhash(block.number - 1), "INVALID_L1_BLOCKHASH"); + require(_block.l1Hash == blockhash(_block.l1StateBlockNumber), "INVALID_L1_BLOCKHASH"); + require(_block.blockHash != 0x0, "INVALID_L2_BLOCKHASH"); require(_block.difficulty == block.prevrandao, "INVALID_DIFFICULTY"); - require(_block.timestamp == uint64(block.timestamp), "INVALID_TIMESTAMP"); // Verify misc data require(_block.gasLimit == config.blockMaxGasLimit, "INVALID_GAS_LIMIT"); + require(_block.blobUsed == (txList.length == 0), "INVALID_BLOB_USED"); // Verify DA data if (_block.blobUsed) { //require(_block.blobHash == blobhash(0), "invalid data blob"); - require(uint256(_block.txListByteOffset) + _block.txListByteSize <= MAX_BYTES_PER_BLOB, "invalid blob size"); + require( + uint256(_block.txListByteOffset) + _block.txListByteSize <= MAX_BYTES_PER_BLOB, + "invalid blob size" + ); } else { require(_block.blobHash == keccak256(txList), "INVALID_TXLIST_HASH"); require(_block.txListByteOffset == 0, "INVALID_TXLIST_START"); @@ -89,31 +97,59 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { } // Check that the tx length is non-zero and within the supported range - require(_block.txListByteSize == 0 || _block.txListByteSize > config.blockMaxTxListBytes, "invalid txlist size"); + require( + _block.txListByteSize == 0 || _block.txListByteSize > config.blockMaxTxListBytes, + "invalid txlist size" + ); + + TaikoData.Block storage parentBlock = state.blocks[(state.numBlocks - 1)]; + + require(_block.parentMetaHash == parentBlock.metaHash, "invalid parentHash"); + + // Verify the passed in L1 state block number. + // We only allow the L1 block to be 4 epochs old. + // The other constraint is that the L1 block number needs to be larger than or equal the one + // in the previous L2 block. + if ( + _block.l1StateBlockNumber + 128 < block.number + || _block.l1StateBlockNumber >= block.number + || _block.l1StateBlockNumber < parentBlock.l1StateBlockNumber + ) { + revert L1_INVALID_L1_STATE_BLOCK(); + } + + // Verify the passed in timestamp. + // We only allow the timestamp to be 4 epochs old. + // The other constraint is that the timestamp needs to be larger than or equal the one + // in the previous L2 block. + if ( + _block.timestamp + 128 * 12 < block.timestamp || _block.timestamp > block.timestamp + || _block.timestamp < parentBlock.timestamp + ) { + revert L1_INVALID_TIMESTAMP(); + } // Create the block that will be stored onchain TaikoData.Block memory blk = TaikoData.Block({ blockHash: _block.blockHash, metaHash: keccak256(data), blockId: state.numBlocks, - proposedAt: uint64(block.timestamp), - proposedIn: uint64(block.number) + timestamp: _block.timestamp, + l1StateBlockNumber: _block.l1StateBlockNumber }); // Store the block state.blocks[state.numBlocks] = blk; - // Store the passed in block hash as in - state.transitions[blk.blockId][_block.parentMetaHash].blockHash = _block.blockHash; - state.transitions[blk.blockId][_block.parentMetaHash].verifiableAfter = uint64(block.timestamp) + 365 days; + // Store the passed in block hash as is + state.transitions[blk.blockId][_block.parentBlockHash].blockHash = _block.blockHash; + // Big enough number so that we are sure we don't hit that deadline in the future. + state.transitions[blk.blockId][_block.parentBlockHash].verifiableAfter = type(uint64).max; // Increment the counter (cursor) by 1. state.numBlocks++; - emit BlockProposed({ - blockId: _block.id, - meta: _block - }); + emit BlockProposed({ blockId: _block.l2BlockNumber, meta: _block }); } /// @notice Proves or contests a block transition. @@ -124,38 +160,41 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { TaikoData.BlockMetadata memory _block, TaikoData.Transition memory transition, address prover - ) + ) external nonReentrant whenNotPaused onlyFromNamed("operator") { // Check that the block has been proposed but has not yet been verified. - if (_block.id <= state.lastVerifiedBlockId || _block.id >= state.numBlocks) { + if ( + _block.l2BlockNumber <= state.lastVerifiedBlockId + || _block.l2BlockNumber >= state.numBlocks + ) { revert L1_INVALID_BLOCK_ID(); } - TaikoData.Block storage blk = state.blocks[_block.id]; + TaikoData.Block storage blk = state.blocks[_block.l2BlockNumber]; // Make sure the correct block was proven - require(blk.metaHash != keccak256(abi.encode(_block)), "incorrect block"); + if (blk.metaHash != keccak256(abi.encode(_block))) { + revert L1_INCORRECT_BLOCK(); + } // Store the transition - TaikoData.TransitionState storage storedTransition = state.transitions[_block.id][transition.parentHash]; + TaikoData.TransitionState storage storedTransition = + state.transitions[_block.l2BlockNumber][transition.parentBlockHash]; storedTransition.blockHash = transition.blockHash; storedTransition.prover = prover; storedTransition.verifiableAfter = uint32(block.timestamp + SECURITY_DELAY_AFTER_PROVEN); + storedTransition.isProven = true; - emit TransitionProved({ - blockId: _block.id, - tran: transition, - prover: prover - }); + emit TransitionProved({ blockId: _block.l2BlockNumber, tran: transition, prover: prover }); } /// @notice Verifies up to N blocks. /// @param maxBlocksToVerify Max number of blocks to verify. - function verifyBlocks(uint maxBlocksToVerify) + function verifyBlocks(uint256 maxBlocksToVerify) external nonReentrant whenNotPaused @@ -164,28 +203,28 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { // Get the last verified blockhash TaikoData.Block storage blk = state.blocks[state.lastVerifiedBlockId]; bytes32 blockHash = blk.blockHash; - // Go to the first unverified block - uint blockId = uint(state.lastVerifiedBlockId) + 1; - uint numBlocksVerified; + uint256 blockId = uint256(state.lastVerifiedBlockId) + 1; + uint256 numBlocksVerified; + while (blockId < state.numBlocks && numBlocksVerified < maxBlocksToVerify) { blk = state.blocks[blockId]; - - // Check if the parent block hash matches the actual block hash of the parent // Check if the timestamp is older than required - if (state.transitions[blockId][blockHash].blockHash == bytes32(0) || - block.timestamp < state.transitions[blockId][blockHash].verifiableAfter) { + if ( + block + // Genesis is already verified with initialization so if we do not allow to set + // blockHash = bytes32(0), then we can remove the bytes32(0) check. + /*state.transitions[blockId][blockHash].blockHash == bytes32(0) + || */ + .timestamp < state.transitions[blockId][blockHash].verifiableAfter + ) { break; } - // Copy the blockhash to the block blk.blockHash = state.transitions[blockId][blockHash].blockHash; // Update latest block hash blockHash = blk.blockHash; - emit BlockVerified({ - blockId: blockId, - blockHash: blockHash - }); + emit BlockVerified({ blockId: blockId, blockHash: blockHash }); ++blockId; ++numBlocksVerified; @@ -216,12 +255,23 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { return state.blocks[blockId]; } - function getTransition(uint64 blockId, bytes32 parentHash) public view returns (TaikoData.TransitionState memory) { + function getTransition( + uint64 blockId, + bytes32 parentHash + ) + public + view + returns (TaikoData.TransitionState memory) + { return state.transitions[blockId][parentHash]; } - function getLastVerifiedBlockId() public view returns (uint) { - return uint(state.lastVerifiedBlockId); + function getLastVerifiedBlockId() public view returns (uint256) { + return uint256(state.lastVerifiedBlockId); + } + + function getNumOfBlocks() public view returns (uint256) { + return uint256(state.numBlocks); } /// @notice Gets the configuration of the TaikoL1 contract. @@ -238,7 +288,7 @@ contract TaikoL1 is EssentialContract, TaikoEvents, TaikoErrors { }); } - function isConfigValid(TaikoData.Config memory config ) public pure returns (bool) { + function isConfigValid(TaikoData.Config memory config) public pure returns (bool) { if ( config.chainId <= 1 // || config.blockMaxGasLimit == 0 || config.blockMaxTxListBytes == 0 diff --git a/packages/protocol/contracts/L1/TaikoToken.sol b/packages/protocol/contracts/L1/TaikoToken.sol deleted file mode 100644 index 5d0f6195253c..000000000000 --- a/packages/protocol/contracts/L1/TaikoToken.sol +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20SnapshotUpgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; -import "../common/EssentialContract.sol"; - -/// @title TaikoToken -/// @dev Labeled in AddressResolver as "taiko_token" -/// @notice The TaikoToken (TKO), in the protocol is used for prover collateral -/// in the form of bonds. It is an ERC20 token with 18 decimal places of -/// precision. -contract TaikoToken is EssentialContract, ERC20SnapshotUpgradeable, ERC20VotesUpgradeable { - error TKO_INVALID_ADDR(); - error TKO_INVALID_PREMINT_PARAMS(); - - /// @notice Initializes the TaikoToken contract and mints initial tokens. - /// @param _name The name of the token. - /// @param _symbol The symbol of the token. - /// @param _recipient The address to receive initial token minting. - function init( - string calldata _name, - string calldata _symbol, - address _recipient - ) - public - initializer - { - __Essential_init(); - __ERC20_init(_name, _symbol); - __ERC20Snapshot_init(); - __ERC20Votes_init(); - - // Mint 1 billion tokens - _mint(_recipient, 1_000_000_000 ether); - } - - /// @notice Mints new tokens to the specified address. - /// @param to The address to receive the minted tokens. - /// @param amount The amount of tokens to mint. - function mint(address to, uint256 amount) public onlyOwner { - _mint(to, amount); - } - - /// @notice Burns tokens from the specified address. - /// @param from The address to burn tokens from. - /// @param amount The amount of tokens to burn. - function burn(address from, uint256 amount) public onlyOwner { - _burn(from, amount); - } - - /// @notice Creates a new token snapshot. - function snapshot() public onlyOwner { - _snapshot(); - } - - /// @notice Transfers tokens to a specified address. - /// @param to The address to transfer tokens to. - /// @param amount The amount of tokens to transfer. - /// @return A boolean indicating whether the transfer was successful or not. - function transfer(address to, uint256 amount) public override returns (bool) { - if (to == address(this)) revert TKO_INVALID_ADDR(); - return super.transfer(to, amount); - } - - /// @notice Transfers tokens from one address to another. - /// @param from The address to transfer tokens from. - /// @param to The address to transfer tokens to. - /// @param amount The amount of tokens to transfer. - /// @return A boolean indicating whether the transfer was successful or not. - function transferFrom( - address from, - address to, - uint256 amount - ) - public - override - returns (bool) - { - if (to == address(this)) revert TKO_INVALID_ADDR(); - return super.transferFrom(from, to, amount); - } - - function _beforeTokenTransfer( - address from, - address to, - uint256 amount - ) - internal - override(ERC20Upgradeable, ERC20SnapshotUpgradeable) - { - super._beforeTokenTransfer(from, to, amount); - } - - function _afterTokenTransfer( - address from, - address to, - uint256 amount - ) - internal - override(ERC20Upgradeable, ERC20VotesUpgradeable) - { - super._afterTokenTransfer(from, to, amount); - } - - function _mint( - address to, - uint256 amount - ) - internal - override(ERC20Upgradeable, ERC20VotesUpgradeable) - { - super._mint(to, amount); - } - - function _burn( - address from, - uint256 amount - ) - internal - override(ERC20Upgradeable, ERC20VotesUpgradeable) - { - super._burn(from, amount); - } -} diff --git a/packages/protocol/contracts/L1/VerifierBattleRoyale.sol b/packages/protocol/contracts/L1/VerifierBattleRoyale.sol index a62cef871b85..2d7b149a1a72 100644 --- a/packages/protocol/contracts/L1/VerifierBattleRoyale.sol +++ b/packages/protocol/contracts/L1/VerifierBattleRoyale.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.20; - import "../common/AddressResolver.sol"; import "../common/EssentialContract.sol"; import "../libs/LibAddress.sol"; @@ -20,26 +19,28 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; /// @notice A permissionless bounty to claim a reward for breaking a prover contract VerifierBattleRoyale is EssentialContract { struct Bounty { - uint startedAt; - uint rate; // per second - uint maxReward; - uint claimedAt; + uint256 startedAt; + uint256 rate; // per second + uint256 maxReward; + uint256 claimedAt; address winner; } /// @dev Struct representing transition to be proven. struct ProofData { IVerifier verifier; - TaikoData.Transition transition; + TaikoData.Transition transition; // This differs from BasedOperator ! Mainly because of + // transition comparison for the battle!! bytes proof; } struct ProofBatch { - TaikoData.BlockMetadata _block; + TaikoData.BlockMetadata blockMetadata; ProofData[] proofs; address prover; } - uint constant public PERCENTAGE_CLAIMED_IMMEDIATELY = 25; + + uint256 public constant PERCENTAGE_CLAIMED_IMMEDIATELY = 25; VerifierRegistry public verifierRegistry; mapping(address verifier => Bounty) public bounties; @@ -49,18 +50,13 @@ contract VerifierBattleRoyale is EssentialContract { } /// @dev Proposes a Taiko L2 block. - function openBounty(address verifier, Bounty memory bounty) - external - onlyOwner() - { + function openBounty(address verifier, Bounty memory bounty) external onlyOwner { require(bounty.winner == address(0), "winner needs to be set to 0"); bounties[verifier] = bounty; } // Allows anyone to claim the bounty be proving that some verifier is broken - function claimBounty(address brokenVerifier, bytes calldata data) - external - { + function claimBounty(address brokenVerifier, bytes calldata data) external { require(bounties[brokenVerifier].startedAt != 0, "bounty doesn't exist"); require(bounties[brokenVerifier].winner == address(0), "bounty already claimed"); @@ -68,50 +64,59 @@ contract VerifierBattleRoyale is EssentialContract { ProofBatch memory proofBatch = abi.decode(data, (ProofBatch)); // Verify the all the proofs - for (uint i = 0; i < proofBatch.proofs.length; i++) { + for (uint256 i = 0; i < proofBatch.proofs.length; i++) { IVerifier verifier = proofBatch.proofs[i].verifier; require(verifierRegistry.isVerifier(address(verifier)), "invalid verifier"); - verifier.verifyProof(proofBatch._block, proofBatch.proofs[i].transition, proofBatch.prover, proofBatch.proofs[i].proof); + verifier.verifyProof( + proofBatch.proofs[i].transition, + keccak256(abi.encode(proofBatch.blockMetadata)), + proofBatch.prover, + proofBatch.proofs[i].proof + ); } if (proofBatch.proofs.length == 2) { /* Same verifier, same block, but different blockhashes/signalroots */ - require(proofBatch.proofs[0].verifier == proofBatch.proofs[1].verifier, "verifiers not the same"); + require( + proofBatch.proofs[0].verifier == proofBatch.proofs[1].verifier, + "verifiers not the same" + ); TaikoData.Transition memory transitionA = proofBatch.proofs[0].transition; TaikoData.Transition memory transitionB = proofBatch.proofs[1].transition; - require(transitionA.parentHash == transitionB.parentHash, "parentHash not the same"); require( - transitionA.blockHash != transitionB.blockHash, - "blockhash the same" + transitionA.parentBlockHash == transitionB.parentBlockHash, + "parentHash not the same" ); + require(transitionA.blockHash != transitionB.blockHash, "blockhash the same"); } else if (proofBatch.proofs.length == 3) { /* Multiple verifiers in a consensus show that another verifier is faulty */ // Check that all verifiers are unique // Verify the proofs uint160 prevVerifier = 0; - for (uint i = 0; i < proofBatch.proofs.length; i++) { - require(prevVerifier >= uint160(address(proofBatch.proofs[i].verifier)), "duplicated verifier"); + for (uint256 i = 0; i < proofBatch.proofs.length; i++) { + require( + prevVerifier >= uint160(address(proofBatch.proofs[i].verifier)), + "duplicated verifier" + ); prevVerifier = uint160(address(proofBatch.proofs[i].verifier)); } - // Reference proofs need to be placed first in the array, the faulty proof is listed last - for (uint i = 0; i < proofBatch.proofs.length - 1; i++) { + // Reference proofs need to be placed first in the array, the faulty proof is listed + // last + for (uint256 i = 0; i < proofBatch.proofs.length - 1; i++) { TaikoData.Transition memory transitionA = proofBatch.proofs[i].transition; - TaikoData.Transition memory transitionB = proofBatch.proofs[i+1].transition; - require(transitionA.parentHash == transitionB.parentHash, "parentHash not the same"); + TaikoData.Transition memory transitionB = proofBatch.proofs[i + 1].transition; + require( + transitionA.parentBlockHash == transitionB.parentBlockHash, + "parentHash not the same" + ); if (i < proofBatch.proofs.length - 2) { - require( - transitionA.blockHash == transitionB.blockHash, - "blockhash the same" - ); + require(transitionA.blockHash == transitionB.blockHash, "blockhash the same"); } else { - require( - transitionA.blockHash != transitionB.blockHash, - "blockhash the same" - ); + require(transitionA.blockHash != transitionB.blockHash, "blockhash the same"); } } } else { @@ -123,7 +128,8 @@ contract VerifierBattleRoyale is EssentialContract { bounties[brokenVerifier].winner = msg.sender; // Distribute part of the reward immediately - uint initialReward = (calculateTotalReward(bounties[brokenVerifier]) * PERCENTAGE_CLAIMED_IMMEDIATELY) / 100; + uint256 initialReward = + (calculateTotalReward(bounties[brokenVerifier]) * PERCENTAGE_CLAIMED_IMMEDIATELY) / 100; IERC20 tko = IERC20(resolve("taiko_token", false)); tko.transfer(bounties[brokenVerifier].winner, initialReward); @@ -133,16 +139,16 @@ contract VerifierBattleRoyale is EssentialContract { // Called after the one who claimed a bounty has either disclosed // how the prover was broken or not - function closeBounty(address verifier, bool disclosed) - external - onlyOwner() - { + function closeBounty(address verifier, bool disclosed) external onlyOwner { require(bounties[verifier].winner != address(0), "bounty not claimed yet"); - // Transfer out the remaining locked part only the winner has disclosed how the prover was broken + // Transfer out the remaining locked part only the winner has disclosed how the prover was + // broken if (disclosed) { // Distribute the remaining part of the reward - uint remainingReward = (calculateTotalReward(bounties[verifier]) * (100 - PERCENTAGE_CLAIMED_IMMEDIATELY)) / 100; + uint256 remainingReward = ( + calculateTotalReward(bounties[verifier]) * (100 - PERCENTAGE_CLAIMED_IMMEDIATELY) + ) / 100; IERC20 tko = IERC20(resolve("taiko_token", false)); tko.transfer(bounties[verifier].winner, remainingReward); } @@ -152,12 +158,8 @@ contract VerifierBattleRoyale is EssentialContract { delete bounties[verifier]; } - function calculateTotalReward(Bounty memory bounty) - internal - pure - returns (uint) - { - uint accumulated = (bounty.claimedAt - bounty.startedAt) * bounty.rate; + function calculateTotalReward(Bounty memory bounty) internal pure returns (uint256) { + uint256 accumulated = (bounty.claimedAt - bounty.startedAt) * bounty.rate; if (accumulated > bounty.maxReward) { accumulated = bounty.maxReward; } diff --git a/packages/protocol/contracts/L1/VerifierRegistry.sol b/packages/protocol/contracts/L1/VerifierRegistry.sol index 557d91b83317..318950bc5af5 100644 --- a/packages/protocol/contracts/L1/VerifierRegistry.sol +++ b/packages/protocol/contracts/L1/VerifierRegistry.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.20; - import "../common/AddressResolver.sol"; import "../common/EssentialContract.sol"; @@ -20,8 +19,8 @@ contract VerifierRegistry is EssentialContract { } mapping(address verifier => Verifier) public verifiers; - mapping(address verifier => uint id) public verifierId; - mapping(uint id => address verifier) public verifierAddress; + mapping(address verifier => uint256 id) public verifierId; + mapping(uint256 id => address verifier) public verifierAddress; uint16 public verifierIdGenerator; @@ -31,37 +30,20 @@ contract VerifierRegistry is EssentialContract { } /// Adds a verifier - function addVerifier( - address verifier, - bytes4 tag - ) - external - onlyOwner() - { + function addVerifier(address verifier, bytes4 tag) external onlyOwner { // Generate a unique id uint16 id = verifierIdGenerator++; - verifiers[verifier] = Verifier({ - id: id, - tag: tag, - poisoned: false - }); + verifiers[verifier] = Verifier({ id: id, tag: tag, poisoned: false }); verifierId[verifier] = id; verifierAddress[id] = verifier; } /// Makes a verifier unusable - function poisonVerifier(address verifier) - external - onlyFromOwnerOrNamed("verifier_watchdog") - { + function poisonVerifier(address verifier) external onlyFromOwnerOrNamed("verifier_watchdog") { delete verifiers[verifier]; } - function isVerifier(address addr) - external - view - returns (bool) - { + function isVerifier(address addr) external view returns (bool) { return verifiers[addr].id != 0 && !verifiers[addr].poisoned; } } diff --git a/packages/protocol/contracts/L1/actors/PBSActor.sol b/packages/protocol/contracts/L1/actors/PBSActor.sol index 18b56ebe3468..e6ec245317cd 100644 --- a/packages/protocol/contracts/L1/actors/PBSActor.sol +++ b/packages/protocol/contracts/L1/actors/PBSActor.sol @@ -24,19 +24,20 @@ contract PBSActor { bytes calldata params, bytes calldata txList, bytes memory proverPaymentData, - bytes32 parentMetaHash, - uint tip + bytes32 parentHash, + uint256 tip ) external payable { // TODO(Brecht): just pass in opaque data to make it general, though kind of doesn't matter - TaikoData.BlockMetadata memory _block = operator.proposeBlock{ value: msg.value - tip }(params, txList, proverPaymentData); + TaikoData.BlockMetadata memory _block = + operator.proposeBlock{ value: msg.value - tip }(params, txList, proverPaymentData); // Check if parent block has the right meta hash - require(keccak256(abi.encode(_block)) == parentMetaHash, "unexpected parent"); + require(keccak256(abi.encode(_block)) == parentHash, "unexpected parent"); // Do conditional payment - address(block.coinbase).sendEther(tip); + address(block.coinbase).sendEtherAndVerify(tip); } } diff --git a/packages/protocol/contracts/L1/actors/ProverPayment.sol b/packages/protocol/contracts/L1/actors/ProverPayment.sol index f97ac2973e37..0075adb5b21f 100644 --- a/packages/protocol/contracts/L1/actors/ProverPayment.sol +++ b/packages/protocol/contracts/L1/actors/ProverPayment.sol @@ -18,7 +18,7 @@ contract ProverPayment { struct ProverAssignment { address prover; - uint fee; + uint256 fee; uint64 maxBlockId; uint64 maxProposedIn; bytes32 metaHash; @@ -27,7 +27,7 @@ contract ProverPayment { BasedOperator public operator; - mapping(address => uint) public balances; + mapping(address => uint256) public balances; // Max gas paying the prover. This should be large enough to prevent the // worst cases, usually block proposer shall be aware the risks and only @@ -51,7 +51,9 @@ contract ProverPayment { balances[assignment.prover] -= operator.PROVER_BOND(); // Propose the block - _block = operator.proposeBlock{value: operator.PROVER_BOND()}(params, txList, assignment.prover); + _block = operator.proposeBlock{ value: operator.PROVER_BOND() }( + params, txList, assignment.prover + ); // Hash the assignment with the blobHash, this hash will be signed by // the prover, therefore, we add a string as a prefix. @@ -59,21 +61,18 @@ contract ProverPayment { require(assignment.prover.isValidSignature(hash, assignment.signature), "invalid signature"); // Check assignment validity - require((assignment.metaHash != 0 || keccak256(abi.encode(_block)) != assignment.metaHash) - && (assignment.maxBlockId != 0 || _block.id > assignment.maxBlockId) - && (assignment.maxProposedIn != 0 || block.number > assignment.maxProposedIn), + require( + (assignment.metaHash != 0 || keccak256(abi.encode(_block)) != assignment.metaHash) + && (assignment.maxBlockId != 0 || _block.l2BlockNumber > assignment.maxBlockId) + && (assignment.maxProposedIn != 0 || block.number > assignment.maxProposedIn), "unexpected block" ); // Pay the prover - assignment.prover.sendEther(msg.value, MAX_GAS_PAYING_PROVER); + assignment.prover.sendEtherAndVerify(msg.value, MAX_GAS_PAYING_PROVER); } - function hashAssignment(ProverAssignment memory assignment) - internal - view - returns (bytes32) - { + function hashAssignment(ProverAssignment memory assignment) internal view returns (bytes32) { return keccak256( abi.encode( "PROVER_ASSIGNMENT", @@ -87,18 +86,13 @@ contract ProverPayment { ); } - function deposit(address to) - external - payable - { + function deposit(address to) external payable { balances[to] += msg.value; } // TODO(Brecht): delay - function witdraw(address from, address to, uint amount) - external - { + function witdraw(address from, address to, uint256 amount) external { balances[from] -= amount; - to.sendEther(amount); + to.sendEtherAndVerify(amount); } } diff --git a/packages/protocol/contracts/L1/preconfs/ISequencerRegistry.sol b/packages/protocol/contracts/L1/preconfs/ISequencerRegistry.sol new file mode 100644 index 000000000000..a0bfcbf5d531 --- /dev/null +++ b/packages/protocol/contracts/L1/preconfs/ISequencerRegistry.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title ISequencerRegistry +/// @custom:security-contact security@taiko.xyz +interface ISequencerRegistry { + /// @notice Return true if the specified address can propose blocks, false otherwise + /// @param _proposer The address proposing a block + function isEligibleSigner(address _proposer) external returns (bool); +} diff --git a/packages/protocol/contracts/L1/preconfs/SequencerRegistry.sol b/packages/protocol/contracts/L1/preconfs/SequencerRegistry.sol new file mode 100644 index 000000000000..7842512169f7 --- /dev/null +++ b/packages/protocol/contracts/L1/preconfs/SequencerRegistry.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../../common/EssentialContract.sol"; +import "./ISequencerRegistry.sol"; + +/// @title SequencerRegistry +/// A dummy implementation that only whitelist some trusted addresses. A real +/// implementation would only allow a single proposer address to propose a block +/// using some selection mechanism. +/// @custom:security-contact security@taiko.xyz +contract SequencerRegistry is EssentialContract, ISequencerRegistry { + /// @dev Emitted when the status of a sequencer is updated. + /// @param sequencer The address of the sequencer whose state has updated. + /// @param enabled If the sequencer is now enabled or not. + event SequencerUpdated(address indexed sequencer, bool enabled); + + /// @notice Whitelisted sequencers + mapping(address sequencer => bool enabled) public sequencers; + + uint256[49] private __gap; + + /// @notice Initializes the contract with the provided address manager. + /// @param _owner The address of the owner. + function init(address _owner) external initializer { + __Essential_init(_owner); + } + + /// @notice Sets/unsets an the imageId as trusted entity + /// @param _sequencers The list of sequencers + /// @param _enabled The corresponding list of the new status of the sequencers + function setSequencers( + address[] memory _sequencers, + bool[] memory _enabled + ) + external + onlyOwner + { + require(_sequencers.length == _enabled.length, "invalid input data"); + for (uint256 i = 0; i < _sequencers.length; i++) { + sequencers[_sequencers[i]] = _enabled[i]; + emit SequencerUpdated(_sequencers[i], _enabled[i]); + } + } + + /// @inheritdoc ISequencerRegistry + function isEligibleSigner(address _proposer) external view returns (bool) { + return sequencers[_proposer]; + } +} diff --git a/packages/protocol/contracts/L1/provers/GuardianProver.sol b/packages/protocol/contracts/L1/provers/GuardianProver.sol index 166f3755a164..539b9858add4 100644 --- a/packages/protocol/contracts/L1/provers/GuardianProver.sol +++ b/packages/protocol/contracts/L1/provers/GuardianProver.sol @@ -29,7 +29,7 @@ contract GuardianProver is Guardians { returns (bool approved) { bytes32 hash = keccak256(abi.encode(meta, tran)); - approved = approve(meta.id, hash); + approved = approve(meta.l2BlockNumber, hash); if (approved) { deleteApproval(hash); diff --git a/packages/protocol/contracts/L1/provers/GuardianProver_tm.sol b/packages/protocol/contracts/L1/provers/GuardianProver_tm.sol new file mode 100644 index 000000000000..5638929f2350 --- /dev/null +++ b/packages/protocol/contracts/L1/provers/GuardianProver_tm.sol @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../../common/EssentialContract.sol"; +import "../../common/LibStrings.sol"; +import "../verifiers/IVerifier.sol"; +import "../ITaikoL1.sol"; + +/// @dev IMPORTANT NOTICE!! +/// @title GuardianProver - brought over from taiko-mono repository BUT MADE SOME CHANGES to make it +/// compiling. If we won't use, we can just delete/ignore this. +/// This prover uses itself as the verifier. +/// @custom:security-contact security@taiko.xyz +contract GuardianProver is EssentialContract { + /// @notice Contains the index of the guardian in `guardians` plus one (zero means not a + /// guardian) + /// @dev Slot 1 + mapping(address guardian => uint256 id) public guardianIds; + + /// @notice Mapping to store the approvals for a given hash, for a given version + mapping(uint256 version => mapping(bytes32 proofHash => uint256 approvalBits)) public approvals; + + /// @notice The set of guardians + /// @dev Slot 3 + address[] public guardians; + + /// @notice The version of the guardians + /// @dev Slot 4 + uint32 public version; + + /// @notice The minimum number of guardians required to approve + uint32 public minGuardians; + + /// @notice True to enable pausing taiko proving upon conflicting proofs + bool public provingAutoPauseEnabled; + + /// @notice Mapping from blockId to its latest proof hash + /// @dev Slot 5 + mapping(uint256 version => mapping(uint256 blockId => bytes32 hash)) public latestProofHash; + + uint256[45] private __gap; + + /// @notice Emitted when a guardian proof is approved. + /// @param addr The address of the guardian. + /// @param blockId The block ID. + /// @param blockHash The block hash. + /// @param approved If the proof is approved. + /// @param proofData The proof data. + event GuardianApproval( + address indexed addr, + uint256 indexed blockId, + bytes32 indexed blockHash, + bool approved, + bytes proofData + ); + + /// @notice Emitted when the set of guardians is updated + /// @param version The new version + /// @param guardians The new set of guardians + event GuardiansUpdated(uint32 version, address[] guardians); + + /// @notice Emitted when an approval is made + /// @param operationId The operation ID + /// @param approvalBits The new approval bits + /// @param minGuardiansReached If the proof was submitted + event Approved(uint256 indexed operationId, uint256 approvalBits, bool minGuardiansReached); + + /// @notice Emitted when a guardian prover submit a different proof for the same block + /// @param blockId The block ID + /// @param guardian The guardian prover address + /// @param currentProofHash The existing proof hash + /// @param newProofHash The new and different proof hash + /// @param provingPaused True if TaikoL1's proving is paused. + event ConflictingProofs( + uint256 indexed blockId, + address indexed guardian, + bytes32 currentProofHash, + bytes32 newProofHash, + bool provingPaused + ); + + /// @notice Emitted when auto pausing is enabled. + /// @param enabled True if TaikoL1 proving auto-pause is enabled. + event ProvingAutoPauseEnabled(bool indexed enabled); + + error GP_INVALID_GUARDIAN(); + error GP_INVALID_GUARDIAN_SET(); + error GP_INVALID_MIN_GUARDIANS(); + error GP_INVALID_STATUS(); + error GV_PERMISSION_DENIED(); + error GV_ZERO_ADDRESS(); + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + } + + /// @notice Set the set of guardians + /// @param _newGuardians The new set of guardians + /// @param _minGuardians The minimum required to sign + /// @param _clearData true to invalidate all existing data. + function setGuardians( + address[] memory _newGuardians, + uint8 _minGuardians, + bool _clearData + ) + external + onlyOwner + { + // We need at most 255 guardians (so the approval bits fit in a uint256) + if (_newGuardians.length == 0 || _newGuardians.length > type(uint8).max) { + revert GP_INVALID_GUARDIAN_SET(); + } + // Minimum number of guardians to approve is at least equal or greater than half the + // guardians (rounded up) and less or equal than the total number of guardians + if (_minGuardians == 0 || _minGuardians > _newGuardians.length) { + revert GP_INVALID_MIN_GUARDIANS(); + } + + // Delete the current guardians + for (uint256 i; i < guardians.length; ++i) { + delete guardianIds[guardians[i]]; + } + delete guardians; + + // Set the new guardians + for (uint256 i; i < _newGuardians.length; ++i) { + address guardian = _newGuardians[i]; + if (guardian == address(0)) revert GP_INVALID_GUARDIAN(); + // This makes sure there are not duplicate addresses + if (guardianIds[guardian] != 0) revert GP_INVALID_GUARDIAN_SET(); + + // Save and index the guardian + guardians.push(guardian); + guardianIds[guardian] = guardians.length; + } + + // Bump the version so previous approvals get invalidated + if (_clearData) ++version; + + minGuardians = _minGuardians; + emit GuardiansUpdated(version, _newGuardians); + } + + /// @dev Enables or disables proving auto pause. + /// @param _enable true to enable, false to disable. + function enableProvingAutoPause(bool _enable) external onlyOwner { + if (provingAutoPauseEnabled == _enable) revert GP_INVALID_STATUS(); + provingAutoPauseEnabled = _enable; + + emit ProvingAutoPauseEnabled(_enable); + } + + /// @notice Enables unlimited allowance for Taiko L1 contract. + /// param _enable true if unlimited allowance is approved, false to set the allowance to 0. + function enableTaikoTokenAllowance(bool _enable) external onlyOwner { + address tko = resolve(LibStrings.B_TAIKO_TOKEN, false); + address taiko = resolve(LibStrings.B_TAIKO, false); + IERC20(tko).approve(taiko, _enable ? type(uint256).max : 0); + } + + /// @dev Withdraws Taiko Token to a given address. + /// @param _to The recipient address. + /// @param _amount The amount of Taiko token to withdraw. Use 0 for all balance. + function withdrawTaikoToken(address _to, uint256 _amount) external onlyOwner { + if (_to == address(0)) revert GV_ZERO_ADDRESS(); + + IERC20 tko = IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)); + uint256 amount = _amount == 0 ? tko.balanceOf(address(this)) : _amount; + tko.transfer(_to, amount); + } + + /// @dev Called by guardians to approve a guardian proof + /// @param _meta The block's metadata. + /// @param _tran The valid transition. + /// @return approved_ True if the minimum number of approval is acquired, false otherwise. + function approve( + TaikoData.BlockMetadata calldata _meta, + TaikoData.Transition calldata _tran /*, + TaikoData.TierProof calldata _proof*/ + ) + external + whenNotPaused + nonReentrant + returns (bool approved_) + { + bytes32 proofHash = keccak256(abi.encode(_meta, _tran, "")); //"" shall be removed + uint256 _version = version; + bytes32 currProofHash = latestProofHash[_version][0]; // constant ID for now.. for taiko-mon + // vs. taiko simplified comp. + + if (currProofHash == 0) { + latestProofHash[_version][0] = proofHash; + currProofHash = proofHash; + } + + bool conflicting = currProofHash != proofHash; + bool pauseProving = conflicting && provingAutoPauseEnabled + && address(this) == resolve(LibStrings.B_CHAIN_WATCHDOG, true); + + if (conflicting) { + latestProofHash[_version][0] = proofHash; + emit ConflictingProofs(0, msg.sender, currProofHash, proofHash, pauseProving); + } + + if (pauseProving) { + ITaikoL1(resolve(LibStrings.B_TAIKO, false)).pauseProving(true); + } else { + approved_ = _approve(0, proofHash); + emit GuardianApproval(msg.sender, 0, _tran.blockHash, approved_, ""); // "" = empty + // bytes + + if (approved_) { + delete approvals[_version][proofHash]; + delete latestProofHash[_version][0]; + + ITaikoL1(resolve(LibStrings.B_TAIKO, false)).proveBlock( + 0, + abi.encode(_meta, _tran, "") // empty bytes as proof for taiko-simplified vs. + // taiko-mono comp + ); + } + } + } + + /// @notice Pauses chain proving and verification. + function pauseTaikoProving() external whenNotPaused { + if (guardianIds[msg.sender] == 0) revert GP_INVALID_GUARDIAN(); + + if (address(this) != resolve(LibStrings.B_CHAIN_WATCHDOG, true)) { + revert GV_PERMISSION_DENIED(); + } + + ITaikoL1(resolve(LibStrings.B_TAIKO, false)).pauseProving(true); + } + + function verifyProof( + /*Context calldata _ctx,*/ + TaikoData.Transition calldata /*, + TaikoData.TierProof calldata*/ + ) + external + view + { + //if (_ctx.msgSender != address(this)) revert GV_PERMISSION_DENIED(); + } + + /// @notice Returns the number of guardians + /// @return The number of guardians + function numGuardians() public view returns (uint256) { + return guardians.length; + } + + function _approve(uint256 _blockId, bytes32 _proofHash) internal returns (bool approved_) { + uint256 id = guardianIds[msg.sender]; + if (id == 0) revert GP_INVALID_GUARDIAN(); + + uint256 _version = version; + + unchecked { + approvals[_version][_proofHash] |= 1 << (id - 1); + } + + uint256 _approval = approvals[_version][_proofHash]; + approved_ = _isApproved(_approval); + emit Approved(_blockId, _approval, approved_); + } + + function _isApproved(uint256 _approvalBits) private view returns (bool) { + uint256 count; + uint256 bits = _approvalBits; + uint256 guardiansLength = guardians.length; + unchecked { + for (uint256 i; i < guardiansLength; ++i) { + if (bits & 1 == 1) ++count; + if (count == minGuardians) return true; + bits >>= 1; + } + } + return false; + } +} diff --git a/packages/protocol/contracts/L1/verifiers/GuardianVerifier.sol b/packages/protocol/contracts/L1/verifiers/GuardianVerifier.sol index 61a9517d57ba..894ca3663767 100644 --- a/packages/protocol/contracts/L1/verifiers/GuardianVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/GuardianVerifier.sol @@ -24,8 +24,8 @@ contract GuardianVerifier is EssentialContract, IVerifier { /// @inheritdoc IVerifier function verifyProof( - TaikoData.BlockMetadata calldata /*_block*/, - TaikoData.Transition calldata /*transition*/, + TaikoData.Transition calldata, /*transition*/ + bytes32, /*blockMetaHash*/ address prover, bytes calldata /*proof*/ ) diff --git a/packages/protocol/contracts/L1/verifiers/IVerifier.sol b/packages/protocol/contracts/L1/verifiers/IVerifier.sol index 56d09d85749f..99f4616302b3 100644 --- a/packages/protocol/contracts/L1/verifiers/IVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/IVerifier.sol @@ -11,11 +11,39 @@ import "../TaikoData.sol"; /// @title IVerifier Interface /// @notice Defines the function that handles proof verification. interface IVerifier { + // Todo(Brecht/Dani): + // This interface differs from taiko-mono's latest verifyProof(), mainly because we dont have + // contestation for example, so no need to have TierProof structure. But further bundling the + // structs into 1, and incorporate to TaikoData might be desireable, depending on how we need to + // use. + // See the taiko-mono used interface below this function signature. function verifyProof( - TaikoData.BlockMetadata calldata _block, TaikoData.Transition calldata transition, + bytes32 blockMetaHash, //We dont need to post the full BlockMetadata struct address prover, bytes calldata proof ) - external; + external; + + // As a reference, used by taiko-mono currently: + // struct Context { + // bytes32 metaHash; + // bytes32 blobHash; + // address prover; + // uint64 blockId; + // bool isContesting; + // bool blobUsed; + // address msgSender; + // } + + // /// @notice Verifies a proof. + // /// @param _ctx The context of the proof verification. + // /// @param _tran The transition to verify. + // /// @param _proof The proof to verify. + // function verifyProof( + // Context calldata _ctx, + // TaikoData.Transition calldata _tran, + // TaikoData.TierProof calldata _proof + // ) + // external; } diff --git a/packages/protocol/contracts/L1/verifiers/MockSgxVerifier.sol b/packages/protocol/contracts/L1/verifiers/MockSgxVerifier.sol new file mode 100644 index 000000000000..9b4e145253b5 --- /dev/null +++ b/packages/protocol/contracts/L1/verifiers/MockSgxVerifier.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../TaikoL1.sol"; +import "../../common/EssentialContract.sol"; +import "../../automata-attestation/interfaces/IAttestation.sol"; +import "../../automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; +import "./libs/LibPublicInput.sol"; +import "./IVerifier.sol"; + +/// @title SgxVerifier +/// @notice This contract is the implementation of verifying SGX signature proofs +/// onchain. +/// @dev Please see references below: +/// - Reference #1: https://ethresear.ch/t/2fa-zk-rollups-using-sgx/14462 +/// - Reference #2: https://github.com/gramineproject/gramine/discussions/1579 +/// @custom:security-contact security@taiko.xyz +contract SgxVerifier is EssentialContract, IVerifier { + /// @dev Each public-private key pair (Ethereum address) is generated within + /// the SGX program when it boots up. The off-chain remote attestation + /// ensures the validity of the program hash and has the capability of + /// bootstrapping the network with trustworthy instances. + struct Instance { + address addr; + uint64 validSince; + } + + /// @notice The expiry time for the SGX instance. + uint64 public constant INSTANCE_EXPIRY = 365 days; + + /// @notice A security feature, a delay until an instance is enabled when using onchain RA + /// verification + uint64 public constant INSTANCE_VALIDITY_DELAY = 0; + + /// @dev For gas savings, we shall assign each SGX instance with an id that when we need to + /// set a new pub key, just write storage once. + /// Slot 1. + uint256 public nextInstanceId; + + /// @dev One SGX instance is uniquely identified (on-chain) by it's ECDSA public key + /// (or rather ethereum address). Once that address is used (by proof verification) it has to be + /// overwritten by a new one (representing the same instance). This is due to side-channel + /// protection. Also this public key shall expire after some time + /// (for now it is a long enough 6 months setting). + /// Slot 2. + mapping(uint256 instanceId => Instance instance) public instances; + + /// @dev One address shall be registered (during attestation) only once, otherwise it could + /// bypass this contract's expiry check by always registering with the same attestation and + /// getting multiple valid instanceIds. While during proving, it is technically possible to + /// register the old addresses, it is less of a problem, because the instanceId would be the + /// same for those addresses and if deleted - the attestation cannot be reused anyways. + /// Slot 3. + mapping(address instanceAddress => bool alreadyAttested) public addressRegistered; + + uint256[47] private __gap; + + /// @notice Emitted when a new SGX instance is added to the registry, or replaced. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + /// @param replaced The address of the SGX instance that was replaced. If it is the first + /// instance, this value is zero address. + /// @param validSince The time since the instance is valid. + event InstanceAdded( + uint256 indexed id, address indexed instance, address indexed replaced, uint256 validSince + ); + + /// @notice Emitted when an SGX instance is deleted from the registry. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + event InstanceDeleted(uint256 indexed id, address indexed instance); + + error SGX_ALREADY_ATTESTED(); + error SGX_INVALID_ATTESTATION(); + error SGX_INVALID_INSTANCE(); + error SGX_INVALID_PROOF(); + error SGX_RA_NOT_SUPPORTED(); + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + } + + /// @notice Adds trusted SGX instances to the registry. + /// @param _instances The address array of trusted SGX instances. + /// @return The respective instanceId array per addresses. + function addInstances(address[] calldata _instances) + external + onlyOwner + returns (uint256[] memory) + { + return _addInstances(_instances, true); + } + + /// @notice Deletes SGX instances from the registry. + /// @param _ids The ids array of SGX instances. + function deleteInstances(uint256[] calldata _ids) + external + onlyFromOwnerOrNamed("sgx_watchdog") + { + for (uint256 i; i < _ids.length; ++i) { + uint256 idx = _ids[i]; + + if (instances[idx].addr == address(0)) revert SGX_INVALID_INSTANCE(); + + emit InstanceDeleted(idx, instances[idx].addr); + + delete instances[idx]; + } + } + + /// @notice Adds an SGX instance after the attestation is verified + /// @param _attestation The parsed attestation quote. + /// @return The respective instanceId + function registerInstance(V3Struct.ParsedV3QuoteStruct calldata _attestation) + external + returns (uint256) + { + address automataDcapAttestation = resolve("automata_dcap_attestation", true); + + if (automataDcapAttestation == address(0)) { + revert SGX_RA_NOT_SUPPORTED(); + } + + (bool verified,) = IAttestation(automataDcapAttestation).verifyParsedQuote(_attestation); + + if (!verified) revert SGX_INVALID_ATTESTATION(); + + address[] memory _address = new address[](1); + _address[0] = address(bytes20(_attestation.localEnclaveReport.reportData)); + + return _addInstances(_address, false)[0]; + } + + /// @inheritdoc IVerifier + /* MODIFIED- TO RETURN TRUE WITHOUT REAL VERIFICATION!!! */ + function verifyProof( + TaikoData.Transition calldata, /*transition*/ + bytes32, /*blockMetaHash*/ + address, /*prover*/ + bytes calldata /*proof*/ + ) + external + { + return; + } + + function _addInstances( + address[] memory _instances, + bool instantValid + ) + private + returns (uint256[] memory ids) + { + ids = new uint256[](_instances.length); + + uint64 validSince = uint64(block.timestamp); + + if (!instantValid) { + validSince += INSTANCE_VALIDITY_DELAY; + } + + for (uint256 i; i < _instances.length; ++i) { + if (addressRegistered[_instances[i]]) revert SGX_ALREADY_ATTESTED(); + + addressRegistered[_instances[i]] = true; + + if (_instances[i] == address(0)) revert SGX_INVALID_INSTANCE(); + + instances[nextInstanceId] = Instance(_instances[i], validSince); + ids[i] = nextInstanceId; + + emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince); + + ++nextInstanceId; + } + } + + function _replaceInstance(uint256 id, address oldInstance, address newInstance) private { + // Replacing an instance means, it went through a cooldown (if added by on-chain RA) so no + // need to have a cooldown + instances[id] = Instance(newInstance, uint64(block.timestamp)); + emit InstanceAdded(id, newInstance, oldInstance, block.timestamp); + } + + function _isInstanceValid(uint256 id, address instance) private view returns (bool) { + if (instance == address(0)) return false; + if (instance != instances[id].addr) return false; + return instances[id].validSince <= block.timestamp + && block.timestamp <= instances[id].validSince + INSTANCE_EXPIRY; + } +} diff --git a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol index 59398ccf3337..99e69e3420c7 100644 --- a/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/SgxVerifier.sol @@ -1,22 +1,21 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../TaikoL1.sol"; import "../../common/EssentialContract.sol"; -import "../../thirdparty/LibBytesUtils.sol"; -import "../TaikoData.sol"; +import "../../automata-attestation/interfaces/IAttestation.sol"; +import "../../automata-attestation/lib/QuoteV3Auth/V3Struct.sol"; +import "./libs/LibPublicInput.sol"; import "./IVerifier.sol"; /// @title SgxVerifier -/// @notice This contract is the implementation of verifying SGX signature -/// proofs on-chain. Please see references below! -/// Reference #1: https://ethresear.ch/t/2fa-zk-rollups-using-sgx/14462 -/// Reference #2: https://github.com/gramineproject/gramine/discussions/1579 +/// @notice This contract is the implementation of verifying SGX signature proofs +/// onchain. +/// @dev Please see references below: +/// - Reference #1: https://ethresear.ch/t/2fa-zk-rollups-using-sgx/14462 +/// - Reference #2: https://github.com/gramineproject/gramine/discussions/1579 +/// @custom:security-contact security@taiko.xyz contract SgxVerifier is EssentialContract, IVerifier { /// @dev Each public-private key pair (Ethereum address) is generated within /// the SGX program when it boots up. The off-chain remote attestation @@ -24,128 +23,186 @@ contract SgxVerifier is EssentialContract, IVerifier { /// bootstrapping the network with trustworthy instances. struct Instance { address addr; - uint64 addedAt; // We can calculate if expired + uint64 validSince; } - uint256 public constant INSTANCE_EXPIRY = 180 days; - - /// @dev For gas savings, we shall assign each SGX instance with an id - /// so that when we need to set a new pub key, just write storage once. - uint256 public nextInstanceId; // slot 1 - - /// @dev One SGX instance is uniquely identified (on-chain) by it's ECDSA - /// public key (or rather ethereum address). Once that address is used (by - /// proof verification) it has to be overwritten by a new one (representing - /// the same instance). This is due to side-channel protection. Also this - /// public key shall expire after some time. (For now it is a long enough 6 - /// months setting.) - mapping(uint256 instanceId => Instance) public instances; // slot 2 - - uint256[48] private __gap; - + /// @notice The expiry time for the SGX instance. + uint64 public constant INSTANCE_EXPIRY = 365 days; + + /// @notice A security feature, a delay until an instance is enabled when using onchain RA + /// verification + uint64 public constant INSTANCE_VALIDITY_DELAY = 0; + + /// @dev For gas savings, we shall assign each SGX instance with an id that when we need to + /// set a new pub key, just write storage once. + /// Slot 1. + uint256 public nextInstanceId; + + /// @dev One SGX instance is uniquely identified (on-chain) by it's ECDSA public key + /// (or rather ethereum address). Once that address is used (by proof verification) it has to be + /// overwritten by a new one (representing the same instance). This is due to side-channel + /// protection. Also this public key shall expire after some time + /// (for now it is a long enough 6 months setting). + /// Slot 2. + mapping(uint256 instanceId => Instance instance) public instances; + + /// @dev One address shall be registered (during attestation) only once, otherwise it could + /// bypass this contract's expiry check by always registering with the same attestation and + /// getting multiple valid instanceIds. While during proving, it is technically possible to + /// register the old addresses, it is less of a problem, because the instanceId would be the + /// same for those addresses and if deleted - the attestation cannot be reused anyways. + /// Slot 3. + mapping(address instanceAddress => bool alreadyAttested) public addressRegistered; + + uint256[47] private __gap; + + /// @notice Emitted when a new SGX instance is added to the registry, or replaced. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + /// @param replaced The address of the SGX instance that was replaced. If it is the first + /// instance, this value is zero address. + /// @param validSince The time since the instance is valid. event InstanceAdded( - uint256 indexed id, address indexed instance, address replaced, uint256 timstamp + uint256 indexed id, address indexed instance, address indexed replaced, uint256 validSince ); + /// @notice Emitted when an SGX instance is deleted from the registry. + /// @param id The ID of the SGX instance. + /// @param instance The address of the SGX instance. + event InstanceDeleted(uint256 indexed id, address indexed instance); + + error SGX_ALREADY_ATTESTED(); + error SGX_INVALID_ATTESTATION(); error SGX_INVALID_INSTANCE(); - error SGX_INVALID_INSTANCES(); error SGX_INVALID_PROOF(); + error SGX_RA_NOT_SUPPORTED(); - /// @notice Initializes the contract with the provided address manager. - /// @param _addressManager The address of the address manager contract. - function init(address _addressManager) external initializer { - __Essential_init(_addressManager); + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); } /// @notice Adds trusted SGX instances to the registry. /// @param _instances The address array of trusted SGX instances. - /// @return ids The respective instanceId array per addresses. + /// @return The respective instanceId array per addresses. function addInstances(address[] calldata _instances) external onlyOwner - returns (uint256[] memory ids) + returns (uint256[] memory) { - if (_instances.length == 0) revert SGX_INVALID_INSTANCES(); - ids = _addInstances(_instances); + return _addInstances(_instances, true); } - /// @notice Adds SGX instances to the registry by another SGX instance. - /// @param id The id of the SGX instance who is adding new members. - /// @param newInstance The new address of this instance. - /// @param extraInstances The address array of SGX instances. - /// @param signature The signature proving authenticity. - /// @return ids The respective instanceId array per addresses. - function addInstances( - uint256 id, - address newInstance, - address[] calldata extraInstances, - bytes calldata signature - ) + /// @notice Deletes SGX instances from the registry. + /// @param _ids The ids array of SGX instances. + function deleteInstances(uint256[] calldata _ids) external - returns (uint256[] memory ids) + onlyFromOwnerOrNamed("sgx_watchdog") { - bytes32 signedHash = keccak256(abi.encode("ADD_INSTANCES", extraInstances)); - address oldInstance = ECDSA.recover(signedHash, signature); - if (!_isInstanceValid(id, oldInstance)) revert SGX_INVALID_INSTANCE(); + for (uint256 i; i < _ids.length; ++i) { + uint256 idx = _ids[i]; + + if (instances[idx].addr == address(0)) revert SGX_INVALID_INSTANCE(); + + emit InstanceDeleted(idx, instances[idx].addr); + + delete instances[idx]; + } + } + + /// @notice Adds an SGX instance after the attestation is verified + /// @param _attestation The parsed attestation quote. + /// @return The respective instanceId + function registerInstance(V3Struct.ParsedV3QuoteStruct calldata _attestation) + external + returns (uint256) + { + address automataDcapAttestation = resolve("automata_dcap_attestation", true); + + if (automataDcapAttestation == address(0)) { + revert SGX_RA_NOT_SUPPORTED(); + } + + (bool verified,) = IAttestation(automataDcapAttestation).verifyParsedQuote(_attestation); + + if (!verified) revert SGX_INVALID_ATTESTATION(); - _replaceInstance(id, oldInstance, newInstance); + address[] memory _address = new address[](1); + _address[0] = address(bytes20(_attestation.localEnclaveReport.reportData)); - ids = _addInstances(extraInstances); + return _addInstances(_address, false)[0]; } /// @inheritdoc IVerifier function verifyProof( - TaikoData.BlockMetadata calldata _block, TaikoData.Transition calldata transition, + bytes32 blockMetaHash, address prover, bytes calldata proof ) external + onlyFromNamed("taiko") { // Size is: 89 bytes // 4 bytes + 20 bytes + 65 bytes (signature) = 89 if (proof.length != 89) revert SGX_INVALID_PROOF(); - uint32 id = uint32(bytes4(LibBytesUtils.slice(proof, 0, 4))); - address newInstance = address(bytes20(LibBytesUtils.slice(proof, 4, 20))); - bytes memory signature = LibBytesUtils.slice(proof, 24); + uint32 id = uint32(bytes4(proof[:4])); + address newInstance = address(bytes20(proof[4:24])); + bytes memory signature = proof[24:]; - address oldInstance = - ECDSA.recover(getSignedHash(transition, newInstance, prover, keccak256(abi.encode(_block))), signature); + uint64 chainId = TaikoL1(resolve("taiko", false)).getConfig().chainId; + + address oldInstance = ECDSA.recover( + LibPublicInput.hashPublicInputs( + transition, address(this), newInstance, prover, blockMetaHash, chainId + ), + signature + ); if (!_isInstanceValid(id, oldInstance)) revert SGX_INVALID_INSTANCE(); - _replaceInstance(id, oldInstance, newInstance); + + if (oldInstance != newInstance) { + _replaceInstance(id, oldInstance, newInstance); + } } - function getSignedHash( - TaikoData.Transition memory tran, - address newInstance, - address prover, - bytes32 metaHash + function _addInstances( + address[] memory _instances, + bool instantValid ) - public - pure - returns (bytes32 signedHash) + private + returns (uint256[] memory ids) { - return keccak256(abi.encode(tran, newInstance, prover, metaHash)); - } - - function _addInstances(address[] calldata _instances) private returns (uint256[] memory ids) { ids = new uint256[](_instances.length); + uint64 validSince = uint64(block.timestamp); + + if (!instantValid) { + validSince += INSTANCE_VALIDITY_DELAY; + } + for (uint256 i; i < _instances.length; ++i) { + if (addressRegistered[_instances[i]]) revert SGX_ALREADY_ATTESTED(); + + addressRegistered[_instances[i]] = true; + if (_instances[i] == address(0)) revert SGX_INVALID_INSTANCE(); - instances[nextInstanceId] = Instance(_instances[i], uint64(block.timestamp)); + instances[nextInstanceId] = Instance(_instances[i], validSince); ids[i] = nextInstanceId; - emit InstanceAdded(nextInstanceId, _instances[i], address(0), block.timestamp); + emit InstanceAdded(nextInstanceId, _instances[i], address(0), validSince); - nextInstanceId++; + ++nextInstanceId; } } function _replaceInstance(uint256 id, address oldInstance, address newInstance) private { + // Replacing an instance means, it went through a cooldown (if added by on-chain RA) so no + // need to have a cooldown instances[id] = Instance(newInstance, uint64(block.timestamp)); emit InstanceAdded(id, newInstance, oldInstance, block.timestamp); } @@ -153,6 +210,7 @@ contract SgxVerifier is EssentialContract, IVerifier { function _isInstanceValid(uint256 id, address instance) private view returns (bool) { if (instance == address(0)) return false; if (instance != instances[id].addr) return false; - return instances[id].addedAt + INSTANCE_EXPIRY > block.timestamp; + return instances[id].validSince <= block.timestamp + && block.timestamp <= instances[id].validSince + INSTANCE_EXPIRY; } } diff --git a/packages/protocol/contracts/L1/verifiers/libs/LibPublicInput.sol b/packages/protocol/contracts/L1/verifiers/libs/LibPublicInput.sol new file mode 100644 index 000000000000..a54b3d8487e2 --- /dev/null +++ b/packages/protocol/contracts/L1/verifiers/libs/LibPublicInput.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../../TaikoData.sol"; + +/// @title LibPublicInput +/// @notice A library for handling hashing the so-called public input hash, used by sgx and zk +/// proofs. +/// @custom:security-contact security@taiko.xyz +library LibPublicInput { + /// @notice Hashes the public input for the proof verification. + /// @param _tran The transition to verify. + /// @param _verifierContract The contract address which as current verifier. + /// @param _newInstance The new instance address. For SGX it is the new signer address, for ZK + /// this variable is not used and must have value address(0). + /// @param _prover The prover address. + /// @param _metaHash The meta hash. + /// @param _chainId The chain id. + /// @return The public input hash. + function hashPublicInputs( + TaikoData.Transition memory _tran, + address _verifierContract, + address _newInstance, + address _prover, + bytes32 _metaHash, + uint64 _chainId + ) + internal + pure + returns (bytes32) + { + return keccak256( + abi.encode( + "VERIFY_PROOF", _chainId, _verifierContract, _tran, _newInstance, _prover, _metaHash + ) + ); + } +} diff --git a/packages/protocol/contracts/L2/Lib1559Math.sol b/packages/protocol/contracts/L2/Lib1559Math.sol index 71595c8173e0..4b5316c28f6f 100644 --- a/packages/protocol/contracts/L2/Lib1559Math.sol +++ b/packages/protocol/contracts/L2/Lib1559Math.sol @@ -1,33 +1,73 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "../thirdparty/LibFixedPointMath.sol"; +import "../thirdparty/solmate/LibFixedPointMath.sol"; +import "../libs/LibMath.sol"; /// @title Lib1559Math -/// @dev Implementation of e^(x) based bonding curve for EIP-1559 -/// See https://ethresear.ch/t/make-eip-1559-more-like-an-amm-curve/9082 +/// @notice Implements e^(x) based bonding curve for EIP-1559 +/// @dev See https://ethresear.ch/t/make-eip-1559-more-like-an-amm-curve/9082 but some minor +/// difference as stated in docs/eip1559_on_l2.md. +/// @custom:security-contact security@taiko.xyz library Lib1559Math { + using LibMath for uint256; + error EIP1559_INVALID_PARAMS(); + function calc1559BaseFee( + uint32 _gasTargetPerL1Block, + uint8 _adjustmentQuotient, + uint64 _gasExcess, + uint64 _gasIssuance, + uint32 _parentGasUsed + ) + internal + pure + returns (uint256 basefee_, uint64 gasExcess_) + { + // We always add the gas used by parent block to the gas excess + // value as this has already happened + uint256 excess = uint256(_gasExcess) + _parentGasUsed; + excess = excess > _gasIssuance ? excess - _gasIssuance : 1; + gasExcess_ = uint64(excess.min(type(uint64).max)); + + // The base fee per gas used by this block is the spot price at the + // bonding curve, regardless the actual amount of gas used by this + // block, however, this block's gas used will affect the next + // block's base fee. + basefee_ = basefee(gasExcess_, uint256(_adjustmentQuotient) * _gasTargetPerL1Block); + + // Always make sure basefee is nonzero, this is required by the node. + if (basefee_ == 0) basefee_ = 1; + } + /// @dev eth_qty(excess_gas_issued) / (TARGET * ADJUSTMENT_QUOTIENT) - /// @param adjustmentFactor The product of gasTarget and adjustmentQuotient - function basefee(uint256 gasExcess, uint256 adjustmentFactor) internal pure returns (uint256) { - if (adjustmentFactor == 0) { + /// @param _gasExcess The gas excess value + /// @param _adjustmentFactor The product of gasTarget and adjustmentQuotient + function basefee( + uint256 _gasExcess, + uint256 _adjustmentFactor + ) + internal + pure + returns (uint256) + { + if (_adjustmentFactor == 0) { revert EIP1559_INVALID_PARAMS(); } - - return _ethQty(gasExcess, adjustmentFactor) / LibFixedPointMath.SCALING_FACTOR - / adjustmentFactor; + return _ethQty(_gasExcess, _adjustmentFactor) / LibFixedPointMath.SCALING_FACTOR; } /// @dev exp(gas_qty / TARGET / ADJUSTMENT_QUOTIENT) - function _ethQty(uint256 gasExcess, uint256 adjustmentFactor) private pure returns (uint256) { - uint256 input = gasExcess * LibFixedPointMath.SCALING_FACTOR / adjustmentFactor; + function _ethQty( + uint256 _gasExcess, + uint256 _adjustmentFactor + ) + private + pure + returns (uint256) + { + uint256 input = _gasExcess * LibFixedPointMath.SCALING_FACTOR / _adjustmentFactor; if (input > LibFixedPointMath.MAX_EXP_INPUT) { input = LibFixedPointMath.MAX_EXP_INPUT; } diff --git a/packages/protocol/contracts/L2/LibL2Config.sol b/packages/protocol/contracts/L2/LibL2Config.sol new file mode 100644 index 000000000000..70af37013b8c --- /dev/null +++ b/packages/protocol/contracts/L2/LibL2Config.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title LibL2Config +library LibL2Config { + struct Config { + uint32 gasTargetPerL1Block; + uint8 basefeeAdjustmentQuotient; + } + + /// @notice Returns EIP1559 related configurations. + /// @return config_ struct containing configuration parameters. + function get() internal pure returns (Config memory config_) { + // Assuming we sell 3x more blockspace than Ethereum: 15_000_000 * 4 + // Note that Brecht's concern is that this value may be too large. + // We need to monitor L2 state growth and lower this value when necessary. + config_.gasTargetPerL1Block = 60_000_000; + config_.basefeeAdjustmentQuotient = 8; + } +} diff --git a/packages/protocol/contracts/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index c38427f57c2b..896376c1bdc5 100644 --- a/packages/protocol/contracts/L2/TaikoL2.sol +++ b/packages/protocol/contracts/L2/TaikoL2.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "../common/EssentialContract.sol"; -import "../common/ICrossChainSync.sol"; -import "../libs/LibMath.sol"; +import "../common/LibStrings.sol"; +import "../libs/LibAddress.sol"; import "../signal/ISignalService.sol"; import "./Lib1559Math.sol"; -import "./TaikoL2Signer.sol"; +import "./LibL2Config.sol"; /// @title TaikoL2 /// @notice Taiko L2 is a smart contract that handles cross-layer message @@ -19,48 +17,71 @@ import "./TaikoL2Signer.sol"; /// It is used to anchor the latest L1 block details to L2 for cross-layer /// communication, manage EIP-1559 parameters for gas pricing, and store /// verified L1 block information. -contract TaikoL2 is EssentialContract, TaikoL2Signer, ICrossChainSync { - using LibMath for uint256; +/// @custom:security-contact security@taiko.xyz +contract TaikoL2 is EssentialContract { + using LibAddress for address; + using SafeERC20 for IERC20; - struct Config { - uint32 gasTargetPerL1Block; - uint8 basefeeAdjustmentQuotient; - } + /// @notice Golden touch address is the only address that can do the anchor transaction. + address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; - // Mapping from L2 block numbers to their block hashes. - // All L2 block hashes will be saved in this mapping. + /// @notice Mapping from L2 block numbers to their block hashes. All L2 block hashes will + /// be saved in this mapping. mapping(uint256 blockId => bytes32 blockHash) public l2Hashes; - mapping(uint256 l1height => ICrossChainSync.Snippet) public snippets; - // A hash to check the integrity of public inputs. - address public signalService; // slot 3 - bytes32 public publicInputHash; // slot 4 + /// @notice A hash to check the integrity of public inputs. + /// @dev Slot 2. + bytes32 public publicInputHash; + + /// @notice The gas excess value used to calculate the base fee. + /// @dev Slot 3. + uint64 public gasExcess; + + /// @notice The last synced L1 block height. + uint64 public lastSyncedBlock; - uint64 public gasExcess; // slot 5 - uint64 public latestSyncedL1Height; + uint64 private __deprecated1; // was parentTimestamp + uint64 private __deprecated2; // was __currentBlockTimestamp - uint256[145] private __gap; + /// @notice The L1's chain ID. + uint64 public l1ChainId; + uint256[46] private __gap; + + /// @notice Emitted when the latest L1 block details are anchored to L2. + /// @param parentHash The hash of the parent block. + /// @param gasExcess The gas excess value used to calculate the base fee. event Anchored(bytes32 parentHash, uint64 gasExcess); error L2_BASEFEE_MISMATCH(); - error L2_INVALID_CHAIN_ID(); + error L2_INVALID_L1_CHAIN_ID(); + error L2_INVALID_L2_CHAIN_ID(); error L2_INVALID_PARAM(); error L2_INVALID_SENDER(); error L2_PUBLIC_INPUT_HASH_MISMATCH(); error L2_TOO_LATE(); - /// @notice Initializes the TaikoL2 contract. - /// @param _signalService Address of the {ISignalService} contract. + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + /// @param _l1ChainId The ID of the base layer. /// @param _gasExcess The initial gasExcess. - function init(address _signalService, uint64 _gasExcess) external initializer { - __Essential_init(); - - if (_signalService == address(0)) revert L2_INVALID_PARAM(); - signalService = _signalService; + function init( + address _owner, + address _addressManager, + uint64 _l1ChainId, + uint64 _gasExcess + ) + external + initializer + { + __Essential_init(_owner, _addressManager); - if (block.chainid <= 1 || block.chainid >= type(uint64).max) { - revert L2_INVALID_CHAIN_ID(); + if (_l1ChainId == 0 || _l1ChainId == block.chainid) { + revert L2_INVALID_L1_CHAIN_ID(); + } + if (block.chainid <= 1 || block.chainid > type(uint64).max) { + revert L2_INVALID_L2_CHAIN_ID(); } if (block.number == 0) { @@ -73,29 +94,36 @@ contract TaikoL2 is EssentialContract, TaikoL2Signer, ICrossChainSync { revert L2_TOO_LATE(); } + l1ChainId = _l1ChainId; gasExcess = _gasExcess; (publicInputHash,) = _calcPublicInputHash(block.number); } /// @notice Anchors the latest L1 block details to L2 for cross-layer /// message verification. - /// @param l1BlockHash The latest L1 block hash when this block was + /// @dev This function can be called freely as the golden touch private key is publicly known, + /// but the Taiko node guarantees the first transaction of each block is always this anchor + /// transaction, and any subsequent calls will revert with L2_PUBLIC_INPUT_HASH_MISMATCH. + /// @param _l1BlockHash The latest L1 block hash when this block was /// proposed. - /// @param l1SignalRoot The latest value of the L1 signal root. - /// @param l1Height The latest L1 block height when this block was proposed. - /// @param parentGasUsed The gas used in the parent block. + /// @param _l1StateRoot The latest L1 block's state root. + /// @param _l1BlockId The latest L1 block height when this block was proposed. + /// @param _parentGasUsed The gas used in the parent block. function anchor( - bytes32 l1BlockHash, - bytes32 l1SignalRoot, - uint64 l1Height, - uint32 parentGasUsed + bytes32 _l1BlockHash, + bytes32 _l1StateRoot, + uint64 _l1BlockId, + uint32 _parentGasUsed ) external + nonReentrant { if ( - l1BlockHash == 0 || l1SignalRoot == 0 || l1Height == 0 - || (block.number != 1 && parentGasUsed == 0) - ) revert L2_INVALID_PARAM(); + _l1BlockHash == 0 || _l1StateRoot == 0 || _l1BlockId == 0 + || (block.number != 1 && _parentGasUsed == 0) + ) { + revert L2_INVALID_PARAM(); + } if (msg.sender != GOLDEN_TOUCH_ADDRESS) revert L2_INVALID_SENDER(); @@ -110,86 +138,101 @@ contract TaikoL2 is EssentialContract, TaikoL2Signer, ICrossChainSync { revert L2_PUBLIC_INPUT_HASH_MISMATCH(); } - Config memory config = getConfig(); - // Verify the base fee per gas is correct - uint256 basefee; - (basefee, gasExcess) = _calc1559BaseFee(config, l1Height, parentGasUsed); - if (!skipFeeCheck() && block.basefee != basefee) { + (uint256 _basefee, uint64 _gasExcess) = getBasefee(_l1BlockId, _parentGasUsed); + + if (!skipFeeCheck() && block.basefee != _basefee) { revert L2_BASEFEE_MISMATCH(); } - // Store the L1's signal root as a signal to the local signal service to - // allow for multi-hop bridging. - ISignalService(signalService).sendSignal(l1SignalRoot); - emit CrossChainSynced(uint64(block.number), l1Height, l1BlockHash, l1SignalRoot); + if (_l1BlockId > lastSyncedBlock) { + // Store the L1's state root as a signal to the local signal service to + // allow for multi-hop bridging. + ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).syncChainData( + l1ChainId, LibStrings.H_STATE_ROOT, _l1BlockId, _l1StateRoot + ); + + lastSyncedBlock = _l1BlockId; + } // Update state variables - l2Hashes[parentId] = blockhash(parentId); - snippets[l1Height] = ICrossChainSync.Snippet({ - remoteBlockId: l1Height, - syncedInBlock: uint64(block.number), - blockHash: l1BlockHash, - signalRoot: l1SignalRoot - }); + bytes32 _parentHash = blockhash(parentId); + l2Hashes[parentId] = _parentHash; publicInputHash = publicInputHashNew; - latestSyncedL1Height = l1Height; + gasExcess = _gasExcess; - emit Anchored(blockhash(parentId), gasExcess); + emit Anchored(_parentHash, _gasExcess); } - /// @inheritdoc ICrossChainSync - function getSyncedSnippet(uint64 blockId) - public - view - override - returns (ICrossChainSync.Snippet memory) + /// @notice Withdraw token or Ether from this address + /// @param _token Token address or address(0) if Ether. + /// @param _to Withdraw to address. + function withdraw( + address _token, + address _to + ) + external + whenNotPaused + onlyFromOwnerOrNamed(LibStrings.B_WITHDRAWER) + nonReentrant { - uint256 id = blockId == 0 ? latestSyncedL1Height : blockId; - return snippets[id]; + if (_to == address(0)) revert L2_INVALID_PARAM(); + if (_token == address(0)) { + _to.sendEtherAndVerify(address(this).balance); + } else { + IERC20(_token).safeTransfer(_to, IERC20(_token).balanceOf(address(this))); + } } /// @notice Gets the basefee and gas excess using EIP-1559 configuration for /// the given parameters. - /// @param l1Height The synced L1 height in the next Taiko block - /// @param parentGasUsed Gas used in the parent block. - /// @return basefee The calculated EIP-1559 base fee per gas. + /// @param _l1BlockId The synced L1 height in the next Taiko block + /// @param _parentGasUsed Gas used in the parent block. + /// @return basefee_ The calculated EIP-1559 base fee per gas. + /// @return gasExcess_ The new gasExcess value. function getBasefee( - uint64 l1Height, - uint32 parentGasUsed + uint64 _l1BlockId, + uint32 _parentGasUsed ) public view - returns (uint256 basefee) + returns (uint256 basefee_, uint64 gasExcess_) { - (basefee,) = _calc1559BaseFee(getConfig(), l1Height, parentGasUsed); + LibL2Config.Config memory config = getConfig(); + uint64 gasIssuance = uint64(_l1BlockId - lastSyncedBlock) * config.gasTargetPerL1Block; + + (basefee_, gasExcess_) = Lib1559Math.calc1559BaseFee( + config.gasTargetPerL1Block, + config.basefeeAdjustmentQuotient, + gasExcess, + gasIssuance, + _parentGasUsed + ); } /// @notice Retrieves the block hash for the given L2 block number. - /// @param blockId The L2 block number to retrieve the block hash for. + /// @param _blockId The L2 block number to retrieve the block hash for. /// @return The block hash for the specified L2 block id, or zero if the /// block id is greater than or equal to the current block number. - function getBlockHash(uint64 blockId) public view returns (bytes32) { - if (blockId >= block.number) return 0; - if (blockId >= block.number - 256) return blockhash(blockId); - return l2Hashes[blockId]; + function getBlockHash(uint64 _blockId) public view returns (bytes32) { + if (_blockId >= block.number) return 0; + if (_blockId + 256 >= block.number) return blockhash(_blockId); + return l2Hashes[_blockId]; } - /// @notice Returns EIP1559 related configurations - function getConfig() public view virtual returns (Config memory config) { - // 4x Ethereum gas target, if we assume most of the time, L2 block time - // is 3s, and each block is full (gasUsed is 15_000_000), then its - // ~60_000_000, if the network is congester than that, the base fee - // will increase. - config.gasTargetPerL1Block = 15 * 1e6 * 4; - config.basefeeAdjustmentQuotient = 8; + /// @notice Returns EIP1559 related configurations. + /// @return config_ struct containing configuration parameters. + function getConfig() public view virtual returns (LibL2Config.Config memory) { + return LibL2Config.get(); } /// @notice Tells if we need to validate basefee (for simulation). /// @return Returns true to skip checking basefee mismatch. - function skipFeeCheck() public pure virtual returns (bool) { } + function skipFeeCheck() public pure virtual returns (bool) { + return false; + } - function _calcPublicInputHash(uint256 blockId) + function _calcPublicInputHash(uint256 _blockId) private view returns (bytes32 publicInputHashOld, bytes32 publicInputHashNew) @@ -200,8 +243,8 @@ contract TaikoL2 is EssentialContract, TaikoL2Signer, ICrossChainSync { unchecked { // Put the previous 255 blockhashes (excluding the parent's) into a // ring buffer. - for (uint256 i; i < 255 && blockId >= i + 1; ++i) { - uint256 j = blockId - i - 1; + for (uint256 i; i < 255 && _blockId >= i + 1; ++i) { + uint256 j = _blockId - i - 1; inputs[j % 255] = blockhash(j); } } @@ -212,53 +255,9 @@ contract TaikoL2 is EssentialContract, TaikoL2Signer, ICrossChainSync { publicInputHashOld := keccak256(inputs, 8192 /*mul(256, 32)*/ ) } - inputs[blockId % 255] = blockhash(blockId); + inputs[_blockId % 255] = blockhash(_blockId); assembly { publicInputHashNew := keccak256(inputs, 8192 /*mul(256, 32)*/ ) } } - - function _calc1559BaseFee( - Config memory config, - uint64 l1Height, - uint32 parentGasUsed - ) - private - view - returns (uint256 _basefee, uint64 _gasExcess) - { - // gasExcess being 0 indicate the dynamic 1559 base fee is disabled. - if (gasExcess > 0) { - // We always add the gas used by parent block to the gas excess - // value as this has already happend - uint256 excess = uint256(gasExcess) + parentGasUsed; - - // Calculate how much more gas to issue to offset gas excess. - // after each L1 block time, config.gasTarget more gas is issued, - // the gas excess will be reduced accordingly. - // Note that when latestSyncedL1Height is zero, we skip this step. - uint256 numL1Blocks; - if (latestSyncedL1Height > 0 && l1Height > latestSyncedL1Height) { - numL1Blocks = l1Height - latestSyncedL1Height; - } - - if (numL1Blocks > 0) { - uint256 issuance = numL1Blocks * config.gasTargetPerL1Block; - excess = excess > issuance ? excess - issuance : 1; - } - - _gasExcess = uint64(excess.min(type(uint64).max)); - - // The base fee per gas used by this block is the spot price at the - // bonding curve, regardless the actual amount of gas used by this - // block, however, the this block's gas used will affect the next - // block's base fee. - _basefee = Lib1559Math.basefee( - _gasExcess, uint256(config.basefeeAdjustmentQuotient) * config.gasTargetPerL1Block - ); - } - - // Always make sure basefee is nonzero, this is required by the node. - if (_basefee == 0) _basefee = 1; - } } diff --git a/packages/protocol/contracts/L2/TaikoL2EIP1559Configurable.sol b/packages/protocol/contracts/L2/TaikoL2EIP1559Configurable.sol deleted file mode 100644 index 1d0b023af881..000000000000 --- a/packages/protocol/contracts/L2/TaikoL2EIP1559Configurable.sol +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "./TaikoL2.sol"; - -/// @title TaikoL2EIP1559Configurable -/// @notice Taiko L2 with a setter to change EIP-1559 configurations and states. -contract TaikoL2EIP1559Configurable is TaikoL2 { - Config private _config; - uint256[49] private __gap; - - event ConfigAndExcessChanged(Config config, uint64 gasExcess); - - error L2_INVALID_CONFIG(); - - /// @notice Sets EIP1559 configuration and gas excess. - /// @param config The new EIP1559 config. - /// @param newGasExcess The new gas excess - function setConfigAndExcess( - Config memory config, - uint64 newGasExcess - ) - external - virtual - onlyOwner - { - if (config.gasTargetPerL1Block == 0) revert L2_INVALID_CONFIG(); - if (config.basefeeAdjustmentQuotient == 0) revert L2_INVALID_CONFIG(); - - _config = config; - gasExcess = newGasExcess; - - emit ConfigAndExcessChanged(config, newGasExcess); - } - - function getConfig() public view override returns (Config memory) { - return _config; - } -} diff --git a/packages/protocol/contracts/L2/TaikoL2Signer.sol b/packages/protocol/contracts/L2/TaikoL2Signer.sol deleted file mode 100644 index 29b449a3a50b..000000000000 --- a/packages/protocol/contracts/L2/TaikoL2Signer.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "../thirdparty/LibUint512Math.sol"; - -/// @title TaikoL2Signer -/// @notice This contract allows for signing operations required on Taiko L2. -/// @dev It uses precomputed values for optimized signature creation. -abstract contract TaikoL2Signer { - // Constants related to the golden touch signature. - address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; - uint256 public constant GOLDEN_TOUCH_PRIVATEKEY = - 0x92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38; - - // Precomputed curve constants. - uint256 private constant GX = 0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798; - uint256 private constant GY = 0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8; - uint256 private constant GX2 = - 0xc6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5; - uint256 private constant GY2 = - 0x1ae168fea63dc339a3c58419466ceaeef7f632653266d0e1236431a950cfe52a; - - // Curve order. - uint256 private constant N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141; - - // Precomputed multiplication results for optimization. - uint256 private constant GX_MUL_GOLDEN_TOUCH_PRIVATEKEY_LOW = - 0xb4a95509ce05fe8d45987859a067780d16a367c0e2cacf79cd301b93fb717940; - uint256 private constant GX_MUL_GOLDEN_TOUCH_PRIVATEKEY_HIGH = - 0x45b59254b0320fd853f3f38ac574999e91bd75fd5e6cab9c22c5e71fc6d276e4; - uint256 private constant GX2_MUL_GOLDEN_TOUCH_PRIVATEKEY_LOW = - 0xad77eceea844778cb4376153fc8f06f12f1695df4585bf75bfb17ec19ce90818; - uint256 private constant GX2_MUL_GOLDEN_TOUCH_PRIVATEKEY_HIGH = - 0x71620584f61c57e688bbd3fd7a39a036e588d962c4c830f3dacbc15c917e02f2; - - // Invert K (= 2) in the field F(N) - uint256 private constant K_2_INVM_N = - 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a1; - - error L2_INVALID_GOLDEN_TOUCH_K(); - - /// @notice Signs the provided digest using the golden touch mechanism. - /// @param digest The hash of the data to be signed. - /// @param k The selector for signature optimization. - /// @return v The recovery id. - /// @return r The r component of the signature. - /// @return s The s component of the signature. - function signAnchor( - bytes32 digest, - uint8 k - ) - public - view - returns (uint8 v, uint256 r, uint256 s) - { - if (k != 1 && k != 2) revert L2_INVALID_GOLDEN_TOUCH_K(); - - r = k == 1 ? GX : GX2; - - uint256 low256 = - k == 1 ? GX_MUL_GOLDEN_TOUCH_PRIVATEKEY_LOW : GX2_MUL_GOLDEN_TOUCH_PRIVATEKEY_LOW; - uint256 high256 = - k == 1 ? GX_MUL_GOLDEN_TOUCH_PRIVATEKEY_HIGH : GX2_MUL_GOLDEN_TOUCH_PRIVATEKEY_HIGH; - - (low256, high256) = LibUint512Math.add(low256, high256, uint256(digest), 0); - - if (k == 1) { - s = _expmod(low256, high256, 1, N); - } else { - (low256, high256) = LibUint512Math.mul(K_2_INVM_N, _expmod(low256, high256, 1, N)); - s = _expmod(low256, high256, 1, N); - } - - if (s > N >> 1) { - s = N - s; - v ^= 1; - } - } - - /// @dev Computes base^e mod m. - /// @param baseLow Lower 256 bits of the base. - /// @param baseHigh Higher 256 bits of the base. - /// @param e Exponent. - /// @param m Modulus. - /// @return o Result. - function _expmod( - uint256 baseLow, - uint256 baseHigh, - uint256 e, - uint256 m - ) - private - view - returns (uint256 o) - { - assembly { - // Define pointer - let p := mload(0x40) - // Store data in assembly-favouring ways - mstore(p, 0x40) // Length of Base - mstore(add(p, 0x20), 0x20) // Length of Exponent - mstore(add(p, 0x40), 0x20) // Length of Modulus - mstore(add(p, 0x60), baseHigh) // BaseHigh - mstore(add(p, 0x80), baseLow) // BaseLow - mstore(add(p, 0xa0), e) // Exponent - mstore(add(p, 0xc0), m) // Modulus - - if iszero(staticcall(sub(gas(), 2000), 0x05, p, 0xe0, p, 0x20)) { revert(0, 0) } - o := mload(p) - } - } -} diff --git a/packages/protocol/contracts/automata-attestation/AutomataDcapV3Attestation.sol b/packages/protocol/contracts/automata-attestation/AutomataDcapV3Attestation.sol new file mode 100644 index 000000000000..900ade360029 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/AutomataDcapV3Attestation.sol @@ -0,0 +1,508 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { V3Struct } from "./lib/QuoteV3Auth/V3Struct.sol"; +import { V3Parser } from "./lib/QuoteV3Auth/V3Parser.sol"; +import { IPEMCertChainLib } from "./lib/interfaces/IPEMCertChainLib.sol"; +import { PEMCertChainLib } from "./lib/PEMCertChainLib.sol"; +import { TCBInfoStruct } from "./lib/TCBInfoStruct.sol"; +import { EnclaveIdStruct } from "./lib/EnclaveIdStruct.sol"; +import { IAttestation } from "./interfaces/IAttestation.sol"; + +// Internal Libraries +import { Base64 } from "solady/src/utils/Base64.sol"; +import { LibString } from "solady/src/utils/LibString.sol"; +import { BytesUtils } from "./utils/BytesUtils.sol"; + +// External Libraries +import { ISigVerifyLib } from "./interfaces/ISigVerifyLib.sol"; + +import { EssentialContract } from "../common/EssentialContract.sol"; + +/// @title AutomataDcapV3Attestation +/// @custom:security-contact security@taiko.xyz +contract AutomataDcapV3Attestation is IAttestation, EssentialContract { + using BytesUtils for bytes; + + // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64 + uint256 internal constant CPUSVN_LENGTH = 16; + + // keccak256(hex"0ba9c4c0c0c86193a3fe23d6b02cda10a8bbd4e88e48b4458561a36e705525f567918e2edc88e40d860bd0cc4ee26aacc988e505a953558c453f6b0904ae7394") + // the uncompressed (0x04) prefix is not included in the pubkey pre-image + bytes32 internal constant ROOTCA_PUBKEY_HASH = + 0x89f72d7c488e5b53a77c23ebcb36970ef7eb5bcf6658e9b8292cfbe4703a8473; + + uint8 internal constant INVALID_EXIT_CODE = 255; + + ISigVerifyLib public sigVerifyLib; // slot 1 + IPEMCertChainLib public pemCertLib; // slot 2 + + bool public checkLocalEnclaveReport; // slot 3 + mapping(bytes32 enclave => bool trusted) public trustedUserMrEnclave; // slot 4 + mapping(bytes32 signer => bool trusted) public trustedUserMrSigner; // slot 5 + + // Quote Collateral Configuration + + // Index definition: + // 0 = Quote PCKCrl + // 1 = RootCrl + mapping(uint256 idx => mapping(bytes serialNum => bool revoked)) public serialNumIsRevoked; // slot + // 6 + // fmspc => tcbInfo + mapping(string fmspc => TCBInfoStruct.TCBInfo tcbInfo) public tcbInfo; // slot 7 + EnclaveIdStruct.EnclaveId public qeIdentity; // takes 4 slots, slot 8,9,10,11 + + uint256[39] __gap; + + event MrSignerUpdated(bytes32 indexed mrSigner, bool trusted); + event MrEnclaveUpdated(bytes32 indexed mrEnclave, bool trusted); + event TcbInfoJsonConfigured(string indexed fmspc, TCBInfoStruct.TCBInfo tcbInfoInput); + event QeIdentityConfigured(EnclaveIdStruct.EnclaveId qeIdentityInput); + event LocalReportCheckToggled(bool checkLocalEnclaveReport); + event RevokedCertSerialNumAdded(uint256 indexed index, bytes serialNum); + event RevokedCertSerialNumRemoved(uint256 indexed index, bytes serialNum); + + // @notice Initializes the contract. + /// @param sigVerifyLibAddr Address of the signature verification library. + /// @param pemCertLibAddr Address of certificate library. + function init( + address owner, + address sigVerifyLibAddr, + address pemCertLibAddr + ) + external + initializer + { + __Essential_init(owner); + sigVerifyLib = ISigVerifyLib(sigVerifyLibAddr); + pemCertLib = PEMCertChainLib(pemCertLibAddr); + } + + function setMrSigner(bytes32 _mrSigner, bool _trusted) external onlyOwner { + trustedUserMrSigner[_mrSigner] = _trusted; + emit MrSignerUpdated(_mrSigner, _trusted); + } + + function setMrEnclave(bytes32 _mrEnclave, bool _trusted) external onlyOwner { + trustedUserMrEnclave[_mrEnclave] = _trusted; + emit MrEnclaveUpdated(_mrEnclave, _trusted); + } + + function addRevokedCertSerialNum( + uint256 index, + bytes[] calldata serialNumBatch + ) + external + onlyOwner + { + for (uint256 i; i < serialNumBatch.length; ++i) { + if (serialNumIsRevoked[index][serialNumBatch[i]]) { + continue; + } + serialNumIsRevoked[index][serialNumBatch[i]] = true; + emit RevokedCertSerialNumAdded(index, serialNumBatch[i]); + } + } + + function removeRevokedCertSerialNum( + uint256 index, + bytes[] calldata serialNumBatch + ) + external + onlyOwner + { + for (uint256 i; i < serialNumBatch.length; ++i) { + if (!serialNumIsRevoked[index][serialNumBatch[i]]) { + continue; + } + delete serialNumIsRevoked[index][serialNumBatch[i]]; + emit RevokedCertSerialNumRemoved(index, serialNumBatch[i]); + } + } + + function configureTcbInfoJson( + string calldata fmspc, + TCBInfoStruct.TCBInfo calldata tcbInfoInput + ) + public + onlyOwner + { + // 2.2M gas + tcbInfo[fmspc] = tcbInfoInput; + emit TcbInfoJsonConfigured(fmspc, tcbInfoInput); + } + + function configureQeIdentityJson(EnclaveIdStruct.EnclaveId calldata qeIdentityInput) + external + onlyOwner + { + // 250k gas + qeIdentity = qeIdentityInput; + emit QeIdentityConfigured(qeIdentityInput); + } + + function toggleLocalReportCheck() external onlyOwner { + checkLocalEnclaveReport = !checkLocalEnclaveReport; + emit LocalReportCheckToggled(checkLocalEnclaveReport); + } + + function _attestationTcbIsValid(TCBInfoStruct.TCBStatus status) + internal + pure + virtual + returns (bool valid) + { + return status == TCBInfoStruct.TCBStatus.OK + || status == TCBInfoStruct.TCBStatus.TCB_SW_HARDENING_NEEDED + || status == TCBInfoStruct.TCBStatus.TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED + || status == TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE + || status == TCBInfoStruct.TCBStatus.TCB_OUT_OF_DATE_CONFIGURATION_NEEDED; + } + + function verifyAttestation(bytes calldata data) external view override returns (bool success) { + (success,) = _verify(data); + } + + /// @dev Provide the raw quote binary as input + /// @dev The attestation data (or the returned data of this method) + /// is constructed depending on the validity of the quote verification. + /// @dev After confirming that a quote has been verified, the attestation's validity then + /// depends on the + /// status of the associated TCB. + /// @dev Example scenarios as below: + /// -------------------------------- + /// @dev Invalid quote verification: returns (false, INVALID_EXIT_CODE) + /// + /// @dev For all valid quote verification, the validity of the attestation depends on the status + /// of a + /// matching TCBInfo and this is defined in the _attestationTcbIsValid() method, which can be + /// overwritten + /// in derived contracts. (Except for "Revoked" status, which also returns (false, + /// INVALID_EXIT_CODE) value) + /// @dev For all valid quote verification, returns the following data: + /// (_attestationTcbIsValid(), abi.encodePacked(sha256(quote), uint8 exitCode)) + /// @dev exitCode is defined in the {{ TCBInfoStruct.TCBStatus }} enum + function _verify(bytes calldata quote) private view returns (bool, bytes memory) { + bytes memory retData = abi.encodePacked(INVALID_EXIT_CODE); + + // Step 1: Parse the quote input = 152k gas + (bool successful, V3Struct.ParsedV3QuoteStruct memory parsedV3Quote) = + V3Parser.parseInput(quote, address(pemCertLib)); + if (!successful) { + return (false, retData); + } + + return _verifyParsedQuote(parsedV3Quote); + } + + function _verifyQEReportWithIdentity(V3Struct.EnclaveReport memory quoteEnclaveReport) + private + view + returns (bool, EnclaveIdStruct.EnclaveIdStatus status) + { + EnclaveIdStruct.EnclaveId memory enclaveId = qeIdentity; + bool miscselectMatched = + quoteEnclaveReport.miscSelect & enclaveId.miscselectMask == enclaveId.miscselect; + + bool attributesMatched = + quoteEnclaveReport.attributes & enclaveId.attributesMask == enclaveId.attributes; + bool mrsignerMatched = quoteEnclaveReport.mrSigner == enclaveId.mrsigner; + + bool isvprodidMatched = quoteEnclaveReport.isvProdId == enclaveId.isvprodid; + + bool tcbFound; + for (uint256 i; i < enclaveId.tcbLevels.length; ++i) { + EnclaveIdStruct.TcbLevel memory tcb = enclaveId.tcbLevels[i]; + if (tcb.tcb.isvsvn <= quoteEnclaveReport.isvSvn) { + tcbFound = true; + status = tcb.tcbStatus; + break; + } + } + return ( + miscselectMatched && attributesMatched && mrsignerMatched && isvprodidMatched + && tcbFound, + status + ); + } + + function _checkTcbLevels( + IPEMCertChainLib.PCKCertificateField memory pck, + TCBInfoStruct.TCBInfo memory tcb + ) + private + pure + returns (bool, TCBInfoStruct.TCBStatus status) + { + for (uint256 i; i < tcb.tcbLevels.length; ++i) { + TCBInfoStruct.TCBLevelObj memory current = tcb.tcbLevels[i]; + bool pceSvnIsHigherOrGreater = pck.sgxExtension.pcesvn >= current.pcesvn; + bool cpuSvnsAreHigherOrGreater = _isCpuSvnHigherOrGreater( + pck.sgxExtension.sgxTcbCompSvnArr, current.sgxTcbCompSvnArr + ); + if (pceSvnIsHigherOrGreater && cpuSvnsAreHigherOrGreater) { + status = current.status; + bool tcbIsRevoked = status == TCBInfoStruct.TCBStatus.TCB_REVOKED; + return (!tcbIsRevoked, status); + } + } + return (true, TCBInfoStruct.TCBStatus.TCB_UNRECOGNIZED); + } + + function _isCpuSvnHigherOrGreater( + uint256[] memory pckCpuSvns, + uint8[] memory tcbCpuSvns + ) + private + pure + returns (bool) + { + if (pckCpuSvns.length != CPUSVN_LENGTH || tcbCpuSvns.length != CPUSVN_LENGTH) { + return false; + } + for (uint256 i; i < CPUSVN_LENGTH; ++i) { + if (pckCpuSvns[i] < tcbCpuSvns[i]) { + return false; + } + } + return true; + } + + function _verifyCertChain(IPEMCertChainLib.ECSha256Certificate[] memory certs) + private + view + returns (bool) + { + uint256 n = certs.length; + bool certRevoked; + bool certNotExpired; + bool verified; + bool certChainCanBeTrusted; + + for (uint256 i; i < n; ++i) { + IPEMCertChainLib.ECSha256Certificate memory issuer; + if (i == n - 1) { + // rootCA + issuer = certs[i]; + } else { + issuer = certs[i + 1]; + if (i == n - 2) { + // this cert is expected to be signed by the root + certRevoked = serialNumIsRevoked[uint256(IPEMCertChainLib.CRL.ROOT)][certs[i] + .serialNumber]; + } else if (certs[i].isPck) { + certRevoked = + serialNumIsRevoked[uint256(IPEMCertChainLib.CRL.PCK)][certs[i].serialNumber]; + } + if (certRevoked) { + break; + } + } + + certNotExpired = + block.timestamp > certs[i].notBefore && block.timestamp < certs[i].notAfter; + if (!certNotExpired) { + break; + } + + verified = sigVerifyLib.verifyES256Signature( + certs[i].tbsCertificate, certs[i].signature, issuer.pubKey + ); + if (!verified) { + break; + } + + bytes32 issuerPubKeyHash = keccak256(issuer.pubKey); + + if (issuerPubKeyHash == ROOTCA_PUBKEY_HASH) { + certChainCanBeTrusted = true; + break; + } + } + + return !certRevoked && certNotExpired && verified && certChainCanBeTrusted; + } + + function _enclaveReportSigVerification( + bytes memory pckCertPubKey, + bytes memory signedQuoteData, + V3Struct.ECDSAQuoteV3AuthData memory authDataV3, + V3Struct.EnclaveReport memory qeEnclaveReport + ) + private + view + returns (bool) + { + bytes32 expectedAuthDataHash = bytes32(qeEnclaveReport.reportData.substring(0, 32)); + bytes memory concatOfAttestKeyAndQeAuthData = + abi.encodePacked(authDataV3.ecdsaAttestationKey, authDataV3.qeAuthData.data); + bytes32 computedAuthDataHash = sha256(concatOfAttestKeyAndQeAuthData); + + bool qeReportDataIsValid = expectedAuthDataHash == computedAuthDataHash; + if (qeReportDataIsValid) { + bytes memory pckSignedQeReportBytes = + V3Parser.packQEReport(authDataV3.pckSignedQeReport); + bool qeSigVerified = sigVerifyLib.verifyES256Signature( + pckSignedQeReportBytes, authDataV3.qeReportSignature, pckCertPubKey + ); + bool quoteSigVerified = sigVerifyLib.verifyES256Signature( + signedQuoteData, authDataV3.ecdsa256BitSignature, authDataV3.ecdsaAttestationKey + ); + return qeSigVerified && quoteSigVerified; + } else { + return false; + } + } + + /// --------------- validate parsed quote --------------- + + /// @dev Provide the parsed quote binary as input + /// @dev The attestation data (or the returned data of this method) + /// is constructed depending on the validity of the quote verification. + /// @dev After confirming that a quote has been verified, the attestation's validity then + /// depends on the + /// status of the associated TCB. + /// @dev Example scenarios as below: + /// -------------------------------- + /// @dev Invalid quote verification: returns (false, INVALID_EXIT_CODE) + /// + /// @dev For all valid quote verification, the validity of the attestation depends on the status + /// of a + /// matching TCBInfo and this is defined in the _attestationTcbIsValid() method, which can be + /// overwritten + /// in derived contracts. (Except for "Revoked" status, which also returns (false, + /// INVALID_EXIT_CODE) value) + /// @dev For all valid quote verification, returns the following data: + /// (_attestationTcbIsValid()) + /// @dev exitCode is defined in the {{ TCBInfoStruct.TCBStatus }} enum + function verifyParsedQuote(V3Struct.ParsedV3QuoteStruct calldata v3quote) + external + view + override + returns (bool, bytes memory) + { + return _verifyParsedQuote(v3quote); + } + + function _verifyParsedQuote(V3Struct.ParsedV3QuoteStruct memory v3quote) + internal + view + returns (bool, bytes memory) + { + bytes memory retData = abi.encodePacked(INVALID_EXIT_CODE); + + // // Step 1: Parse the quote input = 152k gas + ( + bool successful, + , + , + bytes memory signedQuoteData, + V3Struct.ECDSAQuoteV3AuthData memory authDataV3 + ) = V3Parser.validateParsedInput(v3quote); + if (!successful) { + return (false, retData); + } + + // Step 2: Verify application enclave report MRENCLAVE and MRSIGNER + { + if (checkLocalEnclaveReport) { + // 4k gas + bool mrEnclaveIsTrusted = trustedUserMrEnclave[v3quote.localEnclaveReport.mrEnclave]; + bool mrSignerIsTrusted = trustedUserMrSigner[v3quote.localEnclaveReport.mrSigner]; + + if (!mrEnclaveIsTrusted || !mrSignerIsTrusted) { + return (false, retData); + } + } + } + + // Step 3: Verify enclave identity = 43k gas + EnclaveIdStruct.EnclaveIdStatus qeTcbStatus; + { + bool verifiedEnclaveIdSuccessfully; + (verifiedEnclaveIdSuccessfully, qeTcbStatus) = + _verifyQEReportWithIdentity(v3quote.v3AuthData.pckSignedQeReport); + if (!verifiedEnclaveIdSuccessfully) { + return (false, retData); + } + if ( + !verifiedEnclaveIdSuccessfully + || qeTcbStatus == EnclaveIdStruct.EnclaveIdStatus.SGX_ENCLAVE_REPORT_ISVSVN_REVOKED + ) { + return (false, retData); + } + } + + // Step 4: Parse Quote CertChain + IPEMCertChainLib.ECSha256Certificate[] memory parsedQuoteCerts; + TCBInfoStruct.TCBInfo memory fetchedTcbInfo; + { + // 536k gas + parsedQuoteCerts = new IPEMCertChainLib.ECSha256Certificate[](3); + for (uint256 i; i < 3; ++i) { + bool isPckCert = i == 0; // additional parsing for PCKCert + bool certDecodedSuccessfully; + // todo! move decodeCert offchain + (certDecodedSuccessfully, parsedQuoteCerts[i]) = pemCertLib.decodeCert( + authDataV3.certification.decodedCertDataArray[i], isPckCert + ); + if (!certDecodedSuccessfully) { + return (false, retData); + } + } + } + + // Step 5: basic PCK and TCB check = 381k gas + { + string memory parsedFmspc = parsedQuoteCerts[0].pck.sgxExtension.fmspc; + fetchedTcbInfo = tcbInfo[parsedFmspc]; + bool tcbConfigured = LibString.eq(parsedFmspc, fetchedTcbInfo.fmspc); + if (!tcbConfigured) { + return (false, retData); + } + + IPEMCertChainLib.ECSha256Certificate memory pckCert = parsedQuoteCerts[0]; + bool pceidMatched = LibString.eq(pckCert.pck.sgxExtension.pceid, fetchedTcbInfo.pceid); + if (!pceidMatched) { + return (false, retData); + } + } + + // Step 6: Verify TCB Level + TCBInfoStruct.TCBStatus tcbStatus; + { + // 4k gas + bool tcbVerified; + (tcbVerified, tcbStatus) = _checkTcbLevels(parsedQuoteCerts[0].pck, fetchedTcbInfo); + if (!tcbVerified) { + return (false, retData); + } + } + + // Step 7: Verify cert chain for PCK + { + // 660k gas (rootCA pubkey is trusted) + bool pckCertChainVerified = _verifyCertChain(parsedQuoteCerts); + if (!pckCertChainVerified) { + return (false, retData); + } + } + + // Step 8: Verify the local attestation sig and qe report sig = 670k gas + { + bool enclaveReportSigsVerified = _enclaveReportSigVerification( + parsedQuoteCerts[0].pubKey, + signedQuoteData, + authDataV3, + v3quote.v3AuthData.pckSignedQeReport + ); + if (!enclaveReportSigsVerified) { + return (false, retData); + } + } + + retData = abi.encodePacked(sha256(abi.encode(v3quote)), tcbStatus); + + return (_attestationTcbIsValid(tcbStatus), retData); + } +} diff --git a/packages/protocol/contracts/automata-attestation/README.md b/packages/protocol/contracts/automata-attestation/README.md new file mode 100644 index 000000000000..448c4bcd42fb --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/README.md @@ -0,0 +1,5 @@ +# Readme + +Original code (main branch) forked from https://github.com/automata-network/automata-dcap-v3-attestation and applied some gas optimizations here: https://github.com/smtmfft/automata-dcap-v3-attestation/tree/parse-quote-offline, which then got merged into taiko-mono. +The corresponding upstream PR is: https://github.com/automata-network/automata-dcap-v3-attestation/pull/6, waiting to be merged. +Atomata's attestation shall be 100% identical to taiko-mono's attestation code at this point. diff --git a/packages/protocol/contracts/automata-attestation/interfaces/IAttestation.sol b/packages/protocol/contracts/automata-attestation/interfaces/IAttestation.sol new file mode 100644 index 000000000000..7f918de6e2ca --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/interfaces/IAttestation.sol @@ -0,0 +1,13 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { V3Struct } from "../lib/QuoteV3Auth/V3Struct.sol"; + +/// @title IAttestation +/// @custom:security-contact security@taiko.xyz +interface IAttestation { + function verifyAttestation(bytes calldata data) external returns (bool); + function verifyParsedQuote(V3Struct.ParsedV3QuoteStruct calldata v3quote) + external + returns (bool success, bytes memory retData); +} diff --git a/packages/protocol/contracts/automata-attestation/interfaces/ISigVerifyLib.sol b/packages/protocol/contracts/automata-attestation/interfaces/ISigVerifyLib.sol new file mode 100644 index 000000000000..5f407625cadc --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/interfaces/ISigVerifyLib.sol @@ -0,0 +1,15 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title ISigVerifyLib +/// @custom:security-contact security@taiko.xyz +interface ISigVerifyLib { + function verifyES256Signature( + bytes memory tbs, + bytes memory signature, + bytes memory publicKey + ) + external + view + returns (bool sigValid); +} diff --git a/packages/protocol/contracts/automata-attestation/lib/EnclaveIdStruct.sol b/packages/protocol/contracts/automata-attestation/lib/EnclaveIdStruct.sol new file mode 100644 index 000000000000..3e889e084e57 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/EnclaveIdStruct.sol @@ -0,0 +1,30 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title EnclaveIdStruct +/// @custom:security-contact security@taiko.xyz +library EnclaveIdStruct { + struct EnclaveId { + bytes4 miscselect; // Slot 1: + bytes4 miscselectMask; + uint16 isvprodid; + bytes16 attributes; // Slot 2 + bytes16 attributesMask; + bytes32 mrsigner; // Slot 3 + TcbLevel[] tcbLevels; // Slot 4 + } + + struct TcbLevel { + TcbObj tcb; + EnclaveIdStatus tcbStatus; + } + + struct TcbObj { + uint16 isvsvn; + } + + enum EnclaveIdStatus { + OK, + SGX_ENCLAVE_REPORT_ISVSVN_REVOKED + } +} diff --git a/packages/protocol/contracts/automata-attestation/lib/PEMCertChainLib.sol b/packages/protocol/contracts/automata-attestation/lib/PEMCertChainLib.sol new file mode 100644 index 000000000000..f69c3e0200c9 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/PEMCertChainLib.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { LibString } from "solady/src/utils/LibString.sol"; +import { Asn1Decode, NodePtr } from "../utils/Asn1Decode.sol"; +import { BytesUtils } from "../utils/BytesUtils.sol"; +import { X509DateUtils } from "../utils/X509DateUtils.sol"; +import { IPEMCertChainLib } from "./interfaces/IPEMCertChainLib.sol"; + +/// @title PEMCertChainLib +/// @custom:security-contact security@taiko.xyz +contract PEMCertChainLib is IPEMCertChainLib { + using Asn1Decode for bytes; + using NodePtr for uint256; + using BytesUtils for bytes; + + string internal constant HEADER = "-----BEGIN CERTIFICATE-----"; + string internal constant FOOTER = "-----END CERTIFICATE-----"; + uint256 internal constant HEADER_LENGTH = 27; + uint256 internal constant FOOTER_LENGTH = 25; + + string internal constant PCK_COMMON_NAME = "Intel SGX PCK Certificate"; + string internal constant PLATFORM_ISSUER_NAME = "Intel SGX PCK Platform CA"; + string internal constant PROCESSOR_ISSUER_NAME = "Intel SGX PCK Processor CA"; + bytes internal constant SGX_EXTENSION_OID = hex"2A864886F84D010D01"; + bytes internal constant TCB_OID = hex"2A864886F84D010D0102"; + bytes internal constant PCESVN_OID = hex"2A864886F84D010D010211"; + bytes internal constant PCEID_OID = hex"2A864886F84D010D0103"; + bytes internal constant FMSPC_OID = hex"2A864886F84D010D0104"; + + // https://github.com/intel/SGXDataCenterAttestationPrimitives/blob/e7604e02331b3377f3766ed3653250e03af72d45/QuoteVerification/QVL/Src/AttestationLibrary/src/CertVerification/X509Constants.h#L64 + uint256 constant SGX_TCB_CPUSVN_SIZE = 16; + + struct PCKTCBFlags { + bool fmspcFound; + bool pceidFound; + bool tcbFound; + } + + function splitCertificateChain( + bytes memory pemChain, + uint256 size + ) + external + pure + returns (bool success, bytes[] memory certs) + { + certs = new bytes[](size); + string memory pemChainStr = string(pemChain); + + uint256 index = 0; + uint256 len = pemChain.length; + + for (uint256 i; i < size; ++i) { + string memory input; + if (i != 0) { + input = LibString.slice(pemChainStr, index, index + len); + } else { + input = pemChainStr; + } + uint256 increment; + (success, certs[i], increment) = _removeHeadersAndFooters(input); + + if (!success) { + return (false, certs); + } + + index += increment; + } + + success = true; + } + + function decodeCert( + bytes memory der, + bool isPckCert + ) + external + pure + returns (bool success, ECSha256Certificate memory cert) + { + uint256 root = der.root(); + + // Entering tbsCertificate sequence + uint256 tbsParentPtr = der.firstChildOf(root); + + // Begin iterating through the descendants of tbsCertificate + uint256 tbsPtr = der.firstChildOf(tbsParentPtr); + + // The Serial Number is located one element below Version + + // The issuer commonName value is contained in the Issuer sequence + // which is 3 elements below the first element of the tbsCertificate sequence + + // The Validity sequence is located 4 elements below the first element of the tbsCertificate + // sequence + + // The subject commanName value is contained in the Subject sequence + // which is 5 elements below the first element of the tbsCertificate sequence + + // The PublicKey is located in the second element of subjectPublicKeyInfo sequence + // which is 6 elements below the first element of the tbsCertificate sequence + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + bytes memory serialNumBytes = der.bytesAt(tbsPtr); + cert.serialNumber = serialNumBytes; + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + tbsPtr = der.nextSiblingOf(tbsPtr); + + if (isPckCert) { + uint256 issuerPtr = der.firstChildOf(tbsPtr); + issuerPtr = der.firstChildOf(issuerPtr); + issuerPtr = der.firstChildOf(issuerPtr); + issuerPtr = der.nextSiblingOf(issuerPtr); + cert.pck.issuerName = string(der.bytesAt(issuerPtr)); + bool issuerNameIsValid = LibString.eq(cert.pck.issuerName, PLATFORM_ISSUER_NAME) + || LibString.eq(cert.pck.issuerName, PROCESSOR_ISSUER_NAME); + if (!issuerNameIsValid) { + return (false, cert); + } + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + uint256 notBeforePtr = der.firstChildOf(tbsPtr); + uint256 notAfterPtr = der.nextSiblingOf(notBeforePtr); + bytes1 notBeforeTag = der[notBeforePtr.ixs()]; + bytes1 notAfterTag = der[notAfterPtr.ixs()]; + if ( + (notBeforeTag != 0x17 && notBeforeTag != 0x18) + || (notAfterTag != 0x17 && notAfterTag != 0x18) + ) { + return (false, cert); + } + cert.notBefore = X509DateUtils.toTimestamp(der.bytesAt(notBeforePtr)); + cert.notAfter = X509DateUtils.toTimestamp(der.bytesAt(notAfterPtr)); + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + if (isPckCert) { + uint256 subjectPtr = der.firstChildOf(tbsPtr); + subjectPtr = der.firstChildOf(subjectPtr); + subjectPtr = der.firstChildOf(subjectPtr); + subjectPtr = der.nextSiblingOf(subjectPtr); + cert.pck.commonName = string(der.bytesAt(subjectPtr)); + if (!LibString.eq(cert.pck.commonName, PCK_COMMON_NAME)) { + return (false, cert); + } + } + + tbsPtr = der.nextSiblingOf(tbsPtr); + + { + // Entering subjectPublicKeyInfo sequence + uint256 subjectPublicKeyInfoPtr = der.firstChildOf(tbsPtr); + subjectPublicKeyInfoPtr = der.nextSiblingOf(subjectPublicKeyInfoPtr); + + // The Signature sequence is located two sibling elements below the tbsCertificate + // element + uint256 sigPtr = der.nextSiblingOf(tbsParentPtr); + sigPtr = der.nextSiblingOf(sigPtr); + + // Skip three bytes to the right + // the three bytes in question: 0x034700 or 0x034800 or 0x034900 + sigPtr = NodePtr.getPtr(sigPtr.ixs() + 3, sigPtr.ixf() + 3, sigPtr.ixl()); + + sigPtr = der.firstChildOf(sigPtr); + bytes memory sigX = _trimBytes(der.bytesAt(sigPtr), 32); + + sigPtr = der.nextSiblingOf(sigPtr); + bytes memory sigY = _trimBytes(der.bytesAt(sigPtr), 32); + + cert.tbsCertificate = der.allBytesAt(tbsParentPtr); + cert.pubKey = _trimBytes(der.bytesAt(subjectPublicKeyInfoPtr), 64); + cert.signature = abi.encodePacked(sigX, sigY); + } + + if (isPckCert) { + // entering Extension sequence + tbsPtr = der.nextSiblingOf(tbsPtr); + + // check for the extension tag + if (der[tbsPtr.ixs()] != 0xA3) { + return (false, cert); + } + + tbsPtr = der.firstChildOf(tbsPtr); + tbsPtr = der.firstChildOf(tbsPtr); + + bool sgxExtnTraversedSuccessfully; + uint256 pcesvn; + uint256[] memory cpuSvns; + bytes memory fmspcBytes; + bytes memory pceidBytes; + (sgxExtnTraversedSuccessfully, pcesvn, cpuSvns, fmspcBytes, pceidBytes) = + _findPckTcbInfo(der, tbsPtr, tbsParentPtr); + if (!sgxExtnTraversedSuccessfully) { + return (false, cert); + } + cert.pck.sgxExtension.pcesvn = pcesvn; + cert.pck.sgxExtension.sgxTcbCompSvnArr = cpuSvns; + cert.pck.sgxExtension.pceid = LibString.toHexStringNoPrefix(pceidBytes); + cert.pck.sgxExtension.fmspc = LibString.toHexStringNoPrefix(fmspcBytes); + cert.isPck = true; + } + + success = true; + } + + function _removeHeadersAndFooters(string memory pemData) + private + pure + returns (bool success, bytes memory extracted, uint256 endIndex) + { + // Check if the input contains the "BEGIN" and "END" headers + uint256 beginPos = LibString.indexOf(pemData, HEADER); + uint256 endPos = LibString.indexOf(pemData, FOOTER); + + bool headerFound = beginPos != LibString.NOT_FOUND; + bool footerFound = endPos != LibString.NOT_FOUND; + + if (!headerFound || !footerFound) { + return (false, extracted, endIndex); + } + + // Extract the content between the headers + uint256 contentStart = beginPos + HEADER_LENGTH; + + // Extract and return the content + bytes memory contentBytes; + + // do not include newline + bytes memory delimiter = hex"0a"; + string memory contentSlice = LibString.slice(pemData, contentStart, endPos); + string[] memory split = LibString.split(contentSlice, string(delimiter)); + string memory contentStr; + + for (uint256 i; i < split.length; ++i) { + contentStr = LibString.concat(contentStr, split[i]); + } + + contentBytes = bytes(contentStr); + return (true, contentBytes, endPos + FOOTER_LENGTH); + } + + function _trimBytes( + bytes memory input, + uint256 expectedLength + ) + private + pure + returns (bytes memory output) + { + uint256 n = input.length; + + if (n <= expectedLength) { + return input; + } + uint256 lengthDiff = n - expectedLength; + output = input.substring(lengthDiff, expectedLength); + } + + function _findPckTcbInfo( + bytes memory der, + uint256 tbsPtr, + uint256 tbsParentPtr + ) + private + pure + returns ( + bool success, + uint256 pcesvn, + uint256[] memory cpusvns, + bytes memory fmspcBytes, + bytes memory pceidBytes + ) + { + // iterate through the elements in the Extension sequence + // until we locate the SGX Extension OID + while (tbsPtr != 0) { + uint256 internalPtr = der.firstChildOf(tbsPtr); + if (der[internalPtr.ixs()] != 0x06) { + return (false, pcesvn, cpusvns, fmspcBytes, pceidBytes); + } + + if (BytesUtils.compareBytes(der.bytesAt(internalPtr), SGX_EXTENSION_OID)) { + // 1.2.840.113741.1.13.1 + internalPtr = der.nextSiblingOf(internalPtr); + uint256 extnValueParentPtr = der.rootOfOctetStringAt(internalPtr); + uint256 extnValuePtr = der.firstChildOf(extnValueParentPtr); + + // Copy flags to memory to avoid stack too deep + PCKTCBFlags memory flags; + + while (!(flags.fmspcFound && flags.pceidFound && flags.tcbFound)) { + uint256 extnValueOidPtr = der.firstChildOf(extnValuePtr); + if (der[extnValueOidPtr.ixs()] != 0x06) { + return (false, pcesvn, cpusvns, fmspcBytes, pceidBytes); + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), TCB_OID)) { + // 1.2.840.113741.1.13.1.2 + (flags.tcbFound, pcesvn, cpusvns) = _findTcb(der, extnValueOidPtr); + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), PCEID_OID)) { + // 1.2.840.113741.1.13.1.3 + uint256 pceidPtr = der.nextSiblingOf(extnValueOidPtr); + pceidBytes = der.bytesAt(pceidPtr); + flags.pceidFound = true; + } + if (BytesUtils.compareBytes(der.bytesAt(extnValueOidPtr), FMSPC_OID)) { + // 1.2.840.113741.1.13.1.4 + uint256 fmspcPtr = der.nextSiblingOf(extnValueOidPtr); + fmspcBytes = der.bytesAt(fmspcPtr); + flags.fmspcFound = true; + } + + if (extnValuePtr.ixl() < extnValueParentPtr.ixl()) { + extnValuePtr = der.nextSiblingOf(extnValuePtr); + } else { + break; + } + } + success = flags.fmspcFound && flags.pceidFound && flags.tcbFound; + break; + } + + if (tbsPtr.ixl() < tbsParentPtr.ixl()) { + tbsPtr = der.nextSiblingOf(tbsPtr); + } else { + tbsPtr = 0; // exit + } + } + } + + function _findTcb( + bytes memory der, + uint256 oidPtr + ) + private + pure + returns (bool success, uint256 pcesvn, uint256[] memory cpusvns) + { + // sibling of tcbOid + uint256 tcbPtr = der.nextSiblingOf(oidPtr); + // get the first svn object in the sequence + uint256 svnParentPtr = der.firstChildOf(tcbPtr); + cpusvns = new uint256[](SGX_TCB_CPUSVN_SIZE); + for (uint256 i; i < SGX_TCB_CPUSVN_SIZE + 1; ++i) { + uint256 svnPtr = der.firstChildOf(svnParentPtr); // OID + uint256 svnValuePtr = der.nextSiblingOf(svnPtr); // value + bytes memory svnValueBytes = der.bytesAt(svnValuePtr); + uint16 svnValue = svnValueBytes.length < 2 + ? uint16(bytes2(svnValueBytes)) / 256 + : uint16(bytes2(svnValueBytes)); + if (BytesUtils.compareBytes(der.bytesAt(svnPtr), PCESVN_OID)) { + // pcesvn is 4 bytes in size + pcesvn = uint256(svnValue); + } else { + // each cpusvn is at maximum two bytes in size + uint256 cpusvn = uint256(svnValue); + cpusvns[i] = cpusvn; + } + + // iterate to the next svn object in the sequence + svnParentPtr = der.nextSiblingOf(svnParentPtr); + } + success = true; + } +} diff --git a/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol new file mode 100644 index 000000000000..4e574b7eec91 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol @@ -0,0 +1,306 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { Base64 } from "solady/src/utils/Base64.sol"; +import { BytesUtils } from "../../utils/BytesUtils.sol"; +import { IPEMCertChainLib, PEMCertChainLib } from "../../lib/PEMCertChainLib.sol"; +import { V3Struct } from "./V3Struct.sol"; + +/// @title V3Parser +/// @custom:security-contact security@taiko.xyz +library V3Parser { + using BytesUtils for bytes; + + uint256 internal constant MINIMUM_QUOTE_LENGTH = 1020; + bytes2 internal constant SUPPORTED_QUOTE_VERSION = 0x0300; + bytes2 internal constant SUPPORTED_ATTESTATION_KEY_TYPE = 0x0200; + // SGX only + bytes4 internal constant SUPPORTED_TEE_TYPE = 0; + bytes16 internal constant VALID_QE_VENDOR_ID = 0x939a7233f79c4ca9940a0db3957f0607; + + error V3PARSER_INVALID_QUOTE_LENGTN(); + error V3PARSER_INVALID_QUOTE_MEMBER_LENGTN(); + error V3PARSER_INVALID_QEREPORT_LENGTN(); + error V3PARSER_UNSUPPORT_CERTIFICATION_TYPE(); + error V3PARSER_INVALID_CERTIFICATION_CHAIN_SIZE(); + error V3PARSER_INVALID_CERTIFICATION_CHAIN_DATA(); + error V3PARSER_INVALID_ECDSA_SIGNATURE(); + error V3PARSER_INVALID_QEAUTHDATA_SIZE(); + + function parseInput( + bytes memory quote, + address pemCertLibAddr + ) + internal + pure + returns (bool success, V3Struct.ParsedV3QuoteStruct memory v3ParsedQuote) + { + if (quote.length <= MINIMUM_QUOTE_LENGTH) { + return (false, v3ParsedQuote); + } + + uint256 localAuthDataSize = littleEndianDecode(quote.substring(432, 4)); + if (quote.length - 436 != localAuthDataSize) { + return (false, v3ParsedQuote); + } + + bytes memory rawHeader = quote.substring(0, 48); + (bool headerVerifiedSuccessfully, V3Struct.Header memory header) = + parseAndVerifyHeader(rawHeader); + if (!headerVerifiedSuccessfully) { + return (false, v3ParsedQuote); + } + + (bool authDataVerifiedSuccessfully, V3Struct.ECDSAQuoteV3AuthData memory authDataV3) = + parseAuthDataAndVerifyCertType(quote.substring(436, localAuthDataSize), pemCertLibAddr); + if (!authDataVerifiedSuccessfully) { + return (false, v3ParsedQuote); + } + + bytes memory rawLocalEnclaveReport = quote.substring(48, 384); + V3Struct.EnclaveReport memory localEnclaveReport = parseEnclaveReport(rawLocalEnclaveReport); + + v3ParsedQuote = V3Struct.ParsedV3QuoteStruct({ + header: header, + localEnclaveReport: localEnclaveReport, + v3AuthData: authDataV3 + }); + success = true; + } + + function validateParsedInput(V3Struct.ParsedV3QuoteStruct memory v3Quote) + internal + pure + returns ( + bool success, + V3Struct.Header memory header, + V3Struct.EnclaveReport memory localEnclaveReport, + bytes memory signedQuoteData, // concatenation of header and local enclave report bytes + V3Struct.ECDSAQuoteV3AuthData memory authDataV3 + ) + { + success = true; + localEnclaveReport = v3Quote.localEnclaveReport; + V3Struct.EnclaveReport memory pckSignedQeReport = v3Quote.v3AuthData.pckSignedQeReport; + + if ( + localEnclaveReport.reserved3.length != 96 || localEnclaveReport.reserved4.length != 60 + || localEnclaveReport.reportData.length != 64 + ) revert V3PARSER_INVALID_QUOTE_MEMBER_LENGTN(); + + if ( + pckSignedQeReport.reserved3.length != 96 || pckSignedQeReport.reserved4.length != 60 + || pckSignedQeReport.reportData.length != 64 + ) { + revert V3PARSER_INVALID_QEREPORT_LENGTN(); + } + + if (v3Quote.v3AuthData.certification.certType != 5) { + revert V3PARSER_UNSUPPORT_CERTIFICATION_TYPE(); + } + + if (v3Quote.v3AuthData.certification.decodedCertDataArray.length != 3) { + revert V3PARSER_INVALID_CERTIFICATION_CHAIN_SIZE(); + } + + if ( + v3Quote.v3AuthData.ecdsa256BitSignature.length != 64 + || v3Quote.v3AuthData.ecdsaAttestationKey.length != 64 + || v3Quote.v3AuthData.qeReportSignature.length != 64 + ) { + revert V3PARSER_INVALID_ECDSA_SIGNATURE(); + } + + if ( + v3Quote.v3AuthData.qeAuthData.parsedDataSize + != v3Quote.v3AuthData.qeAuthData.data.length + ) { + revert V3PARSER_INVALID_QEAUTHDATA_SIZE(); + } + + uint32 totalQuoteSize = 48 // header + + 384 // local QE report + + 64 // ecdsa256BitSignature + + 64 // ecdsaAttestationKey + + 384 // QE report + + 64 // qeReportSignature + + 2 // sizeof(v3Quote.v3AuthData.qeAuthData.parsedDataSize) + + v3Quote.v3AuthData.qeAuthData.parsedDataSize + 2 // sizeof(v3Quote.v3AuthData.certification.certType) + + 4 // sizeof(v3Quote.v3AuthData.certification.certDataSize) + + v3Quote.v3AuthData.certification.certDataSize; + if (totalQuoteSize <= MINIMUM_QUOTE_LENGTH) { + revert V3PARSER_INVALID_QUOTE_LENGTN(); + } + + header = v3Quote.header; + bytes memory headerBytes = abi.encodePacked( + header.version, + header.attestationKeyType, + header.teeType, + header.qeSvn, + header.pceSvn, + header.qeVendorId, + header.userData + ); + + signedQuoteData = abi.encodePacked(headerBytes, V3Parser.packQEReport(localEnclaveReport)); + authDataV3 = v3Quote.v3AuthData; + } + + function parseEnclaveReport(bytes memory rawEnclaveReport) + internal + pure + returns (V3Struct.EnclaveReport memory enclaveReport) + { + enclaveReport.cpuSvn = bytes16(rawEnclaveReport.substring(0, 16)); + enclaveReport.miscSelect = bytes4(rawEnclaveReport.substring(16, 4)); + enclaveReport.reserved1 = bytes28(rawEnclaveReport.substring(20, 28)); + enclaveReport.attributes = bytes16(rawEnclaveReport.substring(48, 16)); + enclaveReport.mrEnclave = bytes32(rawEnclaveReport.substring(64, 32)); + enclaveReport.reserved2 = bytes32(rawEnclaveReport.substring(96, 32)); + enclaveReport.mrSigner = bytes32(rawEnclaveReport.substring(128, 32)); + enclaveReport.reserved3 = rawEnclaveReport.substring(160, 96); + enclaveReport.isvProdId = uint16(littleEndianDecode(rawEnclaveReport.substring(256, 2))); + enclaveReport.isvSvn = uint16(littleEndianDecode(rawEnclaveReport.substring(258, 2))); + enclaveReport.reserved4 = rawEnclaveReport.substring(260, 60); + enclaveReport.reportData = rawEnclaveReport.substring(320, 64); + } + + function littleEndianDecode(bytes memory encoded) private pure returns (uint256 decoded) { + for (uint256 i; i < encoded.length; ++i) { + uint256 digits = uint256(uint8(bytes1(encoded[i]))); + uint256 upperDigit = digits / 16; + uint256 lowerDigit = digits % 16; + + uint256 acc = lowerDigit * (16 ** (2 * i)); + acc += upperDigit * (16 ** ((2 * i) + 1)); + + decoded += acc; + } + } + + function parseAndVerifyHeader(bytes memory rawHeader) + private + pure + returns (bool success, V3Struct.Header memory header) + { + bytes2 version = bytes2(rawHeader.substring(0, 2)); + if (version != SUPPORTED_QUOTE_VERSION) { + return (false, header); + } + + bytes2 attestationKeyType = bytes2(rawHeader.substring(2, 2)); + if (attestationKeyType != SUPPORTED_ATTESTATION_KEY_TYPE) { + return (false, header); + } + + bytes4 teeType = bytes4(rawHeader.substring(4, 4)); + if (teeType != SUPPORTED_TEE_TYPE) { + return (false, header); + } + + bytes16 qeVendorId = bytes16(rawHeader.substring(12, 16)); + if (qeVendorId != VALID_QE_VENDOR_ID) { + return (false, header); + } + + header = V3Struct.Header({ + version: version, + attestationKeyType: attestationKeyType, + teeType: teeType, + qeSvn: bytes2(rawHeader.substring(8, 2)), + pceSvn: bytes2(rawHeader.substring(10, 2)), + qeVendorId: qeVendorId, + userData: bytes20(rawHeader.substring(28, 20)) + }); + + success = true; + } + + function parseAuthDataAndVerifyCertType( + bytes memory rawAuthData, + address pemCertLibAddr + ) + private + pure + returns (bool success, V3Struct.ECDSAQuoteV3AuthData memory authDataV3) + { + V3Struct.QEAuthData memory qeAuthData; + qeAuthData.parsedDataSize = uint16(littleEndianDecode(rawAuthData.substring(576, 2))); + qeAuthData.data = rawAuthData.substring(578, qeAuthData.parsedDataSize); + + uint256 offset = 578 + qeAuthData.parsedDataSize; + V3Struct.CertificationData memory cert; + cert.certType = uint16(littleEndianDecode(rawAuthData.substring(offset, 2))); + if (cert.certType < 1 || cert.certType > 5) { + return (false, authDataV3); + } + offset += 2; + cert.certDataSize = uint32(littleEndianDecode(rawAuthData.substring(offset, 4))); + offset += 4; + bytes memory certData = rawAuthData.substring(offset, cert.certDataSize); + cert.decodedCertDataArray = parseCerificationChainBytes(certData, pemCertLibAddr); + + authDataV3.ecdsa256BitSignature = rawAuthData.substring(0, 64); + authDataV3.ecdsaAttestationKey = rawAuthData.substring(64, 64); + bytes memory rawQeReport = rawAuthData.substring(128, 384); + authDataV3.pckSignedQeReport = parseEnclaveReport(rawQeReport); + authDataV3.qeReportSignature = rawAuthData.substring(512, 64); + authDataV3.qeAuthData = qeAuthData; + authDataV3.certification = cert; + + success = true; + } + + /// enclaveReport to bytes for hash calculation. + /// the only difference between enclaveReport and packedQEReport is the + /// order of isvProdId and isvSvn. enclaveReport is in little endian, while + /// in bytes should be in big endian according to Intel spec. + /// @param enclaveReport enclave report + /// @return packedQEReport enclave report in bytes + function packQEReport(V3Struct.EnclaveReport memory enclaveReport) + internal + pure + returns (bytes memory packedQEReport) + { + uint16 isvProdIdPackBE = (enclaveReport.isvProdId >> 8) | (enclaveReport.isvProdId << 8); + uint16 isvSvnPackBE = (enclaveReport.isvSvn >> 8) | (enclaveReport.isvSvn << 8); + packedQEReport = abi.encodePacked( + enclaveReport.cpuSvn, + enclaveReport.miscSelect, + enclaveReport.reserved1, + enclaveReport.attributes, + enclaveReport.mrEnclave, + enclaveReport.reserved2, + enclaveReport.mrSigner, + enclaveReport.reserved3, + isvProdIdPackBE, + isvSvnPackBE, + enclaveReport.reserved4, + enclaveReport.reportData + ); + } + + function parseCerificationChainBytes( + bytes memory certBytes, + address pemCertLibAddr + ) + internal + pure + returns (bytes[3] memory certChainData) + { + IPEMCertChainLib pemCertLib = PEMCertChainLib(pemCertLibAddr); + IPEMCertChainLib.ECSha256Certificate[] memory parsedQuoteCerts; + (bool certParsedSuccessfully, bytes[] memory quoteCerts) = + pemCertLib.splitCertificateChain(certBytes, 3); + if (!certParsedSuccessfully) { + revert V3PARSER_INVALID_CERTIFICATION_CHAIN_DATA(); + } + parsedQuoteCerts = new IPEMCertChainLib.ECSha256Certificate[](3); + for (uint256 i; i < 3; ++i) { + quoteCerts[i] = Base64.decode(string(quoteCerts[i])); + } + + certChainData = [quoteCerts[0], quoteCerts[1], quoteCerts[2]]; + } +} diff --git a/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Struct.sol b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Struct.sol new file mode 100644 index 000000000000..3fbf799c8fc3 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Struct.sol @@ -0,0 +1,61 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title V3Struct +/// @custom:security-contact security@taiko.xyz +library V3Struct { + struct Header { + bytes2 version; + bytes2 attestationKeyType; + bytes4 teeType; + bytes2 qeSvn; + bytes2 pceSvn; + bytes16 qeVendorId; + bytes20 userData; + } + + struct EnclaveReport { + bytes16 cpuSvn; + bytes4 miscSelect; + bytes28 reserved1; + bytes16 attributes; + bytes32 mrEnclave; + bytes32 reserved2; + bytes32 mrSigner; + bytes reserved3; // 96 bytes + uint16 isvProdId; + uint16 isvSvn; + bytes reserved4; // 60 bytes + bytes reportData; // 64 bytes - For QEReports, this contains the hash of the concatenation + // of attestation key and QEAuthData + } + + struct QEAuthData { + uint16 parsedDataSize; + bytes data; + } + + struct CertificationData { + uint16 certType; + // todo! In encoded path, we need to calculate the size of certDataArray + // certDataSize = len(join((BEGIN_CERT, certArray[i], END_CERT) for i in 0..3)) + // But for plain bytes path, we don't need that. + uint32 certDataSize; + bytes[3] decodedCertDataArray; // base64 decoded cert bytes array + } + + struct ECDSAQuoteV3AuthData { + bytes ecdsa256BitSignature; // 64 bytes + bytes ecdsaAttestationKey; // 64 bytes + EnclaveReport pckSignedQeReport; // 384 bytes + bytes qeReportSignature; // 64 bytes + QEAuthData qeAuthData; + CertificationData certification; + } + + struct ParsedV3QuoteStruct { + Header header; + EnclaveReport localEnclaveReport; + ECDSAQuoteV3AuthData v3AuthData; + } +} diff --git a/packages/protocol/contracts/automata-attestation/lib/TCBInfoStruct.sol b/packages/protocol/contracts/automata-attestation/lib/TCBInfoStruct.sol new file mode 100644 index 000000000000..f40c05bdef58 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/TCBInfoStruct.sol @@ -0,0 +1,29 @@ +//SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title TCBInfoStruct +/// @custom:security-contact security@taiko.xyz +library TCBInfoStruct { + struct TCBInfo { + string pceid; + string fmspc; + TCBLevelObj[] tcbLevels; + } + + struct TCBLevelObj { + uint256 pcesvn; + uint8[] sgxTcbCompSvnArr; + TCBStatus status; + } + + enum TCBStatus { + OK, + TCB_SW_HARDENING_NEEDED, + TCB_CONFIGURATION_AND_SW_HARDENING_NEEDED, + TCB_CONFIGURATION_NEEDED, + TCB_OUT_OF_DATE, + TCB_OUT_OF_DATE_CONFIGURATION_NEEDED, + TCB_REVOKED, + TCB_UNRECOGNIZED + } +} diff --git a/packages/protocol/contracts/automata-attestation/lib/interfaces/IPEMCertChainLib.sol b/packages/protocol/contracts/automata-attestation/lib/interfaces/IPEMCertChainLib.sol new file mode 100644 index 000000000000..7f728558bda8 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/lib/interfaces/IPEMCertChainLib.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IPEMCertChainLib +/// @custom:security-contact security@taiko.xyz +interface IPEMCertChainLib { + struct ECSha256Certificate { + uint256 notBefore; + uint256 notAfter; + bytes serialNumber; + bytes tbsCertificate; + bytes pubKey; + bytes signature; + bool isPck; + PCKCertificateField pck; + } + + struct PCKCertificateField { + string commonName; + string issuerName; + PCKTCBInfo sgxExtension; + } + + struct PCKTCBInfo { + string pceid; + string fmspc; + uint256 pcesvn; + uint256[] sgxTcbCompSvnArr; + } + + enum CRL { + PCK, + ROOT + } + + function splitCertificateChain( + bytes memory pemChain, + uint256 size + ) + external + pure + returns (bool success, bytes[] memory certs); + + function decodeCert( + bytes memory der, + bool isPckCert + ) + external + pure + returns (bool success, ECSha256Certificate memory cert); +} diff --git a/packages/protocol/contracts/automata-attestation/utils/Asn1Decode.sol b/packages/protocol/contracts/automata-attestation/utils/Asn1Decode.sol new file mode 100644 index 000000000000..94b165c6c76b --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/utils/Asn1Decode.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +// Original source: https://github.com/JonahGroendal/asn1-decode +pragma solidity 0.8.24; + +// Inspired by PufferFinance/rave - Apache-2.0 license +// https://github.com/JonahGroendal/asn1-decode/blob/5c2d1469fc678513753786acb441e597969192ec/contracts/Asn1Decode.sol + +import "./BytesUtils.sol"; + +/// @title NodePtr +/// @custom:security-contact security@taiko.xyz +library NodePtr { + // Unpack first byte index + function ixs(uint256 self) internal pure returns (uint256) { + return uint80(self); + } + + // Unpack first content byte index + function ixf(uint256 self) internal pure returns (uint256) { + return uint80(self >> 80); + } + + // Unpack last content byte index + function ixl(uint256 self) internal pure returns (uint256) { + return uint80(self >> 160); + } + + // Pack 3 uint80s into a uint256 + function getPtr(uint256 _ixs, uint256 _ixf, uint256 _ixl) internal pure returns (uint256) { + _ixs |= _ixf << 80; + _ixs |= _ixl << 160; + return _ixs; + } +} + +/// @title Asn1Decode +/// @custom:security-contact security@taiko.xyz +library Asn1Decode { + using NodePtr for uint256; + using BytesUtils for bytes; + + /* + * @dev Get the root node. First step in traversing an ASN1 structure + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function root(bytes memory der) internal pure returns (uint256) { + return _readNodeLength(der, 0); + } + + /* + * @dev Get the root node of an ASN1 structure that's within an octet string value + * @param der The DER-encoded ASN1 structure + * @return A pointer to the outermost node + */ + function rootOfOctetStringAt(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] == 0x04, "Not type OCTET STRING"); + return _readNodeLength(der, ptr.ixf()); + } + + /* + * @dev Get the next sibling node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the next sibling node + */ + function nextSiblingOf(bytes memory der, uint256 ptr) internal pure returns (uint256) { + return _readNodeLength(der, ptr.ixl() + 1); + } + + /* + * @dev Get the first child node of the current node + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return A pointer to the first child node + */ + function firstChildOf(bytes memory der, uint256 ptr) internal pure returns (uint256) { + require(der[ptr.ixs()] & 0x20 == 0x20, "Not a constructed type"); + return _readNodeLength(der, ptr.ixf()); + } + + /* + * @dev Extract value of node from DER-encoded structure + * @param der The der-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return Value bytes of node + */ + function bytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + return der.substring(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); + } + + /* + * @dev Extract entire node from DER-encoded structure + * @param der The DER-encoded ASN1 structure + * @param ptr Points to the indices of the current node + * @return All bytes of node + */ + function allBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes memory) { + return der.substring(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); + } + + function keccakOfBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes32) { + return der.keccak(ptr.ixf(), ptr.ixl() + 1 - ptr.ixf()); + } + + function keccakOfAllBytesAt(bytes memory der, uint256 ptr) internal pure returns (bytes32) { + return der.keccak(ptr.ixs(), ptr.ixl() + 1 - ptr.ixs()); + } + + function _readNodeLength(bytes memory der, uint256 ix) private pure returns (uint256) { + uint256 length; + uint80 ixFirstContentByte; + uint80 ixLastContentByte; + if ((der[ix + 1] & 0x80) == 0) { + length = uint8(der[ix + 1]); + ixFirstContentByte = uint80(ix + 2); + ixLastContentByte = uint80(ixFirstContentByte + length - 1); + } else { + uint8 lengthbytesLength = uint8(der[ix + 1] & 0x7F); + if (lengthbytesLength == 1) { + length = der.readUint8(ix + 2); + } else if (lengthbytesLength == 2) { + length = der.readUint16(ix + 2); + } else { + length = uint256( + der.readBytesN(ix + 2, lengthbytesLength) >> (32 - lengthbytesLength) * 8 + ); + } + ixFirstContentByte = uint80(ix + 2 + lengthbytesLength); + ixLastContentByte = uint80(ixFirstContentByte + length - 1); + } + return NodePtr.getPtr(ix, ixFirstContentByte, ixLastContentByte); + } +} diff --git a/packages/protocol/contracts/automata-attestation/utils/BytesUtils.sol b/packages/protocol/contracts/automata-attestation/utils/BytesUtils.sol new file mode 100644 index 000000000000..f1711842248c --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/utils/BytesUtils.sol @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: BSD 2-Clause License +pragma solidity 0.8.24; + +// Inspired by ensdomains/dnssec-oracle - BSD-2-Clause license +// https://github.com/ensdomains/dnssec-oracle/blob/master/contracts/BytesUtils.sol +/// @title BytesUtils +/// @custom:security-contact security@taiko.xyz +library BytesUtils { + /* + * @dev Returns the keccak-256 hash of a byte range. + * @param self The byte string to hash. + * @param offset The position to start hashing at. + * @param len The number of bytes to hash. + * @return The hash of the byte range. + */ + function keccak( + bytes memory self, + uint256 offset, + uint256 len + ) + internal + pure + returns (bytes32 ret) + { + require(offset + len <= self.length, "invalid offset"); + assembly { + ret := keccak256(add(add(self, 32), offset), len) + } + } + + /* + * @dev Returns true if the two byte ranges are equal. + * @param self The first byte range to compare. + * @param offset The offset into the first byte range. + * @param other The second byte range to compare. + * @param otherOffset The offset into the second byte range. + * @param len The number of bytes to compare + * @return true if the byte ranges are equal, false otherwise. + */ + function equals( + bytes memory self, + uint256 offset, + bytes memory other, + uint256 otherOffset, + uint256 len + ) + internal + pure + returns (bool) + { + return keccak(self, offset, len) == keccak(other, otherOffset, len); + } + + /* + * @dev Returns the 8-bit number at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 8 bits of the string, interpreted as an integer. + */ + function readUint8(bytes memory self, uint256 idx) internal pure returns (uint8 ret) { + return uint8(self[idx]); + } + + /* + * @dev Returns the 16-bit number at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes + * @return The specified 16 bits of the string, interpreted as an integer. + */ + function readUint16(bytes memory self, uint256 idx) internal pure returns (uint16 ret) { + require(idx + 2 <= self.length, "invalid idx"); + assembly { + ret := and(mload(add(add(self, 2), idx)), 0xFFFF) + } + } + + /* + * @dev Returns the n byte value at the specified index of self. + * @param self The byte string. + * @param idx The index into the bytes. + * @param len The number of bytes. + * @return The specified 32 bytes of the string. + */ + function readBytesN( + bytes memory self, + uint256 idx, + uint256 len + ) + internal + pure + returns (bytes32 ret) + { + require(len <= 32, "unexpected len"); + require(idx + len <= self.length, "unexpected idx"); + assembly { + let mask := not(sub(exp(256, sub(32, len)), 1)) + ret := and(mload(add(add(self, 32), idx)), mask) + } + } + + function memcpy(uint256 dest, uint256 src, uint256 len) private pure { + assembly { + mcopy(dest, src, len) + } + } + + /* + * @dev Copies a substring into a new byte string. + * @param self The byte string to copy from. + * @param offset The offset to start copying at. + * @param len The number of bytes to copy. + */ + function substring( + bytes memory self, + uint256 offset, + uint256 len + ) + internal + pure + returns (bytes memory) + { + require(offset + len <= self.length, "unexpected offset"); + + bytes memory ret = new bytes(len); + uint256 dest; + uint256 src; + + assembly { + dest := add(ret, 32) + src := add(add(self, 32), offset) + } + memcpy(dest, src, len); + + return ret; + } + + function compareBytes(bytes memory a, bytes memory b) internal pure returns (bool) { + return keccak256(a) == keccak256(b); + } +} diff --git a/packages/protocol/contracts/automata-attestation/utils/SHA1.sol b/packages/protocol/contracts/automata-attestation/utils/SHA1.sol new file mode 100644 index 000000000000..856e841f90a6 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/utils/SHA1.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: BSD 2-Clause License + +pragma solidity 0.8.24; + +// Inspired by ensdomains/solsha1 - BSD 2-Clause License +// https://github.com/ensdomains/solsha1/blob/master/contracts/SHA1.sol + +/// @title SHA1 +/// @custom:security-contact security@taiko.xyz +library SHA1 { + function sha1(bytes memory data) internal pure returns (bytes20 ret) { + assembly { + // Get a safe scratch location + let scratch := mload(0x40) + + // Get the data length, and point data at the first byte + let len := mload(data) + data := add(data, 32) + + // Find the length after padding + let totallen := add(and(add(len, 1), 0xFFFFFFFFFFFFFFC0), 64) + switch lt(sub(totallen, len), 9) + case 1 { totallen := add(totallen, 64) } + + let h := 0x6745230100EFCDAB890098BADCFE001032547600C3D2E1F0 + + function readword(ptr, off, count) -> result { + result := 0 + if lt(off, count) { + result := mload(add(ptr, off)) + count := sub(count, off) + if lt(count, 32) { + let mask := not(sub(exp(256, sub(32, count)), 1)) + result := and(result, mask) + } + } + } + + for { let i := 0 } lt(i, totallen) { i := add(i, 64) } { + mstore(scratch, readword(data, i, len)) + mstore(add(scratch, 32), readword(data, add(i, 32), len)) + + // If we loaded the last byte, store the terminator byte + switch lt(sub(len, i), 64) + case 1 { mstore8(add(scratch, sub(len, i)), 0x80) } + + // If this is the last block, store the length + switch eq(i, sub(totallen, 64)) + case 1 { mstore(add(scratch, 32), or(mload(add(scratch, 32)), mul(len, 8))) } + + // Expand the 16 32-bit words into 80 + for { let j := 64 } lt(j, 128) { j := add(j, 12) } { + let temp := + xor( + xor(mload(add(scratch, sub(j, 12))), mload(add(scratch, sub(j, 32)))), + xor(mload(add(scratch, sub(j, 56))), mload(add(scratch, sub(j, 64)))) + ) + temp := + or( + and( + mul(temp, 2), + 0xFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFEFFFFFFFE + ), + and( + div(temp, 0x80000000), + 0x0000000100000001000000010000000100000001000000010000000100000001 + ) + ) + mstore(add(scratch, j), temp) + } + for { let j := 128 } lt(j, 320) { j := add(j, 24) } { + let temp := + xor( + xor(mload(add(scratch, sub(j, 24))), mload(add(scratch, sub(j, 64)))), + xor(mload(add(scratch, sub(j, 112))), mload(add(scratch, sub(j, 128)))) + ) + temp := + or( + and( + mul(temp, 4), + 0xFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFCFFFFFFFC + ), + and( + div(temp, 0x40000000), + 0x0000000300000003000000030000000300000003000000030000000300000003 + ) + ) + mstore(add(scratch, j), temp) + } + + let x := h + let f := 0 + let k := 0 + for { let j := 0 } lt(j, 80) { j := add(j, 1) } { + switch div(j, 20) + case 0 { + // f = d xor (b and (c xor d)) + f := xor(div(x, 0x100000000000000000000), div(x, 0x10000000000)) + f := and(div(x, 0x1000000000000000000000000000000), f) + f := xor(div(x, 0x10000000000), f) + k := 0x5A827999 + } + case 1 { + // f = b xor c xor d + f := + xor( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := xor(div(x, 0x10000000000), f) + k := 0x6ED9EBA1 + } + case 2 { + // f = (b and c) or (d and (b or c)) + f := + or( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := and(div(x, 0x10000000000), f) + f := + or( + and( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ), + f + ) + k := 0x8F1BBCDC + } + case 3 { + // f = b xor c xor d + f := + xor( + div(x, 0x1000000000000000000000000000000), + div(x, 0x100000000000000000000) + ) + f := xor(div(x, 0x10000000000), f) + k := 0xCA62C1D6 + } + // temp = (a leftrotate 5) + f + e + k + w[i] + let temp := and(div(x, 0x80000000000000000000000000000000000000000000000), 0x1F) + temp := + or(and(div(x, 0x800000000000000000000000000000000000000), 0xFFFFFFE0), temp) + temp := add(f, temp) + temp := add(and(x, 0xFFFFFFFF), temp) + temp := add(k, temp) + temp := + add( + div( + mload(add(scratch, mul(j, 4))), + 0x100000000000000000000000000000000000000000000000000000000 + ), + temp + ) + x := + or( + div(x, 0x10000000000), + mul(temp, 0x10000000000000000000000000000000000000000) + ) + x := + or( + and(x, 0xFFFFFFFF00FFFFFFFF000000000000FFFFFFFF00FFFFFFFF), + mul( + or( + and(div(x, 0x4000000000000), 0xC0000000), + and(div(x, 0x400000000000000000000), 0x3FFFFFFF) + ), + 0x100000000000000000000 + ) + ) + } + + h := and(add(h, x), 0xFFFFFFFF00FFFFFFFF00FFFFFFFF00FFFFFFFF00FFFFFFFF) + } + ret := + mul( + or( + or( + or( + or( + and(div(h, 0x100000000), 0xFFFFFFFF00000000000000000000000000000000), + and(div(h, 0x1000000), 0xFFFFFFFF000000000000000000000000) + ), + and(div(h, 0x10000), 0xFFFFFFFF0000000000000000) + ), + and(div(h, 0x100), 0xFFFFFFFF00000000) + ), + and(h, 0xFFFFFFFF) + ), + 0x1000000000000000000000000 + ) + } + } +} diff --git a/packages/protocol/contracts/automata-attestation/utils/SigVerifyLib.sol b/packages/protocol/contracts/automata-attestation/utils/SigVerifyLib.sol new file mode 100644 index 000000000000..80c99cb0b9ea --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/utils/SigVerifyLib.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.24; + +import "../interfaces/ISigVerifyLib.sol"; +import "./BytesUtils.sol"; + +/// @title SigVerifyLib +/// @custom:security-contact security@taiko.xyz +// Library for verifying signatures +contract SigVerifyLib is ISigVerifyLib { + using BytesUtils for bytes; + + address private immutable __es256Verifier; + + constructor(address es256Verifier) { + __es256Verifier = es256Verifier; + } + + function verifyES256Signature( + bytes calldata tbs, + bytes calldata signature, + bytes calldata publicKey + ) + external + view + returns (bool sigValid) + { + // Parse signature + if (signature.length != 64) { + return false; + } + uint256 r = uint256(bytes32(signature.substring(0, 32))); + uint256 s = uint256(bytes32(signature.substring(32, 32))); + // Parse public key + if (publicKey.length != 64) { + return false; + } + uint256 gx = uint256(bytes32(publicKey.substring(0, 32))); + uint256 gy = uint256(bytes32(publicKey.substring(32, 32))); + + // Verify signature + bytes memory args = abi.encode(sha256(tbs), r, s, gx, gy); + (bool success, bytes memory ret) = __es256Verifier.staticcall(args); + assert(success); // never reverts, always returns 0 or 1 + + return abi.decode(ret, (uint256)) == 1; + } +} diff --git a/packages/protocol/contracts/automata-attestation/utils/X509DateUtils.sol b/packages/protocol/contracts/automata-attestation/utils/X509DateUtils.sol new file mode 100644 index 000000000000..dbbcb44ae717 --- /dev/null +++ b/packages/protocol/contracts/automata-attestation/utils/X509DateUtils.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +/// @title X509DateUtils +/// @custom:security-contact security@taiko.xyz +library X509DateUtils { + function toTimestamp(bytes memory x509Time) internal pure returns (uint256) { + uint16 yrs; + uint8 mnths; + uint8 dys; + uint8 hrs; + uint8 mins; + uint8 secs; + uint8 offset; + + if (x509Time.length == 13) { + if (uint8(x509Time[0]) - 48 < 5) yrs += 2000; + else yrs += 1900; + } else { + yrs += (uint8(x509Time[0]) - 48) * 1000 + (uint8(x509Time[1]) - 48) * 100; + offset = 2; + } + yrs += (uint8(x509Time[offset + 0]) - 48) * 10 + uint8(x509Time[offset + 1]) - 48; + mnths = (uint8(x509Time[offset + 2]) - 48) * 10 + uint8(x509Time[offset + 3]) - 48; + dys += (uint8(x509Time[offset + 4]) - 48) * 10 + uint8(x509Time[offset + 5]) - 48; + hrs += (uint8(x509Time[offset + 6]) - 48) * 10 + uint8(x509Time[offset + 7]) - 48; + mins += (uint8(x509Time[offset + 8]) - 48) * 10 + uint8(x509Time[offset + 9]) - 48; + secs += (uint8(x509Time[offset + 10]) - 48) * 10 + uint8(x509Time[offset + 11]) - 48; + + return toUnixTimestamp(yrs, mnths, dys, hrs, mins, secs); + } + + function toUnixTimestamp( + uint16 year, + uint8 month, + uint8 day, + uint8 hour, + uint8 minute, + uint8 second + ) + internal + pure + returns (uint256) + { + uint256 timestamp = 0; + + for (uint16 i = 1970; i < year; ++i) { + if (isLeapYear(i)) { + timestamp += 31_622_400; // Leap year in seconds + } else { + timestamp += 31_536_000; // Normal year in seconds + } + } + + uint8[12] memory monthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + if (isLeapYear(year)) monthDays[1] = 29; + + for (uint8 i = 1; i < month; ++i) { + timestamp += uint256(monthDays[i - 1]) * 86_400; // Days in seconds + } + + timestamp += uint256(day - 1) * 86_400; // Days in seconds + timestamp += uint256(hour) * 3600; // Hours in seconds + timestamp += uint256(minute) * 60; // Minutes in seconds + timestamp += second; + + return timestamp; + } + + function isLeapYear(uint16 year) internal pure returns (bool) { + if (year % 4 != 0) return false; + if (year % 100 != 0) return true; + if (year % 400 != 0) return false; + return true; + } +} diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 01866378dbda..71ef33cc21db 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -1,411 +1,738 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ -pragma solidity ^0.8.20; +pragma solidity 0.8.24; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; import "../libs/LibAddress.sol"; +import "../libs/LibMath.sol"; import "../signal/ISignalService.sol"; import "./IBridge.sol"; +import "./IQuotaManager.sol"; /// @title Bridge -/// @dev Labeled in AddressResolver as "bridge" /// @notice See the documentation for {IBridge}. -/// @dev The code hash for the same address on L1 and L2 may be different. +/// @dev Labeled in AddressResolver as "bridge". Additionally, the code hash for the same address on +/// L1 and L2 may be different. +/// @custom:security-contact security@taiko.xyz contract Bridge is EssentialContract, IBridge { + using Address for address; + using LibMath for uint256; using LibAddress for address; using LibAddress for address payable; - enum Status { - NEW, - RETRIABLE, - DONE, - FAILED + struct ProcessingStats { + uint32 gasUsedInFeeCalc; + uint32 proofSize; + uint32 numCacheOps; + bool processedByRelayer; } - uint256 internal constant PLACEHOLDER = type(uint256).max; + /// @dev A debug event for fine-tuning gas related constants in the future. + event MessageProcessed(bytes32 indexed msgHash, Message message, ProcessingStats stats); - uint128 public nextMessageId; // slot 1 - mapping(bytes32 msgHash => bool recalled) public isMessageRecalled; - mapping(bytes32 msgHash => Status) public messageStatus; // slot 3 - Context private _ctx; // // slot 4,5,6pnpm - uint256[44] private __gap; + /// @dev The amount of gas that will be deducted from message.gasLimit before calculating the + /// invocation gas limit. This value should be fine-tuned with production data. + uint32 public constant GAS_RESERVE = 800_000; + + /// @dev The gas overhead for both receiving and invoking a message, as well as the proof + /// calldata cost. + /// This value should be fine-tuned with production data. + uint32 public constant GAS_OVERHEAD = 120_000; + + ///@dev The max proof size for a message to be processable by a relayer. + uint256 public constant RELAYER_MAX_PROOF_BYTES = 200_000; + + /// @dev The amount of gas not to charge fee per cache operation. + uint256 private constant _GAS_REFUND_PER_CACHE_OPERATION = 20_000; + + /// @dev The slot in transient storage of the call context. This is the keccak256 hash + /// of "bridge.ctx_slot" + bytes32 private constant _CTX_SLOT = + 0xe4ece82196de19aabe639620d7f716c433d1348f96ce727c9989a982dbadc2b9; + + /// @dev Gas limit for sending Ether. + // - EOA gas used is < 21000 + // - For Loopring smart wallet, gas used is about 23000 + // - For Argent smart wallet on Ethereum, gas used is about 24000 + // - For Gnosis Safe wallet, gas used is about 28000 + uint256 private constant _SEND_ETHER_GAS_LIMIT = 35_000; + + /// @dev Place holder value when not using transient storage + uint256 private constant _PLACEHOLDER = type(uint256).max; + + /// @notice The next message ID. + /// @dev Slot 1. + uint64 private __reserved1; + uint64 public nextMessageId; + + /// @notice Mapping to store the status of a message from its hash. + /// @dev Slot 2. + mapping(bytes32 msgHash => Status status) public messageStatus; - event SignalSent(address indexed sender, bytes32 msgHash); - event MessageSent(bytes32 indexed msgHash, Message message); - event MessageRecalled(bytes32 indexed msgHash); - event DestChainEnabled(uint64 indexed chainId, bool enabled); - event MessageStatusChanged(bytes32 indexed msgHash, Status status); + /// @dev Slots 3 and 4 + Context private __ctx; + + /// @dev Slot 5. + uint256 private __reserved2; + + /// @dev Slot 6. + uint256 private __reserved3; + + uint256[44] private __gap; error B_INVALID_CHAINID(); error B_INVALID_CONTEXT(); + error B_INVALID_FEE(); error B_INVALID_GAS_LIMIT(); - error B_INVALID_SIGNAL(); - error B_INVALID_USER(); + error B_INVALID_STATUS(); error B_INVALID_VALUE(); - error B_NON_RETRIABLE(); - error B_NOT_FAILED(); - error B_NOT_RECEIVED(); + error B_INSUFFICIENT_GAS(); + error B_MESSAGE_NOT_SENT(); + error B_OUT_OF_ETH_QUOTA(); error B_PERMISSION_DENIED(); - error B_RECALLED_ALREADY(); - error B_STATUS_MISMATCH(); + error B_PROOF_TOO_LARGE(); + error B_RETRY_FAILED(); + error B_SIGNAL_NOT_RECEIVED(); - modifier sameChain(uint64 chainId) { - if (chainId != block.chainid) revert B_INVALID_CHAINID(); + modifier sameChain(uint64 _chainId) { + if (_chainId != block.chainid) revert B_INVALID_CHAINID(); _; } - receive() external payable { } + modifier diffChain(uint64 _chainId) { + if (_chainId == 0 || _chainId == block.chainid) revert B_INVALID_CHAINID(); + _; + } /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. /// @param _addressManager The address of the {AddressManager} contract. - function init(address _addressManager) external initializer { - __Essential_init(_addressManager); - _ctx.msgHash == bytes32(PLACEHOLDER); + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + } + + function init2() external onlyOwner reinitializer(2) { + // reset some previously used slots for future reuse + __reserved1 = 0; + __reserved2 = 0; + __reserved3 = 0; + } + + /// @notice Delegates a given token's voting power to the bridge itself. + /// @param _anyToken Any token that supports delegation. + function selfDelegate(address _anyToken) external nonZeroAddr(_anyToken) { + ERC20VotesUpgradeable(_anyToken).delegate(address(this)); } - /// @notice Sends a message to the destination chain and takes custody - /// of Ether required in this contract. All extra Ether will be refunded. /// @inheritdoc IBridge - function sendMessage(Message calldata message) + function sendMessage(Message calldata _message) external payable override - nonReentrant + nonZeroAddr(_message.srcOwner) + nonZeroAddr(_message.destOwner) + diffChain(_message.destChainId) whenNotPaused - returns (bytes32 msgHash, Message memory _message) + nonReentrant + returns (bytes32 msgHash_, Message memory message_) { - // Ensure the message user is not null. - if (message.owner == address(0)) revert B_INVALID_USER(); + if (_message.gasLimit == 0) { + if (_message.fee != 0) revert B_INVALID_FEE(); + } else if (_invocationGasLimit(_message) == 0) { + revert B_INVALID_GAS_LIMIT(); + } // Check if the destination chain is enabled. - (bool destChainEnabled,) = isDestChainEnabled(message.destChainId); + (bool destChainEnabled,) = isDestChainEnabled(_message.destChainId); - // Verify destination chain and to address. + // Verify destination chain. if (!destChainEnabled) revert B_INVALID_CHAINID(); - if (message.destChainId == block.chainid) { - revert B_INVALID_CHAINID(); - } // Ensure the sent value matches the expected amount. - uint256 expectedAmount = message.value + message.fee; - if (expectedAmount != msg.value) revert B_INVALID_VALUE(); + if (_message.value + _message.fee != msg.value) revert B_INVALID_VALUE(); - _message = message; - // Configure message details and send signal to indicate message - // sending. - _message.id = nextMessageId++; - _message.from = msg.sender; - _message.srcChainId = uint64(block.chainid); + message_ = _message; - msgHash = hashMessage(_message); + // Configure message details and send signal to indicate message sending. + message_.id = nextMessageId++; + message_.from = msg.sender; + message_.srcChainId = uint64(block.chainid); - ISignalService(resolve("signal_service", false)).sendSignal(msgHash); - emit MessageSent(msgHash, _message); + msgHash_ = hashMessage(message_); + + emit MessageSent(msgHash_, message_); + ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).sendSignal(msgHash_); } - /// @notice Recalls a failed message on its source chain, releasing - /// associated assets. - /// @dev This function checks if the message failed on the source chain and - /// releases associated Ether or tokens. - /// @param message The message whose associated Ether should be released. - /// @param proof The merkle inclusion proof. + /// @inheritdoc IBridge function recallMessage( - Message calldata message, - bytes calldata proof + Message calldata _message, + bytes calldata _proof ) external - nonReentrant + sameChain(_message.srcChainId) + diffChain(_message.destChainId) whenNotPaused - sameChain(message.srcChainId) + nonReentrant { - bytes32 msgHash = hashMessage(message); - if (isMessageRecalled[msgHash]) revert B_RECALLED_ALREADY(); + bytes32 msgHash = hashMessage(_message); + _checkStatus(msgHash, Status.NEW); + + address signalService = resolve(LibStrings.B_SIGNAL_SERVICE, false); + + if (!ISignalService(signalService).isSignalSent(address(this), msgHash)) { + revert B_MESSAGE_NOT_SENT(); + } - bool received = - _proveSignalReceived(_signalForFailedMessage(msgHash), message.destChainId, proof); - if (!received) revert B_NOT_FAILED(); + _proveSignalReceived( + signalService, signalForFailedMessage(msgHash), _message.destChainId, _proof + ); - isMessageRecalled[msgHash] = true; + _updateMessageStatus(msgHash, Status.RECALLED); + if (!_consumeEtherQuota(_message.value)) revert B_OUT_OF_ETH_QUOTA(); // Execute the recall logic based on the contract's support for the // IRecallableSender interface - bool support = message.from.supportsInterface(type(IRecallableSender).interfaceId); - if (support) { - _ctx = - Context({ msgHash: msgHash, from: address(this), srcChainId: message.srcChainId }); + if (_message.from.supportsInterface(type(IRecallableSender).interfaceId)) { + _storeContext(msgHash, address(this), _message.srcChainId); // Perform recall - IRecallableSender(message.from).onMessageRecalled{ value: message.value }( - message, msgHash + IRecallableSender(_message.from).onMessageRecalled{ value: _message.value }( + _message, msgHash ); - // Reset the context after the message call - _ctx = Context({ - msgHash: bytes32(PLACEHOLDER), - from: address(uint160(PLACEHOLDER)), - srcChainId: uint64(PLACEHOLDER) - }); + // Must reset the context after the message call + _resetContext(); } else { - message.owner.sendEther(message.value); + _message.srcOwner.sendEtherAndVerify(_message.value, _SEND_ETHER_GAS_LIMIT); } - - emit MessageRecalled(msgHash); } - /// @notice Processes a bridge message on the destination chain. This - /// function is callable by any address, including the `message.owner`. - /// @dev The process begins by hashing the message and checking the message - /// status in the bridge If the status is "NEW", the message is invoked. The - /// status is updated accordingly, and processing fees are refunded as - /// needed. - /// @param message The message to be processed. - /// @param proof The merkle inclusion proof. + /// @inheritdoc IBridge + /// @dev To ensure successful execution, we recommend this transaction's gas limit not to be + /// smaller than: + /// `(message.gasLimit - GAS_RESERVE) * 64 / 63 + GAS_RESERVE`, + /// Or we can use a simplified rule: `tx.gaslimit = message.gaslimit * 102%`. function processMessage( - Message calldata message, - bytes calldata proof + Message calldata _message, + bytes calldata _proof ) external - nonReentrant whenNotPaused - sameChain(message.destChainId) + nonReentrant + returns (Status status_, StatusReason reason_) { - // If the gas limit is set to zero, only the owner can process the - // message. - if (message.gasLimit == 0 && msg.sender != message.owner) { - revert B_PERMISSION_DENIED(); + uint256 gasStart = gasleft(); + + // same as `sameChain(_message.destChainId)` but without stack-too-deep + if (_message.destChainId != block.chainid) revert B_INVALID_CHAINID(); + + // same as `diffChain(_message.srcChainId)` but without stack-too-deep + if (_message.srcChainId == 0 || _message.srcChainId == block.chainid) { + revert B_INVALID_CHAINID(); } - bytes32 msgHash = hashMessage(message); + ProcessingStats memory stats; + stats.processedByRelayer = msg.sender != _message.destOwner; - if (messageStatus[msgHash] != Status.NEW) revert B_STATUS_MISMATCH(); + // If the gas limit is set to zero, only the owner can process the message. + if (stats.processedByRelayer) { + if (_message.gasLimit == 0) revert B_PERMISSION_DENIED(); + if (_proof.length > RELAYER_MAX_PROOF_BYTES) revert B_PROOF_TOO_LARGE(); + } - bool received = _proveSignalReceived(msgHash, message.srcChainId, proof); - if (!received) revert B_NOT_RECEIVED(); + bytes32 msgHash = hashMessage(_message); + _checkStatus(msgHash, Status.NEW); - Status status; - uint256 refundAmount; + address signalService = resolve(LibStrings.B_SIGNAL_SERVICE, false); + + stats.proofSize = uint32(_proof.length); + stats.numCacheOps = + _proveSignalReceived(signalService, msgHash, _message.srcChainId, _proof); + + if (!_consumeEtherQuota(_message.value + _message.fee)) revert B_OUT_OF_ETH_QUOTA(); - // Process message differently based on the target address - if (message.to == address(0) || message.to == address(this)) { - // Handle special addresses that don't require actual invocation but - // mark message as DONE - status = Status.DONE; - refundAmount = message.value; + uint256 refundAmount; + if (_unableToInvokeMessageCall(_message, signalService)) { + // Handle special addresses and message.data encoded function calldata that don't + // require or cannot proceed with actual invocation and mark message as DONE + refundAmount = _message.value; + status_ = Status.DONE; + reason_ = StatusReason.INVOCATION_PROHIBITED; } else { - // Use the specified message gas limit if called by the owner, else - // use remaining gas - uint256 gasLimit = msg.sender == message.owner ? gasleft() : message.gasLimit; + uint256 gasLimit = stats.processedByRelayer ? _invocationGasLimit(_message) : gasleft(); - if (_invokeMessageCall(message, msgHash, gasLimit)) { - status = Status.DONE; + if (_invokeMessageCall(_message, msgHash, gasLimit, stats.processedByRelayer)) { + status_ = Status.DONE; + reason_ = StatusReason.INVOCATION_OK; } else { - status = Status.RETRIABLE; + status_ = Status.RETRIABLE; + reason_ = StatusReason.INVOCATION_FAILED; } } - // Update the message status - _updateMessageStatus(msgHash, status); + if (_message.fee != 0) { + refundAmount += _message.fee; + + if (stats.processedByRelayer && _message.gasLimit != 0) { + unchecked { + // The relayer (=message processor) needs to get paid from the fee, and below it + // the calculation mechanism of that. + // The high level overview is: "gasCharged * block.basefee" with some caveat. + // Sometimes over or under estimated and it has different reasons: + // - a rational relayer shall simulate transactions off-chain so he/she would + // exactly know if the txn is profitable or not. + // - need to have a buffer/small revenue to the realyer since it consumes + // maintenance and infra costs to operate + uint256 refund = stats.numCacheOps * _GAS_REFUND_PER_CACHE_OPERATION; + // Taking into account the encoded message calldata cost, and can count with 16 + // gas per bytes (vs. checking each and every byte if zero or non-zero) + stats.gasUsedInFeeCalc = uint32( + GAS_OVERHEAD + gasStart + _messageCalldataCost(_message.data.length) + - gasleft() + ); + + uint256 gasCharged = refund.max(stats.gasUsedInFeeCalc) - refund; + uint256 maxFee = gasCharged * _message.fee / _message.gasLimit; + uint256 baseFee = gasCharged * block.basefee; + uint256 fee = + (baseFee >= maxFee ? maxFee : (maxFee + baseFee) >> 1).min(_message.fee); + + refundAmount -= fee; + msg.sender.sendEtherAndVerify(fee, _SEND_ETHER_GAS_LIMIT); + } + } + } - // Determine the refund recipient - address refundTo = message.refundTo == address(0) ? message.owner : message.refundTo; + _message.destOwner.sendEtherAndVerify(refundAmount, _SEND_ETHER_GAS_LIMIT); - // Refund the processing fee - if (msg.sender == refundTo) { - refundTo.sendEther(message.fee + refundAmount); - } else { - // If sender is another address, reward it and refund the rest - msg.sender.sendEther(message.fee); - refundTo.sendEther(refundAmount); - } + _updateMessageStatus(msgHash, status_); + emit MessageProcessed(msgHash, _message, stats); } - /// @notice Retries to invoke the messageCall after releasing associated - /// Ether and tokens. - /// @dev This function can be called by any address, including the - /// `message.owner`. - /// It attempts to invoke the messageCall and updates the message status - /// accordingly. - /// @param message The message to retry. - /// @param isLastAttempt Specifies if this is the last attempt to retry the - /// message. + /// @inheritdoc IBridge function retryMessage( - Message calldata message, - bool isLastAttempt + Message calldata _message, + bool _isLastAttempt ) external - nonReentrant + sameChain(_message.destChainId) + diffChain(_message.srcChainId) whenNotPaused - sameChain(message.destChainId) + nonReentrant { - // If the gasLimit is set to 0 or isLastAttempt is true, the caller must - // be the message.owner. - if (message.gasLimit == 0 || isLastAttempt) { - if (msg.sender != message.owner) revert B_PERMISSION_DENIED(); - } + bytes32 msgHash = hashMessage(_message); + _checkStatus(msgHash, Status.RETRIABLE); + + if (!_consumeEtherQuota(_message.value)) revert B_OUT_OF_ETH_QUOTA(); + + bool succeeded; + if (_unableToInvokeMessageCall(_message, resolve(LibStrings.B_SIGNAL_SERVICE, false))) { + succeeded = _message.destOwner.sendEther(_message.value, _SEND_ETHER_GAS_LIMIT, ""); + } else { + if ((_message.gasLimit == 0 || _isLastAttempt) && msg.sender != _message.destOwner) { + revert B_PERMISSION_DENIED(); + } - bytes32 msgHash = hashMessage(message); - if (messageStatus[msgHash] != Status.RETRIABLE) { - revert B_NON_RETRIABLE(); + // Attempt to invoke the messageCall. + succeeded = _invokeMessageCall(_message, msgHash, gasleft(), false); } - // Attempt to invoke the messageCall. - if (_invokeMessageCall(message, msgHash, gasleft())) { - // Update the message status to "DONE" on successful invocation. + if (succeeded) { _updateMessageStatus(msgHash, Status.DONE); - } else { - // Update the message status to "FAILED" + } else if (_isLastAttempt) { _updateMessageStatus(msgHash, Status.FAILED); + + ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).sendSignal( + signalForFailedMessage(msgHash) + ); + } else { + revert B_RETRY_FAILED(); } } - /// @notice Checks if the message was sent. - /// @param message The message. - /// @return True if the message was sent. - function isMessageSent(Message calldata message) public view returns (bool) { - if (message.srcChainId != block.chainid) return false; - return ISignalService(resolve("signal_service", false)).isSignalSent({ - app: address(this), - signal: hashMessage(message) + /// @inheritdoc IBridge + function failMessage(Message calldata _message) + external + sameChain(_message.destChainId) + diffChain(_message.srcChainId) + whenNotPaused + nonReentrant + { + if (msg.sender != _message.destOwner) revert B_PERMISSION_DENIED(); + + bytes32 msgHash = hashMessage(_message); + _checkStatus(msgHash, Status.RETRIABLE); + + _updateMessageStatus(msgHash, Status.FAILED); + ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).sendSignal( + signalForFailedMessage(msgHash) + ); + } + + /// @inheritdoc IBridge + function isMessageSent(Message calldata _message) external view returns (bool) { + if (_message.srcChainId != block.chainid) return false; + return ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).isSignalSent({ + _app: address(this), + _signal: hashMessage(_message) }); } /// @notice Checks if a msgHash has failed on its destination chain. - /// @param message The message. - /// @param proof The merkle inclusion proof. - /// @return Returns true if the message has failed, false otherwise. - function proveMessageFailed( - Message calldata message, - bytes calldata proof + /// This is the 'readonly' version of proveMessageFailed. + /// @param _message The message. + /// @param _proof The merkle inclusion proof. + /// @return true if the message has failed, false otherwise. + function isMessageFailed( + Message calldata _message, + bytes calldata _proof ) - public + external view returns (bool) { - if (message.srcChainId != block.chainid) return false; - return _proveSignalReceived( - _signalForFailedMessage(hashMessage(message)), message.destChainId, proof + if (_message.srcChainId != block.chainid) return false; + + return _isSignalReceived( + resolve(LibStrings.B_SIGNAL_SERVICE, false), + signalForFailedMessage(hashMessage(_message)), + _message.destChainId, + _proof ); } - /// @notice Checks if a msgHash has failed on its destination chain. - /// @param message The message. - /// @param proof The merkle inclusion proof. - /// @return Returns true if the message has failed, false otherwise. - function proveMessageReceived( - Message calldata message, - bytes calldata proof + /// @notice Checks if a msgHash has been received on its source chain. + /// This is the 'readonly' version of proveMessageReceived. + /// @param _message The message. + /// @param _proof The merkle inclusion proof. + /// @return true if the message has been received, false otherwise. + function isMessageReceived( + Message calldata _message, + bytes calldata _proof ) - public + external view returns (bool) { - if (message.destChainId != block.chainid) return false; - return _proveSignalReceived(hashMessage(message), message.srcChainId, proof); + if (_message.destChainId != block.chainid) return false; + return _isSignalReceived( + resolve(LibStrings.B_SIGNAL_SERVICE, false), + hashMessage(_message), + _message.srcChainId, + _proof + ); } /// @notice Checks if the destination chain is enabled. - /// @param chainId The destination chain ID. - /// @return enabled True if the destination chain is enabled. - /// @return destBridge The bridge of the destination chain. - function isDestChainEnabled(uint64 chainId) + /// @param _chainId The destination chain ID. + /// @return enabled_ True if the destination chain is enabled. + /// @return destBridge_ The bridge of the destination chain. + function isDestChainEnabled(uint64 _chainId) public view - returns (bool enabled, address destBridge) + returns (bool enabled_, address destBridge_) { - destBridge = resolve(chainId, "bridge", true); - enabled = destBridge != address(0); + destBridge_ = resolve(_chainId, LibStrings.B_BRIDGE, true); + enabled_ = destBridge_ != address(0); } /// @notice Gets the current context. /// @inheritdoc IBridge - function context() public view returns (Context memory) { - if (_ctx.msgHash == bytes32(PLACEHOLDER) || _ctx.msgHash == 0) { + function context() external view returns (Context memory ctx_) { + ctx_ = _loadContext(); + if (ctx_.msgHash == 0 || ctx_.msgHash == bytes32(_PLACEHOLDER)) { revert B_INVALID_CONTEXT(); } - return _ctx; } - /// @notice Hash the message - function hashMessage(Message memory message) public pure returns (bytes32) { - return keccak256(abi.encode("TAIKO_MESSAGE", message)); + /// @inheritdoc IBridge + function hashMessage(Message memory _message) public pure returns (bytes32) { + return keccak256(abi.encode("TAIKO_MESSAGE", _message)); + } + + /// @notice Returns a signal representing a failed/recalled message. + /// @param _msgHash The message hash. + /// @return The failed representation of it as bytes32. + function signalForFailedMessage(bytes32 _msgHash) public pure returns (bytes32) { + return _msgHash ^ bytes32(uint256(Status.FAILED)); + } + + /// @notice Returns the minimal gas limit required for sending a given message. + /// @param dataLength The length of message.data. + /// @return The minimal gas limit required for sending this message. + function getMessageMinGasLimit(uint256 dataLength) public pure returns (uint32) { + return _messageCalldataCost(dataLength) + GAS_RESERVE; + } + + /// @notice Checks if the given address can pause and/or unpause the bridge. + /// @dev Considering that the watchdog is a hot wallet, in case its private key is leaked, we + /// only allow watchdog to pause the bridge, but does not allow it to unpause the bridge. + function _authorizePause(address addr, bool toPause) internal view override { + // Owner and chain_pauser can pause/unpause the bridge. + if (addr == owner() || addr == resolve(LibStrings.B_CHAIN_WATCHDOG, true)) return; + + // bridge_watchdog can pause the bridge, but cannot unpause it. + if (toPause && addr == resolve(LibStrings.B_BRIDGE_WATCHDOG, true)) return; + + revert RESOLVER_DENIED(); } /// @notice Invokes a call message on the Bridge. - /// @param message The call message to be invoked. - /// @param msgHash The hash of the message. - /// @param gasLimit The gas limit for the message call. - /// @return success A boolean value indicating whether the message call was - /// successful. + /// @param _message The call message to be invoked. + /// @param _msgHash The hash of the message. + /// @param _shouldCheckForwardedGas True to check gasleft is sufficient for target function + /// invocation. + /// @return success_ A boolean value indicating whether the message call was successful. /// @dev This function updates the context in the state before and after the /// message call. function _invokeMessageCall( - Message calldata message, - bytes32 msgHash, - uint256 gasLimit + Message calldata _message, + bytes32 _msgHash, + uint256 _gasLimit, + bool _shouldCheckForwardedGas ) private - returns (bool success) + returns (bool success_) { - if (gasLimit == 0) revert B_INVALID_GAS_LIMIT(); - assert(message.from != address(this)); + assert(_message.from != address(this)); - _ctx = Context({ msgHash: msgHash, from: message.from, srcChainId: message.srcChainId }); + if (_gasLimit == 0) return false; - // Perform the message call and capture the success value - (success,) = message.to.call{ value: message.value, gas: gasLimit }(message.data); + _storeContext(_msgHash, _message.from, _message.srcChainId); - // Reset the context after the message call - _ctx = Context({ - msgHash: bytes32(PLACEHOLDER), - from: address(uint160(PLACEHOLDER)), - srcChainId: uint64(PLACEHOLDER) - }); + address to = _message.to; + uint256 value = _message.value; + bytes memory data = _message.data; + uint256 gasLeft; + + assembly { + success_ := call(_gasLimit, to, value, add(data, 0x20), mload(data), 0, 0) + gasLeft := gas() + } + + if (_shouldCheckForwardedGas) { + _checkForwardedGas(gasLeft, _gasLimit); + } + _resetContext(); } /// @notice Updates the status of a bridge message. /// @dev If the new status is different from the current status in the /// mapping, the status is updated and an event is emitted. - /// @param msgHash The hash of the message. - /// @param status The new status of the message. - function _updateMessageStatus(bytes32 msgHash, Status status) private { - if (messageStatus[msgHash] == status) return; + /// @param _msgHash The hash of the message. + /// @param _status The new status of the message. + function _updateMessageStatus(bytes32 _msgHash, Status _status) private { + if (messageStatus[_msgHash] == _status) revert B_INVALID_STATUS(); + messageStatus[_msgHash] = _status; + emit MessageStatusChanged(_msgHash, _status); + } - messageStatus[msgHash] = status; - emit MessageStatusChanged(msgHash, status); + /// @notice Resets the call context + function _resetContext() private { + if (LibNetwork.isDencunSupported(block.chainid)) { + _storeContext(bytes32(0), address(0), uint64(0)); + } else { + _storeContext( + bytes32(_PLACEHOLDER), address(uint160(_PLACEHOLDER)), uint64(_PLACEHOLDER) + ); + } + } - if (status != Status.FAILED) return; + /// @notice Stores the call context + /// @param _msgHash The message hash. + /// @param _from The sender's address. + /// @param _srcChainId The source chain ID. + function _storeContext(bytes32 _msgHash, address _from, uint64 _srcChainId) private { + if (LibNetwork.isDencunSupported(block.chainid)) { + assembly { + tstore(_CTX_SLOT, _msgHash) + tstore(add(_CTX_SLOT, 1), _from) + tstore(add(_CTX_SLOT, 2), _srcChainId) + } + } else { + __ctx = Context(_msgHash, _from, _srcChainId); + } + } - ISignalService(resolve("signal_service", false)).sendSignal( - _signalForFailedMessage(msgHash) - ); + /// @notice Checks if the signal was received and caches cross-chain data if requested. + /// @param _signalService The signal service address. + /// @param _signal The signal. + /// @param _chainId The ID of the chain the signal is stored on. + /// @param _proof The merkle inclusion proof. + /// @return numCacheOps_ Num of cached items + function _proveSignalReceived( + address _signalService, + bytes32 _signal, + uint64 _chainId, + bytes calldata _proof + ) + private + returns (uint32 numCacheOps_) + { + try ISignalService(_signalService).proveSignalReceived( + _chainId, resolve(_chainId, LibStrings.B_BRIDGE, false), _signal, _proof + ) returns (uint256 numCacheOps) { + numCacheOps_ = uint32(numCacheOps); + } catch { + revert B_SIGNAL_NOT_RECEIVED(); + } + } + + /// @notice Consumes a given amount of Ether from quota manager. + /// @param _amount The amount of Ether to consume. + /// @return true if quota manager has unlimited quota for Ether or the given amount of Ether is + /// consumed already. + function _consumeEtherQuota(uint256 _amount) private returns (bool) { + address quotaManager = resolve(LibStrings.B_QUOTA_MANAGER, true); + if (quotaManager == address(0)) return true; + + try IQuotaManager(quotaManager).consumeQuota(address(0), _amount) { + return true; + } catch { + return false; + } + } + + /// @notice Loads and returns the call context. + /// @return ctx_ The call context. + function _loadContext() private view returns (Context memory) { + if (LibNetwork.isDencunSupported(block.chainid)) { + bytes32 msgHash; + address from; + uint64 srcChainId; + assembly { + msgHash := tload(_CTX_SLOT) + from := tload(add(_CTX_SLOT, 1)) + srcChainId := tload(add(_CTX_SLOT, 2)) + } + return Context(msgHash, from, srcChainId); + } else { + return __ctx; + } } /// @notice Checks if the signal was received. - /// @param signal The signal. - /// @param srcChainId The ID of the source chain. - /// @param proof The merkle inclusion proof. - /// @return True if the message was received. - function _proveSignalReceived( - bytes32 signal, - uint64 srcChainId, - bytes calldata proof + /// This is the 'readonly' version of _proveSignalReceived. + /// @param _signalService The signal service address. + /// @param _signal The signal. + /// @param _chainId The ID of the chain the signal is stored on. + /// @param _proof The merkle inclusion proof. + /// @return true if the message was received. + function _isSignalReceived( + address _signalService, + bytes32 _signal, + uint64 _chainId, + bytes calldata _proof ) private view returns (bool) { - return ISignalService(resolve("signal_service", false)).proveSignalReceived({ - srcChainId: srcChainId, - app: resolve(srcChainId, "bridge", false), - signal: signal, - proof: proof - }); + try ISignalService(_signalService).verifySignalReceived( + _chainId, resolve(_chainId, LibStrings.B_BRIDGE, false), _signal, _proof + ) { + return true; + } catch { + return false; + } + } + + function _checkStatus(bytes32 _msgHash, Status _expectedStatus) private view { + if (messageStatus[_msgHash] != _expectedStatus) revert B_INVALID_STATUS(); + } + + function _unableToInvokeMessageCall( + Message calldata _message, + address _signalService + ) + private + view + returns (bool) + { + if (_message.to == address(0)) return true; + if (_message.to == address(this)) return true; + if (_message.to == _signalService) return true; + + return _message.data.length >= 4 + && bytes4(_message.data) != IMessageInvocable.onMessageInvocation.selector + && _message.to.isContract(); } - function _signalForFailedMessage(bytes32 msgHash) private pure returns (bytes32) { - return msgHash ^ bytes32(uint256(Status.FAILED)); + function _invocationGasLimit(Message calldata _message) private pure returns (uint256) { + uint256 minGasRequired = getMessageMinGasLimit(_message.data.length); + unchecked { + return minGasRequired.max(_message.gasLimit) - minGasRequired; + } + } + + function _messageCalldataCost(uint256 dataLength) private pure returns (uint32) { + // The abi encoding of A = (Message calldata msg) is 10 * 32 bytes + // + 32 bytes (A is a dynamic tuple, offset to first elements) + // + 32 bytes (offset to last bytes element of Message) + // + 32 bytes (padded encoding of length of Message.data + dataLength + // (padded to 32 // bytes) = 13 * 32 + ((dataLength + 31) / 32 * 32). + // Non-zero calldata cost per byte is 16. + unchecked { + return uint32(((dataLength + 31) / 32 * 32 + 416) << 4); + } + } + + /// @dev Suggested by OpenZeppelin and copied from + /// https://github.com/OpenZeppelin/openzeppelin-contracts/ + /// blob/83c7e45092dac350b070c421cd2bf7105616cf1a/contracts/ + /// metatx/ERC2771Forwarder.sol#L327C1-L370C6 + /// + /// @dev Checks if the requested gas was correctly forwarded to the callee. + /// As a consequence of https://eips.ethereum.org/EIPS/eip-150[EIP-150]: + /// - At most `gasleft() - floor(gasleft() / 64)` is forwarded to the callee. + /// - At least `floor(gasleft() / 64)` is kept in the caller. + /// + /// It reverts consuming all the available gas if the forwarded gas is not the requested gas. + /// + /// IMPORTANT: The `gasLeft` parameter should be measured exactly at the end of the forwarded + /// call. + /// Any gas consumed in between will make room for bypassing this check. + function _checkForwardedGas(uint256 _gasLeft, uint256 _gasRequested) private pure { + // To avoid insufficient gas griefing attacks, as referenced in + // https://ronan.eth.limo/blog/ethereum-gas-dangers/ + // + // A malicious relayer can attempt to shrink the gas forwarded so that the underlying call + // reverts out-of-gas + // but the forwarding itself still succeeds. In order to make sure that the subcall received + // sufficient gas, + // we will inspect gasleft() after the forwarding. + // + // Let X be the gas available before the subcall, such that the subcall gets at most X * 63 + // / 64. + // We can't know X after CALL dynamic costs, but we want it to be such that X * 63 / 64 >= + // req.gas. + // Let Y be the gas used in the subcall. gasleft() measured immediately after the subcall + // will be gasleft() = X - Y. + // If the subcall ran out of gas, then Y = X * 63 / 64 and gasleft() = X - Y = X / 64. + // Under this assumption req.gas / 63 > gasleft() is true is true if and only if + // req.gas / 63 > X / 64, or equivalently req.gas > X * 63 / 64. + // This means that if the subcall runs out of gas we are able to detect that insufficient + // gas was passed. + // + // We will now also see that req.gas / 63 > gasleft() implies that req.gas >= X * 63 / 64. + // The contract guarantees Y <= req.gas, thus gasleft() = X - Y >= X - req.gas. + // - req.gas / 63 > gasleft() + // - req.gas / 63 >= X - req.gas + // - req.gas >= X * 63 / 64 + // In other words if req.gas < X * 63 / 64 then req.gas / 63 <= gasleft(), thus if the + // relayer behaves honestly + // the forwarding does not revert. + if (_gasLeft < _gasRequested / 63) { + // We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, + // since + // neither revert or assert consume all gas since Solidity 0.8.20 + // https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require + /// @solidity memory-safe-assembly + assembly { + invalid() + } + } } } diff --git a/packages/protocol/contracts/bridge/IBridge.sol b/packages/protocol/contracts/bridge/IBridge.sol index 3c96a0635a46..99af78b249fd 100644 --- a/packages/protocol/contracts/bridge/IBridge.sol +++ b/packages/protocol/contracts/bridge/IBridge.sol @@ -1,71 +1,161 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; /// @title IBridge /// @notice The bridge used in conjunction with the {ISignalService}. /// @dev Ether is held by Bridges on L1 and L2s. +/// @custom:security-contact security@taiko.xyz interface IBridge { + enum Status { + NEW, + RETRIABLE, + DONE, + FAILED, + RECALLED + } + + enum StatusReason { + INVOCATION_OK, + INVOCATION_PROHIBITED, + INVOCATION_FAILED, + OUT_OF_ETH_QUOTA + } + struct Message { - // Message ID. - uint128 id; + // Message ID whose value is automatically assigned. + uint64 id; + // The max processing fee for the relayer. This fee has 3 parts: + // - the fee for message calldata. + // - the minimal fee reserve for general processing, excluding function call. + // - the invocation fee for the function call. + // Any unpaid fee will be refunded to the destOwner on the destination chain. + // Note that fee must be 0 if gasLimit is 0, or large enough to make the invocation fee + // non-zero. + uint64 fee; + // gasLimit that the processMessage call must have. + uint32 gasLimit; // The address, EOA or contract, that interacts with this bridge. + // The value is automatically assigned. address from; - // Source chain ID. + // Source chain ID whose value is automatically assigned. uint64 srcChainId; + // The owner of the message on the source chain. + address srcOwner; // Destination chain ID where the `to` address lives. uint64 destChainId; - // The owner of the message. - address owner; + // The owner of the message on the destination chain. + address destOwner; // The destination address on the destination chain. address to; - // Alternate address to send any refund. If blank, defaults to owner. - address refundTo; // value to invoke on the destination chain. uint256 value; - // Processing fee for the relayer. Zero if owner will process themself. - uint256 fee; - // gasLimit to invoke on the destination chain. - uint256 gasLimit; // callData to invoke on the destination chain. bytes data; - // Optional memo. - string memo; } // Struct representing the context of a bridge operation. + // 2 slots struct Context { bytes32 msgHash; // Message hash. address from; // Sender's address. uint64 srcChainId; // Source chain ID. } + /// @notice Emitted when a message is sent. + /// @param msgHash The hash of the message. + /// @param message The message. + event MessageSent(bytes32 indexed msgHash, Message message); + + /// @notice Emitted when the status of a message changes. + /// @param msgHash The hash of the message. + /// @param status The new status of the message. + event MessageStatusChanged(bytes32 indexed msgHash, Status status); + /// @notice Sends a message to the destination chain and takes custody - /// of Ether required in this contract. All extra Ether will be refunded. - /// @param message The message to be sent. - /// @return msgHash The hash of the sent message. - /// @return updatedMessage The updated message sent. - function sendMessage(Message calldata message) + /// of Ether required in this contract. + /// @param _message The message to be sent. + /// @return msgHash_ The hash of the sent message. + /// @return message_ The updated message sent. + function sendMessage(Message calldata _message) external payable - returns (bytes32 msgHash, Message memory updatedMessage); + returns (bytes32 msgHash_, Message memory message_); + + /// @notice Recalls a failed message on its source chain, releasing + /// associated assets. + /// @dev This function checks if the message failed on the source chain and + /// releases associated Ether or tokens. + /// @param _message The message whose associated Ether should be released. + /// @param _proof The merkle inclusion proof. + function recallMessage(Message calldata _message, bytes calldata _proof) external; + + /// @notice Processes a bridge message on the destination chain. This + /// function is callable by any address, including the `message.destOwner`. + /// @dev The process begins by hashing the message and checking the message + /// status in the bridge If the status is "NEW", the message is invoked. The + /// status is updated accordingly, and processing fees are refunded as + /// needed. + /// @param _message The message to be processed. + /// @param _proof The merkle inclusion proof. + /// @return The message's status after processing and the reason for the change. + function processMessage( + Message calldata _message, + bytes calldata _proof + ) + external + returns (Status, StatusReason); + + /// @notice Retries to invoke the messageCall after releasing associated + /// Ether and tokens. + /// @dev This function can be called by any address, including the + /// `message.destOwner`. + /// It attempts to invoke the messageCall and updates the message status + /// accordingly. + /// @param _message The message to retry. + /// @param _isLastAttempt Specifies if this is the last attempt to retry the + /// message. + function retryMessage(Message calldata _message, bool _isLastAttempt) external; + + /// @notice Mark a message as failed if the message is currently retriable. + /// @dev This function can only be called by `message.destOwner`. + /// @param _message The message to fail. + /// message. + function failMessage(Message calldata _message) external; /// @notice Returns the bridge state context. - /// @return context The context of the current bridge operation. - function context() external view returns (Context memory context); + /// @return ctx_ The context of the current bridge operation. + function context() external view returns (Context memory ctx_); + + /// @notice Checks if the message was sent. + /// @param _message The message. + /// @return true if the message was sent. + function isMessageSent(Message calldata _message) external view returns (bool); + + /// @notice Hash the message + /// @param _message The message struct variable to be hashed. + /// @return The message's hash. + function hashMessage(Message memory _message) external pure returns (bytes32); } /// @title IRecallableSender /// @notice An interface that all recallable message senders shall implement. interface IRecallableSender { + /// @notice Called when a message is recalled. + /// @param _message The recalled message. + /// @param _msgHash The hash of the recalled message. function onMessageRecalled( - IBridge.Message calldata message, - bytes32 msgHash + IBridge.Message calldata _message, + bytes32 _msgHash ) external payable; } + +/// @title IMessageInvocable +/// @notice An interface that all bridge message receiver shall implement +interface IMessageInvocable { + /// @notice Called when this contract is the bridge target. + /// @param _data The data for this contract to interpret. + /// @dev This method should be guarded with `onlyFromNamed("bridge")`. + function onMessageInvocation(bytes calldata _data) external payable; +} diff --git a/packages/protocol/contracts/bridge/IQuotaManager.sol b/packages/protocol/contracts/bridge/IQuotaManager.sol new file mode 100644 index 000000000000..3091e0193151 --- /dev/null +++ b/packages/protocol/contracts/bridge/IQuotaManager.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IQuotaManager +/// @custom:security-contact security@taiko.xyz +interface IQuotaManager { + /// @notice Consumes a specific amount of quota for a given address. + /// This function must revert if available quota is smaller than the given amount of quota. + /// + /// @dev Note that IQuotaManager is used by vaults and bridge, and should be registered in a + /// shared address manager on the L1, therefore, a registered IQuotaManager and its per-token + /// quota settings will be shared by all Taiko L2s. To enable a per-L2 quota, we need to modify + /// this function to: + /// `function consumeQuota(uint256 _srcChainId, address _token, uint256 _amount) ` + /// + /// @param _token The token address. Ether is represented with address(0). + /// @param _amount The amount of quota to consume. + function consumeQuota(address _token, uint256 _amount) external; +} diff --git a/packages/protocol/contracts/common/AddressManager.sol b/packages/protocol/contracts/common/AddressManager.sol index 79cbf3bc486e..91ab17e86daf 100644 --- a/packages/protocol/contracts/common/AddressManager.sol +++ b/packages/protocol/contracts/common/AddressManager.sol @@ -1,61 +1,68 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "./OwnerUUPSUpgradable.sol"; -/// @title IAddressManager -/// @notice Specifies methods to manage address mappings for given chainId-name -/// pairs. - -interface IAddressManager { - /// @notice Gets the address mapped to a specific chainId-name pair. - /// @dev Note that in production, this method shall be a pure function - /// without any storage access. - /// @param chainId The chainId for which the address needs to be fetched. - /// @param name The name for which the address needs to be fetched. - /// @return Address associated with the chainId-name pair. - function getAddress(uint64 chainId, bytes32 name) external view returns (address); -} +import "./EssentialContract.sol"; /// @title AddressManager -/// @notice Manages a mapping of chainId-name pairs to Ethereum addresses. -contract AddressManager is OwnerUUPSUpgradable, IAddressManager { - mapping(uint256 => mapping(bytes32 => address)) private addresses; +/// @notice See the documentation in {IAddressManager}. +/// @custom:security-contact security@taiko.xyz +contract AddressManager is EssentialContract, IAddressManager { + /// @dev Mapping of chainId to mapping of name to address. + mapping(uint256 chainId => mapping(bytes32 name => address addr)) private __addresses; + uint256[49] private __gap; + /// @notice Emitted when an address is set. + /// @param chainId The chainId for the address mapping. + /// @param name The name for the address mapping. + /// @param newAddress The new address. + /// @param oldAddress The old address. event AddressSet( uint64 indexed chainId, bytes32 indexed name, address newAddress, address oldAddress ); - /// @notice Initializes the owner for the upgradable contract. - function init() external initializer { - __OwnerUUPSUpgradable_init(); + error AM_ADDRESS_ALREADY_SET(); + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + function init(address _owner) external initializer { + __Essential_init(_owner); + addressManager = address(this); + } + + function init2() external onlyOwner reinitializer(2) { + addressManager = address(this); } /// @notice Sets the address for a specific chainId-name pair. - /// @param chainId The chainId to which the address will be mapped. - /// @param name The name to which the address will be mapped. - /// @param newAddress The Ethereum address to be mapped. + /// @param _chainId The chainId to which the address will be mapped. + /// @param _name The name to which the address will be mapped. + /// @param _newAddress The Ethereum address to be mapped. function setAddress( - uint64 chainId, - bytes32 name, - address newAddress + uint64 _chainId, + bytes32 _name, + address _newAddress ) external virtual onlyOwner { - address oldAddress = addresses[chainId][name]; - addresses[chainId][name] = newAddress; - emit AddressSet(chainId, name, newAddress, oldAddress); + address oldAddress = __addresses[_chainId][_name]; + if (_newAddress == oldAddress) revert AM_ADDRESS_ALREADY_SET(); + __addresses[_chainId][_name] = _newAddress; + emit AddressSet(_chainId, _name, _newAddress, oldAddress); } /// @inheritdoc IAddressManager - function getAddress(uint64 chainId, bytes32 name) public view override returns (address) { - return addresses[chainId][name]; + function getAddress(uint64 _chainId, bytes32 _name) external view override returns (address) { + address addr = _getOverride(_chainId, _name); + if (addr != address(0)) return addr; + else return __addresses[_chainId][_name]; } + + /// @notice Gets the address mapped to a specific chainId-name pair. + /// @dev Sub-contracts can override this method to avoid reading from storage. + function _getOverride(uint64 _chainId, bytes32 _name) internal pure virtual returns (address) { } + + function _authorizePause(address, bool) internal pure override notImplemented { } } diff --git a/packages/protocol/contracts/common/AddressResolver.sol b/packages/protocol/contracts/common/AddressResolver.sol index 3fd49a68275b..27f88b2a2216 100644 --- a/packages/protocol/contracts/common/AddressResolver.sol +++ b/packages/protocol/contracts/common/AddressResolver.sol @@ -1,111 +1,106 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/Strings.sol"; -import "./AddressManager.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "./IAddressManager.sol"; +import "./IAddressResolver.sol"; /// @title AddressResolver -/// @notice This contract acts as a bridge for name-to-address resolution. -/// It delegates the resolution to the AddressManager. By separating the logic, -/// we can maintain flexibility in address management without affecting the -/// resolving process. -/// -/// Note that the address manager should be changed using upgradability, there -/// is no setAddressManager() function go guarantee atomicness across all -/// contracts that are resolvers. -abstract contract AddressResolver { - using Strings for uint256; - +/// @notice See the documentation in {IAddressResolver}. +/// @custom:security-contact security@taiko.xyz +abstract contract AddressResolver is IAddressResolver, Initializable { + /// @notice Address of the AddressManager. address public addressManager; uint256[49] private __gap; error RESOLVER_DENIED(); error RESOLVER_INVALID_MANAGER(); error RESOLVER_UNEXPECTED_CHAINID(); - error RESOLVER_ZERO_ADDR(uint64 chainId, string name); + error RESOLVER_ZERO_ADDR(uint64 chainId, bytes32 name); /// @dev Modifier that ensures the caller is the resolved address of a given /// name. - /// @param name The name to check against. - modifier onlyFromNamed(bytes32 name) { - if (msg.sender != resolve(name, true)) revert RESOLVER_DENIED(); + /// @param _name The name to check against. + modifier onlyFromNamed(bytes32 _name) { + if (msg.sender != resolve(_name, true)) revert RESOLVER_DENIED(); _; } - /// @notice Resolves a name to its address deployed on this chain. - /// @param name Name whose address is to be resolved. - /// @param allowZeroAddress If set to true, does not throw if the resolved - /// address is `address(0)`. - /// @return addr Address associated with the given name. + /// @dev Modifier that ensures the caller is a resolved address to either _name1 or _name2 + /// name. + /// @param _name1 The first name to check against. + /// @param _name2 The second name to check against. + modifier onlyFromNamedEither(bytes32 _name1, bytes32 _name2) { + if (msg.sender != resolve(_name1, true) && msg.sender != resolve(_name2, true)) { + revert RESOLVER_DENIED(); + } + _; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @inheritdoc IAddressResolver function resolve( - bytes32 name, - bool allowZeroAddress + bytes32 _name, + bool _allowZeroAddress ) public view virtual - returns (address payable addr) + returns (address payable) { - return _resolve(uint64(block.chainid), name, allowZeroAddress); + return _resolve(uint64(block.chainid), _name, _allowZeroAddress); } - /// @notice Resolves a name to its address deployed on a specified chain. - /// @param chainId The chainId of interest. - /// @param name Name whose address is to be resolved. - /// @param allowZeroAddress If set to true, does not throw if the resolved - /// address is `address(0)`. - /// @return addr Address associated with the given name on the specified - /// chain. + /// @inheritdoc IAddressResolver function resolve( - uint64 chainId, - bytes32 name, - bool allowZeroAddress + uint64 _chainId, + bytes32 _name, + bool _allowZeroAddress ) public view virtual - returns (address payable addr) + returns (address payable) { - return _resolve(chainId, name, allowZeroAddress); + return _resolve(_chainId, _name, _allowZeroAddress); } /// @dev Initialization method for setting up AddressManager reference. /// @param _addressManager Address of the AddressManager. - // solhint-disable-next-line func-name-mixedcase - function __AddressResolver_init(address _addressManager) internal virtual { - if (block.chainid >= type(uint64).max) { + function __AddressResolver_init(address _addressManager) internal virtual onlyInitializing { + if (block.chainid > type(uint64).max) { revert RESOLVER_UNEXPECTED_CHAINID(); } addressManager = _addressManager; } /// @dev Helper method to resolve name-to-address. - /// @param chainId The chainId of interest. - /// @param name Name whose address is to be resolved. - /// @param allowZeroAddress If set to true, does not throw if the resolved + /// @param _chainId The chainId of interest. + /// @param _name Name whose address is to be resolved. + /// @param _allowZeroAddress If set to true, does not throw if the resolved /// address is `address(0)`. - /// @return addr Address associated with the given name on the specified + /// @return addr_ Address associated with the given name on the specified /// chain. function _resolve( - uint64 chainId, - bytes32 name, - bool allowZeroAddress + uint64 _chainId, + bytes32 _name, + bool _allowZeroAddress ) private view - returns (address payable addr) + returns (address payable addr_) { - if (addressManager == address(0)) revert RESOLVER_INVALID_MANAGER(); + address _addressManager = addressManager; + if (_addressManager == address(0)) revert RESOLVER_INVALID_MANAGER(); - addr = payable(IAddressManager(addressManager).getAddress(chainId, name)); + addr_ = payable(IAddressManager(_addressManager).getAddress(_chainId, _name)); - if (!allowZeroAddress && addr == address(0)) { - revert RESOLVER_ZERO_ADDR(chainId, uint256(name).toString()); + if (!_allowZeroAddress && addr_ == address(0)) { + revert RESOLVER_ZERO_ADDR(_chainId, _name); } } } diff --git a/packages/protocol/contracts/common/EssentialContract.sol b/packages/protocol/contracts/common/EssentialContract.sol index 4ba7b71a16f7..3bb208173661 100644 --- a/packages/protocol/contracts/common/EssentialContract.sol +++ b/packages/protocol/contracts/common/EssentialContract.sol @@ -1,35 +1,175 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "./AddressResolver.sol"; -import "./OwnerUUPSUpgradable.sol"; +import "../libs/LibNetwork.sol"; + +/// @title EssentialContract +/// @custom:security-contact security@taiko.xyz +abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, AddressResolver { + uint8 private constant _FALSE = 1; + + uint8 private constant _TRUE = 2; + + /// @dev The slot in transient storage of the reentry lock. + /// This is the result of keccak256("ownerUUPS.reentry_slot") plus 1. The addition aims to + /// prevent hash collisions with slots defined in EIP-1967, where slots are derived by + /// keccak256("something") - 1, and with slots in SignalService, calculated directly with + /// keccak256("something"). + bytes32 private constant _REENTRY_SLOT = + 0xa5054f728453d3dbe953bdc43e4d0cb97e662ea32d7958190f3dc2da31d9721b; + + /// @dev Slot 1. + uint8 private __reentry; + uint8 private __paused; + uint64 public lastUnpausedAt; + + uint256[49] private __gap; -abstract contract EssentialContract is OwnerUUPSUpgradable, AddressResolver { - uint256[50] private __gap; + /// @notice Emitted when the contract is paused. + /// @param account The account that paused the contract. + event Paused(address account); + + /// @notice Emitted when the contract is unpaused. + /// @param account The account that unpaused the contract. + event Unpaused(address account); + + error INVALID_PAUSE_STATUS(); + error FUNC_NOT_IMPLEMENTED(); + error REENTRANT_CALL(); + error ZERO_ADDRESS(); + error ZERO_VALUE(); /// @dev Modifier that ensures the caller is the owner or resolved address of a given name. - /// @param name The name to check against. - modifier onlyFromOwnerOrNamed(bytes32 name) { - if (msg.sender != owner() && msg.sender != resolve(name, true)) revert RESOLVER_DENIED(); + /// @param _name The name to check against. + modifier onlyFromOwnerOrNamed(bytes32 _name) { + if (msg.sender != owner() && msg.sender != resolve(_name, true)) revert RESOLVER_DENIED(); + _; + } + + modifier notImplemented() { + revert FUNC_NOT_IMPLEMENTED(); + _; + } + + modifier nonReentrant() { + if (_loadReentryLock() == _TRUE) revert REENTRANT_CALL(); + _storeReentryLock(_TRUE); + _; + _storeReentryLock(_FALSE); + } + + modifier whenPaused() { + if (!paused()) revert INVALID_PAUSE_STATUS(); + _; + } + + modifier whenNotPaused() { + if (paused()) revert INVALID_PAUSE_STATUS(); + _; + } + + modifier nonZeroAddr(address _addr) { + if (_addr == address(0)) revert ZERO_ADDRESS(); + _; + } + + modifier nonZeroValue(bytes32 _value) { + if (_value == 0) revert ZERO_VALUE(); _; } - /// @notice Initializes the contract with an address manager. - /// @param _addressManager The address of the address manager. - // solhint-disable-next-line func-name-mixedcase - function __Essential_init(address _addressManager) internal virtual { - __OwnerUUPSUpgradable_init(); + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Pauses the contract. + function pause() public virtual { + _pause(); + // We call the authorize function here to avoid: + // Warning (5740): Unreachable code. + _authorizePause(msg.sender, true); + } + + /// @notice Unpauses the contract. + function unpause() public virtual { + _unpause(); + // We call the authorize function here to avoid: + // Warning (5740): Unreachable code. + _authorizePause(msg.sender, false); + } + + function impl() public view returns (address) { + return _getImplementation(); + } + + /// @notice Returns true if the contract is paused, and false otherwise. + /// @return true if paused, false otherwise. + function paused() public view returns (bool) { + return __paused == _TRUE; + } + + function inNonReentrant() public view returns (bool) { + return _loadReentryLock() == _TRUE; + } + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function __Essential_init( + address _owner, + address _addressManager + ) + internal + nonZeroAddr(_addressManager) + { + __Essential_init(_owner); __AddressResolver_init(_addressManager); } - /// @notice Initializes the contract with an address manager. - // solhint-disable-next-line func-name-mixedcase - function __Essential_init() internal virtual { - __Essential_init(address(0)); + function __Essential_init(address _owner) internal virtual onlyInitializing { + __Context_init(); + _transferOwnership(_owner == address(0) ? msg.sender : _owner); + __paused = _FALSE; + } + + function _pause() internal whenNotPaused { + __paused = _TRUE; + emit Paused(msg.sender); + } + + function _unpause() internal whenPaused { + __paused = _FALSE; + lastUnpausedAt = uint64(block.timestamp); + emit Unpaused(msg.sender); + } + + function _authorizeUpgrade(address) internal virtual override onlyOwner { } + + function _authorizePause(address, bool) internal virtual onlyOwner { } + + // Stores the reentry lock + function _storeReentryLock(uint8 _reentry) internal virtual { + if (LibNetwork.isDencunSupported(block.chainid)) { + assembly { + tstore(_REENTRY_SLOT, _reentry) + } + } else { + __reentry = _reentry; + } + } + + // Loads the reentry lock + function _loadReentryLock() internal view virtual returns (uint8 reentry_) { + if (LibNetwork.isDencunSupported(block.chainid)) { + assembly { + reentry_ := tload(_REENTRY_SLOT) + } + } else { + reentry_ = __reentry; + } } } diff --git a/packages/protocol/contracts/common/IAddressManager.sol b/packages/protocol/contracts/common/IAddressManager.sol new file mode 100644 index 000000000000..f88933462fe3 --- /dev/null +++ b/packages/protocol/contracts/common/IAddressManager.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IAddressManager +/// @notice Manages a mapping of (chainId, name) pairs to Ethereum addresses. +/// @custom:security-contact security@taiko.xyz +interface IAddressManager { + /// @notice Gets the address mapped to a specific chainId-name pair. + /// @dev Note that in production, this method shall be a pure function + /// without any storage access. + /// @param _chainId The chainId for which the address needs to be fetched. + /// @param _name The name for which the address needs to be fetched. + /// @return Address associated with the chainId-name pair. + function getAddress(uint64 _chainId, bytes32 _name) external view returns (address); +} diff --git a/packages/protocol/contracts/common/IAddressResolver.sol b/packages/protocol/contracts/common/IAddressResolver.sol new file mode 100644 index 000000000000..886e123e1ba9 --- /dev/null +++ b/packages/protocol/contracts/common/IAddressResolver.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IAddressResolver +/// @notice This contract acts as a bridge for name-to-address resolution. +/// It delegates the resolution to the AddressManager. By separating the logic, +/// we can maintain flexibility in address management without affecting the +/// resolving process. +/// @dev Note that the address manager should be changed using upgradability, there +/// is no setAddressManager() function to guarantee atomicity across all +/// contracts that are resolvers. +/// @custom:security-contact security@taiko.xyz +interface IAddressResolver { + /// @notice Resolves a name to its address deployed on this chain. + /// @param _name Name whose address is to be resolved. + /// @param _allowZeroAddress If set to true, does not throw if the resolved + /// address is `address(0)`. + /// @return Address associated with the given name. + function resolve( + bytes32 _name, + bool _allowZeroAddress + ) + external + view + returns (address payable); + + /// @notice Resolves a name to its address deployed on a specified chain. + /// @param _chainId The chainId of interest. + /// @param _name Name whose address is to be resolved. + /// @param _allowZeroAddress If set to true, does not throw if the resolved + /// address is `address(0)`. + /// @return Address associated with the given name on the specified + /// chain. + function resolve( + uint64 _chainId, + bytes32 _name, + bool _allowZeroAddress + ) + external + view + returns (address payable); +} diff --git a/packages/protocol/contracts/common/LibStrings.sol b/packages/protocol/contracts/common/LibStrings.sol new file mode 100644 index 000000000000..3403c683a16f --- /dev/null +++ b/packages/protocol/contracts/common/LibStrings.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title LibStrings +/// @custom:security-contact security@taiko.xyz +library LibStrings { + bytes32 internal constant B_AUTOMATA_DCAP_ATTESTATION = bytes32("automata_dcap_attestation"); + bytes32 internal constant B_BRIDGE = bytes32("bridge"); + bytes32 internal constant B_BRIDGE_WATCHDOG = bytes32("bridge_watchdog"); + bytes32 internal constant B_BRIDGED_ERC1155 = bytes32("bridged_erc1155"); + bytes32 internal constant B_BRIDGED_ERC20 = bytes32("bridged_erc20"); + bytes32 internal constant B_BRIDGED_ERC721 = bytes32("bridged_erc721"); + bytes32 internal constant B_CHAIN_WATCHDOG = bytes32("chain_watchdog"); + bytes32 internal constant B_ERC1155_VAULT = bytes32("erc1155_vault"); + bytes32 internal constant B_ERC20_VAULT = bytes32("erc20_vault"); + bytes32 internal constant B_ERC721_VAULT = bytes32("erc721_vault"); + bytes32 internal constant B_PROVER_ASSIGNMENT = bytes32("PROVER_ASSIGNMENT"); + bytes32 internal constant B_PROVER_SET = bytes32("prover_set"); + bytes32 internal constant B_QUOTA_MANAGER = bytes32("quota_manager"); + bytes32 internal constant B_SGX_WATCHDOG = bytes32("sgx_watchdog"); + bytes32 internal constant B_SIGNAL_SERVICE = bytes32("signal_service"); + bytes32 internal constant B_TAIKO = bytes32("taiko"); + bytes32 internal constant B_TAIKO_TOKEN = bytes32("taiko_token"); + bytes32 internal constant B_TIER_GUARDIAN = bytes32("tier_guardian"); + bytes32 internal constant B_TIER_GUARDIAN_MINORITY = bytes32("tier_guardian_minority"); + bytes32 internal constant B_TIER_ROUTER = bytes32("tier_router"); + bytes32 internal constant B_TIER_SGX = bytes32("tier_sgx"); + bytes32 internal constant B_TIER_SGX_ZKVM = bytes32("tier_sgx_zkvm"); + bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer"); + bytes32 internal constant H_RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); + bytes32 internal constant H_SIGNAL_ROOT = keccak256("SIGNAL_ROOT"); + bytes32 internal constant H_STATE_ROOT = keccak256("STATE_ROOT"); +} diff --git a/packages/protocol/contracts/common/OwnerUUPSUpgradable.sol b/packages/protocol/contracts/common/OwnerUUPSUpgradable.sol deleted file mode 100644 index 9dab1fb81041..000000000000 --- a/packages/protocol/contracts/common/OwnerUUPSUpgradable.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -/// @title OwnerUUPSUpgradable -/// @notice This contract serves as the base contract for many core components. -/// @dev We didn't use OpenZeppelin's PausableUpgradeable and -/// ReentrancyGuardUpgradeable contract to optimize storage reads. -abstract contract OwnerUUPSUpgradable is UUPSUpgradeable, OwnableUpgradeable { - uint8 private constant _FALSE = 1; - uint8 private constant _TRUE = 2; - - uint8 private _reentry; // slot 1 - uint8 private _paused; - uint256[49] private __gap; - - event Paused(address account); - event Unpaused(address account); - - error REENTRANT_CALL(); - error INVALID_PAUSE_STATUS(); - - modifier nonReentrant() { - if (_reentry == _TRUE) revert REENTRANT_CALL(); - _reentry = _TRUE; - _; - _reentry = _FALSE; - } - - modifier whenPaused() { - if (!paused()) revert INVALID_PAUSE_STATUS(); - _; - } - - modifier whenNotPaused() { - if (paused()) revert INVALID_PAUSE_STATUS(); - _; - } - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - function pause() external whenNotPaused onlyOwner { - _paused = _TRUE; - emit Paused(msg.sender); - } - - function unpause() external whenPaused onlyOwner { - _paused = _FALSE; - emit Unpaused(msg.sender); - } - - function paused() public view returns (bool) { - return _paused == _TRUE; - } - - function _authorizeUpgrade(address) internal override onlyOwner { } - - /// @notice Initializes the contract with an address manager. - // solhint-disable-next-line func-name-mixedcase - function __OwnerUUPSUpgradable_init() internal virtual { - __Ownable_init(); - _reentry = _FALSE; - _paused = _FALSE; - } - - function _inNonReentrant() internal view returns (bool) { - return _reentry == _TRUE; - } -} diff --git a/packages/protocol/contracts/compiled/FiatTokenProxy.json b/packages/protocol/contracts/compiled/FiatTokenProxy.json deleted file mode 100644 index cebc9f3ceca1..000000000000 --- a/packages/protocol/contracts/compiled/FiatTokenProxy.json +++ /dev/null @@ -1,552 +0,0 @@ -{ - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "implementationContract", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "previousAdmin", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "AdminChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "implementation", - "type": "address" - } - ], - "name": "Upgraded", - "type": "event" - }, - { - "stateMutability": "payable", - "type": "fallback" - }, - { - "inputs": [], - "name": "admin", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "name": "changeAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "name": "upgradeTo", - "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" - } - ], - "bytecode": { - "object": "0x608060405234801561001057600080fd5b506040516108a93803806108a98339818101604052602081101561003357600080fd5b5051808061004081610051565b5061004a336100c3565b5050610123565b610064816100e760201b61042a1760201c565b61009f5760405162461bcd60e51b815260040180806020018281038252603b81526020018061086e603b913960400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c355565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061011b57508115155b949350505050565b61073c806101326000396000f3fe60806040526004361061005a5760003560e01c80635c60da1b116100435780635c60da1b146101315780638f2839701461016f578063f851a440146101af5761005a565b80633659cfe6146100645780634f1ef286146100a4575b6100626101c4565b005b34801561007057600080fd5b506100626004803603602081101561008757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101de565b610062600480360360408110156100ba57600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691908101906040810160208201356401000000008111156100f257600080fd5b82018360208201111561010457600080fd5b8035906020019184600183028401116401000000008311171561012657600080fd5b509092509050610232565b34801561013d57600080fd5b50610146610309565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561017b57600080fd5b506100626004803603602081101561019257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610318565b3480156101bb57600080fd5b50610146610420565b6101cc610466565b6101dc6101d76104fa565b61051f565b565b6101e6610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275761022281610568565b61022f565b61022f6101c4565b50565b61023a610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fc5761027683610568565b60003073ffffffffffffffffffffffffffffffffffffffff16348484604051808383808284376040519201945060009350909150508083038185875af1925050503d80600081146102e3576040519150601f19603f3d011682016040523d82523d6000602084013e6102e8565b606091505b50509050806102f657600080fd5b50610304565b6103046101c4565b505050565b60006103136104fa565b905090565b610320610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275773ffffffffffffffffffffffffffffffffffffffff81166103bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001806106966036913960400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103e8610543565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301528051918290030190a1610222816105bd565b6000610313610543565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061045e57508115155b949350505050565b61046e610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156104f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806106646032913960400191505060405180910390fd5b6101dc6101dc565b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35490565b3660008037600080366000845af43d6000803e80801561053e573d6000f35b3d6000fd5b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b5490565b610571816105e1565b6040805173ffffffffffffffffffffffffffffffffffffffff8316815290517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9181900360200190a150565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b6105ea8161042a565b61063f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001806106cc603b913960400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35556fe43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e2066726f6d207468652070726f78792061646d696e43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f787920746f20746865207a65726f206164647265737343616e6e6f742073657420612070726f787920696d706c656d656e746174696f6e20746f2061206e6f6e2d636f6e74726163742061646472657373a264697066735822122001b610efbebc57927addec087895f40a32b15707f9bbcd4d028e2ffcc8cd241f64736f6c634300060c003343616e6e6f742073657420612070726f787920696d706c656d656e746174696f6e20746f2061206e6f6e2d636f6e74726163742061646472657373", - "sourceMap": "1385:182:20:-:0;;;1443:122;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1443:122:20;;;2686:42:19;2705:22;2686:18;:42::i;:::-;-1:-1:-1;3044:21:17::1;3054:10;3044:9;:21::i;:::-;2847:225:::0;1443:122:20;1385:182;;3492:342:19;3586:37;3605:17;3586:18;;;;;:37;;:::i;:::-;3565:143;;;;-1:-1:-1;;;3565:143:19;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2298:66;3787:31;3773:55::o;5448:153:17:-;2256:66;5563:22;5549:46::o;718:610:21:-;778:4;1239:20;;1084:66;1278:23;;;;;;:42;;-1:-1:-1;1305:15:21;;;1278:42;1270:51;718:610;-1:-1:-1;;;;718:610:21:o;1385:182:20:-;;;;;;;", - "linkReferences": {} - }, - "deployedBytecode": { - "object": "0x60806040526004361061005a5760003560e01c80635c60da1b116100435780635c60da1b146101315780638f2839701461016f578063f851a440146101af5761005a565b80633659cfe6146100645780634f1ef286146100a4575b6100626101c4565b005b34801561007057600080fd5b506100626004803603602081101561008757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101de565b610062600480360360408110156100ba57600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691908101906040810160208201356401000000008111156100f257600080fd5b82018360208201111561010457600080fd5b8035906020019184600183028401116401000000008311171561012657600080fd5b509092509050610232565b34801561013d57600080fd5b50610146610309565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561017b57600080fd5b506100626004803603602081101561019257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610318565b3480156101bb57600080fd5b50610146610420565b6101cc610466565b6101dc6101d76104fa565b61051f565b565b6101e6610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275761022281610568565b61022f565b61022f6101c4565b50565b61023a610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fc5761027683610568565b60003073ffffffffffffffffffffffffffffffffffffffff16348484604051808383808284376040519201945060009350909150508083038185875af1925050503d80600081146102e3576040519150601f19603f3d011682016040523d82523d6000602084013e6102e8565b606091505b50509050806102f657600080fd5b50610304565b6103046101c4565b505050565b60006103136104fa565b905090565b610320610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275773ffffffffffffffffffffffffffffffffffffffff81166103bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001806106966036913960400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103e8610543565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301528051918290030190a1610222816105bd565b6000610313610543565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a47081811480159061045e57508115155b949350505050565b61046e610543565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156104f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806106646032913960400191505060405180910390fd5b6101dc6101dc565b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35490565b3660008037600080366000845af43d6000803e80801561053e573d6000f35b3d6000fd5b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b5490565b610571816105e1565b6040805173ffffffffffffffffffffffffffffffffffffffff8316815290517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9181900360200190a150565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b6105ea8161042a565b61063f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b8152602001806106cc603b913960400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35556fe43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e2066726f6d207468652070726f78792061646d696e43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f787920746f20746865207a65726f206164647265737343616e6e6f742073657420612070726f787920696d706c656d656e746174696f6e20746f2061206e6f6e2d636f6e74726163742061646472657373a264697066735822122001b610efbebc57927addec087895f40a32b15707f9bbcd4d028e2ffcc8cd241f64736f6c634300060c0033", - "sourceMap": "1385:182:20:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1836:11:18;:9;:11::i;:::-;1385:182:20;4049:109:17;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;4049:109:17;;;;:::i;4702:406::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;4702:406:17;;-1:-1:-1;4702:406:17;-1:-1:-1;4702:406:17;:::i;3294:99::-;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;3581:272;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;3581:272:17;;;;:::i;3141:81::-;;;;;;;;;;;;;:::i;3688:100:18:-;3728:15;:13;:15::i;:::-;3753:28;3763:17;:15;:17::i;:::-;3753:9;:28::i;:::-;3688:100::o;4049:109:17:-;2571:8;:6;:8::i;:::-;2557:22;;:10;:22;;;2553:96;;;4122:29:::1;4133:17;4122:10;:29::i;:::-;2553:96:::0;;;2627:11;:9;:11::i;:::-;4049:109;:::o;4702:406::-;2571:8;:6;:8::i;:::-;2557:22;;:10;:22;;;2553:96;;;4839:29:::1;4850:17;4839:10;:29::i;:::-;4965:12;4990:4;4982:18;;5008:9;5019:4;;4982:42;;;;;;;;;;::::0;;::::1;::::0;-1:-1:-1;4982:42:17::1;::::0;-1:-1:-1;4982:42:17;;-1:-1:-1;;4982:42:17;;::::1;::::0;;;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4964:60;;;5093:7;5085:16;;;::::0;::::1;;2595:1;2553:96:::0;;;2627:11;:9;:11::i;:::-;4702:406;;;:::o;3294:99::-;3343:7;3369:17;:15;:17::i;:::-;3362:24;;3294:99;:::o;3581:272::-;2571:8;:6;:8::i;:::-;2557:22;;:10;:22;;;2553:96;;;3668:22:::1;::::0;::::1;3647:123;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3785:32;3798:8;:6;:8::i;:::-;3785:32;::::0;;::::1;::::0;;::::1;::::0;;;;::::1;;::::0;::::1;::::0;;;;;;;;;::::1;3827:19;3837:8;3827:9;:19::i;3141:81::-:0;3181:7;3207:8;:6;:8::i;718:610:21:-;778:4;1239:20;;1084:66;1278:23;;;;;;:42;;-1:-1:-1;1305:15:21;;;1278:42;1270:51;718:610;-1:-1:-1;;;;718:610:21:o;5684:210:17:-;5772:8;:6;:8::i;:::-;5758:22;;:10;:22;;;;5737:119;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5866:21;:19;:21::i;2863:185:19:-;2298:66;3021:11;;2999:43::o;2293:1025:18:-;2623:14;2620:1;2617;2604:34;2934:1;2915;2883:14;2864:1;2832:14;2809:5;2779:170;3023:16;3020:1;3017;3002:38;3061:6;3136:74;;;;3267:16;3264:1;3257:27;3136:74;3175:16;3172:1;3165:27;5165:157:17;2256:66;5295:11;;5274:42::o;3193:152:19:-;3259:37;3278:17;3259:18;:37::i;:::-;3311:27;;;;;;;;;;;;;;;;;;;3193:152;:::o;5448:153:17:-;2256:66;5563:22;5549:46::o;3492:342:19:-;3586:37;3605:17;3586:18;:37::i;:::-;3565:143;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2298:66;3787:31;3773:55::o", - "linkReferences": {} - }, - "methodIdentifiers": { - "admin()": "f851a440", - "changeAdmin(address)": "8f283970", - "implementation()": "5c60da1b", - "upgradeTo(address)": "3659cfe6", - "upgradeToAndCall(address,bytes)": "4f1ef286" - }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.6.12+commit.27d51765\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"implementationContract\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"AdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"implementation\",\"type\":\"address\"}],\"name\":\"Upgraded\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"admin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"changeAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"upgradeTo\",\"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\":\"This contract proxies FiatToken calls and enables FiatToken upgrades\",\"kind\":\"dev\",\"methods\":{\"admin()\":{\"returns\":{\"_0\":\"The address of the proxy admin.\"}},\"changeAdmin(address)\":{\"details\":\"Changes the admin of the proxy. Only the current admin can call this function.\",\"params\":{\"newAdmin\":\"Address to transfer proxy administration to.\"}},\"implementation()\":{\"returns\":{\"_0\":\"The address of the implementation.\"}},\"upgradeTo(address)\":{\"details\":\"Upgrade the backing implementation of the proxy. Only the admin can call this function.\",\"params\":{\"newImplementation\":\"Address of the new implementation.\"}},\"upgradeToAndCall(address,bytes)\":{\"details\":\"Upgrade the backing implementation of the proxy and call a function on the new implementation. This is useful to initialize the proxied contract.\",\"params\":{\"data\":\"Data to send as msg.data in the low level call. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.\",\"newImplementation\":\"Address of the new implementation.\"}}},\"title\":\"FiatTokenProxy\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/FiatTokenProxy/centre-tokens/contracts/v1/FiatTokenProxy.sol\":\"FiatTokenProxy\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000000},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/FiatTokenProxy/centre-tokens/contracts/upgradeability/AdminUpgradeabilityProxy.sol\":{\"keccak256\":\"0xc93cb352d8b777ea96e743124af5386eeee32a9fdef0b2fbd89623988e66caad\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d0407b40cd936bdf6f6ae141fef3da54824a786affc9a459cd6cd83478043683\",\"dweb:/ipfs/QmQh2LjAVdU2HKc7w1fXxuPEfFvTcBifHiYzgdrZtDB9rk\"]},\"src/FiatTokenProxy/centre-tokens/contracts/upgradeability/Proxy.sol\":{\"keccak256\":\"0x6cc252e2b80c8ecaf6d29b950ba3591e4366caf06c3ccba89a8f9cbd2ee807e3\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d60d210ae173d21b90b989e69c50789fb09dc27ccb0736b41907471248ee3087\",\"dweb:/ipfs/QmQbijUGm48UDyqnefEJExWsxBViKj1M5TAWY82Jn6sJW7\"]},\"src/FiatTokenProxy/centre-tokens/contracts/upgradeability/UpgradeabilityProxy.sol\":{\"keccak256\":\"0x3086b8904fb474eb3d8d701f8ec6991796c5e8a7345ace9c3aabc140973f6c85\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://cde7d942bb42ee3fb09ed643751151bc192522a1dc9ccb5d28aff7fdadf8b9e6\",\"dweb:/ipfs/QmQBVbxYcZjRFH66R1ZByXx8rQym3Sx7jjFtFKRWhLXxdg\"]},\"src/FiatTokenProxy/centre-tokens/contracts/v1/FiatTokenProxy.sol\":{\"keccak256\":\"0x7e640892ac0fd6efafe4a9dc08cbc7ba5e825dafe8a6a8ebf717e7026b8fb69f\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2e84a985f3271652e16eec4df9be1829f042a98b5723db5b87aeeb8eadc9c207\",\"dweb:/ipfs/QmU4JWGMzd3rA64BiDVejhnapKRJG4WHLuw3g866hFPLTx\"]},\"src/FiatTokenProxy/openzeppelin/contracts/utils/Address.sol\":{\"keccak256\":\"0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff\",\"dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS\"]}},\"version\":1}", - "metadata": { - "compiler": { - "version": "0.6.12+commit.27d51765" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "implementationContract", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "previousAdmin", - "type": "address", - "indexed": false - }, - { - "internalType": "address", - "name": "newAdmin", - "type": "address", - "indexed": false - } - ], - "type": "event", - "name": "AdminChanged", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "implementation", - "type": "address", - "indexed": false - } - ], - "type": "event", - "name": "Upgraded", - "anonymous": false - }, - { - "inputs": [], - "stateMutability": "payable", - "type": "fallback" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "admin", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAdmin", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "changeAdmin" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "implementation", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "upgradeTo" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newImplementation", - "type": "address" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "stateMutability": "payable", - "type": "function", - "name": "upgradeToAndCall" - } - ], - "devdoc": { - "kind": "dev", - "methods": { - "admin()": { - "returns": { - "_0": "The address of the proxy admin." - } - }, - "changeAdmin(address)": { - "details": "Changes the admin of the proxy. Only the current admin can call this function.", - "params": { - "newAdmin": "Address to transfer proxy administration to." - } - }, - "implementation()": { - "returns": { - "_0": "The address of the implementation." - } - }, - "upgradeTo(address)": { - "details": "Upgrade the backing implementation of the proxy. Only the admin can call this function.", - "params": { - "newImplementation": "Address of the new implementation." - } - }, - "upgradeToAndCall(address,bytes)": { - "details": "Upgrade the backing implementation of the proxy and call a function on the new implementation. This is useful to initialize the proxied contract.", - "params": { - "data": "Data to send as msg.data in the low level call. It should include the signature and the parameters of the function to be called, as described in https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding.", - "newImplementation": "Address of the new implementation." - } - } - }, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": {}, - "version": 1 - } - }, - "settings": { - "remappings": [ - "ds-test/=lib/forge-std/lib/ds-test/src/", - "forge-std/=lib/forge-std/src/" - ], - "optimizer": { - "enabled": true, - "runs": 10000000 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "compilationTarget": { - "src/FiatTokenProxy/centre-tokens/contracts/v1/FiatTokenProxy.sol": "FiatTokenProxy" - }, - "libraries": {} - }, - "sources": { - "src/FiatTokenProxy/centre-tokens/contracts/upgradeability/AdminUpgradeabilityProxy.sol": { - "keccak256": "0xc93cb352d8b777ea96e743124af5386eeee32a9fdef0b2fbd89623988e66caad", - "urls": [ - "bzz-raw://d0407b40cd936bdf6f6ae141fef3da54824a786affc9a459cd6cd83478043683", - "dweb:/ipfs/QmQh2LjAVdU2HKc7w1fXxuPEfFvTcBifHiYzgdrZtDB9rk" - ], - "license": "MIT" - }, - "src/FiatTokenProxy/centre-tokens/contracts/upgradeability/Proxy.sol": { - "keccak256": "0x6cc252e2b80c8ecaf6d29b950ba3591e4366caf06c3ccba89a8f9cbd2ee807e3", - "urls": [ - "bzz-raw://d60d210ae173d21b90b989e69c50789fb09dc27ccb0736b41907471248ee3087", - "dweb:/ipfs/QmQbijUGm48UDyqnefEJExWsxBViKj1M5TAWY82Jn6sJW7" - ], - "license": "MIT" - }, - "src/FiatTokenProxy/centre-tokens/contracts/upgradeability/UpgradeabilityProxy.sol": { - "keccak256": "0x3086b8904fb474eb3d8d701f8ec6991796c5e8a7345ace9c3aabc140973f6c85", - "urls": [ - "bzz-raw://cde7d942bb42ee3fb09ed643751151bc192522a1dc9ccb5d28aff7fdadf8b9e6", - "dweb:/ipfs/QmQBVbxYcZjRFH66R1ZByXx8rQym3Sx7jjFtFKRWhLXxdg" - ], - "license": "MIT" - }, - "src/FiatTokenProxy/centre-tokens/contracts/v1/FiatTokenProxy.sol": { - "keccak256": "0x7e640892ac0fd6efafe4a9dc08cbc7ba5e825dafe8a6a8ebf717e7026b8fb69f", - "urls": [ - "bzz-raw://2e84a985f3271652e16eec4df9be1829f042a98b5723db5b87aeeb8eadc9c207", - "dweb:/ipfs/QmU4JWGMzd3rA64BiDVejhnapKRJG4WHLuw3g866hFPLTx" - ], - "license": "MIT" - }, - "src/FiatTokenProxy/openzeppelin/contracts/utils/Address.sol": { - "keccak256": "0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498", - "urls": [ - "bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff", - "dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS" - ], - "license": "MIT" - } - }, - "version": 1 - }, - "ast": { - "absolutePath": "src/FiatTokenProxy/centre-tokens/contracts/v1/FiatTokenProxy.sol", - "id": 40713, - "exportedSymbols": { - "FiatTokenProxy": [40712] - }, - "nodeType": "SourceUnit", - "src": "1154:414:20", - "nodes": [ - { - "id": 40697, - "nodeType": "PragmaDirective", - "src": "1154:23:20", - "nodes": [], - "literals": ["solidity", "0.6", ".12"] - }, - { - "id": 40699, - "nodeType": "ImportDirective", - "src": "1179:94:20", - "nodes": [], - "absolutePath": "src/FiatTokenProxy/centre-tokens/contracts/upgradeability/AdminUpgradeabilityProxy.sol", - "file": "../upgradeability/AdminUpgradeabilityProxy.sol", - "scope": 40713, - "sourceUnit": 40566, - "symbolAliases": [ - { - "foreign": { - "argumentTypes": null, - "id": 40698, - "name": "AdminUpgradeabilityProxy", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": null, - "src": "1192:24:20", - "typeDescriptions": { - "typeIdentifier": null, - "typeString": null - } - }, - "local": null - } - ], - "unitAlias": "" - }, - { - "id": 40712, - "nodeType": "ContractDefinition", - "src": "1385:182:20", - "nodes": [ - { - "id": 40711, - "nodeType": "FunctionDefinition", - "src": "1443:122:20", - "nodes": [], - "body": { - "id": 40710, - "nodeType": "Block", - "src": "1563:2:20", - "nodes": [], - "statements": [] - }, - "documentation": null, - "implemented": true, - "kind": "constructor", - "modifiers": [ - { - "arguments": [ - { - "argumentTypes": null, - "id": 40707, - "name": "implementationContract", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 40704, - "src": "1535:22:20", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - } - ], - "id": 40708, - "modifierName": { - "argumentTypes": null, - "id": 40706, - "name": "AdminUpgradeabilityProxy", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 40565, - "src": "1510:24:20", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_contract$_AdminUpgradeabilityProxy_$40565_$", - "typeString": "type(contract AdminUpgradeabilityProxy)" - } - }, - "nodeType": "ModifierInvocation", - "src": "1510:48:20" - } - ], - "name": "", - "overrides": null, - "parameters": { - "id": 40705, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 40704, - "mutability": "mutable", - "name": "implementationContract", - "nodeType": "VariableDeclaration", - "overrides": null, - "scope": 40711, - "src": "1455:30:20", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 40703, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "1455:7:20", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - } - ], - "src": "1454:32:20" - }, - "returnParameters": { - "id": 40709, - "nodeType": "ParameterList", - "parameters": [], - "src": "1563:0:20" - }, - "scope": 40712, - "stateMutability": "nonpayable", - "virtual": false, - "visibility": "public" - } - ], - "abstract": false, - "baseContracts": [ - { - "arguments": null, - "baseName": { - "contractScope": null, - "id": 40701, - "name": "AdminUpgradeabilityProxy", - "nodeType": "UserDefinedTypeName", - "referencedDeclaration": 40565, - "src": "1412:24:20", - "typeDescriptions": { - "typeIdentifier": "t_contract$_AdminUpgradeabilityProxy_$40565", - "typeString": "contract AdminUpgradeabilityProxy" - } - }, - "id": 40702, - "nodeType": "InheritanceSpecifier", - "src": "1412:24:20" - } - ], - "contractDependencies": [40565, 40609, 40695], - "contractKind": "contract", - "documentation": { - "id": 40700, - "nodeType": "StructuredDocumentation", - "src": "1275:109:20", - "text": " @title FiatTokenProxy\n @dev This contract proxies FiatToken calls and enables FiatToken upgrades" - }, - "fullyImplemented": true, - "linearizedBaseContracts": [40712, 40565, 40695, 40609], - "name": "FiatTokenProxy", - "scope": 40713 - } - ], - "license": "MIT" - }, - "id": 20 -} diff --git a/packages/protocol/contracts/compiled/FiatTokenV2_1.json b/packages/protocol/contracts/compiled/FiatTokenV2_1.json deleted file mode 100644 index ecde2cf65796..000000000000 --- a/packages/protocol/contracts/compiled/FiatTokenV2_1.json +++ /dev/null @@ -1,3852 +0,0 @@ -{ - "abi": [ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "name": "AuthorizationCanceled", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "indexed": true, - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "name": "AuthorizationUsed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "Blacklisted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newBlacklister", - "type": "address" - } - ], - "name": "BlacklisterChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "burner", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Burn", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newMasterMinter", - "type": "address" - } - ], - "name": "MasterMinterChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "Mint", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "minterAllowedAmount", - "type": "uint256" - } - ], - "name": "MinterConfigured", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "oldMinter", - "type": "address" - } - ], - "name": "MinterRemoved", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": false, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Pause", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newAddress", - "type": "address" - } - ], - "name": "PauserChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newRescuer", - "type": "address" - } - ], - "name": "RescuerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "UnBlacklisted", - "type": "event" - }, - { - "anonymous": false, - "inputs": [], - "name": "Unpause", - "type": "event" - }, - { - "inputs": [], - "name": "CANCEL_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "PERMIT_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "RECEIVE_WITH_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "TRANSFER_WITH_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "name": "authorizationState", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "blacklist", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "blacklister", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "burn", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "cancelAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "internalType": "uint256", - "name": "minterAllowedAmount", - "type": "uint256" - } - ], - "name": "configureMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "currency", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "decrement", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "increment", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "tokenName", - "type": "string" - }, - { - "internalType": "string", - "name": "tokenSymbol", - "type": "string" - }, - { - "internalType": "string", - "name": "tokenCurrency", - "type": "string" - }, - { - "internalType": "uint8", - "name": "tokenDecimals", - "type": "uint8" - }, - { - "internalType": "address", - "name": "newMasterMinter", - "type": "address" - }, - { - "internalType": "address", - "name": "newPauser", - "type": "address" - }, - { - "internalType": "address", - "name": "newBlacklister", - "type": "address" - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "newName", - "type": "string" - } - ], - "name": "initializeV2", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "lostAndFound", - "type": "address" - } - ], - "name": "initializeV2_1", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "isBlacklisted", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "isMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "masterMinter", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "name": "mint", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "minterAllowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "pauser", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "permit", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validAfter", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validBefore", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "receiveWithAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "name": "removeMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "tokenContract", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "rescueERC20", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "rescuer", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validAfter", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validBefore", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "name": "transferWithAuthorization", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "name": "unBlacklist", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newBlacklister", - "type": "address" - } - ], - "name": "updateBlacklister", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newMasterMinter", - "type": "address" - } - ], - "name": "updateMasterMinter", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newPauser", - "type": "address" - } - ], - "name": "updatePauser", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newRescuer", - "type": "address" - } - ], - "name": "updateRescuer", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": { - "object": "0x60806040526001805460ff60a01b191690556000600b553480156200002357600080fd5b506200002f3362000035565b62000057565b600080546001600160a01b0319166001600160a01b0392909216919091179055565b6153eb80620000676000396000f3fe608060405234801561001057600080fd5b50600436106103365760003560e01c80637f2eecc3116101b2578063b2118a8d116100f9578063e3ee160e116100a2578063ef55bec61161007c578063ef55bec614610cc1578063f2fde38b14610d2d578063f9f92be414610d60578063fe575a8714610d9357610336565b8063e3ee160e14610c14578063e5a6b10f14610c80578063e94a010214610c8857610336565b8063d608ea64116100d3578063d608ea6414610b61578063d916948714610bd1578063dd62ed3e14610bd957610336565b8063b2118a8d14610ab8578063bd10243014610afb578063d505accf14610b0357610336565b8063a0cc6a681161015b578063aa20e1e411610135578063aa20e1e414610a1f578063aa271e1a14610a52578063ad38bf2214610a8557610336565b8063a0cc6a68146109a5578063a457c2d7146109ad578063a9059cbb146109e657610336565b80638da5cb5b1161018c5780638da5cb5b1461098d57806395d89b41146109955780639fd0506d1461099d57610336565b80637f2eecc31461094a5780638456cb59146109525780638a6db9c31461095a57610336565b80633644e515116102815780634e44d9561161022a5780635a049a70116102045780635a049a701461088e5780635c975abb146108dc57806370a08231146108e45780637ecebe001461091757610336565b80634e44d9561461081a57806354fd4d5014610853578063554bab3c1461085b57610336565b80633f4ba83a1161025b5780633f4ba83a146107bc57806340c10f19146107c457806342966c68146107fd57610336565b80633644e5151461077357806338a631831461077b578063395093511461078357610336565b80632fc81e09116102e3578063313ce567116102bd578063313ce567146105385780633357162b1461055657806335d99f351461074257610336565b80632fc81e09146104ca5780633092afd5146104fd57806330adf81f1461053057610336565b80631a895266116103145780631a8952661461041f57806323b872dd146104545780632ab600451461049757610336565b806306fdde031461033b578063095ea7b3146103b857806318160ddd14610405575b600080fd5b610343610dc6565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561037d578181015183820152602001610365565b50505050905090810190601f1680156103aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103f1600480360360408110156103ce57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610e72565b604080519115158252519081900360200190f35b61040d610fff565b60408051918252519081900360200190f35b6104526004803603602081101561043557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611005565b005b6103f16004803603606081101561046a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356110e9565b610452600480360360208110156104ad57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113ef565b610452600480360360208110156104e057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611550565b6103f16004803603602081101561051357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166115ce565b61040d6116c7565b6105406116eb565b6040805160ff9092168252519081900360200190f35b610452600480360361010081101561056d57600080fd5b81019060208101813564010000000081111561058857600080fd5b82018360208201111561059a57600080fd5b803590602001918460018302840111640100000000831117156105bc57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561060f57600080fd5b82018360208201111561062157600080fd5b8035906020019184600183028401116401000000008311171561064357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561069657600080fd5b8201836020820111156106a857600080fd5b803590602001918460018302840111640100000000831117156106ca57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813560ff16925050602081013573ffffffffffffffffffffffffffffffffffffffff908116916040810135821691606082013581169160800135166116f4565b61074a611a36565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61040d611a52565b61074a611a58565b6103f16004803603604081101561079957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611a74565b610452611bf6565b6103f1600480360360408110156107da57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611cb9565b6104526004803603602081101561081357600080fd5b50356120ee565b6103f16004803603604081101561083057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356123a8565b61034361253b565b6104526004803603602081101561087157600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612572565b610452600480360360a08110156108a457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060208101359060ff60408201351690606081013590608001356126d9565b6103f1612777565b61040d600480360360208110156108fa57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612798565b61040d6004803603602081101561092d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166127c0565b61040d6127e8565b61045261280c565b61040d6004803603602081101561097057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166128e6565b61074a61290e565b61034361292a565b61074a6129a3565b61040d6129bf565b6103f1600480360360408110156109c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356129e3565b6103f1600480360360408110156109fc57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612b65565b61045260048036036020811015610a3557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612ce7565b6103f160048036036020811015610a6857600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612e4e565b61045260048036036020811015610a9b57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612e79565b61045260048036036060811015610ace57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135612fe0565b61074a613076565b610452600480360360e0811015610b1957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135613092565b61045260048036036020811015610b7757600080fd5b810190602081018135640100000000811115610b9257600080fd5b820183602082011115610ba457600080fd5b80359060200191846001830284011164010000000083111715610bc657600080fd5b509092509050613238565b61040d613321565b61040d60048036036040811015610bef57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516613345565b6104526004803603610120811015610c2b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e081013590610100013561337d565b610343613527565b6103f160048036036040811015610c9e57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356135a0565b6104526004803603610120811015610cd857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e08101359061010001356135d8565b61045260048036036020811015610d4357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613775565b61045260048036036020811015610d7657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166138c8565b6103f160048036036020811015610da957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166139af565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b820191906000526020600020905b815481529060010190602001808311610e4d57829003601f168201915b505050505081565b60015460009074010000000000000000000000000000000000000000900460ff1615610eff57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615610f68576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615610fe9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff43386866139da565b506001949350505050565b600b5490565b60025473ffffffffffffffffffffffffffffffffffffffff163314611075576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061506b602c913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f117e3210bb9aa7d9baff172026820255c6f6c30ba8999d1c2fd88e2848137c4e9190a250565b60015460009074010000000000000000000000000000000000000000900460ff161561117657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff16156111df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040902054859060ff1615611260576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040902054859060ff16156112e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a6020908152604080832033845290915290205485111561136a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806151316028913960400191505060405180910390fd5b611375878787613b21565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a602090815260408083203384529091529020546113b09086613d4c565b73ffffffffffffffffffffffffffffffffffffffff88166000908152600a60209081526040808320338452909152902055600193505050509392505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461147557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166114e1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180614fc9602a913960400191505060405180910390fd5b600e80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fe475e580d85111348e40d8ca33cfdd74c30fe1655c2d8537a13abc10065ffa5a90600090a250565b60125460ff1660011461156257600080fd5b30600090815260096020526040902054801561158357611583308383613b21565b505030600090815260036020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00908116600117909155601280549091166002179055565b60085460009073ffffffffffffffffffffffffffffffffffffffff163314611641576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806150426029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600d909152808220829055517fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb666929190a2506001919050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60065460ff1681565b60085474010000000000000000000000000000000000000000900460ff1615611768576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806151ac602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166117d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806150de602f913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316611840576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614fa06029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166118ac576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615159602e913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611918576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806152bf6028913960400191505060405180910390fd5b875161192b9060049060208b0190614d50565b50865161193f9060059060208a0190614d50565b508551611953906007906020890190614d50565b50600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8716179055600880547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff87811691909117909255600180548216868416179055600280549091169184169190911790556119ed81613d95565b5050600880547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055505050505050565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b600f5481565b600e5473ffffffffffffffffffffffffffffffffffffffff1690565b60015460009074010000000000000000000000000000000000000000900460ff1615611b0157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615611b6a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615611beb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686613ddc565b60015473ffffffffffffffffffffffffffffffffffffffff163314611c66576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061524d6022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b3390600090a1565b60015460009074010000000000000000000000000000000000000000900460ff1615611d4657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16611dae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806150bd6021913960400191505060405180910390fd5b3360008181526003602052604090205460ff1615611e17576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615611e98576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516611f04576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614f0f6023913960400191505060405180910390fd5b60008411611f5d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614ff36029913960400191505060405180910390fd5b336000908152600d602052604090205480851115611fc6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e81526020018061521f602e913960400191505060405180910390fd5b600b54611fd39086613e26565b600b5573ffffffffffffffffffffffffffffffffffffffff86166000908152600960205260409020546120069086613e26565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600960205260409020556120368186613d4c565b336000818152600d6020908152604091829020939093558051888152905173ffffffffffffffffffffffffffffffffffffffff8a16937fab8530f87dc9b59234c4623bf917212bb2536d647574c8e7e5da92c2ede0c9f8928290030190a360408051868152905173ffffffffffffffffffffffffffffffffffffffff8816916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a350600195945050505050565b60015474010000000000000000000000000000000000000000900460ff161561217857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff166121e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806150bd6021913960400191505060405180910390fd5b3360008181526003602052604090205460ff1615612249576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b33600090815260096020526040902054826122af576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614ee66029913960400191505060405180910390fd5b82811015612308576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806150976026913960400191505060405180910390fd5b600b546123159084613d4c565b600b556123228184613d4c565b33600081815260096020908152604091829020939093558051868152905191927fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca592918290030190a260408051848152905160009133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60015460009074010000000000000000000000000000000000000000900460ff161561243557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b60085473ffffffffffffffffffffffffffffffffffffffff1633146124a5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806150426029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055600d825291829020859055815185815291517f46980fca912ef9bcdbd36877427b6b90e860769f604e89c0e67720cece530d209281900390910190a250600192915050565b60408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015290565b60005473ffffffffffffffffffffffffffffffffffffffff1633146125f857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612664576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180614e936028913960400191505060405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a60490600090a250565b60015474010000000000000000000000000000000000000000900460ff161561276357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6127708585858585613e9a565b5050505050565b60015474010000000000000000000000000000000000000000900460ff1681565b73ffffffffffffffffffffffffffffffffffffffff1660009081526009602052604090205490565b73ffffffffffffffffffffffffffffffffffffffff1660009081526011602052604090205490565b7fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de881565b60015473ffffffffffffffffffffffffffffffffffffffff16331461287c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061524d6022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff62590600090a1565b73ffffffffffffffffffffffffffffffffffffffff166000908152600d602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b7f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226781565b60015460009074010000000000000000000000000000000000000000900460ff1615612a7057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615612ad9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615612b5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686614023565b60015460009074010000000000000000000000000000000000000000900460ff1615612bf257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615612c5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615612cdc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686613b21565b60005473ffffffffffffffffffffffffffffffffffffffff163314612d6d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612dd9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806150de602f913960400191505060405180910390fd5b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fdb66dfa9c6b8f5226fe9aac7e51897ae8ee94ac31dc70bb6c9900b2574b707e690600090a250565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602052604090205460ff1690565b60005473ffffffffffffffffffffffffffffffffffffffff163314612eff57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806153156032913960400191505060405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fc67398012c111ce95ecb7429b933096c977380ee6c421175a71a4a4c6c88c06e90600090a250565b600e5473ffffffffffffffffffffffffffffffffffffffff163314613050576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602481526020018061510d6024913960400191505060405180910390fd5b61307173ffffffffffffffffffffffffffffffffffffffff8416838361407f565b505050565b60025473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff161561311c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8716600090815260036020526040902054879060ff161561319d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8716600090815260036020526040902054879060ff161561321e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61322d8989898989898961410c565b505050505050505050565b60085474010000000000000000000000000000000000000000900460ff168015613265575060125460ff16155b61326e57600080fd5b61327a60048383614dce565b506132ef82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015291506142b59050565b600f555050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742981565b73ffffffffffffffffffffffffffffffffffffffff9182166000908152600a6020908152604080832093909416825291909152205490565b60015474010000000000000000000000000000000000000000900460ff161561340757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613488576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613509576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61351a8b8b8b8b8b8b8b8b8b614327565b5050505050505050505050565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152601060209081526040808320938352929052205460ff1690565b60015474010000000000000000000000000000000000000000900460ff161561366257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff16156136e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613764576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61351a8b8b8b8b8b8b8b8b8b614469565b60005473ffffffffffffffffffffffffffffffffffffffff1633146137fb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613867576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180614f586026913960400191505060405180910390fd5b6000546040805173ffffffffffffffffffffffffffffffffffffffff9283168152918316602083015280517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09281900390910190a16138c581613d95565b50565b60025473ffffffffffffffffffffffffffffffffffffffff163314613938576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061506b602c913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517fffa4e6181777692565cf28528fc88fd1516ea86b56da075235fa575af6a4b8559190a250565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205460ff1690565b73ffffffffffffffffffffffffffffffffffffffff8316613a46576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806151fb6024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613ab2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180614f7e6022913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8084166000818152600a6020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316613b8d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806151d66025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613bf9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614e706023913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260096020526040902054811115613c77576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061501c6026913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260096020526040902054613ca79082613d4c565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600960205260408082209390935590841681522054613ce39082613e26565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526009602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000613d8e83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250614576565b9392505050565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600a60209081526040808320938616835292905220546130719084908490613e219085613e26565b6139da565b600082820183811015613d8e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b613ea48585614627565b604080517f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429602082015273ffffffffffffffffffffffffffffffffffffffff87168183018190526060828101889052835180840390910181526080909201909252600f54909190613f1890868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff1614613f9a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8616600081815260106020908152604080832089845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518792917f1cdd46ff242716cdaa72d159d339a485b3438398348d68f09d7c8c0a59353d8191a3505050505050565b6130718383613e21846040518060600160405280602581526020016153916025913973ffffffffffffffffffffffffffffffffffffffff808a166000908152600a60209081526040808320938c16835292905220549190614576565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052613071908490614727565b4284101561417b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a207065726d697420697320657870697265640000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff80881660008181526011602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c992810192909252818301849052938a1660608201526080810189905260a081019390935260c08084018890528151808503909101815260e09093019052600f5461421e90868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff16146142a057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f454950323631323a20696e76616c6964207369676e6174757265000000000000604482015290519081900360640190fd5b6142ab8888886139da565b5050505050505050565b8151602092830120815191830191909120604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818601528082019390935260608301919091524660808301523060a0808401919091528151808403909101815260c09092019052805191012090565b614333898588886147ff565b604080517f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267602082015273ffffffffffffffffffffffffffffffffffffffff808c16828401819052908b166060830152608082018a905260a0820189905260c0820188905260e080830188905283518084039091018152610100909201909252600f549091906143c690868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff161461444857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b6144528a866148bf565b61445d8a8a8a613b21565b50505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff881633146144d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806151876025913960400191505060405180910390fd5b6144e3898588886147ff565b604080517fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8602082015273ffffffffffffffffffffffffffffffffffffffff808c16828401819052908b166060830152608082018a905260a0820189905260c0820188905260e080830188905283518084039091018152610100909201909252600f549091906143c690868686866146b5565b6000818484111561461f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156145e45781810151838201526020016145cc565b50505050905090810190601f1680156146115780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b73ffffffffffffffffffffffffffffffffffffffff8216600090815260106020908152604080832084845290915290205460ff16156146b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e8152602001806152e7602e913960400191505060405180910390fd5b5050565b8051602080830191909120604080517f19010000000000000000000000000000000000000000000000000000000000008185015260228101899052604280820193909352815180820390930183526062019052805191012060009061471c81878787614944565b979650505050505050565b6060614789826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16614b419092919063ffffffff16565b805190915015613071578080602001905160208110156147a857600080fd5b5051613071576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615295602a913960400191505060405180910390fd5b814211614857576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180614ebb602b913960400191505060405180910390fd5b8042106148af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061536c6025913960400191505060405180910390fd5b6148b98484614627565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8216600081815260106020908152604080832085845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518392917f98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a591a35050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156149bf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061526f6026913960400191505060405180910390fd5b8360ff16601b141580156149d757508360ff16601c14155b15614a2d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180614f326026913960400191505060405180910390fd5b600060018686868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015614a89573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116614b3657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f45435265636f7665723a20696e76616c6964207369676e617475726500000000604482015290519081900360640190fd5b90505b949350505050565b6060614b3984846000856060614b5685614d17565b614bc157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b60208310614c2b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614bee565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114614c8d576040519150601f19603f3d011682016040523d82523d6000602084013e614c92565b606091505b50915091508115614ca6579150614b399050565b805115614cb65780518082602001fd5b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482018181528651602484015286518793919283926044019190850190808383600083156145e45781810151838201526020016145cc565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590614b39575050151592915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10614d9157805160ff1916838001178555614dbe565b82800160010185558215614dbe579182015b82811115614dbe578251825591602001919060010190614da3565b50614dca929150614e5a565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10614e2d578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00823516178555614dbe565b82800160010185558215614dbe579182015b82811115614dbe578235825591602001919060010190614e3f565b5b80821115614dca5760008155600101614e5b56fe45524332303a207472616e7366657220746f20746865207a65726f20616464726573735061757361626c653a206e65772070617573657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e206973206e6f74207965742076616c696446696174546f6b656e3a206275726e20616d6f756e74206e6f742067726561746572207468616e203046696174546f6b656e3a206d696e7420746f20746865207a65726f206164647265737345435265636f7665723a20696e76616c6964207369676e6174757265202776272076616c75654f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737346696174546f6b656e3a206e65772070617573657220697320746865207a65726f2061646472657373526573637561626c653a206e6577207265736375657220697320746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e74206e6f742067726561746572207468616e203045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f7420746865206d61737465724d696e746572426c61636b6c69737461626c653a2063616c6c6572206973206e6f742074686520626c61636b6c697374657246696174546f6b656e3a206275726e20616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f742061206d696e74657246696174546f6b656e3a206e6577206d61737465724d696e74657220697320746865207a65726f2061646472657373526573637561626c653a2063616c6c6572206973206e6f7420746865207265736375657245524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636546696174546f6b656e3a206e657720626c61636b6c697374657220697320746865207a65726f206164647265737346696174546f6b656e56323a2063616c6c6572206d7573742062652074686520706179656546696174546f6b656e3a20636f6e747261637420697320616c726561647920696e697469616c697a656445524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e742065786365656473206d696e746572416c6c6f77616e63655061757361626c653a2063616c6c6572206973206e6f74207468652070617573657245435265636f7665723a20696e76616c6964207369676e6174757265202773272076616c75655361666545524332303a204552433230206f7065726174696f6e20646964206e6f74207375636365656446696174546f6b656e3a206e6577206f776e657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e2069732075736564206f722063616e63656c6564426c61636b6c69737461626c653a206e657720626c61636b6c697374657220697320746865207a65726f2061646472657373426c61636b6c69737461626c653a206163636f756e7420697320626c61636b6c697374656446696174546f6b656e56323a20617574686f72697a6174696f6e206973206578706972656445524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212203ccc7055acd0820f4e7d094fa75ff2d3b0c27bdd446079aeef6bd6d02c56938e64736f6c634300060c0033", - "sourceMap": "1362:764:41:-:0;;;2106:26:35;;;-1:-1:-1;2106:26:35;;;-1:-1:-1;1940:33:33;;1362:764:41;;;;;;;;;-1:-1:-1;2223:20:34;2232:10;2223:8;:20::i;:::-;1362:764:41;;2493:79:34;2548:6;:17;;-1:-1:-1;2548:17:34;-1:-1:-1;2548:17:34;;;;;;;;;;2493:79::o;1362:764:41:-;;;;;;;", - "linkReferences": {} - }, - "deployedBytecode": { - "object": "0x608060405234801561001057600080fd5b50600436106103365760003560e01c80637f2eecc3116101b2578063b2118a8d116100f9578063e3ee160e116100a2578063ef55bec61161007c578063ef55bec614610cc1578063f2fde38b14610d2d578063f9f92be414610d60578063fe575a8714610d9357610336565b8063e3ee160e14610c14578063e5a6b10f14610c80578063e94a010214610c8857610336565b8063d608ea64116100d3578063d608ea6414610b61578063d916948714610bd1578063dd62ed3e14610bd957610336565b8063b2118a8d14610ab8578063bd10243014610afb578063d505accf14610b0357610336565b8063a0cc6a681161015b578063aa20e1e411610135578063aa20e1e414610a1f578063aa271e1a14610a52578063ad38bf2214610a8557610336565b8063a0cc6a68146109a5578063a457c2d7146109ad578063a9059cbb146109e657610336565b80638da5cb5b1161018c5780638da5cb5b1461098d57806395d89b41146109955780639fd0506d1461099d57610336565b80637f2eecc31461094a5780638456cb59146109525780638a6db9c31461095a57610336565b80633644e515116102815780634e44d9561161022a5780635a049a70116102045780635a049a701461088e5780635c975abb146108dc57806370a08231146108e45780637ecebe001461091757610336565b80634e44d9561461081a57806354fd4d5014610853578063554bab3c1461085b57610336565b80633f4ba83a1161025b5780633f4ba83a146107bc57806340c10f19146107c457806342966c68146107fd57610336565b80633644e5151461077357806338a631831461077b578063395093511461078357610336565b80632fc81e09116102e3578063313ce567116102bd578063313ce567146105385780633357162b1461055657806335d99f351461074257610336565b80632fc81e09146104ca5780633092afd5146104fd57806330adf81f1461053057610336565b80631a895266116103145780631a8952661461041f57806323b872dd146104545780632ab600451461049757610336565b806306fdde031461033b578063095ea7b3146103b857806318160ddd14610405575b600080fd5b610343610dc6565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561037d578181015183820152602001610365565b50505050905090810190601f1680156103aa5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6103f1600480360360408110156103ce57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135610e72565b604080519115158252519081900360200190f35b61040d610fff565b60408051918252519081900360200190f35b6104526004803603602081101561043557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611005565b005b6103f16004803603606081101561046a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356110e9565b610452600480360360208110156104ad57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166113ef565b610452600480360360208110156104e057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611550565b6103f16004803603602081101561051357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166115ce565b61040d6116c7565b6105406116eb565b6040805160ff9092168252519081900360200190f35b610452600480360361010081101561056d57600080fd5b81019060208101813564010000000081111561058857600080fd5b82018360208201111561059a57600080fd5b803590602001918460018302840111640100000000831117156105bc57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561060f57600080fd5b82018360208201111561062157600080fd5b8035906020019184600183028401116401000000008311171561064357600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561069657600080fd5b8201836020820111156106a857600080fd5b803590602001918460018302840111640100000000831117156106ca57600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813560ff16925050602081013573ffffffffffffffffffffffffffffffffffffffff908116916040810135821691606082013581169160800135166116f4565b61074a611a36565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b61040d611a52565b61074a611a58565b6103f16004803603604081101561079957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611a74565b610452611bf6565b6103f1600480360360408110156107da57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611cb9565b6104526004803603602081101561081357600080fd5b50356120ee565b6103f16004803603604081101561083057600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356123a8565b61034361253b565b6104526004803603602081101561087157600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612572565b610452600480360360a08110156108a457600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060208101359060ff60408201351690606081013590608001356126d9565b6103f1612777565b61040d600480360360208110156108fa57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612798565b61040d6004803603602081101561092d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166127c0565b61040d6127e8565b61045261280c565b61040d6004803603602081101561097057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166128e6565b61074a61290e565b61034361292a565b61074a6129a3565b61040d6129bf565b6103f1600480360360408110156109c357600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356129e3565b6103f1600480360360408110156109fc57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612b65565b61045260048036036020811015610a3557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612ce7565b6103f160048036036020811015610a6857600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612e4e565b61045260048036036020811015610a9b57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612e79565b61045260048036036060811015610ace57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060400135612fe0565b61074a613076565b610452600480360360e0811015610b1957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135613092565b61045260048036036020811015610b7757600080fd5b810190602081018135640100000000811115610b9257600080fd5b820183602082011115610ba457600080fd5b80359060200191846001830284011164010000000083111715610bc657600080fd5b509092509050613238565b61040d613321565b61040d60048036036040811015610bef57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020013516613345565b6104526004803603610120811015610c2b57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e081013590610100013561337d565b610343613527565b6103f160048036036040811015610c9e57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356135a0565b6104526004803603610120811015610cd857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e08101359061010001356135d8565b61045260048036036020811015610d4357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613775565b61045260048036036020811015610d7657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166138c8565b6103f160048036036020811015610da957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166139af565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b820191906000526020600020905b815481529060010190602001808311610e4d57829003601f168201915b505050505081565b60015460009074010000000000000000000000000000000000000000900460ff1615610eff57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615610f68576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615610fe9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff43386866139da565b506001949350505050565b600b5490565b60025473ffffffffffffffffffffffffffffffffffffffff163314611075576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061506b602c913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055517f117e3210bb9aa7d9baff172026820255c6f6c30ba8999d1c2fd88e2848137c4e9190a250565b60015460009074010000000000000000000000000000000000000000900460ff161561117657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff16156111df576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040902054859060ff1615611260576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516600090815260036020526040902054859060ff16156112e1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a6020908152604080832033845290915290205485111561136a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806151316028913960400191505060405180910390fd5b611375878787613b21565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600a602090815260408083203384529091529020546113b09086613d4c565b73ffffffffffffffffffffffffffffffffffffffff88166000908152600a60209081526040808320338452909152902055600193505050509392505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461147557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166114e1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180614fc9602a913960400191505060405180910390fd5b600e80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fe475e580d85111348e40d8ca33cfdd74c30fe1655c2d8537a13abc10065ffa5a90600090a250565b60125460ff1660011461156257600080fd5b30600090815260096020526040902054801561158357611583308383613b21565b505030600090815260036020526040902080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00908116600117909155601280549091166002179055565b60085460009073ffffffffffffffffffffffffffffffffffffffff163314611641576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806150426029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600d909152808220829055517fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb666929190a2506001919050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60065460ff1681565b60085474010000000000000000000000000000000000000000900460ff1615611768576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a8152602001806151ac602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff84166117d4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806150de602f913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316611840576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614fa06029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166118ac576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615159602e913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611918576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806152bf6028913960400191505060405180910390fd5b875161192b9060049060208b0190614d50565b50865161193f9060059060208a0190614d50565b508551611953906007906020890190614d50565b50600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8716179055600880547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff87811691909117909255600180548216868416179055600280549091169184169190911790556119ed81613d95565b5050600880547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055505050505050565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b600f5481565b600e5473ffffffffffffffffffffffffffffffffffffffff1690565b60015460009074010000000000000000000000000000000000000000900460ff1615611b0157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615611b6a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615611beb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686613ddc565b60015473ffffffffffffffffffffffffffffffffffffffff163314611c66576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061524d6022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b3390600090a1565b60015460009074010000000000000000000000000000000000000000900460ff1615611d4657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff16611dae576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806150bd6021913960400191505060405180910390fd5b3360008181526003602052604090205460ff1615611e17576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615611e98576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8516611f04576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614f0f6023913960400191505060405180910390fd5b60008411611f5d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614ff36029913960400191505060405180910390fd5b336000908152600d602052604090205480851115611fc6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e81526020018061521f602e913960400191505060405180910390fd5b600b54611fd39086613e26565b600b5573ffffffffffffffffffffffffffffffffffffffff86166000908152600960205260409020546120069086613e26565b73ffffffffffffffffffffffffffffffffffffffff87166000908152600960205260409020556120368186613d4c565b336000818152600d6020908152604091829020939093558051888152905173ffffffffffffffffffffffffffffffffffffffff8a16937fab8530f87dc9b59234c4623bf917212bb2536d647574c8e7e5da92c2ede0c9f8928290030190a360408051868152905173ffffffffffffffffffffffffffffffffffffffff8816916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a350600195945050505050565b60015474010000000000000000000000000000000000000000900460ff161561217857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336000908152600c602052604090205460ff166121e0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806150bd6021913960400191505060405180910390fd5b3360008181526003602052604090205460ff1615612249576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b33600090815260096020526040902054826122af576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526029815260200180614ee66029913960400191505060405180910390fd5b82811015612308576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806150976026913960400191505060405180910390fd5b600b546123159084613d4c565b600b556123228184613d4c565b33600081815260096020908152604091829020939093558051868152905191927fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca592918290030190a260408051848152905160009133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3505050565b60015460009074010000000000000000000000000000000000000000900460ff161561243557604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b60085473ffffffffffffffffffffffffffffffffffffffff1633146124a5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806150426029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055600d825291829020859055815185815291517f46980fca912ef9bcdbd36877427b6b90e860769f604e89c0e67720cece530d209281900390910190a250600192915050565b60408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015290565b60005473ffffffffffffffffffffffffffffffffffffffff1633146125f857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612664576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180614e936028913960400191505060405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a60490600090a250565b60015474010000000000000000000000000000000000000000900460ff161561276357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6127708585858585613e9a565b5050505050565b60015474010000000000000000000000000000000000000000900460ff1681565b73ffffffffffffffffffffffffffffffffffffffff1660009081526009602052604090205490565b73ffffffffffffffffffffffffffffffffffffffff1660009081526011602052604090205490565b7fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de881565b60015473ffffffffffffffffffffffffffffffffffffffff16331461287c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602281526020018061524d6022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff62590600090a1565b73ffffffffffffffffffffffffffffffffffffffff166000908152600d602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b7f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226781565b60015460009074010000000000000000000000000000000000000000900460ff1615612a7057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615612ad9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615612b5a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686614023565b60015460009074010000000000000000000000000000000000000000900460ff1615612bf257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b3360008181526003602052604090205460ff1615612c5b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416600090815260036020526040902054849060ff1615612cdc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b610ff4338686613b21565b60005473ffffffffffffffffffffffffffffffffffffffff163314612d6d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612dd9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f8152602001806150de602f913960400191505060405180910390fd5b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fdb66dfa9c6b8f5226fe9aac7e51897ae8ee94ac31dc70bb6c9900b2574b707e690600090a250565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602052604090205460ff1690565b60005473ffffffffffffffffffffffffffffffffffffffff163314612eff57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612f6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260328152602001806153156032913960400191505060405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fc67398012c111ce95ecb7429b933096c977380ee6c421175a71a4a4c6c88c06e90600090a250565b600e5473ffffffffffffffffffffffffffffffffffffffff163314613050576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602481526020018061510d6024913960400191505060405180910390fd5b61307173ffffffffffffffffffffffffffffffffffffffff8416838361407f565b505050565b60025473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff161561311c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8716600090815260036020526040902054879060ff161561319d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8716600090815260036020526040902054879060ff161561321e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61322d8989898989898961410c565b505050505050505050565b60085474010000000000000000000000000000000000000000900460ff168015613265575060125460ff16155b61326e57600080fd5b61327a60048383614dce565b506132ef82828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015291506142b59050565b600f555050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742981565b73ffffffffffffffffffffffffffffffffffffffff9182166000908152600a6020908152604080832093909416825291909152205490565b60015474010000000000000000000000000000000000000000900460ff161561340757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613488576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613509576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61351a8b8b8b8b8b8b8b8b8b614327565b5050505050505050505050565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f81018490048402820184019092528181529291830182828015610e6a5780601f10610e3f57610100808354040283529160200191610e6a565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152601060209081526040808320938352929052205460ff1690565b60015474010000000000000000000000000000000000000000900460ff161561366257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff16156136e3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8916600090815260036020526040902054899060ff1615613764576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806153476025913960400191505060405180910390fd5b61351a8b8b8b8b8b8b8b8b8b614469565b60005473ffffffffffffffffffffffffffffffffffffffff1633146137fb57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613867576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180614f586026913960400191505060405180910390fd5b6000546040805173ffffffffffffffffffffffffffffffffffffffff9283168152918316602083015280517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09281900390910190a16138c581613d95565b50565b60025473ffffffffffffffffffffffffffffffffffffffff163314613938576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c81526020018061506b602c913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff811660008181526003602052604080822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055517fffa4e6181777692565cf28528fc88fd1516ea86b56da075235fa575af6a4b8559190a250565b73ffffffffffffffffffffffffffffffffffffffff1660009081526003602052604090205460ff1690565b73ffffffffffffffffffffffffffffffffffffffff8316613a46576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806151fb6024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613ab2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180614f7e6022913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8084166000818152600a6020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b73ffffffffffffffffffffffffffffffffffffffff8316613b8d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806151d66025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613bf9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180614e706023913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260096020526040902054811115613c77576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061501c6026913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316600090815260096020526040902054613ca79082613d4c565b73ffffffffffffffffffffffffffffffffffffffff8085166000908152600960205260408082209390935590841681522054613ce39082613e26565b73ffffffffffffffffffffffffffffffffffffffff80841660008181526009602090815260409182902094909455805185815290519193928716927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef92918290030190a3505050565b6000613d8e83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250614576565b9392505050565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600a60209081526040808320938616835292905220546130719084908490613e219085613e26565b6139da565b600082820183811015613d8e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b613ea48585614627565b604080517f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a1597429602082015273ffffffffffffffffffffffffffffffffffffffff87168183018190526060828101889052835180840390910181526080909201909252600f54909190613f1890868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff1614613f9a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8616600081815260106020908152604080832089845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518792917f1cdd46ff242716cdaa72d159d339a485b3438398348d68f09d7c8c0a59353d8191a3505050505050565b6130718383613e21846040518060600160405280602581526020016153916025913973ffffffffffffffffffffffffffffffffffffffff808a166000908152600a60209081526040808320938c16835292905220549190614576565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052613071908490614727565b4284101561417b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a207065726d697420697320657870697265640000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff80881660008181526011602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c992810192909252818301849052938a1660608201526080810189905260a081019390935260c08084018890528151808503909101815260e09093019052600f5461421e90868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff16146142a057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f454950323631323a20696e76616c6964207369676e6174757265000000000000604482015290519081900360640190fd5b6142ab8888886139da565b5050505050505050565b8151602092830120815191830191909120604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f818601528082019390935260608301919091524660808301523060a0808401919091528151808403909101815260c09092019052805191012090565b614333898588886147ff565b604080517f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a2267602082015273ffffffffffffffffffffffffffffffffffffffff808c16828401819052908b166060830152608082018a905260a0820189905260c0820188905260e080830188905283518084039091018152610100909201909252600f549091906143c690868686866146b5565b73ffffffffffffffffffffffffffffffffffffffff161461444857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b6144528a866148bf565b61445d8a8a8a613b21565b50505050505050505050565b73ffffffffffffffffffffffffffffffffffffffff881633146144d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260258152602001806151876025913960400191505060405180910390fd5b6144e3898588886147ff565b604080517fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de8602082015273ffffffffffffffffffffffffffffffffffffffff808c16828401819052908b166060830152608082018a905260a0820189905260c0820188905260e080830188905283518084039091018152610100909201909252600f549091906143c690868686866146b5565b6000818484111561461f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156145e45781810151838201526020016145cc565b50505050905090810190601f1680156146115780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b73ffffffffffffffffffffffffffffffffffffffff8216600090815260106020908152604080832084845290915290205460ff16156146b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e8152602001806152e7602e913960400191505060405180910390fd5b5050565b8051602080830191909120604080517f19010000000000000000000000000000000000000000000000000000000000008185015260228101899052604280820193909352815180820390930183526062019052805191012060009061471c81878787614944565b979650505050505050565b6060614789826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff16614b419092919063ffffffff16565b805190915015613071578080602001905160208110156147a857600080fd5b5051613071576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615295602a913960400191505060405180910390fd5b814211614857576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180614ebb602b913960400191505060405180910390fd5b8042106148af576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602581526020018061536c6025913960400191505060405180910390fd5b6148b98484614627565b50505050565b73ffffffffffffffffffffffffffffffffffffffff8216600081815260106020908152604080832085845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518392917f98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a591a35050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156149bf576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061526f6026913960400191505060405180910390fd5b8360ff16601b141580156149d757508360ff16601c14155b15614a2d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526026815260200180614f326026913960400191505060405180910390fd5b600060018686868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015614a89573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff8116614b3657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f45435265636f7665723a20696e76616c6964207369676e617475726500000000604482015290519081900360640190fd5b90505b949350505050565b6060614b3984846000856060614b5685614d17565b614bc157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b60208310614c2b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09092019160209182019101614bee565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d8060008114614c8d576040519150601f19603f3d011682016040523d82523d6000602084013e614c92565b606091505b50915091508115614ca6579150614b399050565b805115614cb65780518082602001fd5b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482018181528651602484015286518793919283926044019190850190808383600083156145e45781810151838201526020016145cc565b6000813f7fc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470818114801590614b39575050151592915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10614d9157805160ff1916838001178555614dbe565b82800160010185558215614dbe579182015b82811115614dbe578251825591602001919060010190614da3565b50614dca929150614e5a565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10614e2d578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00823516178555614dbe565b82800160010185558215614dbe579182015b82811115614dbe578235825591602001919060010190614e3f565b5b80821115614dca5760008155600101614e5b56fe45524332303a207472616e7366657220746f20746865207a65726f20616464726573735061757361626c653a206e65772070617573657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e206973206e6f74207965742076616c696446696174546f6b656e3a206275726e20616d6f756e74206e6f742067726561746572207468616e203046696174546f6b656e3a206d696e7420746f20746865207a65726f206164647265737345435265636f7665723a20696e76616c6964207369676e6174757265202776272076616c75654f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737346696174546f6b656e3a206e65772070617573657220697320746865207a65726f2061646472657373526573637561626c653a206e6577207265736375657220697320746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e74206e6f742067726561746572207468616e203045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f7420746865206d61737465724d696e746572426c61636b6c69737461626c653a2063616c6c6572206973206e6f742074686520626c61636b6c697374657246696174546f6b656e3a206275726e20616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f742061206d696e74657246696174546f6b656e3a206e6577206d61737465724d696e74657220697320746865207a65726f2061646472657373526573637561626c653a2063616c6c6572206973206e6f7420746865207265736375657245524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636546696174546f6b656e3a206e657720626c61636b6c697374657220697320746865207a65726f206164647265737346696174546f6b656e56323a2063616c6c6572206d7573742062652074686520706179656546696174546f6b656e3a20636f6e747261637420697320616c726561647920696e697469616c697a656445524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e742065786365656473206d696e746572416c6c6f77616e63655061757361626c653a2063616c6c6572206973206e6f74207468652070617573657245435265636f7665723a20696e76616c6964207369676e6174757265202773272076616c75655361666545524332303a204552433230206f7065726174696f6e20646964206e6f74207375636365656446696174546f6b656e3a206e6577206f776e657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e2069732075736564206f722063616e63656c6564426c61636b6c69737461626c653a206e657720626c61636b6c697374657220697320746865207a65726f2061646472657373426c61636b6c69737461626c653a206163636f756e7420697320626c61636b6c697374656446696174546f6b656e56323a20617574686f72697a6174696f6e206973206578706972656445524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa26469706673582212203ccc7055acd0820f4e7d094fa75ff2d3b0c27bdd446079aeef6bd6d02c56938e64736f6c634300060c0033", - "sourceMap": "1362:764:41:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1649:18:33;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6725:273;;;;;;;;;;;;;;;;-1:-1:-1;6725:273:33;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;6132:100;;;:::i;:::-;;;;;;;;;;;;;;;;2772:148:32;;;;;;;;;;;;;;;;-1:-1:-1;2772:148:32;;;;:::i;:::-;;7776:536:33;;;;;;;;;;;;;;;;-1:-1:-1;7776:536:33;;;;;;;;;;;;;;;;;;:::i;2466:264:30:-;;;;;;;;;;;;;;;;-1:-1:-1;2466:264:30;;;;:::i;1528:398:41:-;;;;;;;;;;;;;;;;-1:-1:-1;1528:398:41;;;;:::i;10209:239:33:-;;;;;;;;;;;;;;;;-1:-1:-1;10209:239:33;;;;:::i;1612:116:37:-;;;:::i;1699:21:33:-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;2413:1160;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2413:1160:33;;;;;;;;-1:-1:-1;2413:1160:33;;-1:-1:-1;;2413:1160:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2413:1160:33;;;;;;;;-1:-1:-1;2413:1160:33;;-1:-1:-1;;2413:1160:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2413:1160:33;;-1:-1:-1;;;2413:1160:33;;;;;-1:-1:-1;;2413:1160:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;1754:27::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;;1291:31:39;;;:::i;1636:83:30:-;;;:::i;2269:284:40:-;;;;;;;;;;;;;;;;-1:-1:-1;2269:284:40;;;;;;;;;:::i;2802:94:35:-;;;:::i;4097:840:33:-;;;;;;;;;;;;;;;;-1:-1:-1;4097:840:33;;;;;;;;;:::i;10737:538::-;;;;;;;;;;;;;;;;-1:-1:-1;10737:538:33;;:::i;9703:334::-;;;;;;;;;;;;;;;;-1:-1:-1;9703:334:33;;;;;;;;;:::i;2040:84:41:-;;;:::i;2953:254:35:-;;;;;;;;;;;;;;;;-1:-1:-1;2953:254:35;;;;:::i;5782:229:40:-;;;;;;;;;;;;;;;;-1:-1:-1;5782:229:40;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;2106:26:35:-;;;:::i;6340:154:33:-;;;;;;;;;;;;;;;;-1:-1:-1;6340:154:33;;;;:::i;1921:107:37:-;;;;;;;;;;;;;;;;-1:-1:-1;1921:107:37;;;;:::i;2068:136:38:-;;;:::i;2623:89:35:-;;;:::i;5318:118:33:-;;;;;;;;;;;;;;;;-1:-1:-1;5318:118:33;;;;:::i;2355:79:34:-;;;:::i;1673:20:33:-;;;:::i;2079:21:35:-;;;:::i;1787:137:38:-;;;:::i;2766:284:40:-;;;;;;;;;;;;;;;;-1:-1:-1;2766:284:40;;;;;;;;;:::i;8487:260:33:-;;;;;;;;;;;;;;;;-1:-1:-1;8487:260:33;;;;;;;;;:::i;11281:303::-;;;;;;;;;;;;;;;;-1:-1:-1;11281:303:33;;;;:::i;5543:104::-;;;;;;;;;;;;;;;;-1:-1:-1;5543:104:33;;;;:::i;2926:299:32:-;;;;;;;;;;;;;;;;-1:-1:-1;2926:299:32;;;;:::i;2161:177:30:-;;;;;;;;;;;;;;;;-1:-1:-1;2161:177:30;;;;;;;;;;;;;;;;;;:::i;1365:26:32:-;;;:::i;6439:309:40:-;;;;;;;;;;;;;;;;-1:-1:-1;6439:309:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;1758:298::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1758:298:40;;-1:-1:-1;1758:298:40;-1:-1:-1;1758:298:40;:::i;2285:130:38:-;;;:::i;5898:175:33:-;;;;;;;;;;;;;;;;-1:-1:-1;5898:175:33;;;;;;;;;;;:::i;3621:523:40:-;;;;;;;;;;;;;;;;-1:-1:-1;3621:523:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;1726:22:33:-;;;:::i;3088:183:38:-;;;;;;;;;;;;;;;;-1:-1:-1;3088:183:38;;;;;;;;;:::i;4883:521:40:-;;;;;;;;;;;;;;;;-1:-1:-1;4883:521:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;2945:269:34:-;;;;;;;;;;;;;;;;-1:-1:-1;2945:269:34;;;;:::i;2500:143:32:-;;;;;;;;;;;;;;;;-1:-1:-1;2500:143:32;;;;:::i;2277:115::-;;;;;;;;;;;;;;;;-1:-1:-1;2277:115:32;;;;:::i;1649:18:33:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;6725:273::-;2286:6:35;;6914:4:33;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6853:10:33::1;2064:21:32;::::0;;;:11:::1;:21;::::0;;;;;::::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;6888:7:33;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6934:36:33::3;6943:10;6955:7;6964:5;6934:8;:36::i;:::-;-1:-1:-1::0;6987:4:33::3;::::0;6725:273;-1:-1:-1;;;;6725:273:33:o;6132:100::-;6213:12;;6132:100;:::o;2772:148:32:-;1771:11;;;;1757:10;:25;1736:116;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2846:21:::1;::::0;::::1;2870:5;2846:21:::0;;;:11:::1;:21;::::0;;;;;:29;;;::::1;::::0;;2890:23;::::1;::::0;2870:5;2890:23:::1;2772:148:::0;:::o;7776:536:33:-;2286:6:35;;8033:4:33;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7948:10:33::1;2064:21:32;::::0;;;:11:::1;:21;::::0;;;;;::::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;7983:4:33;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::3;::::0;::::3;;::::0;;;:11:::3;:21;::::0;;;;;8012:2:33;;2064:21:32::3;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8083:13:33::4;::::0;::::4;;::::0;;;:7:::4;:13;::::0;;;;;;;8097:10:::4;8083:25:::0;;;;;;;;8074:34;::::4;;8053:121;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8184:26;8194:4;8200:2;8204:5;8184:9;:26::i;:::-;8248:13;::::0;::::4;;::::0;;;:7:::4;:13;::::0;;;;;;;8262:10:::4;8248:25:::0;;;;;;;;:36:::4;::::0;8278:5;8248:29:::4;:36::i;:::-;8220:13;::::0;::::4;;::::0;;;:7:::4;:13;::::0;;;;;;;8234:10:::4;8220:25:::0;;;;;;;:64;8301:4:::4;::::0;-1:-1:-1;2158:1:32::3;::::2;2323::35::1;7776:536:33::0;;;;;:::o;2466:264:30:-;2713:6:34;;;;2699:10;:20;2691:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2559:24:30::1;::::0;::::1;2538:113;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2661:8;:21:::0;;;::::1;;::::0;::::1;::::0;;::::1;::::0;;;2697:26:::1;::::0;::::1;::::0;-1:-1:-1;;2697:26:30::1;2466:264:::0;:::o;1528:398:41:-;1652:19;;;;;:24;1644:33;;;;;;1728:4;1688:20;1711:23;;;:8;:23;;;;;;1748:16;;1744:99;;1780:52;1798:4;1805:12;1819;1780:9;:52::i;:::-;-1:-1:-1;;1872:4:41;1852:26;;;;:11;:26;;;;;:33;;;;;;1881:4;1852:33;;;;1896:19;:23;;;;;1918:1;1896:23;;;1528:398::o;10209:239:33:-;5104:12;;10306:4;;5104:12;;5090:10;:26;5069:114;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10326:15:::1;::::0;::::1;10344:5;10326:15:::0;;;:7:::1;:15;::::0;;;;;;;:23;;;::::1;::::0;;10359:13:::1;:21:::0;;;;;;:25;;;10399:21;::::1;::::0;10344:5;10399:21:::1;-1:-1:-1::0;10437:4:33::1;10209:239:::0;;;:::o;1612:116:37:-;1662:66;1612:116;:::o;1699:21:33:-;;;;;;:::o;2413:1160::-;2717:11;;;;;;;2716:12;2708:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2806:29;;;2785:123;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2939:23;;;2918:111;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3060:28;;;3039:121;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3191:22;;;3170:109;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3290:16;;;;:4;;:16;;;;;:::i;:::-;-1:-1:-1;3316:20:33;;;;:6;;:20;;;;;:::i;:::-;-1:-1:-1;3346:24:33;;;;:8;;:24;;;;;:::i;:::-;-1:-1:-1;3380:8:33;:24;;;;;;;;;;3414:12;:30;;;;;;;;;;;;;;;;;-1:-1:-1;3454:18:33;;;;;;;;;;3482:11;:28;;;;;;;;;;;;;;3520:18;3529:8;3520;:18::i;:::-;-1:-1:-1;;3548:11:33;:18;;;;;;;;-1:-1:-1;;;;;;2413:1160:33:o;1754:27::-;;;;;;:::o;1291:31:39:-;;;;:::o;1636:83:30:-;1704:8;;;;1636:83;:::o;2269:284:40:-;2286:6:35;;2455:4:40;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2394:10:40::1;2064:21:32;::::0;;;:11:::1;:21;::::0;;;;;::::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;2429:7:40;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2475:50:40::3;2494:10;2506:7;2515:9;2475:18;:50::i;2802:94:35:-:0;2473:6;;;;2459:10;:20;2451:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2851:6:::1;:14:::0;;;::::1;::::0;;2880:9:::1;::::0;::::1;::::0;2860:5:::1;::::0;2880:9:::1;2802:94::o:0;4097:840:33:-;2286:6:35;;4280:4:33;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3708:10:33::1;3700:19;::::0;;;:7:::1;:19;::::0;;;;;::::1;;3692:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4223:10:::2;2064:21:32;::::0;;;:11:::2;:21;::::0;;;;;::::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::3;::::0;::::3;;::::0;;;:11:::3;:21;::::0;;;;;4258:3:33;;2064:21:32::3;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4308:17:33::4;::::0;::::4;4300:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4393:1;4383:7;:11;4375:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4496:10;4451:28;4482:25:::0;;;:13:::4;:25;::::0;;;;;4538:31;;::::4;;4517:124;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4667:12;::::0;:25:::4;::::0;4684:7;4667:16:::4;:25::i;:::-;4652:12;:40:::0;4718:13:::4;::::0;::::4;;::::0;;;:8:::4;:13;::::0;;;;;:26:::4;::::0;4736:7;4718:17:::4;:26::i;:::-;4702:13;::::0;::::4;;::::0;;;:8:::4;:13;::::0;;;;:42;4782:33:::4;:20:::0;4807:7;4782:24:::4;:33::i;:::-;4768:10;4754:25;::::0;;;:13:::4;:25;::::0;;;;;;;;:61;;;;4830:30;;;;;;;4754:25:::4;4830:30:::0;::::4;::::0;::::4;::::0;;;;;;::::4;4875:34;::::0;;;;;;;::::4;::::0;::::4;::::0;4892:1:::4;::::0;4875:34:::4;::::0;;;;::::4;::::0;;::::4;-1:-1:-1::0;4926:4:33::4;::::0;4097:840;-1:-1:-1;;;;;4097:840:33:o;10737:538::-;2286:6:35;;;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3708:10:33::1;3700:19;::::0;;;:7:::1;:19;::::0;;;;;::::1;;3692:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10850:10:::2;2064:21:32;::::0;;;:11:::2;:21;::::0;;;;;::::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10903:10:33::3;10876:15;10894:20:::0;;;:8:::3;:20;::::0;;;;;10932:11;10924:65:::3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11018:7;11007;:18;;10999:69;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11094:12;::::0;:25:::3;::::0;11111:7;11094:16:::3;:25::i;:::-;11079:12;:40:::0;11152:20:::3;:7:::0;11164;11152:11:::3;:20::i;:::-;11138:10;11129:20;::::0;;;:8:::3;:20;::::0;;;;;;;;:43;;;;11187:25;;;;;;;11138:10;;11187:25:::3;::::0;;;;;;;::::3;11227:41;::::0;;;;;;;11256:1:::3;::::0;11236:10:::3;::::0;11227:41:::3;::::0;;;;::::3;::::0;;::::3;2158:1:32;3767::33::2;10737:538:::0;:::o;9703:334::-;2286:6:35;;9854:4:33;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5104:12:33::1;::::0;::::1;;5090:10;:26;5069:114;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9874:15:::2;::::0;::::2;;::::0;;;:7:::2;:15;::::0;;;;;;;:22;;;::::2;9892:4;9874:22;::::0;;9906:13:::2;:21:::0;;;;;;:43;;;9964:45;;;;;;;::::2;::::0;;;;;;;;::::2;-1:-1:-1::0;10026:4:33::2;9703:334:::0;;;;:::o;2040:84:41:-;2107:10;;;;;;;;;;;;;;;;;2040:84;:::o;2953:254:35:-;2713:6:34;;;;2699:10;:20;2691:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3045:24:35::1;::::0;::::1;3024:111;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3145:6;:19:::0;;;::::1;;::::0;;::::1;::::0;;;::::1;::::0;;;;3179:21:::1;::::0;3193:6;::::1;::::0;3179:21:::1;::::0;-1:-1:-1;;3179:21:35::1;2953:254:::0;:::o;5782:229:40:-;2286:6:35;;;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5956:48:40::1;5977:10;5989:5;5996:1;5999;6002;5956:20;:48::i;:::-;5782:229:::0;;;;;:::o;2106:26:35:-;;;;;;;;;:::o;6340:154:33:-;6470:17;;6440:7;6470:17;;;:8;:17;;;;;;;6340:154::o;1921:107:37:-;2001:20;;1975:7;2001:20;;;:13;:20;;;;;;;1921:107::o;2068:136:38:-;2138:66;2068:136;:::o;2623:89:35:-;2473:6;;;;2459:10;:20;2451:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2679:4:::1;2670:13:::0;;;::::1;::::0;::::1;::::0;;2698:7:::1;::::0;::::1;::::0;2670:13;;2698:7:::1;2623:89::o:0;5318:118:33:-;5408:21;;5382:7;5408:21;;;:13;:21;;;;;;;5318:118::o;2355:79:34:-;2395:7;2421:6;;;2355:79;:::o;1673:20:33:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2079:21:35;;;;;;:::o;1787:137:38:-;1858:66;1787:137;:::o;2766:284:40:-;2286:6:35;;2952:4:40;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2891:10:40::1;2064:21:32;::::0;;;:11:::1;:21;::::0;;;;;::::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;2926:7:40;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2972:50:40::3;2991:10;3003:7;3012:9;2972:18;:50::i;8487:260:33:-:0;2286:6:35;;8667:4:33;;2286:6:35;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8611:10:33::1;2064:21:32;::::0;;;:11:::1;:21;::::0;;;;;::::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;8646:2:33;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8687:32:33::3;8697:10;8709:2;8713:5;8687:9;:32::i;11281:303::-:0;2713:6:34;;;;2699:10;:20;2691:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11385:30:33::1;::::0;::::1;11364:124;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11498:12;:31:::0;;;::::1;;::::0;;::::1;::::0;;;::::1;::::0;;;;11544:33:::1;::::0;11564:12;::::1;::::0;11544:33:::1;::::0;-1:-1:-1;;11544:33:33::1;11281:303:::0;:::o;5543:104::-;5624:16;;5601:4;5624:16;;;:7;:16;;;;;;;;;5543:104::o;2926:299:32:-;2713:6:34;;;;2699:10;:20;2691:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3028:29:32::1;::::0;::::1;3007:126;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3143:11;:29:::0;;;::::1;;::::0;;::::1;::::0;;;::::1;::::0;;;;3187:31:::1;::::0;3206:11;::::1;::::0;3187:31:::1;::::0;-1:-1:-1;;3187:31:32::1;2926:299:::0;:::o;2161:177:30:-;1867:8;;;;1853:10;:22;1845:71;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2293:38:::1;:26;::::0;::::1;2320:2:::0;2324:6;2293:26:::1;:38::i;:::-;2161:177:::0;;;:::o;1365:26:32:-;;;;;;:::o;6439:309:40:-;2286:6:35;;;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:32::1;::::0;::::1;;::::0;;;:11:::1;:21;::::0;;;;;6651:5:40;;2064:21:32::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;6673:7:40;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6692:49:40::3;6700:5;6707:7;6716:5;6723:8;6733:1;6736;6739;6692:7;:49::i;:::-;2158:1:32::2;2323::35::1;6439:309:40::0;;;;;;;:::o;1758:298::-;1883:11;;;;;;;:39;;;;-1:-1:-1;1898:19:40;;;;:24;1883:39;1875:48;;;;;;1933:14;:4;1940:7;;1933:14;:::i;:::-;;1976:40;2003:7;;1976:40;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;1976:40:40;;;;;;;;;;;;;;;;;;-1:-1:-1;1976:26:40;;-1:-1:-1;1976:40:40:i;:::-;1957:16;:59;-1:-1:-1;;2026:19:40;:23;;;;2048:1;2026:23;;;1758:298::o;2285:130:38:-;2349:66;2285:130;:::o;5898:175:33:-;6043:14;;;;6013:7;6043:14;;;:7;:14;;;;;;;;:23;;;;;;;;;;;;;5898:175::o;3621:523:40:-;2286:6:35;;;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:32::1;::::0;::::1;;::::0;;;:11:::1;:21;::::0;;;;;3900:4:40;;2064:21:32::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;3921:2:40;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3935:202:40::3;3975:4;3993:2;4009:5;4028:10;4052:11;4077:5;4096:1;4111;4126;3935:26;:202::i;:::-;2158:1:32::2;2323::35::1;3621:523:40::0;;;;;;;;;:::o;1726:22:33:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3088:183:38;3225:32;;;;;3198:4;3225:32;;;:20;:32;;;;;;;;:39;;;;;;;;;;;3088:183::o;4883:521:40:-;2286:6:35;;;;;;;2285:7;2277:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:32::1;::::0;::::1;;::::0;;;:11:::1;:21;::::0;;;;;5161:4:40;;2064:21:32::1;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2064:21:::2;::::0;::::2;;::::0;;;:11:::2;:21;::::0;;;;;5182:2:40;;2064:21:32::2;;2063:22;2042:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5196:201:40::3;5235:4;5253:2;5269:5;5288:10;5312:11;5337:5;5356:1;5371;5386;5196:25;:201::i;2945:269:34:-:0;2713:6;;;;2699:10;:20;2691:65;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3040:22:::1;::::0;::::1;3019:107;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3162:6;::::0;3141:38:::1;::::0;;3162:6:::1;::::0;;::::1;3141:38:::0;;;;::::1;;::::0;::::1;::::0;;;::::1;::::0;;;;;;;;::::1;3189:18;3198:8;3189;:18::i;:::-;2945:269:::0;:::o;2500:143:32:-;1771:11;;;;1757:10;:25;1736:116;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2572:21:::1;::::0;::::1;;::::0;;;:11:::1;:21;::::0;;;;;:28;;;::::1;2596:4;2572:28;::::0;;2615:21;::::1;::::0;2572;2615::::1;2500:143:::0;:::o;2277:115::-;2364:21;;2341:4;2364:21;;;:11;:21;;;;;;;;;2277:115::o;7196:363:33:-;7327:19;;;7319:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7405:21;;;7397:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7475:14;;;;;;;;:7;:14;;;;;;;;:23;;;;;;;;;;;;;:31;;;7521;;;;;;;;;;;;;;;;;7196:363;;;:::o;8931:526::-;9057:18;;;9049:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9135:16;;;9127:64;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9231:14;;;;;;;:8;:14;;;;;;9222:23;;;9201:108;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9337:14;;;;;;;:8;:14;;;;;;:25;;9356:5;9337:18;:25::i;:::-;9320:14;;;;;;;;:8;:14;;;;;;:42;;;;9387:12;;;;;;;:23;;9404:5;9387:16;:23::i;:::-;9372:12;;;;;;;;:8;:12;;;;;;;;;:38;;;;9425:25;;;;;;;9372:12;;9425:25;;;;;;;;;;;;;8931:526;;;:::o;1321:134:42:-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i;:::-;1398:50;1321:134;-1:-1:-1;;;1321:134:42:o;2493:79:34:-;2548:6;:17;;;;;;;;;;;;;;;2493:79::o;6981:208:40:-;7143:14;;;;;;;;:7;:14;;;;;;;;:23;;;;;;;;;;7118:64;;7127:5;;7134:7;;7143:38;;7171:9;7143:27;:38::i;:::-;7118:8;:64::i;874:176:42:-;932:7;963:5;;;986:6;;;;978:46;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6532:611:38;6693:46;6721:10;6733:5;6693:27;:46::i;:::-;6770:106;;;2349:66;6770:106;;;;;;;;;;;;;6750:17;6770:106;;;;;;;;;;;;;;;;;;;;;;;6922:16;;6770:106;;;6907:47;;6940:1;6943;6946;6770:106;6907:14;:47::i;:::-;:61;;;6886:138;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7035:32;;;;;;;:20;:32;;;;;;;;:39;;;;;;;;;:46;;;;7077:4;7035:46;;;7096:40;7068:5;;7035:32;7096:40;;;6532:611;;;;;;:::o;7422:341:40:-;7559:197;7581:5;7600:7;7621:125;7666:9;7621:125;;;;;;;;;;;;;;;;;:14;;;;;;;;:7;:14;;;;;;;;:23;;;;;;;;;;;:125;:27;:125::i;696:175:44:-;805:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;828:23;805:58;;;778:86;;798:5;;778:19;:86::i;2459:637:37:-;2673:3;2661:8;:15;;2653:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2854:20;;;;2766:15;2854:20;;;:13;:20;;;;;;;;;:22;;;;;;;;2742:166;;1662:66;2742:166;;;;;;;;;;;;;;;;2722:17;2742:166;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2954:16;;2939:47;;2972:1;2975;2978;2742:166;2939:14;:47::i;:::-;:56;;;2918:129;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3058:31;3067:5;3074:7;3083:5;3058:8;:31::i;:::-;2459:637;;;;;;;;:::o;1501:686:28:-;2015:22;;;;;;;2059:25;;;;;;;;;1776:390;;;1927:66;1776:390;;;;;;;;;;;;;;;;;;1702:9;1776:390;;;;2143:4;1776:390;;;;;;;;;;;;;;;;;;;;;;;;1749:431;;;;;;1501:686::o;3842:780:38:-;4103:64;4130:4;4136:5;4143:10;4155:11;4103:26;:64::i;:::-;4198:191;;;1858:66;4198:191;;;;;;;;;;;;;;;;;4178:17;4198:191;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4435:16;;4198:191;;;4420:47;;4453:1;4456;4459;4198:191;4420:14;:47::i;:::-;:55;;;4399:132;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4542:37;4567:4;4573:5;4542:24;:37::i;:::-;4589:26;4599:4;4605:2;4609:5;4589:9;:26::i;:::-;3842:780;;;;;;;;;;:::o;5361:854::-;5629:16;;;5635:10;5629:16;5621:66;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5697:64;5724:4;5730:5;5737:10;5749:11;5697:26;:64::i;:::-;5792:190;;;2138:66;5792:190;;;;;;;;;;;;;;;;;5772:17;5792:190;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6028:16;;5792:190;;;6013:47;;6046:1;6049;6052;5792:190;6013:14;:47::i;1746:187:42:-;1832:7;1867:12;1859:6;;;;1851:29;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:42;;;1746:187::o;7322:255:38:-;7459:32;;;;;;;:20;:32;;;;;;;;:39;;;;;;;;;;;7458:40;7437:133;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7322:255;;:::o;2572:434:28:-;2898:26;;;;;;;;;;2803:135;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2780:168;;;;;2744:7;;2965:34;2780:168;2991:1;2994;2997;2965:17;:34::i;:::-;2958:41;2572:434;-1:-1:-1;;;;;;;2572:434:28:o;2959:751:44:-;3378:23;3404:69;3432:4;3404:69;;;;;;;;;;;;;;;;;3412:5;3404:27;;;;:69;;;;;:::i;:::-;3487:17;;3378:95;;-1:-1:-1;3487:21:44;3483:221;;3627:10;3616:30;;;;;;;;;;;;;;;-1:-1:-1;3616:30:44;3608:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7903:419:38;8103:10;8097:3;:16;8076:106;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8206:11;8200:3;:17;8192:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8269:46;8297:10;8309:5;8269:27;:46::i;:::-;7903:419;;;;:::o;8493:203::-;8592:32;;;;;;;:20;:32;;;;;;;;:39;;;;;;;;;:46;;;;8634:4;8592:46;;;8653:36;8625:5;;8592:32;8653:36;;;8493:203;;:::o;1872:1556:27:-;1997:7;2932:66;2907:91;;2890:192;;;3023:48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2890:192;3096:1;:7;;3101:2;3096:7;;:18;;;;;3107:1;:7;;3112:2;3107:7;;3096:18;3092:97;;;3130:48;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3092:97;3283:14;3300:26;3310:6;3318:1;3321;3324;3300:26;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3300:26:27;;;;;;-1:-1:-1;;3344:20:27;;;3336:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3415:6;-1:-1:-1;1872:1556:27;;;;;;;:::o;3770:194:45:-;3873:12;3904:53;3927:6;3935:4;3941:1;3944:12;5247;5279:18;5290:6;5279:10;:18::i;:::-;5271:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5402:12;5416:23;5443:6;:11;;5463:8;5474:4;5443:36;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5401:78;;;;5493:7;5489:580;;;5523:10;-1:-1:-1;5516:17:45;;-1:-1:-1;5516:17:45;5489:580;5634:17;;:21;5630:429;;5892:10;5886:17;5952:15;5939:10;5935:2;5931:19;5924:44;5841:145;6024:20;;;;;;;;;;;;;;;;;;;;6031:12;;6024:20;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;718:610;778:4;1239:20;;1084:66;1278:23;;;;;;:42;;-1:-1:-1;;1305:15:45;;;1270:51;-1:-1:-1;;718:610:45:o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", - "linkReferences": {} - }, - "methodIdentifiers": { - "CANCEL_AUTHORIZATION_TYPEHASH()": "d9169487", - "DOMAIN_SEPARATOR()": "3644e515", - "PERMIT_TYPEHASH()": "30adf81f", - "RECEIVE_WITH_AUTHORIZATION_TYPEHASH()": "7f2eecc3", - "TRANSFER_WITH_AUTHORIZATION_TYPEHASH()": "a0cc6a68", - "allowance(address,address)": "dd62ed3e", - "approve(address,uint256)": "095ea7b3", - "authorizationState(address,bytes32)": "e94a0102", - "balanceOf(address)": "70a08231", - "blacklist(address)": "f9f92be4", - "blacklister()": "bd102430", - "burn(uint256)": "42966c68", - "cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)": "5a049a70", - "configureMinter(address,uint256)": "4e44d956", - "currency()": "e5a6b10f", - "decimals()": "313ce567", - "decreaseAllowance(address,uint256)": "a457c2d7", - "increaseAllowance(address,uint256)": "39509351", - "initialize(string,string,string,uint8,address,address,address,address)": "3357162b", - "initializeV2(string)": "d608ea64", - "initializeV2_1(address)": "2fc81e09", - "isBlacklisted(address)": "fe575a87", - "isMinter(address)": "aa271e1a", - "masterMinter()": "35d99f35", - "mint(address,uint256)": "40c10f19", - "minterAllowance(address)": "8a6db9c3", - "name()": "06fdde03", - "nonces(address)": "7ecebe00", - "owner()": "8da5cb5b", - "pause()": "8456cb59", - "paused()": "5c975abb", - "pauser()": "9fd0506d", - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": "d505accf", - "receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": "ef55bec6", - "removeMinter(address)": "3092afd5", - "rescueERC20(address,address,uint256)": "b2118a8d", - "rescuer()": "38a63183", - "symbol()": "95d89b41", - "totalSupply()": "18160ddd", - "transfer(address,uint256)": "a9059cbb", - "transferFrom(address,address,uint256)": "23b872dd", - "transferOwnership(address)": "f2fde38b", - "transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": "e3ee160e", - "unBlacklist(address)": "1a895266", - "unpause()": "3f4ba83a", - "updateBlacklister(address)": "ad38bf22", - "updateMasterMinter(address)": "aa20e1e4", - "updatePauser(address)": "554bab3c", - "updateRescuer(address)": "2ab60045", - "version()": "54fd4d50" - }, - "rawMetadata": "{\"compiler\":{\"version\":\"0.6.12+commit.27d51765\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"authorizer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"}],\"name\":\"AuthorizationCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"authorizer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"}],\"name\":\"AuthorizationUsed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"Blacklisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newBlacklister\",\"type\":\"address\"}],\"name\":\"BlacklisterChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"burner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Burn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newMasterMinter\",\"type\":\"address\"}],\"name\":\"MasterMinterChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Mint\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"minterAllowedAmount\",\"type\":\"uint256\"}],\"name\":\"MinterConfigured\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldMinter\",\"type\":\"address\"}],\"name\":\"MinterRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Pause\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newAddress\",\"type\":\"address\"}],\"name\":\"PauserChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newRescuer\",\"type\":\"address\"}],\"name\":\"RescuerChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"UnBlacklisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"Unpause\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"CANCEL_AUTHORIZATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RECEIVE_WITH_AUTHORIZATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"TRANSFER_WITH_AUTHORIZATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"authorizer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"}],\"name\":\"authorizationState\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"blacklist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blacklister\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"burn\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"authorizer\",\"type\":\"address\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"cancelAuthorization\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"minterAllowedAmount\",\"type\":\"uint256\"}],\"name\":\"configureMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"currency\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"decrement\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"increment\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"tokenName\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"tokenSymbol\",\"type\":\"string\"},{\"internalType\":\"string\",\"name\":\"tokenCurrency\",\"type\":\"string\"},{\"internalType\":\"uint8\",\"name\":\"tokenDecimals\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"newMasterMinter\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"newPauser\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"newBlacklister\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"newName\",\"type\":\"string\"}],\"name\":\"initializeV2\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"lostAndFound\",\"type\":\"address\"}],\"name\":\"initializeV2_1\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"isBlacklisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"masterMinter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"mint\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"minterAllowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pauser\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"validAfter\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"validBefore\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"receiveWithAuthorization\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"minter\",\"type\":\"address\"}],\"name\":\"removeMinter\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"tokenContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rescuer\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"validAfter\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"validBefore\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"nonce\",\"type\":\"bytes32\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"transferWithAuthorization\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_account\",\"type\":\"address\"}],\"name\":\"unBlacklist\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newBlacklister\",\"type\":\"address\"}],\"name\":\"updateBlacklister\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newMasterMinter\",\"type\":\"address\"}],\"name\":\"updateMasterMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newPauser\",\"type\":\"address\"}],\"name\":\"updatePauser\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newRescuer\",\"type\":\"address\"}],\"name\":\"updateRescuer\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"allowance(address,address)\":{\"params\":{\"owner\":\"Token owner's address\",\"spender\":\"Spender's address\"},\"returns\":{\"_0\":\"Allowance amount\"}},\"approve(address,uint256)\":{\"params\":{\"spender\":\"Spender's address\",\"value\":\"Allowance amount\"},\"returns\":{\"_0\":\"True if successful\"}},\"authorizationState(address,bytes32)\":{\"details\":\"Nonces are randomly generated 32-byte data unique to the authorizer's address\",\"params\":{\"authorizer\":\"Authorizer's address\",\"nonce\":\"Nonce of the authorization\"},\"returns\":{\"_0\":\"True if the nonce is used\"}},\"balanceOf(address)\":{\"details\":\"Get token balance of an account\",\"params\":{\"account\":\"address The account\"}},\"blacklist(address)\":{\"details\":\"Adds account to blacklist\",\"params\":{\"_account\":\"The address to blacklist\"}},\"burn(uint256)\":{\"details\":\"allows a minter to burn some of its own tokens Validates that caller is a minter and that sender is not blacklisted amount is less than or equal to the minter's account balance\",\"params\":{\"_amount\":\"uint256 the amount of tokens to be burned\"}},\"cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)\":{\"details\":\"Works only if the authorization is not yet used.\",\"params\":{\"authorizer\":\"Authorizer's address\",\"nonce\":\"Nonce of the authorization\",\"r\":\"r of the signature\",\"s\":\"s of the signature\",\"v\":\"v of the signature\"}},\"configureMinter(address,uint256)\":{\"details\":\"Function to add/update a new minter\",\"params\":{\"minter\":\"The address of the minter\",\"minterAllowedAmount\":\"The minting amount allowed for the minter\"},\"returns\":{\"_0\":\"True if the operation was successful.\"}},\"decreaseAllowance(address,uint256)\":{\"params\":{\"decrement\":\"Amount of decrease in allowance\",\"spender\":\"Spender's address\"},\"returns\":{\"_0\":\"True if successful\"}},\"increaseAllowance(address,uint256)\":{\"params\":{\"increment\":\"Amount of increase in allowance\",\"spender\":\"Spender's address\"},\"returns\":{\"_0\":\"True if successful\"}},\"initializeV2(string)\":{\"params\":{\"newName\":\"New token name\"}},\"initializeV2_1(address)\":{\"params\":{\"lostAndFound\":\"The address to which the locked funds are sent\"}},\"isBlacklisted(address)\":{\"details\":\"Checks if account is blacklisted\",\"params\":{\"_account\":\"The address to check\"}},\"isMinter(address)\":{\"details\":\"Checks if account is a minter\",\"params\":{\"account\":\"The address to check\"}},\"mint(address,uint256)\":{\"details\":\"Function to mint tokens\",\"params\":{\"_amount\":\"The amount of tokens to mint. Must be less than or equal to the minterAllowance of the caller.\",\"_to\":\"The address that will receive the minted tokens.\"},\"returns\":{\"_0\":\"A boolean that indicates if the operation was successful.\"}},\"minterAllowance(address)\":{\"details\":\"Get minter allowance for an account\",\"params\":{\"minter\":\"The address of the minter\"}},\"nonces(address)\":{\"params\":{\"owner\":\"Token owner's address (Authorizer)\"},\"returns\":{\"_0\":\"Next nonce\"}},\"owner()\":{\"details\":\"Tells the address of the owner\",\"returns\":{\"_0\":\"the address of the owner\"}},\"pause()\":{\"details\":\"called by the owner to pause, triggers stopped state\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"params\":{\"deadline\":\"Expiration time, seconds since the epoch\",\"owner\":\"Token owner's address (Authorizer)\",\"r\":\"r of the signature\",\"s\":\"s of the signature\",\"spender\":\"Spender's address\",\"v\":\"v of the signature\",\"value\":\"Amount of allowance\"}},\"receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)\":{\"details\":\"This has an additional check to ensure that the payee's address matches the caller of this function to prevent front-running attacks.\",\"params\":{\"from\":\"Payer's address (Authorizer)\",\"nonce\":\"Unique nonce\",\"r\":\"r of the signature\",\"s\":\"s of the signature\",\"to\":\"Payee's address\",\"v\":\"v of the signature\",\"validAfter\":\"The time after which this is valid (unix time)\",\"validBefore\":\"The time before which this is valid (unix time)\",\"value\":\"Amount to be transferred\"}},\"removeMinter(address)\":{\"details\":\"Function to remove a minter\",\"params\":{\"minter\":\"The address of the minter to remove\"},\"returns\":{\"_0\":\"True if the operation was successful.\"}},\"rescueERC20(address,address,uint256)\":{\"params\":{\"amount\":\"Amount to withdraw\",\"to\":\"Recipient address\",\"tokenContract\":\"ERC20 token contract address\"}},\"rescuer()\":{\"returns\":{\"_0\":\"Rescuer's address\"}},\"totalSupply()\":{\"details\":\"Get totalSupply of token\"},\"transfer(address,uint256)\":{\"params\":{\"to\":\"Payee's address\",\"value\":\"Transfer amount\"},\"returns\":{\"_0\":\"True if successful\"}},\"transferFrom(address,address,uint256)\":{\"params\":{\"from\":\"Payer's address\",\"to\":\"Payee's address\",\"value\":\"Transfer amount\"},\"returns\":{\"_0\":\"True if successful\"}},\"transferOwnership(address)\":{\"details\":\"Allows the current owner to transfer control of the contract to a newOwner.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}},\"transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)\":{\"params\":{\"from\":\"Payer's address (Authorizer)\",\"nonce\":\"Unique nonce\",\"r\":\"r of the signature\",\"s\":\"s of the signature\",\"to\":\"Payee's address\",\"v\":\"v of the signature\",\"validAfter\":\"The time after which this is valid (unix time)\",\"validBefore\":\"The time before which this is valid (unix time)\",\"value\":\"Amount to be transferred\"}},\"unBlacklist(address)\":{\"details\":\"Removes account from blacklist\",\"params\":{\"_account\":\"The address to remove from the blacklist\"}},\"unpause()\":{\"details\":\"called by the owner to unpause, returns to normal state\"},\"updatePauser(address)\":{\"details\":\"update the pauser role\"},\"updateRescuer(address)\":{\"params\":{\"newRescuer\":\"New rescuer's address\"}},\"version()\":{\"returns\":{\"_0\":\"Version string\"}}},\"title\":\"FiatToken V2.1\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"allowance(address,address)\":{\"notice\":\"Amount of remaining tokens spender is allowed to transfer on behalf of the token owner\"},\"approve(address,uint256)\":{\"notice\":\"Set spender's allowance over the caller's tokens to be a given value.\"},\"authorizationState(address,bytes32)\":{\"notice\":\"Returns the state of an authorization\"},\"cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)\":{\"notice\":\"Attempt to cancel an authorization\"},\"decreaseAllowance(address,uint256)\":{\"notice\":\"Decrease the allowance by a given decrement\"},\"increaseAllowance(address,uint256)\":{\"notice\":\"Increase the allowance by a given increment\"},\"initializeV2(string)\":{\"notice\":\"Initialize v2\"},\"initializeV2_1(address)\":{\"notice\":\"Initialize v2.1\"},\"nonces(address)\":{\"notice\":\"Nonces for permit\"},\"permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\":{\"notice\":\"Update allowance with a signed permit\"},\"receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)\":{\"notice\":\"Receive a transfer with a signed authorization from the payer\"},\"rescueERC20(address,address,uint256)\":{\"notice\":\"Rescue ERC20 tokens locked up in this contract.\"},\"rescuer()\":{\"notice\":\"Returns current rescuer\"},\"transfer(address,uint256)\":{\"notice\":\"Transfer tokens from the caller\"},\"transferFrom(address,address,uint256)\":{\"notice\":\"Transfer tokens by spending allowance\"},\"transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)\":{\"notice\":\"Execute a transfer with a signed authorization\"},\"updateRescuer(address)\":{\"notice\":\"Assign the rescuer role to a given address.\"},\"version()\":{\"notice\":\"Version string for the EIP712 domain separator\"}},\"notice\":\"ERC20 Token backed by fiat reserves, version 2.1\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2_1.sol\":\"FiatTokenV2_1\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":10000000},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":forge-std/=lib/forge-std/src/\"]},\"sources\":{\"src/FiatTokenV2_1/centre-tokens/contracts/util/ECRecover.sol\":{\"keccak256\":\"0xb4e623304daaf25e40292e60a814ae60a60745d10003f1881a36be763dbc09aa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d6ef83bd777bfb13b0968b9cadd971216a95150427fa5d6b50f984e4b7268d31\",\"dweb:/ipfs/QmZS5TeT6n7tM36PBdYZNJW3wgYgAZE9zMcouNs4FuEDKj\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/util/EIP712.sol\":{\"keccak256\":\"0x39319612a776e16f355d5ab71575b68c427a058839544f032733df228b5debd1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d5ac6450e881a4275f4b5baf7b5d6e15c0a0c7c2039edf39f106e0b821c9829d\",\"dweb:/ipfs/QmShHneDasAXQzjA8iKWXk3sbZgxbQGPiMQ1MUpJGF64N8\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1.1/FiatTokenV1_1.sol\":{\"keccak256\":\"0xa5f8fc4b5e739ddcafe52dd76ebb7605e09eb9d52a5c1d77e48dd88e83106308\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://24543f05d3eb4768ee9798768cb6c9ce8b88d30606a5def83118ad1ba9382ac8\",\"dweb:/ipfs/QmYREtrAvoxn6DKXehJPP1pbs8vciqMvRHVk2eFKLtmKVT\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1.1/Rescuable.sol\":{\"keccak256\":\"0x8c02b979e06aa4133c93e47c743ebebd56d120dd10aeaf56b2da2a36f06b68b1\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9d73d6f98bd494500a414ca616eabe9ad6e99f283e00a471bddd7f8ac5825334\",\"dweb:/ipfs/QmXesAuMcXJiG3r2og7adeT5wBcY6ntFncVUFjNPfEhJC9\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1/AbstractFiatTokenV1.sol\":{\"keccak256\":\"0xb81ae053cff8eced79f29c3542b7693763ed2bfdd9a25d6b150439d21b3fa57d\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e16ce40aef6188334cd0dbc2e3aead22c1f89101d782df821821061d5efa891b\",\"dweb:/ipfs/QmW2WnaMTzpUityk5Mpv7FFdKCh2CeueJaDneoABGVowVm\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1/Blacklistable.sol\":{\"keccak256\":\"0xc4ff3bfe34c8ecf9f2c333f8373c111fdd4640ed15677c4891bb9cf3cbff9554\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://91cfc583d47eda58f356783698a130c1c6adf797c4ba55461598c0bac8159f33\",\"dweb:/ipfs/QmeWfmhcwUE7H5Ge8TVhWkJ4kDwDY8a4kExMaufukLqsh1\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1/FiatTokenV1.sol\":{\"keccak256\":\"0xaed130ecb4b0714a887dccaaff61321915f2afbf0839ee8af10673507a010471\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://3961d2ed8f4f2c69cc4ce0fdb2346b8bd5c09d644627704181fa03b82231ff31\",\"dweb:/ipfs/Qmb7sdix44isYUaD4TD4QsVmR8d86NN4koiFE6SsjRagGQ\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1/Ownable.sol\":{\"keccak256\":\"0x654e645d6d09616fde908eba4d29abf318fede7e8cc3e31705203fc1d2599217\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://fce1ff1458e817aaa5f87d3ef4207fbbdeb25e548d460f47d9cca4fb80175390\",\"dweb:/ipfs/QmfY7E5xfTyeiuU2nDXEdYfy5LKjGRh69fuKK4HV6YTv9v\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v1/Pausable.sol\":{\"keccak256\":\"0x873ce4f17eb8694cd0420ef6682c2da54290fe6e243f21ead37e90f211ac91b6\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://7df712210c8bb5fc08e0ff7b684ee4035530cd67b21f20d81766a17407606a3c\",\"dweb:/ipfs/QmYPFYAw4W8VdcBdknLhqfNfiUuGhGJD3ZZ7rAjrKjhtXd\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/AbstractFiatTokenV2.sol\":{\"keccak256\":\"0x5d393663d48e4bbb730630c117c2b703dd3c9968833e66dbbb18c92eab207afe\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://b0ce642b7ab095d970e3e5d031e5de6b2a98a7ddd20fd94123a42ed81e21757e\",\"dweb:/ipfs/QmbPixwbbpHS7zBRcJV1idzhaSd1SPRm3LjpywxFnXxR8A\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP2612.sol\":{\"keccak256\":\"0x8ed169be2f6423b8e7002241857d719e9eb9545f5dbad5209a8f6445132bdbe0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0646f70d57d072c0569ccbd9b02c922bbb183e6052f94c241c4fef9a267e73bc\",\"dweb:/ipfs/QmZK3D3qqTMenkdc5EpZSiyxETMpTVpRke61uuRH75ctVk\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP3009.sol\":{\"keccak256\":\"0x74a81d5b1682cb6716f60c27254e8a15463797e1772b37c884390eb9c7985070\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://1d3a6e220c2b8aab8c5070312cc9320ce2b79955b0bddafd99e5feec13851016\",\"dweb:/ipfs/QmQwc2nZtMKyDcGT8Scnv1R4ThP2Va9trUHnRiqMz4G2WN\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP712Domain.sol\":{\"keccak256\":\"0x56d8c0259e7f0baa5bb0d0d94810f25d001fb2dbe4eaf54dbe369ba0f1b8fd2b\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9356cfa1d42bfb8c4f84a3db535ab75b2088975bfbda30834d79c3eb678047ea\",\"dweb:/ipfs/QmSBeiLwNGEC4vnr82EWQinGZFZemxKwxvwLZ9bu48FWF2\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2.sol\":{\"keccak256\":\"0x59654e02023dd9d712bb160545854eae6cba80d707a547f6abfaadcd830af2e7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://e22c43b71ea393e9910bd91a355f711803d6971be25e9dabc702aaefac2a597f\",\"dweb:/ipfs/QmRjopnHgyKh1mXBDETBaXaom3NJSaacGEJweB5b28BdSE\"]},\"src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2_1.sol\":{\"keccak256\":\"0x6328091a86a3ab02471fba7ff3bf44200f6daf9f0ff3b61fe4043ee14cc1a4f0\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://4fed880853c249f6f812ddfa37bf99633b688f0bfd734e03372e4523f6cd2af9\",\"dweb:/ipfs/QmUMD1SvykvTJ8jmAjnsW13Yo3Wt6KH5dfKRPKCRogLki8\"]},\"src/FiatTokenV2_1/openzeppelin/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"src/FiatTokenV2_1/openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]},\"src/FiatTokenV2_1/openzeppelin/contracts/token/ERC20/SafeERC20.sol\":{\"keccak256\":\"0xf3b30f8a49631420635a8c35daacfcaa338012755f18a76fdd118730256f9a27\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0d7de652204c2ee291a61aa984103dfc7ae4392d651fbbc44a0079caee7c69a3\",\"dweb:/ipfs/Qmcw1cQnq9eWDnrCBwU3TNyqLfTMUFg5YKpYUkELoMPuUE\"]},\"src/FiatTokenV2_1/openzeppelin/contracts/utils/Address.sol\":{\"keccak256\":\"0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff\",\"dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS\"]}},\"version\":1}", - "metadata": { - "compiler": { - "version": "0.6.12+commit.27d51765" - }, - "language": "Solidity", - "output": { - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address", - "indexed": true - }, - { - "internalType": "address", - "name": "spender", - "type": "address", - "indexed": true - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256", - "indexed": false - } - ], - "type": "event", - "name": "Approval", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address", - "indexed": true - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32", - "indexed": true - } - ], - "type": "event", - "name": "AuthorizationCanceled", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address", - "indexed": true - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32", - "indexed": true - } - ], - "type": "event", - "name": "AuthorizationUsed", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "Blacklisted", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newBlacklister", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "BlacklisterChanged", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "burner", - "type": "address", - "indexed": true - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256", - "indexed": false - } - ], - "type": "event", - "name": "Burn", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newMasterMinter", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "MasterMinterChanged", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address", - "indexed": true - }, - { - "internalType": "address", - "name": "to", - "type": "address", - "indexed": true - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256", - "indexed": false - } - ], - "type": "event", - "name": "Mint", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address", - "indexed": true - }, - { - "internalType": "uint256", - "name": "minterAllowedAmount", - "type": "uint256", - "indexed": false - } - ], - "type": "event", - "name": "MinterConfigured", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "oldMinter", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "MinterRemoved", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "previousOwner", - "type": "address", - "indexed": false - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address", - "indexed": false - } - ], - "type": "event", - "name": "OwnershipTransferred", - "anonymous": false - }, - { - "inputs": [], - "type": "event", - "name": "Pause", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newAddress", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "PauserChanged", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newRescuer", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "RescuerChanged", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address", - "indexed": true - }, - { - "internalType": "address", - "name": "to", - "type": "address", - "indexed": true - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256", - "indexed": false - } - ], - "type": "event", - "name": "Transfer", - "anonymous": false - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address", - "indexed": true - } - ], - "type": "event", - "name": "UnBlacklisted", - "anonymous": false - }, - { - "inputs": [], - "type": "event", - "name": "Unpause", - "anonymous": false - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "CANCEL_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "DOMAIN_SEPARATOR", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "PERMIT_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "RECEIVE_WITH_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "TRANSFER_WITH_AUTHORIZATION_TYPEHASH", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function", - "name": "authorizationState", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "blacklist" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "blacklister", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "burn" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "authorizer", - "type": "address" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "cancelAuthorization" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - }, - { - "internalType": "uint256", - "name": "minterAllowedAmount", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "configureMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "currency", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "decrement", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "increment", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "string", - "name": "tokenName", - "type": "string" - }, - { - "internalType": "string", - "name": "tokenSymbol", - "type": "string" - }, - { - "internalType": "string", - "name": "tokenCurrency", - "type": "string" - }, - { - "internalType": "uint8", - "name": "tokenDecimals", - "type": "uint8" - }, - { - "internalType": "address", - "name": "newMasterMinter", - "type": "address" - }, - { - "internalType": "address", - "name": "newPauser", - "type": "address" - }, - { - "internalType": "address", - "name": "newBlacklister", - "type": "address" - }, - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "initialize" - }, - { - "inputs": [ - { - "internalType": "string", - "name": "newName", - "type": "string" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "initializeV2" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "lostAndFound", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "initializeV2_1" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "isBlacklisted", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "isMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "masterMinter", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_amount", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "mint", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "minterAllowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function", - "name": "nonces", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "function", - "name": "pause" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "pauser", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "deadline", - "type": "uint256" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "permit" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validAfter", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validBefore", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "receiveWithAuthorization" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "minter", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "removeMinter", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "tokenContract", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "rescueERC20" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "rescuer", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ] - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ] - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "transferOwnership" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validAfter", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "validBefore", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "nonce", - "type": "bytes32" - }, - { - "internalType": "uint8", - "name": "v", - "type": "uint8" - }, - { - "internalType": "bytes32", - "name": "r", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "s", - "type": "bytes32" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "transferWithAuthorization" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_account", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "unBlacklist" - }, - { - "inputs": [], - "stateMutability": "nonpayable", - "type": "function", - "name": "unpause" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newBlacklister", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "updateBlacklister" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newMasterMinter", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "updateMasterMinter" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_newPauser", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "updatePauser" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newRescuer", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "function", - "name": "updateRescuer" - }, - { - "inputs": [], - "stateMutability": "view", - "type": "function", - "name": "version", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ] - } - ], - "devdoc": { - "kind": "dev", - "methods": { - "allowance(address,address)": { - "params": { - "owner": "Token owner's address", - "spender": "Spender's address" - }, - "returns": { - "_0": "Allowance amount" - } - }, - "approve(address,uint256)": { - "params": { - "spender": "Spender's address", - "value": "Allowance amount" - }, - "returns": { - "_0": "True if successful" - } - }, - "authorizationState(address,bytes32)": { - "details": "Nonces are randomly generated 32-byte data unique to the authorizer's address", - "params": { - "authorizer": "Authorizer's address", - "nonce": "Nonce of the authorization" - }, - "returns": { - "_0": "True if the nonce is used" - } - }, - "balanceOf(address)": { - "details": "Get token balance of an account", - "params": { - "account": "address The account" - } - }, - "blacklist(address)": { - "details": "Adds account to blacklist", - "params": { - "_account": "The address to blacklist" - } - }, - "burn(uint256)": { - "details": "allows a minter to burn some of its own tokens Validates that caller is a minter and that sender is not blacklisted amount is less than or equal to the minter's account balance", - "params": { - "_amount": "uint256 the amount of tokens to be burned" - } - }, - "cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)": { - "details": "Works only if the authorization is not yet used.", - "params": { - "authorizer": "Authorizer's address", - "nonce": "Nonce of the authorization", - "r": "r of the signature", - "s": "s of the signature", - "v": "v of the signature" - } - }, - "configureMinter(address,uint256)": { - "details": "Function to add/update a new minter", - "params": { - "minter": "The address of the minter", - "minterAllowedAmount": "The minting amount allowed for the minter" - }, - "returns": { - "_0": "True if the operation was successful." - } - }, - "decreaseAllowance(address,uint256)": { - "params": { - "decrement": "Amount of decrease in allowance", - "spender": "Spender's address" - }, - "returns": { - "_0": "True if successful" - } - }, - "increaseAllowance(address,uint256)": { - "params": { - "increment": "Amount of increase in allowance", - "spender": "Spender's address" - }, - "returns": { - "_0": "True if successful" - } - }, - "initializeV2(string)": { - "params": { - "newName": "New token name" - } - }, - "initializeV2_1(address)": { - "params": { - "lostAndFound": "The address to which the locked funds are sent" - } - }, - "isBlacklisted(address)": { - "details": "Checks if account is blacklisted", - "params": { - "_account": "The address to check" - } - }, - "isMinter(address)": { - "details": "Checks if account is a minter", - "params": { - "account": "The address to check" - } - }, - "mint(address,uint256)": { - "details": "Function to mint tokens", - "params": { - "_amount": "The amount of tokens to mint. Must be less than or equal to the minterAllowance of the caller.", - "_to": "The address that will receive the minted tokens." - }, - "returns": { - "_0": "A boolean that indicates if the operation was successful." - } - }, - "minterAllowance(address)": { - "details": "Get minter allowance for an account", - "params": { - "minter": "The address of the minter" - } - }, - "nonces(address)": { - "params": { - "owner": "Token owner's address (Authorizer)" - }, - "returns": { - "_0": "Next nonce" - } - }, - "owner()": { - "details": "Tells the address of the owner", - "returns": { - "_0": "the address of the owner" - } - }, - "pause()": { - "details": "called by the owner to pause, triggers stopped state" - }, - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { - "params": { - "deadline": "Expiration time, seconds since the epoch", - "owner": "Token owner's address (Authorizer)", - "r": "r of the signature", - "s": "s of the signature", - "spender": "Spender's address", - "v": "v of the signature", - "value": "Amount of allowance" - } - }, - "receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": { - "details": "This has an additional check to ensure that the payee's address matches the caller of this function to prevent front-running attacks.", - "params": { - "from": "Payer's address (Authorizer)", - "nonce": "Unique nonce", - "r": "r of the signature", - "s": "s of the signature", - "to": "Payee's address", - "v": "v of the signature", - "validAfter": "The time after which this is valid (unix time)", - "validBefore": "The time before which this is valid (unix time)", - "value": "Amount to be transferred" - } - }, - "removeMinter(address)": { - "details": "Function to remove a minter", - "params": { - "minter": "The address of the minter to remove" - }, - "returns": { - "_0": "True if the operation was successful." - } - }, - "rescueERC20(address,address,uint256)": { - "params": { - "amount": "Amount to withdraw", - "to": "Recipient address", - "tokenContract": "ERC20 token contract address" - } - }, - "rescuer()": { - "returns": { - "_0": "Rescuer's address" - } - }, - "totalSupply()": { - "details": "Get totalSupply of token" - }, - "transfer(address,uint256)": { - "params": { - "to": "Payee's address", - "value": "Transfer amount" - }, - "returns": { - "_0": "True if successful" - } - }, - "transferFrom(address,address,uint256)": { - "params": { - "from": "Payer's address", - "to": "Payee's address", - "value": "Transfer amount" - }, - "returns": { - "_0": "True if successful" - } - }, - "transferOwnership(address)": { - "details": "Allows the current owner to transfer control of the contract to a newOwner.", - "params": { - "newOwner": "The address to transfer ownership to." - } - }, - "transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": { - "params": { - "from": "Payer's address (Authorizer)", - "nonce": "Unique nonce", - "r": "r of the signature", - "s": "s of the signature", - "to": "Payee's address", - "v": "v of the signature", - "validAfter": "The time after which this is valid (unix time)", - "validBefore": "The time before which this is valid (unix time)", - "value": "Amount to be transferred" - } - }, - "unBlacklist(address)": { - "details": "Removes account from blacklist", - "params": { - "_account": "The address to remove from the blacklist" - } - }, - "unpause()": { - "details": "called by the owner to unpause, returns to normal state" - }, - "updatePauser(address)": { - "details": "update the pauser role" - }, - "updateRescuer(address)": { - "params": { - "newRescuer": "New rescuer's address" - } - }, - "version()": { - "returns": { - "_0": "Version string" - } - } - }, - "version": 1 - }, - "userdoc": { - "kind": "user", - "methods": { - "allowance(address,address)": { - "notice": "Amount of remaining tokens spender is allowed to transfer on behalf of the token owner" - }, - "approve(address,uint256)": { - "notice": "Set spender's allowance over the caller's tokens to be a given value." - }, - "authorizationState(address,bytes32)": { - "notice": "Returns the state of an authorization" - }, - "cancelAuthorization(address,bytes32,uint8,bytes32,bytes32)": { - "notice": "Attempt to cancel an authorization" - }, - "decreaseAllowance(address,uint256)": { - "notice": "Decrease the allowance by a given decrement" - }, - "increaseAllowance(address,uint256)": { - "notice": "Increase the allowance by a given increment" - }, - "initializeV2(string)": { - "notice": "Initialize v2" - }, - "initializeV2_1(address)": { - "notice": "Initialize v2.1" - }, - "nonces(address)": { - "notice": "Nonces for permit" - }, - "permit(address,address,uint256,uint256,uint8,bytes32,bytes32)": { - "notice": "Update allowance with a signed permit" - }, - "receiveWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": { - "notice": "Receive a transfer with a signed authorization from the payer" - }, - "rescueERC20(address,address,uint256)": { - "notice": "Rescue ERC20 tokens locked up in this contract." - }, - "rescuer()": { - "notice": "Returns current rescuer" - }, - "transfer(address,uint256)": { - "notice": "Transfer tokens from the caller" - }, - "transferFrom(address,address,uint256)": { - "notice": "Transfer tokens by spending allowance" - }, - "transferWithAuthorization(address,address,uint256,uint256,uint256,bytes32,uint8,bytes32,bytes32)": { - "notice": "Execute a transfer with a signed authorization" - }, - "updateRescuer(address)": { - "notice": "Assign the rescuer role to a given address." - }, - "version()": { - "notice": "Version string for the EIP712 domain separator" - } - }, - "version": 1 - } - }, - "settings": { - "remappings": [ - "ds-test/=lib/forge-std/lib/ds-test/src/", - "forge-std/=lib/forge-std/src/" - ], - "optimizer": { - "enabled": true, - "runs": 10000000 - }, - "metadata": { - "bytecodeHash": "ipfs" - }, - "compilationTarget": { - "src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2_1.sol": "FiatTokenV2_1" - }, - "libraries": {} - }, - "sources": { - "src/FiatTokenV2_1/centre-tokens/contracts/util/ECRecover.sol": { - "keccak256": "0xb4e623304daaf25e40292e60a814ae60a60745d10003f1881a36be763dbc09aa", - "urls": [ - "bzz-raw://d6ef83bd777bfb13b0968b9cadd971216a95150427fa5d6b50f984e4b7268d31", - "dweb:/ipfs/QmZS5TeT6n7tM36PBdYZNJW3wgYgAZE9zMcouNs4FuEDKj" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/util/EIP712.sol": { - "keccak256": "0x39319612a776e16f355d5ab71575b68c427a058839544f032733df228b5debd1", - "urls": [ - "bzz-raw://d5ac6450e881a4275f4b5baf7b5d6e15c0a0c7c2039edf39f106e0b821c9829d", - "dweb:/ipfs/QmShHneDasAXQzjA8iKWXk3sbZgxbQGPiMQ1MUpJGF64N8" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1.1/FiatTokenV1_1.sol": { - "keccak256": "0xa5f8fc4b5e739ddcafe52dd76ebb7605e09eb9d52a5c1d77e48dd88e83106308", - "urls": [ - "bzz-raw://24543f05d3eb4768ee9798768cb6c9ce8b88d30606a5def83118ad1ba9382ac8", - "dweb:/ipfs/QmYREtrAvoxn6DKXehJPP1pbs8vciqMvRHVk2eFKLtmKVT" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1.1/Rescuable.sol": { - "keccak256": "0x8c02b979e06aa4133c93e47c743ebebd56d120dd10aeaf56b2da2a36f06b68b1", - "urls": [ - "bzz-raw://9d73d6f98bd494500a414ca616eabe9ad6e99f283e00a471bddd7f8ac5825334", - "dweb:/ipfs/QmXesAuMcXJiG3r2og7adeT5wBcY6ntFncVUFjNPfEhJC9" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1/AbstractFiatTokenV1.sol": { - "keccak256": "0xb81ae053cff8eced79f29c3542b7693763ed2bfdd9a25d6b150439d21b3fa57d", - "urls": [ - "bzz-raw://e16ce40aef6188334cd0dbc2e3aead22c1f89101d782df821821061d5efa891b", - "dweb:/ipfs/QmW2WnaMTzpUityk5Mpv7FFdKCh2CeueJaDneoABGVowVm" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1/Blacklistable.sol": { - "keccak256": "0xc4ff3bfe34c8ecf9f2c333f8373c111fdd4640ed15677c4891bb9cf3cbff9554", - "urls": [ - "bzz-raw://91cfc583d47eda58f356783698a130c1c6adf797c4ba55461598c0bac8159f33", - "dweb:/ipfs/QmeWfmhcwUE7H5Ge8TVhWkJ4kDwDY8a4kExMaufukLqsh1" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1/FiatTokenV1.sol": { - "keccak256": "0xaed130ecb4b0714a887dccaaff61321915f2afbf0839ee8af10673507a010471", - "urls": [ - "bzz-raw://3961d2ed8f4f2c69cc4ce0fdb2346b8bd5c09d644627704181fa03b82231ff31", - "dweb:/ipfs/Qmb7sdix44isYUaD4TD4QsVmR8d86NN4koiFE6SsjRagGQ" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1/Ownable.sol": { - "keccak256": "0x654e645d6d09616fde908eba4d29abf318fede7e8cc3e31705203fc1d2599217", - "urls": [ - "bzz-raw://fce1ff1458e817aaa5f87d3ef4207fbbdeb25e548d460f47d9cca4fb80175390", - "dweb:/ipfs/QmfY7E5xfTyeiuU2nDXEdYfy5LKjGRh69fuKK4HV6YTv9v" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v1/Pausable.sol": { - "keccak256": "0x873ce4f17eb8694cd0420ef6682c2da54290fe6e243f21ead37e90f211ac91b6", - "urls": [ - "bzz-raw://7df712210c8bb5fc08e0ff7b684ee4035530cd67b21f20d81766a17407606a3c", - "dweb:/ipfs/QmYPFYAw4W8VdcBdknLhqfNfiUuGhGJD3ZZ7rAjrKjhtXd" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/AbstractFiatTokenV2.sol": { - "keccak256": "0x5d393663d48e4bbb730630c117c2b703dd3c9968833e66dbbb18c92eab207afe", - "urls": [ - "bzz-raw://b0ce642b7ab095d970e3e5d031e5de6b2a98a7ddd20fd94123a42ed81e21757e", - "dweb:/ipfs/QmbPixwbbpHS7zBRcJV1idzhaSd1SPRm3LjpywxFnXxR8A" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP2612.sol": { - "keccak256": "0x8ed169be2f6423b8e7002241857d719e9eb9545f5dbad5209a8f6445132bdbe0", - "urls": [ - "bzz-raw://0646f70d57d072c0569ccbd9b02c922bbb183e6052f94c241c4fef9a267e73bc", - "dweb:/ipfs/QmZK3D3qqTMenkdc5EpZSiyxETMpTVpRke61uuRH75ctVk" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP3009.sol": { - "keccak256": "0x74a81d5b1682cb6716f60c27254e8a15463797e1772b37c884390eb9c7985070", - "urls": [ - "bzz-raw://1d3a6e220c2b8aab8c5070312cc9320ce2b79955b0bddafd99e5feec13851016", - "dweb:/ipfs/QmQwc2nZtMKyDcGT8Scnv1R4ThP2Va9trUHnRiqMz4G2WN" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/EIP712Domain.sol": { - "keccak256": "0x56d8c0259e7f0baa5bb0d0d94810f25d001fb2dbe4eaf54dbe369ba0f1b8fd2b", - "urls": [ - "bzz-raw://9356cfa1d42bfb8c4f84a3db535ab75b2088975bfbda30834d79c3eb678047ea", - "dweb:/ipfs/QmSBeiLwNGEC4vnr82EWQinGZFZemxKwxvwLZ9bu48FWF2" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2.sol": { - "keccak256": "0x59654e02023dd9d712bb160545854eae6cba80d707a547f6abfaadcd830af2e7", - "urls": [ - "bzz-raw://e22c43b71ea393e9910bd91a355f711803d6971be25e9dabc702aaefac2a597f", - "dweb:/ipfs/QmRjopnHgyKh1mXBDETBaXaom3NJSaacGEJweB5b28BdSE" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2_1.sol": { - "keccak256": "0x6328091a86a3ab02471fba7ff3bf44200f6daf9f0ff3b61fe4043ee14cc1a4f0", - "urls": [ - "bzz-raw://4fed880853c249f6f812ddfa37bf99633b688f0bfd734e03372e4523f6cd2af9", - "dweb:/ipfs/QmUMD1SvykvTJ8jmAjnsW13Yo3Wt6KH5dfKRPKCRogLki8" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/openzeppelin/contracts/math/SafeMath.sol": { - "keccak256": "0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806", - "urls": [ - "bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5", - "dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/openzeppelin/contracts/token/ERC20/IERC20.sol": { - "keccak256": "0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385", - "urls": [ - "bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017", - "dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/openzeppelin/contracts/token/ERC20/SafeERC20.sol": { - "keccak256": "0xf3b30f8a49631420635a8c35daacfcaa338012755f18a76fdd118730256f9a27", - "urls": [ - "bzz-raw://0d7de652204c2ee291a61aa984103dfc7ae4392d651fbbc44a0079caee7c69a3", - "dweb:/ipfs/Qmcw1cQnq9eWDnrCBwU3TNyqLfTMUFg5YKpYUkELoMPuUE" - ], - "license": "MIT" - }, - "src/FiatTokenV2_1/openzeppelin/contracts/utils/Address.sol": { - "keccak256": "0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498", - "urls": [ - "bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff", - "dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS" - ], - "license": "MIT" - } - }, - "version": 1 - }, - "ast": { - "absolutePath": "src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2_1.sol", - "id": 47381, - "exportedSymbols": { - "FiatTokenV2_1": [47380] - }, - "nodeType": "SourceUnit", - "src": "1154:973:41", - "nodes": [ - { - "id": 47316, - "nodeType": "PragmaDirective", - "src": "1154:23:41", - "nodes": [], - "literals": ["solidity", "0.6", ".12"] - }, - { - "id": 47318, - "nodeType": "ImportDirective", - "src": "1179:48:41", - "nodes": [], - "absolutePath": "src/FiatTokenV2_1/centre-tokens/contracts/v2/FiatTokenV2.sol", - "file": "./FiatTokenV2.sol", - "scope": 47381, - "sourceUnit": 47315, - "symbolAliases": [ - { - "foreign": { - "argumentTypes": null, - "id": 47317, - "name": "FiatTokenV2", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": null, - "src": "1188:11:41", - "typeDescriptions": { - "typeIdentifier": null, - "typeString": null - } - }, - "local": null - } - ], - "unitAlias": "" - }, - { - "id": 47380, - "nodeType": "ContractDefinition", - "src": "1362:764:41", - "nodes": [ - { - "id": 47370, - "nodeType": "FunctionDefinition", - "src": "1528:398:41", - "nodes": [], - "body": { - "id": 47369, - "nodeType": "Block", - "src": "1583:343:41", - "nodes": [], - "statements": [ - { - "expression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "commonType": { - "typeIdentifier": "t_uint8", - "typeString": "uint8" - }, - "id": 47330, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "argumentTypes": null, - "id": 47328, - "name": "_initializedVersion", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 47025, - "src": "1652:19:41", - "typeDescriptions": { - "typeIdentifier": "t_uint8", - "typeString": "uint8" - } - }, - "nodeType": "BinaryOperation", - "operator": "==", - "rightExpression": { - "argumentTypes": null, - "hexValue": "31", - "id": 47329, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1675:1:41", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_rational_1_by_1", - "typeString": "int_const 1" - }, - "value": "1" - }, - "src": "1652:24:41", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - ], - "id": 47327, - "name": "require", - "nodeType": "Identifier", - "overloadedDeclarations": [-18, -18], - "referencedDeclaration": -18, - "src": "1644:7:41", - "typeDescriptions": { - "typeIdentifier": "t_function_require_pure$_t_bool_$returns$__$", - "typeString": "function (bool) pure" - } - }, - "id": 47331, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "1644:33:41", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 47332, - "nodeType": "ExpressionStatement", - "src": "1644:33:41" - }, - { - "assignments": [47334], - "declarations": [ - { - "constant": false, - "id": 47334, - "mutability": "mutable", - "name": "lockedAmount", - "nodeType": "VariableDeclaration", - "overrides": null, - "scope": 47369, - "src": "1688:20:41", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 47333, - "name": "uint256", - "nodeType": "ElementaryTypeName", - "src": "1688:7:41", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": null, - "visibility": "internal" - } - ], - "id": 47341, - "initialValue": { - "argumentTypes": null, - "baseExpression": { - "argumentTypes": null, - "id": 47335, - "name": "balances", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 45622, - "src": "1711:8:41", - "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", - "typeString": "mapping(address => uint256)" - } - }, - "id": 47340, - "indexExpression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "id": 47338, - "name": "this", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -28, - "src": "1728:4:41", - "typeDescriptions": { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - ], - "id": 47337, - "isConstant": false, - "isLValue": false, - "isPure": true, - "lValueRequested": false, - "nodeType": "ElementaryTypeNameExpression", - "src": "1720:7:41", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_address_$", - "typeString": "type(address)" - }, - "typeName": { - "id": 47336, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "1720:7:41", - "typeDescriptions": { - "typeIdentifier": null, - "typeString": null - } - } - }, - "id": 47339, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "typeConversion", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "1720:13:41", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": false, - "nodeType": "IndexAccess", - "src": "1711:23:41", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "nodeType": "VariableDeclarationStatement", - "src": "1688:46:41" - }, - { - "condition": { - "argumentTypes": null, - "commonType": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "id": 47344, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftExpression": { - "argumentTypes": null, - "id": 47342, - "name": "lockedAmount", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 47334, - "src": "1748:12:41", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "nodeType": "BinaryOperation", - "operator": ">", - "rightExpression": { - "argumentTypes": null, - "hexValue": "30", - "id": 47343, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1763:1:41", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_rational_0_by_1", - "typeString": "int_const 0" - }, - "value": "0" - }, - "src": "1748:16:41", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - }, - "falseBody": null, - "id": 47355, - "nodeType": "IfStatement", - "src": "1744:99:41", - "trueBody": { - "id": 47354, - "nodeType": "Block", - "src": "1766:77:41", - "statements": [ - { - "expression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "id": 47348, - "name": "this", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -28, - "src": "1798:4:41", - "typeDescriptions": { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - ], - "id": 47347, - "isConstant": false, - "isLValue": false, - "isPure": true, - "lValueRequested": false, - "nodeType": "ElementaryTypeNameExpression", - "src": "1790:7:41", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_address_$", - "typeString": "type(address)" - }, - "typeName": { - "id": 47346, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "1790:7:41", - "typeDescriptions": { - "typeIdentifier": null, - "typeString": null - } - } - }, - "id": 47349, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "typeConversion", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "1790:13:41", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - { - "argumentTypes": null, - "id": 47350, - "name": "lostAndFound", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 47324, - "src": "1805:12:41", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - { - "argumentTypes": null, - "id": 47351, - "name": "lockedAmount", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 47334, - "src": "1819:12:41", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_address", - "typeString": "address" - }, - { - "typeIdentifier": "t_address", - "typeString": "address" - }, - { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - ], - "id": 47345, - "name": "_transfer", - "nodeType": "Identifier", - "overloadedDeclarations": [46200], - "referencedDeclaration": 46200, - "src": "1780:9:41", - "typeDescriptions": { - "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", - "typeString": "function (address,address,uint256)" - } - }, - "id": 47352, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "1780:52:41", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 47353, - "nodeType": "ExpressionStatement", - "src": "1780:52:41" - } - ] - } - }, - { - "expression": { - "argumentTypes": null, - "id": 47363, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "argumentTypes": null, - "baseExpression": { - "argumentTypes": null, - "id": 47356, - "name": "blacklisted", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 45464, - "src": "1852:11:41", - "typeDescriptions": { - "typeIdentifier": "t_mapping$_t_address_$_t_bool_$", - "typeString": "mapping(address => bool)" - } - }, - "id": 47361, - "indexExpression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "id": 47359, - "name": "this", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": -28, - "src": "1872:4:41", - "typeDescriptions": { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_contract$_FiatTokenV2_1_$47380", - "typeString": "contract FiatTokenV2_1" - } - ], - "id": 47358, - "isConstant": false, - "isLValue": false, - "isPure": true, - "lValueRequested": false, - "nodeType": "ElementaryTypeNameExpression", - "src": "1864:7:41", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_address_$", - "typeString": "type(address)" - }, - "typeName": { - "id": 47357, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "1864:7:41", - "typeDescriptions": { - "typeIdentifier": null, - "typeString": null - } - } - }, - "id": 47360, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "typeConversion", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "1864:13:41", - "tryCall": false, - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "isConstant": false, - "isLValue": true, - "isPure": false, - "lValueRequested": true, - "nodeType": "IndexAccess", - "src": "1852:26:41", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "argumentTypes": null, - "hexValue": "74727565", - "id": 47362, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "bool", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1881:4:41", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - }, - "value": "true" - }, - "src": "1852:33:41", - "typeDescriptions": { - "typeIdentifier": "t_bool", - "typeString": "bool" - } - }, - "id": 47364, - "nodeType": "ExpressionStatement", - "src": "1852:33:41" - }, - { - "expression": { - "argumentTypes": null, - "id": 47367, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "argumentTypes": null, - "id": 47365, - "name": "_initializedVersion", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 47025, - "src": "1896:19:41", - "typeDescriptions": { - "typeIdentifier": "t_uint8", - "typeString": "uint8" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "argumentTypes": null, - "hexValue": "32", - "id": 47366, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "1918:1:41", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_rational_2_by_1", - "typeString": "int_const 2" - }, - "value": "2" - }, - "src": "1896:23:41", - "typeDescriptions": { - "typeIdentifier": "t_uint8", - "typeString": "uint8" - } - }, - "id": 47368, - "nodeType": "ExpressionStatement", - "src": "1896:23:41" - } - ] - }, - "documentation": { - "id": 47322, - "nodeType": "StructuredDocumentation", - "src": "1406:117:41", - "text": " @notice Initialize v2.1\n @param lostAndFound The address to which the locked funds are sent" - }, - "functionSelector": "2fc81e09", - "implemented": true, - "kind": "function", - "modifiers": [], - "name": "initializeV2_1", - "overrides": null, - "parameters": { - "id": 47325, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 47324, - "mutability": "mutable", - "name": "lostAndFound", - "nodeType": "VariableDeclaration", - "overrides": null, - "scope": 47370, - "src": "1552:20:41", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 47323, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "1552:7:41", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - } - ], - "src": "1551:22:41" - }, - "returnParameters": { - "id": 47326, - "nodeType": "ParameterList", - "parameters": [], - "src": "1583:0:41" - }, - "scope": 47380, - "stateMutability": "nonpayable", - "virtual": false, - "visibility": "external" - }, - { - "id": 47379, - "nodeType": "FunctionDefinition", - "src": "2040:84:41", - "nodes": [], - "body": { - "id": 47378, - "nodeType": "Block", - "src": "2097:27:41", - "nodes": [], - "statements": [ - { - "expression": { - "argumentTypes": null, - "hexValue": "32", - "id": 47376, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "string", - "lValueRequested": false, - "nodeType": "Literal", - "src": "2114:3:41", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_stringliteral_ad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5", - "typeString": "literal_string \"2\"" - }, - "value": "2" - }, - "functionReturnParameters": 47375, - "id": 47377, - "nodeType": "Return", - "src": "2107:10:41" - } - ] - }, - "documentation": { - "id": 47371, - "nodeType": "StructuredDocumentation", - "src": "1932:103:41", - "text": " @notice Version string for the EIP712 domain separator\n @return Version string" - }, - "functionSelector": "54fd4d50", - "implemented": true, - "kind": "function", - "modifiers": [], - "name": "version", - "overrides": null, - "parameters": { - "id": 47372, - "nodeType": "ParameterList", - "parameters": [], - "src": "2056:2:41" - }, - "returnParameters": { - "id": 47375, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 47374, - "mutability": "mutable", - "name": "", - "nodeType": "VariableDeclaration", - "overrides": null, - "scope": 47379, - "src": "2082:13:41", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 47373, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "2082:6:41", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "value": null, - "visibility": "internal" - } - ], - "src": "2081:15:41" - }, - "scope": 47380, - "stateMutability": "view", - "virtual": false, - "visibility": "external" - } - ], - "abstract": false, - "baseContracts": [ - { - "arguments": null, - "baseName": { - "contractScope": null, - "id": 47320, - "name": "FiatTokenV2", - "nodeType": "UserDefinedTypeName", - "referencedDeclaration": 47314, - "src": "1388:11:41", - "typeDescriptions": { - "typeIdentifier": "t_contract$_FiatTokenV2_$47314", - "typeString": "contract FiatTokenV2" - } - }, - "id": 47321, - "nodeType": "InheritanceSpecifier", - "src": "1388:11:41" - } - ], - "contractDependencies": [ - 45337, 45426, 45451, 45582, 46359, 46443, 46543, 46568, 46663, 46995, - 47002, 47314, 47654 - ], - "contractKind": "contract", - "documentation": { - "id": 47319, - "nodeType": "StructuredDocumentation", - "src": "1269:92:41", - "text": " @title FiatToken V2.1\n @notice ERC20 Token backed by fiat reserves, version 2.1" - }, - "fullyImplemented": true, - "linearizedBaseContracts": [ - 47380, 47314, 46663, 46995, 47002, 46568, 45337, 45426, 46359, 45582, - 46543, 46443, 45451, 47654 - ], - "name": "FiatTokenV2_1", - "scope": 47381 - } - ], - "license": "MIT" - }, - "id": 41 -} diff --git a/packages/protocol/contracts/compiled/README.md b/packages/protocol/contracts/compiled/README.md deleted file mode 100644 index 076670757c3d..000000000000 --- a/packages/protocol/contracts/compiled/README.md +++ /dev/null @@ -1,24 +0,0 @@ -## About the compiled contracts - -Following Circle's recommendation for native token support (USDC, EURC), one needs to follow the standard proposed below: - -https://github.com/circlefin/stablecoin-evm/blob/master/doc/bridged_USDC_standard.md - -According to this document: - -> The third-party team’s bridged USDC token contract is expected to be identical to native USDC token contracts on other EVM blockchains. USDC uses a proxy pattern, so the standard applies to both the implementation contract code and the token proxy. -> -> Using identical code facilitates trustless contract verification by Circle and supports a seamless integration with existing USDC services. To facilitate this, the third-party team may choose one of the following: -> -> Copy previously deployed bytecode from a recent, native USDC token contract deployment (both proxy and implementation) on an EVM blockchain, for example Arbitrum, Base, OP Mainnet, or Polygon PoS Note that you must supply different constructor and initializer parameters where needed. -> -> Build the FiatToken contracts from source. In this case, the compiler metadata must be published or made available to support full contract verification. Various suggested compiler settings that Circle uses can be found here, which will allow the third-party team to reach the same bytecode if followed consistently. - -Following the recommendations the contracts were built with the same compiler settings (version + optimization) and they have bytecode equivalance with the other contracts (mentioned in the doc, and can be found on links below (Arbitrum, Scroll, Polygon, etc.)). - -For reference, here are Arbitrum's proxy + token contracts: - -- Proxy: https://arbiscan.io/token/0xaf88d065e77c8cc2239327c5edb3a432268e5831#code -- Implementation: https://arbiscan.io/address/0x0f4fb9474303d10905AB86aA8d5A65FE44b6E04A#code - -As a cross-reference, one can compare the bytecode of the ones present on arbiscan and here in the .json files (under bytcode key), the additional (meta)data could be helpful for contracts verfication. diff --git a/packages/protocol/contracts/libs/LibAddress.sol b/packages/protocol/contracts/libs/LibAddress.sol index 554f5280ac86..bf9f9b8107d0 100644 --- a/packages/protocol/contracts/libs/LibAddress.sol +++ b/packages/protocol/contracts/libs/LibAddress.sol @@ -1,10 +1,5 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -13,48 +8,79 @@ import "@openzeppelin/contracts/interfaces/IERC1271.sol"; /// @title LibAddress /// @dev Provides utilities for address-related operations. +/// @custom:security-contact security@taiko.xyz library LibAddress { bytes4 private constant EIP1271_MAGICVALUE = 0x1626ba7e; error ETH_TRANSFER_FAILED(); - /// @dev Sends Ether to the specified address. - /// @param to The recipient address. - /// @param amount The amount of Ether to send in wei. - /// @param gasLimit The max amount gas to pay for this transaction. - function sendEther(address to, uint256 amount, uint256 gasLimit) internal { - // Check for zero-value or zero-address transactions - if (to == address(0)) revert ETH_TRANSFER_FAILED(); - - // Attempt to send Ether to the recipient address - // WARNING: call() functions do not have an upper gas cost limit, so - // it's important to note that it may not reliably execute as expected - // when invoked with untrusted addresses. - (bool success,) = payable(to).call{ value: amount, gas: gasLimit }(""); + /// @dev Sends Ether to the specified address. This method will not revert even if sending ether + /// fails. + /// This function is inspired by + /// https://github.com/nomad-xyz/ExcessivelySafeCall/blob/main/src/ExcessivelySafeCall.sol + /// @param _to The recipient address. + /// @param _amount The amount of Ether to send in wei. + /// @param _gasLimit The max amount gas to pay for this transaction. + /// @return success_ true if the call is successful, false otherwise. + function sendEther( + address _to, + uint256 _amount, + uint256 _gasLimit, + bytes memory _calldata + ) + internal + returns (bool success_) + { + // Check for zero-address transactions + if (_to == address(0)) revert ETH_TRANSFER_FAILED(); + // dispatch message to recipient + // by assembly calling "handle" function + // we call via assembly to avoid memcopying a very large returndata + // returned by a malicious contract + assembly { + success_ := + call( + _gasLimit, // gas + _to, // recipient + _amount, // ether value + add(_calldata, 0x20), // inloc + mload(_calldata), // inlen + 0, // outloc + 0 // outlen + ) + } + } - // Ensure the transfer was successful - if (!success) revert ETH_TRANSFER_FAILED(); + /// @dev Sends Ether to the specified address. This method will revert if sending ether fails. + /// @param _to The recipient address. + /// @param _amount The amount of Ether to send in wei. + /// @param _gasLimit The max amount gas to pay for this transaction. + function sendEtherAndVerify(address _to, uint256 _amount, uint256 _gasLimit) internal { + if (_amount == 0) return; + if (!sendEther(_to, _amount, _gasLimit, "")) { + revert ETH_TRANSFER_FAILED(); + } } - /// @dev Sends Ether to the specified address. - /// @param to The recipient address. - /// @param amount The amount of Ether to send in wei. - function sendEther(address to, uint256 amount) internal { - sendEther(to, amount, gasleft()); + /// @dev Sends Ether to the specified address. This method will revert if sending ether fails. + /// @param _to The recipient address. + /// @param _amount The amount of Ether to send in wei. + function sendEtherAndVerify(address _to, uint256 _amount) internal { + sendEtherAndVerify(_to, _amount, gasleft()); } function supportsInterface( - address addr, - bytes4 interfaceId + address _addr, + bytes4 _interfaceId ) internal view - returns (bool result) + returns (bool result_) { - if (!Address.isContract(addr)) return false; + if (!Address.isContract(_addr)) return false; - try IERC165(addr).supportsInterface(interfaceId) returns (bool _result) { - result = _result; + try IERC165(_addr).supportsInterface(_interfaceId) returns (bool _result) { + result_ = _result; } catch { } } @@ -73,8 +99,4 @@ library LibAddress { return ECDSA.recover(hash, sig) == addr; } } - - function isSenderEOA() internal view returns (bool) { - return msg.sender == tx.origin; - } } diff --git a/packages/protocol/contracts/libs/LibBytes.sol b/packages/protocol/contracts/libs/LibBytes.sol new file mode 100644 index 000000000000..3ddddcf2b6af --- /dev/null +++ b/packages/protocol/contracts/libs/LibBytes.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +library LibBytes { + error INNER_ERROR(bytes innerError); + + // Function body taken from: + // https://github.com/clober-dex/core/blob/main/contracts/utils/BoringERC20.sol#L17-L33 + /// @notice Function to convert returned data to string + /// returns '' as fallback value. + function toString(bytes memory _data) internal pure returns (string memory) { + if (_data.length >= 64) { + return abi.decode(_data, (string)); + } else if (_data.length == 32) { + uint8 i = 0; + while (i < 32 && _data[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _data[i] != 0; i++) { + bytesArray[i] = _data[i]; + } + return string(bytesArray); + } else { + return ""; + } + } + + // Taken from: + // https://github.com/boringcrypto/BoringSolidity/blob/master/contracts/BoringBatchable.sol + /// @dev Helper function to extract a useful revert message from a failed call. + /// If the returned data is malformed or not correctly abi encoded then this call can fail + /// itself. + function revertWithExtractedError(bytes memory _returnData) internal pure { + // If the _res length is less than 68, then + // the transaction failed with custom error or silently (without a revert message) + if (_returnData.length < 68) revert INNER_ERROR(_returnData); + + assembly { + // Slice the sighash. + _returnData := add(_returnData, 0x04) + } + revert(abi.decode(_returnData, (string))); // All that remains is the revert string + } +} diff --git a/packages/protocol/contracts/libs/LibNetwork.sol b/packages/protocol/contracts/libs/LibNetwork.sol new file mode 100644 index 000000000000..e5ddd51bccdc --- /dev/null +++ b/packages/protocol/contracts/libs/LibNetwork.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title LibNetwork +library LibNetwork { + uint256 internal constant MAINNET = 1; + uint256 internal constant ROPSTEN = 2; + uint256 internal constant RINKEBY = 4; + uint256 internal constant GOERLI = 5; + uint256 internal constant KOVAN = 42; + uint256 internal constant HOLESKY = 17_000; + uint256 internal constant SEPOLIA = 11_155_111; + + uint64 internal constant TAIKO_MAINNET = 167_000; + uint64 internal constant TAIKO_HEKLA = 167_009; + + /// @dev Checks if the chain ID represents an Ethereum testnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an Ethereum testnet, false otherwise. + function isEthereumTestnet(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.ROPSTEN || _chainId == LibNetwork.RINKEBY + || _chainId == LibNetwork.GOERLI || _chainId == LibNetwork.KOVAN + || _chainId == LibNetwork.HOLESKY || _chainId == LibNetwork.SEPOLIA; + } + + /// @dev Checks if the chain ID represents an Ethereum testnet or the Etheruem mainnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an Ethereum testnet or the Etheruem mainnet, false + /// otherwise. + function isEthereumMainnetOrTestnet(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.MAINNET || isEthereumTestnet(_chainId); + } + + /// @dev Checks if the chain ID represents the Taiko L2 mainnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents the Taiko L2 mainnet. + function isTaikoMainnet(uint256 _chainId) internal pure returns (bool) { + return _chainId == TAIKO_MAINNET; + } + + /// @dev Checks if the chain ID represents an internal Taiko devnet's base layer. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an internal Taiko devnet's base layer, false + /// otherwise. + function isTaikoDevnet(uint256 _chainId) internal pure returns (bool) { + return _chainId >= 32_300 && _chainId <= 32_400; + } + + /// @dev Checks if the chain supports Dencun hardfork. Note that this check doesn't need to be + /// exhaustive. + /// @param _chainId The chain ID. + /// @return true if the chain supports Dencun hardfork, false otherwise. + function isDencunSupported(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.MAINNET || _chainId == LibNetwork.HOLESKY + || _chainId == LibNetwork.SEPOLIA || isTaikoDevnet(_chainId); + } +} diff --git a/packages/protocol/contracts/libs/LibTrieProof.sol b/packages/protocol/contracts/libs/LibTrieProof.sol new file mode 100644 index 000000000000..f28476318c89 --- /dev/null +++ b/packages/protocol/contracts/libs/LibTrieProof.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity 0.8.24; + +import "../thirdparty/optimism/rlp/RLPReader.sol"; +import "../thirdparty/optimism/rlp/RLPWriter.sol"; +import "../thirdparty/optimism/trie/SecureMerkleTrie.sol"; + +/// @title LibTrieProof +/// @custom:security-contact security@taiko.xyz +library LibTrieProof { + // The consensus format representing account is RLP encoded in the + // following order: nonce, balance, storageHash, codeHash. + uint256 private constant _ACCOUNT_FIELD_INDEX_STORAGE_HASH = 2; + + error LTP_INVALID_ACCOUNT_PROOF(); + error LTP_INVALID_INCLUSION_PROOF(); + + /// @notice Verifies that the value of a slot in the storage of an account is value. + /// + /// @param _rootHash The merkle root of state tree or the account tree. If accountProof's length + /// is zero, it is used as the account's storage root, otherwise it will be used as the state + /// root. + /// @param _addr The address of contract. + /// @param _slot The slot in the contract. + /// @param _value The value to be verified. + /// @param _accountProof The account proof + /// @param _storageProof The storage proof + /// @return storageRoot_ The account's storage root + function verifyMerkleProof( + bytes32 _rootHash, + address _addr, + bytes32 _slot, + bytes32 _value, + bytes[] memory _accountProof, + bytes[] memory _storageProof + ) + internal + pure + returns (bytes32 storageRoot_) + { + if (_accountProof.length != 0) { + bytes memory rlpAccount = + SecureMerkleTrie.get(abi.encodePacked(_addr), _accountProof, _rootHash); + + if (rlpAccount.length == 0) revert LTP_INVALID_ACCOUNT_PROOF(); + + RLPReader.RLPItem[] memory accountState = RLPReader.readList(rlpAccount); + + storageRoot_ = + bytes32(RLPReader.readBytes(accountState[_ACCOUNT_FIELD_INDEX_STORAGE_HASH])); + } else { + storageRoot_ = _rootHash; + } + + bool verified = SecureMerkleTrie.verifyInclusionProof( + bytes.concat(_slot), RLPWriter.writeUint(uint256(_value)), _storageProof, storageRoot_ + ); + + if (!verified) revert LTP_INVALID_INCLUSION_PROOF(); + } +} diff --git a/packages/protocol/contracts/signal/ISignalService.sol b/packages/protocol/contracts/signal/ISignalService.sol index 8b7b54a42293..804cfdde6e19 100644 --- a/packages/protocol/contracts/signal/ISignalService.sol +++ b/packages/protocol/contracts/signal/ISignalService.sol @@ -1,11 +1,5 @@ // SPDX-License-Identifier: MIT - -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; /// @title ISignalService /// @notice The SignalService contract serves as a secure cross-chain message @@ -14,35 +8,174 @@ pragma solidity ^0.8.20; /// access to the merkle root (such as Taiko injects it in the anchor /// transaction). With this, verifying a signal is reduced to simply verifying /// a merkle proof. - +/// @custom:security-contact security@taiko.xyz interface ISignalService { - /// @notice Send a signal (message) by setting the storage slot to a value - /// of 1. - /// @param signal The signal (message) to send. - /// @return storageSlot The location in storage where this signal is stored. - function sendSignal(bytes32 signal) external returns (bytes32 storageSlot); + enum CacheOption { + CACHE_NOTHING, + CACHE_SIGNAL_ROOT, + CACHE_STATE_ROOT, + CACHE_BOTH + } - /// @notice Verifies if a particular signal has already been sent. + struct HopProof { + /// @notice This hop's destination chain ID. If there is a next hop, this ID is the next + /// hop's source chain ID. + uint64 chainId; + /// @notice The ID of a source chain block whose state root has been synced to the hop's + /// destination chain. + /// Note that this block ID must be greater than or equal to the block ID where the signal + /// was sent on the source chain. + uint64 blockId; + /// @notice The state root or signal root of the source chain at the above blockId. This + /// value has been synced to the destination chain. + /// @dev To get both the blockId and the rootHash, apps should subscribe to the + /// ChainDataSynced event or query `topBlockId` first using the source chain's ID and + /// LibStrings.H_STATE_ROOT to get the most recent block ID synced, then call + /// `getSyncedChainData` to read the synchronized data. + bytes32 rootHash; + /// @notice Options to cache either the state roots or signal roots of middle-hops to the + /// current chain. + CacheOption cacheOption; + /// @notice The signal service's account proof. If this value is empty, then `rootHash` will + /// be used as the signal root, otherwise, `rootHash` will be used as the state root. + bytes[] accountProof; + /// @notice The signal service's storage proof. + bytes[] storageProof; + } + + /// @notice Emitted when a remote chain's state root or signal root is + /// synced locally as a signal. + /// @param chainId The remote chainId. + /// @param blockId The chain data's corresponding blockId. + /// @param kind A value to mark the data type. + /// @param data The remote data. + /// @param signal The signal for this chain data. + event ChainDataSynced( + uint64 indexed chainId, + uint64 indexed blockId, + bytes32 indexed kind, + bytes32 data, + bytes32 signal + ); + + /// @notice Emitted when a signal is sent. /// @param app The address that initiated the signal. - /// @param signal The signal (message) to send. - /// @return True if the signal has been sent, otherwise false. - function isSignalSent(address app, bytes32 signal) external view returns (bool); + /// @param signal The signal (message) that was sent. + /// @param slot The location in storage where this signal is stored. + /// @param value The value of the signal. + event SignalSent(address app, bytes32 signal, bytes32 slot, bytes32 value); + + /// @notice Emitted when an address is authorized or deauthorized. + /// @param addr The address to be authorized or deauthorized. + /// @param authorized True if authorized, false otherwise. + event Authorized(address indexed addr, bool authorized); + + /// @notice Send a signal (message) by setting the storage slot to the same value as the signal + /// itself. + /// @param _signal The signal (message) to send. + /// @return slot_ The location in storage where this signal is stored. + function sendSignal(bytes32 _signal) external returns (bytes32 slot_); + + /// @notice Sync a data from a remote chain locally as a signal. The signal is calculated + /// uniquely from chainId, kind, and data. + /// @param _chainId The remote chainId. + /// @param _kind A value to mark the data type. + /// @param _blockId The chain data's corresponding blockId + /// @param _chainData The remote data. + /// @return signal_ The signal for this chain data. + function syncChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId, + bytes32 _chainData + ) + external + returns (bytes32 signal_); /// @notice Verifies if a signal has been received on the target chain. - /// @param srcChainId The identifier for the source chain from which the + /// @param _chainId The identifier for the source chain from which the /// signal originated. - /// @param app The address that initiated the signal. - /// @param signal The signal (message) to send. - /// @param proof Merkle proof that the signal was persisted on the + /// @param _app The address that initiated the signal. + /// @param _signal The signal (message) to send. + /// @param _proof Merkle proof that the signal was persisted on the /// source chain. - /// @return True if the signal has been received, otherwise false. + /// @return numCacheOps_ The number of newly cached items. function proveSignalReceived( - uint64 srcChainId, - address app, - bytes32 signal, - bytes calldata proof + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + external + returns (uint256 numCacheOps_); + + /// @notice Verifies if a signal has been received on the target chain. + /// This is the "readonly" version of proveSignalReceived. + /// @param _chainId The identifier for the source chain from which the + /// signal originated. + /// @param _app The address that initiated the signal. + /// @param _signal The signal (message) to send. + /// @param _proof Merkle proof that the signal was persisted on the + /// source chain. + function verifySignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + external + view; + + /// @notice Verifies if a particular signal has already been sent. + /// @param _app The address that initiated the signal. + /// @param _signal The signal (message) that was sent. + /// @return true if the signal has been sent, otherwise false. + function isSignalSent(address _app, bytes32 _signal) external view returns (bool); + + /// @notice Checks if a chain data has been synced. + /// @param _chainId The remote chainId. + /// @param _kind A value to mark the data type. + /// @param _blockId The chain data's corresponding blockId + /// @param _chainData The remote data. + /// @return true if the data has been synced, otherwise false. + function isChainDataSynced( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId, + bytes32 _chainData ) external view returns (bool); + + /// @notice Returns the given block's chain data. + /// @param _chainId Identifier of the chainId. + /// @param _kind A value to mark the data type. + /// @param _blockId The chain data's corresponding block id. If this value is 0, use the top + /// block id. + /// @return blockId_ The actual block id. + /// @return chainData_ The synced chain data. + function getSyncedChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId + ) + external + view + returns (uint64 blockId_, bytes32 chainData_); + + /// @notice Returns the data to be used for caching slot generation. + /// @param _chainId Identifier of the chainId. + /// @param _kind A value to mark the data type. + /// @param _blockId The chain data's corresponding block id. If this value is 0, use the top + /// block id. + /// @return signal_ The signal used for caching slot creation. + function signalForChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId + ) + external + pure + returns (bytes32 signal_); } diff --git a/packages/protocol/contracts/signal/SignalService.sol b/packages/protocol/contracts/signal/SignalService.sol index ae7c0a9a31fe..0448330d9d91 100644 --- a/packages/protocol/contracts/signal/SignalService.sol +++ b/packages/protocol/contracts/signal/SignalService.sol @@ -1,161 +1,372 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import "../common/AuthorizableContract.sol"; -import "../common/ICrossChainSync.sol"; -import "../thirdparty/LibSecureMerkleTrie.sol"; +import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; +import "../libs/LibTrieProof.sol"; import "./ISignalService.sol"; /// @title SignalService -/// @dev Labeled in AddressResolver as "signal_service" /// @notice See the documentation in {ISignalService} for more details. -/// -/// @dev Authorization Guide for Multi-Hop Bridging: -/// For facilitating multi-hop bridging, authorize all deployed TaikoL1 and -/// TaikoL2 contracts involved in the bridging path. -/// Use the respective chain IDs as labels for authorization. -/// Note: SignalService should not authorize Bridges or other Bridgable -/// applications. -contract SignalService is AuthorizableContract, ISignalService { - using SafeCast for uint256; - - // storageProof represents ABI-encoded tuple of (key, value, and proof) - // returned from the eth_getProof() API. - struct Hop { - address signalRootRelay; +/// @dev Labeled in AddressResolver as "signal_service". +/// @custom:security-contact security@taiko.xyz +contract SignalService is EssentialContract, ISignalService { + /// @notice Mapping to store the top blockId. + /// @dev Slot 1. + mapping(uint64 chainId => mapping(bytes32 kind => uint64 blockId)) public topBlockId; + + /// @notice Mapping to store the authorized addresses. + /// @dev Slot 2. + mapping(address addr => bool authorized) public isAuthorized; + + uint256[48] private __gap; + + struct CacheAction { + bytes32 rootHash; bytes32 signalRoot; - bytes storageProof; + uint64 chainId; + uint64 blockId; + bool isFullProof; + bool isLastHop; + CacheOption option; } - struct Proof { - address crossChainSync; - uint64 height; - bytes storageProof; - Hop[] hops; + error SS_EMPTY_PROOF(); + error SS_INVALID_HOPS_WITH_LOOP(); + error SS_INVALID_LAST_HOP_CHAINID(); + error SS_INVALID_MID_HOP_CHAINID(); + error SS_INVALID_STATE(); + error SS_SIGNAL_NOT_FOUND(); + error SS_UNAUTHORIZED(); + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); } - error SS_INVALID_APP(); - error SS_INVALID_SIGNAL(); + /// @dev Authorize or deauthorize an address for calling syncChainData. + /// @dev Note that addr is supposed to be TaikoL1 and TaikoL1 contracts deployed locally. + /// @param _addr The address to be authorized or deauthorized. + /// @param _authorize True if authorize, false otherwise. + function authorize(address _addr, bool _authorize) external onlyOwner { + if (isAuthorized[_addr] == _authorize) revert SS_INVALID_STATE(); + isAuthorized[_addr] = _authorize; + emit Authorized(_addr, _authorize); + } - /// @dev Initializer to be called after being deployed behind a proxy. - function init() external initializer { - __OwnerUUPSUpgradable_init(); + /// @inheritdoc ISignalService + function sendSignal(bytes32 _signal) external returns (bytes32) { + return _sendSignal(msg.sender, _signal, _signal); } /// @inheritdoc ISignalService - function sendSignal(bytes32 signal) public returns (bytes32 slot) { - if (signal == 0) revert SS_INVALID_SIGNAL(); - slot = getSignalSlot(uint64(block.chainid), msg.sender, signal); - assembly { - sstore(slot, 1) - } + function syncChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId, + bytes32 _chainData + ) + external + returns (bytes32) + { + if (!isAuthorized[msg.sender]) revert SS_UNAUTHORIZED(); + return _syncChainData(_chainId, _kind, _blockId, _chainData); } /// @inheritdoc ISignalService - function isSignalSent(address app, bytes32 signal) public view returns (bool) { - if (signal == 0) revert SS_INVALID_SIGNAL(); - if (app == address(0)) revert SS_INVALID_APP(); - bytes32 slot = getSignalSlot(uint64(block.chainid), app, signal); - uint256 value; - assembly { - value := sload(slot) + /// @dev This function may revert. + function proveSignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + external + virtual + whenNotPaused + nonReentrant + returns (uint256 numCacheOps_) + { + CacheAction[] memory actions = // actions for caching + _verifySignalReceived(_chainId, _app, _signal, _proof, true); + + for (uint256 i; i < actions.length; ++i) { + numCacheOps_ += _cache(actions[i]); } - return value == 1; } /// @inheritdoc ISignalService - function proveSignalReceived( - uint64 srcChainId, - address app, - bytes32 signal, - bytes calldata proof + /// @dev This function may revert. + function verifySignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof + ) + external + view + { + _verifySignalReceived(_chainId, _app, _signal, _proof, false); + } + + /// @inheritdoc ISignalService + function isChainDataSynced( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId, + bytes32 _chainData ) public view + nonZeroValue(_chainData) returns (bool) { - if (skipProofCheck()) return true; + bytes32 signal = signalForChainData(_chainId, _kind, _blockId); + return _loadSignalValue(address(this), signal) == _chainData; + } - if (app == address(0) || signal == 0 || srcChainId == 0 || srcChainId == block.chainid) { - return false; - } + /// @inheritdoc ISignalService + function isSignalSent(address _app, bytes32 _signal) public view returns (bool) { + return _loadSignalValue(_app, _signal) != 0; + } - Proof memory p = abi.decode(proof, (Proof)); - if (p.crossChainSync == address(0) || p.storageProof.length == 0) { - return false; - } + /// @inheritdoc ISignalService + function getSyncedChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId + ) + public + view + returns (uint64 blockId_, bytes32 chainData_) + { + blockId_ = _blockId != 0 ? _blockId : topBlockId[_chainId][_kind]; - for (uint256 i; i < p.hops.length; ++i) { - if (p.hops[i].signalRoot == 0) return false; - if (p.hops[i].storageProof.length == 0) return false; + if (blockId_ != 0) { + bytes32 signal = signalForChainData(_chainId, _kind, blockId_); + chainData_ = _loadSignalValue(address(this), signal); + if (chainData_ == 0) revert SS_SIGNAL_NOT_FOUND(); } + } - // Check a chain of inclusion proofs. If this chain is chainA, and the - // message is sent on chainC, and we have chainB in the middle, we - // verify that chainB's signalRoot has been sent as a signal by chainB's - // "taiko" contract, then using chainB's signalRoot, we further check - // the signal is sent by chainC's "bridge" contract. - if (!isAuthorizedAs(p.crossChainSync, bytes32(block.chainid))) { - return false; - } + /// @inheritdoc ISignalService + function signalForChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encode(_chainId, _kind, _blockId)); + } + + /// @notice Returns the slot for a signal. + /// @param _chainId The chainId of the signal. + /// @param _app The address that initiated the signal. + /// @param _signal The signal (message) that was sent. + /// @return The slot for the signal. + function getSignalSlot( + uint64 _chainId, + address _app, + bytes32 _signal + ) + public + pure + returns (bytes32) + { + return keccak256(abi.encodePacked("SIGNAL", _chainId, _app, _signal)); + } - bytes32 signalRoot = ICrossChainSync(p.crossChainSync).getSyncedSnippet(p.height).signalRoot; + function _verifyHopProof( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes32 _value, + HopProof memory _hop, + address _signalService + ) + internal + view + virtual + nonZeroAddr(_app) + nonZeroValue(_signal) + nonZeroValue(_value) + returns (bytes32) + { + return LibTrieProof.verifyMerkleProof( + _hop.rootHash, + _signalService, + getSignalSlot(_chainId, _app, _signal), + _value, + _hop.accountProof, + _hop.storageProof + ); + } - if (signalRoot == 0) return false; + function _authorizePause(address, bool) internal pure override notImplemented { } - for (uint256 i; i < p.hops.length; ++i) { - Hop memory hop = p.hops[i]; + function _syncChainData( + uint64 _chainId, + bytes32 _kind, + uint64 _blockId, + bytes32 _chainData + ) + private + returns (bytes32 signal_) + { + signal_ = signalForChainData(_chainId, _kind, _blockId); + _sendSignal(address(this), signal_, _chainData); - bytes32 label = authorizedAddresses[hop.signalRootRelay]; - if (label == 0) return false; - uint64 chainId = uint256(label).toUint64(); + if (topBlockId[_chainId][_kind] < _blockId) { + topBlockId[_chainId][_kind] = _blockId; + } + emit ChainDataSynced(_chainId, _blockId, _kind, _chainData, signal_); + } - bytes32 slot = getSignalSlot( - chainId, // use label as chainId - hop.signalRootRelay, - hop.signalRoot // as a signal - ); - bool verified = LibSecureMerkleTrie.verifyInclusionProof( - bytes.concat(slot), hex"01", hop.storageProof, signalRoot - ); - if (!verified) return false; + function _sendSignal( + address _app, + bytes32 _signal, + bytes32 _value + ) + private + nonZeroAddr(_app) + nonZeroValue(_signal) + nonZeroValue(_value) + returns (bytes32 slot_) + { + slot_ = getSignalSlot(uint64(block.chainid), _app, _signal); + assembly { + sstore(slot_, _value) + } + emit SignalSent(_app, _signal, slot_, _value); + } - signalRoot = hop.signalRoot; + function _cache(CacheAction memory _action) private returns (uint256 numCacheOps_) { + // cache state root + bool cacheStateRoot = _action.option == CacheOption.CACHE_BOTH + || _action.option == CacheOption.CACHE_STATE_ROOT; + + if (cacheStateRoot && _action.isFullProof && !_action.isLastHop) { + numCacheOps_ = 1; + _syncChainData( + _action.chainId, LibStrings.H_STATE_ROOT, _action.blockId, _action.rootHash + ); } - return LibSecureMerkleTrie.verifyInclusionProof( - bytes.concat(getSignalSlot(srcChainId, app, signal)), - hex"01", - p.storageProof, - signalRoot - ); + // cache signal root + bool cacheSignalRoot = _action.option == CacheOption.CACHE_BOTH + || _action.option == CacheOption.CACHE_SIGNAL_ROOT; + + if (cacheSignalRoot && (_action.isFullProof || !_action.isLastHop)) { + numCacheOps_ += 1; + _syncChainData( + _action.chainId, LibStrings.H_SIGNAL_ROOT, _action.blockId, _action.signalRoot + ); + } } - /// @notice Get the storage slot of the signal. - /// @param chainId The address's chainId. - /// @param app The address that initiated the signal. - /// @param signal The signal to get the storage slot of. - /// @return The unique storage slot of the signal which is - /// created by encoding the sender address with the signal (message). - function getSignalSlot( - uint64 chainId, - address app, - bytes32 signal + function _loadSignalValue( + address _app, + bytes32 _signal ) - public - pure - returns (bytes32) + private + view + nonZeroAddr(_app) + nonZeroValue(_signal) + returns (bytes32 value_) { - return keccak256(abi.encodePacked("SIGNAL", chainId, app, signal)); + bytes32 slot = getSignalSlot(uint64(block.chainid), _app, _signal); + assembly { + value_ := sload(slot) + } } - /// @notice Tells if we need to check real proof or it is a test. - /// @return Returns true to skip checking inclusion proofs. - function skipProofCheck() public pure virtual returns (bool) { } + function _verifySignalReceived( + uint64 _chainId, + address _app, + bytes32 _signal, + bytes calldata _proof, + bool _prepareCaching + ) + private + view + nonZeroAddr(_app) + nonZeroValue(_signal) + returns (CacheAction[] memory actions) + { + HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[])); + if (hopProofs.length == 0) revert SS_EMPTY_PROOF(); + + uint64[] memory trace = new uint64[](hopProofs.length - 1); + + if (_prepareCaching) { + actions = new CacheAction[](hopProofs.length); + } + + uint64 chainId = _chainId; + address app = _app; + bytes32 signal = _signal; + bytes32 value = _signal; + address signalService = resolve(chainId, LibStrings.B_SIGNAL_SERVICE, false); + if (signalService == address(this)) revert SS_INVALID_MID_HOP_CHAINID(); + + HopProof memory hop; + bytes32 signalRoot; + bool isFullProof; + bool isLastHop; + + for (uint256 i; i < hopProofs.length; ++i) { + hop = hopProofs[i]; + + for (uint256 j; j < i; ++j) { + if (trace[j] == hop.chainId) revert SS_INVALID_HOPS_WITH_LOOP(); + } + + signalRoot = _verifyHopProof(chainId, app, signal, value, hop, signalService); + isLastHop = i == trace.length; + if (isLastHop) { + if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID(); + signalService = address(this); + } else { + trace[i] = hop.chainId; + + if (hop.chainId == 0 || hop.chainId == block.chainid) { + revert SS_INVALID_MID_HOP_CHAINID(); + } + signalService = resolve(hop.chainId, LibStrings.B_SIGNAL_SERVICE, false); + if (signalService == address(this)) revert SS_INVALID_MID_HOP_CHAINID(); + } + + isFullProof = hop.accountProof.length != 0; + + if (_prepareCaching) { + actions[i] = CacheAction( + hop.rootHash, + signalRoot, + chainId, + hop.blockId, + isFullProof, + isLastHop, + hop.cacheOption + ); + } + + signal = signalForChainData( + chainId, + isFullProof ? LibStrings.H_STATE_ROOT : LibStrings.H_SIGNAL_ROOT, + hop.blockId + ); + value = hop.rootHash; + chainId = hop.chainId; + app = signalService; + } + + if (value == 0 || value != _loadSignalValue(address(this), signal)) { + revert SS_SIGNAL_NOT_FOUND(); + } + } } diff --git a/packages/protocol/contracts/team/TimelockTokenPool.sol b/packages/protocol/contracts/team/TimelockTokenPool.sol deleted file mode 100644 index 335ade8770e4..000000000000 --- a/packages/protocol/contracts/team/TimelockTokenPool.sol +++ /dev/null @@ -1,231 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../common/EssentialContract.sol"; - -/// @title TimelockTokenPool -/// Contract for managing Taiko tokens allocated to different roles and -/// individuals. -/// -/// Manages Taiko tokens through a three-state lifecycle: "allocated" to -/// "granted, owned, and locked," and finally to "granted, owned, and unlocked." -/// Allocation doesn't transfer ownership unless specified by grant settings. -/// Conditional allocated tokens can be canceled by invoking `void()`, making -/// them available for other uses. Once granted and owned, tokens are -/// irreversible and their unlock schedules are immutable. -/// -/// We should deploy multiple instances of this contract for different roles: -/// - investors -/// - team members, advisors, etc. -/// - grant program grantees -contract TimelockTokenPool is EssentialContract { - using SafeERC20 for IERC20; - - struct Grant { - uint128 amount; - // If non-zero, indicates the start time for the recipient to receive - // tokens, subject to an unlocking schedule. - uint64 grantStart; - // If non-zero, indicates the time after which the token to be received - // will be actually non-zero - uint64 grantCliff; - // If non-zero, specifies the total seconds required for the recipient - // to fully own all granted tokens. - uint32 grantPeriod; - // If non-zero, indicates the start time for the recipient to unlock - // tokens. - uint64 unlockStart; - // If non-zero, indicates the time after which the unlock will be - // actually non-zero - uint64 unlockCliff; - // If non-zero, specifies the total seconds required for the recipient - // to fully unlock all owned tokens. - uint32 unlockPeriod; - } - - struct Recipient { - uint128 amountWithdrawn; - Grant[] grants; - } - - uint256 public constant MAX_GRANTS_PER_ADDRESS = 8; - - address public taikoToken; - address public sharedVault; - uint128 public totalAmountGranted; - uint128 public totalAmountVoided; - uint128 public totalAmountWithdrawn; - mapping(address recipient => Recipient) public recipients; - uint128[44] private __gap; - - event Granted(address indexed recipient, Grant grant); - event Voided(address indexed recipient, uint128 amount); - event Withdrawn(address indexed recipient, address to, uint128 amount); - - error INVALID_GRANT(); - error INVALID_PARAM(); - error NOTHING_TO_VOID(); - error NOTHING_TO_WITHDRAW(); - error TOO_MANY(); - - function init(address _taikoToken, address _sharedVault) external initializer { - __Essential_init(); - - if (_taikoToken == address(0)) revert INVALID_PARAM(); - taikoToken = _taikoToken; - - if (_sharedVault == address(0)) revert INVALID_PARAM(); - sharedVault = _sharedVault; - } - - /// @notice Gives a new grant to a address with its own unlock schedule. - /// This transaction should happen on a regular basis, e.g., quarterly. - /// @dev It is strongly recommended to add one Grant per receipient address - /// so that such a grant can be voided without voiding other grants for the - /// same recipient. - function grant(address recipient, Grant memory g) external onlyOwner { - if (recipient == address(0)) revert INVALID_PARAM(); - if (recipients[recipient].grants.length >= MAX_GRANTS_PER_ADDRESS) { - revert TOO_MANY(); - } - - _validateGrant(g); - - totalAmountGranted += g.amount; - recipients[recipient].grants.push(g); - emit Granted(recipient, g); - } - - /// @notice Puts a stop to all grants for a given recipient.Tokens already - /// granted to the recipient will NOT be voided but are subject to the - /// original unlock schedule. - function void(address recipient) external onlyOwner { - Recipient storage r = recipients[recipient]; - uint128 amountVoided; - for (uint128 i; i < r.grants.length; ++i) { - amountVoided += _voidGrant(r.grants[i]); - } - if (amountVoided == 0) revert NOTHING_TO_VOID(); - - totalAmountVoided += amountVoided; - emit Voided(recipient, amountVoided); - } - - /// @notice Withdraws all withdrawable tokens. - function withdraw() external { - _withdraw(msg.sender, msg.sender); - } - - /// @notice Withdraws all withdrawable tokens. - function withdraw(address to, bytes memory sig) external { - if (to == address(0)) revert INVALID_PARAM(); - bytes32 hash = keccak256(abi.encodePacked("Withdraw unlocked Taiko token to: ", to)); - address recipient = ECDSA.recover(hash, sig); - _withdraw(recipient, to); - } - - function getMyGrantSummary(address recipient) - public - view - returns ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) - { - Recipient storage r = recipients[recipient]; - for (uint128 i; i < r.grants.length; ++i) { - amountOwned += _getAmountOwned(r.grants[i]); - amountUnlocked += _getAmountUnlocked(r.grants[i]); - } - - amountWithdrawn = r.amountWithdrawn; - amountWithdrawable = amountUnlocked - amountWithdrawn; - } - - function getMyGrants(address recipient) public view returns (Grant[] memory) { - return recipients[recipient].grants; - } - - function _withdraw(address recipient, address to) private { - Recipient storage r = recipients[recipient]; - uint128 amount; - - for (uint128 i; i < r.grants.length; ++i) { - amount += _getAmountUnlocked(r.grants[i]); - } - - amount -= r.amountWithdrawn; - if (amount == 0) revert NOTHING_TO_WITHDRAW(); - - r.amountWithdrawn += amount; - totalAmountWithdrawn += amount; - IERC20(taikoToken).transferFrom(sharedVault, to, amount); - - emit Withdrawn(recipient, to, amount); - } - - function _voidGrant(Grant storage g) private returns (uint128 amountVoided) { - uint128 amountOwned = _getAmountOwned(g); - - amountVoided = g.amount - amountOwned; - g.amount = amountOwned; - - g.grantStart = 0; - g.grantPeriod = 0; - } - - function _getAmountOwned(Grant memory g) private view returns (uint128) { - return _calcAmount(g.amount, g.grantStart, g.grantCliff, g.grantPeriod); - } - - function _getAmountUnlocked(Grant memory g) private view returns (uint128) { - return _calcAmount(_getAmountOwned(g), g.unlockStart, g.unlockCliff, g.unlockPeriod); - } - - function _calcAmount( - uint128 amount, - uint64 start, - uint64 cliff, - uint64 period - ) - private - view - returns (uint128) - { - if (amount == 0) return 0; - if (start == 0) return amount; - if (block.timestamp <= start) return 0; - - if (period == 0) return amount; - if (block.timestamp >= start + period) return amount; - - if (block.timestamp <= cliff) return 0; - - return amount * uint64(block.timestamp - start) / period; - } - - function _validateGrant(Grant memory g) private pure { - if (g.amount == 0) revert INVALID_GRANT(); - _validateCliff(g.grantStart, g.grantCliff, g.grantPeriod); - _validateCliff(g.unlockStart, g.unlockCliff, g.unlockPeriod); - } - - function _validateCliff(uint64 start, uint64 cliff, uint32 period) private pure { - if (start == 0 || period == 0) { - if (cliff > 0) revert INVALID_GRANT(); - } else { - if (cliff > 0 && cliff <= start) revert INVALID_GRANT(); - if (cliff >= start + period) revert INVALID_GRANT(); - } - } -} diff --git a/packages/protocol/contracts/team/airdrop/ERC20Airdrop.sol b/packages/protocol/contracts/team/airdrop/ERC20Airdrop.sol deleted file mode 100644 index bef4176893ff..000000000000 --- a/packages/protocol/contracts/team/airdrop/ERC20Airdrop.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "./MerkleClaimable.sol"; - -/// @title ERC20Airdrop -/// Contract for managing Taiko token airdrop for eligible users -contract ERC20Airdrop is MerkleClaimable { - address public token; - address public vault; - uint256[48] private __gap; - - function init( - uint64 _claimStarts, - uint64 _claimEnds, - bytes32 _merkleRoot, - address _token, - address _vault - ) - external - initializer - { - __Essential_init(); - _setConfig(_claimStarts, _claimEnds, _merkleRoot); - - token = _token; - vault = _vault; - } - - function _claimWithData(bytes calldata data) internal override { - (address user, uint256 amount) = abi.decode(data, (address, uint256)); - IERC20Upgradeable(token).transferFrom(vault, user, amount); - } -} diff --git a/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol b/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol deleted file mode 100644 index bc863a1e4ab8..000000000000 --- a/packages/protocol/contracts/team/airdrop/ERC20Airdrop2.sol +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "../../libs/LibMath.sol"; -import "./MerkleClaimable.sol"; - -/// @title ERC20Airdrop2 -/// Contract for managing Taiko token airdrop for eligible users but the -/// withdrawal is not immediate and is subject to a withdrawal window. -contract ERC20Airdrop2 is MerkleClaimable { - using LibMath for uint256; - - address public token; - address public vault; - // Represents the token amount for which the user is (by default) eligible - mapping(address => uint256) public claimedAmount; - // Represents the already withdrawn amount - mapping(address => uint256) public withdrawnAmount; - // Length of the withdrawal window - uint64 public withdrawalWindow; - - uint256[45] private __gap; - - event Withdrawn(address user, uint256 amount); - - error WITHDRAWALS_NOT_ONGOING(); - - modifier ongoingWithdrawals() { - if (claimEnd > block.timestamp || claimEnd + withdrawalWindow < block.timestamp) { - revert WITHDRAWALS_NOT_ONGOING(); - } - _; - } - - function init( - uint64 _claimStarts, - uint64 _claimEnds, - bytes32 _merkleRoot, - address _token, - address _vault, - uint64 _withdrawalWindow - ) - external - initializer - { - __Essential_init(); - // Unix timestamp=_claimEnds+1 marks the first timestamp the users are able to withdraw. - _setConfig(_claimStarts, _claimEnds, _merkleRoot); - - token = _token; - vault = _vault; - withdrawalWindow = _withdrawalWindow; - } - - /// @notice External withdraw function - /// @param user User address - function withdraw(address user) external ongoingWithdrawals { - (, uint256 amount) = getBalance(user); - withdrawnAmount[user] += amount; - IERC20Upgradeable(token).transferFrom(vault, user, amount); - - emit Withdrawn(user, amount); - } - - /// @notice Getter for the balance and withdrawal amount per given user - /// The 2nd airdrop is subject to an unlock period. User has to claim his - /// tokens (within claimStart and claimEnd), but not immediately - /// withdrawable. With a time of X (withdrawalWindow) it becomes fully - /// withdrawable - and unlocks linearly. - /// @param user User address - /// @return balance The balance the user successfully claimed - /// @return withdrawableAmount The amount available to withdraw - function getBalance(address user) - public - view - returns (uint256 balance, uint256 withdrawableAmount) - { - balance = claimedAmount[user]; - // If balance is 0 then there is no balance and withdrawable amount - if (balance == 0) return (0, 0); - // Balance might be positive before end of claiming (claimEnd - if claimed already) but - // withdrawable is 0. - if (block.timestamp < claimEnd) return (balance, 0); - - // Hard cap timestamp - so range cannot go over - to get more allocation over time. - uint256 timeBasedAllowance = balance - * (block.timestamp.min(claimEnd + withdrawalWindow) - claimEnd) / withdrawalWindow; - - withdrawableAmount = timeBasedAllowance - withdrawnAmount[user]; - } - - function _claimWithData(bytes calldata data) internal override { - (address user, uint256 amount) = abi.decode(data, (address, uint256)); - claimedAmount[user] += amount; - } -} diff --git a/packages/protocol/contracts/team/airdrop/ERC721Airdrop.sol b/packages/protocol/contracts/team/airdrop/ERC721Airdrop.sol deleted file mode 100644 index bf5e5d9825b6..000000000000 --- a/packages/protocol/contracts/team/airdrop/ERC721Airdrop.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import "./MerkleClaimable.sol"; - -/// @title ERC721Airdrop -contract ERC721Airdrop is MerkleClaimable { - address public token; - address public vault; - uint256[48] private __gap; - - function init( - uint64 _claimStarts, - uint64 _claimEnds, - bytes32 _merkleRoot, - address _token, - address _vault - ) - external - initializer - { - __Essential_init(); - _setConfig(_claimStarts, _claimEnds, _merkleRoot); - - token = _token; - vault = _vault; - } - - function _claimWithData(bytes calldata data) internal override { - (address user, uint256[] memory tokenIds) = abi.decode(data, (address, uint256[])); - - for (uint256 i; i < tokenIds.length; ++i) { - IERC721Upgradeable(token).safeTransferFrom(vault, user, tokenIds[i]); - } - } -} diff --git a/packages/protocol/contracts/team/airdrop/MerkleClaimable.sol b/packages/protocol/contracts/team/airdrop/MerkleClaimable.sol deleted file mode 100644 index b6c7c55b553b..000000000000 --- a/packages/protocol/contracts/team/airdrop/MerkleClaimable.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import { MerkleProofUpgradeable } from - "@openzeppelin/contracts-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol"; -import "../../common/EssentialContract.sol"; - -/// @title MerkleClaimable -/// Contract for managing Taiko token airdrop for eligible users -abstract contract MerkleClaimable is EssentialContract { - mapping(bytes32 => bool) public isClaimed; - bytes32 public merkleRoot; - uint64 public claimStart; - uint64 public claimEnd; - - uint256[47] private __gap; - - event Claimed(bytes32 hash); - - error CLAIM_NOT_ONGOING(); - error CLAIMED_ALREADY(); - error INVALID_PROOF(); - - modifier ongoingClaim() { - if ( - merkleRoot == 0x0 || claimStart == 0 || claimEnd == 0 || claimStart > block.timestamp - || claimEnd < block.timestamp - ) revert CLAIM_NOT_ONGOING(); - _; - } - - function claim( - bytes calldata data, - bytes32[] calldata proof - ) - external - nonReentrant - ongoingClaim - { - bytes32 hash = keccak256(abi.encode("CLAIM_TAIKO_AIRDROP", data)); - - if (isClaimed[hash]) revert CLAIMED_ALREADY(); - - if (!MerkleProofUpgradeable.verify(proof, merkleRoot, hash)) { - revert INVALID_PROOF(); - } - - isClaimed[hash] = true; - _claimWithData(data); - emit Claimed(hash); - } - - /// @notice Set config parameters - /// @param _claimStart Unix timestamp for claim start - /// @param _claimEnd Unix timestamp for claim end - /// @param _merkleRoot Merkle root of the tree - function setConfig( - uint64 _claimStart, - uint64 _claimEnd, - bytes32 _merkleRoot - ) - external - onlyOwner - { - _setConfig(_claimStart, _claimEnd, _merkleRoot); - } - - function _setConfig(uint64 _claimStart, uint64 _claimEnd, bytes32 _merkleRoot) internal { - claimStart = _claimStart; - claimEnd = _claimEnd; - merkleRoot = _merkleRoot; - } - - /// @dev Must revert in case of errors. - function _claimWithData(bytes calldata data) internal virtual; -} diff --git a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol b/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol deleted file mode 100644 index 77cb64d991d2..000000000000 --- a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol +++ /dev/null @@ -1,327 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/trie/LibMerkleTrie.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity ^0.8.20; - -/* Library Imports */ -import "./LibBytesUtils.sol"; -import "./LibRLPReader.sol"; - -/** - * @title LibMerkleTrie - */ -library LibMerkleTrie { - enum NodeType { - BranchNode, - ExtensionNode, - LeafNode - } - - struct TrieNode { - LibRLPReader.RLPItem[] decoded; - bytes encoded; - } - - // TREE_RADIX determines the number of elements per branch node. - uint8 private constant TREE_RADIX = 16; - // Branch nodes have TREE_RADIX elements plus an additional `value` slot. - uint8 private constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; - // Leaf nodes and extension nodes always have two elements, a `path` and a - // `value`. - uint8 private constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; - - // Prefixes are prepended to the `path` within a leaf or extension node and - // allow us to differentiate between the two node types. `ODD` or `EVEN` is - // determined by the number of nibbles within the unprefixed `path`. If the - // number of nibbles if even, we need to insert an extra padding nibble so - // the resulting prefixed `path` has an even number of nibbles. - uint8 private constant PREFIX_EXTENSION_EVEN = 0; - uint8 private constant PREFIX_EXTENSION_ODD = 1; - uint8 private constant PREFIX_LEAF_EVEN = 2; - uint8 private constant PREFIX_LEAF_ODD = 3; - - // Just a utility constant. RLP represents `NULL` as 0x80. - bytes1 private constant RLP_NULL = bytes1(0x80); - - /** - * @notice Verifies a proof that a given key/value pair is present in the - * Merkle trie. - * @param _key Key of the node to search for, as a hex string. - * @param _value Value of the node to search for, as a hex string. - * @param _proof Merkle trie inclusion proof for the desired node. Unlike - * traditional Merkle trees, this proof is executed top-down and consists - * of a list of RLP-encoded nodes that make a path down to the target node. - * @param _root Known root of the Merkle trie. Used to verify that the - * included proof is correctly constructed. - * @return _verified `true` if the k/v pair exists in the trie, `false` - * otherwise. - */ - function verifyInclusionProof( - bytes memory _key, - bytes memory _value, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _verified) - { - (bool exists, bytes memory value) = get(_key, _proof, _root); - - return (exists && LibBytesUtils.equal(_value, value)); - } - - /** - * @notice Retrieves the value associated with a given key. - * @param _key Key to search for, as hex bytes. - * @param _proof Merkle trie inclusion proof for the key. - * @param _root Known root of the Merkle trie. - * @return _exists Whether or not the key exists. - * @return _value Value of the key if it exists. - */ - function get( - bytes memory _key, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _exists, bytes memory _value) - { - TrieNode[] memory proof = _parseProof(_proof); - (uint256 pathLength, bytes memory keyRemainder, bool isFinalNode) = - _walkNodePath(proof, _key, _root); - - bool exists = keyRemainder.length == 0; - - require(exists || isFinalNode, "Provided proof is invalid."); - - bytes memory value = exists ? _getNodeValue(proof[pathLength - 1]) : bytes(""); - - return (exists, value); - } - - /** - * @notice Walks through a proof using a provided key. - * @param _proof Inclusion proof to walk through. - * @param _key Key to use for the walk. - * @param _root Known root of the trie. - * @return _pathLength Length of the final path - * @return _keyRemainder Portion of the key remaining after the walk. - * @return _isFinalNode Whether or not we've hit a dead end. - */ - function _walkNodePath( - TrieNode[] memory _proof, - bytes memory _key, - bytes32 _root - ) - private - pure - returns (uint256 _pathLength, bytes memory _keyRemainder, bool _isFinalNode) - { - uint256 pathLength; - bytes memory key = LibBytesUtils.toNibbles(_key); - - bytes32 currentNodeID = _root; - uint256 currentKeyIndex = 0; - uint256 currentKeyIncrement = 0; - TrieNode memory currentNode; - - // Proof is top-down, so we start at the first element (root). - for (uint256 i; i < _proof.length; ++i) { - currentNode = _proof[i]; - currentKeyIndex += currentKeyIncrement; - - // Keep track of the proof elements we actually need. - // It's expensive to resize arrays, so this simply reduces gas - // costs. - pathLength += 1; - - if (currentKeyIndex == 0) { - // First proof element is always the root node. - require(keccak256(currentNode.encoded) == currentNodeID, "Invalid root hash"); - } else if (currentNode.encoded.length >= 32) { - // Nodes 32 bytes or larger are hashed inside branch nodes. - require( - keccak256(currentNode.encoded) == currentNodeID, "Invalid large internal hash" - ); - } else { - // Nodes smaller than 31 bytes aren't hashed. - require( - LibBytesUtils.toBytes32(currentNode.encoded) == currentNodeID, - "Invalid internal node hash" - ); - } - - if (currentNode.decoded.length == BRANCH_NODE_LENGTH) { - if (currentKeyIndex == key.length) { - // We've hit the end of the key - // meaning the value should be within this branch node. - break; - } else { - // We're not at the end of the key yet. - // Figure out what the next node ID should be and continue. - uint8 branchKey = uint8(key[currentKeyIndex]); - LibRLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey]; - currentNodeID = _getNodeID(nextNode); - currentKeyIncrement = 1; - continue; - } - } else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { - bytes memory path = _getNodePath(currentNode); - uint8 prefix = uint8(path[0]); - uint8 offset = 2 - (prefix % 2); - bytes memory pathRemainder = LibBytesUtils.slice(path, offset); - bytes memory keyRemainder = LibBytesUtils.slice(key, currentKeyIndex); - uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); - - if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { - if ( - pathRemainder.length == sharedNibbleLength - && keyRemainder.length == sharedNibbleLength - ) { - // The key within this leaf matches our key exactly. - // Increment the key index to reflect that we have no - // remainder. - currentKeyIndex += sharedNibbleLength; - } - - // We've hit a leaf node, so our next node should be NULL. - currentNodeID = bytes32(RLP_NULL); - break; - } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { - if (sharedNibbleLength != pathRemainder.length) { - // Our extension node is not identical to the remainder. - // We've hit the end of this path - // updates will need to modify this extension. - currentNodeID = bytes32(RLP_NULL); - break; - } else { - // Our extension shares some nibbles. - // Carry on to the next node. - currentNodeID = _getNodeID(currentNode.decoded[1]); - currentKeyIncrement = sharedNibbleLength; - continue; - } - } else { - revert("Received a node with an unknown prefix"); - } - } else { - revert("Received an unparseable node."); - } - } - - // If our node ID is NULL, then we're at a dead end. - bool isFinalNode = currentNodeID == bytes32(RLP_NULL); - return (pathLength, LibBytesUtils.slice(key, currentKeyIndex), isFinalNode); - } - - /** - * @notice Parses an RLP-encoded proof into something more useful. - * @param _proof RLP-encoded proof to parse. - * @return _parsed Proof parsed into easily accessible structs. - */ - function _parseProof(bytes memory _proof) private pure returns (TrieNode[] memory _parsed) { - LibRLPReader.RLPItem[] memory nodes = LibRLPReader.readList(_proof); - TrieNode[] memory proof = new TrieNode[](nodes.length); - - for (uint256 i; i < nodes.length; ++i) { - bytes memory encoded = LibRLPReader.readBytes(nodes[i]); - proof[i] = TrieNode({ encoded: encoded, decoded: LibRLPReader.readList(encoded) }); - } - - return proof; - } - - /** - * @notice Picks out the ID for a node. Node ID is referred to as the - * "hash" within the specification, but nodes < 32 bytes are not actually - * hashed. - * @param _node Node to pull an ID for. - * @return _nodeID ID for the node, depending on the size of its contents. - */ - function _getNodeID(LibRLPReader.RLPItem memory _node) private pure returns (bytes32 _nodeID) { - bytes memory nodeID; - - if (_node.length < 32) { - // Nodes smaller than 32 bytes are RLP encoded. - nodeID = LibRLPReader.readRawBytes(_node); - } else { - // Nodes 32 bytes or larger are hashed. - nodeID = LibRLPReader.readBytes(_node); - } - - return LibBytesUtils.toBytes32(nodeID); - } - - /** - * @notice Gets the path for a leaf or extension node. - * @param _node Node to get a path for. - * @return _path Node path, converted to an array of nibbles. - */ - function _getNodePath(TrieNode memory _node) private pure returns (bytes memory _path) { - return LibBytesUtils.toNibbles(LibRLPReader.readBytes(_node.decoded[0])); - } - - /** - * @notice Gets the path for a node. - * @param _node Node to get a value for. - * @return _value Node value, as hex bytes. - */ - function _getNodeValue(TrieNode memory _node) private pure returns (bytes memory _value) { - return LibRLPReader.readBytes(_node.decoded[_node.decoded.length - 1]); - } - - /** - * @notice Utility; determines the number of nibbles shared between two - * nibble arrays. - * @param _a First nibble array. - * @param _b Second nibble array. - * @return _shared Number of shared nibbles. - */ - /** - * @notice Utility; determines the number of nibbles shared between two - * nibble arrays. - * @param _a First nibble array. - * @param _b Second nibble array. - * @return _shared Number of shared nibbles. - */ - function _getSharedNibbleLength( - bytes memory _a, - bytes memory _b - ) - private - pure - returns (uint256 _shared) - { - uint256 i; - while (_a.length > i && _b.length > i && _a[i] == _b[i]) { - ++i; - } - return i; - } -} diff --git a/packages/protocol/contracts/thirdparty/LibRLPReader.sol b/packages/protocol/contracts/thirdparty/LibRLPReader.sol deleted file mode 100644 index e1c367fb1590..000000000000 --- a/packages/protocol/contracts/thirdparty/LibRLPReader.sol +++ /dev/null @@ -1,392 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/rlp/LibRLPReader.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity ^0.8.20; - -/** - * @title LibRLPReader - * @dev Adapted from "RLPReader" by Hamdi Allam (hamdi.allam97@gmail.com). - */ -library LibRLPReader { - uint256 internal constant MAX_LIST_LENGTH = 32; - - enum RLPItemType { - DATA_ITEM, - LIST_ITEM - } - - struct RLPItem { - uint256 length; - uint256 ptr; - } - - /** - * Converts bytes to a reference to memory position and length. - * @param _in Input bytes to convert. - * @return Output memory reference. - */ - function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory) { - uint256 ptr; - assembly { - ptr := add(_in, 32) - } - - return RLPItem({ length: _in.length, ptr: ptr }); - } - - /** - * Reads an RLP list value into a list of RLP items. - * @param _in RLP list value. - * @return Decoded RLP list items. - */ - function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory) { - (uint256 listOffset,, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.LIST_ITEM, "Invalid RLP list value."); - - // Solidity in-memory arrays can't be increased in size, but *can* be - // decreased in size by - // writing to the length. Since we can't know the number of RLP items - // without looping over - // the entire input, we'd have to loop twice to accurately size this - // array. It's easier to - // simply set a reasonable maximum list length and decrease the size - // before we finish. - RLPItem[] memory out = new RLPItem[](MAX_LIST_LENGTH); - - uint256 itemCount; - uint256 offset = listOffset; - while (offset < _in.length) { - require(itemCount < MAX_LIST_LENGTH, "Provided RLP list exceeds max list length."); - - (uint256 itemOffset, uint256 itemLength,) = - _decodeLength(RLPItem({ length: _in.length - offset, ptr: _in.ptr + offset })); - - out[itemCount] = RLPItem({ length: itemLength + itemOffset, ptr: _in.ptr + offset }); - - itemCount += 1; - offset += itemOffset + itemLength; - } - - // Decrease the array size to match the actual item count. - assembly { - mstore(out, itemCount) - } - - return out; - } - - /** - * Reads an RLP list value into a list of RLP items. - * @param _in RLP list value. - * @return Decoded RLP list items. - */ - function readList(bytes memory _in) internal pure returns (RLPItem[] memory) { - return readList(toRLPItem(_in)); - } - - /** - * Reads an RLP bytes value into bytes. - * @param _in RLP bytes value. - * @return Decoded bytes. - */ - function readBytes(RLPItem memory _in) internal pure returns (bytes memory) { - (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes value."); - - return _copy(_in.ptr, itemOffset, itemLength); - } - - /** - * Reads an RLP bytes value into bytes. - * @param _in RLP bytes value. - * @return Decoded bytes. - */ - function readBytes(bytes memory _in) internal pure returns (bytes memory) { - return readBytes(toRLPItem(_in)); - } - - /** - * Reads an RLP string value into a string. - * @param _in RLP string value. - * @return Decoded string. - */ - function readString(RLPItem memory _in) internal pure returns (string memory) { - return string(readBytes(_in)); - } - - /** - * Reads an RLP string value into a string. - * @param _in RLP string value. - * @return Decoded string. - */ - function readString(bytes memory _in) internal pure returns (string memory) { - return readString(toRLPItem(_in)); - } - - /** - * Reads an RLP bytes32 value into a bytes32. - * @param _in RLP bytes32 value. - * @return Decoded bytes32. - */ - function readBytes32(RLPItem memory _in) internal pure returns (bytes32) { - require(_in.length <= 33, "Invalid RLP bytes32 value."); - - (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); - - require(itemType == RLPItemType.DATA_ITEM, "Invalid RLP bytes32 value."); - - uint256 ptr = _in.ptr + itemOffset; - bytes32 out; - assembly { - out := mload(ptr) - - // Shift the bytes over to match the item size. - if lt(itemLength, 32) { out := div(out, exp(256, sub(32, itemLength))) } - } - - return out; - } - - /** - * Reads an RLP bytes32 value into a bytes32. - * @param _in RLP bytes32 value. - * @return Decoded bytes32. - */ - function readBytes32(bytes memory _in) internal pure returns (bytes32) { - return readBytes32(toRLPItem(_in)); - } - - /** - * Reads an RLP uint256 value into a uint256. - * @param _in RLP uint256 value. - * @return Decoded uint256. - */ - function readUint256(RLPItem memory _in) internal pure returns (uint256) { - return uint256(readBytes32(_in)); - } - - /** - * Reads an RLP uint256 value into a uint256. - * @param _in RLP uint256 value. - * @return Decoded uint256. - */ - function readUint256(bytes memory _in) internal pure returns (uint256) { - return readUint256(toRLPItem(_in)); - } - - /** - * Reads an RLP bool value into a bool. - * @param _in RLP bool value. - * @return Decoded bool. - */ - function readBool(RLPItem memory _in) internal pure returns (bool) { - require(_in.length == 1, "Invalid RLP boolean value."); - - uint256 ptr = _in.ptr; - uint256 out; - assembly { - out := byte(0, mload(ptr)) - } - - require(out == 0 || out == 1, "LibRLPReader: Invalid RLP boolean value, must be 0 or 1"); - - return out != 0; - } - - /** - * Reads an RLP bool value into a bool. - * @param _in RLP bool value. - * @return Decoded bool. - */ - function readBool(bytes memory _in) internal pure returns (bool) { - return readBool(toRLPItem(_in)); - } - - /** - * Reads an RLP address value into a address. - * @param _in RLP address value. - * @return Decoded address. - */ - function readAddress(RLPItem memory _in) internal pure returns (address) { - if (_in.length == 1) { - return address(0); - } - - require(_in.length == 21, "Invalid RLP address value."); - - return address(uint160(readUint256(_in))); - } - - /** - * Reads an RLP address value into a address. - * @param _in RLP address value. - * @return Decoded address. - */ - function readAddress(bytes memory _in) internal pure returns (address) { - return readAddress(toRLPItem(_in)); - } - - /** - * Reads the raw bytes of an RLP item. - * @param _in RLP item to read. - * @return Raw RLP bytes. - */ - function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory) { - return _copy(_in); - } - - /** - * Decodes the length of an RLP item. - * @param _in RLP item to decode. - * @return Offset of the encoded data. - * @return Length of the encoded data. - * @return RLP item type (LIST_ITEM or DATA_ITEM). - */ - function _decodeLength(RLPItem memory _in) - private - pure - returns (uint256, uint256, RLPItemType) - { - require(_in.length > 0, "RLP item cannot be null."); - - uint256 ptr = _in.ptr; - uint256 prefix; - assembly { - prefix := byte(0, mload(ptr)) - } - - if (prefix <= 0x7f) { - // Single byte. - - return (0, 1, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xb7) { - // Short string. - - // slither-disable-next-line variable-scope - uint256 strLen = prefix - 0x80; - - require(_in.length > strLen, "Invalid RLP short string."); - - return (1, strLen, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xbf) { - // Long string. - uint256 lenOfStrLen = prefix - 0xb7; - - require(_in.length > lenOfStrLen, "Invalid RLP long string length."); - - uint256 strLen; - assembly { - // Pick out the string length. - strLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfStrLen))) - } - - require(_in.length > lenOfStrLen + strLen, "Invalid RLP long string."); - - return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); - } else if (prefix <= 0xf7) { - // Short list. - // slither-disable-next-line variable-scope - uint256 listLen = prefix - 0xc0; - - require(_in.length > listLen, "Invalid RLP short list."); - - return (1, listLen, RLPItemType.LIST_ITEM); - } else { - // Long list. - uint256 lenOfListLen = prefix - 0xf7; - - require(_in.length > lenOfListLen, "Invalid RLP long list length."); - - uint256 listLen; - assembly { - // Pick out the list length. - listLen := div(mload(add(ptr, 1)), exp(256, sub(32, lenOfListLen))) - } - - require(_in.length > lenOfListLen + listLen, "Invalid RLP long list."); - - return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); - } - } - - /** - * Copies the bytes from a memory location. - * @param _src Pointer to the location to read from. - * @param _offset Offset to start reading from. - * @param _length Number of bytes to read. - * @return Copied bytes. - */ - function _copy( - uint256 _src, - uint256 _offset, - uint256 _length - ) - internal - pure - returns (bytes memory) - { - bytes memory result = new bytes(_length); - if (result.length == 0) { - return result; - } - - bytes memory src; - bytes memory dst; - assembly { - src := add(_src, _offset) - - dst := add(result, 32) - - for { let i := 0 } lt(i, _length) { i := add(i, 32) } { - mstore(add(dst, i), mload(add(src, i))) - } - } - - // Pick out the remaining bytes. - uint256 mask; - unchecked { - mask = 256 ** (32 - (_length % 32)) - 1; - } - - assembly { - mstore(dst, or(and(mload(src), not(mask)), and(mload(dst), mask))) - } - - return result; - } - - /** - * Copies an RLP item into bytes. - * @param _in RLP item to copy. - * @return Copied bytes. - */ - function _copy(RLPItem memory _in) private pure returns (bytes memory) { - return _copy(_in.ptr, 0, _in.length); - } -} diff --git a/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol b/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol deleted file mode 100644 index bfefe125f0bb..000000000000 --- a/packages/protocol/contracts/thirdparty/LibSecureMerkleTrie.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -// Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/trie/LibSecureMerkleTrie.sol -// (The MIT License) -// -// Copyright 2020-2021 Optimism -// Copyright 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity ^0.8.20; - -/* Library Imports */ -import "./LibMerkleTrie.sol"; - -/** - * @title LibSecureMerkleTrie - */ -library LibSecureMerkleTrie { - /*////////////////////////////////////////////////////////////// - INTERNAL FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Verifies a proof that a given key/value pair is present in the - * Merkle trie. - * @param _key Key of the node to search for, as a hex string. - * @param _value Value of the node to search for, as a hex string. - * @param _proof Merkle trie inclusion proof for the desired node. Unlike - * traditional Merkle trees, this proof is executed top-down and consists - * of a list of RLP-encoded nodes that make a path down to the target node. - * @param _root Known root of the Merkle trie. Used to verify that the - * included proof is correctly constructed. - * @return _verified `true` if the k/v pair exists in the trie, `false` - * otherwise. - */ - function verifyInclusionProof( - bytes memory _key, - bytes memory _value, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _verified) - { - bytes memory key = _getSecureKey(_key); - return LibMerkleTrie.verifyInclusionProof(key, _value, _proof, _root); - } - - /** - * @notice Retrieves the value associated with a given key. - * @param _key Key to search for, as hex bytes. - * @param _proof Merkle trie inclusion proof for the key. - * @param _root Known root of the Merkle trie. - * @return _exists Whether or not the key exists. - * @return _value Value of the key if it exists. - */ - function get( - bytes memory _key, - bytes memory _proof, - bytes32 _root - ) - internal - pure - returns (bool _exists, bytes memory _value) - { - bytes memory key = _getSecureKey(_key); - return LibMerkleTrie.get(key, _proof, _root); - } - - /*////////////////////////////////////////////////////////////// - PRIVATE FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /** - * Computes the secure counterpart to a key. - * @param _key Key to get a secure key from. - * @return _secureKey Secure version of the key. - */ - function _getSecureKey(bytes memory _key) private pure returns (bytes memory _secureKey) { - return bytes.concat(keccak256(_key)); - } -} diff --git a/packages/protocol/contracts/thirdparty/LibUint512Math.sol b/packages/protocol/contracts/thirdparty/LibUint512Math.sol deleted file mode 100644 index 60eef4bc0a60..000000000000 --- a/packages/protocol/contracts/thirdparty/LibUint512Math.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -// The MIT License (MIT) -// -// Copyright (c) 2021 Remco Bloemen -// Copyright (c) 2022-2023 Taiko Labs -// -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -pragma solidity ^0.8.20; - -/// @title LibUint512Math -library LibUint512Math { - /// @dev Multiplies two uint256 numbers to return a 512-bit result. - /// Taken from: https://xn--2-umb.com/17/full-mul/index.html - /// @param a The first uint256 operand. - /// @param b The second uint256 operand. - /// @return r0 The lower 256 bits of the result. - /// @return r1 The higher 256 bits of the result. - function mul(uint256 a, uint256 b) internal pure returns (uint256 r0, uint256 r1) { - assembly { - // Calculate modulo of the multiplication by the largest 256-bit - // number. - let mm := mulmod(a, b, not(0)) - // Standard 256-bit multiplication. - r0 := mul(a, b) - // Adjust for overflow, detect if there was a carry. - r1 := sub(sub(mm, r0), lt(mm, r0)) - } - } - - /// @dev Adds two 512-bit numbers represented by two pairs of uint256. - /// Taken from: - /// https://xn--2-umb.com/17/512-bit-division/#add-subtract-two-512-bit-numbers - /// @param a0 The lower 256 bits of the first number. - /// @param a1 The higher 256 bits of the first number. - /// @param b0 The lower 256 bits of the second number. - /// @param b1 The higher 256 bits of the second number. - /// @return r0 The lower 256 bits of the result. - /// @return r1 The higher 256 bits of the result. - function add( - uint256 a0, - uint256 a1, - uint256 b0, - uint256 b1 - ) - internal - pure - returns (uint256 r0, uint256 r1) - { - assembly { - // Standard 256-bit addition for lower bits. - r0 := add(a0, b0) - // Add the upper bits and account for carry from the lower bits. - r1 := add(add(a1, b1), lt(r0, a0)) - } - } -} diff --git a/packages/protocol/contracts/thirdparty/README.md b/packages/protocol/contracts/thirdparty/README.md new file mode 100644 index 000000000000..0102fe8cd679 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/README.md @@ -0,0 +1,9 @@ +# ABOUT THIRDPARTY CODE + +- /optimism: code copied from `packages/contracts-bedrock/src/libraries` in https://github.com/ethereum-optimism/optimism/releases/tag/op-batcher%2Fv1.4.3 as-is with only solidity pragma changed. + +- /solmate: code copied from https://github.com/transmissions11/solmate/blob/v7/src/utils/FixedPointMathLib.sol as-is with only solidity pragma changed. + +- /nomad-xyz: code copied from https://github.com/nomad-xyz/ExcessivelySafeCall/blob/main/src/ExcessivelySafeCall.sol with unused coded removed and solidity pragma changed. + +- /risczero: interface copied from https://sepolia.etherscan.io/address/0x83c2e9cd64b2a16d3908e94c7654f3864212e2f8#code as per: https://dev.risczero.com/api/blockchain-integration/contracts/verifier diff --git a/packages/protocol/contracts/thirdparty/optimism/Bytes.sol b/packages/protocol/contracts/thirdparty/optimism/Bytes.sol new file mode 100644 index 000000000000..6a6fba86e7d1 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/Bytes.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title Bytes +/// @notice Bytes is a library for manipulating byte arrays. +library Bytes { + /// @custom:attribution https://github.com/GNSPS/solidity-bytes-utils + /// @notice Slices a byte array with a given starting index and length. Returns a new byte array + /// as opposed to a pointer to the original array. Will throw if trying to slice more + /// bytes than exist in the array. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @param _length Length of the slice. + /// @return Slice of the input byte array. + function slice( + bytes memory _bytes, + uint256 _start, + uint256 _length + ) + internal + pure + returns (bytes memory) + { + unchecked { + require(_length + 31 >= _length, "slice_overflow"); + require(_start + _length >= _start, "slice_overflow"); + require(_bytes.length >= _start + _length, "slice_outOfBounds"); + } + + bytes memory tempBytes; + + assembly { + switch iszero(_length) + case 0 { + // Get a location of some free memory and store it in tempBytes as + // Solidity does for memory variables. + tempBytes := mload(0x40) + + // The first word of the slice result is potentially a partial + // word read from the original array. To read it, we calculate + // the length of that partial word and start copying that many + // bytes into the array. The first word we copy will start with + // data we don't care about, but the last `lengthmod` bytes will + // land at the beginning of the contents of the new array. When + // we're done copying, we overwrite the full first word with + // the actual length of the slice. + let lengthmod := and(_length, 31) + + // The multiplication in the next line is necessary + // because when slicing multiples of 32 bytes (lengthmod == 0) + // the following copy loop was copying the origin's length + // and then ending prematurely not copying everything it should. + let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) + let end := add(mc, _length) + + for { + // The multiplication in the next line has the same exact purpose + // as the one above. + let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) + } lt(mc, end) { + mc := add(mc, 0x20) + cc := add(cc, 0x20) + } { mstore(mc, mload(cc)) } + + mstore(tempBytes, _length) + + //update free-memory pointer + //allocating the array padded to 32 bytes like the compiler does now + mstore(0x40, and(add(mc, 31), not(31))) + } + //if we want a zero-length slice let's just return a zero-length array + default { + tempBytes := mload(0x40) + + //zero out the 32 bytes slice we are about to return + //we need to do it because Solidity does not garbage collect + mstore(tempBytes, 0) + + mstore(0x40, add(tempBytes, 0x20)) + } + } + + return tempBytes; + } + + /// @notice Slices a byte array with a given starting index up to the end of the original byte + /// array. Returns a new array rather than a pointer to the original. + /// @param _bytes Byte array to slice. + /// @param _start Starting index of the slice. + /// @return Slice of the input byte array. + function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) { + if (_start >= _bytes.length) { + return bytes(""); + } + return slice(_bytes, _start, _bytes.length - _start); + } + + /// @notice Converts a byte array into a nibble array by splitting each byte into two nibbles. + /// Resulting nibble array will be exactly twice as long as the input byte array. + /// @param _bytes Input byte array to convert. + /// @return Resulting nibble array. + function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) { + bytes memory _nibbles; + assembly { + // Grab a free memory offset for the new array + _nibbles := mload(0x40) + + // Load the length of the passed bytes array from memory + let bytesLength := mload(_bytes) + + // Calculate the length of the new nibble array + // This is the length of the input array times 2 + let nibblesLength := shl(0x01, bytesLength) + + // Update the free memory pointer to allocate memory for the new array. + // To do this, we add the length of the new array + 32 bytes for the array length + // rounded up to the nearest 32 byte boundary to the current free memory pointer. + mstore(0x40, add(_nibbles, and(not(0x1F), add(nibblesLength, 0x3F)))) + + // Store the length of the new array in memory + mstore(_nibbles, nibblesLength) + + // Store the memory offset of the _bytes array's contents on the stack + let bytesStart := add(_bytes, 0x20) + + // Store the memory offset of the nibbles array's contents on the stack + let nibblesStart := add(_nibbles, 0x20) + + // Loop through each byte in the input array + for { let i := 0x00 } lt(i, bytesLength) { i := add(i, 0x01) } { + // Get the starting offset of the next 2 bytes in the nibbles array + let offset := add(nibblesStart, shl(0x01, i)) + // Load the byte at the current index within the `_bytes` array + let b := byte(0x00, mload(add(bytesStart, i))) + + // Pull out the first nibble and store it in the new array + mstore8(offset, shr(0x04, b)) + // Pull out the second nibble and store it in the new array + mstore8(add(offset, 0x01), and(b, 0x0F)) + } + } + return _nibbles; + } + + /// @notice Compares two byte arrays by comparing their keccak256 hashes. + /// @param _bytes First byte array to compare. + /// @param _other Second byte array to compare. + /// @return true if the two byte arrays are equal, false otherwise. + function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) { + return keccak256(_bytes) == keccak256(_other); + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol new file mode 100644 index 000000000000..9164b7490edd --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPReader.sol @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @custom:attribution https://github.com/hamdiallam/Solidity-RLP +/// @title RLPReader +/// @notice RLPReader is a library for parsing RLP-encoded byte arrays into Solidity types. Adapted +/// from Solidity-RLP (https://github.com/hamdiallam/Solidity-RLP) by Hamdi Allam with +/// various tweaks to improve readability. (A shout-out to Optimism !) +library RLPReader { + /// @notice Custom pointer type to avoid confusion between pointers and uint256s. + type MemoryPointer is uint256; + + /// @notice RLP item types. + /// @custom:value DATA_ITEM Represents an RLP data item (NOT a list). + /// @custom:value LIST_ITEM Represents an RLP list item. + enum RLPItemType { + DATA_ITEM, + LIST_ITEM + } + + /// @notice Struct representing an RLP item. + /// @custom:field length Length of the RLP item. + /// @custom:field ptr Pointer to the RLP item in memory. + struct RLPItem { + uint256 length; + MemoryPointer ptr; + } + + /// @notice Max list length that this library will accept. + uint256 internal constant MAX_LIST_LENGTH = 32; + + /// @notice Converts bytes to a reference to memory position and length. + /// @param _in Input bytes to convert. + /// @return out_ Output memory reference. + function toRLPItem(bytes memory _in) internal pure returns (RLPItem memory out_) { + // Empty arrays are not RLP items. + require( + _in.length > 0, + "RLPReader: length of an RLP item must be greater than zero to be decodable" + ); + + MemoryPointer ptr; + assembly { + ptr := add(_in, 32) + } + + out_ = RLPItem({ length: _in.length, ptr: ptr }); + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(RLPItem memory _in) internal pure returns (RLPItem[] memory out_) { + (uint256 listOffset, uint256 listLength, RLPItemType itemType) = _decodeLength(_in); + + require( + itemType == RLPItemType.LIST_ITEM, + "RLPReader: decoded item type for list is not a list item" + ); + + require( + listOffset + listLength == _in.length, + "RLPReader: list item has an invalid data remainder" + ); + + // Solidity in-memory arrays can't be increased in size, but *can* be decreased in size by + // writing to the length. Since we can't know the number of RLP items without looping over + // the entire input, we'd have to loop twice to accurately size this array. It's easier to + // simply set a reasonable maximum list length and decrease the size before we finish. + out_ = new RLPItem[](MAX_LIST_LENGTH); + + uint256 itemCount = 0; + uint256 offset = listOffset; + while (offset < _in.length) { + (uint256 itemOffset, uint256 itemLength,) = _decodeLength( + RLPItem({ + length: _in.length - offset, + ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) + }) + ); + + // We don't need to check itemCount < out.length explicitly because Solidity already + // handles this check on our behalf, we'd just be wasting gas. + out_[itemCount] = RLPItem({ + length: itemLength + itemOffset, + ptr: MemoryPointer.wrap(MemoryPointer.unwrap(_in.ptr) + offset) + }); + + itemCount += 1; + offset += itemOffset + itemLength; + } + + // Decrease the array size to match the actual item count. + assembly { + mstore(out_, itemCount) + } + } + + /// @notice Reads an RLP list value into a list of RLP items. + /// @param _in RLP list value. + /// @return out_ Decoded RLP list items. + function readList(bytes memory _in) internal pure returns (RLPItem[] memory out_) { + out_ = readList(toRLPItem(_in)); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + (uint256 itemOffset, uint256 itemLength, RLPItemType itemType) = _decodeLength(_in); + + require( + itemType == RLPItemType.DATA_ITEM, + "RLPReader: decoded item type for bytes is not a data item" + ); + + require( + _in.length == itemOffset + itemLength, + "RLPReader: bytes value contains an invalid remainder" + ); + + out_ = _copy(_in.ptr, itemOffset, itemLength); + } + + /// @notice Reads an RLP bytes value into bytes. + /// @param _in RLP bytes value. + /// @return out_ Decoded bytes. + function readBytes(bytes memory _in) internal pure returns (bytes memory out_) { + out_ = readBytes(toRLPItem(_in)); + } + + /// @notice Reads the raw bytes of an RLP item. + /// @param _in RLP item to read. + /// @return out_ Raw RLP bytes. + function readRawBytes(RLPItem memory _in) internal pure returns (bytes memory out_) { + out_ = _copy(_in.ptr, 0, _in.length); + } + + /// @notice Decodes the length of an RLP item. + /// @param _in RLP item to decode. + /// @return offset_ Offset of the encoded data. + /// @return length_ Length of the encoded data. + /// @return type_ RLP item type (LIST_ITEM or DATA_ITEM). + function _decodeLength(RLPItem memory _in) + private + pure + returns (uint256 offset_, uint256 length_, RLPItemType type_) + { + // Short-circuit if there's nothing to decode, note that we perform this check when + // the user creates an RLP item via toRLPItem, but it's always possible for them to bypass + // that function and create an RLP item directly. So we need to check this anyway. + require( + _in.length > 0, + "RLPReader: length of an RLP item must be greater than zero to be decodable" + ); + + MemoryPointer ptr = _in.ptr; + uint256 prefix; + assembly { + prefix := byte(0, mload(ptr)) + } + + if (prefix <= 0x7f) { + // Single byte. + return (0, 1, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xb7) { + // Short string. + + // slither-disable-next-line variable-scope + uint256 strLen = prefix - 0x80; + + require( + _in.length > strLen, + "RLPReader: length of content must be greater than string length (short string)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + strLen != 1 || firstByteOfContent >= 0x80, + "RLPReader: invalid prefix, single byte < 0x80 are not prefixed (short string)" + ); + + return (1, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xbf) { + // Long string. + uint256 lenOfStrLen = prefix - 0xb7; + + require( + _in.length > lenOfStrLen, + "RLPReader: length of content must be > than length of string length (long string)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + firstByteOfContent != 0x00, + "RLPReader: length of content must not have any leading zeros (long string)" + ); + + uint256 strLen; + assembly { + strLen := shr(sub(256, mul(8, lenOfStrLen)), mload(add(ptr, 1))) + } + + require( + strLen > 55, + "RLPReader: length of content must be greater than 55 bytes (long string)" + ); + + require( + _in.length > lenOfStrLen + strLen, + "RLPReader: length of content must be greater than total length (long string)" + ); + + return (1 + lenOfStrLen, strLen, RLPItemType.DATA_ITEM); + } else if (prefix <= 0xf7) { + // Short list. + // slither-disable-next-line variable-scope + uint256 listLen = prefix - 0xc0; + + require( + _in.length > listLen, + "RLPReader: length of content must be greater than list length (short list)" + ); + + return (1, listLen, RLPItemType.LIST_ITEM); + } else { + // Long list. + uint256 lenOfListLen = prefix - 0xf7; + + require( + _in.length > lenOfListLen, + "RLPReader: length of content must be > than length of list length (long list)" + ); + + bytes1 firstByteOfContent; + assembly { + firstByteOfContent := and(mload(add(ptr, 1)), shl(248, 0xff)) + } + + require( + firstByteOfContent != 0x00, + "RLPReader: length of content must not have any leading zeros (long list)" + ); + + uint256 listLen; + assembly { + listLen := shr(sub(256, mul(8, lenOfListLen)), mload(add(ptr, 1))) + } + + require( + listLen > 55, + "RLPReader: length of content must be greater than 55 bytes (long list)" + ); + + require( + _in.length > lenOfListLen + listLen, + "RLPReader: length of content must be greater than total length (long list)" + ); + + return (1 + lenOfListLen, listLen, RLPItemType.LIST_ITEM); + } + } + + /// @notice Copies the bytes from a memory location. + /// @param _src Pointer to the location to read from. + /// @param _offset Offset to start reading from. + /// @param _length Number of bytes to read. + /// @return out_ Copied bytes. + function _copy( + MemoryPointer _src, + uint256 _offset, + uint256 _length + ) + private + pure + returns (bytes memory out_) + { + out_ = new bytes(_length); + if (_length == 0) { + return out_; + } + + // Mostly based on Solidity's copy_memory_to_memory: + // solhint-disable max-line-length + // https://github.com/ethereum/solidity/blob/34dd30d71b4da730488be72ff6af7083cf2a91f6/libsolidity/codegen/YulUtilFunctions.cpp#L102-L114 + uint256 src = MemoryPointer.unwrap(_src) + _offset; + assembly { + let dest := add(out_, 32) + let i := 0 + for { } lt(i, _length) { i := add(i, 32) } { mstore(add(dest, i), mload(add(src, i))) } + + if gt(i, _length) { mstore(add(dest, _length), 0) } + } + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/rlp/RLPWriter.sol b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPWriter.sol new file mode 100644 index 000000000000..f6eb0bf54a1a --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/rlp/RLPWriter.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @custom:attribution https://github.com/bakaoh/solidity-rlp-encode +/// @title RLPWriter +/// @author RLPWriter is a library for encoding Solidity types to RLP bytes. Adapted from Bakaoh's +/// RLPEncode library (https://github.com/bakaoh/solidity-rlp-encode) with minor +/// modifications to improve legibility. (A shout-out to Optimism !) +library RLPWriter { + /// @notice RLP encodes a byte string. + /// @param _in The byte string to encode. + /// @return out_ The RLP encoded string in bytes. + function writeBytes(bytes memory _in) internal pure returns (bytes memory out_) { + if (_in.length == 1 && uint8(_in[0]) < 128) { + out_ = _in; + } else { + out_ = abi.encodePacked(_writeLength(_in.length, 128), _in); + } + } + + /// @notice RLP encodes a uint. + /// @param _in The uint256 to encode. + /// @return out_ The RLP encoded uint256 in bytes. + function writeUint(uint256 _in) internal pure returns (bytes memory out_) { + out_ = writeBytes(_toBinary(_in)); + } + + /// @notice Encode the first byte and then the `len` in binary form if `length` is more than 55. + /// @param _len The length of the string or the payload. + /// @param _offset 128 if item is string, 192 if item is list. + /// @return out_ RLP encoded bytes. + function _writeLength(uint256 _len, uint256 _offset) private pure returns (bytes memory out_) { + if (_len < 56) { + out_ = new bytes(1); + out_[0] = bytes1(uint8(_len) + uint8(_offset)); + } else { + uint256 lenLen; + uint256 i = 1; + while (_len / i != 0) { + lenLen++; + i *= 256; + } + + out_ = new bytes(lenLen + 1); + out_[0] = bytes1(uint8(lenLen) + uint8(_offset) + 55); + for (i = 1; i <= lenLen; i++) { + out_[i] = bytes1(uint8((_len / (256 ** (lenLen - i))) % 256)); + } + } + } + + /// @notice Encode integer in big endian binary form with no leading zeroes. + /// @param _x The integer to encode. + /// @return out_ RLP encoded bytes. + function _toBinary(uint256 _x) private pure returns (bytes memory out_) { + bytes memory b = abi.encodePacked(_x); + + uint256 i = 0; + for (; i < 32; i++) { + if (b[i] != 0) { + break; + } + } + + out_ = new bytes(32 - i); + for (uint256 j = 0; j < out_.length; j++) { + out_[j] = b[i++]; + } + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol new file mode 100644 index 000000000000..3b883d0185ec --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrie.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { Bytes } from "../Bytes.sol"; +import { RLPReader } from "../rlp/RLPReader.sol"; + +/// @title MerkleTrie +/// @notice MerkleTrie is a small library for verifying standard Ethereum Merkle-Patricia trie +/// inclusion proofs. By default, this library assumes a hexary trie. One can change the +/// trie radix constant to support other trie radixes. +library MerkleTrie { + /// @notice Struct representing a node in the trie. + /// @custom:field encoded The RLP-encoded node. + /// @custom:field decoded The RLP-decoded node. + struct TrieNode { + bytes encoded; + RLPReader.RLPItem[] decoded; + } + + /// @notice Determines the number of elements per branch node. + uint256 internal constant TREE_RADIX = 16; + + /// @notice Branch nodes have TREE_RADIX elements and one value element. + uint256 internal constant BRANCH_NODE_LENGTH = TREE_RADIX + 1; + + /// @notice Leaf nodes and extension nodes have two elements, a `path` and a `value`. + uint256 internal constant LEAF_OR_EXTENSION_NODE_LENGTH = 2; + + /// @notice Prefix for even-nibbled extension node paths. + uint8 internal constant PREFIX_EXTENSION_EVEN = 0; + + /// @notice Prefix for odd-nibbled extension node paths. + uint8 internal constant PREFIX_EXTENSION_ODD = 1; + + /// @notice Prefix for even-nibbled leaf node paths. + uint8 internal constant PREFIX_LEAF_EVEN = 2; + + /// @notice Prefix for odd-nibbled leaf node paths. + uint8 internal constant PREFIX_LEAF_ODD = 3; + + /// @notice Verifies a proof that a given key/value pair is present in the trie. + /// @param _key Key of the node to search for, as a hex string. + /// @param _value Value of the node to search for, as a hex string. + /// @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle + /// trees, this proof is executed top-down and consists of a list of RLP-encoded + /// nodes that make a path down to the target node. + /// @param _root Known root of the Merkle trie. Used to verify that the included proof is + /// correctly constructed. + /// @return valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + valid_ = Bytes.equal(_value, get(_key, _proof, _root)); + } + + /// @notice Retrieves the value associated with a given key. + /// @param _key Key to search for, as hex bytes. + /// @param _proof Merkle trie inclusion proof for the key. + /// @param _root Known root of the Merkle trie. + /// @return value_ Value of the key if it exists. + function get( + bytes memory _key, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bytes memory value_) + { + require(_key.length > 0, "MerkleTrie: empty key"); + + TrieNode[] memory proof = _parseProof(_proof); + bytes memory key = Bytes.toNibbles(_key); + bytes memory currentNodeID = abi.encodePacked(_root); + uint256 currentKeyIndex = 0; + + // Proof is top-down, so we start at the first element (root). + for (uint256 i = 0; i < proof.length; i++) { + TrieNode memory currentNode = proof[i]; + + // Key index should never exceed total key length or we'll be out of bounds. + require(currentKeyIndex <= key.length, "MerkleTrie: key index exceeds total key length"); + + if (currentKeyIndex == 0) { + // First proof element is always the root node. + require( + Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), + "MerkleTrie: invalid root hash" + ); + } else if (currentNode.encoded.length >= 32) { + // Nodes 32 bytes or larger are hashed inside branch nodes. + require( + Bytes.equal(abi.encodePacked(keccak256(currentNode.encoded)), currentNodeID), + "MerkleTrie: invalid large internal hash" + ); + } else { + // Nodes smaller than 32 bytes aren't hashed. + require( + Bytes.equal(currentNode.encoded, currentNodeID), + "MerkleTrie: invalid internal node hash" + ); + } + + if (currentNode.decoded.length == BRANCH_NODE_LENGTH) { + if (currentKeyIndex == key.length) { + // Value is the last element of the decoded list (for branch nodes). There's + // some ambiguity in the Merkle trie specification because bytes(0) is a + // valid value to place into the trie, but for branch nodes bytes(0) can exist + // even when the value wasn't explicitly placed there. Geth treats a value of + // bytes(0) as "key does not exist" and so we do the same. + value_ = RLPReader.readBytes(currentNode.decoded[TREE_RADIX]); + require( + value_.length > 0, + "MerkleTrie: value length must be greater than zero (branch)" + ); + + // Extra proof elements are not allowed. + require( + i == proof.length - 1, + "MerkleTrie: value node must be last node in proof (branch)" + ); + + return value_; + } else { + // We're not at the end of the key yet. + // Figure out what the next node ID should be and continue. + uint8 branchKey = uint8(key[currentKeyIndex]); + RLPReader.RLPItem memory nextNode = currentNode.decoded[branchKey]; + currentNodeID = _getNodeID(nextNode); + currentKeyIndex += 1; + } + } else if (currentNode.decoded.length == LEAF_OR_EXTENSION_NODE_LENGTH) { + bytes memory path = _getNodePath(currentNode); + uint8 prefix = uint8(path[0]); + uint8 offset = 2 - (prefix % 2); + bytes memory pathRemainder = Bytes.slice(path, offset); + bytes memory keyRemainder = Bytes.slice(key, currentKeyIndex); + uint256 sharedNibbleLength = _getSharedNibbleLength(pathRemainder, keyRemainder); + + // Whether this is a leaf node or an extension node, the path remainder MUST be a + // prefix of the key remainder (or be equal to the key remainder) or the proof is + // considered invalid. + require( + pathRemainder.length == sharedNibbleLength, + "MerkleTrie: path remainder must share all nibbles with key" + ); + + if (prefix == PREFIX_LEAF_EVEN || prefix == PREFIX_LEAF_ODD) { + // Prefix of 2 or 3 means this is a leaf node. For the leaf node to be valid, + // the key remainder must be exactly equal to the path remainder. We already + // did the necessary byte comparison, so it's more efficient here to check that + // the key remainder length equals the shared nibble length, which implies + // equality with the path remainder (since we already did the same check with + // the path remainder and the shared nibble length). + require( + keyRemainder.length == sharedNibbleLength, + "MerkleTrie: key remainder must be identical to path remainder" + ); + + // Our Merkle Trie is designed specifically for the purposes of the Ethereum + // state trie. Empty values are not allowed in the state trie, so we can safely + // say that if the value is empty, the key should not exist and the proof is + // invalid. + value_ = RLPReader.readBytes(currentNode.decoded[1]); + require( + value_.length > 0, + "MerkleTrie: value length must be greater than zero (leaf)" + ); + + // Extra proof elements are not allowed. + require( + i == proof.length - 1, + "MerkleTrie: value node must be last node in proof (leaf)" + ); + + return value_; + } else if (prefix == PREFIX_EXTENSION_EVEN || prefix == PREFIX_EXTENSION_ODD) { + // Prefix of 0 or 1 means this is an extension node. We move onto the next node + // in the proof and increment the key index by the length of the path remainder + // which is equal to the shared nibble length. + currentNodeID = _getNodeID(currentNode.decoded[1]); + currentKeyIndex += sharedNibbleLength; + } else { + revert("MerkleTrie: received a node with an unknown prefix"); + } + } else { + revert("MerkleTrie: received an unparseable node"); + } + } + + revert("MerkleTrie: ran out of proof elements"); + } + + /// @notice Parses an array of proof elements into a new array that contains both the original + /// encoded element and the RLP-decoded element. + /// @param _proof Array of proof elements to parse. + /// @return proof_ Proof parsed into easily accessible structs. + function _parseProof(bytes[] memory _proof) private pure returns (TrieNode[] memory proof_) { + uint256 length = _proof.length; + proof_ = new TrieNode[](length); + for (uint256 i = 0; i < length; ++i) { + proof_[i] = TrieNode({ encoded: _proof[i], decoded: RLPReader.readList(_proof[i]) }); + } + } + + /// @notice Picks out the ID for a node. Node ID is referred to as the "hash" within the + /// specification, but nodes < 32 bytes are not actually hashed. + /// @param _node Node to pull an ID for. + /// @return id_ ID for the node, depending on the size of its contents. + function _getNodeID(RLPReader.RLPItem memory _node) private pure returns (bytes memory id_) { + id_ = _node.length < 32 ? RLPReader.readRawBytes(_node) : RLPReader.readBytes(_node); + } + + /// @notice Gets the path for a leaf or extension node. + /// @param _node Node to get a path for. + /// @return nibbles_ Node path, converted to an array of nibbles. + function _getNodePath(TrieNode memory _node) private pure returns (bytes memory nibbles_) { + nibbles_ = Bytes.toNibbles(RLPReader.readBytes(_node.decoded[0])); + } + + /// @notice Utility; determines the number of nibbles shared between two nibble arrays. + /// @param _a First nibble array. + /// @param _b Second nibble array. + /// @return shared_ Number of shared nibbles. + function _getSharedNibbleLength( + bytes memory _a, + bytes memory _b + ) + private + pure + returns (uint256 shared_) + { + uint256 max = (_a.length < _b.length) ? _a.length : _b.length; + for (; shared_ < max && _a[shared_] == _b[shared_];) { + unchecked { + ++shared_; + } + } + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrieProofVerifier.sol b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrieProofVerifier.sol new file mode 100644 index 000000000000..4a914e1045d0 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/trie/MerkleTrieProofVerifier.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { MerkleTrie } from "./MerkleTrie.sol"; + +/// @title MerkleTrieProofVerifier +/// @notice MerkleTrieProofVerifier is a thin wrapper around the MerkleTrie library that hashes the +/// input +/// keys. Ethereum's state trie hashes input keys before storing them. +contract MerkleTrieProofVerifier { + /// @notice Verifies a proof that a given key/value pair is present in the Merkle trie. + /// @param _key Key of the node to search for, as a hex string. + /// @param _value Value of the node to search for, as a hex string. + /// @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle + /// trees, this proof is executed top-down and consists of a list of RLP-encoded + /// nodes that make a path down to the target node. + /// @param _root Known root of the Merkle trie. Used to verify that the included proof is + /// correctly constructed. + /// @return valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + bytes memory key = _getSecureKey(_key); + valid_ = MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); + } + + /// @notice Retrieves the value associated with a given key. + /// @param _key Key to search for, as hex bytes. + /// @param _proof Merkle trie inclusion proof for the key. + /// @param _root Known root of the Merkle trie. + /// @return value_ Value of the key if it exists. + function get( + bytes memory _key, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bytes memory value_) + { + bytes memory key = _getSecureKey(_key); + value_ = MerkleTrie.get(key, _proof, _root); + } + + /// @notice Computes the hashed version of the input key. + /// @param _key Key to hash. + /// @return hash_ Hashed version of the key. + function _getSecureKey(bytes memory _key) private pure returns (bytes memory hash_) { + hash_ = abi.encodePacked(keccak256(_key)); + } +} diff --git a/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol b/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol new file mode 100644 index 000000000000..018084369030 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/optimism/trie/SecureMerkleTrie.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { MerkleTrie } from "./MerkleTrie.sol"; + +/// @title SecureMerkleTrie +/// @notice SecureMerkleTrie is a thin wrapper around the MerkleTrie library that hashes the input +/// keys. Ethereum's state trie hashes input keys before storing them. +library SecureMerkleTrie { + /// @notice Verifies a proof that a given key/value pair is present in the Merkle trie. + /// @param _key Key of the node to search for, as a hex string. + /// @param _value Value of the node to search for, as a hex string. + /// @param _proof Merkle trie inclusion proof for the desired node. Unlike traditional Merkle + /// trees, this proof is executed top-down and consists of a list of RLP-encoded + /// nodes that make a path down to the target node. + /// @param _root Known root of the Merkle trie. Used to verify that the included proof is + /// correctly constructed. + /// @return valid_ Whether or not the proof is valid. + function verifyInclusionProof( + bytes memory _key, + bytes memory _value, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bool valid_) + { + bytes memory key = _getSecureKey(_key); + valid_ = MerkleTrie.verifyInclusionProof(key, _value, _proof, _root); + } + + /// @notice Retrieves the value associated with a given key. + /// @param _key Key to search for, as hex bytes. + /// @param _proof Merkle trie inclusion proof for the key. + /// @param _root Known root of the Merkle trie. + /// @return value_ Value of the key if it exists. + function get( + bytes memory _key, + bytes[] memory _proof, + bytes32 _root + ) + internal + pure + returns (bytes memory value_) + { + bytes memory key = _getSecureKey(_key); + value_ = MerkleTrie.get(key, _proof, _root); + } + + /// @notice Computes the hashed version of the input key. + /// @param _key Key to hash. + /// @return hash_ Hashed version of the key. + function _getSecureKey(bytes memory _key) private pure returns (bytes memory hash_) { + hash_ = abi.encodePacked(keccak256(_key)); + } +} diff --git a/packages/protocol/contracts/thirdparty/risczero/IRiscZeroReceiptVerifier.sol b/packages/protocol/contracts/thirdparty/risczero/IRiscZeroReceiptVerifier.sol new file mode 100644 index 000000000000..841b1c994239 --- /dev/null +++ b/packages/protocol/contracts/thirdparty/risczero/IRiscZeroReceiptVerifier.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @notice Verifier interface for RISC Zero receipts of execution. +/// https://github.com/risc0/risc0-ethereum/blob/release-0.7/contracts/src/IRiscZeroVerifier.sol +interface IRiscZeroReceiptVerifier { + /// @notice Verify that the given seal is a valid RISC Zero proof of execution with the + /// given image ID, post-state digest, and journal digest. + /// @dev This method additionally ensures that the input hash is all-zeros (i.e. no + /// committed input), the exit code is (Halted, 0), and there are no assumptions (i.e. the + /// receipt is unconditional). + /// @param seal The encoded cryptographic proof (i.e. SNARK). + /// @param imageId The identifier for the guest program. + /// @param postStateDigest A hash of the final memory state. Required to run the verifier, but + /// otherwise can be left unconstrained for most use cases. + /// @param journalDigest The SHA-256 digest of the journal bytes. + /// @return true if the receipt passes the verification checks. The return code must be checked. + function verify( + bytes calldata seal, + bytes32 imageId, + bytes32 postStateDigest, + bytes32 journalDigest + ) + external + view + returns (bool); +} diff --git a/packages/protocol/contracts/thirdparty/solmate/LibFixedPointMath.sol b/packages/protocol/contracts/thirdparty/solmate/LibFixedPointMath.sol new file mode 100644 index 000000000000..2ad599363c2a --- /dev/null +++ b/packages/protocol/contracts/thirdparty/solmate/LibFixedPointMath.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +// Taken from the contract below, expWad() function tailored to Taiko's need +// https://github.com/transmissions11/solmate/blob/v7/src/utils/FixedPointMathLib.sol +pragma solidity 0.8.24; + +library LibFixedPointMath { + uint128 public constant MAX_EXP_INPUT = 135_305_999_368_893_231_588; + uint256 public constant SCALING_FACTOR = 1e18; // For fixed point representation factor + + error Overflow(); + + // Computes e^x in 1e18 fixed point. + function exp(int256 x) internal pure returns (int256 r) { + unchecked { + // Input x is in fixed point format, with scale factor 1/1e18. + + // When the result is < 0.5 we return zero. This happens when + // x <= floor(log(0.5e18) * 1e18) ~ -42e18 + if (x <= -42_139_678_854_452_767_551) { + return 0; + } + + // When the result is > (2**255 - 1) / 1e18 we can not represent it + // as an int256. This happens when x >= floor(log((2**255 -1) / + // 1e18) * 1e18) ~ 135. + if (x >= 135_305_999_368_893_231_589) revert Overflow(); + + // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * + // 2**96 + // for more intermediate precision and a binary basis. This base + // conversion + // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78. + x = (x << 78) / 5 ** 18; + + // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out + // powers of two + // such that exp(x) = exp(x') * 2**k, where k is an integer. + // Solving this gives k = round(x / log(2)) and x' = x - k * log(2). + int256 k = ((x << 96) / 54_916_777_467_707_473_351_141_471_128 + 2 ** 95) >> 96; + x = x - k * 54_916_777_467_707_473_351_141_471_128; + // k is in the range [-61, 195]. + + // Evaluate using a (6, 7)-term rational approximation. + // p is made monic, we'll multiply by a scale factor later. + int256 y = x + 1_346_386_616_545_796_478_920_950_773_328; + y = ((y * x) >> 96) + 57_155_421_227_552_351_082_224_309_758_442; + int256 p = y + x - 94_201_549_194_550_492_254_356_042_504_812; + p = ((p * y) >> 96) + 28_719_021_644_029_726_153_956_944_680_412_240; + p = p * x + (4_385_272_521_454_847_904_659_076_985_693_276 << 96); + + // We leave p in 2**192 basis so we don't need to scale it back up + // for the division. + int256 q = x - 2_855_989_394_907_223_263_936_484_059_900; + q = ((q * x) >> 96) + 50_020_603_652_535_783_019_961_831_881_945; + q = ((q * x) >> 96) - 533_845_033_583_426_703_283_633_433_725_380; + q = ((q * x) >> 96) + 3_604_857_256_930_695_427_073_651_918_091_429; + q = ((q * x) >> 96) - 14_423_608_567_350_463_180_887_372_962_807_573; + q = ((q * x) >> 96) + 26_449_188_498_355_588_339_934_803_723_976_023; + assembly { + // Div in assembly because solidity adds a zero check despite + // the `unchecked`. + // The q polynomial is known not to have zeros in the domain. + // (All roots are complex) + // No scaling required because p is already 2**96 too large. + r := sdiv(p, q) + } + // r should be in the range (0.09, 0.25) * 2**96. + + // We now need to multiply r by + // * the scale factor s = ~6.031367120..., + // * the 2**k factor from the range reduction, and + // * the 1e18 / 2**96 factor for base conversion. + // We do all of this at once, with an intermediate result in 2**213 + // basis + // so the final right shift is always by a positive amount. + r = int256( + (uint256(r) * 3_822_833_074_963_236_453_042_738_258_902_158_003_155_416_615_667) + >> uint256(195 - k) + ); + } + } +} diff --git a/packages/protocol/contracts/tko/BridgedTaikoToken.sol b/packages/protocol/contracts/tko/BridgedTaikoToken.sol new file mode 100644 index 000000000000..c847dda66653 --- /dev/null +++ b/packages/protocol/contracts/tko/BridgedTaikoToken.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../tokenvault/IBridgedERC20.sol"; +import "./TaikoTokenBase.sol"; + +/// @title BridgedTaikoToken +/// @notice The TaikoToken on L2 to support checkpoints and voting. For testnets, we do not need to +/// use this contract. +/// @custom:security-contact security@taiko.xyz +contract BridgedTaikoToken is TaikoTokenBase, IBridgedERC20 { + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address manager address. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + __ERC20_init("Taiko Token", "TKO"); + __ERC20Votes_init(); + __ERC20Permit_init("Taiko Token"); + } + + function mint( + address _account, + uint256 _amount + ) + external + override + whenNotPaused + onlyFromOwnerOrNamed(LibStrings.B_ERC20_VAULT) + nonReentrant + { + _mint(_account, _amount); + } + + function burn(uint256 _amount) + external + override + whenNotPaused + onlyFromOwnerOrNamed(LibStrings.B_ERC20_VAULT) + nonReentrant + { + _burn(msg.sender, _amount); + } + + /// @notice Gets the canonical token's address and chain ID. + /// @return The canonical token's address. + /// @return The canonical token's chain ID. + function canonical() public pure returns (address, uint256) { + // 0x10dea67478c5F8C5E2D90e5E9B26dBe60c54d800 is the TKO's mainnet address, + // 1 is the Ethereum's network id. + return (0x10dea67478c5F8C5E2D90e5E9B26dBe60c54d800, 1); + } + + function changeMigrationStatus(address, bool) public pure notImplemented { } +} diff --git a/packages/protocol/contracts/tko/TaikoToken.sol b/packages/protocol/contracts/tko/TaikoToken.sol new file mode 100644 index 000000000000..7bb9d380865a --- /dev/null +++ b/packages/protocol/contracts/tko/TaikoToken.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoTokenBase.sol"; + +/// @title TaikoToken +/// @notice The TaikoToken (TKO), in the protocol is used for prover collateral +/// in the form of bonds. It is an ERC20 token with 18 decimal places of precision. +/// @dev Labeled in AddressResolver as "taiko_token" +/// @dev On Ethereum, this contract is deployed behind a proxy at +/// 0x10dea67478c5F8C5E2D90e5E9B26dBe60c54d800 (token.taiko.eth) +/// @custom:security-contact security@taiko.xyz +contract TaikoToken is TaikoTokenBase { + address private constant _TAIKO_L1 = 0x06a9Ab27c7e2255df1815E6CC0168d7755Feb19a; + address private constant _ERC20_VAULT = 0x996282cA11E5DEb6B5D122CC3B9A1FcAAD4415Ab; + + error TT_INVALID_PARAM(); + + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _recipient The address to receive initial token minting. + function init(address _owner, address _recipient) public initializer { + __Essential_init(_owner); + __ERC20_init("Taiko Token", "TKO"); + __ERC20Votes_init(); + __ERC20Permit_init("Taiko Token"); + // Mint 1 billion tokens + _mint(_recipient, 1_000_000_000 ether); + } +} diff --git a/packages/protocol/contracts/tko/TaikoTokenBase.sol b/packages/protocol/contracts/tko/TaikoTokenBase.sol new file mode 100644 index 000000000000..2e30a8b9547e --- /dev/null +++ b/packages/protocol/contracts/tko/TaikoTokenBase.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; +import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; + +/// @notice TaikoToken was `EssentialContract, ERC20SnapshotUpgradeable, ERC20VotesUpgradeable`. +/// We use this contract to take 50 more slots to remove `ERC20SnapshotUpgradeable` from the parent +/// contract list. +/// We can simplify the code since we no longer need to maintain upgradability with Hekla. +abstract contract TaikoTokenBase0 is EssentialContract { + // solhint-disable var-name-mixedcase + uint256[50] private __slots_previously_used_by_ERC20SnapshotUpgradeable; +} + +/// @title TaikoTokenBase +/// @notice The base contract for both the canonical and the bridged Taiko token. +/// @custom:security-contact security@taiko.xyz +abstract contract TaikoTokenBase is TaikoTokenBase0, ERC20VotesUpgradeable { + uint256[50] private __gap; + + function clock() public view override returns (uint48) { + return SafeCastUpgradeable.toUint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) { + // See https://eips.ethereum.org/EIPS/eip-6372 + return "mode=timestamp"; + } + + function symbol() public pure override returns (string memory) { + return "TAIKO"; + } +} diff --git a/packages/protocol/contracts/tokenvault/BaseNFTVault.sol b/packages/protocol/contracts/tokenvault/BaseNFTVault.sol index c232545fa860..8b4e35595330 100644 --- a/packages/protocol/contracts/tokenvault/BaseNFTVault.sol +++ b/packages/protocol/contracts/tokenvault/BaseNFTVault.sol @@ -1,15 +1,11 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "./BaseVault.sol"; /// @title BaseNFTVault /// @notice Abstract contract for bridging NFTs across different chains. +/// @custom:security-contact security@taiko.xyz abstract contract BaseNFTVault is BaseVault { // Struct representing the canonical NFT on another chain. struct CanonicalNFT { @@ -23,41 +19,41 @@ abstract contract BaseNFTVault is BaseVault { string name; } - // Struct representing the details of a bridged token transfer operation. + /// @devStruct representing the details of a bridged token transfer operation. + /// 5 slots struct BridgeTransferOp { // Destination chain ID. uint64 destChainId; + // The owner of the bridge message on the destination chain. + address destOwner; // Recipient address. address to; + // Processing fee for the relayer. + uint64 fee; // Address of the token. address token; - // IDs of the tokens to transfer. + // Gas limit for the operation. + uint32 gasLimit; + // Token Id array uint256[] tokenIds; - // Amounts of tokens to transfer. + // Respective amounts per given token Ids. uint256[] amounts; - // Gas limit for the operation. - uint256 gasLimit; - // Processing fee for the relayer. - uint256 fee; - // Address for refund, if needed. - address refundTo; - // Optional memo. - string memo; } - // Constants for interface IDs. - bytes4 public constant ERC1155_INTERFACE_ID = 0xd9b67a26; - bytes4 public constant ERC721_INTERFACE_ID = 0x80ac58cd; - uint256 public constant MAX_TOKEN_PER_TXN = 10; - - // Mapping to store bridged NFTs and their canonical counterparts. - mapping(address => CanonicalNFT) public bridgedToCanonical; + /// @notice Mapping to store bridged NFTs and their canonical counterparts. + mapping(address btoken => CanonicalNFT canonical) public bridgedToCanonical; - // Mapping to store canonical NFTs and their bridged counterparts. - mapping(uint256 => mapping(address => address)) public canonicalToBridged; + /// @notice Mapping to store canonical NFTs and their bridged counterparts. + mapping(uint256 chainId => mapping(address ctoken => address btoken)) public canonicalToBridged; uint256[48] private __gap; + /// @notice Emitted when a new bridged token is deployed. + /// @param chainId The chain ID of the bridged token. + /// @param ctoken The address of the canonical token. + /// @param btoken The address of the bridged token. + /// @param ctokenSymbol The symbol of the canonical token. + /// @param ctokenName The name of the canonical token. event BridgedTokenDeployed( uint64 indexed chainId, address indexed ctoken, @@ -66,29 +62,57 @@ abstract contract BaseNFTVault is BaseVault { string ctokenName ); + /// @notice Emitted when a token is sent to another chain. + /// @param msgHash The hash of the message. + /// @param from The sender of the message. + /// @param to The recipient of the message. + /// @param destChainId The destination chain ID. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param tokenIds The IDs of the tokens. + /// @param amounts The amounts of the tokens. event TokenSent( bytes32 indexed msgHash, address indexed from, address indexed to, uint64 destChainId, + address ctoken, address token, uint256[] tokenIds, uint256[] amounts ); + /// @notice Emitted when a token is released on the current chain. + /// @param msgHash The hash of the message. + /// @param from The sender of the message. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param tokenIds The IDs of the tokens. + /// @param amounts The amounts of the tokens. event TokenReleased( bytes32 indexed msgHash, address indexed from, + address ctoken, address token, uint256[] tokenIds, uint256[] amounts ); + /// @notice Emitted when a token is received from another chain. + /// @param msgHash The hash of the message. + /// @param from The sender of the message. + /// @param to The recipient of the message. + /// @param srcChainId The source chain ID. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param tokenIds The IDs of the tokens. + /// @param amounts The amounts of the tokens. event TokenReceived( bytes32 indexed msgHash, address indexed from, address indexed to, uint64 srcChainId, + address ctoken, address token, uint256[] tokenIds, uint256[] amounts @@ -96,22 +120,15 @@ abstract contract BaseNFTVault is BaseVault { error VAULT_INVALID_TOKEN(); error VAULT_INVALID_AMOUNT(); - error VAULT_INVALID_USER(); - error VAULT_INVALID_SRC_CHAIN_ID(); error VAULT_INTERFACE_NOT_SUPPORTED(); error VAULT_TOKEN_ARRAY_MISMATCH(); - error VAULT_MAX_TOKEN_PER_TXN_EXCEEDED(); - modifier withValidOperation(BridgeTransferOp calldata op) { - if (op.tokenIds.length != op.amounts.length) { + modifier withValidOperation(BridgeTransferOp memory _op) { + if (_op.tokenIds.length != _op.amounts.length) { revert VAULT_TOKEN_ARRAY_MISMATCH(); } - if (op.tokenIds.length > MAX_TOKEN_PER_TXN) { - revert VAULT_MAX_TOKEN_PER_TXN_EXCEEDED(); - } - - if (op.token == address(0)) revert VAULT_INVALID_TOKEN(); + if (_op.token == address(0)) revert VAULT_INVALID_TOKEN(); _; } } diff --git a/packages/protocol/contracts/tokenvault/BaseVault.sol b/packages/protocol/contracts/tokenvault/BaseVault.sol index c110c179d659..18d17edb5515 100644 --- a/packages/protocol/contracts/tokenvault/BaseVault.sol +++ b/packages/protocol/contracts/tokenvault/BaseVault.sol @@ -1,59 +1,87 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "../bridge/IBridge.sol"; import "../common/EssentialContract.sol"; -import "../libs/LibAddress.sol"; -import "../libs/LibDeploy.sol"; +import "../common/LibStrings.sol"; +import "../libs/LibBytes.sol"; -abstract contract BaseVault is EssentialContract, IRecallableSender, IERC165Upgradeable { - error VAULT_PERMISSION_DENIED(); +/// @title INameSymbol +/// @notice Interface for contracts that provide name() and symbol() +/// functions. These functions may not be part of the official interface but are +/// used by some contracts. +/// @custom:security-contact security@taiko.xyz +interface INameSymbol { + function name() external view returns (string memory); + function symbol() external view returns (string memory); +} - modifier onlyFromBridge() { - if (msg.sender != resolve("bridge", false)) { - revert VAULT_PERMISSION_DENIED(); - } - _; - } - /// @notice Initializes the contract with the address manager. - /// @param addressManager Address manager contract address. +/// @title BaseVault +/// @notice This abstract contract provides a base implementation for vaults. +/// @custom:security-contact security@taiko.xyz +abstract contract BaseVault is + EssentialContract, + IRecallableSender, + IMessageInvocable, + IERC165Upgradeable +{ + using LibBytes for bytes; - function init(address addressManager) external initializer { - __Essential_init(addressManager); - } + uint256[50] private __gap; + + error VAULT_INSUFFICIENT_FEE(); + error VAULT_INVALID_TO_ADDR(); + error VAULT_PERMISSION_DENIED(); /// @notice Checks if the contract supports the given interface. - /// @param interfaceId The interface identifier. + /// @param _interfaceId The interface identifier. /// @return true if the contract supports the interface, false otherwise. - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { - return interfaceId == type(IRecallableSender).interfaceId; + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { + return _interfaceId == type(IRecallableSender).interfaceId + || _interfaceId == type(IMessageInvocable).interfaceId + || _interfaceId == type(IERC165Upgradeable).interfaceId; } + /// @notice Returns the name of the vault. + /// @return The name of the vault. function name() public pure virtual returns (bytes32); function checkProcessMessageContext() internal view - onlyFromBridge - returns (IBridge.Context memory ctx) + onlyFromNamed(LibStrings.B_BRIDGE) + returns (IBridge.Context memory ctx_) { - ctx = IBridge(msg.sender).context(); - address sender = resolve(ctx.srcChainId, name(), false); - if (ctx.from != sender) revert VAULT_PERMISSION_DENIED(); + ctx_ = IBridge(msg.sender).context(); + address selfOnSourceChain = resolve(ctx_.srcChainId, name(), false); + if (ctx_.from != selfOnSourceChain) revert VAULT_PERMISSION_DENIED(); } function checkRecallMessageContext() internal view - onlyFromBridge - returns (IBridge.Context memory ctx) + onlyFromNamed(LibStrings.B_BRIDGE) + returns (IBridge.Context memory ctx_) { - ctx = IBridge(msg.sender).context(); - if (ctx.from != msg.sender) revert VAULT_PERMISSION_DENIED(); + ctx_ = IBridge(msg.sender).context(); + if (ctx_.from != msg.sender) revert VAULT_PERMISSION_DENIED(); + } + + function checkToAddress(address _to) internal view { + if (_to == address(0) || _to == address(this)) revert VAULT_INVALID_TO_ADDR(); + } + + function safeSymbol(address _token) internal view returns (string memory symbol_) { + (bool success, bytes memory data) = + address(_token).staticcall(abi.encodeCall(INameSymbol.symbol, ())); + return success ? data.toString() : ""; + } + + function safeName(address _token) internal view returns (string memory) { + (bool success, bytes memory data) = + address(_token).staticcall(abi.encodeCall(INameSymbol.name, ())); + return success ? data.toString() : ""; } } diff --git a/packages/protocol/contracts/tokenvault/BridgedERC1155.sol b/packages/protocol/contracts/tokenvault/BridgedERC1155.sol index eeb7c9b608d0..f846fc4d5c1e 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC1155.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC1155.sol @@ -1,134 +1,116 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/utils/Strings.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol"; -import - "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/IERC1155MetadataURIUpgradeable.sol"; -import "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol"; import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; +import "./IBridgedERC1155.sol"; import "./LibBridgedToken.sol"; /// @title BridgedERC1155 /// @notice Contract for bridging ERC1155 tokens across different chains. +/// @custom:security-contact security@taiko.xyz contract BridgedERC1155 is EssentialContract, - IERC1155Upgradeable, - IERC1155MetadataURIUpgradeable, + IBridgedERC1155, + IBridgedERC1155Initializable, ERC1155Upgradeable { - address public srcToken; // Address of the source token contract. - uint256 public srcChainId; // Source chain ID where the token originates. - string private symbol_; // Symbol of the bridged token. - string private name_; // Name of the bridged token. + /// @notice Address of the source token contract. + address public srcToken; - uint256[46] private __gap; + /// @notice Source chain ID where the token originates. + uint256 public srcChainId; + + /// @dev Symbol of the bridged token. + string public symbol; - // Event triggered upon token transfer. - event Transfer(address indexed from, address indexed to, uint256 tokenId, uint256 amount); + /// @dev Name of the bridged token. + string public name; + + uint256[46] private __gap; - error BTOKEN_CANNOT_RECEIVE(); error BTOKEN_INVALID_PARAMS(); - /// @dev Initializer function to be called after deployment. - /// @param _addressManager The address of the address manager. - /// @param _srcToken Address of the source token. - /// @param _srcChainId Source chain ID. - /// @param _symbol Symbol of the bridged token. - /// @param _name Name of the bridged token. + /// @inheritdoc IBridgedERC1155Initializable function init( + address _owner, address _addressManager, address _srcToken, uint256 _srcChainId, - string memory _symbol, - string memory _name + string calldata _symbol, + string calldata _name ) external initializer { - if (_srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid) { - revert BTOKEN_INVALID_PARAMS(); - } - __Essential_init(_addressManager); - __ERC1155_init(""); + // Check if provided parameters are valid. + // The symbol and the name can be empty for ERC1155 tokens so we use some placeholder data + // for them instead. + LibBridgedToken.validateInputs(_srcToken, _srcChainId); + __Essential_init(_owner, _addressManager); + + // The token URI here is not important as the client will have to read the URI from the + // canonical contract to fetch meta data. + __ERC1155_init(LibBridgedToken.buildURI(_srcToken, _srcChainId, "")); + srcToken = _srcToken; srcChainId = _srcChainId; - symbol_ = _symbol; - name_ = _name; + symbol = _symbol; + name = _name; } - /// @dev Mints tokens. - /// @param account Address to receive the minted tokens. - /// @param tokenId ID of the token to mint. - /// @param amount Amount of tokens to mint. - function mint( - address account, - uint256 tokenId, - uint256 amount + /// @inheritdoc IBridgedERC1155 + function mintBatch( + address _to, + uint256[] calldata _tokenIds, + uint256[] calldata _amounts ) - public - nonReentrant + external whenNotPaused - onlyFromNamed("erc1155_vault") + onlyFromNamed(LibStrings.B_ERC1155_VAULT) + nonReentrant { - _mint(account, tokenId, amount, ""); + _mintBatch(_to, _tokenIds, _amounts, ""); } - /// @dev Burns tokens. - /// @param account Address from which tokens are burned. - /// @param tokenId ID of the token to burn. - /// @param amount Amount of tokens to burn. + /// @inheritdoc IBridgedERC1155 function burn( - address account, - uint256 tokenId, - uint256 amount + uint256 _id, + uint256 _amount ) - public - nonReentrant + external whenNotPaused - onlyFromNamed("erc1155_vault") + onlyFromNamed(LibStrings.B_ERC1155_VAULT) + nonReentrant { - _burn(account, tokenId, amount); + _burn(msg.sender, _id, _amount); } - /// @dev Safely transfers tokens from one address to another. - /// @param from Address from which tokens are transferred. - /// @param to Address to which tokens are transferred. - /// @param tokenId ID of the token to transfer. - /// @param amount Amount of tokens to transfer. - /// @param data Additional data. - function safeTransferFrom( - address from, - address to, - uint256 tokenId, - uint256 amount, - bytes memory data - ) - public - override(ERC1155Upgradeable, IERC1155Upgradeable) - nonReentrant - whenNotPaused - { - if (to == address(this)) { - revert BTOKEN_CANNOT_RECEIVE(); - } - return ERC1155Upgradeable.safeTransferFrom(from, to, tokenId, amount, data); + /// @inheritdoc IBridgedERC1155 + function canonical() external view returns (address, uint256) { + return (srcToken, srcChainId); } - /// @notice Gets the name of the bridged token. - /// @return The name. - function name() public view returns (string memory) { - return LibBridgedToken.buildName(name_, srcChainId); + function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { + return _interfaceId == type(IBridgedERC1155).interfaceId + || _interfaceId == type(IBridgedERC1155Initializable).interfaceId + || super.supportsInterface(_interfaceId); } - /// @notice Gets the symbol of the bridged token. - /// @return The symbol. - function symbol() public view returns (string memory) { - return LibBridgedToken.buildSymbol(symbol_); + function _beforeTokenTransfer( + address _operator, + address _from, + address _to, + uint256[] memory _ids, + uint256[] memory _amounts, + bytes memory _data + ) + internal + override + whenNotPaused + { + LibBridgedToken.checkToAddress(_to); + super._beforeTokenTransfer(_operator, _from, _to, _ids, _amounts, _data); } } diff --git a/packages/protocol/contracts/tokenvault/BridgedERC20.sol b/packages/protocol/contracts/tokenvault/BridgedERC20.sol index e019ad2763b5..e8a75b8cbe05 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC20.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC20.sol @@ -1,155 +1,178 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import - "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; +pragma solidity 0.8.24; +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; +import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; +import "./IBridgedERC20.sol"; import "./LibBridgedToken.sol"; -import "./BridgedERC20Base.sol"; /// @title BridgedERC20 /// @notice An upgradeable ERC20 contract that represents tokens bridged from /// another chain. -contract BridgedERC20 is BridgedERC20Base, IERC20MetadataUpgradeable, ERC20Upgradeable { - address public srcToken; // slot 1 - uint8 private srcDecimals; - uint256 public srcChainId; // slot 2 +/// @custom:security-contact security@taiko.xyz +contract BridgedERC20 is + EssentialContract, + IBridgedERC20, + IBridgedERC20Initializable, + IBridgedERC20Migratable, + IERC165Upgradeable, + ERC20Upgradeable +{ + /// @dev Slot 1. + address public srcToken; + + uint8 public __srcDecimals; + + /// @dev Slot 2. + uint256 public srcChainId; + + /// @dev Slot 3. + /// @notice The address of the contract to migrate tokens to or from. + address public migratingAddress; + + /// @notice If true, signals migrating 'to', false if migrating 'from'. + bool public migratingInbound; + + uint256[47] private __gap; + + /// @notice Emitted when the migration status is changed. + /// @param addr The address migrating 'to' or 'from'. + /// @param inbound If false then signals migrating 'from', true if migrating 'into'. + event MigrationStatusChanged(address addr, bool inbound); + + /// @notice Emitted when tokens are migrated to the new bridged token. + /// @param migratedTo The address of the bridged token. + /// @param account The address of the account. + /// @param amount The amount of tokens migrated. + event MigratedTo(address indexed migratedTo, address indexed account, uint256 amount); + + /// @notice Emitted when tokens are migrated from the old bridged token. + /// @param migratedFrom The address of the bridged token. + /// @param account The address of the account. + /// @param amount The amount of tokens migrated. + event MigratedFrom(address indexed migratedFrom, address indexed account, uint256 amount); - uint256[48] private __gap; - - error BTOKEN_CANNOT_RECEIVE(); error BTOKEN_INVALID_PARAMS(); + error BTOKEN_MINT_DISALLOWED(); - /// @notice Initializes the contract. - /// @dev Different BridgedERC20 Contract is deployed per unique _srcToken - /// (e.g., one for USDC, one for USDT, etc.). - /// @param _addressManager The address manager. - /// @param _srcToken The source token address. - /// @param _srcChainId The source chain ID. - /// @param _decimals The number of decimal places of the source token. - /// @param _symbol The symbol of the token. - /// @param _name The name of the token. + /// @inheritdoc IBridgedERC20Initializable function init( + address _owner, address _addressManager, address _srcToken, uint256 _srcChainId, uint8 _decimals, - string memory _symbol, - string memory _name + string calldata _symbol, + string calldata _name ) external initializer { // Check if provided parameters are valid - if ( - _srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid - || bytes(_symbol).length == 0 || bytes(_name).length == 0 - ) { - revert BTOKEN_INVALID_PARAMS(); - } - - // Initialize OwnerUUPSUpgradable and ERC20Upgradeable - __Essential_init(_addressManager); - __ERC20_init({ name_: _name, symbol_: _symbol }); + LibBridgedToken.validateInputs(_srcToken, _srcChainId); + __Essential_init(_owner, _addressManager); + __ERC20_init(_name, _symbol); // Set contract properties srcToken = _srcToken; srcChainId = _srcChainId; - srcDecimals = _decimals; + __srcDecimals = _decimals; } - /// @notice Transfers tokens from the caller to another account. - /// @dev Any address can call this. Caller must have at least 'amount' to - /// call this. - /// @param to The account to transfer tokens to. - /// @param amount The amount of tokens to transfer. - function transfer( - address to, - uint256 amount + /// @inheritdoc IBridgedERC20Migratable + function changeMigrationStatus( + address _migratingAddress, + bool _migratingInbound ) - public - override(ERC20Upgradeable, IERC20Upgradeable) - nonReentrant + external whenNotPaused - returns (bool) + onlyFromNamed(LibStrings.B_ERC20_VAULT) + nonReentrant { - if (to == address(this)) revert BTOKEN_CANNOT_RECEIVE(); - return ERC20Upgradeable.transfer(to, amount); + if (_migratingAddress == migratingAddress && _migratingInbound == migratingInbound) { + revert BTOKEN_INVALID_PARAMS(); + } + + migratingAddress = _migratingAddress; + migratingInbound = _migratingInbound; + emit MigrationStatusChanged(_migratingAddress, _migratingInbound); } - /// @notice Transfers tokens from one account to another account. - /// @dev Any address can call this. Caller must have allowance of at least - /// 'amount' for 'from's tokens. - /// @param from The account to transfer tokens from. - /// @param to The account to transfer tokens to. - /// @param amount The amount of tokens to transfer. - function transferFrom( - address from, - address to, - uint256 amount - ) - public - override(ERC20Upgradeable, IERC20Upgradeable) - nonReentrant - whenNotPaused - returns (bool) - { - if (to == address(this)) { - revert BTOKEN_CANNOT_RECEIVE(); + /// @inheritdoc IBridgedERC20 + function mint(address _account, uint256 _amount) external whenNotPaused nonReentrant { + // mint is disabled while migrating outbound. + if (isMigratingOut()) revert BTOKEN_MINT_DISALLOWED(); + + address _migratingAddress = migratingAddress; + if (msg.sender == _migratingAddress) { + // Inbound migration + emit MigratedFrom(_migratingAddress, _account, _amount); + } else { + // Bridging from vault + _authorizedMintBurn(msg.sender); } - return ERC20Upgradeable.transferFrom(from, to, amount); + + _mint(_account, _amount); } - /// @notice Gets the name of the token. - /// @return The name. - function name() - public - view - override(ERC20Upgradeable, IERC20MetadataUpgradeable) - returns (string memory) - { - return LibBridgedToken.buildName(super.name(), srcChainId); + /// @inheritdoc IBridgedERC20 + function burn(uint256 _amount) external whenNotPaused nonReentrant { + if (isMigratingOut()) { + // Outbound migration + address _migratingAddress = migratingAddress; + emit MigratedTo(_migratingAddress, msg.sender, _amount); + // Ask the new bridged token to mint token for the user. + IBridgedERC20(_migratingAddress).mint(msg.sender, _amount); + } else { + // When user wants to burn tokens only during 'migrating out' phase is possible. If + // ERC20Vault burns the tokens, that will go through the burn(amount) function. + _authorizedMintBurn(msg.sender); + } + + _burn(msg.sender, _amount); } - /// @notice Gets the symbol of the bridged token. - /// @return The symbol. - function symbol() - public - view - override(ERC20Upgradeable, IERC20MetadataUpgradeable) - returns (string memory) - { - return LibBridgedToken.buildSymbol(super.symbol()); + /// @inheritdoc IBridgedERC20 + function canonical() external view returns (address, uint256) { + return (srcToken, srcChainId); } /// @notice Gets the number of decimal places of the token. /// @return The number of decimal places of the token. - function decimals() - public - view - override(ERC20Upgradeable, IERC20MetadataUpgradeable) - returns (uint8) - { - return srcDecimals; + function decimals() public view override returns (uint8) { + return __srcDecimals; } - /// @notice Gets the canonical token's address and chain ID. - /// @return The canonical token's address and chain ID. - function canonical() public view returns (address, uint256) { - return (srcToken, srcChainId); + function isMigratingOut() public view returns (bool) { + return migratingAddress != address(0) && !migratingInbound; } - function _mintToken(address account, uint256 amount) internal override { - _mint(account, amount); + function supportsInterface(bytes4 _interfaceId) public pure returns (bool) { + return _interfaceId == type(IBridgedERC20).interfaceId + || _interfaceId == type(IBridgedERC20Initializable).interfaceId + || _interfaceId == type(IBridgedERC20Migratable).interfaceId + || _interfaceId == type(IERC20Upgradeable).interfaceId + || _interfaceId == type(IERC20MetadataUpgradeable).interfaceId + || _interfaceId == type(IERC165Upgradeable).interfaceId; } - function _burnToken(address from, uint256 amount) internal override { - _burn(from, amount); + function _beforeTokenTransfer( + address _from, + address _to, + uint256 _amount + ) + internal + override + whenNotPaused + { + LibBridgedToken.checkToAddress(_to); + return super._beforeTokenTransfer(_from, _to, _amount); } + + function _authorizedMintBurn(address addr) + private + onlyFromOwnerOrNamed(LibStrings.B_ERC20_VAULT) + { } } diff --git a/packages/protocol/contracts/tokenvault/BridgedERC20Base.sol b/packages/protocol/contracts/tokenvault/BridgedERC20Base.sol deleted file mode 100644 index 2adf59cc3eaf..000000000000 --- a/packages/protocol/contracts/tokenvault/BridgedERC20Base.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import "../common/EssentialContract.sol"; -import "./IBridgedERC20.sol"; - -abstract contract BridgedERC20Base is EssentialContract, IBridgedERC20 { - address public migratingAddress; // slot 1 - bool public migratingInbound; - uint256[49] private __gap; - - event MigrationStatusChanged(address addr, bool inbound); - - event MigratedTo(address indexed fromToken, address indexed account, uint256 amount); - event MigratedFrom(address indexed toToken, address indexed account, uint256 amount); - - error BB_PERMISSION_DENIED(); - error BB_INVALID_PARAMS(); - error BB_MINT_DISALLOWED(); - - function changeMigrationStatus( - address addr, - bool inbound - ) - external - whenNotPaused - onlyFromOwnerOrNamed("erc20_vault") - { - if (addr == migratingAddress && inbound == migratingInbound) { - revert BB_INVALID_PARAMS(); - } - - migratingAddress = addr; - migratingInbound = inbound; - emit MigrationStatusChanged(addr, inbound); - } - - function mint(address account, uint256 amount) public nonReentrant whenNotPaused { - // mint is disabled while migrating outbound. - if (migratingAddress != address(0) && !migratingInbound) revert BB_MINT_DISALLOWED(); - - if (msg.sender == migratingAddress) { - // Inbound migration - emit MigratedTo(migratingAddress, account, amount); - } else if (msg.sender != resolve("erc20_vault", true)) { - // Bridging from vault - revert BB_PERMISSION_DENIED(); - } - - _mintToken(account, amount); - } - - function burn(address account, uint256 amount) public nonReentrant whenNotPaused { - if (migratingAddress != address(0) && !migratingInbound) { - // Outbond migration - emit MigratedTo(migratingAddress, account, amount); - // Ask the new bridged token to mint token for the user. - IBridgedERC20(migratingAddress).mint(account, amount); - } else if (msg.sender != resolve("erc20_vault", true)) { - // Bridging to vault - revert RESOLVER_DENIED(); - } - - _burnToken(account, amount); - } - - function owner() public view override(IBridgedERC20, OwnableUpgradeable) returns (address) { - return super.owner(); - } - - function _mintToken(address account, uint256 amount) internal virtual; - function _burnToken(address from, uint256 amount) internal virtual; -} diff --git a/packages/protocol/contracts/tokenvault/BridgedERC721.sol b/packages/protocol/contracts/tokenvault/BridgedERC721.sol index 786527ce9364..3315c8bb56cc 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC721.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC721.sol @@ -1,130 +1,113 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; import "../common/EssentialContract.sol"; +import "../common/LibStrings.sol"; +import "./IBridgedERC721.sol"; import "./LibBridgedToken.sol"; /// @title BridgedERC721 /// @notice Contract for bridging ERC721 tokens across different chains. -contract BridgedERC721 is EssentialContract, ERC721Upgradeable { - address public srcToken; // Address of the source token contract. - uint256 public srcChainId; // Source chain ID where the token originates. +/// @custom:security-contact security@taiko.xyz +contract BridgedERC721 is + EssentialContract, + IBridgedERC721, + IBridgedERC721Initializable, + ERC721Upgradeable +{ + /// @notice Address of the source token contract. + address public srcToken; + + /// @notice Source chain ID where the token originates. + uint256 public srcChainId; uint256[48] private __gap; - error BTOKEN_CANNOT_RECEIVE(); error BTOKEN_INVALID_PARAMS(); error BTOKEN_INVALID_BURN(); - /// @dev Initializer function to be called after deployment. - /// @param _addressManager The address of the address manager. - /// @param _srcToken Address of the source token. - /// @param _srcChainId Source chain ID. - /// @param _symbol Symbol of the bridged token. - /// @param _name Name of the bridged token. + /// @inheritdoc IBridgedERC721Initializable function init( + address _owner, address _addressManager, address _srcToken, uint256 _srcChainId, - string memory _symbol, - string memory _name + string calldata _symbol, + string calldata _name ) external initializer { - if ( - _srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid - || bytes(_symbol).length == 0 || bytes(_name).length == 0 - ) { - revert BTOKEN_INVALID_PARAMS(); - } - __Essential_init(_addressManager); + // Check if provided parameters are valid + LibBridgedToken.validateInputs(_srcToken, _srcChainId); + __Essential_init(_owner, _addressManager); __ERC721_init(_name, _symbol); + srcToken = _srcToken; srcChainId = _srcChainId; } - /// @dev Mints tokens. - /// @param account Address to receive the minted token. - /// @param tokenId ID of the token to mint. + /// @inheritdoc IBridgedERC721 function mint( - address account, - uint256 tokenId + address _account, + uint256 _tokenId ) - public - nonReentrant + external whenNotPaused - onlyFromNamed("erc721_vault") - { - _mint(account, tokenId); - } - - /// @dev Burns tokens. - /// @param account Address from which the token is burned. - /// @param tokenId ID of the token to burn. - function burn( - address account, - uint256 tokenId - ) - public + onlyFromNamed(LibStrings.B_ERC721_VAULT) nonReentrant - whenNotPaused - onlyFromNamed("erc721_vault") { - // Check if the caller is the owner of the token. - if (ownerOf(tokenId) != account) { - revert BTOKEN_INVALID_BURN(); - } - _burn(tokenId); + _safeMint(_account, _tokenId); } - /// @dev Safely transfers tokens from one address to another. - /// @param from Address from which the token is transferred. - /// @param to Address to which the token is transferred. - /// @param tokenId ID of the token to transfer. - function transferFrom( - address from, - address to, - uint256 tokenId - ) - public - override(ERC721Upgradeable) - nonReentrant + /// @inheritdoc IBridgedERC721 + function burn(uint256 _tokenId) + external whenNotPaused + onlyFromNamed(LibStrings.B_ERC721_VAULT) + nonReentrant { - if (to == address(this)) { - revert BTOKEN_CANNOT_RECEIVE(); + // Check if the caller is the owner of the token. Somehow this is not done inside the + // _burn() function below. + if (ownerOf(_tokenId) != msg.sender) { + revert BTOKEN_INVALID_BURN(); } - return ERC721Upgradeable.transferFrom(from, to, tokenId); + _burn(_tokenId); } - /// @notice Gets the name of the token. - /// @return The name. - function name() public view override(ERC721Upgradeable) returns (string memory) { - return LibBridgedToken.buildName(super.name(), srcChainId); + /// @inheritdoc IBridgedERC721 + function canonical() external view returns (address, uint256) { + return (srcToken, srcChainId); } - /// @notice Gets the symbol of the bridged token. - /// @return The symbol. - function symbol() public view override(ERC721Upgradeable) returns (string memory) { - return LibBridgedToken.buildSymbol(super.symbol()); + /// @notice Returns the token URI. + /// @param _tokenId The token id. + /// @return The token URI following EIP-681. + function tokenURI(uint256 _tokenId) public view override returns (string memory) { + // https://github.com/crytic/slither/wiki/Detector-Documentation#abi-encodePacked-collision + // The abi.encodePacked() call below takes multiple dynamic arguments. This is known and + // considered acceptable in terms of risk. + return LibBridgedToken.buildURI(srcToken, srcChainId, Strings.toString(_tokenId)); } - /// @notice Gets the source token and source chain ID being bridged. - /// @return Source token address and source chain ID. - function source() public view returns (address, uint256) { - return (srcToken, srcChainId); + function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { + return _interfaceId == type(IBridgedERC721).interfaceId + || _interfaceId == type(IBridgedERC721Initializable).interfaceId + || super.supportsInterface(_interfaceId); } - /// @notice Returns an empty token URI. - function tokenURI(uint256) public pure virtual override returns (string memory) { - return ""; + function _beforeTokenTransfer( + address _from, + address _to, + uint256 _firstTokenId, + uint256 _batchSize + ) + internal + override + whenNotPaused + { + LibBridgedToken.checkToAddress(_to); + super._beforeTokenTransfer(_from, _to, _firstTokenId, _batchSize); } } diff --git a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol index 2f042481b07b..f6b82fe1c400 100644 --- a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol @@ -1,27 +1,12 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import - "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155ReceiverUpgradeable.sol"; -import "../bridge/IBridge.sol"; +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC1155/utils/ERC1155ReceiverUpgradeable.sol"; +import "../libs/LibAddress.sol"; +import "../common/LibStrings.sol"; +import "./IBridgedERC1155.sol"; import "./BaseNFTVault.sol"; -import "./BridgedERC1155.sol"; - -/// @title ERC1155NameAndSymbol -/// @notice Interface for ERC1155 contracts that provide name() and symbol() -/// functions. These functions may not be part of the official interface but are -/// used by -/// some contracts. -interface ERC1155NameAndSymbol { - function name() external view returns (string memory); - function symbol() external view returns (string memory); -} /// @title ERC1155Vault /// @dev Labeled in AddressResolver as "erc1155_vault" @@ -33,116 +18,100 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { uint256[50] private __gap; + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + __ERC1155Receiver_init(); + } /// @notice Transfers ERC1155 tokens to this vault and sends a message to /// the destination chain so the user can receive the same (bridged) tokens /// by invoking the message call. - /// @param op Option for sending the ERC1155 token. - function sendToken(BridgeTransferOp calldata op) + /// @param _op Option for sending the ERC1155 token. + /// @return message_ The constructed message. + + function sendToken(BridgeTransferOp calldata _op) external payable - nonReentrant whenNotPaused - withValidOperation(op) - returns (IBridge.Message memory _message) + withValidOperation(_op) + nonReentrant + returns (IBridge.Message memory message_) { - for (uint256 i; i < op.amounts.length; ++i) { - if (op.amounts[i] == 0) revert VAULT_INVALID_AMOUNT(); + if (msg.value < _op.fee) revert VAULT_INSUFFICIENT_FEE(); + + for (uint256 i; i < _op.amounts.length; ++i) { + if (_op.amounts[i] == 0) revert VAULT_INVALID_AMOUNT(); } // Check token interface support - if (!op.token.supportsInterface(ERC1155_INTERFACE_ID)) { + if (!_op.token.supportsInterface(type(IERC1155).interfaceId)) { revert VAULT_INTERFACE_NOT_SUPPORTED(); } - // Store variables in memory to avoid stack-too-deep error - uint256[] memory _amounts = op.amounts; - address _token = op.token; - uint256[] memory _tokenIds = op.tokenIds; + (bytes memory data, CanonicalNFT memory ctoken) = _handleMessage(_op); // Create a message to send to the destination chain - IBridge.Message memory message; - message.destChainId = op.destChainId; - message.data = _handleMessage(msg.sender, op); - message.owner = msg.sender; - message.to = resolve(message.destChainId, name(), false); - message.gasLimit = op.gasLimit; - message.value = msg.value - op.fee; - message.fee = op.fee; - message.refundTo = op.refundTo; - message.memo = op.memo; + IBridge.Message memory message = IBridge.Message({ + id: 0, // will receive a new value + from: address(0), // will receive a new value + srcChainId: 0, // will receive a new value + destChainId: _op.destChainId, + srcOwner: msg.sender, + destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, + to: resolve(_op.destChainId, name(), false), + value: msg.value - _op.fee, + fee: _op.fee, + gasLimit: _op.gasLimit, + data: data + }); // Send the message and obtain the message hash bytes32 msgHash; - (msgHash, _message) = - IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); + (msgHash, message_) = + IBridge(resolve(LibStrings.B_BRIDGE, false)).sendMessage{ value: msg.value }(message); // Emit TokenSent event emit TokenSent({ msgHash: msgHash, - from: _message.owner, - to: op.to, - destChainId: _message.destChainId, - token: _token, - tokenIds: _tokenIds, - amounts: _amounts + from: message_.srcOwner, + to: _op.to, + destChainId: message_.destChainId, + ctoken: ctoken.addr, + token: _op.token, + tokenIds: _op.tokenIds, + amounts: _op.amounts }); } - /// @notice This function can only be called by the bridge contract while - /// invoking a message call. See sendToken, which sets the data to invoke - /// this function. - /// @param ctoken The canonical ERC1155 token which may or may not live on - /// this chain. If not, a BridgedERC1155 contract will be deployed. - /// @param from The source address. - /// @param to The destination address. - /// @param tokenIds The tokenIds to be sent. - /// @param amounts The amounts to be sent. - function receiveToken( - CanonicalNFT calldata ctoken, - address from, - address to, - uint256[] memory tokenIds, - uint256[] memory amounts - ) - external - payable - nonReentrant - whenNotPaused - { + /// @inheritdoc IMessageInvocable + function onMessageInvocation(bytes calldata data) external payable whenNotPaused nonReentrant { + ( + CanonicalNFT memory ctoken, + address from, + address to, + uint256[] memory tokenIds, + uint256[] memory amounts + ) = abi.decode(data, (CanonicalNFT, address, address, uint256[], uint256[])); + // Check context validity + // `onlyFromBridge` checked in checkProcessMessageContext IBridge.Context memory ctx = checkProcessMessageContext(); - address _to = to == address(0) || to == address(this) ? from : to; - address token; - - unchecked { - if (ctoken.chainId == block.chainid) { - // Token lives on this chain - token = ctoken.addr; - for (uint256 i; i < tokenIds.length; ++i) { - ERC1155(token).safeTransferFrom({ - from: address(this), - to: _to, - id: tokenIds[i], - amount: amounts[i], - data: "" - }); - } - } else { - // Token does not live on this chain - token = _getOrDeployBridgedToken(ctoken); - for (uint256 i; i < tokenIds.length; ++i) { - BridgedERC1155(token).mint(_to, tokenIds[i], amounts[i]); - } - } - } + // Don't allow sending to disallowed addresses. + // Don't send the tokens back to `from` because `from` is on the source chain. + checkToAddress(to); - _to.sendEther(msg.value); + // Transfer the ETH and the tokens to the `to` address + address token = _transferTokens(ctoken, to, tokenIds, amounts); + to.sendEtherAndVerify(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, from: from, to: to, srcChainId: ctx.srcChainId, + ctoken: ctoken.addr, token: token, tokenIds: tokenIds, amounts: amounts @@ -157,46 +126,32 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { external payable override - nonReentrant whenNotPaused + nonReentrant { + // `onlyFromBridge` checked in checkRecallMessageContext checkRecallMessageContext(); - (CanonicalNFT memory nft,,, uint256[] memory tokenIds, uint256[] memory amounts) = - abi.decode(message.data[4:], (CanonicalNFT, address, address, uint256[], uint256[])); + (bytes memory data) = abi.decode(message.data[4:], (bytes)); + (CanonicalNFT memory ctoken,,, uint256[] memory tokenIds, uint256[] memory amounts) = + abi.decode(data, (CanonicalNFT, address, address, uint256[], uint256[])); - if (nft.addr == address(0)) revert VAULT_INVALID_TOKEN(); - - unchecked { - if (bridgedToCanonical[nft.addr].addr != address(0)) { - for (uint256 i; i < tokenIds.length; ++i) { - BridgedERC1155(nft.addr).mint(message.owner, tokenIds[i], amounts[i]); - } - } else { - for (uint256 i; i < tokenIds.length; ++i) { - ERC1155(nft.addr).safeTransferFrom({ - from: address(this), - to: message.owner, - id: tokenIds[i], - amount: amounts[i], - data: "" - }); - } - } - } - // Send back Ether - message.owner.sendEther(message.value); + // Transfer the ETH and tokens back to the owner + address token = _transferTokens(ctoken, message.srcOwner, tokenIds, amounts); + message.srcOwner.sendEtherAndVerify(message.value); // Emit TokenReleased event emit TokenReleased({ msgHash: msgHash, - from: message.owner, - token: nft.addr, + from: message.srcOwner, + ctoken: ctoken.addr, + token: token, tokenIds: tokenIds, amounts: amounts }); } + /// @notice See {ERC1155ReceiverUpgradeable-onERC1155BatchReceived}. function onERC1155BatchReceived( address, address, @@ -211,6 +166,7 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { return IERC1155ReceiverUpgradeable.onERC1155BatchReceived.selector; } + /// @notice See {ERC1155ReceiverUpgradeable-onERC1155Received}. function onERC1155Received( address, address, @@ -225,108 +181,126 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { return IERC1155ReceiverUpgradeable.onERC1155Received.selector; } - /// @dev See {IERC165-supportsInterface}. - function supportsInterface(bytes4 interfaceId) + /// @dev See {BaseVault-supportsInterface}. + /// @param _interfaceId The interface identifier. + /// @return true if supports, else otherwise. + function supportsInterface(bytes4 _interfaceId) public view - virtual override(BaseVault, ERC1155ReceiverUpgradeable) returns (bool) { - return interfaceId == type(ERC1155ReceiverUpgradeable).interfaceId - || BaseVault.supportsInterface(interfaceId); + // Here we cannot user `super.supportsInterface(_interfaceId)` + return BaseVault.supportsInterface(_interfaceId) + || ERC1155ReceiverUpgradeable.supportsInterface(_interfaceId); } + /// @inheritdoc BaseVault function name() public pure override returns (bytes32) { - return "erc1155_vault"; + return LibStrings.B_ERC1155_VAULT; + } + + /// @dev Transfers ERC1155 tokens to the `to` address. + /// @param ctoken CanonicalNFT data. + /// @param to The address to transfer the tokens to. + /// @param tokenIds The token IDs to transfer. + /// @param amounts The amounts to transfer. + /// @return token The address of the token. + function _transferTokens( + CanonicalNFT memory ctoken, + address to, + uint256[] memory tokenIds, + uint256[] memory amounts + ) + private + returns (address token) + { + if (ctoken.chainId == block.chainid) { + // Token lives on this chain + token = ctoken.addr; + IERC1155(token).safeBatchTransferFrom(address(this), to, tokenIds, amounts, ""); + } else { + // Token does not live on this chain + token = _getOrDeployBridgedToken(ctoken); + IBridgedERC1155(token).mintBatch(to, tokenIds, amounts); + } } /// @dev Handles the message on the source chain and returns the encoded /// call on the destination call. - /// @param user The user's address. - /// @param op BridgeTransferOp data. - /// @return msgData Encoded message data. - function _handleMessage( - address user, - BridgeTransferOp memory op - ) + /// @param _op BridgeTransferOp data. + /// @return msgData_ Encoded message data. + /// @return ctoken_ The canonical token. + function _handleMessage(BridgeTransferOp calldata _op) private - returns (bytes memory msgData) + returns (bytes memory msgData_, CanonicalNFT memory ctoken_) { - CanonicalNFT memory nft; unchecked { // is a btoken, meaning, it does not live on this chain - if (bridgedToCanonical[op.token].addr != address(0)) { - nft = bridgedToCanonical[op.token]; - for (uint256 i; i < op.tokenIds.length; ++i) { - BridgedERC1155(op.token).burn(user, op.tokenIds[i], op.amounts[i]); + CanonicalNFT storage _ctoken = bridgedToCanonical[_op.token]; + if (_ctoken.addr != address(0)) { + ctoken_ = _ctoken; + IERC1155(_op.token).safeBatchTransferFrom( + msg.sender, address(this), _op.tokenIds, _op.amounts, "" + ); + for (uint256 i; i < _op.tokenIds.length; ++i) { + IBridgedERC1155(_op.token).burn(_op.tokenIds[i], _op.amounts[i]); } } else { // is a ctoken token, meaning, it lives on this chain - nft = CanonicalNFT({ + ctoken_ = CanonicalNFT({ chainId: uint64(block.chainid), - addr: op.token, - symbol: "", - name: "" + addr: _op.token, + symbol: safeSymbol(_op.token), + name: safeName(_op.token) }); - ERC1155NameAndSymbol t = ERC1155NameAndSymbol(op.token); - try t.name() returns (string memory _name) { - nft.name = _name; - } catch { } - try t.symbol() returns (string memory _symbol) { - nft.symbol = _symbol; - } catch { } - for (uint256 i; i < op.tokenIds.length; ++i) { - ERC1155(op.token).safeTransferFrom({ - from: msg.sender, - to: address(this), - id: op.tokenIds[i], - amount: op.amounts[i], - data: "" - }); - } + + IERC1155(_op.token).safeBatchTransferFrom( + msg.sender, address(this), _op.tokenIds, _op.amounts, "" + ); } } - msgData = abi.encodeWithSelector( - this.receiveToken.selector, nft, user, op.to, op.tokenIds, op.amounts + msgData_ = abi.encodeCall( + this.onMessageInvocation, + abi.encode(ctoken_, msg.sender, _op.to, _op.tokenIds, _op.amounts) ); } /// @dev Retrieve or deploy a bridged ERC1155 token contract. - /// @param ctoken CanonicalNFT data. - /// @return btoken Address of the bridged token contract. - function _getOrDeployBridgedToken(CanonicalNFT memory ctoken) + /// @param _ctoken CanonicalNFT data. + /// @return btoken_ Address of the bridged token contract. + function _getOrDeployBridgedToken(CanonicalNFT memory _ctoken) private - returns (address btoken) + returns (address btoken_) { - btoken = canonicalToBridged[ctoken.chainId][ctoken.addr]; - if (btoken == address(0)) { - btoken = _deployBridgedToken(ctoken); + btoken_ = canonicalToBridged[_ctoken.chainId][_ctoken.addr]; + if (btoken_ == address(0)) { + btoken_ = _deployBridgedToken(_ctoken); } } /// @dev Deploy a new BridgedNFT contract and initialize it. /// This must be called before the first time a bridged token is sent to /// this chain. - /// @param ctoken CanonicalNFT data. - /// @return btoken Address of the deployed bridged token contract. - function _deployBridgedToken(CanonicalNFT memory ctoken) private returns (address btoken) { - bytes memory data = bytes.concat( - BridgedERC1155.init.selector, - abi.encode(addressManager, ctoken.addr, ctoken.chainId, ctoken.symbol, ctoken.name) + /// @param _ctoken CanonicalNFT data. + /// @return btoken_ Address of the deployed bridged token contract. + function _deployBridgedToken(CanonicalNFT memory _ctoken) private returns (address btoken_) { + bytes memory data = abi.encodeCall( + IBridgedERC1155Initializable.init, + (owner(), addressManager, _ctoken.addr, _ctoken.chainId, _ctoken.symbol, _ctoken.name) ); - btoken = LibDeploy.deployERC1967Proxy(resolve("bridged_erc1155", false), owner(), data); + btoken_ = address(new ERC1967Proxy(resolve(LibStrings.B_BRIDGED_ERC1155, false), data)); - bridgedToCanonical[btoken] = ctoken; - canonicalToBridged[ctoken.chainId][ctoken.addr] = btoken; + bridgedToCanonical[btoken_] = _ctoken; + canonicalToBridged[_ctoken.chainId][_ctoken.addr] = btoken_; emit BridgedTokenDeployed({ - chainId: ctoken.chainId, - ctoken: ctoken.addr, - btoken: btoken, - ctokenSymbol: ctoken.symbol, - ctokenName: ctoken.name + chainId: _ctoken.chainId, + ctoken: _ctoken.addr, + btoken: btoken_, + ctokenSymbol: _ctoken.symbol, + ctokenName: _ctoken.name }); } } diff --git a/packages/protocol/contracts/tokenvault/ERC20Vault.sol b/packages/protocol/contracts/tokenvault/ERC20Vault.sol index 7671cce4a199..fbe0d82a3b14 100644 --- a/packages/protocol/contracts/tokenvault/ERC20Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC20Vault.sol @@ -1,27 +1,30 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ +pragma solidity 0.8.24; -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "../bridge/IBridge.sol"; -import "./BridgedERC20.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import "../bridge/IQuotaManager.sol"; +import "../common/LibStrings.sol"; +import "../libs/LibAddress.sol"; +import "./IBridgedERC20.sol"; import "./BaseVault.sol"; /// @title ERC20Vault -/// @dev Labeled in AddressResolver as "erc20_vault" /// @notice This vault holds all ERC20 tokens (excluding Ether) that users have /// deposited. It also manages the mapping between canonical ERC20 tokens and -/// their bridged tokens. +/// their bridged tokens. This vault does not support rebase/elastic tokens. +/// @dev Labeled in AddressResolver as "erc20_vault". +/// @custom:security-contact security@taiko.xyz contract ERC20Vault is BaseVault { + using Address for address; using LibAddress for address; - using SafeERC20 for ERC20; + using SafeERC20 for IERC20; + + uint256 public constant MIN_MIGRATION_DELAY = 90 days; - // Structs for canonical ERC20 tokens and transfer operations + /// @dev Represents a canonical ERC20 token. struct CanonicalERC20 { uint64 chainId; address addr; @@ -30,28 +33,48 @@ contract ERC20Vault is BaseVault { string name; } + /// @dev Represents an operation to send tokens to another chain. + /// 4 slots struct BridgeTransferOp { + // Destination chain ID. uint64 destChainId; + // The owner of the bridge message on the destination chain. + address destOwner; + // Recipient address. address to; + // Processing fee for the relayer. + uint64 fee; + // Address of the token. address token; + // Gas limit for the operation. + uint32 gasLimit; + // Amount to be bridged. uint256 amount; - uint256 gasLimit; - uint256 fee; - address refundTo; - string memo; } - // Mappings from btokens to their canonical tokens. - mapping(address => CanonicalERC20) public bridgedToCanonical; + /// @notice Mappings from bridged tokens to their canonical tokens. + mapping(address btoken => CanonicalERC20 canonical) public bridgedToCanonical; + + /// @notice Mappings from canonical tokens to their bridged tokens. Also storing + /// the chainId for tokens across other chains aside from Ethereum. + mapping(uint256 chainId => mapping(address ctoken => address btoken)) public canonicalToBridged; - // Mappings from canonical tokens to their btokens. Also storing chainId for - // tokens across other chains aside from Ethereum. - mapping(uint256 => mapping(address => address)) public canonicalToBridged; + /// @notice Mappings from bridged tokens to their blacklist status. + mapping(address btoken => bool denied) public btokenDenylist; - mapping(address btoken => bool blacklisted) public btokenBlacklist; + /// @notice Mappings from ctoken to its last migration timestamp. + mapping(uint256 chainId => mapping(address ctoken => uint256 timestamp)) public + lastMigrationStart; uint256[46] private __gap; + /// @notice Emitted when a new bridged token is deployed. + /// @param srcChainId The chain ID of the canonical token. + /// @param ctoken The address of the canonical token. + /// @param btoken The address of the bridged token. + /// @param ctokenSymbol The symbol of the canonical token. + /// @param ctokenName The name of the canonical token. + /// @param ctokenDecimal The decimal of the canonical token. event BridgedTokenDeployed( uint256 indexed srcChainId, address indexed ctoken, @@ -61,6 +84,14 @@ contract ERC20Vault is BaseVault { uint8 ctokenDecimal ); + /// @notice Emitted when a bridged token is changed. + /// @param srcChainId The chain ID of the canonical token. + /// @param ctoken The address of the canonical token. + /// @param btokenOld The address of the old bridged token. + /// @param btokenNew The address of the new bridged token. + /// @param ctokenSymbol The symbol of the canonical token. + /// @param ctokenName The name of the canonical token. + /// @param ctokenDecimal The decimal of the canonical token. event BridgedTokenChanged( uint256 indexed srcChainId, address indexed ctoken, @@ -71,22 +102,50 @@ contract ERC20Vault is BaseVault { uint8 ctokenDecimal ); + /// @notice Emitted when a token is sent to another chain. + /// @param msgHash The hash of the message. + /// @param from The address of the sender. + /// @param to The address of the recipient. + /// @param canonicalChainId The chain ID of the canonical token. + /// @param destChainId The chain ID of the destination chain. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param amount The amount of tokens sent. event TokenSent( bytes32 indexed msgHash, address indexed from, address indexed to, + uint64 canonicalChainId, uint64 destChainId, + address ctoken, address token, uint256 amount ); + + /// @notice Emitted when a token is released from a message. + /// @param msgHash The hash of the message. + /// @param from The address of the sender. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param amount The amount of tokens released. event TokenReleased( - bytes32 indexed msgHash, address indexed from, address token, uint256 amount + bytes32 indexed msgHash, address indexed from, address ctoken, address token, uint256 amount ); + + /// @notice Emitted when a token is received from another chain. + /// @param msgHash The hash of the message. + /// @param from The address of the sender. + /// @param to The address of the recipient. + /// @param srcChainId The chain ID of the source chain. + /// @param ctoken The address of the canonical token. + /// @param token The address of the bridged token. + /// @param amount The amount of tokens received. event TokenReceived( bytes32 indexed msgHash, address indexed from, address indexed to, uint64 srcChainId, + address ctoken, address token, uint256 amount ); @@ -95,237 +154,268 @@ contract ERC20Vault is BaseVault { error VAULT_CTOKEN_MISMATCH(); error VAULT_INVALID_TOKEN(); error VAULT_INVALID_AMOUNT(); + error VAULT_INVALID_CTOKEN(); error VAULT_INVALID_NEW_BTOKEN(); - error VAULT_NOT_SAME_OWNER(); + error VAULT_LAST_MIGRATION_TOO_CLOSE(); + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + } + + /// @notice Change bridged token. + /// @param _ctoken The canonical token. + /// @param _btokenNew The new bridged token address. + /// @return btokenOld_ The old bridged token address. function changeBridgedToken( - CanonicalERC20 calldata ctoken, - address btokenNew + CanonicalERC20 calldata _ctoken, + address _btokenNew ) external - nonReentrant - whenNotPaused onlyOwner - returns (address btokenOld) + nonReentrant + returns (address btokenOld_) { - if (btokenNew == address(0) || bridgedToCanonical[btokenNew].addr != address(0)) { + if ( + _btokenNew == address(0) || bridgedToCanonical[_btokenNew].addr != address(0) + || !_btokenNew.isContract() + ) { revert VAULT_INVALID_NEW_BTOKEN(); } - if (btokenBlacklist[btokenNew]) revert VAULT_BTOKEN_BLACKLISTED(); + if (_ctoken.addr == address(0) || _ctoken.chainId == block.chainid) { + revert VAULT_INVALID_CTOKEN(); + } + + if (btokenDenylist[_btokenNew]) revert VAULT_BTOKEN_BLACKLISTED(); - if (IBridgedERC20(btokenNew).owner() != owner()) { - revert VAULT_NOT_SAME_OWNER(); + uint256 _lastMigrationStart = lastMigrationStart[_ctoken.chainId][_ctoken.addr]; + if (block.timestamp < _lastMigrationStart + MIN_MIGRATION_DELAY) { + revert VAULT_LAST_MIGRATION_TOO_CLOSE(); } - btokenOld = canonicalToBridged[ctoken.chainId][ctoken.addr]; + btokenOld_ = canonicalToBridged[_ctoken.chainId][_ctoken.addr]; - if (btokenOld != address(0)) { - CanonicalERC20 memory _ctoken = bridgedToCanonical[btokenOld]; + if (btokenOld_ != address(0)) { + CanonicalERC20 memory ctoken = bridgedToCanonical[btokenOld_]; - // Check that the ctoken must match the saved one. - if ( - _ctoken.decimals != ctoken.decimals - || keccak256(bytes(_ctoken.symbol)) != keccak256(bytes(ctoken.symbol)) - || keccak256(bytes(_ctoken.name)) != keccak256(bytes(ctoken.name)) - ) revert VAULT_CTOKEN_MISMATCH(); + // The ctoken must match the saved one. + if (keccak256(abi.encode(_ctoken)) != keccak256(abi.encode(ctoken))) { + revert VAULT_CTOKEN_MISMATCH(); + } - delete bridgedToCanonical[btokenOld]; - btokenBlacklist[btokenOld] = true; + delete bridgedToCanonical[btokenOld_]; + btokenDenylist[btokenOld_] = true; // Start the migration - IBridgedERC20(btokenOld).changeMigrationStatus(btokenNew, false); - IBridgedERC20(btokenNew).changeMigrationStatus(btokenOld, true); - } else { - IBridgedERC20(btokenNew).changeMigrationStatus(address(0), false); + if ( + btokenOld_.supportsInterface(type(IBridgedERC20Migratable).interfaceId) + && _btokenNew.supportsInterface(type(IBridgedERC20Migratable).interfaceId) + ) { + IBridgedERC20Migratable(btokenOld_).changeMigrationStatus(_btokenNew, false); + IBridgedERC20Migratable(_btokenNew).changeMigrationStatus(btokenOld_, true); + } } - bridgedToCanonical[btokenNew] = ctoken; - canonicalToBridged[ctoken.chainId][ctoken.addr] = btokenNew; + bridgedToCanonical[_btokenNew] = _ctoken; + canonicalToBridged[_ctoken.chainId][_ctoken.addr] = _btokenNew; + lastMigrationStart[_ctoken.chainId][_ctoken.addr] = block.timestamp; emit BridgedTokenChanged({ - srcChainId: ctoken.chainId, - ctoken: ctoken.addr, - btokenOld: btokenOld, - btokenNew: btokenNew, - ctokenSymbol: ctoken.symbol, - ctokenName: ctoken.name, - ctokenDecimal: ctoken.decimals + srcChainId: _ctoken.chainId, + ctoken: _ctoken.addr, + btokenOld: btokenOld_, + btokenNew: _btokenNew, + ctokenSymbol: _ctoken.symbol, + ctokenName: _ctoken.name, + ctokenDecimal: _ctoken.decimals }); } /// @notice Transfers ERC20 tokens to this vault and sends a message to the /// destination chain so the user can receive the same amount of tokens by /// invoking the message call. - /// @param op Option for sending ERC20 tokens. - function sendToken(BridgeTransferOp calldata op) + /// @param _op Option for sending ERC20 tokens. + /// @return message_ The constructed message. + function sendToken(BridgeTransferOp calldata _op) external payable - nonReentrant whenNotPaused - returns (IBridge.Message memory _message) + nonReentrant + returns (IBridge.Message memory message_) { - if (op.amount == 0) revert VAULT_INVALID_AMOUNT(); - if (op.token == address(0)) revert VAULT_INVALID_TOKEN(); - if (btokenBlacklist[op.token]) revert VAULT_BTOKEN_BLACKLISTED(); - - uint256 _amount; - IBridge.Message memory message; - - (message.data, _amount) = - _handleMessage({ user: msg.sender, token: op.token, amount: op.amount, to: op.to }); - - message.destChainId = op.destChainId; - message.owner = msg.sender; - message.to = resolve(op.destChainId, name(), false); - message.gasLimit = op.gasLimit; - message.value = msg.value - op.fee; - message.fee = op.fee; - message.refundTo = op.refundTo; - message.memo = op.memo; + if (_op.amount == 0) revert VAULT_INVALID_AMOUNT(); + if (_op.token == address(0)) revert VAULT_INVALID_TOKEN(); + if (btokenDenylist[_op.token]) revert VAULT_BTOKEN_BLACKLISTED(); + if (msg.value < _op.fee) revert VAULT_INSUFFICIENT_FEE(); + + (bytes memory data, CanonicalERC20 memory ctoken, uint256 balanceChange) = + _handleMessage(_op); + + IBridge.Message memory message = IBridge.Message({ + id: 0, // will receive a new value + from: address(0), // will receive a new value + srcChainId: 0, // will receive a new value + destChainId: _op.destChainId, + srcOwner: msg.sender, + destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, + to: resolve(_op.destChainId, name(), false), + value: msg.value - _op.fee, + fee: _op.fee, + gasLimit: _op.gasLimit, + data: data + }); bytes32 msgHash; - (msgHash, _message) = - IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); + (msgHash, message_) = + IBridge(resolve(LibStrings.B_BRIDGE, false)).sendMessage{ value: msg.value }(message); emit TokenSent({ msgHash: msgHash, - from: _message.owner, - to: op.to, - destChainId: op.destChainId, - token: op.token, - amount: _amount + from: message_.srcOwner, + to: _op.to, + canonicalChainId: ctoken.chainId, + destChainId: _op.destChainId, + ctoken: ctoken.addr, + token: _op.token, + amount: balanceChange }); } - /// @notice Receive bridged ERC20 tokens and Ether. - /// @param ctoken Canonical ERC20 data for the token being received. - /// @param from Source address. - /// @param to Destination address. - /// @param amount Amount of tokens being received. - function receiveToken( - CanonicalERC20 calldata ctoken, - address from, - address to, - uint256 amount - ) - external - payable - nonReentrant - whenNotPaused - { + /// @inheritdoc IMessageInvocable + function onMessageInvocation(bytes calldata _data) public payable whenNotPaused nonReentrant { + (CanonicalERC20 memory ctoken, address from, address to, uint256 amount) = + abi.decode(_data, (CanonicalERC20, address, address, uint256)); + + // `onlyFromBridge` checked in checkProcessMessageContext IBridge.Context memory ctx = checkProcessMessageContext(); - address _to = to == address(0) || to == address(this) ? from : to; - address token; - if (ctoken.chainId == block.chainid) { - token = ctoken.addr; - ERC20(token).safeTransfer(_to, amount); - } else { - token = _getOrDeployBridgedToken(ctoken); - IBridgedERC20(token).mint(_to, amount); - } + // Don't allow sending to disallowed addresses. + // Don't send the tokens back to `from` because `from` is on the source chain. + checkToAddress(to); - _to.sendEther(msg.value); + // Transfer the ETH and the tokens to the `to` address + address token = _transferTokens(ctoken, to, amount); + to.sendEtherAndVerify(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, from: from, to: to, srcChainId: ctx.srcChainId, + ctoken: ctoken.addr, token: token, amount: amount }); } + /// @inheritdoc IRecallableSender function onMessageRecalled( - IBridge.Message calldata message, - bytes32 msgHash + IBridge.Message calldata _message, + bytes32 _msgHash ) external payable override - nonReentrant whenNotPaused + nonReentrant { + // `onlyFromBridge` checked in checkRecallMessageContext checkRecallMessageContext(); - (, address token,, uint256 amount) = - abi.decode(message.data[4:], (CanonicalERC20, address, address, uint256)); + (bytes memory data) = abi.decode(_message.data[4:], (bytes)); + (CanonicalERC20 memory ctoken,,, uint256 amount) = + abi.decode(data, (CanonicalERC20, address, address, uint256)); - if (token == address(0)) revert VAULT_INVALID_TOKEN(); + // Transfer the ETH and tokens back to the owner + address token = _transferTokens(ctoken, _message.srcOwner, amount); + _message.srcOwner.sendEtherAndVerify(_message.value); - if (amount > 0) { - if (bridgedToCanonical[token].addr != address(0)) { - IBridgedERC20(token).mint(message.owner, amount); - } else { - ERC20(token).safeTransfer(message.owner, amount); - } - } - - message.owner.sendEther(message.value); - - emit TokenReleased({ msgHash: msgHash, from: message.owner, token: token, amount: amount }); + emit TokenReleased({ + msgHash: _msgHash, + from: _message.srcOwner, + ctoken: ctoken.addr, + token: token, + amount: amount + }); } + /// @inheritdoc BaseVault function name() public pure override returns (bytes32) { - return "erc20_vault"; + return LibStrings.B_ERC20_VAULT; + } + + function _transferTokens( + CanonicalERC20 memory _ctoken, + address _to, + uint256 _amount + ) + private + returns (address token_) + { + if (_ctoken.chainId == block.chainid) { + token_ = _ctoken.addr; + IERC20(token_).safeTransfer(_to, _amount); + } else { + token_ = _getOrDeployBridgedToken(_ctoken); + //For native bridged tokens (like USDC), the mint() signature is the same, so no need to + // check. + IBridgedERC20(token_).mint(_to, _amount); + } + _consumeTokenQuota(token_, _amount); } /// @dev Handles the message on the source chain and returns the encoded /// call on the destination call. - /// @param user The user's address. - /// @param token The token address. - /// @param to To address. - /// @param amount Amount to be sent. - /// @return msgData Encoded message data. - /// @return _balanceChange User token balance actual change after the token + /// @param _op The BridgeTransferOp object. + /// @return msgData_ Encoded message data. + /// @return ctoken_ The canonical token. + /// @return balanceChange_ User token balance actual change after the token /// transfer. This value is calculated so we do not assume token balance - /// change is the amount of token transfered away. - function _handleMessage( - address user, - address token, - address to, - uint256 amount - ) + /// change is the amount of token transferred away. + function _handleMessage(BridgeTransferOp calldata _op) private - returns (bytes memory msgData, uint256 _balanceChange) + returns (bytes memory msgData_, CanonicalERC20 memory ctoken_, uint256 balanceChange_) { - CanonicalERC20 memory ctoken; - // If it's a bridged token - if (bridgedToCanonical[token].addr != address(0)) { - ctoken = bridgedToCanonical[token]; - IBridgedERC20(token).burn(msg.sender, amount); - _balanceChange = amount; + CanonicalERC20 storage _ctoken = bridgedToCanonical[_op.token]; + if (_ctoken.addr != address(0)) { + ctoken_ = _ctoken; + // Following the "transfer and burn" pattern, as used by USDC + IERC20(_op.token).safeTransferFrom(msg.sender, address(this), _op.amount); + IBridgedERC20(_op.token).burn(_op.amount); + balanceChange_ = _op.amount; } else { // If it's a canonical token - ERC20 t = ERC20(token); - ctoken = CanonicalERC20({ + ctoken_ = CanonicalERC20({ chainId: uint64(block.chainid), - addr: token, - decimals: t.decimals(), - symbol: t.symbol(), - name: t.name() + addr: _op.token, + decimals: _safeDecimals(_op.token), + symbol: safeSymbol(_op.token), + name: safeName(_op.token) }); // Query the balance then query it again to get the actual amount of // token transferred into this address, this is more accurate than // simply using `amount` -- some contract may deduct a fee from the // transferred amount. + IERC20 t = IERC20(_op.token); uint256 _balance = t.balanceOf(address(this)); - t.transferFrom({ from: msg.sender, to: address(this), amount: amount }); - _balanceChange = t.balanceOf(address(this)) - _balance; + t.safeTransferFrom(msg.sender, address(this), _op.amount); + balanceChange_ = t.balanceOf(address(this)) - _balance; } - msgData = - abi.encodeWithSelector(this.receiveToken.selector, ctoken, user, to, _balanceChange); + msgData_ = abi.encodeCall( + this.onMessageInvocation, abi.encode(ctoken_, msg.sender, _op.to, balanceChange_) + ); } /// @dev Retrieve or deploy a bridged ERC20 token contract. /// @param ctoken CanonicalERC20 data. /// @return btoken Address of the bridged token contract. - function _getOrDeployBridgedToken(CanonicalERC20 calldata ctoken) + function _getOrDeployBridgedToken(CanonicalERC20 memory ctoken) private returns (address btoken) { @@ -341,10 +431,11 @@ contract ERC20Vault is BaseVault { /// this chain. /// @param ctoken CanonicalERC20 data. /// @return btoken Address of the deployed bridged token contract. - function _deployBridgedToken(CanonicalERC20 calldata ctoken) private returns (address btoken) { - bytes memory data = bytes.concat( - BridgedERC20.init.selector, - abi.encode( + function _deployBridgedToken(CanonicalERC20 memory ctoken) private returns (address btoken) { + bytes memory data = abi.encodeCall( + IBridgedERC20Initializable.init, + ( + owner(), addressManager, ctoken.addr, ctoken.chainId, @@ -354,8 +445,7 @@ contract ERC20Vault is BaseVault { ) ); - btoken = LibDeploy.deployERC1967Proxy(resolve("bridged_erc20", false), owner(), data); - + btoken = address(new ERC1967Proxy(resolve(LibStrings.B_BRIDGED_ERC20, false), data)); bridgedToCanonical[btoken] = ctoken; canonicalToBridged[ctoken.chainId][ctoken.addr] = btoken; @@ -368,4 +458,17 @@ contract ERC20Vault is BaseVault { ctokenDecimal: ctoken.decimals }); } + + function _consumeTokenQuota(address _token, uint256 _amount) private { + address quotaManager = resolve(LibStrings.B_QUOTA_MANAGER, true); + if (quotaManager != address(0)) { + IQuotaManager(quotaManager).consumeQuota(_token, _amount); + } + } + + function _safeDecimals(address _token) private view returns (uint8) { + (bool success, bytes memory data) = + address(_token).staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + return success && data.length == 32 ? abi.decode(data, (uint8)) : 18; + } } diff --git a/packages/protocol/contracts/tokenvault/ERC721Vault.sol b/packages/protocol/contracts/tokenvault/ERC721Vault.sol index 010bd5f76f60..ac07f53bb2dd 100644 --- a/packages/protocol/contracts/tokenvault/ERC721Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC721Vault.sol @@ -1,183 +1,151 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import - "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; -import "../bridge/IBridge.sol"; +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import "../libs/LibAddress.sol"; +import "../common/LibStrings.sol"; +import "./IBridgedERC721.sol"; import "./BaseNFTVault.sol"; -import "./BridgedERC721.sol"; /// @title ERC721Vault -/// @dev Labeled in AddressResolver as "erc721_vault" -/// @notice This vault holds all ERC721 tokens that users have deposited. -/// It also manages the mapping between canonical tokens and their bridged -/// tokens. -contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { +/// @notice This vault holds all ERC721 tokens that users have deposited. It also manages +/// the mapping between canonical tokens and their bridged tokens. +/// @dev Labeled in AddressResolver as "erc721_vault". +/// @custom:security-contact security@taiko.xyz +contract ERC721Vault is BaseNFTVault, IERC721Receiver { using LibAddress for address; uint256[50] private __gap; + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + function init(address _owner, address _addressManager) external initializer { + __Essential_init(_owner, _addressManager); + } + /// @notice Transfers ERC721 tokens to this vault and sends a message to the /// destination chain so the user can receive the same (bridged) tokens /// by invoking the message call. - /// @param op Option for sending the ERC721 token. - function sendToken(BridgeTransferOp calldata op) + /// @param _op Option for sending the ERC721 token. + /// @return message_ The constructed message. + function sendToken(BridgeTransferOp calldata _op) external payable - nonReentrant whenNotPaused - withValidOperation(op) - returns (IBridge.Message memory _message) + withValidOperation(_op) + nonReentrant + returns (IBridge.Message memory message_) { - for (uint256 i; i < op.tokenIds.length; ++i) { - if (op.amounts[i] != 0) revert VAULT_INVALID_AMOUNT(); + if (msg.value < _op.fee) revert VAULT_INSUFFICIENT_FEE(); + + for (uint256 i; i < _op.tokenIds.length; ++i) { + if (_op.amounts[i] != 0) revert VAULT_INVALID_AMOUNT(); } - if (!op.token.supportsInterface(ERC721_INTERFACE_ID)) { + if (!_op.token.supportsInterface(type(IERC721).interfaceId)) { revert VAULT_INTERFACE_NOT_SUPPORTED(); } - // We need to save them into memory - because structs containing - // dynamic arrays will cause stack-too-deep error when passed - uint256[] memory _amounts = op.amounts; - address _token = op.token; - uint256[] memory _tokenIds = op.tokenIds; - - IBridge.Message memory message; - message.destChainId = op.destChainId; - message.data = _handleMessage(msg.sender, op); - message.owner = msg.sender; - message.to = resolve(message.destChainId, name(), false); - message.gasLimit = op.gasLimit; - message.value = msg.value - op.fee; - message.fee = op.fee; - message.refundTo = op.refundTo; - message.memo = op.memo; + (bytes memory data, CanonicalNFT memory ctoken) = _handleMessage(_op); + + IBridge.Message memory message = IBridge.Message({ + id: 0, // will receive a new value + from: address(0), // will receive a new value + srcChainId: 0, // will receive a new value + destChainId: _op.destChainId, + srcOwner: msg.sender, + destOwner: _op.destOwner != address(0) ? _op.destOwner : msg.sender, + to: resolve(_op.destChainId, name(), false), + value: msg.value - _op.fee, + fee: _op.fee, + gasLimit: _op.gasLimit, + data: data + }); bytes32 msgHash; - (msgHash, _message) = - IBridge(resolve("bridge", false)).sendMessage{ value: msg.value }(message); + (msgHash, message_) = + IBridge(resolve(LibStrings.B_BRIDGE, false)).sendMessage{ value: msg.value }(message); emit TokenSent({ msgHash: msgHash, - from: _message.owner, - to: op.to, - destChainId: _message.destChainId, - token: _token, - tokenIds: _tokenIds, - amounts: _amounts + from: message_.srcOwner, + to: _op.to, + destChainId: message_.destChainId, + ctoken: ctoken.addr, + token: _op.token, + tokenIds: _op.tokenIds, + amounts: _op.amounts }); } - /// @notice Receive bridged ERC721 tokens and handle them accordingly. - /// @param ctoken Canonical NFT data for the token being received. - /// @param from Source address. - /// @param to Destination address. - /// @param tokenIds Array of token IDs being received. - function receiveToken( - CanonicalNFT calldata ctoken, - address from, - address to, - uint256[] memory tokenIds - ) + /// @inheritdoc IMessageInvocable + function onMessageInvocation(bytes calldata _data) external payable - nonReentrant whenNotPaused + nonReentrant { - IBridge.Context memory ctx = checkProcessMessageContext(); + (CanonicalNFT memory ctoken, address from, address to, uint256[] memory tokenIds) = + abi.decode(_data, (CanonicalNFT, address, address, uint256[])); - address _to = to == address(0) || to == address(this) ? from : to; - address token; + // `onlyFromBridge` checked in checkProcessMessageContext + IBridge.Context memory ctx = checkProcessMessageContext(); - unchecked { - if (ctoken.chainId == block.chainid) { - token = ctoken.addr; - for (uint256 i; i < tokenIds.length; ++i) { - ERC721Upgradeable(token).transferFrom({ - from: address(this), - to: _to, - tokenId: tokenIds[i] - }); - } - } else { - token = _getOrDeployBridgedToken(ctoken); - for (uint256 i; i < tokenIds.length; ++i) { - BridgedERC721(token).mint(_to, tokenIds[i]); - } - } - } + // Don't allow sending to disallowed addresses. + // Don't send the tokens back to `from` because `from` is on the source chain. + checkToAddress(to); - _to.sendEther(msg.value); + // Transfer the ETH and the tokens to the `to` address + address token = _transferTokens(ctoken, to, tokenIds); + to.sendEtherAndVerify(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, from: from, to: to, srcChainId: ctx.srcChainId, + ctoken: ctoken.addr, token: token, tokenIds: tokenIds, - amounts: new uint256[](0) + amounts: new uint256[](tokenIds.length) }); } + /// @inheritdoc IRecallableSender function onMessageRecalled( - IBridge.Message calldata message, - bytes32 msgHash + IBridge.Message calldata _message, + bytes32 _msgHash ) external payable override - nonReentrant whenNotPaused + nonReentrant { + // `onlyFromBridge` checked in checkRecallMessageContext checkRecallMessageContext(); - if (message.owner == address(0)) revert VAULT_INVALID_USER(); - if (message.srcChainId != block.chainid) { - revert VAULT_INVALID_SRC_CHAIN_ID(); - } - - (CanonicalNFT memory nft,,, uint256[] memory tokenIds) = - abi.decode(message.data[4:], (CanonicalNFT, address, address, uint256[])); + (bytes memory data) = abi.decode(_message.data[4:], (bytes)); + (CanonicalNFT memory ctoken,,, uint256[] memory tokenIds) = + abi.decode(data, (CanonicalNFT, address, address, uint256[])); - if (nft.addr == address(0)) revert VAULT_INVALID_TOKEN(); - - unchecked { - if (bridgedToCanonical[nft.addr].addr != address(0)) { - for (uint256 i; i < tokenIds.length; ++i) { - BridgedERC721(nft.addr).mint(message.owner, tokenIds[i]); - } - } else { - for (uint256 i; i < tokenIds.length; ++i) { - ERC721Upgradeable(nft.addr).safeTransferFrom({ - from: address(this), - to: message.owner, - tokenId: tokenIds[i] - }); - } - } - } - - // send back Ether - message.owner.sendEther(message.value); + // Transfer the ETH and tokens back to the owner + address token = _transferTokens(ctoken, _message.srcOwner, tokenIds); + _message.srcOwner.sendEtherAndVerify(_message.value); emit TokenReleased({ - msgHash: msgHash, - from: message.owner, - token: nft.addr, + msgHash: _msgHash, + from: _message.srcOwner, + ctoken: ctoken.addr, + token: token, tokenIds: tokenIds, - amounts: new uint256[](0) + amounts: new uint256[](tokenIds.length) }); } - /// @inheritdoc IERC721ReceiverUpgradeable + /// @inheritdoc IERC721Receiver function onERC721Received( address, address, @@ -188,88 +156,106 @@ contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { pure returns (bytes4) { - return IERC721ReceiverUpgradeable.onERC721Received.selector; + return IERC721Receiver.onERC721Received.selector; } + /// @inheritdoc BaseVault function name() public pure override returns (bytes32) { - return "erc721_vault"; + return LibStrings.B_ERC721_VAULT; } - /// @dev Handles the message on the source chain and returns the encoded - /// call on the destination call. - /// @param user The user's address. - /// @param op BridgeTransferOp data. - /// @return msgData Encoded message data. - function _handleMessage( - address user, - BridgeTransferOp calldata op + function _transferTokens( + CanonicalNFT memory _ctoken, + address _to, + uint256[] memory _tokenIds ) private - returns (bytes memory msgData) + returns (address token_) { - CanonicalNFT memory nft; + if (_ctoken.chainId == block.chainid) { + token_ = _ctoken.addr; + for (uint256 i; i < _tokenIds.length; ++i) { + IERC721(token_).safeTransferFrom(address(this), _to, _tokenIds[i]); + } + } else { + token_ = _getOrDeployBridgedToken(_ctoken); + for (uint256 i; i < _tokenIds.length; ++i) { + IBridgedERC721(token_).mint(_to, _tokenIds[i]); + } + } + } + /// @dev Handles the message on the source chain and returns the encoded + /// call on the destination call. + /// @param _op BridgeTransferOp data. + /// @return msgData_ Encoded message data. + /// @return ctoken_ The canonical token. + function _handleMessage(BridgeTransferOp calldata _op) + private + returns (bytes memory msgData_, CanonicalNFT memory ctoken_) + { unchecked { - if (bridgedToCanonical[op.token].addr != address(0)) { - nft = bridgedToCanonical[op.token]; - for (uint256 i; i < op.tokenIds.length; ++i) { - BridgedERC721(op.token).burn(user, op.tokenIds[i]); + CanonicalNFT storage _ctoken = bridgedToCanonical[_op.token]; + if (_ctoken.addr != address(0)) { + ctoken_ = _ctoken; + for (uint256 i; i < _op.tokenIds.length; ++i) { + IERC721(_op.token).safeTransferFrom(msg.sender, address(this), _op.tokenIds[i]); + IBridgedERC721(_op.token).burn(_op.tokenIds[i]); } } else { - ERC721Upgradeable t = ERC721Upgradeable(op.token); - - nft = CanonicalNFT({ + ctoken_ = CanonicalNFT({ chainId: uint64(block.chainid), - addr: op.token, - symbol: t.symbol(), - name: t.name() + addr: _op.token, + symbol: safeSymbol(_op.token), + name: safeName(_op.token) }); - for (uint256 i; i < op.tokenIds.length; ++i) { - t.transferFrom(user, address(this), op.tokenIds[i]); + for (uint256 i; i < _op.tokenIds.length; ++i) { + IERC721(_op.token).safeTransferFrom(msg.sender, address(this), _op.tokenIds[i]); } } } - msgData = abi.encodeWithSelector(this.receiveToken.selector, nft, user, op.to, op.tokenIds); + msgData_ = abi.encodeCall( + this.onMessageInvocation, abi.encode(ctoken_, msg.sender, _op.to, _op.tokenIds) + ); } /// @dev Retrieve or deploy a bridged ERC721 token contract. - /// @param ctoken CanonicalNFT data. - /// @return btoken Address of the bridged token contract. - function _getOrDeployBridgedToken(CanonicalNFT calldata ctoken) + /// @param _ctoken CanonicalNFT data. + /// @return btoken_ Address of the bridged token contract. + function _getOrDeployBridgedToken(CanonicalNFT memory _ctoken) private - returns (address btoken) + returns (address btoken_) { - btoken = canonicalToBridged[ctoken.chainId][ctoken.addr]; + btoken_ = canonicalToBridged[_ctoken.chainId][_ctoken.addr]; - if (btoken == address(0)) { - btoken = _deployBridgedToken(ctoken); + if (btoken_ == address(0)) { + btoken_ = _deployBridgedToken(_ctoken); } } /// @dev Deploy a new BridgedNFT contract and initialize it. /// This must be called before the first time a bridged token is sent to /// this chain. - /// @param ctoken CanonicalNFT data. - /// @return btoken Address of the deployed bridged token contract. - function _deployBridgedToken(CanonicalNFT memory ctoken) private returns (address btoken) { - bytes memory data = bytes.concat( - BridgedERC721.init.selector, - abi.encode(addressManager, ctoken.addr, ctoken.chainId, ctoken.symbol, ctoken.name) + /// @param _ctoken CanonicalNFT data. + /// @return btoken_ Address of the deployed bridged token contract. + function _deployBridgedToken(CanonicalNFT memory _ctoken) private returns (address btoken_) { + bytes memory data = abi.encodeCall( + IBridgedERC721Initializable.init, + (owner(), addressManager, _ctoken.addr, _ctoken.chainId, _ctoken.symbol, _ctoken.name) ); - btoken = LibDeploy.deployERC1967Proxy(resolve("bridged_erc721", false), owner(), data); - - bridgedToCanonical[btoken] = ctoken; - canonicalToBridged[ctoken.chainId][ctoken.addr] = btoken; + btoken_ = address(new ERC1967Proxy(resolve(LibStrings.B_BRIDGED_ERC721, false), data)); + bridgedToCanonical[btoken_] = _ctoken; + canonicalToBridged[_ctoken.chainId][_ctoken.addr] = btoken_; emit BridgedTokenDeployed({ - chainId: ctoken.chainId, - ctoken: ctoken.addr, - btoken: btoken, - ctokenSymbol: ctoken.symbol, - ctokenName: ctoken.name + chainId: _ctoken.chainId, + ctoken: _ctoken.addr, + btoken: btoken_, + ctokenSymbol: _ctoken.symbol, + ctokenName: _ctoken.name }); } } diff --git a/packages/protocol/contracts/tokenvault/IBridgedERC1155.sol b/packages/protocol/contracts/tokenvault/IBridgedERC1155.sol new file mode 100644 index 000000000000..3d0ac0a93de1 --- /dev/null +++ b/packages/protocol/contracts/tokenvault/IBridgedERC1155.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IBridgedERC1155 +/// @notice Contract for bridging ERC1155 tokens across different chains. +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC1155 { + /// @dev Mints tokens. + /// @param _to Address to receive the minted tokens. + /// @param _tokenIds ID of the token to mint. + /// @param _amounts Amount of tokens to mint. + function mintBatch( + address _to, + uint256[] calldata _tokenIds, + uint256[] calldata _amounts + ) + external; + + /// @dev Burns tokens. + /// @param _id ID of the token to burn. + /// @param _amount Amount of token to burn respectively. + function burn(uint256 _id, uint256 _amount) external; + + /// @notice Gets the canonical token's address and chain ID. + /// @return The canonical token's address. + /// @return The canonical token's chain ID. + function canonical() external view returns (address, uint256); +} + +/// @title IBridgedERC1155Initializable +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC1155Initializable { + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + /// @param _srcToken Address of the source token. + /// @param _srcChainId Source chain ID. + /// @param _symbol Symbol of the bridged token. + /// @param _name Name of the bridged token. + function init( + address _owner, + address _addressManager, + address _srcToken, + uint256 _srcChainId, + string calldata _symbol, + string calldata _name + ) + external; +} diff --git a/packages/protocol/contracts/tokenvault/IBridgedERC20.sol b/packages/protocol/contracts/tokenvault/IBridgedERC20.sol index aec9cd02d062..5750341924c6 100644 --- a/packages/protocol/contracts/tokenvault/IBridgedERC20.sol +++ b/packages/protocol/contracts/tokenvault/IBridgedERC20.sol @@ -1,30 +1,71 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; /// @title IBridgedERC20 /// @notice Interface for all bridged tokens. -/// @dev To facilitate compatibility with third-party bridged tokens, such as USDC's native -/// standard, it's necessary to implement an intermediary adapter contract which should conform to -/// this interface, enabling effective interaction with third-party contracts. +/// @dev Here is the list of assumptions that guarantees that the bridged token can be bridged back +/// to it's canonical counterpart (by-default it is, but in case a third-party "native" token is set +/// and used in our bridge): +/// - The token should be ERC-20 compliant +/// - Supply increases should only be caused by mints from the vault. Notably, rebasing tokens are +/// not supported. +/// - Token balances should change by exactly the given amounts on `transfer`/`mint`/`burn`. Notable, +/// tokens with fees on transfers are not supported. +/// - If the bridged token is not directly deployed by the Bridge (ERC20Vault), - for example a USDT +/// token bytecode deployed on Taiko to support native tokens - it might be necessary to implement +/// an intermediary adapter contract which should conform mint() and burn() interfaces, so that the +/// ERC20Vault can call these actions on the adapter. +/// - If the bridged token is not directly deployed by the Bridge (ERC20Vault), but conforms the +/// mint() and burn() interface and the ERC20Vault has the right to perform these actions (has +/// minter/burner role). +/// - If the bridged token is directly deployed by our Bridge (ERC20Vault). +/// @custom:security-contact security@taiko.xyz interface IBridgedERC20 { /// @notice Mints `amount` tokens and assigns them to the `account` address. - /// @param account The account to receive the minted tokens. - /// @param amount The amount of tokens to mint. - function mint(address account, uint256 amount) external; + /// @param _account The account to receive the minted tokens. + /// @param _amount The amount of tokens to mint. + function mint(address _account, uint256 _amount) external; + + /// @notice Burns tokens from msg.sender. This is only allowed if: + /// - 1) tokens are migrating out to a new bridged token + /// - 2) The token is burned by ERC20Vault to bridge back to the canonical chain. + /// @param _amount The amount of tokens to burn. + function burn(uint256 _amount) external; - /// @notice Burns `amount` tokens from the `from` address. - /// @param from The account from which the tokens will be burned. - /// @param amount The amount of tokens to burn. - function burn(address from, uint256 amount) external; + /// @notice Gets the canonical token's address and chain ID. + /// @return The canonical token's address. + /// @return The canonical token's chain ID. + function canonical() external view returns (address, uint256); +} - /// @notice Start or stop migration to/from a specified contract. - function changeMigrationStatus(address addr, bool inbound) external; +/// @title IBridgedERC20Migratable +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC20Migratable { + /// @notice Starts or stops migration to/from a specified contract. + /// @param _addr The address migrating 'to' or 'from'. + /// @param _inbound If false then signals migrating 'from', true if migrating 'into'. + function changeMigrationStatus(address _addr, bool _inbound) external; +} - /// @notice Returns the owner - function owner() external view returns (address); +/// @title IBridgedERC20Initializable +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC20Initializable { + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + /// @param _srcToken The source token address. + /// @param _srcChainId The source chain ID. + /// @param _decimals The number of decimal places of the source token. + /// @param _symbol The symbol of the token. + /// @param _name The name of the token. + function init( + address _owner, + address _addressManager, + address _srcToken, + uint256 _srcChainId, + uint8 _decimals, + string calldata _symbol, + string calldata _name + ) + external; } diff --git a/packages/protocol/contracts/tokenvault/IBridgedERC721.sol b/packages/protocol/contracts/tokenvault/IBridgedERC721.sol new file mode 100644 index 000000000000..e8a2d4266054 --- /dev/null +++ b/packages/protocol/contracts/tokenvault/IBridgedERC721.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IBridgedERC721 +/// @notice Contract for bridging ERC721 tokens across different chains. +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC721 { + /// @dev Mints tokens. + /// @param _account Address to receive the minted token. + /// @param _tokenId ID of the token to mint. + function mint(address _account, uint256 _tokenId) external; + + /// @dev Burns tokens. + /// @param _tokenId ID of the token to burn. + function burn(uint256 _tokenId) external; + + /// @notice Gets the canonical token's address and chain ID. + /// @return The canonical token's address. + /// @return The canonical token's chain ID. + function canonical() external view returns (address, uint256); +} + +/// @title IBridgedERC721Initializable +/// @custom:security-contact security@taiko.xyz +interface IBridgedERC721Initializable { + /// @notice Initializes the contract. + /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. + /// @param _addressManager The address of the {AddressManager} contract. + /// @param _srcToken Address of the source token. + /// @param _srcChainId Source chain ID. + /// @param _symbol Symbol of the bridged token. + /// @param _name Name of the bridged token. + function init( + address _owner, + address _addressManager, + address _srcToken, + uint256 _srcChainId, + string calldata _symbol, + string calldata _name + ) + external; +} diff --git a/packages/protocol/contracts/tokenvault/LibBridgedToken.sol b/packages/protocol/contracts/tokenvault/LibBridgedToken.sol index 81c7ddc91c4e..c81313ccb045 100644 --- a/packages/protocol/contracts/tokenvault/LibBridgedToken.sol +++ b/packages/protocol/contracts/tokenvault/LibBridgedToken.sol @@ -1,27 +1,44 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/utils/Strings.sol"; /// @title LibBridgedToken +/// @custom:security-contact security@taiko.xyz library LibBridgedToken { - function buildName( - string memory name, - uint256 srcChainId + error BTOKEN_INVALID_PARAMS(); + error BTOKEN_INVALID_TO_ADDR(); + + function validateInputs(address _srcToken, uint256 _srcChainId) internal view { + if (_srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid) { + revert BTOKEN_INVALID_PARAMS(); + } + } + + function checkToAddress(address _to) internal view { + if (_to == address(this)) revert BTOKEN_INVALID_TO_ADDR(); + } + + function buildURI( + address _srcToken, + uint256 _srcChainId, + string memory _extraParams ) internal pure returns (string memory) { - return string.concat("Bridged ", name, unicode" (⭀", Strings.toString(srcChainId), ")"); - } - - function buildSymbol(string memory symbol) internal pure returns (string memory) { - return string.concat(symbol, ".t"); + // Creates a base URI in the format specified by EIP-681: + // https://eips.ethereum.org/EIPS/eip-681 + return string( + abi.encodePacked( + "ethereum:", + Strings.toHexString(uint160(_srcToken), 20), + "@", + Strings.toString(_srcChainId), + "/tokenURI?uint256=", + _extraParams + ) + ); } } diff --git a/packages/protocol/contracts/tokenvault/adaptors/USDCAdaptor.sol b/packages/protocol/contracts/tokenvault/adaptors/USDCAdaptor.sol deleted file mode 100644 index 9476286ffffa..000000000000 --- a/packages/protocol/contracts/tokenvault/adaptors/USDCAdaptor.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ - -pragma solidity ^0.8.20; - -import "../BridgedERC20Base.sol"; - -interface IUSDC { - function burn(uint256 amount) external; - function mint(address to, uint256 amount) external; - function transferFrom(address from, address to, uint256 amount) external; -} - -/// @title USDCAdaptor -contract USDCAdaptor is BridgedERC20Base { - IUSDC public usdc; // slot 1 - uint256[49] private __gap; - - function init(address _adressManager, IUSDC _usdc) external initializer { - __Essential_init(_adressManager); - usdc = _usdc; - } - - function _mintToken(address account, uint256 amount) internal override { - usdc.mint(account, amount); - } - - function _burnToken(address from, uint256 amount) internal override { - usdc.transferFrom(from, address(this), amount); - usdc.burn(amount); - } -} diff --git a/packages/protocol/deployments/deploy_l1.json b/packages/protocol/deployments/deploy_l1.json index 9e26dfeeb6e6..7e611cc41815 100644 --- a/packages/protocol/deployments/deploy_l1.json +++ b/packages/protocol/deployments/deploy_l1.json @@ -1 +1,5 @@ -{} \ No newline at end of file +{ + "address_manager": "0x2946259E0334f33A064106302415aD3391BeD384", + "bridge": "0x6D411e0A54382eD43F02410Ce1c7a7c122afA6E1", + "signal_service": "0x66a15edcC3b50a663e72F1457FFd49b9AE284dDc" +} \ No newline at end of file diff --git a/packages/protocol/deployments/local_deployment.md b/packages/protocol/deployments/local_deployment.md new file mode 100644 index 000000000000..3498bc2ab706 --- /dev/null +++ b/packages/protocol/deployments/local_deployment.md @@ -0,0 +1,65 @@ +# How to deploy Gwyneth locally - on a reth-based private network + +The first part is coming from [Reth Book](https://reth.rs/run/private-testnet.html), but if you want to dig deeper, please visit the website, otherwise it is not necessary. + +### 0. Pre-requisites: +- have docker installed (and docker daemon running) +- have Kurtosis installed, on Mac, e.g.: +```shell +brew install kurtosis-tech/tap/kurtosis-cli +``` + +### 1. Define the network config parameters + +Create a `network_param.yaml` file. + +```shell +participants: + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth # We can use custom image, (remote, e.g.: ethpandaops/reth:main-9c0bc84 or locally: taiko_reth) + cl_type: lighthouse + cl_image: sigp/lighthouse:latest + - el_type: reth + el_image: ghcr.io/paradigmxyz/reth # We can use custom image, (remote, e.g.: ethpandaops/reth:main-9c0bc84 or locally: taiko_reth) + cl_type: teku + cl_image: consensys/teku:latest +``` + +#### 1.1 Local reth-based network + +1. Go to the root of the repository, and build the image, e.g.: +```shell +docker build . -t taiko_reth +``` + +2. Use simply the `taiko_reth` image, in `el_image` variable of the network yaml file. + +### 2. Spin up the network + +```shell +kurtosis run github.com/ethpandaops/ethereum-package --args-file YOUR_NETWORK_FILE_PATH/network_params.yaml +``` + +It will show you a lot of information in the terminal - along with the genesis info, network id, addresses with pre-funded ETH, etc. + +### 3. Set .env vars and run contract deployment script +Paste one PK and ADDR pair from anvil output to .env file and set the correct corresponding (PRIVATE_KEY and MAINNET_CONTRACT_OWNER) variables. + +Run script: + +```shell +$ forge script --rpc-url http://127.0.0.1:52178 scripts/DeployL1Locally.s.sol -vvvv --broadcast --private-key --legacy +``` + +Important: shall be the same PK as you set in the ENV file. + +### 4. Test interaction with the blockchain + +Shoot it with simple RPC commands e.g. via `curl`, to see the blockchain is operational. + +```shell +curl http://127.0.0.1:YOUR_EXPOSED_PORT \ + -X POST \ + -H "Content-Type: application/json" \ + --data '{"method":"eth_getBlockByNumber","params":["0x0",false],"id":1,"jsonrpc":"2.0"}' +``` \ No newline at end of file diff --git a/packages/protocol/script/Counter.s.sol b/packages/protocol/script/Counter.s.sol deleted file mode 100644 index df9ee8b02ce4..000000000000 --- a/packages/protocol/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/packages/protocol/scripts/DeployL1Locally.s.sol b/packages/protocol/scripts/DeployL1Locally.s.sol new file mode 100644 index 000000000000..fe8bbbdb78af --- /dev/null +++ b/packages/protocol/scripts/DeployL1Locally.s.sol @@ -0,0 +1,386 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "@openzeppelin/contracts/utils/Strings.sol"; + +import "../contracts/L1/TaikoL1.sol"; +import "../contracts/L1/BasedOperator.sol"; +import "../contracts/L1/VerifierRegistry.sol"; +import "../contracts/L1/TaikoToken.sol"; +import "../contracts/L1/provers/GuardianProver.sol"; +// import "../contracts/L1/tiers/DevnetTierProvider.sol"; +// import "../contracts/L1/tiers/TierProviderV2.sol"; +// import "../contracts/bridge/Bridge.sol"; +// import "../contracts/tokenvault/BridgedERC20.sol"; +// import "../contracts/tokenvault/BridgedERC721.sol"; +// import "../contracts/tokenvault/BridgedERC1155.sol"; +// import "../contracts/tokenvault/ERC20Vault.sol"; +// import "../contracts/tokenvault/ERC1155Vault.sol"; +// import "../contracts/tokenvault/ERC721Vault.sol"; +// import "../contracts/signal/SignalService.sol"; +// import "../contracts/automata-attestation/AutomataDcapV3Attestation.sol"; +// import "../contracts/automata-attestation/utils/SigVerifyLib.sol"; +// import "../contracts/automata-attestation/lib/PEMCertChainLib.sol"; +import "../contracts/L1/verifiers/SgxVerifier.sol"; +import "../contracts/L1/verifiers/MockSgxVerifier.sol"; // Avoid proof verification for now! +// import "../contracts/team/proving/ProverSet.sol"; +// import "../test/common/erc20/FreeMintERC20.sol"; +// import "../test/common/erc20/MayFailFreeMintERC20.sol"; +// import "../test/L1/TestTierProvider.sol"; +import "../test/DeployCapability.sol"; + +// Actually this one is deployed already on mainnet, but we are now deploying our own (non via-ir) +// version. For mainnet, it is easier to go with one of: +// - https://github.com/daimo-eth/p256-verifier +// - https://github.com/rdubois-crypto/FreshCryptoLib +import { P256Verifier } from "p256-verifier/src/P256Verifier.sol"; + +/// @title DeployOnL1 +/// @notice This script deploys the core Taiko protocol smart contract on L1, +/// initializing the rollup. +contract DeployL1OnAnvil is DeployCapability { + // uint256 public NUM_MIN_MAJORITY_GUARDIANS = vm.envUint("NUM_MIN_MAJORITY_GUARDIANS"); + // uint256 public NUM_MIN_MINORITY_GUARDIANS = vm.envUint("NUM_MIN_MINORITY_GUARDIANS"); + + address public MAINNET_CONTRACT_OWNER = vm.envAddress("MAINNET_CONTRACT_OWNER"); //Dani: Use an address anvil provides, with preminted ETH + + modifier broadcast() { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + require(privateKey != 0, "invalid priv key"); + vm.startBroadcast(); + _; + vm.stopBroadcast(); + } + + function run() external broadcast { + /* + IMPORTANT NOTICES: + - TaikoL2 deployments (and not only TaikoL2, but all contracts sitting on L2) obviously not done and haven't even dealt with + - SignalService, Bridge, Vaults also not dealt with on L1 + */ + // addressNotNull(vm.envAddress("TAIKO_L2_ADDRESS"), "TAIKO_L2_ADDRESS"); + // addressNotNull(vm.envAddress("L2_SIGNAL_SERVICE"), "L2_SIGNAL_SERVICE"); + // addressNotNull(vm.envAddress("CONTRACT_OWNER"), "CONTRACT_OWNER"); + + require(vm.envBytes32("L2_GENESIS_HASH") != 0, "L2_GENESIS_HASH"); + address contractOwner = MAINNET_CONTRACT_OWNER; + + // --------------------------------------------------------------- + // Deploy shared contracts + (address sharedAddressManager) = deploySharedContracts(contractOwner); + console2.log("sharedAddressManager: ", sharedAddressManager); + // --------------------------------------------------------------- + // Deploy rollup contracts + address rollupAddressManager = deployRollupContracts(sharedAddressManager, contractOwner); + + // // --------------------------------------------------------------- + // // Signal service need to authorize the new rollup + // address signalServiceAddr = AddressManager(sharedAddressManager).getAddress( + // uint64(block.chainid), LibStrings.B_SIGNAL_SERVICE + // ); + // addressNotNull(signalServiceAddr, "signalServiceAddr"); + // SignalService signalService = SignalService(signalServiceAddr); + + address taikoL1Addr = AddressManager(rollupAddressManager).getAddress( + uint64(block.chainid), "taiko" + ); + addressNotNull(taikoL1Addr, "taikoL1Addr"); + TaikoL1 taikoL1 = TaikoL1(payable(taikoL1Addr)); + + // if (vm.envAddress("SHARED_ADDRESS_MANAGER") == address(0)) { + // SignalService(signalServiceAddr).authorize(taikoL1Addr, true); + // } + + // uint64 l2ChainId = taikoL1.getConfig().chainId; + // require(l2ChainId != block.chainid, "same chainid"); + + // console2.log("------------------------------------------"); + // console2.log("msg.sender: ", msg.sender); + // console2.log("address(this): ", address(this)); + // console2.log("signalService.owner(): ", signalService.owner()); + // console2.log("------------------------------------------"); + + // if (signalService.owner() == msg.sender) { + // signalService.transferOwnership(contractOwner); + // } else { + // console2.log("------------------------------------------"); + // console2.log("Warning - you need to transact manually:"); + // console2.log("signalService.authorize(taikoL1Addr, bytes32(block.chainid))"); + // console2.log("- signalService : ", signalServiceAddr); + // console2.log("- taikoL1Addr : ", taikoL1Addr); + // console2.log("- chainId : ", block.chainid); + // } + + // // --------------------------------------------------------------- + // // Register L2 addresses + // register(rollupAddressManager, "taiko", vm.envAddress("TAIKO_L2_ADDRESS"), l2ChainId); + // register( + // rollupAddressManager, "signal_service", vm.envAddress("L2_SIGNAL_SERVICE"), l2ChainId + // ); + + // // --------------------------------------------------------------- + // // Deploy other contracts + // if (block.chainid != 1) { + // deployAuxContracts(); + // } + + // if (AddressManager(sharedAddressManager).owner() == msg.sender) { + // AddressManager(sharedAddressManager).transferOwnership(contractOwner); + // console2.log("** sharedAddressManager ownership transferred to:", contractOwner); + // } + + // AddressManager(rollupAddressManager).transferOwnership(contractOwner); + // console2.log("** rollupAddressManager ownership transferred to:", contractOwner); + } + + function deploySharedContracts(address owner) internal returns (address sharedAddressManager) { + addressNotNull(owner, "owner"); + + sharedAddressManager = address(0);// Dani: Can be set tho via ENV var, for now, for anvil, easy to just deploy every time + if (sharedAddressManager == address(0)) { + sharedAddressManager = deployProxy({ + name: "shared_address_manager", + impl: address(new AddressManager()), + data: abi.encodeCall(AddressManager.init, ()) + }); + } + + //dataToFeed = abi.encodeCall(TaikoToken.init, ("TAIKO", "TAIKO", MAINNET_CONTRACT_OWNER)); + address taikoToken = address(0); // Later on use this as env. var since already deployed (on testnets): vm.envAddress("TAIKO_TOKEN"); + if (taikoToken == address(0)) { + taikoToken = deployProxy({ + name: "taiko_token", + impl: address(new TaikoToken()), + data: abi.encodeCall(TaikoToken.init, ("TAIKO", "TAIKO", MAINNET_CONTRACT_OWNER)), + registerTo: sharedAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + } + + // // Deploy Bridging contracts - to be done later. + // deployProxy({ + // name: "signal_service", + // impl: address(new SignalService()), + // data: abi.encodeCall(SignalService.init, (address(0), sharedAddressManager)), + // registerTo: sharedAddressManager + // }); + + // address brdige = deployProxy({ + // name: "bridge", + // impl: address(new Bridge()), + // data: abi.encodeCall(Bridge.init, (address(0), sharedAddressManager)), + // registerTo: sharedAddressManager + // }); + + // if (vm.envBool("PAUSE_BRIDGE")) { + // Bridge(payable(brdige)).pause(); + // } + + // Bridge(payable(brdige)).transferOwnership(owner); + + // console2.log("------------------------------------------"); + // console2.log( + // "Warning - you need to register *all* counterparty bridges to enable multi-hop bridging:" + // ); + // console2.log( + // "sharedAddressManager.setAddress(remoteChainId, \"bridge\", address(remoteBridge))" + // ); + // console2.log("- sharedAddressManager : ", sharedAddressManager); + + // // Deploy Vaults + // deployProxy({ + // name: "erc20_vault", + // impl: address(new ERC20Vault()), + // data: abi.encodeCall(ERC20Vault.init, (owner, sharedAddressManager)), + // registerTo: sharedAddressManager + // }); + + // deployProxy({ + // name: "erc721_vault", + // impl: address(new ERC721Vault()), + // data: abi.encodeCall(ERC721Vault.init, (owner, sharedAddressManager)), + // registerTo: sharedAddressManager + // }); + + // deployProxy({ + // name: "erc1155_vault", + // impl: address(new ERC1155Vault()), + // data: abi.encodeCall(ERC1155Vault.init, (owner, sharedAddressManager)), + // registerTo: sharedAddressManager + // }); + + // console2.log("------------------------------------------"); + // console2.log( + // "Warning - you need to register *all* counterparty vaults to enable multi-hop bridging:" + // ); + // console2.log( + // "sharedAddressManager.setAddress(remoteChainId, \"erc20_vault\", address(remoteERC20Vault))" + // ); + // console2.log( + // "sharedAddressManager.setAddress(remoteChainId, \"erc721_vault\", address(remoteERC721Vault))" + // ); + // console2.log( + // "sharedAddressManager.setAddress(remoteChainId, \"erc1155_vault\", address(remoteERC1155Vault))" + // ); + // console2.log("- sharedAddressManager : ", sharedAddressManager); + + // // Deploy Bridged token implementations + // register(sharedAddressManager, "bridged_erc20", address(new BridgedERC20())); + // register(sharedAddressManager, "bridged_erc721", address(new BridgedERC721())); + // register(sharedAddressManager, "bridged_erc1155", address(new BridgedERC1155())); + } + + function deployRollupContracts( + address _sharedAddressManager, + address owner + ) + internal + returns (address rollupAddressManager) + { + addressNotNull(_sharedAddressManager, "sharedAddressManager"); + addressNotNull(owner, "owner"); + + rollupAddressManager = deployProxy({ + name: "rollup_address_manager", + impl: address(new AddressManager()), + data: abi.encodeCall(AddressManager.init, ()) + }); + + // --------------------------------------------------------------- + // Register shared contracts in the new rollup + copyRegister(rollupAddressManager, _sharedAddressManager, "taiko_token"); + // Not deployed yet, so not needed: + // copyRegister(rollupAddressManager, _sharedAddressManager, "signal_service"); + // copyRegister(rollupAddressManager, _sharedAddressManager, "bridge"); + + deployProxy({ + name: "taiko", + impl: address(new TaikoL1()), + data: abi.encodeCall( + TaikoL1.init, + ( + rollupAddressManager, + vm.envBytes32("L2_GENESIS_HASH") + ) + ), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + + /* Deploy BasedOperator */ + deployProxy({ + name: "operator", + impl: address(new BasedOperator()), + data: abi.encodeCall(BasedOperator.init, (address(rollupAddressManager))), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + + /* Deploy MockSGXVerifier 3 times for now, so that we can call verifyProof without modifications of the protocol code. Later obv. shall be replaced with real verifiers. */ + address verifier1 = deployProxy({ + name: "tier_sgx1", + impl: address(new MockSgxVerifier()), + data: abi.encodeCall(MockSgxVerifier.init, (rollupAddressManager)), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + address verifier2 = deployProxy({ + name: "tier_sgx2", + impl: address(new MockSgxVerifier()), + data: abi.encodeCall(MockSgxVerifier.init, (rollupAddressManager)), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + address verifier3 = deployProxy({ + name: "tier_sgx3", + impl: address(new MockSgxVerifier()), + data: abi.encodeCall(MockSgxVerifier.init, (rollupAddressManager)), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + + /* Deploy VerifierRegistry */ + address vieriferRegistry = deployProxy({ + name: "verifier_registry", + impl: address(new VerifierRegistry()), + data: abi.encodeCall(VerifierRegistry.init, (rollupAddressManager)), + registerTo: rollupAddressManager, + owner: MAINNET_CONTRACT_OWNER + }); + + // Add those 3 to verifier registry + VerifierRegistry(vieriferRegistry).addVerifier(verifier1, "sgx1"); + VerifierRegistry(vieriferRegistry).addVerifier(verifier2, "sgx2"); + VerifierRegistry(vieriferRegistry).addVerifier(verifier3, "sgx3"); + + // Leave out guardians "tier" for now. + // address guardianProverImpl = address(new GuardianProver()); + + // address guardianProverMinority = deployProxy({ + // name: "guardian_prover_minority", + // impl: guardianProverImpl, + // data: abi.encodeCall(GuardianProver.init, (address(0), rollupAddressManager)) + // }); + + // GuardianProver(guardianProverMinority).enableTaikoTokenAllowance(true); + + // address guardianProver = deployProxy({ + // name: "guardian_prover", + // impl: guardianProverImpl, + // data: abi.encodeCall(GuardianProver.init, (address(0), rollupAddressManager)) + // }); + + // register(rollupAddressManager, "tier_guardian_minority", guardianProverMinority); + // register(rollupAddressManager, "tier_guardian", guardianProver); + // register( + // rollupAddressManager, + // "tier_router", + // address(deployTierProvider(vm.envString("TIER_PROVIDER"))) + // ); + + // address[] memory guardians = vm.envAddress("GUARDIAN_PROVERS", ","); + + // GuardianProver(guardianProverMinority).setGuardians( + // guardians, uint8(NUM_MIN_MINORITY_GUARDIANS), true + // ); + // GuardianProver(guardianProverMinority).transferOwnership(owner); + + // GuardianProver(guardianProver).setGuardians( + // guardians, uint8(NUM_MIN_MAJORITY_GUARDIANS), true + // ); + // GuardianProver(guardianProver).transferOwnership(owner); + + // // No need to proxy these, because they are 3rd party. If we want to modify, we simply + // // change the registerAddress("automata_dcap_attestation", address(attestation)); + // P256Verifier p256Verifier = new P256Verifier(); + // SigVerifyLib sigVerifyLib = new SigVerifyLib(address(p256Verifier)); + // PEMCertChainLib pemCertChainLib = new PEMCertChainLib(); + // address automateDcapV3AttestationImpl = address(new AutomataDcapV3Attestation()); + + // address automataProxy = deployProxy({ + // name: "automata_dcap_attestation", + // impl: automateDcapV3AttestationImpl, + // data: abi.encodeCall( + // AutomataDcapV3Attestation.init, (owner, address(sigVerifyLib), address(pemCertChainLib)) + // ), + // registerTo: rollupAddressManager + // }); + + // // Log addresses for the user to register sgx instance + // console2.log("SigVerifyLib", address(sigVerifyLib)); + // console2.log("PemCertChainLib", address(pemCertChainLib)); + // console2.log("AutomataDcapVaAttestation", automataProxy); + + // deployProxy({ + // name: "prover_set", + // impl: address(new ProverSet()), + // data: abi.encodeCall( + // ProverSet.init, (owner, vm.envAddress("PROVER_SET_ADMIN"), rollupAddressManager) + // ) + // }); + } + + function addressNotNull(address addr, string memory err) private pure { + require(addr != address(0), err); + } +} diff --git a/packages/protocol/test/Counter.t.sol b/packages/protocol/test/Counter.t.sol deleted file mode 100644 index 54b724f7ae76..000000000000 --- a/packages/protocol/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/packages/protocol/test/DeployCapability.sol b/packages/protocol/test/DeployCapability.sol index 5e71f316dba7..1def977c3c0c 100644 --- a/packages/protocol/test/DeployCapability.sol +++ b/packages/protocol/test/DeployCapability.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -13,7 +9,6 @@ import "forge-std/console2.sol"; import "forge-std/Script.sol"; import "../contracts/common/AddressManager.sol"; -import "../contracts/libs/LibDeploy.sol"; /// @title DeployCapability abstract contract DeployCapability is Script { @@ -23,13 +18,12 @@ abstract contract DeployCapability is Script { string memory name, address impl, bytes memory data, - address registerTo, - address owner + address registerTo ) internal returns (address proxy) { - proxy = LibDeploy.deployERC1967Proxy(impl, owner, data); + proxy = address(new ERC1967Proxy(impl, data)); if (registerTo != address(0)) { AddressManager(registerTo).setAddress( @@ -58,7 +52,7 @@ abstract contract DeployCapability is Script { internal returns (address proxy) { - return deployProxy(name, impl, data, address(0), address(0)); + return deployProxy(name, impl, data, address(0)); } function register(address registerTo, string memory name, address addr) internal { diff --git a/packages/protocol/test/HelperContracts.sol b/packages/protocol/test/HelperContracts.sol index abd7c7a7a87c..2253a3be915b 100644 --- a/packages/protocol/test/HelperContracts.sol +++ b/packages/protocol/test/HelperContracts.sol @@ -1,9 +1,8 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../contracts/bridge/Bridge.sol"; import "../contracts/signal/SignalService.sol"; -import "../contracts/common/ICrossChainSync.sol"; contract BadReceiver { receive() external payable { @@ -19,10 +18,11 @@ contract BadReceiver { } } -contract GoodReceiver { +contract GoodReceiver is IMessageInvocable { receive() external payable { } - function forward(address addr) public payable { + function onMessageInvocation(bytes calldata data) public payable { + address addr = abi.decode(data, (address)); payable(addr).transfer(address(this).balance / 2); } } @@ -37,20 +37,15 @@ contract NonNftContract { } contract SkipProofCheckSignal is SignalService { - function skipProofCheck() public pure override returns (bool) { - return true; - } -} - -contract DummyCrossChainSync is EssentialContract, ICrossChainSync { - Snippet private _snippet; - - function setSyncedData(bytes32 blockHash, bytes32 signalRoot) external { - _snippet.blockHash = blockHash; - _snippet.signalRoot = signalRoot; - } - - function getSyncedSnippet(uint64) external view returns (Snippet memory) { - return _snippet; - } + function proveSignalReceived( + uint64, /*srcChainId*/ + address, /*app*/ + bytes32, /*signal*/ + bytes calldata /*proof*/ + ) + public + pure + override + returns (uint256) + { } } diff --git a/packages/protocol/test/L1/Guardians.t.sol b/packages/protocol/test/L1/Guardians.t.sol index 7feff7b0111f..e3da2e89613b 100644 --- a/packages/protocol/test/L1/Guardians.t.sol +++ b/packages/protocol/test/L1/Guardians.t.sol @@ -85,4 +85,4 @@ contract TestSignalService is TaikoTest { assertEq(target.isApproved(hash), false); } } -*/ \ No newline at end of file +*/ diff --git a/packages/protocol/test/L1/SgxVerifier.t.sol b/packages/protocol/test/L1/SgxVerifier.t.sol index 0b3d4d9f1f26..4afb1209629d 100644 --- a/packages/protocol/test/L1/SgxVerifier.t.sol +++ b/packages/protocol/test/L1/SgxVerifier.t.sol @@ -6,7 +6,7 @@ import "./TaikoL1TestBase.sol"; contract TestSgxVerifier is TaikoL1TestBase { function deployTaikoL1() internal override returns (TaikoL1) { return - TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); +TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); } function test_addInstancesByOwner() external { @@ -52,4 +52,4 @@ contract TestSgxVerifier is TaikoL1TestBase { signature = abi.encodePacked(r, s, v); } } -*/ \ No newline at end of file +*/ diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index 0c7f813f64a6..6a2cedc7d3cb 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -2,196 +2,92 @@ pragma solidity ^0.8.20; import "./TaikoL1TestBase.sol"; -/* -contract TaikoL1_NoCooldown is TaikoL1 { - function getConfig() public view override returns (TaikoData.Config memory config) { - config = TaikoL1.getConfig(); - // over-write the following - config.maxBlocksToVerifyPerProposal = 0; - config.blockMaxProposals = 10; - config.blockRingBufferSize = 12; - config.livenessBond = 1e18; // 1 Taiko token - } -} - -contract Verifier { - fallback(bytes calldata) external returns (bytes memory) { - return bytes.concat(keccak256("taiko")); - } -} contract TaikoL1Test is TaikoL1TestBase { - function deployTaikoL1() internal override returns (TaikoL1) { - return TaikoL1( - payable( - deployProxy({ name: "taiko", impl: address(new TaikoL1_NoCooldown()), data: "" }) - ) - ); + function deployTaikoL1(address addressManager) internal override returns (TaikoL1) { + return + TaikoL1(payable(deployProxy({ name: "taiko", impl: address(new TaikoL1()), data: "" }))); } - /// @dev Test we can propose, prove, then verify more blocks than - /// 'blockMaxProposals' - function test_L1_more_blocks_than_ring_buffer_size() external { - giveEthAndTko(Alice, 1e8 ether, 100 ether); - // This is a very weird test (code?) issue here. - // If this line (or Bob's query balance) is uncommented, - // Alice/Bob has no balance.. (Causing reverts !!!) - console2.log("Alice balance:", tko.balanceOf(Alice)); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - giveEthAndTko(Carol, 1e8 ether, 100 ether); - // Bob - vm.prank(Bob, Bob); - - bytes32 parentHash = GENESIS_BLOCK_HASH; - - for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { - //printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - //printVariables("after propose"); - mine(1); - - bytes32 blockHash = bytes32(1e10 + blockId); - bytes32 signalRoot = bytes32(1e9 + blockId); - proveBlock(Bob, Bob, meta, parentHash, blockHash, signalRoot, meta.minTier, ""); - vm.roll(block.number + 15 * 12); - - uint16 minTier = meta.minTier; - vm.warp(block.timestamp + L1.getTier(minTier).cooldownWindow + 1); - - verifyBlock(Carol, 1); - parentHash = blockHash; - } - printVariables(""); - } + function test_L1_propose_prove_and_verify_blocks_sequentially() external { + giveEthAndTko(Alice, 100 ether, 100 ether); + + TaikoData.BlockMetadata memory meta; - /// @dev Test more than one block can be proposed, proven, & verified in the - /// same L1 block. - function test_L1_multiple_blocks_in_one_L1_block() external { - giveEthAndTko(Alice, 1000 ether, 1000 ether); - console2.log("Alice balance:", tko.balanceOf(Alice)); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - giveEthAndTko(Carol, 1e8 ether, 100 ether); - // Bob - vm.prank(Bob, Bob); - - bytes32 parentHash = GENESIS_BLOCK_HASH; - - for (uint256 blockId = 1; blockId <= 20; ++blockId) { - printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - printVariables("after propose"); - - bytes32 blockHash = bytes32(1e10 + blockId); - bytes32 signalRoot = bytes32(1e9 + blockId); - - proveBlock(Bob, Bob, meta, parentHash, blockHash, signalRoot, meta.minTier, ""); - vm.roll(block.number + 15 * 12); - uint16 minTier = meta.minTier; - vm.warp(block.timestamp + L1.getTier(minTier).cooldownWindow + 1); - - verifyBlock(Alice, 2); - parentHash = blockHash; + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); + + bytes32 parentMetaHash; + for (uint64 blockId = 1; blockId <= 20; blockId++) { + printVariables("before propose & prove & verify"); + // Create metadata and propose the block + meta = createBlockMetaData(Alice, blockId, 1, true); + proposeBlock(Alice, Alice, meta, ""); + + // Create proofs and prove a block + BasedOperator.ProofBatch memory blockProofs = createProofs(meta, Alice, true); + proveBlock(Alice, abi.encode(blockProofs)); + + //Wait enought time and verify block + vm.warp(uint32(block.timestamp + L1.SECURITY_DELAY_AFTER_PROVEN() + 1)); + vm.roll(block.number + 10); + verifyBlock(1); + parentMetaHash = keccak256(abi.encode(meta)); + printVariables("after verify"); } - printVariables(""); } - /// @dev Test verifying multiple blocks in one transaction - function test_L1_verifying_multiple_blocks_once() external { - giveEthAndTko(Alice, 1000 ether, 1000 ether); - console2.log("Alice balance:", tko.balanceOf(Alice)); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - giveEthAndTko(Carol, 1e8 ether, 100 ether); - // Bob - vm.prank(Bob, Bob); + function test_L1_propose_some_blocks_in_a_row_then_prove_and_verify() external { + giveEthAndTko(Alice, 100 ether, 100 ether); - bytes32 parentHash = GENESIS_BLOCK_HASH; + TaikoData.BlockMetadata[] memory blockMetaDatas = new TaikoData.BlockMetadata[](20); - for (uint256 blockId = 1; blockId <= conf.blockMaxProposals; blockId++) { - printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - printVariables("after propose"); + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); - bytes32 blockHash = bytes32(1e10 + blockId); - bytes32 signalRoot = bytes32(1e9 + blockId); - - proveBlock(Bob, Bob, meta, parentHash, blockHash, signalRoot, meta.minTier, ""); - parentHash = blockHash; + bytes32 parentMetaHash; + for (uint64 blockId = 1; blockId <= 20; blockId++) { + printVariables("before propose & prove & verify"); + // Create metadata and propose the block + blockMetaDatas[blockId - 1] = createBlockMetaData(Alice, blockId, 1, true); + proposeBlock(Alice, Alice, blockMetaDatas[blockId - 1], ""); + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); } - vm.roll(block.number + 15 * 12); - verifyBlock(Alice, conf.blockMaxProposals - 1); - printVariables("after verify"); - verifyBlock(Alice, conf.blockMaxProposals); - printVariables("after verify"); + for (uint64 blockId = 1; blockId <= 20; blockId++) { + // Create proofs and prove a block + BasedOperator.ProofBatch memory blockProofs = + createProofs(blockMetaDatas[blockId - 1], Alice, true); + proveBlock(Alice, abi.encode(blockProofs)); + + //Wait enought time and verify block (currently we simply just "wait enough" from latest + // block and not time it perfectly) + vm.warp(uint32(block.timestamp + L1.SECURITY_DELAY_AFTER_PROVEN() + 1)); + vm.roll(block.number + 10); + verifyBlock(1); + parentMetaHash = keccak256(abi.encode(blockMetaDatas[blockId - 1])); + printVariables("after verify 1"); + } } - /// @dev getCrossChainBlockHash tests - function test_L1_getCrossChainBlockHash0() external { - bytes32 genHash = L1.getSyncedSnippet(0).blockHash; - assertEq(GENESIS_BLOCK_HASH, genHash); + function test_L1_propose_block_outside_the_4_epoch_window() external { + giveEthAndTko(Alice, 100 ether, 100 ether); - // Reverts if block is not yet verified! - vm.expectRevert(TaikoErrors.L1_BLOCK_MISMATCH.selector); - L1.getSyncedSnippet(1); - } - - /// @dev getSyncedSnippet tests - function test_L1_getSyncedSnippet() external { - uint64 count = 10; - // Declare here so that block prop/prove/verif. can be used in 1 place TaikoData.BlockMetadata memory meta; - bytes32 blockHash; - bytes32 signalRoot; - bytes32[] memory parentHashes = new bytes32[](count); - parentHashes[0] = GENESIS_BLOCK_HASH; - - giveEthAndTko(Alice, 1e6 ether, 100_000 ether); - console2.log("Alice balance:", tko.balanceOf(Alice)); - giveEthAndTko(Bob, 1e7 ether, 100_000 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - - // Propose blocks - for (uint64 blockId = 1; blockId < count; ++blockId) { - printVariables("before propose"); - (meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - mine(5); - - blockHash = bytes32(1e10 + uint256(blockId)); - signalRoot = bytes32(1e9 + uint256(blockId)); - - proveBlock( - Bob, Bob, meta, parentHashes[blockId - 1], blockHash, signalRoot, meta.minTier, "" - ); - - vm.roll(block.number + 15 * 12); - uint16 minTier = meta.minTier; - vm.warp(block.timestamp + L1.getTier(minTier).cooldownWindow + 1); - verifyBlock(Carol, 1); + vm.roll(block.number + 1); + vm.warp(block.timestamp + 12); - // Querying written blockhash - assertEq(L1.getSyncedSnippet(blockId).blockHash, blockHash); + // Create metadata and propose the block 129 blocks later only + meta = createBlockMetaData(Alice, 1, 1, true); + vm.roll(block.number + 129); + vm.warp(block.timestamp + 129 * 12); - mine(5); - parentHashes[blockId] = blockHash; - } - - uint64 queriedBlockId = 1; - bytes32 expectedSR = bytes32(1e9 + uint256(queriedBlockId)); - - assertEq(expectedSR, L1.getSyncedSnippet(queriedBlockId).signalRoot); - - // 2nd - queriedBlockId = 2; - expectedSR = bytes32(1e9 + uint256(queriedBlockId)); - assertEq(expectedSR, L1.getSyncedSnippet(queriedBlockId).signalRoot); + proposeBlock(Alice, Alice, meta, TaikoErrors.L1_INVALID_L1_STATE_BLOCK.selector); + } - // Not found -> reverts - vm.expectRevert(TaikoErrors.L1_BLOCK_MISMATCH.selector); - L1.getSyncedSnippet((count + 1)); + function test_print_genesis_hash() external pure { + console2.logBytes32(keccak256("GENESIS_BLOCK_HASH")); } } -*/ \ No newline at end of file diff --git a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol index 86c6524a61b2..f5370caf6cfc 100644 --- a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol +++ b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol @@ -817,4 +817,4 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { printVariables(""); } } -*/ \ No newline at end of file +*/ diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index 6ba1fcfc3795..f928ffc7d86a 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -8,337 +8,340 @@ contract MockVerifier { return bytes.concat(keccak256("taiko")); } } - +*/ // TODO (dani): remove some code to sub-contracts, this one shall only contain // shared logics and data. + abstract contract TaikoL1TestBase is TaikoTest { AddressManager public addressManager; - AssignmentHook public assignmentHook; + // AssignmentHook public assignmentHook; + BasedOperator public basedOperator; TaikoToken public tko; - SignalService public ss; + // SignalService public ss; TaikoL1 public L1; TaikoData.Config conf; uint256 internal logCount; - PseZkVerifier public pv; - SgxVerifier public sv; - SgxAndZkVerifier public sgxZkVerifier; - GuardianVerifier public gv; - GuardianProver public gp; - TaikoA6TierProvider public cp; - Bridge public bridge; + // PseZkVerifier public pv; + /* 3 proof verifiers - to fulfill the requirement in BasedOperator.sol */ + SgxVerifier public sv1; + SgxVerifier public sv2; + SgxVerifier public sv3; + VerifierRegistry public vr; + // SgxAndZkVerifier public sgxZkVerifier; + // GuardianVerifier public gv; + // GuardianProver public gp; + // TaikoA6TierProvider public cp; + // Bridge public bridge; bytes32 public GENESIS_BLOCK_HASH = keccak256("GENESIS_BLOCK_HASH"); address public L2SS = randAddress(); address public L2 = randAddress(); - function deployTaikoL1() internal virtual returns (TaikoL1 taikoL1); + function deployTaikoL1(address addressManager) internal virtual returns (TaikoL1 taikoL1); function setUp() public virtual { - L1 = deployTaikoL1(); - conf = L1.getConfig(); + vm.startPrank(Alice); + vm.roll(20_232_182); //A real Ethereum block number from Jul-04-2024 09:13:47 + vm.warp(1_720_077_269); addressManager = AddressManager( deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + data: abi.encodeCall(AddressManager.init, (Alice)) }) ); - ss = SignalService( + L1 = deployTaikoL1(address(addressManager)); + conf = L1.getConfig(); + + console2.log("Address szar:", (address(addressManager))); + basedOperator = BasedOperator( deployProxy({ - name: "signal_service", - impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + name: "operator", + impl: address(new BasedOperator()), + data: abi.encodeCall(BasedOperator.init, (Alice, address(addressManager))) }) ); - pv = PseZkVerifier( + vr = VerifierRegistry( deployProxy({ - name: "tier_pse_zkevm", - impl: address(new PseZkVerifier()), - data: bytes.concat(PseZkVerifier.init.selector, abi.encode(address(addressManager))) + name: "verifier_registry", + impl: address(new VerifierRegistry()), + data: abi.encodeCall(VerifierRegistry.init, (address(addressManager))) }) ); - sv = SgxVerifier( + registerAddress("taiko", address(L1)); + registerAddress("operator", address(basedOperator)); + registerAddress("verifier_registry", address(vr)); + + // ss = SignalService( + // deployProxy({ + // name: "signal_service", + // impl: address(new SignalService()), + // data: bytes.concat(SignalService.init.selector) + // }) + // ); + + // pv = PseZkVerifier( + // deployProxy({ + // name: "tier_pse_zkevm", + // impl: address(new PseZkVerifier()), + // data: bytes.concat(PseZkVerifier.init.selector, + // abi.encode(address(addressManager))) + // }) + // ); + + address sgxImpl = address(new SgxVerifier()); + //Naming is like: 3, 1, 2, is because we need to have incremental order of addresses in + // BasedOperator, so figured out this is actually the way + + sv1 = SgxVerifier( deployProxy({ - name: "tier_sgx", - impl: address(new SgxVerifier()), - data: bytes.concat(SgxVerifier.init.selector, abi.encode(address(addressManager))) + name: "sgx2", //Name does not matter now, since we check validity via + // verifierRegistry + impl: sgxImpl, + data: abi.encodeCall(SgxVerifier.init, (Alice, address(addressManager))) }) ); - address[] memory initSgxInstances = new address[](1); - initSgxInstances[0] = SGX_X_0; - sv.addInstances(initSgxInstances); + console2.log(address(sv1)); - sgxZkVerifier = SgxAndZkVerifier( + sv2 = SgxVerifier( deployProxy({ - name: "tier_sgx_and_pse_zkevm", - impl: address(new SgxAndZkVerifier()), - data: bytes.concat(SgxAndZkVerifier.init.selector, abi.encode(address(addressManager))) + name: "sgx3", //Name does not matter now, since we check validity via + // verifierRegistry + impl: sgxImpl, + data: abi.encodeCall(SgxVerifier.init, (Alice, address(addressManager))) }) ); - gv = GuardianVerifier( + sv3 = SgxVerifier( deployProxy({ - name: "guardian_verifier", - impl: address(new GuardianVerifier()), - data: bytes.concat(GuardianVerifier.init.selector, abi.encode(address(addressManager))) + name: "sgx1", //Name does not matter now, since we check validity via + // verifierRegistry + impl: sgxImpl, + data: abi.encodeCall(SgxVerifier.init, (Alice, address(addressManager))) }) ); - gp = GuardianProver( - deployProxy({ - name: "guardian_prover", - impl: address(new GuardianProver()), - data: bytes.concat(GuardianProver.init.selector, abi.encode(address(addressManager))) - }) - ); + console2.log(address(sv2)); - setupGuardianProverMultisig(); + // sv2 = SgxVerifier( + // deployProxy({ + // name: "sgx3", //Name does not matter now, since we check validity via + // // verifierRegistry + // impl: sgxImpl, + // data: abi.encodeCall(SgxVerifier.init, (Alice, address(addressManager))) + // }) + // ); - cp = TaikoA6TierProvider( - deployProxy({ - name: "tier_provider", - impl: address(new TaikoA6TierProvider()), - data: bytes.concat(TaikoA6TierProvider.init.selector) - }) - ); + console2.log(address(sv3)); - bridge = Bridge( - payable( - deployProxy({ - name: "bridge", - impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) - }) - ) - ); + // Bootstrap / add first trusted instance -> SGX code needs some change tho - because + // changed since taiko-simplified was created first. + address[] memory initSgxInstances = new address[](1); + initSgxInstances[0] = SGX_X_0; - assignmentHook = AssignmentHook( - deployProxy({ - name: "assignment_hook", - impl: address(new AssignmentHook()), - data: bytes.concat(AssignmentHook.init.selector, abi.encode(address(addressManager))) - }) - ); - - registerAddress("taiko", address(L1)); - registerAddress("tier_pse_zkevm", address(pv)); - registerAddress("tier_sgx", address(sv)); - registerAddress("tier_guardian", address(gv)); - registerAddress("tier_sgx_and_pse_zkevm", address(sgxZkVerifier)); - registerAddress("tier_provider", address(cp)); - registerAddress("signal_service", address(ss)); - registerAddress("guardian_prover", address(gp)); - registerAddress("bridge", address(bridge)); - registerL2Address("taiko", address(L2)); - registerL2Address("signal_service", address(L2SS)); - registerL2Address("taiko_l2", address(L2)); - - registerAddress(pv.getVerifierName(300), address(new MockVerifier())); + sv1.addInstances(initSgxInstances); + sv2.addInstances(initSgxInstances); + sv3.addInstances(initSgxInstances); + + // address[] memory initSgxInstances = new address[](1); + // initSgxInstances[0] = SGX_X_0; + // sv.addInstances(initSgxInstances); + + // sgxZkVerifier = SgxAndZkVerifier( + // deployProxy({ + // name: "tier_sgx_and_pse_zkevm", + // impl: address(new SgxAndZkVerifier()), + // data: bytes.concat(SgxAndZkVerifier.init.selector, abi.encode(address(addressManager))) + // }) + // ); + + // gv = GuardianVerifier( + // deployProxy({ + // name: "guardian_verifier", + // impl: address(new GuardianVerifier()), + // data: bytes.concat(GuardianVerifier.init.selector, abi.encode(address(addressManager))) + // }) + // ); + + // gp = GuardianProver( + // deployProxy({ + // name: "guardian_prover", + // impl: address(new GuardianProver()), + // data: bytes.concat(GuardianProver.init.selector, abi.encode(address(addressManager))) + // }) + // ); + + // setupGuardianProverMultisig(); + + // cp = TaikoA6TierProvider( + // deployProxy({ + // name: "tier_provider", + // impl: address(new TaikoA6TierProvider()), + // data: bytes.concat(TaikoA6TierProvider.init.selector) + // }) + // ); + + // bridge = Bridge( + // payable( + // deployProxy({ + // name: "bridge", + // impl: address(new Bridge()), + // data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), + // registerTo: address(addressManager), + // owner: address(0) + // }) + // ) + // ); + + // assignmentHook = AssignmentHook( + // deployProxy({ + // name: "assignment_hook", + // impl: address(new AssignmentHook()), + // data: bytes.concat(AssignmentHook.init.selector, abi.encode(address(addressManager))) + // }) + // ); + + // registerAddress("taiko", address(L1)); + // registerAddress("tier_pse_zkevm", address(pv)); + // registerAddress("tier_sgx", address(sv)); + // registerAddress("tier_guardian", address(gv)); + // registerAddress("tier_sgx_and_pse_zkevm", address(sgxZkVerifier)); + // registerAddress("tier_provider", address(cp)); + // registerAddress("signal_service", address(ss)); + // registerAddress("guardian_prover", address(gp)); + // registerAddress("bridge", address(bridge)); + // registerL2Address("taiko", address(L2)); + // registerL2Address("signal_service", address(L2SS)); + // registerL2Address("taiko_l2", address(L2)); + + // registerAddress(pv.getVerifierName(300), address(new MockVerifier())); tko = TaikoToken( deployProxy({ name: "taiko_token", impl: address(new TaikoToken()), - data: bytes.concat( - TaikoToken.init.selector, - abi.encode( - "Taiko Token", // - "TTKOk", - address(this) - ) - ), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(TaikoToken.init, (address(0), address(this))), + registerTo: address(addressManager) }) ); - L1.init(address(addressManager), GENESIS_BLOCK_HASH); + L1.init(Alice, address(addressManager), GENESIS_BLOCK_HASH); printVariables("init "); + vm.stopPrank(); + + // Add those 3 to verifier registry + vm.startPrank(vr.owner()); + vr.addVerifier(address(sv1), "sgx1"); + vr.addVerifier(address(sv2), "sgx2"); + vr.addVerifier(address(sv3), "sgx3"); + vm.stopPrank(); } function proposeBlock( address proposer, address prover, - uint32 gasLimit, - uint24 txListSize - ) - internal - returns ( - TaikoData.BlockMetadata memory meta - ) - { - TaikoData.TierFee[] memory tierFees = new TaikoData.TierFee[](5); - // Register the tier fees - // Based on OPL2ConfigTier we need 3: - // - LibTiers.TIER_PSE_ZKEVM; - // - LibTiers.TIER_SGX; - // - LibTiers.TIER_OPTIMISTIC; - // - LibTiers.TIER_GUARDIAN; - // - LibTiers.TIER_SGX_AND_PSE_ZKEVM - tierFees[0] = TaikoData.TierFee(LibTiers.TIER_OPTIMISTIC, 1 ether); - tierFees[1] = TaikoData.TierFee(LibTiers.TIER_SGX, 1 ether); - tierFees[2] = TaikoData.TierFee(LibTiers.TIER_PSE_ZKEVM, 2 ether); - tierFees[3] = TaikoData.TierFee(LibTiers.TIER_SGX_AND_PSE_ZKEVM, 2 ether); - tierFees[4] = TaikoData.TierFee(LibTiers.TIER_GUARDIAN, 0 ether); - // For the test not to fail, set the message.value to the highest, the - // rest will be returned - // anyways - uint256 msgValue = 2 ether; - - AssignmentHook.ProverAssignment memory assignment = AssignmentHook.ProverAssignment({ - feeToken: address(0), - tierFees: tierFees, - expiry: uint64(block.timestamp + 60 minutes), - maxBlockId: 0, - maxProposedIn: 0, - metaHash: 0, - signature: new bytes(0) - }); - - assignment.signature = - _signAssignment(prover, assignment, address(L1), keccak256(new bytes(txListSize))); - - (, TaikoData.SlotB memory b) = L1.getStateVariables(); - - uint256 _difficulty; - unchecked { - _difficulty = block.prevrandao * b.numBlocks; - } - - meta.timestamp = uint64(block.timestamp); - meta.l1Height = uint64(block.number - 1); - meta.l1Hash = blockhash(block.number - 1); - meta.difficulty = bytes32(_difficulty); - meta.gasLimit = gasLimit; - - TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1); - - hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment)); - - vm.prank(proposer, proposer); - meta = L1.proposeBlock{ value: msgValue }( - abi.encode(TaikoData.BlockParams(prover, 0, 0, 0, 0, false, 0, hookcalls)), - new bytes(txListSize) - ); - } - - function proveBlock( - address msgSender, - address prover, TaikoData.BlockMetadata memory meta, - bytes32 parentHash, - bytes32 blockHash, - bytes32 signalRoot, - uint16 tier, bytes4 revertReason ) internal + returns (TaikoData.BlockMetadata memory) { - TaikoData.Transition memory tran = TaikoData.Transition({ - parentHash: parentHash, - blockHash: blockHash, - signalRoot: signalRoot, - graffiti: 0x0 - }); - - bytes32 instance = - pv.calcInstance(tran, prover, keccak256(abi.encode(meta)), meta.blobHash, 0); - - TaikoData.TierProof memory proof; - proof.tier = tier; - { - PseZkVerifier.ZkEvmProof memory zkProof; - zkProof.verifierId = 300; - zkProof.zkp = bytes.concat( - bytes16(0), - bytes16(instance), - bytes16(0), - bytes16(uint128(uint256(instance))), - new bytes(100) + // TaikoData.TierFee[] memory tierFees = new TaikoData.TierFee[](5); + // // Register the tier fees + // // Based on OPL2ConfigTier we need 3: + // // - LibTiers.TIER_PSE_ZKEVM; + // // - LibTiers.TIER_SGX; + // // - LibTiers.TIER_OPTIMISTIC; + // // - LibTiers.TIER_GUARDIAN; + // // - LibTiers.TIER_SGX_AND_PSE_ZKEVM + // tierFees[0] = TaikoData.TierFee(LibTiers.TIER_OPTIMISTIC, 1 ether); + // tierFees[1] = TaikoData.TierFee(LibTiers.TIER_SGX, 1 ether); + // tierFees[2] = TaikoData.TierFee(LibTiers.TIER_PSE_ZKEVM, 2 ether); + // tierFees[3] = TaikoData.TierFee(LibTiers.TIER_SGX_AND_PSE_ZKEVM, 2 ether); + // tierFees[4] = TaikoData.TierFee(LibTiers.TIER_GUARDIAN, 0 ether); + // // For the test not to fail, set the message.value to the highest, the + // // rest will be returned + // // anyways + // uint256 msgValue = 2 ether; + + // AssignmentHook.ProverAssignment memory assignment = AssignmentHook.ProverAssignment({ + // feeToken: address(0), + // tierFees: tierFees, + // expiry: uint64(block.timestamp + 60 minutes), + // maxBlockId: 0, + // maxProposedIn: 0, + // metaHash: 0, + // signature: new bytes(0) + // }); + + // assignment.signature = + // _signAssignment(prover, assignment, address(L1), keccak256(new bytes(txListSize))); + + // (, TaikoData.SlotB memory b) = L1.getStateVariables(); + + // uint256 _difficulty; + // unchecked { + // _difficulty = block.prevrandao; + // } + + // meta.blockHash = blockHash; + // meta.parentHash = parentHash; + + // meta.timestamp = uint64(block.timestamp); + // meta.l1Height = uint64(block.number - 1); + // meta.l1Hash = blockhash(block.number - 1); + // meta.difficulty = bytes32(_difficulty); + // meta.gasLimit = gasLimit; + + // TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1); + + // hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment)); + + bytes memory dummyTxList = + hex"0000000000000000000000000000000000000000000000000000000000000001"; + bytes memory emptyTxList; + + if (revertReason == "") { + vm.prank(proposer, proposer); + meta = basedOperator.proposeBlock{ value: 1 ether / 10 }( + abi.encode(meta), meta.blobUsed == true ? emptyTxList : dummyTxList, prover ); - - proof.data = abi.encode(zkProof); - } - - address newInstance; - // Keep changing the pub key associated with an instance to avoid - // attacks, - // obviously just a mock due to 2 addresses changing all the time. - (newInstance,) = sv.instances(0); - if (newInstance == SGX_X_0) { - newInstance = SGX_X_1; } else { - newInstance = SGX_X_0; - } - - if (tier == LibTiers.TIER_SGX) { - bytes memory signature = - createSgxSignatureProof(tran, newInstance, prover, keccak256(abi.encode(meta))); - - proof.data = bytes.concat(bytes4(0), bytes20(newInstance), signature); + vm.prank(proposer, proposer); + vm.expectRevert(revertReason); + meta = basedOperator.proposeBlock{ value: 1 ether / 10 }( + abi.encode(meta), meta.blobUsed == true ? emptyTxList : dummyTxList, prover + ); } - if (tier == LibTiers.TIER_SGX_AND_PSE_ZKEVM) { - bytes memory signature = - createSgxSignatureProof(tran, newInstance, prover, keccak256(abi.encode(meta))); - - bytes memory sgxProof = bytes.concat(bytes4(0), bytes20(newInstance), signature); - // Concatenate SGX and ZK (in this order) - proof.data = bytes.concat(sgxProof, proof.data); - } + return meta; + } - if (tier == LibTiers.TIER_GUARDIAN) { - proof.data = ""; - - // Grant 2 signatures, 3rd might be a revert - vm.prank(David, David); - gp.approve(meta, tran, proof); - vm.prank(Emma, Emma); - gp.approve(meta, tran, proof); - - if (revertReason != "") { - vm.prank(Frank, Frank); - vm.expectRevert(); // Revert reason is 'wrapped' so will not be - // identical to the expectedRevert - gp.approve(meta, tran, proof); - } else { - vm.prank(Frank, Frank); - gp.approve(meta, tran, proof); - } - } else { - if (revertReason != "") { - vm.prank(msgSender, msgSender); - vm.expectRevert(revertReason); - L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); - } else { - vm.prank(msgSender, msgSender); - L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); - } - } + function proveBlock(address prover, bytes memory blockProof) internal { + vm.prank(prover, prover); + basedOperator.proveBlock(blockProof); } - function verifyBlock(address, uint64 count) internal { - L1.verifyBlocks(count); + function verifyBlock(uint64 count) internal { + basedOperator.verifyBlocks(count); } - function setupGuardianProverMultisig() internal { - address[] memory initMultiSig = new address[](5); - initMultiSig[0] = David; - initMultiSig[1] = Emma; - initMultiSig[2] = Frank; - initMultiSig[3] = Grace; - initMultiSig[4] = Henry; + // function setupGuardianProverMultisig() internal { + // address[] memory initMultiSig = new address[](5); + // initMultiSig[0] = David; + // initMultiSig[1] = Emma; + // initMultiSig[2] = Frank; + // initMultiSig[3] = Grace; + // initMultiSig[4] = Henry; - gp.setGuardians(initMultiSig, 3); - } + // gp.setGuardians(initMultiSig, 3); + // } function registerAddress(bytes32 nameHash, address addr) internal { addressManager.setAddress(uint64(block.chainid), nameHash, addr); @@ -350,32 +353,32 @@ abstract contract TaikoL1TestBase is TaikoTest { console2.log(conf.chainId, string(abi.encodePacked(nameHash)), unicode"→", addr); } - function _signAssignment( - address signer, - AssignmentHook.ProverAssignment memory assignment, - address taikoAddr, - bytes32 blobHash - ) - internal - view - returns (bytes memory signature) - { - uint256 signerPrivateKey; - - // In the test suite these are the 3 which acts as provers - if (signer == Alice) { - signerPrivateKey = 0x1; - } else if (signer == Bob) { - signerPrivateKey = 0x2; - } else if (signer == Carol) { - signerPrivateKey = 0x3; - } - - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - signerPrivateKey, assignmentHook.hashAssignment(assignment, taikoAddr, blobHash) - ); - signature = abi.encodePacked(r, s, v); - } + // function _signAssignment( + // address signer, + // AssignmentHook.ProverAssignment memory assignment, + // address taikoAddr, + // bytes32 blobHash + // ) + // internal + // view + // returns (bytes memory signature) + // { + // uint256 signerPrivateKey; + + // // In the test suite these are the 3 which acts as provers + // if (signer == Alice) { + // signerPrivateKey = 0x1; + // } else if (signer == Bob) { + // signerPrivateKey = 0x2; + // } else if (signer == Carol) { + // signerPrivateKey = 0x3; + // } + + // (uint8 v, bytes32 r, bytes32 s) = vm.sign( + // signerPrivateKey, assignmentHook.hashAssignment(assignment, taikoAddr, blobHash) + // ); + // signature = abi.encodePacked(r, s, v); + // } function createSgxSignatureProof( TaikoData.Transition memory tran, @@ -387,7 +390,9 @@ abstract contract TaikoL1TestBase is TaikoTest { view returns (bytes memory signature) { - bytes32 digest = sv.getSignedHash(tran, newInstance, prover, metaHash); + bytes32 digest = LibPublicInput.hashPublicInputs( + tran, address(sv1), newInstance, prover, metaHash, L1.getConfig().chainId + ); uint256 signerPrivateKey; @@ -408,40 +413,121 @@ abstract contract TaikoL1TestBase is TaikoTest { vm.prank(to, to); tko.approve(address(L1), amountTko); - vm.prank(to, to); - tko.approve(address(assignmentHook), amountTko); + // vm.prank(to, to); + // tko.approve(address(assignmentHook), amountTko); console2.log("TKO balance:", to, tko.balanceOf(to)); console2.log("ETH balance:", to, to.balance); } function printVariables(string memory comment) internal { - (TaikoData.SlotA memory a, TaikoData.SlotB memory b) = L1.getStateVariables(); - string memory str = string.concat( Strings.toString(logCount++), ":[", - Strings.toString(b.lastVerifiedBlockId), + Strings.toString(L1.getLastVerifiedBlockId()), unicode"→", - Strings.toString(b.numBlocks), - "]" - ); - - str = string.concat( - str, - " nextEthDepositToProcess:", - Strings.toString(a.nextEthDepositToProcess), - " numEthDeposits:", - Strings.toString(a.numEthDeposits), - " // ", + Strings.toString(L1.getNumOfBlocks()), + "] // ", comment ); console2.log(str); } function mine(uint256 counts) internal { - vm.warp(block.timestamp + 20 * counts); + vm.warp(block.timestamp + 12 * counts); vm.roll(block.number + counts); } + + function createBlockMetaData( + address coinbase, + uint64 l2BlockNumber, + uint32 belowBlockTipHeight, // How many blocks below from current tip (block.id) + bool blobUsed + ) + internal + returns (TaikoData.BlockMetadata memory meta) + { + meta.blockHash = randBytes32(); + + TaikoData.Block memory parentBlock = L1.getBlock(l2BlockNumber - 1); + meta.parentMetaHash = parentBlock.metaHash; + meta.parentBlockHash = parentBlock.blockHash; + meta.l1Hash = blockhash(block.number - belowBlockTipHeight); + meta.difficulty = block.prevrandao; + meta.blobHash = randBytes32(); + meta.coinbase = coinbase; + meta.l2BlockNumber = l2BlockNumber; + meta.gasLimit = L1.getConfig().blockMaxGasLimit; + meta.l1StateBlockNumber = uint32(block.number - belowBlockTipHeight); + meta.timestamp = uint64(block.timestamp - (belowBlockTipHeight * 12)); // x blocks behind + + if (blobUsed) { + meta.txListByteOffset = 0; + meta.txListByteSize = 0; + meta.blobUsed = true; + } else { + meta.txListByteOffset = 0; + meta.txListByteSize = 32; // Corresponding dummyTxList is set during proposeBlock() + meta.blobUsed = false; + } + } + + function createProofs( + TaikoData.BlockMetadata memory meta, + address prover, + bool threeMockSGXProofs // Used to indicate to "trick" the BasedProver with 3 different (but + // same code) deployments of SGX verifier - later we can fine tune to have 3 correct, + // valid proofs. + ) + internal + view + returns (BasedOperator.ProofBatch memory proofBatch) + { + // Set metadata + proofBatch.blockMetadata = meta; + + // Set transition + TaikoData.Transition memory transition; + transition.parentBlockHash = L1.getBlock(meta.l2BlockNumber - 1).blockHash; + transition.blockHash = meta.blockHash; + proofBatch.transition = transition; + + // Set prover + proofBatch.prover = prover; + + address newInstance; + // Keep changing the pub key associated with an instance to avoid + // attacks, + // obviously just a mock due to 2 addresses changing all the time. + (newInstance,) = sv1.instances(0); + if (newInstance == SGX_X_0) { + newInstance = SGX_X_1; + } else { + newInstance = SGX_X_0; + } + + BasedOperator.ProofData[] memory proofs = new BasedOperator.ProofData[](3); + + bytes memory signature = + createSgxSignatureProof(transition, newInstance, prover, keccak256(abi.encode(meta))); + + // The order is on purpose reversed, becase of the L1_INVALID_OR_DUPLICATE_VERIFIER() check + proofs[0].verifier = sv2; + proofs[0].proof = bytes.concat(bytes4(0), bytes20(newInstance), signature); + + if (threeMockSGXProofs) { + // The order is on purpose reversed, becase of the L1_INVALID_OR_DUPLICATE_VERIFIER() + // check + proofs[1].verifier = sv1; + proofs[1].proof = bytes.concat(bytes4(0), bytes20(newInstance), signature); + + proofs[2].verifier = sv3; + proofs[2].proof = bytes.concat(bytes4(0), bytes20(newInstance), signature); + } else { + //Todo(dani): Implement more proof and verifiers when needed/available but for now, not + // to change the code in BasedOperator, maybe best to mock those 3 + } + + proofBatch.proofs = proofs; + } } -*/ \ No newline at end of file diff --git a/packages/protocol/test/L2/Lib1559Math.t.sol b/packages/protocol/test/L2/Lib1559Math.t.sol index 474af869c009..a393a4ff7056 100644 --- a/packages/protocol/test/L2/Lib1559Math.t.sol +++ b/packages/protocol/test/L2/Lib1559Math.t.sol @@ -1,36 +1,39 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; +import "../../contracts/L2/LibL2Config.sol"; contract TestLib1559Math is TaikoTest { using LibMath for uint256; - function test_eip1559_math() external { - uint256 gasTarget = 15 * 1e6 * 10; - uint256 adjustmentQuotient = 8; - uint256 adjustmentFactor = gasTarget * adjustmentQuotient; - // The expected values are calculated in eip1559_util.py - _assertAmostEq( - 999_999_916, - Lib1559Math.basefee({ gasExcess: 49_954_623_777, adjustmentFactor: adjustmentFactor }) - ); - - _assertAmostEq( - 48_246_703_821_869_050_543_408_253_349_256_099_602_613_005_189_120, - Lib1559Math.basefee({ - gasExcess: LibFixedPointMath.MAX_EXP_INPUT * adjustmentFactor - / LibFixedPointMath.SCALING_FACTOR, - adjustmentFactor: adjustmentFactor - }) - ); - } - - // Assert the different between two number is smaller than 1/1000000 - function _assertAmostEq(uint256 a, uint256 b) private { - uint256 min = a.min(b); - uint256 max = a.max(b); - assertTrue(max > 0 && ((max - min) * 1_000_000) / max <= 1); - console2.log(a, " <> ", b); - } + // function test_eip1559_math() external pure { + // LibL2Config.Config memory config = LibL2Config.get(); + // uint256 adjustmentFactor = config.gasTargetPerL1Block * config.basefeeAdjustmentQuotient; + + // uint256 baseFee; + // uint256 i; + // uint256 target = 0.01 gwei; + + // for (uint256 k; k < 5; ++k) { + // for (; baseFee < target; ++i) { + // baseFee = Lib1559Math.basefee(config.gasTargetPerL1Block * i, adjustmentFactor); + // } + // console2.log("base fee:", baseFee); + // console2.log(" gasExcess:", config.gasTargetPerL1Block * i); + // console2.log(" i:", i); + // target *= 10; + // } + // } + + // function test_eip1559_math_max() external pure { + // LibL2Config.Config memory config = LibL2Config.get(); + // uint256 adjustmentFactor = config.gasTargetPerL1Block * config.basefeeAdjustmentQuotient; + + // uint256 gasExcess = type(uint64).max; + // uint256 baseFee = Lib1559Math.basefee(gasExcess, adjustmentFactor); + + // console2.log("base fee (gwei):", baseFee / 1 gwei); + // console2.log(" gasExcess:", gasExcess); + // } } diff --git a/packages/protocol/test/L2/TaikoL2.t.sol b/packages/protocol/test/L2/TaikoL2.t.sol deleted file mode 100644 index e62b3fe21152..000000000000 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "../TaikoTest.sol"; - -contract SkipBasefeeCheckL2 is TaikoL2EIP1559Configurable { - function skipFeeCheck() public pure override returns (bool) { - return true; - } -} - -contract TestTaikoL2 is TaikoTest { - using SafeCast for uint256; - - // Initial salt for semi-random generation - uint256 salt = 2_195_684_615_435_261_315_311; - // same as `block_gas_limit` in foundry.toml - uint32 public constant BLOCK_GAS_LIMIT = 30_000_000; - - AddressManager public addressManager; - SignalService public ss; - TaikoL2EIP1559Configurable public L2; - SkipBasefeeCheckL2 public L2skip; - - function setUp() public { - addressManager = AddressManager( - deployProxy({ - name: "address_manager", - impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) - }) - ); - - ss = SignalService( - deployProxy({ - name: "signal_service", - impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector), - registerTo: address(addressManager), - owner: address(0) - }) - ); - - uint64 gasExcess = 0; - uint8 quotient = 8; - uint32 gasTarget = 60_000_000; - - L2 = TaikoL2EIP1559Configurable( - deployProxy({ - name: "taiko_l2", - impl: address(new TaikoL2EIP1559Configurable()), - data: bytes.concat(TaikoL2.init.selector, abi.encode(address(ss), gasExcess)) - }) - ); - - L2.setConfigAndExcess(TaikoL2.Config(gasTarget, quotient), gasExcess); - - gasExcess = 195_420_300_100; - L2skip = SkipBasefeeCheckL2( - deployProxy({ - name: "taiko_l2", - impl: address(new SkipBasefeeCheckL2()), - data: bytes.concat(TaikoL2.init.selector, abi.encode(address(ss), gasExcess)) - }) - ); - - L2skip.setConfigAndExcess(TaikoL2.Config(gasTarget, quotient), gasExcess); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 30); - } - - function test_L2_AnchorTx_with_constant_block_time() external { - for (uint256 i; i < 100; ++i) { - vm.fee(1); - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - _anchor(BLOCK_GAS_LIMIT); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 30); - } - } - - function test_L2_AnchorTx_with_decreasing_block_time() external { - for (uint256 i; i < 32; ++i) { - vm.fee(1); - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - _anchor(BLOCK_GAS_LIMIT); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 30 - i); - } - } - - function test_L2_AnchorTx_with_increasing_block_time() external { - for (uint256 i; i < 30; ++i) { - vm.fee(1); - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - _anchor(BLOCK_GAS_LIMIT); - - vm.roll(block.number + 1); - - vm.warp(block.timestamp + 30 + i); - } - } - - function test_simulation_lower_traffic() external { - console2.log("LOW TRAFFIC STARTS"); // For parser - _simulation(100_000, 10_000_000, 1, 8); - console2.log("LOW TRAFFIC ENDS"); - } - - function test_simulation_higher_traffic() external { - console2.log("HIGH TRAFFIC STARTS"); // For parser - _simulation(100_000, 120_000_000, 1, 8); - console2.log("HIGH TRAFFIC ENDS"); - } - - function test_simulation_target_traffic() external { - console2.log("TARGET TRAFFIC STARTS"); // For parser - _simulation(60_000_000, 0, 12, 0); - console2.log("TARGET TRAFFIC ENDS"); - } - - function _simulation( - uint256 minGas, - uint256 maxDiffToMinGas, - uint8 quickest, - uint8 maxDiffToQuickest - ) - internal - { - // We need to randomize the: - // - parent gas used (We should sometimes exceed 150.000.000 gas / 12 - // seconds (to simulate congestion a bit) !!) - // - the time we fire away an L2 block (anchor transaction). - // The rest is baked in. - // initial gas excess issued: 49954623777 (from eip1559_util.py) if we - // want to stick to the params of 10x Ethereum gas, etc. - - // This variables counts if we reached the 12seconds (L1) height, if so - // then resets the accumulated parent gas used and increments the L1 - // height number - uint8 accumulated_seconds = 0; - uint256 accumulated_parent_gas_per_l1_block = 0; - uint64 l1Height = uint64(block.number); - uint64 l1BlockCounter = 0; - uint64 maxL2BlockCount = 180; - uint256 allBaseFee = 0; - uint256 allGasUsed = 0; - uint256 newRandomWithoutSalt; - // Simulate 200 L2 blocks - for (uint256 i; i < maxL2BlockCount; ++i) { - newRandomWithoutSalt = uint256( - keccak256( - abi.encodePacked( - block.prevrandao, msg.sender, block.timestamp, i, newRandomWithoutSalt, salt - ) - ) - ); - - uint32 currentGasUsed; - if (maxDiffToMinGas == 0) { - currentGasUsed = uint32(minGas); - } else { - currentGasUsed = - uint32(pickRandomNumber(newRandomWithoutSalt, minGas, maxDiffToMinGas)); - } - salt = uint256(keccak256(abi.encodePacked(currentGasUsed, salt))); - accumulated_parent_gas_per_l1_block += currentGasUsed; - allGasUsed += currentGasUsed; - - uint8 currentTimeAhead; - if (maxDiffToQuickest == 0) { - currentTimeAhead = uint8(quickest); - } else { - currentTimeAhead = - uint8(pickRandomNumber(newRandomWithoutSalt, quickest, maxDiffToQuickest)); - } - accumulated_seconds += currentTimeAhead; - - if (accumulated_seconds >= 12) { - console2.log( - "Gas used per L1 block:", l1Height, ":", accumulated_parent_gas_per_l1_block - ); - l1Height++; - l1BlockCounter++; - accumulated_parent_gas_per_l1_block = 0; - accumulated_seconds = 0; - } - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - _anchorSimulation(currentGasUsed, l1Height); - uint256 currentBaseFee = L2skip.getBasefee(l1Height, currentGasUsed); - allBaseFee += currentBaseFee; - console2.log("Actual gas in L2 block is:", currentGasUsed); - console2.log("L2block to baseFee is:", i, ":", currentBaseFee); - vm.roll(block.number + 1); - - vm.warp(block.timestamp + currentTimeAhead); - } - - console2.log("Average wei gas price per L2 block is:", (allBaseFee / maxL2BlockCount)); - console2.log("Average gasUsed per L1 block:", (allGasUsed / l1BlockCounter)); - } - - // calling anchor in the same block more than once should fail - function test_L2_AnchorTx_revert_in_same_block() external { - vm.fee(1); - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - _anchor(BLOCK_GAS_LIMIT); - - vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); - vm.expectRevert(); // L2_PUBLIC_INPUT_HASH_MISMATCH - _anchor(BLOCK_GAS_LIMIT); - } - - // calling anchor in the same block more than once should fail - function test_L2_AnchorTx_revert_from_wrong_signer() external { - vm.fee(1); - vm.expectRevert(); - _anchor(BLOCK_GAS_LIMIT); - } - - function test_L2_AnchorTx_signing(bytes32 digest) external { - (uint8 v, uint256 r, uint256 s) = L2.signAnchor(digest, uint8(1)); - address signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); - assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); - - (v, r, s) = L2.signAnchor(digest, uint8(2)); - signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); - assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); - - vm.expectRevert(); - L2.signAnchor(digest, uint8(0)); - - vm.expectRevert(); - L2.signAnchor(digest, uint8(3)); - } - - function _anchor(uint32 parentGasLimit) private { - bytes32 l1Hash = randBytes32(); - bytes32 l1SignalRoot = randBytes32(); - L2.anchor(l1Hash, l1SignalRoot, 12_345, parentGasLimit); - } - - function _anchorSimulation(uint32 parentGasLimit, uint64 l1Height) private { - bytes32 l1Hash = randBytes32(); - bytes32 l1SignalRoot = randBytes32(); - L2skip.anchor(l1Hash, l1SignalRoot, l1Height, parentGasLimit); - } - - function registerAddress(bytes32 nameHash, address addr) internal { - addressManager.setAddress(uint64(block.chainid), nameHash, addr); - console2.log(block.chainid, uint256(nameHash), unicode"→", addr); - } - - // Semi-random number generator - function pickRandomNumber( - uint256 randomNum, - uint256 lowerLimit, - uint256 diffBtwLowerAndUpperLimit - ) - internal - view - returns (uint256) - { - randomNum = uint256(keccak256(abi.encodePacked(randomNum, salt))); - return (lowerLimit + (randomNum % diffBtwLowerAndUpperLimit)); - } -} diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index a004f29255f2..8406d763c9c3 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -17,9 +17,11 @@ import "../contracts/tokenvault/ERC20Vault.sol"; import "../contracts/tokenvault/ERC721Vault.sol"; import "../contracts/tokenvault/ERC1155Vault.sol"; -import "../contracts/L1/TaikoToken.sol"; +import "../contracts/tko/TaikoToken.sol"; +import "../contracts/L1/BasedOperator.sol"; +import "../contracts/L1/VerifierRegistry.sol"; +import "../contracts/L1/verifiers/MockSgxVerifier.sol"; /*import "../contracts/L1/TaikoL1.sol"; -import "../contracts/L1/verifiers/SgxVerifier.sol"; import "../contracts/L1/verifiers/GuardianVerifier.sol"; import "../contracts/L1/verifiers/PseZkVerifier.sol"; import "../contracts/L1/verifiers/SgxAndZkVerifier.sol"; @@ -28,13 +30,9 @@ import "../contracts/L1/tiers/ITierProvider.sol"; import "../contracts/L1/tiers/ITierProvider.sol"; import "../contracts/L1/provers/GuardianProver.sol";*/ -import "../contracts/L2/Lib1559Math.sol"; -import "../contracts/L2/TaikoL2EIP1559Configurable.sol"; -import "../contracts/L2/TaikoL2.sol"; - -import "../contracts/team/TimelockTokenPool.sol"; -import "../contracts/team/airdrop/ERC20Airdrop.sol"; -import "../contracts/team/airdrop/ERC20Airdrop2.sol"; +// import "../contracts/L2/Lib1559Math.sol"; +//import "../contracts/L2/TaikoL2EIP1559Configurable.sol"; +// import "../contracts/L2/TaikoL2.sol"; import "../contracts/test/erc20/FreeMintERC20.sol"; @@ -82,4 +80,12 @@ abstract contract TaikoTest is Test, DeployCapability { function randBytes32() internal returns (bytes32) { return keccak256(abi.encodePacked("bytes32", _seed++)); } -} \ No newline at end of file + + function strToBytes32(string memory input) internal pure returns (bytes32 result) { + require(bytes(input).length <= 32, "String too long"); + // Copy the string's bytes directly into the bytes32 variable + assembly { + result := mload(add(input, 32)) + } + } +} diff --git a/packages/protocol/test/bridge/Bridge.t.sol b/packages/protocol/test/bridge/Bridge.t.sol index b76a89293a1d..99df95ce9342 100644 --- a/packages/protocol/test/bridge/Bridge.t.sol +++ b/packages/protocol/test/bridge/Bridge.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; @@ -13,11 +13,24 @@ contract UntrustedSendMessageRelayer { uint256 message_value ) public + returns (bytes32 msgHash, IBridge.Message memory updatedMessage) { - IBridge(bridge).sendMessage{ value: message_value }(message); + return IBridge(bridge).sendMessage{ value: message_value }(message); } } +// A malicious contract that attempts to exhaust gas +contract MaliciousContract2 { + fallback() external payable { + while (true) { } // infinite loop + } +} + +// Non malicious contract that does not exhaust gas +contract NonMaliciousContract1 { + fallback() external payable { } +} + contract BridgeTest is TaikoTest { AddressManager addressManager; BadReceiver badReceiver; @@ -25,9 +38,14 @@ contract BridgeTest is TaikoTest { Bridge bridge; Bridge destChainBridge; SignalService signalService; - DummyCrossChainSync crossChainSync; SkipProofCheckSignal mockProofSignalService; UntrustedSendMessageRelayer untrustedSenderContract; + + NonMaliciousContract1 nonmaliciousContract1; + MaliciousContract2 maliciousContract2; + + address mockDAO = randAddress(); //as "real" L1 owner + uint64 destChainId = 19_389; function setUp() public { @@ -38,7 +56,7 @@ contract BridgeTest is TaikoTest { deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + data: abi.encodeCall(AddressManager.init, (address(0))) }) ); @@ -47,9 +65,8 @@ contract BridgeTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -59,18 +76,23 @@ contract BridgeTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))) }) ) ); + // "Deploy" on L2 only + uint64 l1ChainId = uint64(block.chainid); + vm.chainId(destChainId); + + vm.chainId(l1ChainId); + mockProofSignalService = SkipProofCheckSignal( deployProxy({ name: "signal_service", impl: address(new SkipProofCheckSignal()), - data: bytes.concat(SignalService.init.selector), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ); @@ -78,14 +100,12 @@ contract BridgeTest is TaikoTest { deployProxy({ name: "signal_service", impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); vm.deal(address(destChainBridge), 100 ether); - crossChainSync = new DummyCrossChainSync(); - untrustedSenderContract = new UntrustedSendMessageRelayer(); vm.deal(address(untrustedSenderContract), 10 ether); @@ -96,6 +116,9 @@ contract BridgeTest is TaikoTest { register(address(addressManager), "bridge", address(destChainBridge), destChainId); register(address(addressManager), "taiko", address(uint160(123)), destChainId); + + register(address(addressManager), "bridge_watchdog", address(uint160(123)), destChainId); + vm.stopPrank(); } @@ -105,17 +128,16 @@ contract BridgeTest is TaikoTest { from: address(bridge), srcChainId: uint64(block.chainid), destChainId: destChainId, - owner: Alice, + srcOwner: Alice, + destOwner: Alice, to: Alice, - refundTo: Alice, - value: 1000, + value: 10_000, fee: 1000, gasLimit: 1_000_000, - data: "", - memo: "" + data: "" }); // Mocking proof - but obviously it needs to be created in prod - // coresponding to the message + // corresponding to the message bytes memory proof = hex"00"; bytes32 msgHash = destChainBridge.hashMessage(message); @@ -124,19 +146,19 @@ contract BridgeTest is TaikoTest { vm.prank(Bob, Bob); destChainBridge.processMessage(message, proof); - Bridge.Status status = destChainBridge.messageStatus(msgHash); + IBridge.Status status = destChainBridge.messageStatus(msgHash); - assertEq(status == Bridge.Status.DONE, true); + assertEq(status == IBridge.Status.DONE, true); // Alice has 100 ether + 1000 wei balance, because we did not use the // 'sendMessage' // since we mocking the proof, so therefore the 1000 wei - // deduction/transfer did - // not happen - assertEq(Alice.balance, 100_000_000_000_000_001_000); - assertEq(Bob.balance, 1000); + // deduction/transfer did not happen + assertTrue(Alice.balance >= 100 ether + 10_000); + assertTrue(Alice.balance <= 100 ether + 10_000 + 1000); + assertTrue(Bob.balance >= 0 && Bob.balance <= 1000); } - function test_Bridge_send_ether_to_contract_with_value() public { + function test_Bridge_send_ether_to_contract_with_value_simple() public { goodReceiver = new GoodReceiver(); IBridge.Message memory message = IBridge.Message({ @@ -144,17 +166,16 @@ contract BridgeTest is TaikoTest { from: address(bridge), srcChainId: uint64(block.chainid), destChainId: destChainId, - owner: Alice, + srcOwner: Alice, + destOwner: Alice, to: address(goodReceiver), - refundTo: Alice, - value: 1000, + value: 10_000, fee: 1000, gasLimit: 1_000_000, - data: "", - memo: "" + data: "" }); // Mocking proof - but obviously it needs to be created in prod - // coresponding to the message + // corresponding to the message bytes memory proof = hex"00"; bytes32 msgHash = destChainBridge.hashMessage(message); @@ -164,13 +185,14 @@ contract BridgeTest is TaikoTest { vm.prank(Bob, Bob); destChainBridge.processMessage(message, proof); - Bridge.Status status = destChainBridge.messageStatus(msgHash); + IBridge.Status status = destChainBridge.messageStatus(msgHash); - assertEq(status == Bridge.Status.DONE, true); + assertEq(status == IBridge.Status.DONE, true); // Bob (relayer) and goodContract has 1000 wei balance - assertEq(address(goodReceiver).balance, 1000); - assertEq(Bob.balance, 1000); + assertEq(address(goodReceiver).balance, 10_000); + console2.log("Bob.balance:", Bob.balance); + assertTrue(Bob.balance >= 0 && Bob.balance <= 1000); } function test_Bridge_send_ether_to_contract_with_value_and_message_data() public { @@ -181,17 +203,16 @@ contract BridgeTest is TaikoTest { from: address(bridge), srcChainId: uint64(block.chainid), destChainId: destChainId, - owner: Alice, + srcOwner: Alice, + destOwner: Alice, to: address(goodReceiver), - refundTo: Alice, value: 1000, fee: 1000, gasLimit: 1_000_000, - data: abi.encodeWithSelector(GoodReceiver.forward.selector, Carol), - memo: "" + data: abi.encodeCall(GoodReceiver.onMessageInvocation, abi.encode(Carol)) }); // Mocking proof - but obviously it needs to be created in prod - // coresponding to the message + // corresponding to the message bytes memory proof = hex"00"; bytes32 msgHash = destChainBridge.hashMessage(message); @@ -201,9 +222,9 @@ contract BridgeTest is TaikoTest { vm.prank(Bob, Bob); destChainBridge.processMessage(message, proof); - Bridge.Status status = destChainBridge.messageStatus(msgHash); + IBridge.Status status = destChainBridge.messageStatus(msgHash); - assertEq(status == Bridge.Status.DONE, true); + assertEq(status == IBridge.Status.DONE, true); // Carol and goodContract has 500 wei balance assertEq(address(goodReceiver).balance, 500); @@ -216,8 +237,8 @@ contract BridgeTest is TaikoTest { owner: Alice, to: Alice, value: 0, - gasLimit: 0, - fee: 1, + gasLimit: 1_000_000, + fee: 1_000_000, destChain: destChainId }); @@ -236,7 +257,7 @@ contract BridgeTest is TaikoTest { destChain: destChainId }); - vm.expectRevert(Bridge.B_INVALID_USER.selector); + vm.expectRevert(EssentialContract.ZERO_ADDRESS.selector); bridge.sendMessage{ value: amount }(message); } @@ -289,12 +310,12 @@ contract BridgeTest is TaikoTest { function test_Bridge_send_message_ether_with_processing_fee() public { uint256 amount = 0 wei; - uint256 fee = 1 wei; + uint64 fee = 1_000_000 wei; IBridge.Message memory message = newMessage({ owner: Alice, to: Alice, value: 0, - gasLimit: 0, + gasLimit: 1_000_000, fee: fee, destChain: destChainId }); @@ -305,7 +326,7 @@ contract BridgeTest is TaikoTest { function test_Bridge_recall_message_ether() public { uint256 amount = 1 ether; - uint256 fee = 1 wei; + uint64 fee = 0 wei; IBridge.Message memory message = newMessage({ owner: Alice, to: Alice, @@ -336,7 +357,7 @@ contract BridgeTest is TaikoTest { // ERCXXTokenVault (message.from) but directly from the Bridge uint256 amount = 1 ether; - uint256 fee = 1 wei; + uint64 fee = 0 wei; IBridge.Message memory message = newMessage({ owner: Alice, to: Alice, @@ -348,7 +369,7 @@ contract BridgeTest is TaikoTest { uint256 starterBalanceVault = address(bridge).balance; - untrustedSenderContract.sendMessage(address(bridge), message, amount + fee); + (, message) = untrustedSenderContract.sendMessage(address(bridge), message, amount + fee); assertEq(address(bridge).balance, (starterBalanceVault + amount + fee)); @@ -359,12 +380,12 @@ contract BridgeTest is TaikoTest { function test_Bridge_send_message_ether_with_processing_fee_invalid_amount() public { uint256 amount = 0 wei; - uint256 fee = 1 wei; + uint64 fee = 1_000_000 wei; IBridge.Message memory message = newMessage({ owner: Alice, to: Alice, value: 0, - gasLimit: 0, + gasLimit: 1_000_000, fee: fee, destChain: destChainId }); @@ -377,9 +398,6 @@ contract BridgeTest is TaikoTest { // proofs via rpc // in foundry function test_Bridge_process_message() public { - /* DISCALIMER: From now on we do not need to have real - proofs because we can bypass with overriding skipProofCheck() - in a mockBirdge AND proof system already 'battle tested'.*/ // This predefined successful process message call fails now // since we modified the iBridge.Message struct and cut out // depositValue @@ -391,18 +409,15 @@ contract BridgeTest is TaikoTest { destChainBridge.processMessage(message, proof); - Bridge.Status status = destChainBridge.messageStatus(msgHash); + IBridge.Status status = destChainBridge.messageStatus(msgHash); - assertEq(status == Bridge.Status.DONE, true); + assertEq(status == IBridge.Status.DONE, true); } // test with a known good merkle proof / message since we cant generate // proofs via rpc // in foundry function test_Bridge_retry_message_and_end_up_in_failed_status() public { - /* DISCALIMER: From now on we do not need to have real - proofs because we can bypass with overriding skipProofCheck() - in a mockBirdge AND proof system already 'battle tested'.*/ vm.startPrank(Alice); (IBridge.Message memory message, bytes memory proof) = setUpPredefinedSuccessfulProcessMessageCall(); @@ -414,18 +429,100 @@ contract BridgeTest is TaikoTest { destChainBridge.processMessage(message, proof); - Bridge.Status status = destChainBridge.messageStatus(msgHash); + IBridge.Status status = destChainBridge.messageStatus(msgHash); - assertEq(status == Bridge.Status.RETRIABLE, true); + assertEq(status == IBridge.Status.RETRIABLE, true); vm.stopPrank(); - vm.prank(message.owner); + vm.prank(message.destOwner); + vm.expectRevert(Bridge.B_RETRY_FAILED.selector); + destChainBridge.retryMessage(message, false); + + vm.prank(message.destOwner); destChainBridge.retryMessage(message, true); + IBridge.Status postRetryStatus = destChainBridge.messageStatus(msgHash); + assertEq(postRetryStatus == IBridge.Status.FAILED, true); + } + + function test_Bridge_fail_message() public { + vm.startPrank(Alice); + (IBridge.Message memory message, bytes memory proof) = + setUpPredefinedSuccessfulProcessMessageCall(); - Bridge.Status postRetryStatus = destChainBridge.messageStatus(msgHash); + // etch bad receiver at the to address, so it fails. + vm.etch(message.to, address(badReceiver).code); + + bytes32 msgHash = destChainBridge.hashMessage(message); + + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + + assertEq(status == IBridge.Status.RETRIABLE, true); + + vm.stopPrank(); - assertEq(postRetryStatus == Bridge.Status.FAILED, true); + vm.prank(message.destOwner); + destChainBridge.failMessage(message); + IBridge.Status postRetryStatus = destChainBridge.messageStatus(msgHash); + assertEq(postRetryStatus == IBridge.Status.FAILED, true); + } + + function test_processMessage_InvokeMessageCall_DoS1() public { + nonmaliciousContract1 = new NonMaliciousContract1(); + + IBridge.Message memory message = IBridge.Message({ + id: 0, + from: address(this), + srcChainId: uint64(block.chainid), + destChainId: destChainId, + srcOwner: Alice, + destOwner: Alice, + to: address(nonmaliciousContract1), + value: 1000, + fee: 1000, + gasLimit: 1_000_000, + data: "" + }); + + bytes memory proof = hex"00"; + bytes32 msgHash = destChainBridge.hashMessage(message); + vm.chainId(destChainId); + vm.prank(Bob, Bob); + + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.DONE, true); // test pass check + } + + function test_processMessage_InvokeMessageCall_DoS2_testfail() public { + maliciousContract2 = new MaliciousContract2(); + + IBridge.Message memory message = IBridge.Message({ + id: 0, + from: address(this), + srcChainId: uint64(block.chainid), + destChainId: destChainId, + srcOwner: Alice, + destOwner: Alice, + to: address(maliciousContract2), + value: 1000, + fee: 1000, + gasLimit: 1_000_000, + data: "" + }); + + bytes memory proof = hex"00"; + bytes32 msgHash = destChainBridge.hashMessage(message); + vm.chainId(destChainId); + vm.prank(Bob, Bob); + + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.RETRIABLE, true); //Test fail check } function retry_message_reverts_when_status_non_retriable() public { @@ -438,7 +535,7 @@ contract BridgeTest is TaikoTest { destChain: destChainId }); - vm.expectRevert(Bridge.B_NON_RETRIABLE.selector); + vm.expectRevert(Bridge.B_INVALID_STATUS.selector); destChainBridge.retryMessage(message, true); } @@ -457,9 +554,6 @@ contract BridgeTest is TaikoTest { destChainBridge.retryMessage(message, true); } - /* DISCALIMER: From now on we do not need to have real - proofs because we can bypass with overriding skipProofCheck() - in a mockBirdge AND proof system already 'battle tested'.*/ function setUpPredefinedSuccessfulProcessMessageCall() internal returns (IBridge.Message memory, bytes memory) @@ -467,8 +561,6 @@ contract BridgeTest is TaikoTest { badReceiver = new BadReceiver(); uint64 dest = 1337; - addressManager.setAddress(dest, "taiko", address(crossChainSync)); - addressManager.setAddress(1336, "bridge", 0x564540a26Fb667306b3aBdCB4ead35BEb88698ab); addressManager.setAddress(dest, "bridge", address(destChainBridge)); @@ -477,11 +569,6 @@ contract BridgeTest is TaikoTest { addressManager.setAddress(dest, "signal_service", address(mockProofSignalService)); - crossChainSync.setSyncedData( - 0xd5f5d8ac6bc37139c97389b00e9cf53e89c153ad8a5fc765ffe9f44ea9f3d31e, - 0x631b214fb030d82847224f0b3d3b906a6764dded176ad3c7262630204867ba85 - ); - vm.deal(address(destChainBridge), 1 ether); vm.chainId(dest); @@ -492,14 +579,13 @@ contract BridgeTest is TaikoTest { from: 0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39, srcChainId: 1336, destChainId: dest, - owner: 0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39, + srcOwner: 0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39, + destOwner: 0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39, to: 0x200708D76eB1B69761c23821809d53F65049939e, - refundTo: 0x10020FCb72e27650651B05eD2CEcA493bC807Ba4, value: 1000, fee: 1000, gasLimit: 1_000_000, - data: "", - memo: "" + data: "" }); bytes memory proof = @@ -512,8 +598,8 @@ contract BridgeTest is TaikoTest { address owner, address to, uint256 value, - uint256 gasLimit, - uint256 fee, + uint32 gasLimit, + uint64 fee, uint64 destChain ) internal @@ -521,7 +607,8 @@ contract BridgeTest is TaikoTest { returns (IBridge.Message memory) { return IBridge.Message({ - owner: owner, + srcOwner: owner, + destOwner: owner, destChainId: destChain, to: to, value: value, @@ -529,10 +616,8 @@ contract BridgeTest is TaikoTest { id: 0, // placeholder, will be overwritten from: owner, // placeholder, will be overwritten srcChainId: uint64(block.chainid), // will be overwritten - refundTo: owner, gasLimit: gasLimit, - data: "", - memo: "" + data: "" }); } } diff --git a/packages/protocol/test/common/EssentialContract.t.sol b/packages/protocol/test/common/EssentialContract.t.sol index f46dce22367c..2012cffebd98 100644 --- a/packages/protocol/test/common/EssentialContract.t.sol +++ b/packages/protocol/test/common/EssentialContract.t.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; contract Target1 is EssentialContract { uint256 public count; - function init() external initializer { - __Essential_init(); + function init(address _owner) external initializer { + __Essential_init(_owner); count = 100; } @@ -26,9 +26,9 @@ contract Target2 is Target1 { } } -contract TestOwnerUUPSUpgradable is TaikoTest { +contract TestEssentialContract is TaikoTest { function test_essential_behind_1967_proxy() external { - bytes memory data = bytes.concat(Target1.init.selector); + bytes memory data = abi.encodeCall(Target1.init, (address(0))); vm.startPrank(Alice); ERC1967Proxy proxy = new ERC1967Proxy(address(new Target1()), data); Target1 target = Target1(address(proxy)); @@ -49,7 +49,7 @@ contract TestOwnerUUPSUpgradable is TaikoTest { target.adjust(); address v2 = address(new Target2()); - data = bytes.concat(Target2.update.selector); + data = abi.encodeCall(Target2.update, ()); vm.prank(Bob); vm.expectRevert(); diff --git a/packages/protocol/test/signal/SignalService.t.sol b/packages/protocol/test/signal/SignalService.t.sol index 40c62f1b52ca..25726864d182 100644 --- a/packages/protocol/test/signal/SignalService.t.sol +++ b/packages/protocol/test/signal/SignalService.t.sol @@ -1,14 +1,34 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; +import "forge-std/console2.sol"; + +contract MockSignalService is SignalService { + function _verifyHopProof( + uint64, /*chainId*/ + address, /*app*/ + bytes32, /*signal*/ + bytes32, /*value*/ + HopProof memory, /*hop*/ + address /*relay*/ + ) + internal + pure + override + returns (bytes32) + { + // Skip verifying the merkle proof entirely + return bytes32(uint256(789)); + } +} contract TestSignalService is TaikoTest { AddressManager addressManager; - SignalService signalService; - SignalService destSignalService; - DummyCrossChainSync crossChainSync; + MockSignalService signalService; + SignalService realSignalService; uint64 public destChainId = 7; + address taiko; function setUp() public { vm.startPrank(Alice); @@ -19,55 +39,66 @@ contract TestSignalService is TaikoTest { deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(AddressManager.init, (address(0))), + registerTo: address(addressManager) }) ); - signalService = SignalService( + signalService = MockSignalService( deployProxy({ name: "signal_service", - impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + impl: address(new MockSignalService()), + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); - destSignalService = SignalService( + realSignalService = SignalService( deployProxy({ name: "signal_service", impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (Alice, address(addressManager))) }) ); - crossChainSync = DummyCrossChainSync( - deployProxy({ - name: "dummy_cross_chain_sync", - impl: address(new DummyCrossChainSync()), - data: "" - }) - ); + taiko = randAddress(); + signalService.authorize(taiko, true); + realSignalService.authorize(Alice, true); + vm.stopPrank(); + } - register(address(addressManager), "signal_service", address(destSignalService), destChainId); + function test_real_signal() public { + vm.chainId(167_001); - register(address(addressManager), "taiko", address(crossChainSync), destChainId); + bytes memory proof = + hex"0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000028c5900000000000000000000000000000000000000000000000000000000000015c27a889e6436fc1cde7827f75217adf5371afb14cc56860e6d9032ba5e28214819000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000005000000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000003800000000000000000000000000000000000000000000000000000000000000214f90211a0dfa513509c34b62c66488e60634c6fd42fe00397e4e1d15d5e70f227ded60befa05bdb02a2eca8f311b9671483176ec61fae7edcaf62b04a28c44cc2192e9d0f46a01cc45e358bfc5242aaf32f63dee58b9aa3f4113be91b2b50bb5740beda4fde25a0d92467760a1a9357492b426c682426d81af5cb713839647e49b13e01b02d6440a0c84062f286e3581246bccf7d0b7f82366e880161f91879ebef0180c9c93c941aa0db20b735c7f1053aceb0828f861d0b7e33febd12a123bc0352c929718b845faaa0247a3b41495f2b92708771026d7099e824051e275b8a29e82876030437b67c0aa0477ffe5998d9bc8b5866d020bb1d184b62cd6ab90475bc5cf9ec0a55c417c28ba074ecd264e5eb045d4d93d6670df1364257028d7b7fdda9c4eb627d7cd744780ba02221fdd890129df643dc88a6767c7d586fac3fd573ec069b15232f7b26a7ce28a06ea5ac730ebf7a40efe4cf416fac7ad0bdd3afcfb58d6df8c5915050822a3359a03ec8023d3660e15a8ba0ab69a1ed8ae5f121d3221587d14b1ee11b92fef2f92ca03ed73d6c820ff05ed2b08021ffa9a9cf754e376e26f80ba5fe8ba2906d398f8fa0e8e7f865b0d0ece00599f085d5b3e1ba7ca723b8918242001fe1939e1c5e4636a023f41a3b7932420372da53ae441f422ca8c5da9706a79ff47a81c5c8c1fb4917a003a143ebcd7f0dc5385606c701eb2b8245300e1ea72dd478ebf3dd894b173b598000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3f8b180a0887fcd34179304e7cd2b4e20e2b02a4a8f86479c938ef9f250afa70389b005f9808080a044716b385f8d9459d36317d101408b5ac6918cf2ca6fec073f6bc6a24a3a04e4a024c54ee716f3386163e13b31e8987471a988be429e3aef879217e22b7760561ca00140f6012398f45a3130df7f78f20f7c40bf0d1074b88d3cf981bf0afae32e2580808080a03b0365c6777cd412b8762e2e8216a2fab245f1c83111e08f09c20ae4ed8628e88080808000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006bf869a02016202fe7055f067ad86805f2e5a7f909257e295658bcbfc2b2cb8c3127fb9db846f8440180a014f07e11fa9eac150c017a5fea958a3b73935da8b057843d3314bc53acbd00e5a0dc679fd48cf611aa38e906adc93928c5f8f6fae534978ea2fa4f5935f5ed1b2c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000002e0000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000000000000000000000000000000000000000007600000000000000000000000000000000000000000000000000000000000000214f90211a09e1b6a91ab7ab54dc82ca4bf7c621663b37d09fd0adfc6ee5b04d61d1674be28a0be9221596e9d2200855bf3eed5237bf998f95a94d46f9045c3d15138262aa336a08a468dcca4b10bc41cbfbe1cff581839da9efb3a6453a1a9d51c623393056b75a0eb149f5f12c6e13cf0e0bdff4a87870c1d2a15f512d8610d938ff91420e567bca0ae4668eccb3ec464e47550870fb978fe7840ea30cc94e9b4983ea213de536caca0fd30849f4ce21a0bf9e93e7ff6681ba9f8c4c23d9c2b32aaabbba735cb3ad8bca03a61ccbbd269b701d9da7568421d47355b9b07f88cb1a8b559bcbbda60cdc588a04ccb3f9257808d764c277e5825d005516ac955f64e59d0e7ab2f94b1a4fc4c17a0697d43c2b13982e7971e4bd05cca3a3714163333c764d5383d1f5e642f6b9038a0fad2df5c5417b57cf90bac46a87f3dbb68a02fc415d382b7880ca203998e5848a0456c9736422257556e259b1ec6ef1f57603db9140a81a0537d3efa9392fa1396a0d75f6fb980e2a441be4e5da97f59b411caef24b0ddbbdf69eb51e8294c7721d0a030358f3b1834ef739810ca31f4fabd79f43dc1af8f8ece3b66cbdfec1e2f91d7a0191280d4afd9c5d9e493b78a155abbb8e5bb61754672c3deeb3002c337c7376ea0deb7ba981af9635c6b0df1de8dd515128592e2fb80bb760279f8492d8d4caa8ba070096993175dea6432f4243ae88ccdfacd67453e4d018bfe2f43dee3b5d831ad800000000000000000000000000000000000000000000000000000000000000000000000000000000000000214f90211a0994000eab355e9641a1b22339676cca81f018f367ec829b07ff569bd3f418f43a09a373f01670a460ef4d7f4e2893d5fd9411b696b4a943f18e988912e8bab8349a087849ade71c21e99bd2cff5dbb222ef04fd42d381d488936de43b7ef4380b4d3a072278a26bebb48de2be3ed56fda4bb05cfc430384f76a46402880b16fc56b823a02051ad643d886b6aebaca99b95efbdfb299ff1dbd8f7cba92431a6c83da68381a0fc9d08a35f7850ef9e6932f0b2ccca5e77dee26da198e942935436e7a04f9ba2a01cd8eb71fa95b520e37763506000751e573cdb4d6b7f22809fc7725569bf5362a05097491bc4a3dc25f339fa3be312044e0fb85445bd8bbfba76de7cc8de278db9a0057aca47ca23862aa56aa61e58822f9010782645309b24d7ed41fc564342bf97a0c0bc073a3474c0bd105c3db6a915598559501abe06e9ec9cf9f7345c440ade8ca054be9f6d8226aba0c1b4c530000991fd8476e0c99faecc3f3703751d4929d994a01d192e93484bca5e70661e1655cdc58994e467e58dc6fa6349e3339d9263f8e3a0de752f43804851bc139350b4d09d8eeae52d3793e5a77d966bdcc543e8ed4a07a08d17a09e17828697ba29022f714d57588b55942120b44adb3e7f4311b11a60dca05989405f26fd35e72e532c6528228e90fc3a010eb0d87feca60557fa18b45896a0dcf2645898dead212b4330054c56e51f10af5d3f3bfc64a8f73cdf1bd6617e0d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000194f90191a00415edc1050fe50ad35dbcf7960f64d127b236a1c0d7653a21d75f3cacb529c980a04c18411340e7a2669fd694dee4413c834cf59b13b54107b832503769f2c942b2a0c0910bd1e2e47c03541323952c9da9da3e5807c306b2ca0b4af696508b38f82a80a034e02eebc9d6a274c89c7789fd03eb12333e65346d9453cca75e4a02d7b2992ea0ff68d74f45306203073844aee925a4cc4ae4d78cc032f54504ee9c9335cd32dd8080a09c1849766089cd1349cef938726d7a9e7dc9598fb261d136d9a58e0b7caf275ba0f19ae8192e9b0aadf26d08fbe4f32d0f8949afc2e172c90c834685302ea69bbca0e10bf29e7ae1512acb04c2c1d63c124c4d02e191374f93fefce052420ce55e15a082010d47803c23ef3a2be7b3b089572c2a59de5a9173e9f9404bb71d367e0adaa0a29475985f39e34dec60f13ac4ca2370041873aa30f823b0d0d301998136804ba0d7acb7203bbbd9c87cd646416284ef32bbae269bf178e56127f4a69d6f7a6e43a0224da002242f29898a82f586a50143afa77334954d4581e61266a245c13254c7800000000000000000000000000000000000000000000000000000000000000000000000000000000000000053f851808080a024635e394cfa76468c00494a6e4fc0a50dd27d737c9e41861d32a8be31e3a38d808080808080a06f7c75a0076a5802c84d3d370baefd6b6655641f648e087b60a86c402b07ba84808080808080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000044f8429f20b4780cdd5e2149e06b1a3cd443645775c177c33344f9f36e535023c39e1fa1a02299879cc2fe4c05d8b15238cbf4b15f35a7d434084d279d42f462c90f02b54400000000000000000000000000000000000000000000000000000000"; + bytes32 msgHash = hex"2299879cc2fe4c05d8b15238cbf4b15f35a7d434084d279d42f462c90f02b544"; - vm.stopPrank(); + address srcSignalService = 0x604C61d6618AaCdF7a7A2Fe4c42E35Ecba32AE75; + address srcBridge = 0xde5B0e8a8034eF30a8b71d78e658C85dFE3FC657; + + vm.prank(Alice); + addressManager.setAddress(32_382, "signal_service", srcSignalService); + + bytes32 stateRoot = hex"7a889e6436fc1cde7827f75217adf5371afb14cc56860e6d9032ba5e28214819"; + uint64 blockId = 5570; + vm.prank(Alice); + realSignalService.syncChainData(32_382, LibStrings.H_STATE_ROOT, blockId, stateRoot); + + realSignalService.proveSignalReceived(32_382, srcBridge, msgHash, proof); } function test_SignalService_sendSignal_revert() public { - vm.expectRevert(SignalService.SS_INVALID_SIGNAL.selector); + vm.expectRevert(EssentialContract.ZERO_VALUE.selector); signalService.sendSignal(0); } function test_SignalService_isSignalSent_revert() public { bytes32 signal = bytes32(uint256(1)); - vm.expectRevert(SignalService.SS_INVALID_APP.selector); + vm.expectRevert(EssentialContract.ZERO_ADDRESS.selector); signalService.isSignalSent(address(0), signal); signal = bytes32(uint256(0)); - vm.expectRevert(SignalService.SS_INVALID_SIGNAL.selector); + vm.expectRevert(EssentialContract.ZERO_VALUE.selector); signalService.isSignalSent(Alice, signal); } @@ -79,101 +110,540 @@ contract TestSignalService is TaikoTest { assertTrue(signalService.isSignalSent(Alice, signal)); } - function test_SignalService_getSignalSlot() public { - vm.startPrank(Alice); - for (uint8 i = 1; i < 100; ++i) { - bytes32 signal = bytes32(block.prevrandao + i); - signalService.sendSignal(signal); + function test_SignalService_proveSignalReceived_revert_invalid_chainid_or_signal() public { + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); - assertTrue(signalService.isSignalSent(Alice, signal)); - } + // app being address(0) will revert + vm.expectRevert(EssentialContract.ZERO_ADDRESS.selector); + signalService.proveSignalReceived({ + _chainId: 1, + _app: address(0), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // signal being 0 will revert + vm.expectRevert(EssentialContract.ZERO_VALUE.selector); + signalService.proveSignalReceived({ + _chainId: uint64(block.chainid), + _app: randAddress(), + _signal: 0, + _proof: abi.encode(proofs) + }); } - function test_SignalService_proveSignalReceived_L1_L2() public { - uint64 chainId = 11_155_111; // Created the proofs on a deployed Sepolia - // contract, this is why this chainId. - address app = 0x927a146e18294efb36edCacC99D9aCEA6aB16b95; // Mock app, - // actually it is an EOA, but it is ok for tests! - bytes32 signal = 0x21761f7cd1af3972774272b39a0f4602dbcd418325cddb14e156b4bb073d52a8; - bytes memory inclusionProof = - hex"e5a4e3a1209749684f52b5c0717a7ca78127fb56043d637d81763c04e9d30ba4d4746d56e901"; //eth_getProof's - // result RLP encoded storage proof - bytes32 signalRoot = 0xf7916f389ccda56e3831e115238b7389b30750886785a3c21265601572698f0f; //eth_getProof - // result's storage hash + function test_SignalService_proveSignalReceived_revert_malformat_proof() public { + // "undecodable proof" is not decodeable into SignalService.HopProof[] memory + vm.expectRevert(); + signalService.proveSignalReceived({ + _chainId: 0, + _app: randAddress(), + _signal: randBytes32(), + _proof: "undecodable proof" + }); + } + + function test_SignalService_proveSignalReceived_revert_src_signal_service_not_registered() + public + { + uint64 srcChainId = uint64(block.chainid - 1); + + // Did not call the following, so revert with RESOLVER_ZERO_ADDR + // vm.prank(Alice); + // addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); + + vm.expectRevert( + abi.encodeWithSelector( + AddressResolver.RESOLVER_ZERO_ADDR.selector, + srcChainId, + strToBytes32("signal_service") + ) + ); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_revert_zero_size_proof() public { + uint64 srcChainId = uint64(block.chainid - 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + // proofs.length must > 0 in order not to revert + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](0); + + vm.expectRevert(SignalService.SS_EMPTY_PROOF.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_revert_last_hop_incorrect_chainid() public { + uint64 srcChainId = uint64(block.chainid - 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); + + // proofs[0].chainId must be block.chainid in order not to revert + proofs[0].chainId = uint64(block.chainid + 1); + proofs[0].blockId = 1; + + vm.expectRevert(SignalService.SS_INVALID_LAST_HOP_CHAINID.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_revert_mid_hop_incorrect_chainid() public { + uint64 srcChainId = uint64(block.chainid - 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](2); + + // proofs[0].chainId must NOT be block.chainid in order not to revert + proofs[0].chainId = uint64(block.chainid); + proofs[0].blockId = 1; + + vm.expectRevert(SignalService.SS_INVALID_MID_HOP_CHAINID.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_revert_mid_hop_not_registered() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](2); + + // proofs[0].chainId must NOT be block.chainid in order not to revert + proofs[0].chainId = srcChainId + 1; + proofs[0].blockId = 1; + + vm.expectRevert( + abi.encodeWithSelector( + AddressResolver.RESOLVER_ZERO_ADDR.selector, + proofs[0].chainId, + strToBytes32("signal_service") + ) + ); + + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_local_chaindata_not_found() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); + + proofs[0].chainId = uint64(block.chainid); + proofs[0].blockId = 1; + + // the proof is a storage proof + proofs[0].accountProof = new bytes[](0); + proofs[0].storageProof = new bytes[](10); + + vm.expectRevert(SignalService.SS_SIGNAL_NOT_FOUND.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // the proof is a full proof + proofs[0].accountProof = new bytes[](1); + + vm.expectRevert(SignalService.SS_SIGNAL_NOT_FOUND.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_one_hop_cache_signal_root() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); + + proofs[0].chainId = uint64(block.chainid); + proofs[0].blockId = 1; + proofs[0].rootHash = randBytes32(); + + // the proof is a storage proof + proofs[0].accountProof = new bytes[](0); + proofs[0].storageProof = new bytes[](10); + + vm.expectRevert(SignalService.SS_SIGNAL_NOT_FOUND.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // relay the signal root + vm.prank(taiko); + signalService.syncChainData( + srcChainId, LibStrings.H_SIGNAL_ROOT, proofs[0].blockId, proofs[0].rootHash + ); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + vm.prank(Alice); + signalService.authorize(taiko, false); + + vm.expectRevert(SignalService.SS_UNAUTHORIZED.selector); + vm.prank(taiko); + signalService.syncChainData( + srcChainId, LibStrings.H_SIGNAL_ROOT, proofs[0].blockId, proofs[0].rootHash + ); + } + + function test_SignalService_proveSignalReceived_one_hop_state_root() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](1); + + proofs[0].chainId = uint64(block.chainid); + proofs[0].blockId = 1; + proofs[0].rootHash = randBytes32(); + + // the proof is a full merkle proof + proofs[0].accountProof = new bytes[](1); + proofs[0].storageProof = new bytes[](10); + + vm.expectRevert(SignalService.SS_SIGNAL_NOT_FOUND.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // relay the state root + vm.prank(taiko); + signalService.syncChainData( + srcChainId, LibStrings.H_STATE_ROOT, proofs[0].blockId, proofs[0].rootHash + ); + + // Should not revert + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + assertEq( + signalService.isChainDataSynced( + srcChainId, LibStrings.H_SIGNAL_ROOT, proofs[0].blockId, bytes32(uint256(789)) + ), + false + ); + } + + function test_SignalService_proveSignalReceived_multiple_hops_no_caching() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](3); + + // first hop with full merkle proof + proofs[0].chainId = uint64(block.chainid + 2); + proofs[0].blockId = 1; + proofs[0].rootHash = randBytes32(); + proofs[0].accountProof = new bytes[](1); + proofs[0].storageProof = new bytes[](10); + + // second hop with storage merkle proof + proofs[1].chainId = uint64(block.chainid + 3); + proofs[1].blockId = 2; + proofs[1].rootHash = randBytes32(); + proofs[1].accountProof = new bytes[](0); + proofs[1].storageProof = new bytes[](10); + + // third/last hop with full merkle proof + proofs[2].chainId = uint64(block.chainid); + proofs[2].blockId = 3; + proofs[2].rootHash = randBytes32(); + proofs[2].accountProof = new bytes[](1); + proofs[2].storageProof = new bytes[](10); + + // expect RESOLVER_ZERO_ADDR + vm.expectRevert( + abi.encodeWithSelector( + AddressResolver.RESOLVER_ZERO_ADDR.selector, + proofs[0].chainId, + strToBytes32("signal_service") + ) + ); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // Add two trusted hop relayers vm.startPrank(Alice); - signalService.authorize(address(crossChainSync), bytes32(uint256(block.chainid))); - - crossChainSync.setSyncedData("", signalRoot); - - SignalService.Proof memory p; - SignalService.Hop[] memory h; - p.crossChainSync = address(crossChainSync); - p.height = 10; - p.storageProof = inclusionProof; - p.hops = h; - - bool isSignalReceived = - signalService.proveSignalReceived(chainId, app, signal, abi.encode(p)); - assertEq(isSignalReceived, true); - } - - function test_SignalService_proveSignalReceived_L2_L2() public { - uint64 chainId = 11_155_111; // Created the proofs on a deployed - // Sepolia contract, this is why this chainId. This works as a - // static 'chainId' becuase i imitated 2 contracts (L2A and L1 - // Signal Service contracts) on Sepolia. - address app = 0x927a146e18294efb36edCacC99D9aCEA6aB16b95; // Mock app, - // actually it is an EOA, but it is ok for tests! Same applies here, - // i imitated everything with one 'app' (Bridge) with my same EOA - // wallet. - bytes32 signal_of_L2A_msgHash = - 0x21761f7cd1af3972774272b39a0f4602dbcd418325cddb14e156b4bb073d52a8; - bytes memory inclusionProof_of_L2A_msgHash = - hex"e5a4e3a1209749684f52b5c0717a7ca78127fb56043d637d81763c04e9d30ba4d4746d56e901"; //eth_getProof's - // result RLP encoded storage proof - bytes32 signalRoot_of_L2 = - 0xf7916f389ccda56e3831e115238b7389b30750886785a3c21265601572698f0f; //eth_getProof - // result's storage hash - bytes memory hop_inclusionProof_from_L1_SignalService = - hex"e5a4e3a120bade38703a7b19341b10a4dd482698dc8ffdd861e83ce41de2980bed39b6a02501"; //eth_getProof's - // result RLP encoded storage proof - bytes32 l1_common_signalService_root = - 0x5c5fd43df8bcd7ad44cfcae86ed73a11e0baa9a751f0b520d029358ea284833b; //eth_getProof - // result's storage hash - - // Important to note, we need to have authorized the "relayers' - // addresses" on the source chain we are claiming. - // (TaikoL1 or TaikoL2 depending on where we are) + addressManager.setAddress(proofs[0].chainId, "signal_service", randAddress() /*relay1*/ ); + addressManager.setAddress(proofs[1].chainId, "signal_service", randAddress() /*relay2*/ ); + vm.stopPrank(); + + vm.expectRevert(SignalService.SS_SIGNAL_NOT_FOUND.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + vm.prank(taiko); + signalService.syncChainData( + proofs[1].chainId, LibStrings.H_STATE_ROOT, proofs[2].blockId, proofs[2].rootHash + ); + + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_revert_with_a_loop() public { + uint64 srcChainId = uint64(block.chainid + 1); + + vm.prank(Alice); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](3); + + // first hop with full merkle proof + proofs[0].chainId = uint64(block.chainid + 2); + proofs[0].blockId = 1; + proofs[0].rootHash = randBytes32(); + proofs[0].accountProof = new bytes[](1); + proofs[0].storageProof = new bytes[](10); + + // second hop with storage merkle proof + proofs[1].chainId = proofs[0].chainId; // same + proofs[1].blockId = 2; + proofs[1].rootHash = randBytes32(); + proofs[1].accountProof = new bytes[](0); + proofs[1].storageProof = new bytes[](10); + + // third/last hop with full merkle proof + proofs[2].chainId = uint64(block.chainid); + proofs[2].blockId = 3; + proofs[2].rootHash = randBytes32(); + proofs[2].accountProof = new bytes[](1); + proofs[2].storageProof = new bytes[](10); + + // Add two trusted hop relayers vm.startPrank(Alice); - signalService.authorize(address(crossChainSync), bytes32(block.chainid)); - signalService.authorize(address(app), bytes32(uint256(chainId))); + addressManager.setAddress(proofs[0].chainId, "signal_service", randAddress() /*relay1*/ ); + addressManager.setAddress(proofs[1].chainId, "signal_service", randAddress() /*relay2*/ ); + vm.stopPrank(); + + vm.prank(taiko); + signalService.syncChainData( + proofs[1].chainId, LibStrings.H_STATE_ROOT, proofs[2].blockId, proofs[2].rootHash + ); + + vm.expectRevert(SignalService.SS_INVALID_HOPS_WITH_LOOP.selector); + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + } + + function test_SignalService_proveSignalReceived_multiple_hops_caching() public { + uint64 srcChainId = uint64(block.chainid + 1); + uint64 nextChainId = srcChainId + 100; + + SignalService.HopProof[] memory proofs = new SignalService.HopProof[](9); + + // hop 1: full merkle proof, CACHE_NOTHING + proofs[0].chainId = nextChainId++; + proofs[0].blockId = 1; + proofs[0].rootHash = randBytes32(); + proofs[0].accountProof = new bytes[](1); + proofs[0].storageProof = new bytes[](10); + proofs[0].cacheOption = ISignalService.CacheOption.CACHE_NOTHING; + + // hop 2: full merkle proof, CACHE_STATE_ROOT + proofs[1].chainId = nextChainId++; + proofs[1].blockId = 2; + proofs[1].rootHash = randBytes32(); + proofs[1].accountProof = new bytes[](1); + proofs[1].storageProof = new bytes[](10); + proofs[1].cacheOption = ISignalService.CacheOption.CACHE_STATE_ROOT; + + // hop 3: full merkle proof, CACHE_SIGNAL_ROOT + proofs[2].chainId = nextChainId++; + proofs[2].blockId = 3; + proofs[2].rootHash = randBytes32(); + proofs[2].accountProof = new bytes[](1); + proofs[2].storageProof = new bytes[](10); + proofs[2].cacheOption = ISignalService.CacheOption.CACHE_SIGNAL_ROOT; + + // hop 4: full merkle proof, CACHE_BOTH + proofs[3].chainId = nextChainId++; + proofs[3].blockId = 4; + proofs[3].rootHash = randBytes32(); + proofs[3].accountProof = new bytes[](1); + proofs[3].storageProof = new bytes[](10); + proofs[3].cacheOption = ISignalService.CacheOption.CACHE_BOTH; + // hop 5: storage merkle proof, CACHE_NOTHING + proofs[4].chainId = nextChainId++; + proofs[4].blockId = 5; + proofs[4].rootHash = randBytes32(); + proofs[4].accountProof = new bytes[](0); + proofs[4].storageProof = new bytes[](10); + proofs[4].cacheOption = ISignalService.CacheOption.CACHE_NOTHING; + + // hop 6: storage merkle proof, CACHE_STATE_ROOT + proofs[5].chainId = nextChainId++; + proofs[5].blockId = 6; + proofs[5].rootHash = randBytes32(); + proofs[5].accountProof = new bytes[](0); + proofs[5].storageProof = new bytes[](10); + proofs[5].cacheOption = ISignalService.CacheOption.CACHE_STATE_ROOT; + + // hop 7: storage merkle proof, CACHE_SIGNAL_ROOT + proofs[6].chainId = nextChainId++; + proofs[6].blockId = 7; + proofs[6].rootHash = randBytes32(); + proofs[6].accountProof = new bytes[](0); + proofs[6].storageProof = new bytes[](10); + proofs[6].cacheOption = ISignalService.CacheOption.CACHE_SIGNAL_ROOT; + + // hop 8: storage merkle proof, CACHE_BOTH + proofs[7].chainId = nextChainId++; + proofs[7].blockId = 8; + proofs[7].rootHash = randBytes32(); + proofs[7].accountProof = new bytes[](0); + proofs[7].storageProof = new bytes[](10); + proofs[7].cacheOption = ISignalService.CacheOption.CACHE_BOTH; + + // last hop, 9: full merkle proof, CACHE_BOTH + proofs[8].chainId = uint64(block.chainid); + proofs[8].blockId = 9; + proofs[8].rootHash = randBytes32(); + proofs[8].accountProof = new bytes[](1); + proofs[8].storageProof = new bytes[](10); + proofs[8].cacheOption = ISignalService.CacheOption.CACHE_BOTH; + + // Add two trusted hop relayers vm.startPrank(Alice); - addressManager.setAddress(chainId, "taiko", app); - - crossChainSync.setSyncedData("", l1_common_signalService_root); - - SignalService.Proof memory p; - p.crossChainSync = address(crossChainSync); - p.height = 10; - p.storageProof = inclusionProof_of_L2A_msgHash; - - // Imagine this scenario: L2A to L2B bridging. - // The 'hop' proof is the one that proves to L2B, that L1 Signal service - // contains the signalRoot (as storage slot / leaf) with value 0x1. - // The 'normal' proof is the one which proves that the resolving - // hop.signalRoot is the one which belongs to L2A, and the proof is - // accordingly. - SignalService.Hop[] memory h = new SignalService.Hop[](1); - h[0].signalRootRelay = app; - h[0].signalRoot = signalRoot_of_L2; - h[0].storageProof = hop_inclusionProof_from_L1_SignalService; - - p.hops = h; - - bool isSignalReceived = - signalService.proveSignalReceived(chainId, app, signal_of_L2A_msgHash, abi.encode(p)); - assertEq(isSignalReceived, true); + addressManager.setAddress(srcChainId, "signal_service", randAddress()); + for (uint256 i; i < proofs.length; ++i) { + addressManager.setAddress( + proofs[i].chainId, "signal_service", randAddress() /*relay1*/ + ); + } + vm.stopPrank(); + + vm.prank(taiko); + signalService.syncChainData( + proofs[7].chainId, LibStrings.H_STATE_ROOT, proofs[8].blockId, proofs[8].rootHash + ); + + signalService.proveSignalReceived({ + _chainId: srcChainId, + _app: randAddress(), + _signal: randBytes32(), + _proof: abi.encode(proofs) + }); + + // hop 1: full merkle proof, CACHE_NOTHING + _verifyCache(srcChainId, proofs[0].blockId, proofs[0].rootHash, false, false); + // hop 2: full merkle proof, CACHE_STATE_ROOT + _verifyCache(proofs[0].chainId, proofs[1].blockId, proofs[1].rootHash, true, false); + // hop 3: full merkle proof, CACHE_SIGNAL_ROOT + _verifyCache(proofs[1].chainId, proofs[2].blockId, proofs[2].rootHash, false, true); + // hop 4: full merkle proof, CACHE_BOTH + _verifyCache(proofs[2].chainId, proofs[3].blockId, proofs[3].rootHash, true, true); + // hop 5: storage merkle proof, CACHE_NOTHING + _verifyCache(proofs[3].chainId, proofs[4].blockId, proofs[4].rootHash, false, false); + // hop 6: storage merkle proof, CACHE_STATE_ROOT + _verifyCache(proofs[4].chainId, proofs[5].blockId, proofs[5].rootHash, false, false); + // hop 7: storage merkle proof, CACHE_SIGNAL_ROOT + _verifyCache(proofs[5].chainId, proofs[6].blockId, proofs[6].rootHash, false, true); + // hop 8: storage merkle proof, CACHE_BOTH + _verifyCache(proofs[6].chainId, proofs[7].blockId, proofs[7].rootHash, false, true); + // last hop, 9: full merkle proof, CACHE_BOTH + // last hop's state root is already cached even before the proveSignalReceived call. + _verifyCache(proofs[7].chainId, proofs[8].blockId, proofs[8].rootHash, true, true); + } + + function _verifyCache( + uint64 chainId, + uint64 blockId, + bytes32 stateRoot, + bool stateRootCached, + bool signalRootCached + ) + private + { + assertEq( + signalService.isChainDataSynced(chainId, LibStrings.H_STATE_ROOT, blockId, stateRoot), + stateRootCached + ); + + assertEq( + signalService.isChainDataSynced( + chainId, LibStrings.H_SIGNAL_ROOT, blockId, bytes32(uint256(789)) + ), + signalRootCached + ); } } diff --git a/packages/protocol/test/team/TimelockTokenPool.t.sol b/packages/protocol/test/team/TimelockTokenPool.t.sol deleted file mode 100644 index 1ba0300ff5f4..000000000000 --- a/packages/protocol/test/team/TimelockTokenPool.t.sol +++ /dev/null @@ -1,418 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "../TaikoTest.sol"; - -contract MyERC20 is ERC20 { - constructor(address owner) ERC20("Taiko Token", "TKO") { - _mint(owner, 1_000_000_000e18); - } -} - -contract TestTimelockTokenPool is TaikoTest { - address internal Vault = randAddress(); - - ERC20 tko = new MyERC20(Vault); - TimelockTokenPool pool; - - function setUp() public { - pool = TimelockTokenPool( - deployProxy({ - name: "time_lock_token_pool", - impl: address(new TimelockTokenPool()), - data: bytes.concat(TimelockTokenPool.init.selector, abi.encode(address(tko), Vault)) - }) - ); - } - - function test_invalid_granting() public { - vm.expectRevert(TimelockTokenPool.INVALID_GRANT.selector); - pool.grant(Alice, TimelockTokenPool.Grant(0, 0, 0, 0, 0, 0, 0)); - - vm.expectRevert(TimelockTokenPool.INVALID_PARAM.selector); - pool.grant(address(0), TimelockTokenPool.Grant(100e18, 0, 0, 0, 0, 0, 0)); - } - - function test_single_grant_zero_grant_period_zero_unlock_period() public { - pool.grant(Alice, TimelockTokenPool.Grant(10_000e18, 0, 0, 0, 0, 0, 0)); - vm.prank(Vault); - tko.approve(address(pool), 10_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 10_000e18); - - // Try to void the grant - vm.expectRevert(TimelockTokenPool.NOTHING_TO_VOID.selector); - pool.void(Alice); - - vm.prank(Alice); - pool.withdraw(); - assertEq(tko.balanceOf(Alice), 10_000e18); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 10_000e18); - assertEq(amountWithdrawable, 0); - } - - function test_single_grant_zero_grant_period_1year_unlock_period() public { - uint64 unlockStart = uint64(block.timestamp); - uint32 unlockPeriod = 365 days; - uint64 unlockCliff = unlockStart + unlockPeriod / 2; - - pool.grant( - Alice, - TimelockTokenPool.Grant(10_000e18, 0, 0, 0, unlockStart, unlockCliff, unlockPeriod) - ); - vm.prank(Vault); - tko.approve(address(pool), 10_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - vm.warp(unlockCliff); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - vm.warp(unlockCliff + 1); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - uint256 amount1 = 5_000_000_317_097_919_837_645; - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, amount1); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, amount1); - - vm.prank(Alice); - pool.withdraw(); - - vm.warp(unlockStart + unlockPeriod + 365 days); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, amount1); - assertEq(amountWithdrawable, 10_000e18 - amount1); - - vm.prank(Alice); - pool.withdraw(); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 10_000e18); - assertEq(amountWithdrawable, 0); - } - - function test_single_grant_1year_grant_period_zero_unlock_period() public { - uint64 grantStart = uint64(block.timestamp); - uint32 grantPeriod = 365 days; - uint64 grantCliff = grantStart + grantPeriod / 2; - - pool.grant( - Alice, TimelockTokenPool.Grant(10_000e18, grantStart, grantCliff, grantPeriod, 0, 0, 0) - ); - vm.prank(Vault); - tko.approve(address(pool), 10_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - vm.warp(grantCliff); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - vm.warp(grantCliff + 1); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - uint256 amount1 = 5_000_000_317_097_919_837_645; - assertEq(amountOwned, amount1); - assertEq(amountUnlocked, amount1); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, amount1); - - vm.prank(Alice); - pool.withdraw(); - - vm.warp(grantStart + grantPeriod + 365 days); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, amount1); - assertEq(amountWithdrawable, 10_000e18 - amount1); - - vm.prank(Alice); - pool.withdraw(); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 10_000e18); - assertEq(amountWithdrawable, 0); - } - - function test_single_grant_4year_grant_period_4year_unlock_period() public { - uint64 grantStart = uint64(block.timestamp); - uint32 grantPeriod = 4 * 365 days; - uint64 grantCliff = grantStart + 90 days; - - uint64 unlockStart = grantStart + 365 days; - uint32 unlockPeriod = 4 * 365 days; - uint64 unlockCliff = unlockStart + 365 days; - - pool.grant( - Alice, - TimelockTokenPool.Grant( - 10_000e18, - grantStart, - grantCliff, - grantPeriod, - unlockStart, - unlockCliff, - unlockPeriod - ) - ); - vm.prank(Vault); - tko.approve(address(pool), 10_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - // 90 days later - vm.warp(grantStart + 90 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - // 1 year later - vm.warp(grantStart + 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 2500e18); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - // 2 year later - vm.warp(grantStart + 2 * 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 5000e18); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - // 3 year later - vm.warp(grantStart + 3 * 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 7500e18); - assertEq(amountUnlocked, 3750e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 3750e18); - - // 4 year later - vm.warp(grantStart + 4 * 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 7500e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 7500e18); - - // 5 year later - vm.warp(grantStart + 5 * 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 10_000e18); - - // 6 year later - vm.warp(grantStart + 6 * 365 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 10_000e18); - assertEq(amountUnlocked, 10_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 10_000e18); - } - - function test_multiple_grants() public { - pool.grant(Alice, TimelockTokenPool.Grant(10_000e18, 0, 0, 0, 0, 0, 0)); - pool.grant(Alice, TimelockTokenPool.Grant(20_000e18, 0, 0, 0, 0, 0, 0)); - - vm.prank(Vault); - tko.approve(address(pool), 30_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 30_000e18); - assertEq(amountUnlocked, 30_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 30_000e18); - } - - function test_void_multiple_grants_before_granted() public { - uint64 grantStart = uint64(block.timestamp) + 30 days; - pool.grant(Alice, TimelockTokenPool.Grant(10_000e18, grantStart, 0, 0, 0, 0, 0)); - pool.grant(Alice, TimelockTokenPool.Grant(20_000e18, grantStart, 0, 0, 0, 0, 0)); - - vm.prank(Vault); - tko.approve(address(pool), 30_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - // Try to void the grant - pool.void(Alice); - - TimelockTokenPool.Grant[] memory grants = pool.getMyGrants(Alice); - for (uint256 i; i < grants.length; ++i) { - assertEq(grants[i].grantStart, 0); - assertEq(grants[i].grantPeriod, 0); - assertEq(grants[i].grantCliff, 0); - - assertEq(grants[i].unlockStart, 0); - assertEq(grants[i].unlockPeriod, 0); - assertEq(grants[i].unlockCliff, 0); - - assertEq(grants[i].amount, 0); - } - } - - function test_void_multiple_grants_after_granted() public { - uint64 grantStart = uint64(block.timestamp) + 30 days; - pool.grant(Alice, TimelockTokenPool.Grant(10_000e18, grantStart, 0, 0, 0, 0, 0)); - pool.grant(Alice, TimelockTokenPool.Grant(20_000e18, grantStart, 0, 0, 0, 0, 0)); - - vm.prank(Vault); - tko.approve(address(pool), 30_000e18); - - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - - assertEq(amountOwned, 0); - assertEq(amountUnlocked, 0); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 0); - - vm.warp(grantStart + 1); - - // Try to void the grant - // Try to void the grant - vm.expectRevert(TimelockTokenPool.NOTHING_TO_VOID.selector); - pool.void(Alice); - } - - function test_void_multiple_grants_in_the_middle() public { - uint64 grantStart = uint64(block.timestamp); - uint32 grantPeriod = 100 days; - pool.grant(Alice, TimelockTokenPool.Grant(10_000e18, grantStart, 0, grantPeriod, 0, 0, 0)); - pool.grant(Alice, TimelockTokenPool.Grant(20_000e18, grantStart, 0, grantPeriod, 0, 0, 0)); - - vm.prank(Vault); - tko.approve(address(pool), 30_000e18); - - vm.warp(grantStart + 50 days); - ( - uint128 amountOwned, - uint128 amountUnlocked, - uint128 amountWithdrawn, - uint128 amountWithdrawable - ) = pool.getMyGrantSummary(Alice); - - assertEq(amountOwned, 15_000e18); - assertEq(amountUnlocked, 15_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 15_000e18); - - pool.void(Alice); - - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 15_000e18); - assertEq(amountUnlocked, 15_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 15_000e18); - - vm.warp(grantStart + 100 days); - (amountOwned, amountUnlocked, amountWithdrawn, amountWithdrawable) = - pool.getMyGrantSummary(Alice); - assertEq(amountOwned, 15_000e18); - assertEq(amountUnlocked, 15_000e18); - assertEq(amountWithdrawn, 0); - assertEq(amountWithdrawable, 15_000e18); - } -} diff --git a/packages/protocol/test/team/airdrop/MerkleClaimable.t.sol b/packages/protocol/test/team/airdrop/MerkleClaimable.t.sol deleted file mode 100644 index 44fba6980188..000000000000 --- a/packages/protocol/test/team/airdrop/MerkleClaimable.t.sol +++ /dev/null @@ -1,248 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "../../TaikoTest.sol"; - -contract MyERC20 is ERC20 { - constructor(address owner) ERC20("Taiko Token", "TKO") { - _mint(owner, 1_000_000_000e18); - } -} - -contract TestERC20Airdrop is TaikoTest { - uint64 claimStart; - uint64 claimEnd; - address internal owner = randAddress(); - - bytes32 merkleRoot = 0x73a7330a8657ad864b954215a8f636bb3709d2edea60bcd4fcb8a448dbc6d70f; - - ERC20Airdrop airdrop; - ERC20Airdrop2 airdrop2; - ERC20 token; - - function setUp() public { - token = new MyERC20(address(owner)); - // 1st 'genesis' airdrop - airdrop = ERC20Airdrop( - deployProxy({ - name: "airdrop", - impl: address(new ERC20Airdrop()), - data: bytes.concat( - ERC20Airdrop.init.selector, abi.encode(0, 0, merkleRoot, address(token), owner) - ) - }) - ); - - // 2nd airdrop subject to unlocking (e.g. 10 days after starting after - // claim window) - airdrop2 = ERC20Airdrop2( - deployProxy({ - name: "airdrop", - impl: address(new ERC20Airdrop2()), - data: bytes.concat( - ERC20Airdrop2.init.selector, - abi.encode(0, 0, merkleRoot, address(token), owner, 10 days) - ) - }) - ); - - claimStart = uint64(block.timestamp + 10); - claimEnd = uint64(block.timestamp + 10_000); - - airdrop.setConfig(claimStart, claimEnd, merkleRoot); - - airdrop2.setConfig(claimStart, claimEnd, merkleRoot); - - vm.roll(block.number + 1); - vm.warp(block.timestamp + 12); - - vm.prank(owner, owner); - MyERC20(address(token)).approve(address(airdrop), 1_000_000_000e18); - - vm.prank(owner, owner); - MyERC20(address(token)).approve(address(airdrop2), 1_000_000_000e18); - } - - function test_claim_but_claim_not_ongoing_yet() public { - vm.warp(1); - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.expectRevert(MerkleClaimable.CLAIM_NOT_ONGOING.selector); - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 100), merkleProof); - } - - function test_claim_but_claim_not_ongoing_anymore() public { - vm.warp(uint64(block.timestamp + 11_000)); - - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.expectRevert(MerkleClaimable.CLAIM_NOT_ONGOING.selector); - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 100), merkleProof); - } - - function test_claim_but_with_invalid_allowance() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.expectRevert(MerkleClaimable.INVALID_PROOF.selector); - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 200), merkleProof); - } - - function test_claim() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 100), merkleProof); - - // Check Alice balance - assertEq(token.balanceOf(Alice), 100); - } - - function test_claim_with_same_proofs_twice() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 100), merkleProof); - - // Check Alice balance - assertEq(token.balanceOf(Alice), 100); - - vm.expectRevert(MerkleClaimable.CLAIMED_ALREADY.selector); - vm.prank(Alice, Alice); - airdrop.claim(abi.encode(Alice, 100), merkleProof); - } - - function test_withdraw_for_airdrop2_withdraw_daily() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.prank(Alice, Alice); - airdrop2.claim(abi.encode(Alice, 100), merkleProof); - - // Try withdraw but not started yet - vm.expectRevert(ERC20Airdrop2.WITHDRAWALS_NOT_ONGOING.selector); - airdrop2.withdraw(Alice); - - // Roll one day after another, for 10 days and see the 100 allowance be withdrawn all and no - // more left for the 11th day - uint256 i = 1; - uint256 balance; - uint256 withdrawable; - for (i = 1; i < 11; i++) { - vm.roll(block.number + 200); - vm.warp(claimEnd + (i * 1 days)); - - (balance, withdrawable) = airdrop2.getBalance(Alice); - - assertEq(balance, 100); - assertEq(withdrawable, 10); - - airdrop2.withdraw(Alice); - // Check Alice balance - assertEq(token.balanceOf(Alice), (i * 10)); - } - - // On the 10th day (midnight), Alice has no claims left - vm.roll(block.number + 200); - vm.warp(claimEnd + (10 days)); - - (balance, withdrawable) = airdrop2.getBalance(Alice); - - assertEq(balance, 100); - assertEq(withdrawable, 0); - - // No effect - airdrop2.withdraw(Alice); - // Check Alice balance - assertEq(token.balanceOf(Alice), 100); - } - - function test_withdraw_for_airdrop2_withdraw_at_the_end() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.prank(Alice, Alice); - airdrop2.claim(abi.encode(Alice, 100), merkleProof); - - // Try withdraw but not started yet - vm.expectRevert(ERC20Airdrop2.WITHDRAWALS_NOT_ONGOING.selector); - airdrop2.withdraw(Alice); - - // Roll 10 day after - vm.roll(block.number + 200); - vm.warp(claimEnd + 10 days); - - (uint256 balance, uint256 withdrawable) = airdrop2.getBalance(Alice); - - assertEq(balance, 100); - assertEq(withdrawable, 100); - - airdrop2.withdraw(Alice); - - // Check Alice balance - assertEq(token.balanceOf(Alice), 100); - } - - function test_withdraw_for_airdrop2_but_out_of_withdrawal_window() public { - vm.warp(uint64(block.timestamp + 11)); - // These proofs are coming from 'pnpm run buildMerkle' - bytes32[] memory merkleProof = new bytes32[](3); - merkleProof[0] = 0x4014b456db813d18e801fe3b30bbe14542c9c84caa9a92b643f7f46849283077; - merkleProof[1] = 0xfc2f09b34fb9437f9bde16049237a2ab3caa6d772bd794da57a8c314aea22b3f; - merkleProof[2] = 0xc13844b93533d8aec9c7c86a3d9399efb4e834f4069b9fd8a88e7290be612d05; - - vm.prank(Alice, Alice); - airdrop2.claim(abi.encode(Alice, 100), merkleProof); - - // Try withdraw but not started yet - vm.expectRevert(ERC20Airdrop2.WITHDRAWALS_NOT_ONGOING.selector); - airdrop2.withdraw(Alice); - - // Roll 11 day after - vm.roll(block.number + 200); - vm.warp(claimEnd + 11 days); - - (uint256 balance, uint256 withdrawable) = airdrop2.getBalance(Alice); - - // Balance and withdrawable is 100,100 --> bc. it is out of withdrawal window - assertEq(balance, 100); - assertEq(withdrawable, 100); - - vm.expectRevert(ERC20Airdrop2.WITHDRAWALS_NOT_ONGOING.selector); - airdrop2.withdraw(Alice); - - // Check Alice balance - assertEq(token.balanceOf(Alice), 0); - } -} diff --git a/packages/protocol/test/tokenvault/BridgedERC20.t.sol b/packages/protocol/test/tokenvault/BridgedERC20.t.sol index 2999a88806b1..80470c706449 100644 --- a/packages/protocol/test/tokenvault/BridgedERC20.t.sol +++ b/packages/protocol/test/tokenvault/BridgedERC20.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; @@ -12,7 +12,7 @@ contract TestBridgedERC20 is TaikoTest { manager = deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + data: abi.encodeCall(AddressManager.init, (address(0))) }); register(manager, "erc20_vault", vault); @@ -24,15 +24,6 @@ contract TestBridgedERC20 is TaikoTest { vm.expectRevert(); btoken.changeMigrationStatus(Emma, false); - vm.startPrank(owner); - btoken.changeMigrationStatus(Emma, false); - btoken.changeMigrationStatus(address(0), false); - btoken.changeMigrationStatus(address(0), true); - btoken.changeMigrationStatus(Emma, true); - vm.expectRevert(); - btoken.changeMigrationStatus(Emma, true); - vm.stopPrank(); - vm.startPrank(vault); btoken.changeMigrationStatus(Frank, false); btoken.changeMigrationStatus(address(0), false); @@ -44,22 +35,23 @@ contract TestBridgedERC20 is TaikoTest { vm.stopPrank(); } - function test_20Vault_migration___only_vault_can_min_burn_when_migration_off() public { + function test_20Vault_migration___only_vault_can_min__but_cannot_burn_when_migration_off() + public + { BridgedERC20 btoken = deployBridgedToken("BAR"); // only erc20_vault can brun and mint - vm.startPrank(vault); + vm.prank(vault, vault); btoken.mint(Bob, 1000); - btoken.burn(Bob, 600); - assertEq(btoken.balanceOf(Bob), 400); + //Vault cannot burn only if it owns the tokens + vm.expectRevert(); + vm.prank(Bob, Bob); + btoken.burn(600); + assertEq(btoken.balanceOf(Bob), 1000); vm.stopPrank(); - // Owner cannot burn/mint - vm.startPrank(owner); - vm.expectRevert(); + // Owner can burn/mint + vm.prank(owner, owner); btoken.mint(Bob, 1000); - vm.expectRevert(); - btoken.burn(Bob, 100); - vm.stopPrank(); } function test_20Vault_migration__old_to_new() public { @@ -88,9 +80,9 @@ contract TestBridgedERC20 is TaikoTest { vm.expectRevert(); oldToken.mint(Bob, 10); - // 2. burning can be done by anyone - vm.prank(randAddress()); - oldToken.burn(Bob, 10); + // but can be done by the token owner - if migrating out phase + vm.prank(Bob); + oldToken.burn(10); assertEq(oldToken.balanceOf(Bob), 90); assertEq(newToken.balanceOf(Bob), 210); @@ -101,25 +93,31 @@ contract TestBridgedERC20 is TaikoTest { newToken.mint(Bob, 10); vm.prank(owner); - vm.expectRevert(); newToken.mint(Bob, 10); vm.prank(vault); newToken.mint(Bob, 15); - assertEq(newToken.balanceOf(Bob), 225); + assertEq(newToken.balanceOf(Bob), 235); - // 2. Nobody can burn except the vault - vm.prank(Bob); + // Vault can only burn if it owns the tokens + vm.prank(vault); vm.expectRevert(); - newToken.burn(Bob, 10); + newToken.burn(25); + assertEq(newToken.balanceOf(Bob), 235); - vm.prank(owner); - vm.expectRevert(); - newToken.burn(Bob, 10); + // Imitate current bridge-back operation, as Bob gave approval (for bridging back) and then + // ERC20Vault does the "transfer and burn" + vm.prank(Bob); + newToken.approve(vault, 25); + + // Following the "transfer and burn" pattern + vm.prank(vault); + newToken.transferFrom(Bob, vault, 25); vm.prank(vault); - newToken.burn(Bob, 25); - assertEq(newToken.balanceOf(Bob), 200); + newToken.burn(25); + + assertEq(newToken.balanceOf(Bob), 210); } function deployBridgedToken(string memory name) internal returns (BridgedERC20) { @@ -130,12 +128,11 @@ contract TestBridgedERC20 is TaikoTest { deployProxy({ name: "bridged_token1", impl: address(new BridgedERC20()), - data: bytes.concat( - BridgedERC20.init.selector, - abi.encode(address(manager), srcToken, srcChainId, srcDecimals, name, name) - ), - registerTo: manager, - owner: owner + data: abi.encodeCall( + BridgedERC20.init, + (owner, address(manager), srcToken, srcChainId, srcDecimals, name, name) + ), + registerTo: manager }) ); } diff --git a/packages/protocol/test/tokenvault/ERC1155Vault.t.sol b/packages/protocol/test/tokenvault/ERC1155Vault.t.sol index f35723539135..c7a4853e664c 100644 --- a/packages/protocol/test/tokenvault/ERC1155Vault.t.sol +++ b/packages/protocol/test/tokenvault/ERC1155Vault.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import "../TaikoTest.sol"; @@ -72,8 +72,8 @@ contract PrankDestBridge { // a contract // most probably due to some deployment address nonce issue. (Seems a // known issue). - destERC1155Vault.receiveToken{ value: mockLibInvokeMsgValue }( - ctoken, from, to, tokenIds, amounts + destERC1155Vault.onMessageInvocation{ value: mockLibInvokeMsgValue }( + abi.encode(ctoken, from, to, tokenIds, amounts) ); ctx.sender = address(0); @@ -89,6 +89,7 @@ contract UpdatedBridgedERC1155 is BridgedERC1155 { } contract ERC1155VaultTest is TaikoTest { + uint32 private constant GAS_LIMIT = 2_000_000; AddressManager addressManager; BadReceiver badReceiver; Bridge bridge; @@ -99,7 +100,6 @@ contract ERC1155VaultTest is TaikoTest { ERC1155Vault destChainErc1155Vault; TestTokenERC1155 ctoken1155; SignalService signalService; - DummyCrossChainSync crossChainSync; uint64 destChainId = 19_389; function setUp() public { @@ -111,7 +111,7 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + data: abi.encodeCall(AddressManager.init, (address(0))) }) ); @@ -120,9 +120,8 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -132,9 +131,8 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -143,7 +141,7 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "signal_service", impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); @@ -151,7 +149,7 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "erc1155_vault", impl: address(new ERC1155Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC1155Vault.init, (address(0), address(addressManager))) }) ); @@ -159,7 +157,7 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "erc1155_vault", impl: address(new ERC1155Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC1155Vault.init, (address(0), address(addressManager))) }) ); @@ -170,12 +168,10 @@ contract ERC1155VaultTest is TaikoTest { deployProxy({ name: "signal_service", impl: address(new SkipProofCheckSignal()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); - crossChainSync = new DummyCrossChainSync(); - addressManager.setAddress( uint64(block.chainid), "signal_service", address(mockProofSignalService) ); @@ -237,10 +233,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -260,11 +263,11 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(0), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, address(0), Alice, GAS_LIMIT, address(0), GAS_LIMIT, tokenIds, amounts ); vm.prank(Alice, Alice); vm.expectRevert(BaseNFTVault.VAULT_INVALID_TOKEN.selector); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_1155Vault_sendToken_with_0_tokens_1155() public { @@ -281,11 +284,18 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 0; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); vm.expectRevert(BaseNFTVault.VAULT_INVALID_AMOUNT.selector); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_1155Vault_receiveTokens_from_newly_deployed_bridged_contract_on_destination_chain_1155( @@ -305,10 +315,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -360,10 +377,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -404,10 +428,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 1; sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 7); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 3); @@ -449,7 +480,14 @@ contract ERC1155VaultTest is TaikoTest { uint256 etherValue = 0.1 ether; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, David, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + David, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); erc1155Vault.sendToken{ value: etherValue }(sendOpts); @@ -503,11 +541,18 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - IBridge.Message memory message = erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + IBridge.Message memory message = erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -538,10 +583,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[1] = 5; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -594,10 +646,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 1; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 1); @@ -644,11 +703,18 @@ contract ERC1155VaultTest is TaikoTest { ERC1155(deployedContract).setApprovalForAll(address(destChainErc1155Vault), true); sendOpts = BaseNFTVault.BridgeTransferOp( - chainId, Bob, address(deployedContract), tokenIds, amounts, 140_000, 140_000, Bob, "" + chainId, + address(0), + Bob, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Bob, Bob); - destChainErc1155Vault.sendToken{ value: 140_000 }(sendOpts); + destChainErc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); vm.chainId(chainId); @@ -690,10 +756,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 1; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 1); @@ -740,12 +813,19 @@ contract ERC1155VaultTest is TaikoTest { ERC1155(deployedContract).setApprovalForAll(address(destChainErc1155Vault), true); sendOpts = BaseNFTVault.BridgeTransferOp( - chainId, Alice, address(deployedContract), tokenIds, amounts, 140_000, 140_000, Bob, "" + chainId, + address(0), + Alice, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - vm.expectRevert("ERC1155: burn amount exceeds balance"); - destChainErc1155Vault.sendToken{ value: 140_000 }(sendOpts); + vm.expectRevert("ERC1155: caller is not token owner or approved"); + destChainErc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_1155Vault_upgrade_bridged_tokens_1155() public { @@ -762,10 +842,17 @@ contract ERC1155VaultTest is TaikoTest { amounts[0] = 2; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(ctoken1155), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - erc1155Vault.sendToken{ value: 140_000 }(sendOpts); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ctoken1155.balanceOf(Alice, 1), 8); assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 2); @@ -811,4 +898,91 @@ contract ERC1155VaultTest is TaikoTest { fail(); } } + + function test_1155Vault_shall_not_be_able_to_burn_arbitrarily() public { + vm.prank(Alice, Alice); + ctoken1155.setApprovalForAll(address(erc1155Vault), true); + + assertEq(ctoken1155.balanceOf(Alice, 1), 10); + assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 0); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + + BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(ctoken1155), + GAS_LIMIT, + tokenIds, + amounts + ); + vm.prank(Alice, Alice); + erc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + + assertEq(ctoken1155.balanceOf(address(erc1155Vault), 1), 1); + + // This canonicalToken is basically need to be exact same as the + // sendToken() puts together + // - here is just mocking putting it together. + BaseNFTVault.CanonicalNFT memory canonicalToken = BaseNFTVault.CanonicalNFT({ + chainId: 31_337, + addr: address(ctoken1155), + symbol: "TT", + name: "TT" + }); + + uint64 chainId = uint64(block.chainid); + vm.chainId(destChainId); + + destChainIdBridge.sendReceiveERC1155ToERC1155Vault( + canonicalToken, + Alice, + Alice, + tokenIds, + amounts, + bytes32(0), + address(erc1155Vault), + chainId, + 0 + ); + + // Query canonicalToBridged + address deployedContract = + destChainErc1155Vault.canonicalToBridged(chainId, address(ctoken1155)); + // Alice bridged over 1 from tokenId 1 + assertEq(ERC1155(deployedContract).balanceOf(Alice, 1), 1); + + sendOpts = BaseNFTVault.BridgeTransferOp( + chainId, + address(0), + Alice, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts + ); + + // Alice hasn't approved the vault yet! + vm.prank(Alice, Alice); + vm.expectRevert("ERC1155: caller is not token owner or approved"); + destChainErc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + + // Also Vault cannot burn tokens it does not own (even if the priv key compromised) + vm.prank(address(destChainErc1155Vault), address(destChainErc1155Vault)); + vm.expectRevert("ERC1155: burn amount exceeds balance"); + BridgedERC1155(deployedContract).burn(1, 20); + + // After setApprovalForAll() ERC1155Vault can transfer and burn + vm.prank(Alice, Alice); + ERC1155(deployedContract).setApprovalForAll(address(destChainErc1155Vault), true); + vm.prank(Alice, Alice); + destChainErc1155Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + } } diff --git a/packages/protocol/test/tokenvault/ERC20Vault.t.sol b/packages/protocol/test/tokenvault/ERC20Vault.t.sol index 8440cb695cdd..98f1c9ddc12b 100644 --- a/packages/protocol/test/tokenvault/ERC20Vault.t.sol +++ b/packages/protocol/test/tokenvault/ERC20Vault.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "../TaikoTest.sol"; @@ -32,7 +32,7 @@ contract PrankDestBridge { ERC20Vault.CanonicalERC20 calldata canonicalToken, address from, address to, - uint256 amount, + uint64 amount, bytes32 msgHash, address srcChainERC20Vault, uint64 srcChainId, @@ -51,8 +51,8 @@ contract PrankDestBridge { // The problem (with foundry) is that this way it is not able to deploy // a contract most probably due to some deployment address nonce issue. (Seems a known // issue). - destERC20Vault.receiveToken{ value: mockLibInvokeMsgValue }( - canonicalToken, from, to, amount + destERC20Vault.onMessageInvocation{ value: mockLibInvokeMsgValue }( + abi.encode(canonicalToken, from, to, amount) ); ctx.sender = address(0); @@ -74,32 +74,35 @@ contract TestERC20Vault is TaikoTest { ERC20Vault erc20Vault; ERC20Vault destChainIdERC20Vault; PrankDestBridge destChainIdBridge; + SkipProofCheckSignal mockProofSignalService; FreeMintERC20 erc20; - SignalService signalService; + FreeMintERC20 weirdNamedToken; uint64 destChainId = 7; uint64 srcChainId = uint64(block.chainid); + BridgedERC20 usdc; + BridgedERC20 usdt; + BridgedERC20 stETH; + function setUp() public { vm.startPrank(Carol); vm.deal(Alice, 1 ether); vm.deal(Carol, 1 ether); vm.deal(Bob, 1 ether); - tko = TaikoToken( + addressManager = AddressManager( deployProxy({ - name: "taiko_token", - impl: address(new TaikoToken()), - data: bytes.concat( - TaikoToken.init.selector, abi.encode("Taiko Token", "TTKOk", address(this)) - ) + name: "address_manager", + impl: address(new AddressManager()), + data: abi.encodeCall(AddressManager.init, (address(0))) }) ); - addressManager = AddressManager( + tko = TaikoToken( deployProxy({ - name: "address_manager", - impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + name: "taiko_token", + impl: address(new TaikoToken()), + data: abi.encodeCall(TaikoToken.init, (address(0), address(this))) }) ); @@ -109,7 +112,7 @@ contract TestERC20Vault is TaikoTest { deployProxy({ name: "erc20_vault", impl: address(new ERC20Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC20Vault.init, (address(0), address(addressManager))) }) ); @@ -117,21 +120,23 @@ contract TestERC20Vault is TaikoTest { deployProxy({ name: "erc20_vault", impl: address(new ERC20Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC20Vault.init, (address(0), address(addressManager))) }) ); erc20 = new FreeMintERC20("ERC20", "ERC20"); erc20.mint(Alice); + weirdNamedToken = new FreeMintERC20("", "123456abcdefgh"); + weirdNamedToken.mint(Alice); + bridge = Bridge( payable( deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -139,19 +144,19 @@ contract TestERC20Vault is TaikoTest { destChainIdBridge = new PrankDestBridge(erc20Vault); vm.deal(address(destChainIdBridge), 100 ether); - signalService = SignalService( + mockProofSignalService = SkipProofCheckSignal( deployProxy({ name: "signal_service", - impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector), - registerTo: address(0), - owner: address(0) + impl: address(new SkipProofCheckSignal()), + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); - addressManager.setAddress(uint64(block.chainid), "bridge", address(bridge)); + addressManager.setAddress( + uint64(block.chainid), "signal_service", address(mockProofSignalService) + ); - addressManager.setAddress(uint64(block.chainid), "signal_service", address(signalService)); + addressManager.setAddress(destChainId, "signal_service", address(mockProofSignalService)); addressManager.setAddress(uint64(block.chainid), "erc20_vault", address(erc20Vault)); @@ -165,16 +170,55 @@ contract TestERC20Vault is TaikoTest { addressManager.setAddress(uint64(block.chainid), "bridged_erc20", bridgedERC20); + usdc = BridgedERC20( + deployProxy({ + name: "usdc", + impl: address(new BridgedERC20()), + data: abi.encodeCall( + BridgedERC20.init, + (address(0), address(addressManager), randAddress(), 100, 18, "USDC", "USDC coin") + ) + }) + ); + + usdt = BridgedERC20( + deployProxy({ + name: "usdt", + impl: address(new BridgedERC20()), + data: abi.encodeCall( + BridgedERC20.init, + (address(0), address(addressManager), randAddress(), 100, 18, "USDT", "USDT coin") + ) + }) + ); + + stETH = BridgedERC20( + deployProxy({ + name: "stETH", + impl: address(new BridgedERC20()), + data: abi.encodeCall( + BridgedERC20.init, + ( + address(0), + address(addressManager), + randAddress(), + 100, + 18, + "stETH", + "Lido Staked ETH" + ) + ) + }) + ); vm.stopPrank(); } function test_20Vault_send_erc20_revert_if_allowance_not_set() public { vm.startPrank(Alice); - - vm.expectRevert("ERC20: insufficient allowance"); + vm.expectRevert(BaseVault.VAULT_INSUFFICIENT_FEE.selector); erc20Vault.sendToken( ERC20Vault.BridgeTransferOp( - destChainId, Bob, address(erc20), 1 wei, 1_000_000, 1, Bob, "" + destChainId, address(0), Bob, 1, address(erc20), 1_000_000, 1 wei ) ); } @@ -182,7 +226,7 @@ contract TestERC20Vault is TaikoTest { function test_20Vault_send_erc20_no_processing_fee() public { vm.startPrank(Alice); - uint256 amount = 2 wei; + uint64 amount = 2 wei; erc20.approve(address(erc20Vault), amount); uint256 aliceBalanceBefore = erc20.balanceOf(Alice); @@ -190,7 +234,7 @@ contract TestERC20Vault is TaikoTest { erc20Vault.sendToken( ERC20Vault.BridgeTransferOp( - destChainId, Bob, address(erc20), amount, 1_000_000, 0, Bob, "" + destChainId, address(0), Bob, 0, address(erc20), 1_000_000, amount ) ); @@ -204,13 +248,13 @@ contract TestERC20Vault is TaikoTest { function test_20Vault_send_erc20_processing_fee_reverts_if_msg_value_too_low() public { vm.startPrank(Alice); - uint256 amount = 2 wei; + uint64 amount = 2 wei; erc20.approve(address(erc20Vault), amount); vm.expectRevert(); erc20Vault.sendToken( ERC20Vault.BridgeTransferOp( - destChainId, Bob, address(erc20), amount, 1_000_000, amount - 1, Bob, "" + destChainId, address(0), Bob, amount - 1, address(erc20), 1_000_000, amount ) ); } @@ -218,7 +262,7 @@ contract TestERC20Vault is TaikoTest { function test_20Vault_send_erc20_processing_fee() public { vm.startPrank(Alice); - uint256 amount = 2 wei; + uint64 amount = 2 wei; erc20.approve(address(erc20Vault), amount); uint256 aliceBalanceBefore = erc20.balanceOf(Alice); @@ -227,13 +271,12 @@ contract TestERC20Vault is TaikoTest { erc20Vault.sendToken{ value: amount }( ERC20Vault.BridgeTransferOp( destChainId, + address(0), Bob, + amount - 1, address(erc20), - amount - 1, // value: (msg.value - fee) 1_000_000, - amount - 1, - Bob, - "" + amount - 1 // value: (msg.value - fee) ) ); @@ -247,12 +290,12 @@ contract TestERC20Vault is TaikoTest { function test_20Vault_send_erc20_reverts_invalid_amount() public { vm.startPrank(Alice); - uint256 amount = 0; + uint64 amount = 0; vm.expectRevert(ERC20Vault.VAULT_INVALID_AMOUNT.selector); erc20Vault.sendToken( ERC20Vault.BridgeTransferOp( - destChainId, Bob, address(erc20), amount, 1_000_000, 0, Bob, "" + destChainId, address(0), Bob, 0, address(erc20), 1_000_000, amount ) ); } @@ -260,11 +303,13 @@ contract TestERC20Vault is TaikoTest { function test_20Vault_send_erc20_reverts_invalid_token_address() public { vm.startPrank(Alice); - uint256 amount = 1; + uint64 amount = 1; vm.expectRevert(ERC20Vault.VAULT_INVALID_TOKEN.selector); erc20Vault.sendToken( - ERC20Vault.BridgeTransferOp(destChainId, Bob, address(0), amount, 1_000_000, 0, Bob, "") + ERC20Vault.BridgeTransferOp( + destChainId, address(0), Bob, 0, address(0), 1_000_000, amount + ) ); } @@ -277,7 +322,7 @@ contract TestERC20Vault is TaikoTest { erc20.mint(address(erc20Vault)); - uint256 amount = 1; + uint64 amount = 1; address to = Bob; uint256 erc20VaultBalanceBefore = erc20.balanceOf(address(erc20Vault)); @@ -308,7 +353,7 @@ contract TestERC20Vault is TaikoTest { erc20.mint(address(erc20Vault)); - uint256 amount = 1; + uint64 amount = 1; uint256 etherAmount = 0.1 ether; address to = David; @@ -342,7 +387,7 @@ contract TestERC20Vault is TaikoTest { vm.chainId(destChainId); - uint256 amount = 1; + uint64 amount = 1; destChainIdBridge.setERC20Vault(address(destChainIdERC20Vault)); @@ -366,8 +411,8 @@ contract TestERC20Vault is TaikoTest { assertEq(bridgedAddressAfter != address(0), true); BridgedERC20 bridgedERC20 = BridgedERC20(bridgedAddressAfter); - assertEq(bridgedERC20.name(), unicode"Bridged ERC20 (⭀31337)"); - assertEq(bridgedERC20.symbol(), unicode"ERC20.t"); + assertEq(bridgedERC20.name(), "ERC20"); + assertEq(bridgedERC20.symbol(), "ERC20"); assertEq(bridgedERC20.balanceOf(Bob), amount); } @@ -385,12 +430,22 @@ contract TestERC20Vault is TaikoTest { }); } + function noNameErc20(uint64 chainId) internal view returns (ERC20Vault.CanonicalERC20 memory) { + return ERC20Vault.CanonicalERC20({ + chainId: chainId, + addr: address(weirdNamedToken), + decimals: weirdNamedToken.decimals(), + symbol: weirdNamedToken.symbol(), + name: weirdNamedToken.name() + }); + } + function test_20Vault_upgrade_bridged_tokens_20() public { vm.startPrank(Alice); vm.chainId(destChainId); - uint256 amount = 1; + uint64 amount = 1; destChainIdBridge.setERC20Vault(address(destChainIdERC20Vault)); @@ -433,4 +488,194 @@ contract TestERC20Vault is TaikoTest { fail(); } } + + function test_20Vault_onMessageRecalled_20() public { + vm.startPrank(Alice); + + uint64 amount = 2 wei; + erc20.approve(address(erc20Vault), amount); + + uint256 aliceBalanceBefore = erc20.balanceOf(Alice); + uint256 erc20VaultBalanceBefore = erc20.balanceOf(address(erc20Vault)); + + IBridge.Message memory _messageToSimulateFail = erc20Vault.sendToken( + ERC20Vault.BridgeTransferOp( + destChainId, address(0), Bob, 0, address(erc20), 1_000_000, amount + ) + ); + + uint256 aliceBalanceAfter = erc20.balanceOf(Alice); + uint256 erc20VaultBalanceAfter = erc20.balanceOf(address(erc20Vault)); + + assertEq(aliceBalanceBefore - aliceBalanceAfter, amount); + assertEq(erc20VaultBalanceAfter - erc20VaultBalanceBefore, amount); + + // No need to imitate that it is failed because we have a mock SignalService + bridge.recallMessage(_messageToSimulateFail, bytes("")); + + uint256 aliceBalanceAfterRecall = erc20.balanceOf(Alice); + uint256 erc20VaultBalanceAfterRecall = erc20.balanceOf(address(erc20Vault)); + + // Release -> original balance + assertEq(aliceBalanceAfterRecall, aliceBalanceBefore); + assertEq(erc20VaultBalanceAfterRecall, erc20VaultBalanceBefore); + } + + function test_20Vault_change_bridged_token() public { + // A mock canonical "token" + address canonicalRandomToken = vm.addr(102); + + vm.warp(block.timestamp + 91 days); + + vm.startPrank(Carol); + + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(usdc) + ); + + assertEq(erc20Vault.canonicalToBridged(1, address(erc20)), address(usdc)); + + vm.expectRevert(ERC20Vault.VAULT_LAST_MIGRATION_TOO_CLOSE.selector); + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(usdt) + ); + + vm.warp(block.timestamp + 91 days); + + vm.expectRevert(ERC20Vault.VAULT_CTOKEN_MISMATCH.selector); + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT_WRONG_NAME", + name: "ERC20 Test token" + }), + address(usdt) + ); + + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(usdt) + ); + + assertEq(erc20Vault.canonicalToBridged(1, address(erc20)), address(usdt)); + + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: canonicalRandomToken, + decimals: 18, + symbol: "ERC20TT2", + name: "ERC20 Test token2" + }), + address(stETH) + ); + + vm.warp(block.timestamp + 91 days); + + // usdc is already blacklisted! + vm.expectRevert(ERC20Vault.VAULT_BTOKEN_BLACKLISTED.selector); + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(usdc) + ); + + // invalid btoken + vm.expectRevert(ERC20Vault.VAULT_INVALID_CTOKEN.selector); + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: uint64(block.chainid), + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(usdc) + ); + + // We cannot use stETH for erc20 (as it is used in connection with another token) + vm.expectRevert(ERC20Vault.VAULT_INVALID_NEW_BTOKEN.selector); + erc20Vault.changeBridgedToken( + ERC20Vault.CanonicalERC20({ + chainId: 1, + addr: address(erc20), + decimals: 18, + symbol: "ERC20TT", + name: "ERC20 Test token" + }), + address(stETH) + ); + + vm.stopPrank(); + } + + function test_20Vault_to_string() public { + vm.startPrank(Alice); + + (, bytes memory symbolData) = + address(weirdNamedToken).staticcall(abi.encodeCall(INameSymbol.symbol, ())); + (, bytes memory nameData) = + address(weirdNamedToken).staticcall(abi.encodeCall(INameSymbol.name, ())); + + string memory decodedSymbol = LibBytes.toString(symbolData); + string memory decodedName = LibBytes.toString(nameData); + + assertEq(decodedSymbol, "123456abcdefgh"); + assertEq(decodedName, ""); + + vm.stopPrank(); + } + + function test_20Vault_deploy_erc20_with_no_name() public { + vm.startPrank(Alice); + + vm.chainId(destChainId); + + uint64 amount = 1; + + destChainIdBridge.setERC20Vault(address(destChainIdERC20Vault)); + + address bridgedAddressBefore = + destChainIdERC20Vault.canonicalToBridged(srcChainId, address(erc20)); + assertEq(bridgedAddressBefore == address(0), true); + + // Token with empty name succeeds + destChainIdBridge.sendReceiveERC20ToERC20Vault( + noNameErc20(srcChainId), + Alice, + Bob, + amount, + bytes32(0), + address(erc20Vault), + srcChainId, + 0 + ); + } } diff --git a/packages/protocol/test/tokenvault/ERC721Vault.t.sol b/packages/protocol/test/tokenvault/ERC721Vault.t.sol index f89d96e98ccd..c43ed5fde0b0 100644 --- a/packages/protocol/test/tokenvault/ERC721Vault.t.sol +++ b/packages/protocol/test/tokenvault/ERC721Vault.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "../TaikoTest.sol"; @@ -87,8 +87,8 @@ contract PrankDestBridge { // a contract // most probably due to some deployment address nonce issue. (Seems a // known issue). - destERC721Vault.receiveToken{ value: mockLibInvokeMsgValue }( - canonicalToken, from, to, tokenIds + destERC721Vault.onMessageInvocation{ value: mockLibInvokeMsgValue }( + abi.encode(canonicalToken, from, to, tokenIds) ); ctx.sender = address(0); @@ -104,6 +104,8 @@ contract UpdatedBridgedERC721 is BridgedERC721 { } contract ERC721VaultTest is TaikoTest { + uint32 private constant GAS_LIMIT = 2_000_000; + AddressManager addressManager; BadReceiver badReceiver; Bridge bridge; @@ -114,7 +116,6 @@ contract ERC721VaultTest is TaikoTest { ERC721Vault destChainErc721Vault; TestTokenERC721 canonicalToken721; SignalService signalService; - DummyCrossChainSync crossChainSync; uint64 destChainId = 19_389; function setUp() public { @@ -127,7 +128,7 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "address_manager", impl: address(new AddressManager()), - data: bytes.concat(AddressManager.init.selector) + data: abi.encodeCall(AddressManager.init, (address(0))) }) ); @@ -136,9 +137,8 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -148,9 +148,8 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "bridge", impl: address(new Bridge()), - data: bytes.concat(Bridge.init.selector, abi.encode(addressManager)), - registerTo: address(addressManager), - owner: address(0) + data: abi.encodeCall(Bridge.init, (address(0), address(addressManager))), + registerTo: address(addressManager) }) ) ); @@ -159,7 +158,7 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "signal_service", impl: address(new SignalService()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); @@ -167,7 +166,7 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "erc721_vault", impl: address(new ERC721Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC721Vault.init, (address(0), address(addressManager))) }) ); @@ -175,7 +174,7 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "erc721_vault", impl: address(new ERC721Vault()), - data: bytes.concat(BaseVault.init.selector, abi.encode(address(addressManager))) + data: abi.encodeCall(ERC721Vault.init, (address(0), address(addressManager))) }) ); @@ -186,12 +185,10 @@ contract ERC721VaultTest is TaikoTest { deployProxy({ name: "signal_service", impl: address(new SkipProofCheckSignal()), - data: bytes.concat(SignalService.init.selector) + data: abi.encodeCall(SignalService.init, (address(0), address(addressManager))) }) ); - crossChainSync = new DummyCrossChainSync(); - addressManager.setAddress( uint64(block.chainid), "signal_service", address(mockProofSignalService) ); @@ -245,17 +242,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, // With ERC721 still need to specify 1 - 140_000, - 140_000, - Alice, - "" + amounts // With ERC721 still need to specify 1 ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(ERC721(canonicalToken721).ownerOf(1), address(erc721Vault)); } @@ -273,11 +269,11 @@ contract ERC721VaultTest is TaikoTest { amounts[0] = 0; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( - destChainId, Alice, address(0), tokenIds, amounts, 140_000, 140_000, Alice, "" + destChainId, address(0), Alice, GAS_LIMIT, address(0), GAS_LIMIT, tokenIds, amounts ); vm.prank(Alice, Alice); vm.expectRevert(BaseNFTVault.VAULT_INVALID_TOKEN.selector); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_721Vault_sendToken_with_1_tokens_but_erc721_amount_1_invalid() public { @@ -293,18 +289,17 @@ contract ERC721VaultTest is TaikoTest { amounts[0] = 1; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); vm.expectRevert(BaseNFTVault.VAULT_INVALID_AMOUNT.selector); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_721Vault_receiveTokens_from_newly_deployed_bridged_contract_on_destination_chain_721( @@ -324,17 +319,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -376,17 +370,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -423,17 +416,16 @@ contract ERC721VaultTest is TaikoTest { sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(2), address(erc721Vault)); @@ -465,14 +457,13 @@ contract ERC721VaultTest is TaikoTest { uint256 etherValue = 0.1 ether; BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), David, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); erc721Vault.sendToken{ value: etherValue }(sendOpts); @@ -523,18 +514,17 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - IBridge.Message memory message = erc721Vault.sendToken{ value: 140_000 }(sendOpts); + IBridge.Message memory message = erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -563,17 +553,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); assertEq(canonicalToken721.ownerOf(2), address(erc721Vault)); @@ -617,17 +606,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -666,11 +654,18 @@ contract ERC721VaultTest is TaikoTest { ERC721(deployedContract).approve(address(destChainErc721Vault), 1); sendOpts = BaseNFTVault.BridgeTransferOp( - chainId, Bob, address(deployedContract), tokenIds, amounts, 140_000, 140_000, Bob, "" + chainId, + address(0), + Bob, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Bob, Bob); - destChainErc721Vault.sendToken{ value: 140_000 }(sendOpts); + destChainErc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); vm.chainId(chainId); @@ -706,17 +701,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -756,12 +750,19 @@ contract ERC721VaultTest is TaikoTest { // Alice puts together a malicious bridging back message sendOpts = BaseNFTVault.BridgeTransferOp( - chainId, Alice, address(deployedContract), tokenIds, amounts, 140_000, 140_000, Bob, "" + chainId, + address(0), + Alice, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts ); vm.prank(Alice, Alice); - vm.expectRevert(BridgedERC721.BTOKEN_INVALID_BURN.selector); - destChainErc721Vault.sendToken{ value: 140_000 }(sendOpts); + vm.expectRevert("ERC721: transfer from incorrect owner"); + destChainErc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); } function test_721Vault_upgrade_bridged_tokens_721() public { @@ -780,17 +781,16 @@ contract ERC721VaultTest is TaikoTest { BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( destChainId, + address(0), Alice, + GAS_LIMIT, address(canonicalToken721), + GAS_LIMIT, tokenIds, - amounts, - 140_000, - 140_000, - Alice, - "" + amounts ); vm.prank(Alice, Alice); - erc721Vault.sendToken{ value: 140_000 }(sendOpts); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); @@ -833,4 +833,86 @@ contract ERC721VaultTest is TaikoTest { fail(); } } + + function test_721Vault_shall_not_be_able_to_burn_arbitrarily() public { + vm.prank(Alice, Alice); + canonicalToken721.approve(address(erc721Vault), 1); + vm.prank(Alice, Alice); + canonicalToken721.approve(address(erc721Vault), 2); + + assertEq(canonicalToken721.ownerOf(1), Alice); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + + uint256[] memory amounts = new uint256[](1); + amounts[0] = 0; + + BaseNFTVault.BridgeTransferOp memory sendOpts = BaseNFTVault.BridgeTransferOp( + destChainId, + address(0), + Alice, + GAS_LIMIT, + address(canonicalToken721), + GAS_LIMIT, + tokenIds, + amounts + ); + vm.prank(Alice, Alice); + erc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + + assertEq(canonicalToken721.ownerOf(1), address(erc721Vault)); + + // This canonicalToken is basically need to be exact same as the + // sendToken() puts together + // - here is just mocking putting it together. + BaseNFTVault.CanonicalNFT memory canonicalToken = BaseNFTVault.CanonicalNFT({ + chainId: 31_337, + addr: address(canonicalToken721), + symbol: "TT", + name: "TT" + }); + + uint64 chainId = uint64(block.chainid); + vm.chainId(destChainId); + + destChainIdBridge.sendReceiveERC721ToERC721Vault( + canonicalToken, Alice, Alice, tokenIds, bytes32(0), address(erc721Vault), chainId, 0 + ); + + // Query canonicalToBridged + address deployedContract = + destChainErc721Vault.canonicalToBridged(chainId, address(canonicalToken721)); + + // Alice bridged over tokenId 1 + assertEq(ERC721(deployedContract).ownerOf(1), Alice); + + // Alice tries to bridge back message + sendOpts = BaseNFTVault.BridgeTransferOp( + chainId, + address(0), + Alice, + GAS_LIMIT, + address(deployedContract), + GAS_LIMIT, + tokenIds, + amounts + ); + + // Alice hasn't approved the vault yet! + vm.prank(Alice, Alice); + vm.expectRevert("ERC721: caller is not token owner or approved"); + destChainErc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + + // Also Vault cannot burn tokens it does not own (even if the priv key compromised) + vm.prank(address(destChainErc721Vault), address(destChainErc721Vault)); + vm.expectRevert(BridgedERC721.BTOKEN_INVALID_BURN.selector); + BridgedERC721(deployedContract).burn(1); + + // After approve() ERC721Vault can transfer and burn + vm.prank(Alice, Alice); + ERC721(deployedContract).approve(address(destChainErc721Vault), 1); + vm.prank(Alice, Alice); + destChainErc721Vault.sendToken{ value: GAS_LIMIT }(sendOpts); + } }