-
Notifications
You must be signed in to change notification settings - Fork 85
[Ethereum][Base] Bridge Helper Module with CCIP support #2550
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
Changes from 3 commits
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,72 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.0; | ||
|
||
import { AbstractSafeModule } from "./AbstractSafeModule.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
|
||
import { IRouterClient } from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; | ||
import { Client } from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; | ||
|
||
abstract contract AbstractCCIPBridgeHelperModule is AbstractSafeModule { | ||
/** | ||
* @notice Bridges a token from the source chain to the destination chain using CCIP | ||
* @param ccipRouter The CCIP router contract | ||
* @param destinationChainSelector The selector for the destination chain | ||
* @param token The token to bridge | ||
* @param amount The amount of token to bridge | ||
*/ | ||
function _bridgeTokenWithCCIP( | ||
IRouterClient ccipRouter, | ||
uint64 destinationChainSelector, | ||
IERC20 token, | ||
uint256 amount | ||
) internal { | ||
bool success; | ||
|
||
// Approve CCIP Router to move the token | ||
success = safeContract.execTransactionFromModule( | ||
address(token), | ||
0, // Value | ||
abi.encodeWithSelector(token.approve.selector, ccipRouter, amount), | ||
0 // Call | ||
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. nit: this the operation. The previous param is the call 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. Actually, yes, the previous param is the calldata. The fourth parameter takes 0 for normals calls and 1 for delegate calls. That's the |
||
); | ||
require(success, "Failed to approve token"); | ||
|
||
Client.EVMTokenAmount[] | ||
memory tokenAmounts = new Client.EVMTokenAmount[](1); | ||
Client.EVMTokenAmount memory tokenAmount = Client.EVMTokenAmount({ | ||
token: address(token), | ||
amount: amount | ||
}); | ||
tokenAmounts[0] = tokenAmount; | ||
|
||
Client.EVM2AnyMessage memory ccipMessage = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(address(safeContract)), // ABI-encoded receiver address | ||
data: abi.encode(""), | ||
tokenAmounts: tokenAmounts, | ||
extraArgs: Client._argsToBytes( | ||
Client.EVMExtraArgsV1({ gasLimit: 0 }) | ||
), | ||
feeToken: address(0) | ||
}); | ||
|
||
// Get CCIP fee | ||
uint256 ccipFee = ccipRouter.getFee( | ||
destinationChainSelector, | ||
ccipMessage | ||
); | ||
|
||
// Send CCIP message | ||
success = safeContract.execTransactionFromModule( | ||
address(ccipRouter), | ||
ccipFee, // Value | ||
abi.encodeWithSelector( | ||
ccipRouter.ccipSend.selector, | ||
destinationChainSelector, | ||
ccipMessage | ||
), | ||
0 // Call | ||
); | ||
require(success, "Failed to send CCIP message"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.0; | ||
|
||
import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; | ||
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import { ISafe } from "../interfaces/ISafe.sol"; | ||
|
||
abstract contract AbstractSafeModule is AccessControlEnumerable { | ||
ISafe public immutable safeContract; | ||
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. Can we add the names of the ISafe.execTransactionFromModule parameters to the interface. Thanks |
||
|
||
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE"); | ||
|
||
modifier onlySafe() { | ||
require( | ||
msg.sender == address(safeContract), | ||
"Caller is not the safe contract" | ||
); | ||
_; | ||
} | ||
|
||
modifier onlyOperator() { | ||
require( | ||
hasRole(OPERATOR_ROLE, msg.sender), | ||
"Caller is not an operator" | ||
); | ||
_; | ||
} | ||
|
||
constructor(address _safeContract) { | ||
safeContract = ISafe(_safeContract); | ||
_grantRole(DEFAULT_ADMIN_ROLE, address(safeContract)); | ||
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. nit: you could just use |
||
_grantRole(OPERATOR_ROLE, address(safeContract)); | ||
} | ||
|
||
/** | ||
* @dev Helps recovering any tokens accidentally sent to this module. | ||
* @param token Token to transfer. 0x0 to transfer Native token. | ||
* @param amount Amount to transfer. 0 to transfer all balance. | ||
*/ | ||
function transferTokens(address token, uint256 amount) external onlySafe { | ||
if (address(token) == address(0)) { | ||
// Move ETH | ||
amount = amount > 0 ? amount : address(this).balance; | ||
payable(address(safeContract)).transfer(amount); | ||
return; | ||
} | ||
|
||
// Move all balance if amount set to 0 | ||
amount = amount > 0 ? amount : IERC20(token).balanceOf(address(this)); | ||
|
||
// Transfer to Safe contract | ||
// slither-disable-next-line unchecked-transfer unused-return | ||
IERC20(token).transfer(address(safeContract), amount); | ||
} | ||
|
||
receive() external payable { | ||
// Accept ETH to pay for bridge fees | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how these are included as a package