From 109e22eb69bea0db426556c2e72d47d4b3f3de55 Mon Sep 17 00:00:00 2001 From: adaki2004 <keszeydani@gmail.com> Date: Thu, 6 Jul 2023 16:57:51 +0200 Subject: [PATCH] Adjust current bridge api to an nft bridge version --- .../contracts/nft_bridge/Erc721Bridge.sol | 284 ++++++++++++++++++ .../contracts/nft_bridge/Erc721Vault.sol | 111 +++++++ .../contracts/nft_bridge/NftBridgeErrors.sol | 22 ++ .../nft_bridge/erc721/IErc721Bridge.sol | 105 +++++++ .../erc721/libs/LibErc721BridgeData.sol | 58 ++++ .../erc721/libs/LibErc721BridgeProcess.sol | 102 +++++++ .../erc721/libs/LibErc721BridgeSend.sol | 154 ++++++++++ .../erc721/libs/LibErc721BridgeStatus.sol | 146 +++++++++ 8 files changed, 982 insertions(+) create mode 100644 packages/protocol/contracts/nft_bridge/Erc721Bridge.sol create mode 100644 packages/protocol/contracts/nft_bridge/Erc721Vault.sol create mode 100644 packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol create mode 100644 packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol create mode 100644 packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol diff --git a/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol b/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol new file mode 100644 index 00000000000..1a163b37a75 --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/Erc721Bridge.sol @@ -0,0 +1,284 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { AddressResolver } from "../common/AddressResolver.sol"; +import { EssentialContract } from "../common/EssentialContract.sol"; +import { Proxied } from "../common/Proxied.sol"; +import { IErc721Bridge } from "./erc721/IErc721Bridge.sol"; +import { NftBridgeErrors } from "./NftBridgeErrors.sol"; +import { LibErc721BridgeData } from "./erc721/libs/LibErc721BridgeData.sol"; +// import { LibBridgeProcess } from "./libs/LibBridgeProcess.sol"; +// import { LibBridgeRelease } from "./libs/LibBridgeRelease.sol"; +// import { LibBridgeRetry } from "./libs/LibBridgeRetry.sol"; +import { LibErc721BridgeSend } from "./erc721/libs/LibErc721BridgeSend.sol"; +import { LibErc721BridgeStatus } from "./erc721/libs/LibErc721BridgeStatus.sol"; + +/** + * This contract is an ERC-721 token bridge contract which is deployed on both L1 and L2. + * which calls the library implementations. See _IErc721Bridge_ for more details. + * @dev The code hash for the same address on L1 and L2 may be different. + * @custom:security-contact hello@taiko.xyz + */ +contract Erc721Bridge is EssentialContract, IErc721Bridge, NftBridgeErrors { + using LibErc721BridgeData for Message; + + LibErc721BridgeData.State private _state; // 50 slots reserved + + event MessageStatusChanged( + bytes32 indexed msgHash, + LibErc721BridgeStatus.MessageStatus status, + address transactor + ); + + event DestChainEnabled(uint256 indexed chainId, bool enabled); + + /** + * Initializer to be called after being deployed behind a proxy. + * @dev Initializer function to setup the EssentialContract. + * @param _addressManager The address of the AddressManager contract. + */ + function init(address _addressManager) external initializer { + EssentialContract._init(_addressManager); + } + + /** + * Sends a message from the current chain to the destination chain specified + * in the message. + * @dev Sends a message by calling the LibErc721BridgeSend.sendMessageErc721 library + * function. + * @param message The message to send. (See IBridge) + * @return msgHash The hash of the message that was sent. + */ + function sendMessageErc721(Message calldata message) + external + payable + nonReentrant + returns (bytes32 msgHash) + { + return LibErc721BridgeSend.sendMessageErc721({ + state: _state, + resolver: AddressResolver(this), + message: message + }); + } + + // TODO: Implement them gradually + /** + * Releases the Ether locked in the bridge as part of a cross-chain + * transfer. + * @dev Releases the Ether by calling the LibBridgeRelease.releaseEther + * library function. + * @param message The message containing the details of the Ether transfer. + * (See IBridge) + * @param proof The proof of the cross-chain transfer. + */ + function releaseTokenErc721( + IErc721Bridge.Message calldata message, + bytes calldata proof + ) + external + nonReentrant + { + return; + // TODO: Implement + // return LibBridgeRelease.releaseEther({ + // state: _state, + // resolver: AddressResolver(this), + // message: message, + // proof: proof + // }); + } + + // /** + // * Processes a message received from another chain. + // * @dev Processes the message by calling the LibBridgeProcess.processMessage + // * library function. + // * @param message The message to process. + // * @param proof The proof of the cross-chain transfer. + // */ + // function processMessage( + // Message calldata message, + // bytes calldata proof + // ) + // external + // nonReentrant + // { + // return LibBridgeProcess.processMessage({ + // state: _state, + // resolver: AddressResolver(this), + // message: message, + // proof: proof + // }); + // } + + // /** + // * Retries sending a message that previously failed to send. + // * @dev Retries the message by calling the LibBridgeRetry.retryMessage + // * library function. + // * @param message The message to retry. + // * @param isLastAttempt Specifies whether this is the last attempt to send + // * the message. + // */ + // function retryMessage( + // Message calldata message, + // bool isLastAttempt + // ) + // external + // nonReentrant + // { + // return LibBridgeRetry.retryMessage({ + // state: _state, + // resolver: AddressResolver(this), + // message: message, + // isLastAttempt: isLastAttempt + // }); + // } + + /** + * Check if the message with the given hash has been sent. + * @param msgHash The hash of the message. + * @return Returns true if the message has been sent, false otherwise. + */ + function isMessageSentErc721(bytes32 msgHash) + public + view + virtual + returns (bool) + { + return LibErc721BridgeSend.isMessageSentErc721(AddressResolver(this), msgHash); + } + + /** + * Check if the message with the given hash has been received. + * @param msgHash The hash of the message. + * @param srcChainId The source chain ID. + * @param proof The proof of message receipt. + * @return Returns true if the message has been received, false otherwise. + */ + function isMessageReceivedErc721( + bytes32 msgHash, + uint256 srcChainId, + bytes calldata proof + ) + public + view + virtual + override + returns (bool) + { + return LibErc721BridgeSend.isMessageReceivedErc721({ + resolver: AddressResolver(this), + msgHash: msgHash, + srcChainId: srcChainId, + proof: proof + }); + } + + /** + * Check if the message with the given hash has failed. + * @param msgHash The hash of the message. + * @param destChainId The destination chain ID. + * @param proof The proof of message failure. + * @return Returns true if the message has failed, false otherwise. + */ + function isMessageFailedErc721( + bytes32 msgHash, + uint256 destChainId, + bytes calldata proof + ) + public + view + virtual + override + returns (bool) + { + return LibErc721BridgeStatus.isMessageFailedErc721({ + resolver: AddressResolver(this), + msgHash: msgHash, + destChainId: destChainId, + proof: proof + }); + } + + /** + * Get the status of the message with the given hash. + * @param msgHash The hash of the message. + * @return Returns the status of the message. + */ + function getMessageStatusErc721(bytes32 msgHash) + public + view + virtual + returns (LibErc721BridgeStatus.MessageStatus) + { + return LibErc721BridgeStatus.getMessageStatusErc721(msgHash); + } + + /** + * Get the current context + * @return Returns the current context. + */ + function context() public view returns (Context memory) { + return _state.ctx; + } + + // /** + // * Check if the Ether associated with the given message hash has been + // * released. + // * @param msgHash The hash of the message. + // * @return Returns true if the Ether has been released, false otherwise. + // */ + // function isEtherReleased(bytes32 msgHash) public view returns (bool) { + // return _state.etherReleased[msgHash]; + // } + + /** + * Check if the destination chain with the given ID is enabled. + * @param _chainId The ID of the chain. + * @return enabled Returns true if the destination chain is enabled, false + * otherwise. + */ + + function isDestChainEnabled(uint256 _chainId) + public + view + returns (bool enabled) + { + (enabled,) = + LibErc721BridgeSend.isDestChainEnabled(AddressResolver(this), _chainId); + } + + /** + * Compute the hash of a given message. + * @param message The message to compute the hash for. + * @return Returns the hash of the message. + */ + function hashMessage(Message calldata message) + public + pure + override + returns (bytes32) + { + return LibErc721BridgeData.hashMessage(message); + } + + /** + * Get the slot associated with a given message hash status. + * @param msgHash The hash of the message. + * @return Returns the slot associated with the given message hash status. + */ + function getMessageStatusSlot(bytes32 msgHash) + public + pure + returns (bytes32) + { + return LibErc721BridgeStatus.getMessageStatusSlot(msgHash); + } +} + +contract ProxiedErc721Bridge is Proxied, Erc721Bridge { } diff --git a/packages/protocol/contracts/nft_bridge/Erc721Vault.sol b/packages/protocol/contracts/nft_bridge/Erc721Vault.sol new file mode 100644 index 00000000000..2010b5bce2e --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/Erc721Vault.sol @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { IERC721ReceiverUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol"; +import { IERC721Upgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; +import { Create2Upgradeable } from + "@openzeppelin/contracts-upgradeable/utils/Create2Upgradeable.sol"; +import { EssentialContract } from "../common/EssentialContract.sol"; +import { Proxied } from "../common/Proxied.sol"; +import { LibAddress } from "../libs/LibAddress.sol"; +import { NftBridgeErrors } from "./NftBridgeErrors.sol"; + +/** + * This contract is for vaulting (and releasing) ERC721 tokens + * @dev Only the contract owner can authorize or deauthorize addresses. + * @custom:security-contact hello@taiko.xyz + */ +contract Erc721Vault is EssentialContract, NftBridgeErrors, IERC721ReceiverUpgradeable { + using LibAddress for address; + + // Maps contract to a contract on a different chain + struct ContractMapping { + address sisterContract; + string tokenName; + string tokenSymbol; + mapping (uint256 tokenId => bool inVault) tokenInVault; + } + mapping(address tokenContract => ContractMapping wrappedTokenContract) originalToWrapperData; + + mapping(address addr => bool isAuthorized) private _authorizedAddrs; + uint256[49] private __gap; + + event Authorized(address indexed addr, bool authorized); + event TokensReleased(address indexed to, address contractAddress, uint256[] tokenIds); + + modifier onlyAuthorized() { + if (!isAuthorized(msg.sender)) { + revert ERC721_TV_NOT_AUTHORIZED(); + } + _; + } + + /** + * Initialize the contract with an address manager + * @param addressManager The address of the address manager + */ + function init(address addressManager) external initializer { + EssentialContract._init(addressManager); + } + + /** + * Transfer token(s) from Erc721Vault to a designated address, checking that the + * sender is authorized. + * @param recipient Address to receive Ether. + * @param tokenContract Token contract. + * @param tokenIds Array of tokenIds to be sent + */ + function releaseTokens( + address recipient, + address tokenContract, + uint256[] memory tokenIds + ) + public + onlyAuthorized + nonReentrant + { + if (recipient == address(0)) { + revert ERC721_TV_DO_NOT_BURN(); + } + + for (uint256 i; i < tokenIds.length; i++) { + IERC721Upgradeable(tokenContract).safeTransferFrom(address(this), recipient, tokenIds[i]); + } + + emit TokensReleased(recipient, tokenContract, tokenIds); + } + + /** + * Set the authorized status of an address, only the owner can call this. + * @param addr Address to set the authorized status of. + * @param authorized Authorized status to set. + */ + function authorize(address addr, bool authorized) public onlyOwner { + if (addr == address(0) || _authorizedAddrs[addr] == authorized) { + revert ERC721_B_TV_PARAM(); + } + _authorizedAddrs[addr] = authorized; + emit Authorized(addr, authorized); + } + + /** + * Get the authorized status of an address. + * @param addr Address to get the authorized status of. + */ + function isAuthorized(address addr) public view returns (bool) { + return _authorizedAddrs[addr]; + } + + function onERC721Received(address, address, uint256, bytes calldata ) external pure returns (bytes4) { + return IERC721ReceiverUpgradeable.onERC721Received.selector; + } +} + +contract ProxiedErc721Vault is Proxied, Erc721Vault { } diff --git a/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol b/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol new file mode 100644 index 00000000000..3978531db85 --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/NftBridgeErrors.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +// Can be a common error contract for 1155 and 721 +abstract contract NftBridgeErrors { + // TokenVault + error ERC721_TV_NOT_AUTHORIZED(); + error ERC721_TV_DO_NOT_BURN(); + error ERC721_B_TV_PARAM(); + // Bridge + error ERC721_B_FORBIDDEN(); + error ERC721_B_WRONG_CHAIN_ID(); + error ERC721_B_STATUS_MISMATCH(); + error ERC721_B_SIGNAL_NOT_RECEIVED(); + error ERC721_B_OWNER_IS_NULL(); + error ERC721_B_WRONG_TO_ADDRESS(); +} diff --git a/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol b/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol new file mode 100644 index 00000000000..27ea743a690 --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/erc721/IErc721Bridge.sol @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +/** + * Bridge interface for NFT contracts. + * @dev Ether is held by Bridges on L1 and by the EtherVault on L2, + * not TokenVaults. + */ +interface IErc721Bridge { + struct Message { + // Message ID. + uint256 id; + // Message sender address (auto filled). + address sender; + // Source chain ID (auto filled). + uint256 srcChainId; + // Destination chain ID where the `to` address lives (auto filled). + uint256 destChainId; + // Owner address of the bridged asset. + address owner; + // Destination owner address. + address to; + // Alternate address to send any refund. If blank, defaults to owner. + address refundAddress; + // Deposited token contract address (!It is always the deposited AKA original address) + address tokenContract; + // Token Ids - multiple per given contract can be birdged + // For an ERC1155 the only difference is that it can have + // multiple amounts/tokenId + uint256[] tokenIds; + // Processing fee for the relayer. Zero if owner will process themself. + uint256 processingFee; + // gasLimit to invoke on the destination chain + uint256 gasLimit; + // Token symbol + string tokenSymbol; + // Token name + string tokenName; + // URIs - so that it shows up as in parent chain. + string[] tokenURIs; + } + + struct Context { + bytes32 msgHash; + address sender; + uint256 srcChainId; + } + + event SignalSentErc721(address sender, bytes32 msgHash); + event MessageSentErc721(bytes32 indexed msgHash, Message message); + event TokenReleasedErc721(bytes32 indexed msgHash, address to, address token, uint256 tokenId); + + /// Sends a message to the destination chain and takes custody + /// of the token(s) required in this contract. + function sendMessageErc721(Message memory message) + external + payable + returns (bytes32 msgHash); + + // Release token(s) with a proof that the message processing on the destination + // chain has been failed. + function releaseTokenErc721( + IErc721Bridge.Message calldata message, + bytes calldata proof + ) + external; + + /// Checks if a msgHash has been stored on the bridge contract by the + /// current address. + function isMessageSentErc721(bytes32 msgHash) external view returns (bool); + + /// Checks if a msgHash has been received on the destination chain and + /// sent by the src chain. + function isMessageReceivedErc721( + bytes32 msgHash, + uint256 srcChainId, + bytes calldata proof + ) + external + view + returns (bool); + + /// Checks if a msgHash has been failed on the destination chain. + function isMessageFailedErc721( + bytes32 msgHash, + uint256 destChainId, + bytes calldata proof + ) + external + view + returns (bool); + + /// Returns the bridge state context. + function context() external view returns (Context memory context); + + function hashMessage(IErc721Bridge.Message calldata message) + external + pure + returns (bytes32); +} diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol new file mode 100644 index 00000000000..cb0a2c121a2 --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeData.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { AddressResolver } from "../../../common/AddressResolver.sol"; +import { BlockHeader } from "../../../libs/LibBlockHeader.sol"; +import { IErc721Bridge } from "../IErc721Bridge.sol"; +import { LibAddress } from "../../../libs/LibAddress.sol"; + +/** + * Stores message metadata on the Bridge. It's used to keep track of the state + * of messages that are being + * transferred across the bridge, and it contains functions to hash messages and + * check their status. + */ +library LibErc721BridgeData { + /// @dev The State struct stores the state of messages in the Bridge + /// contract. + struct State { + uint256 nextMessageId; + IErc721Bridge.Context ctx; // 3 slots + mapping(bytes32 msgHash => bool released) tokensReleased; + uint256[45] __gap; + } + + /// @dev StatusProof holds the block header and proof for a particular + /// status. + struct StatusProof { + BlockHeader header; + bytes proof; + } + + bytes32 internal constant MESSAGE_HASH_PLACEHOLDER = bytes32(uint256(1)); + uint256 internal constant CHAINID_PLACEHOLDER = type(uint256).max; + address internal constant SRC_CHAIN_SENDER_PLACEHOLDER = + address(uint160(uint256(1))); + + // Note: These events must match the ones defined in Bridge.sol. + event MessageSentErc721(bytes32 indexed msgHash, IErc721Bridge.Message message); + event DestChainEnabledErc721(uint256 indexed chainId, bool enabled); + + /** + * Calculate the keccak256 hash of the message + * @param message The message to be hashed + * @return msgHash The keccak256 hash of the message + */ + function hashMessage(IErc721Bridge.Message memory message) + internal + pure + returns (bytes32) + { + return keccak256(abi.encode(message)); + } +} diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol new file mode 100644 index 00000000000..848f27b622e --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeProcess.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { AddressResolver } from "../../../common/AddressResolver.sol"; +import { Erc721Vault } from "../../Erc721Vault.sol"; +import { IErc721Bridge } from "../IErc721Bridge.sol"; +import { ISignalService } from "../../../signal/ISignalService.sol"; +import { LibAddress } from "../../../libs/LibAddress.sol"; +import { LibErc721BridgeData } from "./LibErc721BridgeData.sol"; +import { LibErc721BridgeStatus } from "./LibErc721BridgeStatus.sol"; +import { LibMath } from "../../../libs/LibMath.sol"; + +/** + * This library provides functions for processing bridge messages on the + * destination chain. + */ +library LibErc721BridgeProcess { + using LibMath for uint256; + using LibAddress for address; + using LibErc721BridgeData for IErc721Bridge.Message; + using LibErc721BridgeData for LibErc721BridgeData.State; + + error ERC721_B_FORBIDDEN(); + error ERC721_B_SIGNAL_NOT_RECEIVED(); + error ERC721_B_STATUS_MISMATCH(); + error ERC721_B_WRONG_CHAIN_ID(); + + /** + * Process the bridge message on the destination chain. It can be called by + * any address, including `message.owner`. + * @dev It starts by hashing the message, + * and doing a lookup in the bridge state to see if the status is "NEW". + * It then does 2 things. Checks if Vault already has this NFT. If yes, then + * releases it (sends) if not it means it is a new bridging (so mints it). + * @param state The bridge state. + * @param resolver The address resolver. + * @param message The message to process. + * @param proof The msgHash proof from the source chain. + */ + function processMessage( + LibErc721BridgeData.State storage state, + AddressResolver resolver, + IErc721Bridge.Message calldata message, + bytes calldata proof + ) + internal + { + // If the gas limit is set to zero, only the owner can process the + // message. + if (message.gasLimit == 0 && msg.sender != message.owner) { + revert ERC721_B_FORBIDDEN(); + } + + if (message.destChainId != block.chainid) { + revert ERC721_B_WRONG_CHAIN_ID(); + } + + // The message status must be "NEW"; "RETRIABLE" is handled in + // LibBridgeRetry.sol. + bytes32 msgHash = message.hashMessage(); + if ( + LibErc721BridgeStatus.getMessageStatusErc721(msgHash) + != LibErc721BridgeStatus.MessageStatus.NEW + ) { + revert ERC721_B_STATUS_MISMATCH(); + } + // Message must have been "received" on the destChain (current chain) + address srcErc721Bridge = + resolver.resolve(message.srcChainId, "erc721_bridge", false); + + if ( + !ISignalService(resolver.resolve("signal_service", false)) + .isSignalReceived({ + srcChainId: message.srcChainId, + app: srcErc721Bridge, + signal: msgHash, + proof: proof + }) + ) { + revert ERC721_B_SIGNAL_NOT_RECEIVED(); + } + + // Release tokens needs to have the mechanism to resolve the addresses + // and deploy modified erc721 contracts if needed (with tokenURI) + address tokenVault = resolver.resolve("erc721_vault", true); + if (tokenVault != address(0)) { + Erc721Vault(tokenVault).releaseTokens( + message.owner, + message.tokenContract, + message.tokenIds + ); + } + + // Mark the status as DONE (otherwise reverts anyways) + LibErc721BridgeStatus.updateMessageStatusErc721(msgHash, LibErc721BridgeStatus.MessageStatus.DONE); + } +} diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol new file mode 100644 index 00000000000..5180c701e0f --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeSend.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { IERC721Upgradeable} from + "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; +import { AddressResolver } from "../../../common/AddressResolver.sol"; +import { IErc721Bridge } from "../IErc721Bridge.sol"; +import { ISignalService } from "../../../signal/ISignalService.sol"; +import { LibAddress } from "../../../libs/LibAddress.sol"; +import { LibErc721BridgeData } from "./LibErc721BridgeData.sol"; + +/** + * Entry point for starting a bridge transaction. + */ +library LibErc721BridgeSend { + using LibAddress for address; + using LibErc721BridgeData for IErc721Bridge.Message; + + error B_INCORRECT_VALUE(); + error ERC721_B_OWNER_IS_NULL(); + error ERC721_B_WRONG_CHAIN_ID(); + error ERC721_B_WRONG_TO_ADDRESS(); + + /** + * Send a message to the Bridge with the details of the request. + * @dev The Bridge takes custody of the funds, unless the source chain is + * Taiko, + * in which case the funds are sent to and managed by the EtherVault. + * @param state The current state of the Bridge + * @param resolver The address resolver + * @param message Specifies the `depositValue`, `callValue`, and + * `processingFee`. + * These must sum to `msg.value`. It also specifies the `destChainId` + * which must have a `bridge` address set on the AddressResolver and + * differ from the current chain ID. + * @return msgHash The hash of the sent message. + * This is picked up by an off-chain relayer which indicates + * a bridge message has been sent and is ready to be processed on the + * destination chain. + */ + function sendMessageErc721( + LibErc721BridgeData.State storage state, + AddressResolver resolver, + IErc721Bridge.Message memory message + ) + internal + returns (bytes32 msgHash) + { + if (message.owner == address(0)) { + revert ERC721_B_OWNER_IS_NULL(); + } + + (bool destChainEnabled, address destChain) = + isDestChainEnabled(resolver, message.destChainId); + + if (!destChainEnabled || message.destChainId == block.chainid) { + revert ERC721_B_WRONG_CHAIN_ID(); + } + if (message.to == address(0) || message.to == destChain) { + revert ERC721_B_WRONG_TO_ADDRESS(); + } + + // Send tokens to vault + address tokenVault = resolver.resolve("erc721_vault", true); + + // User has to accept address(this) to transfer NFT tokens on behalf + for (uint256 i; i < message.tokenIds.length; i++) { + IERC721Upgradeable(message.tokenContract).safeTransferFrom( + message.owner, tokenVault, message.tokenIds[i]); + } + + message.id = state.nextMessageId++; + message.sender = msg.sender; + message.srcChainId = block.chainid; + + msgHash = message.hashMessage(); + // Store a key which is the hash of this contract address and the + // msgHash, with a value of 1. + ISignalService(resolver.resolve("signal_service", false)).sendSignal( + msgHash + ); + emit LibErc721BridgeData.MessageSentErc721(msgHash, message); + } + + /** + * Check if the destination chain is enabled. + * @param resolver The address resolver + * @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( + AddressResolver resolver, + uint256 chainId + ) + internal + view + returns (bool enabled, address destBridge) + { + destBridge = resolver.resolve(chainId, "erc721_bridge", true); + enabled = destBridge != address(0); + } + + /** + * Check if the message was sent. + * @param resolver The address resolver + * @param msgHash The hash of the sent message + * @return True if the message was sent + */ + function isMessageSentErc721( + AddressResolver resolver, + bytes32 msgHash + ) + internal + view + returns (bool) + { + return ISignalService(resolver.resolve("signal_service", false)) + .isSignalSent({ app: address(this), signal: msgHash }); + } + + /** + * Check if the message was received. + * @param resolver The address resolver + * @param msgHash The hash of the received message + * @param srcChainId The id of the source chain + * @param proof The proof of message receipt + * @return True if the message was received + */ + function isMessageReceivedErc721( + AddressResolver resolver, + bytes32 msgHash, + uint256 srcChainId, + bytes calldata proof + ) + internal + view + returns (bool) + { + address srcBridge = resolver.resolve(srcChainId, "bridge", false); + return ISignalService(resolver.resolve("signal_service", false)) + .isSignalReceived({ + srcChainId: srcChainId, + app: srcBridge, + signal: msgHash, + proof: proof + }); + } +} diff --git a/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol new file mode 100644 index 00000000000..d2580a963e6 --- /dev/null +++ b/packages/protocol/contracts/nft_bridge/erc721/libs/LibErc721BridgeStatus.sol @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT +// _____ _ _ _ _ +// |_ _|_ _(_) |_____ | | __ _| |__ ___ +// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< +// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ + +pragma solidity ^0.8.20; + +import { AddressResolver } from "../../../common/AddressResolver.sol"; +import { BlockHeader, LibBlockHeader } from "../../../libs/LibBlockHeader.sol"; +import { ICrossChainSync } from "../../../common/ICrossChainSync.sol"; +import { LibErc721BridgeData } from "./LibErc721BridgeData.sol"; +import { LibTrieProof } from "../../../libs/LibTrieProof.sol"; + +/** + * This library provides functions to get and update the status of bridge + * messages. + */ +library LibErc721BridgeStatus { + using LibBlockHeader for BlockHeader; + + enum MessageStatus { + NEW, + RETRIABLE, + DONE, + FAILED + } + + event MessageStatusChangedErc721( + bytes32 indexed msgHash, MessageStatus status, address transactor + ); + + error B_MSG_HASH_NULL(); + error B_WRONG_CHAIN_ID(); + + /** + * Updates the status of a bridge message. + * @dev If messageStatus is same as in the messageStatus mapping, does + * nothing. + * @param msgHash The hash of the message. + * @param status The new status of the message. + */ + function updateMessageStatusErc721( + bytes32 msgHash, + MessageStatus status + ) + internal + { + if (getMessageStatusErc721(msgHash) != status) { + _setMessageStatus(msgHash, status); + emit MessageStatusChangedErc721(msgHash, status, msg.sender); + } + } + + /** + * Gets the status of a bridge message. + * @param msgHash The hash of the message. + * @return The status of the message. + */ + function getMessageStatusErc721(bytes32 msgHash) + internal + view + returns (MessageStatus) + { + bytes32 slot = getMessageStatusSlot(msgHash); + uint256 value; + assembly { + value := sload(slot) + } + return MessageStatus(value); + } + + /** + * Checks if a bridge message has failed. + * @param resolver The address resolver. + * @param msgHash The hash of the message. + * @param destChainId The ID of the destination chain. + * @param proof The proof of the status of the message. + * @return True if the message has failed, false otherwise. + */ + function isMessageFailedErc721( + AddressResolver resolver, + bytes32 msgHash, + uint256 destChainId, + bytes calldata proof + ) + internal + view + returns (bool) + { + if (destChainId == block.chainid) { + revert B_WRONG_CHAIN_ID(); + } + if (msgHash == 0x0) { + revert B_MSG_HASH_NULL(); + } + + LibErc721BridgeData.StatusProof memory sp = + abi.decode(proof, (LibErc721BridgeData.StatusProof)); + + bytes32 syncedHeaderHash = ICrossChainSync( + resolver.resolve("taiko", false) + ).getCrossChainBlockHash(sp.header.height); + + if ( + syncedHeaderHash == 0 + || syncedHeaderHash != sp.header.hashBlockHeader() + ) { + return false; + } + + return LibTrieProof.verifyWithAccountProof({ + stateRoot: sp.header.stateRoot, + addr: resolver.resolve(destChainId, "bridge", false), + slot: getMessageStatusSlot(msgHash), + value: bytes32(uint256(LibErc721BridgeStatus.MessageStatus.FAILED)), + mkproof: sp.proof + }); + } + /** + * Gets the storage slot for a bridge message status. + * @param msgHash The hash of the message. + * @return The storage slot for the message status. + */ + + function getMessageStatusSlot(bytes32 msgHash) + internal + pure + returns (bytes32) + { + return keccak256(bytes.concat(bytes("MESSAGE_STATUS"), msgHash)); + } + + /** + * Sets the status of a bridge message. + * @param msgHash The hash of the message. + * @param status The new status of the message. + */ + function _setMessageStatus(bytes32 msgHash, MessageStatus status) private { + bytes32 slot = getMessageStatusSlot(msgHash); + uint256 value = uint256(status); + assembly { + sstore(slot, value) + } + } +}