Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/blue-jars-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`ERC1155Crosschain`: Added an ERC-1155 extension to embed an ERC-7786 based crosschain bridge directly in the token contract.
5 changes: 5 additions & 0 deletions .changeset/sweet-houses-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openzeppelin-solidity': minor
---

`BridgeMultiToken` and `BridgeERC1155`: Added bridge contracts to handle crosschain movements of ERC-1155 tokens.
8 changes: 7 additions & 1 deletion contracts/crosschain/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ Additionally there are multiple bridge constructions:
* {BridgeFungible}: Core bridging logic for crosschain ERC-20 transfer. Used by {BridgeERC20}, {BridgeERC7802} and {ERC20Crosschain},
* {BridgeERC20}: Standalone bridge contract to connect an ERC-20 token contract with counterparts on remote chains,
* {BridgeERC7802}: Standalone bridge contract to connect an ERC-7802 token contract with counterparts on remote chains.
* {BridgeMultiToken}: Core bridging logic for crosschain ERC-1155 transfer. Used by {BridgeERC1155} and {ERC1155Crosschain},
* {BridgeERC1155}: Standalone bridge contract to connect an ERC-1155 token contract with counterparts on remote chains,

== Helpers

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

{{BridgeERC20}}

{{BridgeERC7802}}
{{BridgeERC7802}}

{{BridgeMultiToken}}

{{BridgeERC1155}}
101 changes: 101 additions & 0 deletions contracts/crosschain/bridges/BridgeERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import {IERC1155} from "../../interfaces/IERC1155.sol";
import {IERC1155Receiver} from "../../interfaces/IERC1155Receiver.sol";
import {IERC1155Errors} from "../../interfaces/draft-IERC6093.sol";
import {ERC1155Holder} from "../../token/ERC1155/utils/ERC1155Holder.sol";
import {BridgeMultiToken} from "./abstract/BridgeMultiToken.sol";

/**
* @dev This is a variant of {BridgeMultiToken} that implements the bridge logic for ERC-1155 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 BridgeERC1155 is BridgeMultiToken, ERC1155Holder {
IERC1155 private immutable _token;

constructor(IERC1155 token_) {
_token = token_;
}

/// @dev Return the address of the ERC1155 token this bridge operates on.
function token() public view virtual returns (IERC1155) {
return _token;
}

/**
* @dev Transfer `amount` tokens to a crosschain receiver.
*
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
*/
function crosschainTransferFrom(address from, bytes memory to, uint256 id, uint256 value) public returns (bytes32) {
uint256[] memory ids = new uint256[](1);
uint256[] memory values = new uint256[](1);
ids[0] = id;
values[0] = value;

return crosschainTransferFrom(from, to, ids, values);
}

/**
* @dev Transfer `amount` tokens to a crosschain receiver.
*
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
*/
function crosschainTransferFrom(
address from,
bytes memory to,
uint256[] memory ids,
uint256[] memory values
) public virtual returns (bytes32) {
// Permission is handled using the ERC1155's allowance system. This check replicates `ERC1155._checkAuthorized`.
address spender = _msgSender();
require(
from == spender || token().isApprovedForAll(from, spender),
IERC1155Errors.ERC1155MissingApprovalForAll(spender, from)
);

// Perform the crosschain transfer and return the handler
return _crosschainTransfer(from, to, ids, values);
}

/// @dev "Locking" tokens is done by taking custody
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual override {
token().safeBatchTransferFrom(from, address(this), ids, values, "");
}

/// @dev "Unlocking" tokens is done by releasing custody
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
token().safeBatchTransferFrom(address(this), to, ids, values, "");
}

/// @dev Support receiving tokens only if the transfer was initiated by the bridge itself.
function onERC1155Received(
address operator,
address /* from */,
uint256 /* id */,
uint256 /* value */,
bytes memory /* data */
) public virtual override returns (bytes4) {
return
msg.sender == address(_token) && operator == address(this)
? IERC1155Receiver.onERC1155Received.selector
: bytes4(0);
}

