-
Notifications
You must be signed in to change notification settings - Fork 12.2k
ERC-7786 based crosschain bridge for ERC-20 tokens #5914
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
base: master
Are you sure you want to change the base?
Changes from 19 commits
4a33948
fdb2e77
3651638
f2abf9f
ab9643f
d3e2223
9b015d3
b486a81
50be369
4cf52d0
bd4095c
e6e5d79
c0c421c
a051b91
c938d68
69d43a7
d39c18e
3cc4acf
610362d
e0a3fd0
434ee70
c2a6d40
42b450c
b8cc512
984b897
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'openzeppelin-solidity': minor | ||
--- | ||
|
||
`ERC7786Recipient`: Generic ERC-7786 cross-chain message recipient contract. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IERC7786Recipient} from "../interfaces/draft-IERC7786.sol"; | ||
import {BitMaps} from "../utils/structs/BitMaps.sol"; | ||
|
||
/** | ||
* @dev Base implementation of an ERC-7786 compliant cross-chain message receiver. | ||
* | ||
* This abstract contract exposes the `receiveMessage` function that is used for communication with (one or multiple) | ||
* destination gateways. This contract leaves two functions unimplemented: | ||
* | ||
* * {_isAuthorizedGateway}, an internal getter used to verify whether an address is recognised by the contract as a | ||
* valid ERC-7786 destination gateway. One or multiple gateway can be supported. Note that any malicious address for | ||
* which this function returns true would be able to impersonate any account on any other chain sending any message. | ||
* | ||
* * {_processMessage}, the internal function that will be called with any message that has been validated. | ||
* | ||
* This contract implements replay protection, manning that if two messages are received from the same gateway with the | ||
* same `receiveId`, then the second one will NOT be executed, regardless of the result of {_isAuthorizedGateway}. | ||
*/ | ||
abstract contract ERC7786Recipient is IERC7786Recipient { | ||
using BitMaps for BitMaps.BitMap; | ||
|
||
mapping(address gateway => BitMaps.BitMap) private _received; | ||
|
||
error ERC7786RecipientInvalidGateway(address gateway); | ||
error ERC7786RecipientMessageAlreadyProcessed(address gateway, bytes32 receiveId); | ||
|
||
/// @inheritdoc IERC7786Recipient | ||
function receiveMessage( | ||
bytes32 receiveId, | ||
bytes calldata sender, // Binary Interoperable Address | ||
bytes calldata payload | ||
) external payable returns (bytes4) { | ||
require(_isAuthorizedGateway(msg.sender, sender), ERC7786RecipientInvalidGateway(msg.sender)); | ||
require( | ||
!_received[msg.sender].get(uint256(receiveId)), | ||
ERC7786RecipientMessageAlreadyProcessed(msg.sender, receiveId) | ||
); | ||
_received[msg.sender].set(uint256(receiveId)); | ||
|
||
_processMessage(msg.sender, receiveId, sender, payload); | ||
|
||
return IERC7786Recipient.receiveMessage.selector; | ||
} | ||
|
||
/// @dev Virtual getter that returns whether an address is a valid ERC-7786 gateway for a given sender. | ||
function _isAuthorizedGateway(address instance, bytes calldata sender) internal view virtual returns (bool); | ||
|
||
/// @dev Virtual function that should contain the logic to execute when a cross-chain message is received. | ||
function _processMessage( | ||
address gateway, | ||
bytes32 receiveId, | ||
bytes calldata sender, | ||
bytes calldata payload | ||
) internal virtual; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
= Cross chain interoperability | ||
|
||
[.readme-notice] | ||
NOTE: This document is better viewed at https://docs.openzeppelin.com/contracts/api/crosschain | ||
|
||
This directory contains contracts for sending and receiving cross chain messages that follows the ERC-7786 standard. | ||
|
||
- {IERC7786Recipient}: generic ERC-7786 crosschain contract that receives messages from a trusted gateway | ||
|
||
== Helpers | ||
|
||
{{IERC7786Recipient}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.24; | ||
|
||
import {IERC7786GatewaySource} from "../../interfaces/draft-IERC7786.sol"; | ||
import {InteroperableAddress} from "../../utils/draft-InteroperableAddress.sol"; | ||
import {Bytes} from "../../utils/Bytes.sol"; | ||
import {ERC7786Recipient} from "../ERC7786Recipient.sol"; | ||
|
||
/** | ||
* @dev Core bridging mechanism. | ||
* | ||
* This contract contains the logic to register and send messages to counterparts on remote chains using ERC-7786 | ||
* gateways. It ensure received message originate from for a counterpart. This is the code of token bridges such as | ||
* {BridgeERC20}. | ||
* | ||
* Contract that inherit from this contract can use the internal {_senMessage} to send messages to their conterpart | ||
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
* on a foreign chain. They must override the {_processMessage} function to handle the message that have been verified. | ||
*/ | ||
abstract contract BridgeCore is ERC7786Recipient { | ||
using Bytes for bytes; | ||
using InteroperableAddress for bytes; | ||
|
||
struct Link { | ||
address gateway; | ||
bytes remote; | ||
} | ||
mapping(bytes chain => Link) private _links; | ||
|
||
event RemoteRegistered(address gateway, bytes remote); | ||
|
||
error RemoteAlreadyRegistered(bytes chain); | ||
|
||
constructor(Link[] memory links) { | ||
for (uint256 i = 0; i < links.length; ++i) { | ||
_setLink(links[0].gateway, links[0].remote, false); | ||
} | ||
} | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// @dev Returns the ERC-7786 gateway used for sending and receiving cross-chain messages to a given chain | ||
function link(bytes memory chain) public view virtual returns (address gateway, bytes memory remote) { | ||
Link storage self = _links[chain]; | ||
return (self.gateway, self.remote); | ||
} | ||
|
||
/// @dev Internal setter to change the ERC-7786 gateway and remote for a given chain. Called at construction. | ||
function _setLink(address gateway, bytes memory remote, bool allowOverride) internal virtual { | ||
// Sanity check, this should revert if gateway is not an ERC-7786 implementation. Note that since | ||
// supportsAttribute returns data, an EOA would fail that test (nothing returned). | ||
IERC7786GatewaySource(gateway).supportsAttribute(bytes4(0)); | ||
|
||
bytes memory chain = _extractChain(remote); | ||
if (allowOverride || _links[chain].gateway == address(0)) { | ||
_links[chain] = Link(gateway, remote); | ||
emit RemoteRegistered(gateway, remote); | ||
} else { | ||
revert RemoteAlreadyRegistered(chain); | ||
} | ||
} | ||
|
||
/// @dev Internal messaging function. | ||
function _sendMessage( | ||
bytes memory chain, | ||
bytes memory payload, | ||
bytes[] memory attributes | ||
) internal virtual returns (bytes32) { | ||
(address gateway, bytes memory remote) = link(chain); | ||
return IERC7786GatewaySource(gateway).sendMessage(remote, payload, attributes); | ||
} | ||
|
||
/// @inheritdoc ERC7786Recipient | ||
function _isAuthorizedGateway( | ||
address instance, | ||
bytes calldata sender | ||
) internal view virtual override returns (bool) { | ||
(address gateway, bytes memory router) = link(_extractChain(sender)); | ||
return instance == gateway && sender.equal(router); | ||
} | ||
|
||
function _extractChain(bytes memory self) private pure returns (bytes memory) { | ||
(bytes2 chainType, bytes memory chainReference, ) = self.parseV1(); | ||
return InteroperableAddress.formatV1(chainType, chainReference, hex""); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,70 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
// SPDX-License-Identifier: MIT | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pragma solidity ^0.8.24; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {InteroperableAddress} from "../../utils/draft-InteroperableAddress.sol"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {ERC7786Recipient} from "../ERC7786Recipient.sol"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import {BridgeCore} from "./BridgeCore.sol"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* @dev Base contract for bridging ERC-20 between chains using an ERC-7786 gateway. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* In order to use this contract, two function must be implemented to link it to the token: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* * {lock}: called when a crosschain transfer is going out. Must take the sender tokens or revert. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* * {unlock}: called when a crosschain transfer is coming it. Must give tokens to the receiver. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* This base contract is used by the {BridgeERC20Custodial}, which interfaces with legacy ERC-20 tokens, and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* {BrdigeERC20Bridgeable}, which interface with ERC-7802 to provide an approve-free user experience. It is also used | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Amxx marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
* by the {ERC20Crosschain} extension, which embeds the bridge logic directly in the token contract. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
abstract contract BridgeERC20 is BridgeCore { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
using InteroperableAddress for bytes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
event CrossChainTransferSent(bytes32 indexed sendId, address indexed from, bytes to, uint256 amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
event CrossChainTransferReceived(bytes32 indexed receiveId, bytes from, address indexed to, uint256 amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// @dev Transfer `amount` tokens to a crosschain receiver. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function crosschainTransfer(bytes memory to, uint256 amount) public virtual returns (bytes32) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
return _crosschainTransfer(msg.sender, to, amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// @dev Internal crosschain transfer function. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function _crosschainTransfer(address from, bytes memory to, uint256 amount) internal virtual returns (bytes32) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_lock(from, amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(bytes2 chainType, bytes memory chainReference, bytes memory addr) = to.parseV1(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bytes memory chain = InteroperableAddress.formatV1(chainType, chainReference, hex""); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
bytes32 sendId = _sendMessage( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
chain, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
abi.encode(InteroperableAddress.formatEvmV1(block.chainid, from), addr, amount), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
new bytes[](0) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emit CrossChainTransferSent(sendId, from, to, amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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 amount) = abi.decode(payload, (bytes, bytes, uint256)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
address to = address(bytes20(toBinary)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+57
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unsafe address extraction from bytes. The conversion
If Consider using a proper parser or validating that (bytes memory from, bytes memory toBinary, uint256 amount) = abi.decode(payload, (bytes, bytes, uint256));
- address to = address(bytes20(toBinary));
+ // Parse the destination address properly
+ (, , bytes memory addrBytes) = toBinary.parseV1();
+ require(addrBytes.length == 20, "Invalid EVM address");
+ address to = address(bytes20(addrBytes)); Or ensure that the sending side always encodes a raw 20-byte address rather than a full interoperable address structure.
🤖 Prompt for AI Agents
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The origin of the message has been verified, so we have a guarantee that So we have a guarantee that this is the "address" part of the What if this is not properly formated? Reverting here means the the tokens were locked/burn on the source chain, and never minted on this side, creating an inconsistency. Not doing the check means we mint to whatever match the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Amxx, you're absolutely right. I apologize for the misleading suggestion. In a cross-chain bridge context, reverting on malformed addresses would create an irrecoverable inconsistency where tokens are locked/burned on the source chain but never minted on the destination chain. The current best-effort approach is the correct design:
The current implementation correctly prioritizes cross-chain consistency over local validation. Thank you for the clarification! ✏️ Learnings added
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Amxx, thank you for the clarification! I understand now— I appreciate you taking the time to ensure I understood the data structure correctly. 🧠 Learnings used
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
_unlock(to, amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
emit CrossChainTransferReceived(receiveId, from, to, amount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+49
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major 🧩 Analysis chainVerify address extraction safety. The payload decoding and address extraction at line 58 using Consider validating that toBinary is exactly 20 bytes for EVM addresses, or document the expected format and ensure remote bridges comply. 🏁 Script executed: #!/bin/bash
# Description: Verify how addresses are encoded in the sending logic and decoded here
# Find formatEvmV1 implementation to understand the address encoding
ast-grep --pattern $'function formatEvmV1($$$) {
$$$
}'
# Check if there are any address length validations in the codebase
rg -n "bytes20|address.*length" --type solidity -C 2 Length of output: 1274 🏁 Script executed: #!/bin/bash
sed -n '1,200p' contracts/crosschain/bridges/BridgeERC20.sol Length of output: 3066 🏁 Script executed: #!/bin/bash
# Show parseV1 implementation in InteroperableAddress
grep -R "function parseV1" -n contracts/utils/draft-InteroperableAddress.sol -C5 Length of output: 989 🏁 Script executed: #!/bin/bash
grep -R "function tryParseV1" -n contracts/utils/draft-InteroperableAddress.sol -C5 Length of output: 1070 🏁 Script executed: #!/bin/bash
# Display the body of tryParseV1 to inspect how `addr` is extracted
sed -n '90,150p' contracts/utils/draft-InteroperableAddress.sol Length of output: 3077 Enforce 20-byte EVM address in (bytes memory from, bytes memory toBinary, uint256 amount) = abi.decode(payload, (bytes, bytes, uint256));
+ require(toBinary.length == 20, "BridgeERC20: invalid address length");
address to = address(bytes20(toBinary)); This guarantees the 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed:
Length of output: 363 🏁 Script executed:
Length of output: 752 @Amxx, thank you for the reference. I've reviewed the discussion, and you're absolutely right. As you explained in that thread, adding a The best-effort approach with My suggestion to add the length validation was inappropriate for this cross-chain bridging context. Would you like me to resolve this comment thread? 🧠 Learnings used
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// @dev Virtual function: implementation is required to handle token being burnt or locked on the source chain. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function _lock(address from, uint256 amount) internal virtual; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/// @dev Virtual function: implementation is required to handle token being minted or unlocked on the destination chain. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
function _unlock(address to, uint256 amount) internal virtual; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.24; | ||
|
||
import {IERC7802} from "../../interfaces/draft-IERC7802.sol"; | ||
import {BridgeERC20} from "./BridgeERC20.sol"; | ||
|
||
/** | ||
* @dev This is a variant of {BridgeERC20} that implements the bridge logic for ERC-7802 compliant tokens. | ||
*/ | ||
abstract contract BridgeERC20Bridgeable is BridgeERC20 { | ||
IERC7802 private immutable _token; | ||
|
||
constructor(IERC7802 token_) { | ||
_token = token_; | ||
} | ||
|
||
/// @dev Return the address of the ERC20 token this bridge operates on. | ||
function token() public view virtual returns (IERC7802) { | ||
return _token; | ||
} | ||
|
||
/// @dev "Locking" tokens using an ERC-7802 crosschain burn | ||
function _lock(address from, uint256 amount) internal virtual override { | ||
token().crosschainBurn(from, amount); | ||
} | ||
|
||
/// @dev "Unlocking" tokens using an ERC-7802 crosschain burn | ||
function _unlock(address to, uint256 amount) internal virtual override { | ||
token().crosschainMint(to, amount); | ||
} | ||
Amxx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||||||||||||||||
// SPDX-License-Identifier: MIT | ||||||||||||||||||||
|
||||||||||||||||||||
pragma solidity ^0.8.24; | ||||||||||||||||||||
|
||||||||||||||||||||
import {IERC20, SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; | ||||||||||||||||||||
import {BridgeERC20} from "./BridgeERC20.sol"; | ||||||||||||||||||||
|
||||||||||||||||||||
/** | ||||||||||||||||||||
* @dev This is a variant of {BridgeERC20} that implements the bridge logic for ERC-20 tokens that do not expose mint | ||||||||||||||||||||
* and burn mechanism. Instead it takes custody of bridged assets. | ||||||||||||||||||||
*/ | ||||||||||||||||||||
abstract contract BridgeERC20Custodial is BridgeERC20 { | ||||||||||||||||||||
using SafeERC20 for IERC20; | ||||||||||||||||||||
|
||||||||||||||||||||
IERC20 private immutable _token; | ||||||||||||||||||||
|
||||||||||||||||||||
constructor(IERC20 token_) { | ||||||||||||||||||||
_token = token_; | ||||||||||||||||||||
} | ||||||||||||||||||||
Comment on lines
+17
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate token address in constructor. The constructor does not validate that Apply this diff to add validation: + error InvalidToken();
+
constructor(IERC20 token_) {
+ if (address(token_) == address(0)) revert InvalidToken();
_token = token_;
} 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||
|
||||||||||||||||||||
/// @dev Return the address of the ERC20 token this bridge operates on. | ||||||||||||||||||||
function token() public view virtual returns (IERC20) { | ||||||||||||||||||||
return _token; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/// @dev "Locking" tokens is done by taking custody | ||||||||||||||||||||
function _lock(address from, uint256 amount) internal virtual override { | ||||||||||||||||||||
token().safeTransferFrom(from, address(this), amount); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
/// @dev "Unlocking" tokens is done by releasing custody | ||||||||||||||||||||
function _unlock(address to, uint256 amount) internal virtual override { | ||||||||||||||||||||
token().safeTransfer(to, amount); | ||||||||||||||||||||
} | ||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity ^0.8.27; | ||
|
||
import {IERC7786GatewaySource, IERC7786Recipient} from "../../interfaces/draft-IERC7786.sol"; | ||
import {InteroperableAddress} from "../../utils/draft-InteroperableAddress.sol"; | ||
|
||
abstract contract ERC7786GatewayMock is IERC7786GatewaySource { | ||
using InteroperableAddress for bytes; | ||
|
||
error InvalidDestination(); | ||
error ReceiverError(); | ||
|
||
uint256 private _lastReceiveId; | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) { | ||
return false; | ||
} | ||
|
||
/// @inheritdoc IERC7786GatewaySource | ||
function sendMessage( | ||
bytes calldata recipient, | ||
bytes calldata payload, | ||
bytes[] calldata attributes | ||
) public payable virtual returns (bytes32 sendId) { | ||
// attributes are not supported | ||
if (attributes.length > 0) { | ||
revert UnsupportedAttribute(bytes4(attributes[0])); | ||
} | ||
|
||
// parse recipient | ||
(bool success, uint256 chainid, address target) = recipient.tryParseEvmV1Calldata(); | ||
require(success && chainid == block.chainid, InvalidDestination()); | ||
|
||
// perform call | ||
bytes4 magic = IERC7786Recipient(target).receiveMessage{value: msg.value}( | ||
bytes32(++_lastReceiveId), | ||
InteroperableAddress.formatEvmV1(block.chainid, msg.sender), | ||
payload | ||
); | ||
require(magic == IERC7786Recipient.receiveMessage.selector, ReceiverError()); | ||
|
||
// emit standard event | ||
emit MessageSent( | ||
bytes32(0), | ||
InteroperableAddress.formatEvmV1(block.chainid, msg.sender), | ||
recipient, | ||
payload, | ||
msg.value, | ||
attributes | ||
); | ||
|
||
return 0; | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.