-
Notifications
You must be signed in to change notification settings - Fork 12.4k
ERC-7786 based crosschain bridge for ERC-721 tokens #6259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Amxx
wants to merge
17
commits into
OpenZeppelin:master
Choose a base branch
from
Amxx:crosschain/erc721bridge
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+666
−143
Open
Changes from 16 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
9535015
Add crosschain bridging logic for ERC721
Amxx 769398e
update
Amxx adddbc4
Apply suggestions from code review
Amxx e52226c
Apply suggestions from code review
Amxx 4c6ee7e
changeset entries
Amxx 42ffea0
Merge branch 'crosschain/erc721bridge' of https://github.com/Amxx/ope…
Amxx 7a0db4e
slither
Amxx 07f5812
coverage
Amxx d756c5c
slither
Amxx 551a3c7
documentation
Amxx 9f3a5e4
Apply suggestion from @Amxx
Amxx 4355ab0
refactor flow
Amxx 6c50eaf
Remove the IERC721Receiver flow to avoid risks associated to hiden da…
Amxx f490550
Apply suggestions from code review
Amxx 88faa8e
use transferFrom in
Amxx 86ef787
update docs
Amxx ade7c92
Apply suggestions from code review
Amxx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `BridgeERC721Core` and `BridgeERC721`: Added bridge contracts to handle crosschain movements of ERC-721 tokens. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'openzeppelin-solidity': minor | ||
| --- | ||
|
|
||
| `ERC721Crosschain`: Added an ERC-721 extension to embed an ERC-7786 based crosschain bridge directly in the token contract. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.26; | ||
|
|
||
| import {IERC721} from "../../interfaces/IERC721.sol"; | ||
| import {IERC721Errors} from "../../interfaces/draft-IERC6093.sol"; | ||
| import {BridgeERC721Core} from "./BridgeERC721Core.sol"; | ||
|
|
||
| /** | ||
| * @dev This is a variant of {BridgeERC721Core} that implements the bridge logic for ERC-721 tokens that do not expose | ||
| * a crosschain mint and burn mechanism. Instead, it takes custody of bridged assets. | ||
| */ | ||
| // slither-disable-next-line locked-ether | ||
| abstract contract BridgeERC721 is BridgeERC721Core { | ||
| IERC721 private immutable _token; | ||
|
|
||
| error BridgeERC721Unauthorized(address caller); | ||
|
|
||
| constructor(IERC721 token_) { | ||
| _token = token_; | ||
| } | ||
|
|
||
| /// @dev Return the address of the ERC721 token this bridge operates on. | ||
| function token() public view virtual returns (IERC721) { | ||
| return _token; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Transfer `tokenId` from `from` (on this chain) to `to` (on a different chain). | ||
| * | ||
| * The `to` parameter is the full InteroperableAddress that references both the destination chain and the account | ||
| . on that chain. Similarly to the underlying token's {ERC721-transferFrom} function, this function can be called | ||
| * either by the token holder or by anyone that is approved by the token holder. It reuses the token's allowance | ||
| * system, meaning that an account that is "approved for all" or "approved for tokenId" can perform the crosschain | ||
| * transfer directly without having to take temporary custody of the token. | ||
| */ | ||
| function crosschainTransferFrom(address from, bytes memory to, uint256 tokenId) public virtual returns (bytes32) { | ||
| // Permission is handled using the ERC721's allowance system. This check replicates `ERC721._isAuthorized`. | ||
| address spender = _msgSender(); | ||
| require( | ||
| from == spender || token().isApprovedForAll(from, spender) || token().getApproved(tokenId) == spender, | ||
| IERC721Errors.ERC721InsufficientApproval(spender, tokenId) | ||
| ); | ||
|
|
||
| // This call verifies that `from` is the owner of `tokenId` (in `_onSend`), and the previous checks ensure | ||
| // that `spender` is allowed to move tokenId on behalf of `from`. | ||
| // | ||
| // Perform the crosschain transfer and return the send id | ||
| return _crosschainTransfer(from, to, tokenId); | ||
| } | ||
|
|
||
| /// @dev "Locking" tokens is done by taking custody | ||
| function _onSend(address from, uint256 tokenId) internal virtual override { | ||
| // slither-disable-next-line arbitrary-send-erc20 | ||
| token().transferFrom(from, address(this), tokenId); | ||
| } | ||
|
|
||
| /// @dev "Unlocking" tokens is done by releasing custody | ||
| function _onReceive(address to, uint256 tokenId) internal virtual override { | ||
| // slither-disable-next-line arbitrary-send-erc20 | ||
| token().transferFrom(address(this), to, tokenId); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.26; | ||
|
|
||
| import {InteroperableAddress} from "../../utils/draft-InteroperableAddress.sol"; | ||
| import {Context} from "../../utils/Context.sol"; | ||
| import {ERC7786Recipient} from "../ERC7786Recipient.sol"; | ||
| import {CrosschainLinked} from "../CrosschainLinked.sol"; | ||
|
|
||
| /** | ||
| * @dev Base contract for bridging ERC-721 between chains using an ERC-7786 gateway. | ||
| * | ||
| * In order to use this contract, two functions must be implemented to link it to the token: | ||
| * * {_onSend}: called when a crosschain transfer is going out. Must take the sender tokens or revert. | ||
| * * {_onReceive}: called when a crosschain transfer is coming in. Must give tokens to the receiver. | ||
| * | ||
| * This base contract is used by the {BridgeERC721}, which interfaces with legacy ERC-721 tokens. It is also used by | ||
| * the {ERC721Crosschain} extension, which embeds the bridge logic directly in the token contract. | ||
| */ | ||
| abstract contract BridgeERC721Core is Context, CrosschainLinked { | ||
| using InteroperableAddress for bytes; | ||
|
|
||
| event CrosschainERC721TransferSent(bytes32 indexed sendId, address indexed from, bytes to, uint256 tokenId); | ||
| event CrosschainERC721TransferReceived(bytes32 indexed receiveId, bytes from, address indexed to, uint256 tokenId); | ||
|
|
||
| /** | ||
| * @dev Internal crosschain transfer function. | ||
| * | ||
| * Note: The `to` parameter is the full InteroperableAddress (chain ref + address). | ||
| */ | ||
| function _crosschainTransfer(address from, bytes memory to, uint256 tokenId) internal virtual returns (bytes32) { | ||
| _onSend(from, tokenId); | ||
|
|
||
| (bytes2 chainType, bytes memory chainReference, bytes memory addr) = to.parseV1(); | ||
| bytes memory chain = InteroperableAddress.formatV1(chainType, chainReference, hex""); | ||
|
|
||
| bytes32 sendId = _sendMessageToCounterpart( | ||
| chain, | ||
| abi.encode(InteroperableAddress.formatEvmV1(block.chainid, from), addr, tokenId), | ||
| new bytes[](0) | ||
| ); | ||
|
|
||
| emit CrosschainERC721TransferSent(sendId, from, to, tokenId); | ||
|
|
||
| return sendId; | ||
| } | ||
|
|
||
| /// @inheritdoc ERC7786Recipient | ||
| function _processMessage( | ||
| address /*gateway*/, | ||
| bytes32 receiveId, | ||
| bytes calldata /*sender*/, | ||
| bytes calldata payload | ||
| ) internal virtual override { | ||
| // split payload | ||
| (bytes memory from, bytes memory toBinary, uint256 tokenId) = abi.decode(payload, (bytes, bytes, uint256)); | ||
| address to = address(bytes20(toBinary)); | ||
|
|
||
| _onReceive(to, tokenId); | ||
|
|
||
| emit CrosschainERC721TransferReceived(receiveId, from, to, tokenId); | ||
| } | ||
|
|
||
| /// @dev Virtual function: implementation is required to handle token being burnt or locked on the source chain. | ||
| function _onSend(address from, uint256 tokenId) internal virtual; | ||
|
|
||
| /// @dev Virtual function: implementation is required to handle token being minted or unlocked on the destination chain. | ||
| function _onReceive(address to, uint256 tokenId) internal virtual; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.26; | ||
|
|
||
| import {ERC721} from "../ERC721.sol"; | ||
| import {BridgeERC721Core} from "../../../crosschain/bridges/BridgeERC721Core.sol"; | ||
|
|
||
| /** | ||
| * @dev Extension of {ERC721} that makes it natively cross-chain using the ERC-7786 based {BridgeERC721Core}. | ||
| * | ||
| * This extension makes the token compatible with: | ||
| * * {ERC721Crosschain} instances on other chains, | ||
| * * {ERC721} instances on other chains that are bridged using {BridgeERC721}, | ||
| */ | ||
| // slither-disable-next-line locked-ether | ||
| abstract contract ERC721Crosschain is ERC721, BridgeERC721Core { | ||
| /// @dev Crosschain variant of {transferFrom}, using the allowance system from the underlying ERC-721 token. | ||
| function crosschainTransferFrom(address from, bytes memory to, uint256 tokenId) public virtual returns (bytes32) { | ||
| // operator (_msgSender) permission over `from` is checked in `_onSend` | ||
| return _crosschainTransfer(from, to, tokenId); | ||
| } | ||
|
|
||
| /// @dev "Locking" tokens is achieved through burning | ||
| function _onSend(address from, uint256 tokenId) internal virtual override { | ||
| address previousOwner = _update(address(0), tokenId, _msgSender()); | ||
frangio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (previousOwner == address(0)) { | ||
| revert ERC721NonexistentToken(tokenId); | ||
| } else if (previousOwner != from) { | ||
| revert ERC721IncorrectOwner(from, tokenId, previousOwner); | ||
| } | ||
| } | ||
|
|
||
| /// @dev "Unlocking" tokens is achieved through minting | ||
| function _onReceive(address to, uint256 tokenId) internal virtual override { | ||
| _mint(to, tokenId); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.