Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
---

`BridgeERC1155Core` 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:
* {BridgeERC20Core}: 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.
* {BridgeERC1155Core}: 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}}

{{BridgeERC1155Core}}

{{BridgeERC1155}}
103 changes: 103 additions & 0 deletions contracts/crosschain/bridges/BridgeERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 {BridgeERC1155Core} from "./BridgeERC1155Core.sol";

/**
* @dev This is a variant of {BridgeERC1155Core} 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 BridgeERC1155Core, ERC1155Holder {
IERC1155 private immutable _token;

error BridgeERC1155Unauthorized(address caller);

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(0xffffffff);
}

/// @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(0xffffffff);
}
}
87 changes: 87 additions & 0 deletions contracts/crosschain/bridges/BridgeERC1155Core.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// 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.
*/
abstract contract BridgeERC1155Core is Context, CrosschainLinked {
using InteroperableAddress for bytes;

event CrosschainERC1155TransferSent(
bytes32 indexed sendId,
address indexed from,
bytes to,
uint256[] ids,
uint256[] values
);
event CrosschainERC1155TransferReceived(
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 CrosschainERC1155TransferSent(sendId, from, to, ids, values);
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[] memory ids, uint256[] memory values) = abi.decode(
payload,
(bytes, bytes, uint256[], uint256[])
);
address to = address(bytes20(toBinary));

_onReceive(to, ids, values);

emit CrosschainERC1155TransferReceived(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 {BridgeERC1155Core} from "../../../crosschain/bridges/BridgeERC1155Core.sol";

/**
* @dev Extension of {ERC1155} that makes it natively cross-chain using the ERC-7786 based {BridgeERC1155Core}.
*
* 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, BridgeERC1155Core {
/// @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
Loading