/// @dev Support receiving tokens only if the transfer was initiated by the bridge itself.
function onERC1155BatchReceived(
address operator,
address /* from */,
uint256[] memory /* ids */,
uint256[] memory /* values */,
bytes memory /* data */
) public virtual override returns (bytes4) {
return
msg.sender == address(_token) && operator == address(this)
? IERC1155Receiver.onERC1155BatchReceived.selector
: bytes4(0);
}
}
2 changes: 2 additions & 0 deletions contracts/crosschain/bridges/abstract/BridgeFungible.sol
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ abstract contract BridgeFungible is Context, CrosschainLinked {
bytes calldata /*sender*/,
bytes calldata payload
) internal virtual override {
// NOTE: Gateway is validated by {_isAuthorizedGateway} (implemented in {CrosschainLinked}). No need to check here.

// split payload
(bytes memory from, bytes memory toBinary, uint256 amount) = abi.decode(payload, (bytes, bytes, uint256));
address to = address(bytes20(toBinary));
Expand Down
92 changes: 92 additions & 0 deletions contracts/crosschain/bridges/abstract/BridgeMultiToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// 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-1155 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 {BridgeERC1155}, which interfaces with legacy ERC-1155 tokens. It is also used by
* the {ERC1155Crosschain} extension, which embeds the bridge logic directly in the token contract.
*
* This base contract implements the crosschain transfer operation though internal functions. It is for the the "child
* contracts" that inherit from this to implement the external interfaces and make this functions accessible.
*/
abstract contract BridgeMultiToken is Context, CrosschainLinked {
using InteroperableAddress for bytes;

event CrosschainMultiTokenTransferSent(
bytes32 indexed sendId,
address indexed from,
bytes to,
uint256[] ids,
uint256[] values
);
event CrosschainMultiTokenTransferReceived(
bytes32 indexed receiveId,
bytes from,
address indexed to,
uint256[] ids,
uint256[] values
);
/**
* @dev Internal crosschain transfer function.
*
* Note: The `to` parameter is the full InteroperableAddress (chain ref + address).
*/
function _crosschainTransfer(
address from,
bytes memory to,
uint256[] memory ids,
uint256[] memory values
) internal virtual returns (bytes32) {
_onSend(from, ids, values);

(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, ids, values),
new bytes[](0)
);

emit CrosschainMultiTokenTransferSent(sendId, from, to, ids, values);
return sendId;
}

/// @inheritdoc ERC7786Recipient
function _processMessage(
address /*gateway*/,
bytes32 receiveId,
bytes calldata /*sender*/,
bytes calldata payload
) internal virtual override {
// NOTE: Gateway is validated by {_isAuthorizedGateway} (implemented in {CrosschainLinked}). No need to check here.

// split payload
(bytes memory from, bytes memory toEvm, uint256[] memory ids, uint256[] memory values) = abi.decode(
payload,
(bytes, bytes, uint256[], uint256[])
);
address to = address(bytes20(toEvm));

_onReceive(to, ids, values);

emit CrosschainMultiTokenTransferReceived(receiveId, from, to, ids, values);
}

/// @dev Virtual function: implementation is required to handle token being burnt or locked on the source chain.
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual;

/// @dev Virtual function: implementation is required to handle token being minted or unlocked on the destination chain.
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual;
}
7 changes: 5 additions & 2 deletions contracts/token/ERC1155/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Additionally there are multiple custom extensions, including:

* designation of addresses that can pause token transfers for all users ({ERC1155Pausable}).
* destruction of own tokens ({ERC1155Burnable}).
* crosschain bridging of tokens through ERC-7786 gateways ({ERC1155Crosschain}).

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.

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

== Extensions

{{ERC1155Pausable}}

{{ERC1155Burnable}}

{{ERC1155Crosschain}}

{{ERC1155Pausable}}

{{ERC1155Supply}}

{{ERC1155URIStorage}}
Expand Down
53 changes: 53 additions & 0 deletions contracts/token/ERC1155/extensions/ERC1155Crosschain.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.26;

import {ERC1155} from "../ERC1155.sol";
import {BridgeMultiToken} from "../../../crosschain/bridges/abstract/BridgeMultiToken.sol";

/**
* @dev Extension of {ERC1155} that makes it natively cross-chain using the ERC-7786 based {BridgeMultiToken}.
*
* This extension makes the token compatible with:
* * {ERC1155Crosschain} instances on other chains,
* * {ERC1155} instances on other chains that are bridged using {BridgeERC1155},
*/
// slither-disable-next-line locked-ether
abstract contract ERC1155Crosschain is ERC1155, BridgeMultiToken {
/// @dev TransferFrom variant of {crosschainTransferFrom}, using ERC1155 allowance from the sender to the caller.
function crosschainTransferFrom(
address from,
bytes memory to,
uint256 id,
uint256 value
) public virtual returns (bytes32) {
_checkAuthorized(_msgSender(), from);

uint256[] memory ids = new uint256[](1);
uint256[] memory values = new uint256[](1);
ids[0] = id;
values[0] = value;
return _crosschainTransfer(from, to, ids, values);
}

/// @dev TransferFrom variant of {crosschainTransferFrom}, using ERC1155 allowance from the sender to the caller.
function crosschainTransferFrom(
address from,
bytes memory to,
uint256[] memory ids,
uint256[] memory values
) public virtual returns (bytes32) {
_checkAuthorized(_msgSender(), from);
return _crosschainTransfer(from, to, ids, values);
}

/// @dev "Locking" tokens is achieved through burning
function _onSend(address from, uint256[] memory ids, uint256[] memory values) internal virtual override {
_burnBatch(from, ids, values);
}

/// @dev "Unlocking" tokens is achieved through minting
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
_mintBatch(to, ids, values, "");
}
}
Loading