Skip to content

Commit d8c559c

Browse files
authored
ERC-7786 based crosschain bridge for ERC-1155 tokens (#6281)
1 parent 8ff78ff commit d8c559c

File tree

11 files changed

+744
-3
lines changed

11 files changed

+744
-3
lines changed

.changeset/blue-jars-lay.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`ERC1155Crosschain`: Added an ERC-1155 extension to embed an ERC-7786 based crosschain bridge directly in the token contract.

.changeset/sweet-houses-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openzeppelin-solidity': minor
3+
---
4+
5+
`BridgeMultiToken` and `BridgeERC1155`: Added bridge contracts to handle crosschain movements of ERC-1155 tokens.

contracts/crosschain/README.adoc

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Additionally there are multiple bridge constructions:
1313
* {BridgeFungible}: Core bridging logic for crosschain ERC-20 transfer. Used by {BridgeERC20}, {BridgeERC7802} and {ERC20Crosschain},
1414
* {BridgeERC20}: Standalone bridge contract to connect an ERC-20 token contract with counterparts on remote chains,
1515
* {BridgeERC7802}: Standalone bridge contract to connect an ERC-7802 token contract with counterparts on remote chains.
16+
* {BridgeMultiToken}: Core bridging logic for crosschain ERC-1155 transfer. Used by {BridgeERC1155} and {ERC1155Crosschain},
17+
* {BridgeERC1155}: Standalone bridge contract to connect an ERC-1155 token contract with counterparts on remote chains,
1618
1719
== Helpers
1820

@@ -26,4 +28,8 @@ Additionally there are multiple bridge constructions:
2628

2729
{{BridgeERC20}}
2830

29-
{{BridgeERC7802}}
31+
{{BridgeERC7802}}
32+
33+
{{BridgeMultiToken}}
34+
35+
{{BridgeERC1155}}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.26;
4+
5+
import {IERC1155} from "../../interfaces/IERC1155.sol";
6+
import {IERC1155Receiver} from "../../interfaces/IERC1155Receiver.sol";
7+
import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol";
8+
import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol";
9+
import {BridgeMultiToken} from "./abstract/BridgeMultiToken.sol";
10+
11+
/**
12+
* @dev This is a variant of {BridgeMultiToken} that implements the bridge logic for ERC-1155 tokens that do not expose
13+
* a crosschain mint and burn mechanism. Instead, it takes custody of bridged assets.
14+
*/
15+
// slither-disable-next-line locked-ether
16+
abstract contract BridgeERC1155 is BridgeMultiToken, ERC1155Holder {
17+
IERC1155 private immutable _token;
18+
19+
constructor(IERC1155 token_) {
20+
_token = token_;
21+
}
22+
23+
/// @dev Return the address of the ERC1155 token this bridge operates on.
24+
function token() public view virtual returns (IERC1155) {
25+
return _token;
26+
}
27+
28+
/**
29+
* @dev Transfer `amount` tokens to a crosschain receiver.
30+
*
31+
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
32+
*/
33+
function crosschainTransferFrom(address from, bytes memory to, uint256 id, uint256 value) public returns (bytes32) {
34+
uint256[] memory ids = new uint256[](1);
35+
uint256[] memory values = new uint256[](1);
36+
ids[0] = id;
37+
values[0] = value;
38+
39+
return crosschainTransferFrom(from, to, ids, values);
40+
}
41+
42+
/**
43+
* @dev Transfer `amount` tokens to a crosschain receiver.
44+
*
45+
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
46+
*/
47+
function crosschainTransferFrom(
48+
address from,
49+
bytes memory to,
50+
uint256[] memory ids,
51+
uint256[] memory values
52+
) public virtual returns (bytes32) {
53+
// Permission is handled using the ERC1155's allowance system. This check replicates `ERC1155._checkAuthorized`.
54+
address spender = _msgSender();
55+
require(
56+
from == spender || token().isApprovedForAll(from, spender),
57+
IERC1155Errors.ERC1155MissingApprovalForAll(spender, from)
58+
);
59+
60+
// Perform the crosschain transfer and return the handler
61+
return _crosschainTransfer(from, to, ids, values);
62+
}
63+
64+
/// @dev "Locking" tokens is done by taking custody
65+
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual override {
66+
token().safeBatchTransferFrom(from, address(this), ids, values, "");
67+
}
68+
69+
/// @dev "Unlocking" tokens is done by releasing custody
70+
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
71+
token().safeBatchTransferFrom(address(this), to, ids, values, "");
72+
}
73+
74+
/// @dev Support receiving tokens only if the transfer was initiated by the bridge itself.
75+
function onERC1155Received(
76+
address operator,
77+
address /* from */,
78+
uint256 /* id */,
79+
uint256 /* value */,
80+
bytes memory /* data */
81+
) public virtual override returns (bytes4) {
82+
return
83+
msg.sender == address(_token) && operator == address(this)
84+
? IERC1155Receiver.onERC1155Received.selector
85+
: bytes4(0);
86+
}
87+
88+
/// @dev Support receiving tokens only if the transfer was initiated by the bridge itself.
89+
function onERC1155BatchReceived(
90+
address operator,
91+
address /* from */,
92+
uint256[] memory /* ids */,
93+
uint256[] memory /* values */,
94+
bytes memory /* data */
95+
) public virtual override returns (bytes4) {
96+
return
97+
msg.sender == address(_token) && operator == address(this)
98+
? IERC1155Receiver.onERC1155BatchReceived.selector
99+
: bytes4(0);
100+
}
101+
}

