Skip to content
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

Gho ccip bridge (#7) #347

Open
wants to merge 48 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
672d30b
gho bridge contract finished
LucasWongC Sep 12, 2024
8e6e792
added natspec
LucasWongC Sep 12, 2024
35c62df
bugfix on foundry remapping config
LucasWongC Sep 13, 2024
9831e27
added deploy scripts for gho ccip bridge
LucasWongC Sep 13, 2024
f678946
handled feedbacks
LucasWongC Sep 18, 2024
868cfe2
modified for single transfer to aave collector
LucasWongC Sep 18, 2024
08fdd52
used address book for deploy script
LucasWongC Sep 18, 2024
648748f
changed owner and guardian address
LucasWongC Sep 18, 2024
f5c81dd
added testing transactions
LucasWongC Sep 20, 2024
8e0889d
reverted deploy script
LucasWongC Sep 20, 2024
b95013f
removed source check on receive function
LucasWongC Sep 23, 2024
a6554eb
Gho ccip bridge (#7)
LucasWongC Sep 30, 2024
3cd78f2
update from comments
LucasWongC Oct 25, 2024
f1ab526
added more details on finished event
LucasWongC Oct 25, 2024
b57aabd
event type changed
LucasWongC Oct 25, 2024
70f648b
use guardian address from address book
LucasWongC Oct 28, 2024
f61721f
moves remappings define to remappings.txt
LucasWongC Oct 28, 2024
26ee058
return remaining fee functionality test
LucasWongC Oct 28, 2024
10d1c9f
removed guardian access from transfer function
LucasWongC Oct 28, 2024
02d25ab
updated interface can use gho as fee token
LucasWongC Oct 29, 2024
c2ff3e9
readme updated
LucasWongC Oct 29, 2024
e2f1bf1
updated method to verify message
LucasWongC Oct 29, 2024
f5c6cf4
added fee token validate part
LucasWongC Oct 30, 2024
078c1cf
import order fix
LucasWongC Nov 1, 2024
36004e7
style bugfix
LucasWongC Nov 1, 2024
a2739f2
typo bugfix
LucasWongC Nov 1, 2024
f795e91
used fee on contract
LucasWongC Nov 5, 2024
02f0ef3
fix from feedbacks
LucasWongC Nov 5, 2024
4a2994e
added quote function
LucasWongC Nov 5, 2024
7cc4d08
used AccessControl instead of Ownable
LucasWongC Nov 5, 2024
796b1f8
remove comments
LucasWongC Nov 18, 2024
1687c87
removed comment
LucasWongC Nov 18, 2024
64a266d
removed sender from Finished event and added Issued event
LucasWongC Nov 18, 2024
362b349
added arb -> eth test
LucasWongC Nov 18, 2024
b888f8b
added gasLimit customization
LucasWongC Nov 19, 2024
cecb3a8
added invalid message handling part
LucasWongC Nov 20, 2024
97438ac
transfer to collectoer
LucasWongC Nov 21, 2024
66d5c41
used to try-catch
LucasWongC Nov 25, 2024
ea85f3c
added test for processMessage function
LucasWongC Nov 27, 2024
ec3cbe5
added test for ccip internal functions
LucasWongC Nov 27, 2024
64631dc
resolved yarn.lock manullay
LucasWongC Nov 28, 2024
dcdf804
updated chainlink version to latest
LucasWongC Dec 2, 2024
4b5a4e7
added more tests
LucasWongC Dec 4, 2024
f64ed23
bugfix from feedback
LucasWongC Dec 10, 2024
820131f
added multiple fee tokens
LucasWongC Jan 10, 2025
429a840
bugfix on extra arg config
LucasWongC Jan 10, 2025
62f09c5
updated test for multi fee token
LucasWongC Jan 13, 2025
e1f4b89
handled feedback
LucasWongC Jan 13, 2025
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
2 changes: 0 additions & 2 deletions diffs/default_before_default_after.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
## Emodes changes

## Raw diff

```json
Expand Down
2 changes: 1 addition & 1 deletion diffs/preTestV2RatesUpdates_postTestV2RatesUpdates.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
| stableRateSlope1 | 2 % | 69 % |
| optimalUsageRatio | 90 % | 69 % |
| maxExcessUsageRatio | 10 % | 31 % |
| interestRate | ![before](/.assets/23e67c7d46dd80f36d580b243c5716c84080a34f.svg) | ![after](/.assets/64fd6acec636adec0e975e8031f8e3f7fb87bb7d.svg) |
| interestRate | ![before](https://dash.onaave.com/api/static?variableRateSlope1=65000000000000000000000000&variableRateSlope2=600000000000000000000000000&optimalUsageRatio=900000000000000000000000000&baseVariableBorrowRate=0&maxVariableBorrowRate=undefined) | ![after](https://dash.onaave.com/api/static?variableRateSlope1=420000000000000000000000000&variableRateSlope2=600000000000000000000000000&optimalUsageRatio=690000000000000000000000000&baseVariableBorrowRate=0&maxVariableBorrowRate=undefined) |

## Raw diff

Expand Down
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ libs = ['lib']
remappings = []
fs_permissions = [{ access = "read-write", path = "./reports" }]
ffi = true
solc = '0.8.20'
evm_version = 'shanghai'

[profile.zksync]
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
},
"homepage": "https://github.com/bgd-labs/aave-helpers#readme",
"devDependencies": {
"@chainlink/contracts": "^1.3.0",
"@chainlink/contracts-ccip": "^1.5.0",
"@chainlink/local": "^0.2.3",
"prettier": "^2.8.3",
"prettier-plugin-solidity": "^1.1.3"
},
Expand Down
4 changes: 4 additions & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ aave-v3-origin/=lib/aave-address-book/lib/aave-v3-origin/src/
aave-v3-origin-tests/=lib/aave-address-book/lib/aave-v3-origin/tests
forge-std/=lib/forge-std/src/
solidity-utils/=lib/aave-address-book/lib/aave-v3-origin/lib/solidity-utils/src/
@chainlink/contracts/=node_modules/@chainlink/contracts
@chainlink/contracts-ccip/=node_modules/@chainlink/contracts-ccip
@chainlink/local/=node_modules/@chainlink/local

36 changes: 36 additions & 0 deletions scripts/DeployBridges.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {GovernanceV3Ethereum} from 'aave-address-book/GovernanceV3Ethereum.sol';
import {GovernanceV3Optimism} from 'aave-address-book/GovernanceV3Optimism.sol';
import {GovernanceV3Polygon} from 'aave-address-book/GovernanceV3Polygon.sol';
import {GovernanceV3Arbitrum} from 'aave-address-book/GovernanceV3Arbitrum.sol';
import {AaveV3Ethereum, AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
import {AaveV3Arbitrum, AaveV3ArbitrumAssets} from 'aave-address-book/AaveV3Arbitrum.sol';
import {MiscEthereum} from 'aave-address-book/MiscEthereum.sol';
import {MiscArbitrum} from 'aave-address-book/MiscArbitrum.sol';
import {ArbitrumScript, EthereumScript, OptimismScript, PolygonScript} from 'solidity-utils/contracts/utils/ScriptUtils.sol';
import {AaveArbEthERC20Bridge} from 'src/bridges/arbitrum/AaveArbEthERC20Bridge.sol';
import {AavePolEthERC20Bridge} from 'src/bridges/polygon/AavePolEthERC20Bridge.sol';
import {AavePolEthPlasmaBridge} from 'src/bridges/polygon/AavePolEthPlasmaBridge.sol';
import {AaveOpEthERC20Bridge} from 'src/bridges/optimism/AaveOpEthERC20Bridge.sol';
import {AaveCcipGhoBridge} from 'src/bridges/chainlink-ccip/AaveCcipGhoBridge.sol';

contract DeployEthereum is EthereumScript {
function run() external broadcast {
Expand Down Expand Up @@ -60,3 +66,33 @@ contract DeployArbBridgeArbitrum is ArbitrumScript {
new AaveArbEthERC20Bridge{salt: salt}(0x3765A685a401622C060E5D700D9ad89413363a91);
}
}

contract DeployAaveCcipGhoBridgeEthereum is EthereumScript {
// https://etherscan.io/address/0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D
address constant CCIP_ROUTER_ETHEREUM = 0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D; // ccip router address

function run() external broadcast {
bytes32 salt = 'GHO Chainlink CCIP Bridge';
new AaveCcipGhoBridge{salt: salt}(
sendra marked this conversation as resolved.
Show resolved Hide resolved
CCIP_ROUTER_ETHEREUM,
AaveV3EthereumAssets.GHO_UNDERLYING,
address(AaveV3Ethereum.COLLECTOR),
GovernanceV3Ethereum.EXECUTOR_LVL_1
);
}
}

contract DeployAaveCcipGhoBridgeArbitrum is ArbitrumScript {
// https://arbiscan.io/address/0x141fa059441E0ca23ce184B6A78bafD2A517DdE8
address constant CCIP_ROUTER_ARBITRUM = 0x141fa059441E0ca23ce184B6A78bafD2A517DdE8; // ccip router address

function run() external broadcast {
bytes32 salt = 'GHO Chainlink CCIP Bridge';
new AaveCcipGhoBridge{salt: salt}(
CCIP_ROUTER_ARBITRUM,
AaveV3ArbitrumAssets.GHO_UNDERLYING,
address(AaveV3Arbitrum.COLLECTOR),
GovernanceV3Arbitrum.EXECUTOR_LVL_1
);
}
}
293 changes: 293 additions & 0 deletions src/bridges/chainlink-ccip/AaveCcipGhoBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol';
import {Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol';
import {RescuableBase, IRescuableBase} from 'solidity-utils/contracts/utils/RescuableBase.sol';
import {AccessControl, IAccessControl} from 'aave-v3-origin/contracts/dependencies/openzeppelin/contracts/AccessControl.sol';
import {CCIPReceiver, IAny2EVMMessageReceiver, IERC165} from '@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.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';

import {IAaveCcipGhoBridge} from './IAaveCcipGhoBridge.sol';

/**
* @title AaveCcipGhoBridge
* @author LucasWongC
* @notice Helper contract to bridge GHO using Chainlink CCIP
* @dev Sends GHO to AAVE collector of destination chain using chainlink CCIP
*/
contract AaveCcipGhoBridge is IAaveCcipGhoBridge, CCIPReceiver, AccessControl, Rescuable {
using SafeERC20 for IERC20;

/// @dev This role defines which users can call bridge functions.
bytes32 public constant BRIDGER_ROLE = keccak256('BRIDGER_ROLE');

/// @dev Chainlink CCIP router address
address public immutable ROUTER;
/// @dev GHO token address
address public immutable GHO;
/// @dev Aave Collector address
address public immutable COLLECTOR;
/// @dev Aave Executor address
address public immutable EXECUTOR;

/// @dev Address of bridge (chainSelector => bridge address)
mapping(uint64 selector => address bridge) public bridges;

/// @dev Saves invalid message
mapping(bytes32 messageId => Client.EVMTokenAmount[] message) private invalidTokenTransfers;
/// @dev Saves state of invalid message.
mapping(bytes32 messageId => bool failed) public isInvalidMessage;

/// @dev Checks if the destination bridge has been set up
modifier checkDestination(uint64 chainSelector) {
if (bridges[chainSelector] == address(0)) {
revert UnsupportedChain();
}
_;
}

/// @dev Checks if invalid message exists
modifier checkInvalidMessage(bytes32 messageId) {
if (!isInvalidMessage[messageId]) {
revert MessageNotFound();
}
_;
}

/**
* @dev Modifier to allow only the contract itself to execute a function.
* Throws an exception if called by any account other than the contract itself.
*/
modifier onlySelf() {
if (msg.sender != address(this)) revert OnlySelf();
_;
}

/**
* @param _router The address of the Chainlink CCIP router
* @param _gho The address of the GHO token
* @param _collector The address of collector on same chain
* @param _executor The address of the contract executor
*/
constructor(
address _router,
address _gho,
address _collector,
address _executor
) CCIPReceiver(_router) {
ROUTER = _router;
GHO = _gho;
COLLECTOR = _collector;
EXECUTOR = _executor;

_setupRole(DEFAULT_ADMIN_ROLE, _executor);
}

/**
* @dev See {IERC165-supportsInterface}.
*/
function supportsInterface(
bytes4 interfaceId
) public pure virtual override(AccessControl, CCIPReceiver) returns (bool) {
return
interfaceId == type(IAccessControl).interfaceId ||
interfaceId == type(IAny2EVMMessageReceiver).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are all necessary interface ids for CCIP provided here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes


/// @inheritdoc IAaveCcipGhoBridge
function bridge(
uint64 destinationChainSelector,
uint256 amount,
uint256 gasLimit,
address feeToken
)
external
payable
checkDestination(destinationChainSelector)
onlyRole(BRIDGER_ROLE)
returns (bytes32 messageId)
{
Client.EVM2AnyMessage memory message = _buildCCIPMessage(
destinationChainSelector,
amount,
gasLimit,
feeToken
);

uint256 fee = IRouterClient(ROUTER).getFee(destinationChainSelector, message);
LucasWongC marked this conversation as resolved.
Show resolved Hide resolved

uint256 inBalance = IERC20(GHO).balanceOf(address(this));
uint256 totalGhoAmount = amount;

if (feeToken == address(0)) {
if (msg.value < fee) revert InsufficientNativeFee();
} else if (feeToken == GHO) {
totalGhoAmount += fee;
} else {
revert InvalidFeeToken();
}

if (inBalance < totalGhoAmount) {
IERC20(GHO).transferFrom(msg.sender, address(this), totalGhoAmount - inBalance);
}
IERC20(GHO).approve(ROUTER, totalGhoAmount);

messageId = IRouterClient(ROUTER).ccipSend{value: feeToken == address(0) ? fee : 0}(
destinationChainSelector,
message
);

if (feeToken == address(0)) {
if (msg.value > fee) {
payable(msg.sender).call{value: msg.value - fee}('');
}
} else {
payable(msg.sender).call{value: msg.value}('');
}

emit TransferIssued(messageId, destinationChainSelector, msg.sender, amount);
}

/// @inheritdoc IAaveCcipGhoBridge
function quoteBridge(
uint64 destinationChainSelector,
uint256 amount,
uint256 gasLimit,
address feeToken
) external view checkDestination(destinationChainSelector) returns (uint256 fee) {
Client.EVM2AnyMessage memory message = _buildCCIPMessage(
destinationChainSelector,
amount,
gasLimit,
feeToken
);

fee = IRouterClient(ROUTER).getFee(destinationChainSelector, message);
}

/**
* @dev Builds ccip message for token transfer
*/
function _buildCCIPMessage(
uint64 destinationChainSelector,
uint256 amount,
uint256 gasLimit,
address feeToken
) internal view returns (Client.EVM2AnyMessage memory message) {
if (amount == 0) {
revert InvalidTransferAmount();
}
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: GHO, amount: amount});

message = Client.EVM2AnyMessage({
receiver: abi.encode(bridges[destinationChainSelector]),
data: '',
tokenAmounts: tokenAmounts,
extraArgs: gasLimit == 0
? bytes('')
: Client._argsToBytes(
Client.EVMExtraArgsV2({gasLimit: gasLimit, allowOutOfOrderExecution: false})
LucasWongC marked this conversation as resolved.
Show resolved Hide resolved
),
feeToken: feeToken
});
LucasWongC marked this conversation as resolved.
Show resolved Hide resolved
}

/// @inheritdoc CCIPReceiver
function ccipReceive(Client.Any2EVMMessage calldata message) external override onlyRouter {
LucasWongC marked this conversation as resolved.
Show resolved Hide resolved
try this.processMessage(message) {} catch {
bytes32 messageId = message.messageId;

Client.EVMTokenAmount[] memory tokenAmounts = message.destTokenAmounts;
uint256 length = tokenAmounts.length;
for (uint256 i = 0; i < length; ++i) {
invalidTokenTransfers[messageId].push(tokenAmounts[i]);
}
isInvalidMessage[messageId] = true;

emit ReceivedInvalidMessage(messageId);
}
}

/// @dev wrap _ccipReceive as a external function
function processMessage(Client.Any2EVMMessage calldata message) external onlySelf {
if (bridges[message.sourceChainSelector] != abi.decode(message.sender, (address))) {
LucasWongC marked this conversation as resolved.
Show resolved Hide resolved
revert();
}

_ccipReceive(message);
}

/// @inheritdoc CCIPReceiver
function _ccipReceive(Client.Any2EVMMessage memory message) internal override {
uint256 ghoAmount = message.destTokenAmounts[0].amount;

IERC20(GHO).transfer(COLLECTOR, ghoAmount);

emit TransferFinished(message.messageId, COLLECTOR, ghoAmount);
}

/// @inheritdoc IAaveCcipGhoBridge
function getInvalidMessage(
bytes32 messageId
)
external
view
checkInvalidMessage(messageId)
returns (Client.EVMTokenAmount[] memory tokenAmounts)
{
uint256 length = invalidTokenTransfers[messageId].length;
tokenAmounts = new Client.EVMTokenAmount[](length);

for (uint256 i = 0; i < length; ++i) {
tokenAmounts[i] = invalidTokenTransfers[messageId][i];
}
}

/// @inheritdoc IAaveCcipGhoBridge
function handleInvalidMessage(
bytes32 messageId
) external onlyRole(DEFAULT_ADMIN_ROLE) checkInvalidMessage(messageId) {
isInvalidMessage[messageId] = false;

Client.EVMTokenAmount[] memory tokenAmounts = invalidTokenTransfers[messageId];
uint256 length = tokenAmounts.length;
for (uint256 i = 0; i < length; ++i) {
IERC20(tokenAmounts[i].token).safeTransfer(COLLECTOR, tokenAmounts[i].amount);
}

emit HandledInvalidMessage(messageId);
}

/**
* @notice Set up destination bridge data
* @param _destinationChainSelector The selector of the destination chain
* chain selector can be found https://docs.chain.link/ccip/supported-networks/v1_2_0/mainnet
* @param _bridge The address of the bridge deployed on destination chain
*/
function setDestinationBridge(
uint64 _destinationChainSelector,
address _bridge
) external onlyRole(DEFAULT_ADMIN_ROLE) {
bridges[_destinationChainSelector] = _bridge;

emit DestinationUpdated(_destinationChainSelector, _bridge);
}

/// @inheritdoc Rescuable
function whoCanRescue() public view override returns (address) {
return EXECUTOR;
}

/// @inheritdoc IRescuableBase
function maxRescue(
address
) public pure override(RescuableBase, IRescuableBase) returns (uint256) {
return type(uint256).max;
}
}
Loading