Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
116 changes: 116 additions & 0 deletions contracts/crosschain/bridges/BridgeERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// 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
*
* NOTE: `safeTransferFrom` will revert if the receiver is a contract that doesn't implement {IERC721Receiver}
* This can be retried by at the ERC-7786 gateway level.
*/
function _onReceive(address to, uint256[] memory ids, uint256[] memory values) internal virtual override {
token().safeBatchTransferFrom(address(this), to, ids, values, "");
}

/**
* @dev Transfer a token received using an ERC-1155 safeTransferFrom
*
* Note: The `data` must contain the `to` as a full InteroperableAddress (chain ref + address).
*/
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 Transfer a batch of tokens received using an ERC-1155 safeBatchTransferFrom
*
* Note: The `data` must contain the `to` as a full InteroperableAddress (chain ref + address).
*/
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;
}
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