contracts/crosschain/bridges/abstract/BridgeFungible.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ abstract contract BridgeFungible is Context, CrosschainLinked {
6262
bytes calldata /*sender*/,
6363
bytes calldata payload
6464
) internal virtual override {
65+
// NOTE: Gateway is validated by {_isAuthorizedGateway} (implemented in {CrosschainLinked}). No need to check here.
66+
6567
// split payload
6668
(bytes memory from, bytes memory toBinary, uint256 amount) = abi.decode(payload, (bytes, bytes, uint256));
6769
address to = address(bytes20(toBinary));
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.26;
4+
5+
import {InteroperableAddress} from "../../../utils/draft-InteroperableAddress.sol";
6+
import {Context} from "../../../utils/Context.sol";
7+
import {ERC7786Recipient} from "../../ERC7786Recipient.sol";
8+
import {CrosschainLinked} from "../../CrosschainLinked.sol";
9+
10+
/**
11+
* @dev Base contract for bridging ERC-1155 between chains using an ERC-7786 gateway.
12+
*
13+
* In order to use this contract, two functions must be implemented to link it to the token:
14+
* * {_onSend}: called when a crosschain transfer is going out. Must take the sender tokens or revert.
15+
* * {_onReceive}: called when a crosschain transfer is coming in. Must give tokens to the receiver.
16+
*
17+
* This base contract is used by the {BridgeERC1155}, which interfaces with legacy ERC-1155 tokens. It is also used by
18+
* the {ERC1155Crosschain} extension, which embeds the bridge logic directly in the token contract.
19+
*
20+
* This base contract implements the crosschain transfer operation though internal functions. It is for the the "child
21+
* contracts" that inherit from this to implement the external interfaces and make this functions accessible.
22+
*/
23+
abstract contract BridgeMultiToken is Context, CrosschainLinked {
24+
using InteroperableAddress for bytes;
25+
26+
event CrosschainMultiTokenTransferSent(
27+
bytes32 indexed sendId,
28+
address indexed from,
29+
bytes to,
30+
uint256[] ids,
31+
uint256[] values
32+
);
33+
event CrosschainMultiTokenTransferReceived(
34+
bytes32 indexed receiveId,
35+
bytes from,
36+
address indexed to,
37+
uint256[] ids,
38+
uint256[] values
39+
);
40+
/**
41+
* @dev Internal crosschain transfer function.
42+
*
43+
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
44+
*/
45+
function _crosschainTransfer(
46+
address from,
47+
bytes memory to,
48+
uint256[] memory ids,
49+
uint256[] memory values
50+
) internal virtual returns (bytes32) {
51+
_onSend(from, ids, values);
52+
53+
(bytes2 chainType, bytes memory chainReference, bytes memory addr) = to.parseV1();
54+
bytes memory chain = InteroperableAddress.formatV1(chainType, chainReference, hex"");
55+
56+
bytes32 sendId = _sendMessageToCounterpart(
57+
chain,
58+
abi.encode(InteroperableAddress.formatEvmV1(block.chainid, from), addr, ids, values),
59+
new bytes[](0)
60+
);
61+
62+
emit CrosschainMultiTokenTransferSent(sendId, from, to, ids, values);
63+
return sendId;
64+
}
65+
66+
/// @inheritdoc ERC7786Recipient
67+
function _processMessage(
68+
address /*gateway*/,
69+
bytes32 receiveId,
70+
bytes calldata /*sender*/,
71+
bytes calldata payload
72+
) internal virtual override {
73+
// NOTE: Gateway is validated by {_isAuthorizedGateway} (implemented in {CrosschainLinked}). No need to check here.
74+
75+
// split payload
76+
(bytes memory from, bytes memory toEvm, uint256[] memory ids, uint256[] memory values) = abi.decode(
77+
payload,
78+
(bytes, bytes, uint256[], uint256[])
79+
);
80+
address to = address(bytes20(toEvm));
81+
82+
_onReceive(to, ids, values);
83+
84+
emit CrosschainMultiTokenTransferReceived(receiveId, from, to, ids, values);
85+
}
86+
87+
/// @dev Virtual function: implementation is required to handle token being burnt or locked on the source chain.
88+
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual;
89+
90+
/// @dev Virtual function: implementation is required to handle token being minted or unlocked on the destination chain.
91+
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual;
92+
}

contracts/token/ERC1155/README.adoc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Additionally there are multiple custom extensions, including:
1313

1414
* designation of addresses that can pause token transfers for all users ({ERC1155Pausable}).
1515
* destruction of own tokens ({ERC1155Burnable}).
16+
* crosschain bridging of tokens through ERC-7786 gateways ({ERC1155Crosschain}).
1617
1718
NOTE: This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC-1155 (such as <<ERC1155-_mint-address-uint256-uint256-bytes-,`_mint`>>) and expose them as external functions in the way they prefer.
1819

@@ -28,10 +29,12 @@ NOTE: This core set of contracts is designed to be unopinionated, allowing devel
2829

2930
== Extensions
3031

31-
{{ERC1155Pausable}}
32-
3332
{{ERC1155Burnable}}
3433

34+
{{ERC1155Crosschain}}
35+
36+
{{ERC1155Pausable}}
37+
3538
{{ERC1155Supply}}
3639

3740
{{ERC1155URIStorage}}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.26;
4+
5+
import {ERC1155} from "../ERC1155.sol";
6+
import {BridgeMultiToken} from "../../../crosschain/bridges/abstract/BridgeMultiToken.sol";
7+
8+
/**
9+
* @dev Extension of {ERC1155} that makes it natively cross-chain using the ERC-7786 based {BridgeMultiToken}.
10+
*
11+
* This extension makes the token compatible with:
12+
* * {ERC1155Crosschain} instances on other chains,
13+
* * {ERC1155} instances on other chains that are bridged using {BridgeERC1155},
14+
*/
15+
// slither-disable-next-line locked-ether
16+
abstract contract ERC1155Crosschain is ERC1155, BridgeMultiToken {
17+
/// @dev TransferFrom variant of {crosschainTransferFrom}, using ERC1155 allowance from the sender to the caller.
18+
function crosschainTransferFrom(
19+
address from,
20+
bytes memory to,
21+
uint256 id,
22+
uint256 value
23+
) public virtual returns (bytes32) {
24+
_checkAuthorized(_msgSender(), from);
25+
26+
uint256[] memory ids = new uint256[](1);
27+
uint256[] memory values = new uint256[](1);
28+
ids[0] = id;
29+
values[0] = value;
30+
return _crosschainTransfer(from, to, ids, values);
31+
}
32+
33+
/// @dev TransferFrom variant of {crosschainTransferFrom}, using ERC1155 allowance from the sender to the caller.
34+
function crosschainTransferFrom(
35+
address from,
36+
bytes memory to,
37+
uint256[] memory ids,
38+
uint256[] memory values
39+
) public virtual returns (bytes32) {
40+
_checkAuthorized(_msgSender(), from);
41+
return _crosschainTransfer(from, to, ids, values);
42+
}
43+
44+
/// @dev "Locking" tokens is achieved through burning
45+
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual override {
46+
_burnBatch(from, ids, values);
47+
}
48+
49+
/// @dev "Unlocking" tokens is achieved through minting
50+
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
51+
_mintBatch(to, ids, values, "");
52+
}
53+
}

0 commit comments

Comments
 (0)