Skip to content

Commit

Permalink
Feat/share ccip bridge (#180)
Browse files Browse the repository at this point in the history
* Copy over multichain share files

* Fix submodules

* Try to debug dependency bug

* forge install: ccip

v2.6.0

* Still debugging

* forge install: ccip

ccip-develop

* update gitignore

* Write additional natspec for CCIP share-bridging contracts

* Correct small natspec spelling error

* Fix PR comments

* Address hidden PR comments

---------

Co-authored-by: 0xEinCodes <[email protected]>
  • Loading branch information
crispymangoes and 0xEinCodes authored Jan 23, 2024
1 parent 7754e2c commit b7004cd
Show file tree
Hide file tree
Showing 10 changed files with 1,240 additions and 1 deletion.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
cache/
out/
broadcast/
gnosisTxs/

# Environment variables!
.env
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@
[submodule "lib/pendle-core-v2-public"]
path = lib/pendle-core-v2-public
url = https://github.com/pendle-finance/pendle-core-v2-public
[submodule "lib/ccip"]
path = lib/ccip
url = https://github.com/smartcontractkit/ccip
1 change: 1 addition & 0 deletions lib/ccip
Submodule ccip added at c8eed8
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ ds-test/=lib/forge-std/lib/ds-test/src/
@chainlink/=lib/chainlink/
@uniswapV3P=lib/v3-periphery/contracts/
@uniswapV3C=lib/v3-core/contracts/
@balancer=lib/balancer-v2-monorepo/pkg
@balancer=lib/balancer-v2-monorepo/pkg
@ccip=lib/ccip/
49 changes: 49 additions & 0 deletions src/mocks/MockCCIPRouter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { Client } from "@ccip/contracts/src/v0.8/ccip/libraries/Client.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";

contract MockCCIPRouter {
ERC20 public immutable LINK;

constructor(address _link) {
LINK = ERC20(_link);
}

uint256 public messageCount;

uint256 public currentFee = 1e18;

uint64 public constant SOURCE_SELECTOR = 6101244977088475029;
uint64 public constant DESTINATION_SELECTOR = 16015286601757825753;

mapping(bytes32 => Client.Any2EVMMessage) public messages;

bytes32 public lastMessageId;

function setFee(uint256 newFee) external {
currentFee = newFee;
}

function getLastMessage() external view returns (Client.Any2EVMMessage memory) {
return messages[lastMessageId];
}

function getFee(uint64, Client.EVM2AnyMessage memory) external view returns (uint256) {
return currentFee;
}

function ccipSend(uint64 chainSelector, Client.EVM2AnyMessage memory message) external returns (bytes32 messageId) {
LINK.transferFrom(msg.sender, address(this), currentFee);
messageId = bytes32(messageCount);
messageCount++;
lastMessageId = messageId;
messages[messageId].messageId = messageId;
messages[messageId].sourceChainSelector = chainSelector == SOURCE_SELECTOR
? DESTINATION_SELECTOR
: SOURCE_SELECTOR;
messages[messageId].sender = abi.encode(msg.sender);
messages[messageId].data = message.data;
}
}
169 changes: 169 additions & 0 deletions src/modules/multi-chain-share/DestinationMinter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol";
import { ERC20 } from "@solmate/tokens/ERC20.sol";
import { CCIPReceiver } from "@ccip/contracts/src/v0.8/ccip/applications/CCIPReceiver.sol";
import { Client } from "@ccip/contracts/src/v0.8/ccip/libraries/Client.sol";
import { IRouterClient } from "@ccip/contracts/src/v0.8/ccip/interfaces/IRouterClient.sol";

/**
* @title DestinationMinter
* @notice Receives CCIP messages from SourceLocker, to mint ERC20 shares that
* represent ERC4626 shares locked on source chain.
* @author crispymangoes
*/
contract DestinationMinter is ERC20, CCIPReceiver {
using SafeTransferLib for ERC20;

//============================== ERRORS ===============================

error DestinationMinter___SourceChainNotAllowlisted(uint64 sourceChainSelector);
error DestinationMinter___SenderNotAllowlisted(address sender);
error DestinationMinter___InvalidTo();
error DestinationMinter___FeeTooHigh();

//============================== EVENTS ===============================

event BridgeToSource(uint256 amount, address to);
event BridgeFromSource(uint256 amount, address to);

//============================== MODIFIERS ===============================

modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) {
if (_sourceChainSelector != sourceChainSelector)
revert DestinationMinter___SourceChainNotAllowlisted(_sourceChainSelector);
if (_sender != targetSource) revert DestinationMinter___SenderNotAllowlisted(_sender);
_;
}

//============================== IMMUTABLES ===============================

/**
* @notice The address of the SourceLocker on source chain.
*/
address public immutable targetSource;

/**
* @notice The CCIP source chain selector.
*/
uint64 public immutable sourceChainSelector;

/**
* @notice The CCIP destination chain selector.
*/
uint64 public immutable destinationChainSelector;

/**
* @notice This networks LINK contract.
*/
ERC20 public immutable LINK;

/**
* @notice The message gas limit to use for CCIP messages.
*/
uint256 public immutable messageGasLimit;

constructor(
address _router,
address _targetSource,
string memory _name,
string memory _symbol,
uint8 _decimals,
uint64 _sourceChainSelector,
uint64 _destinationChainSelector,
address _link,
uint256 _messageGasLimit
) ERC20(_name, _symbol, _decimals) CCIPReceiver(_router) {
targetSource = _targetSource;
sourceChainSelector = _sourceChainSelector;
destinationChainSelector = _destinationChainSelector;
LINK = ERC20(_link);
messageGasLimit = _messageGasLimit;
}

//============================== BRIDGE ===============================

/**
* @notice Bridge shares back to source chain.
* @dev Caller should approve LINK to be spent by this contract.
* @param amount Number of shares to burn on destination network and unlock/transfer on source network.
* @param to Specified address to burn destination network `share` tokens, and receive unlocked `share` tokens on source network.
* @param maxLinkToPay Specified max amount of LINK fees to pay as per this contract.
* @return messageId Resultant CCIP messageId.
*/
function bridgeToSource(uint256 amount, address to, uint256 maxLinkToPay) external returns (bytes32 messageId) {
if (to == address(0)) revert DestinationMinter___InvalidTo();
_burn(msg.sender, amount);

Client.EVM2AnyMessage memory message = _buildMessage(amount, to);

IRouterClient router = IRouterClient(this.getRouter());

uint256 fees = router.getFee(sourceChainSelector, message);

if (fees > maxLinkToPay) revert DestinationMinter___FeeTooHigh();

LINK.safeTransferFrom(msg.sender, address(this), fees);

LINK.safeApprove(address(router), fees);

messageId = router.ccipSend(sourceChainSelector, message);
emit BridgeToSource(amount, to);
}

//============================== VIEW FUNCTIONS ===============================

/**
* @notice Preview fee required to bridge shares back to source.
* @param amount Specified amount of `share` tokens to bridge to source network.
* @param to Specified address to receive bridged shares on source network.
* @return fee required to bridge shares.
*/
function previewFee(uint256 amount, address to) public view returns (uint256 fee) {
Client.EVM2AnyMessage memory message = _buildMessage(amount, to);

IRouterClient router = IRouterClient(this.getRouter());

fee = router.getFee(sourceChainSelector, message);
}

//============================== CCIP RECEIVER ===============================

/**
* @notice Implement internal _ccipRecevie function logic.
* @param any2EvmMessage CCIP encoded message specifying details to use to 'mint' `share` tokens to a specified address `to` on destination network.
*/
function _ccipReceive(
Client.Any2EVMMessage memory any2EvmMessage
)
internal
override
onlyAllowlisted(any2EvmMessage.sourceChainSelector, abi.decode(any2EvmMessage.sender, (address)))
{
(uint256 amount, address to) = abi.decode(any2EvmMessage.data, (uint256, address));
_mint(to, amount);
emit BridgeFromSource(amount, to);
}

//============================== INTERNAL HELPER ===============================

/**
* @notice Build the CCIP message to send to source locker.
* @param amount number of `share` token to bridge.
* @param to Specified address to receive unlocked bridged shares on source network.
* @return message the CCIP message to send to source locker.
*/
function _buildMessage(uint256 amount, address to) internal view returns (Client.EVM2AnyMessage memory message) {
message = Client.EVM2AnyMessage({
receiver: abi.encode(targetSource),
data: abi.encode(amount, to),
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and non-strict sequencing mode
Client.EVMExtraArgsV1({ gasLimit: messageGasLimit /*, strict: false*/ })
),
feeToken: address(LINK)
});
}
}
Loading

0 comments on commit b7004cd

Please sign in to comment.