Skip to content

Commit

Permalink
Creat MVP for cross chain shares
Browse files Browse the repository at this point in the history
  • Loading branch information
crispymangoes committed Jun 5, 2024
1 parent 5784018 commit cd4e86d
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 7 deletions.
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
@forge-std/=lib/forge-std/src/
@ds-test/=lib/forge-std/lib/ds-test/src/
ds-test/=lib/forge-std/lib/ds-test/src/
@openzeppelin/=lib/openzeppelin-contracts/
@openzeppelin/=lib/openzeppelin-contracts/
@ccip=lib/ccip/
153 changes: 153 additions & 0 deletions src/base/Roles/CrossChain/Bridges/ChainlinkCCIPTeller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {
CrossChainTellerWithGenericBridge, ERC20
} from "src/base/Roles/CrossChain/CrossChainTellerWithGenericBridge.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";
import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol";

abstract contract ChainlinkCCIPTeller is CrossChainTellerWithGenericBridge, CCIPReceiver {
using SafeTransferLib for ERC20;

// ========================================= STRUCTS =========================================

struct Chain {
bool allowMessagesFrom;
address fromAddress;
bool allowMessagesTo;
address toAddress;
}
// ========================================= CONSTANTS =========================================
// ========================================= STATE =========================================

mapping(uint256 => Chain) public idToChains;
//============================== ERRORS ===============================

error ChainlinkCCIPTeller__MessagesNotAllowedFrom(uint256 chainId);
error ChainlinkCCIPTeller__MessagesNotAllowedFromSender(uint256 chainId, address sender);
error ChainlinkCCIPTeller__MessagesNotAllowedTo(uint256 chainId);
error ChainlinkCCIPTeller__FeeExceedsMax(uint256 chainId, uint256 fee, uint256 maxFee);
//============================== EVENTS ===============================

event ChainAdded(
uint256 chainId, bool allowMessagesFrom, address fromAddress, bool allowMessagesTo, address toAddress
);
//============================== IMMUTABLES ===============================
/**
* @notice The message gas limit to use for CCIP messages.
*/

uint256 public immutable messageGasLimit;

constructor(
address _owner,
address _vault,
address _accountant,
address _weth,
address _router,
uint256 _messageGasLimit
) CrossChainTellerWithGenericBridge(_owner, _vault, _accountant, _weth) CCIPReceiver(_router) {
messageGasLimit = _messageGasLimit;
}

// ========================================= ADMIN FUNCTIONS =========================================
// TODO methods to change params of a chain.
function addChain(
uint256 chainId,
bool allowMessagesFrom,
address fromAddress,
bool allowMessagesTo,
address toAddress
) external requiresAuth {
idToChains[chainId] = Chain(allowMessagesFrom, fromAddress, allowMessagesTo, toAddress);

emit ChainAdded(chainId, allowMessagesFrom, fromAddress, allowMessagesTo, toAddress);
}
// ========================================= CCIP RECEIVER =========================================

function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override {
Chain memory source = idToChains[any2EvmMessage.sourceChainSelector];
if (!source.allowMessagesFrom) {
revert ChainlinkCCIPTeller__MessagesNotAllowedFrom(any2EvmMessage.sourceChainSelector);
}
address sender = abi.decode(any2EvmMessage.sender, (address));
if (source.fromAddress != sender) {
revert ChainlinkCCIPTeller__MessagesNotAllowedFromSender(any2EvmMessage.sourceChainSelector, sender);
}
uint256 message = abi.decode(any2EvmMessage.data, (uint256));

_completeMessageReceive(any2EvmMessage.messageId, message);
}

// ========================================= 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.
// */

// ========================================= INTERNAL BRIDGE FUNCTIONS =========================================
function _sendMessage(uint256 message, bytes calldata bridgeWildCard, ERC20 feeToken, uint256 maxFee)
internal
override
returns (bytes32 messageId)
{
uint64 destinationId = abi.decode(bridgeWildCard, (uint64));
Chain memory chain = idToChains[destinationId];
if (!chain.allowMessagesTo) {
revert ChainlinkCCIPTeller__MessagesNotAllowedTo(destinationId);
}

// Build the message.
Client.EVM2AnyMessage memory m = _buildMessage(message, chain.toAddress, address(feeToken));

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

uint256 fee = router.getFee(destinationId, m);

if (fee > maxFee) {
revert ChainlinkCCIPTeller__FeeExceedsMax(destinationId, fee, maxFee);
}

feeToken.safeTransferFrom(msg.sender, address(this), fee);
feeToken.safeApprove(address(router), fee);

messageId = router.ccipSend(destinationId, m);
}

function _previewFee(uint256 message, bytes calldata bridgeWildCard, ERC20 feeToken)
internal
view
override
returns (uint256 fee)
{
uint64 destinationId = abi.decode(bridgeWildCard, (uint64));
Chain memory chain = idToChains[destinationId];
Client.EVM2AnyMessage memory m = _buildMessage(message, chain.toAddress, address(feeToken));

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

fee = router.getFee(destinationId, m);
}

function _buildMessage(uint256 message, address to, address feeToken)
internal
view
returns (Client.EVM2AnyMessage memory m)
{
m = Client.EVM2AnyMessage({
receiver: abi.encode(to),
data: abi.encode(message),
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: feeToken
});
}
}
80 changes: 74 additions & 6 deletions src/base/Roles/CrossChain/CrossChainTellerWithGenericBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@
pragma solidity 0.8.21;

import {TellerWithMultiAssetSupport, ERC20} from "src/base/Roles/TellerWithMultiAssetSupport.sol";
import {MessageLib} from "src/base/Roles/CrossChain/MessageLib.sol";

contract CrossChainTellerWithGenericBridge is TellerWithMultiAssetSupport {
abstract contract CrossChainTellerWithGenericBridge is TellerWithMultiAssetSupport {
using MessageLib for uint256;
using MessageLib for MessageLib.Message;
// ========================================= STRUCTS =========================================

struct ChainLimts {
uint256 maxCumulativeOutflow;
}
// ========================================= CONSTANTS =========================================
// ========================================= STATE =========================================
//============================== ERRORS ===============================
//============================== EVENTS ===============================

event MessageSent(bytes32 messageId, uint256 shareAmount, address to);
event MessageReceived(bytes32 messageId, uint256 shareAmount, address to);
//============================== IMMUTABLES ===============================

constructor(address _owner, address _vault, address _accountant, address _weth)
Expand All @@ -18,13 +28,71 @@ contract CrossChainTellerWithGenericBridge is TellerWithMultiAssetSupport {
// ========================================= ADMIN FUNCTIONS =========================================
// ========================================= PUBLIC FUNCTIONS =========================================

function bridge() external requiresAuth {}
function bridge(uint96 shareAmount, address to, bytes calldata bridgeWildCard, ERC20 feeToken, uint256 maxFee)
external
requiresAuth
{
if (isPaused) revert TellerWithMultiAssetSupport__Paused();
// Burn shares from sender
vault.exit(address(0), ERC20(address(0)), 0, msg.sender, shareAmount);

// Send the message.
MessageLib.Message memory m = MessageLib.Message(shareAmount, to);
// `messageToUnit256` reverts on overflow, eventhough it is not possible to overflow.
// This was done for future proofing.
uint256 message = m.messageToUint256();

bytes32 messageId = _sendMessage(message, bridgeWildCard, feeToken, maxFee);

emit MessageSent(messageId, shareAmount, to);
}

function previewFee(uint96 shareAmount, address to, bytes calldata bridgeWildCard, ERC20 feeToken)
external
view
returns (uint256 fee)
{
MessageLib.Message memory m = MessageLib.Message(shareAmount, to);
uint256 message = m.messageToUint256();

function depositAndBridge() external requiresAuth {
// TODO so if this is used, the shares should become locked on the destination chain.
return _previewFee(message, bridgeWildCard, feeToken);
}

function depositWithPermitAndBridge() external requiresAuth {
// TODO so if this is used, the shares should become locked on the destination chain.
// ========================================= INTERNAL BRIDGE FUNCTIONS =========================================
// Should be called in the receive message function for given bridge implementation, once message has been confirmed is legit.
/**
* @notice Complete the message receive process, should be called in child contract once
* message has been confirmed as legit.`
*/
function _completeMessageReceive(bytes32 messageId, uint256 message) internal {
MessageLib.Message memory m = message.uint256ToMessage();

// Mint shares to message.to
vault.enter(address(0), ERC20(address(0)), 0, m.to, m.shareAmount);

emit MessageReceived(messageId, m.shareAmount, m.to);
}

/**
* @notice Send the message to the bridge implementation.
* @dev This function should handle reverting if maxFee exceeds the fee required to send the message.
* @dev This function should handle collecting the fee.
* @param message The message to send.
* @param bridgeWildCard The bridge specific data to configure message.
* @param feeToken The token to pay the bridge fee in.
* @param maxFee The maximum fee to pay the bridge.
*/
function _sendMessage(uint256 message, bytes calldata bridgeWildCard, ERC20 feeToken, uint256 maxFee)
internal
virtual
returns (bytes32 messageId);

/**
* @notice Preview fee required to bridge shares in a given token.
*/
function _previewFee(uint256 message, bytes calldata bridgeWildCard, ERC20 feeToken)
internal
view
virtual
returns (uint256 fee);
}
30 changes: 30 additions & 0 deletions src/base/Roles/CrossChain/MessageLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

library MessageLib {
error MessageLib__ShareAmountOverflow();
/**
* @notice Messages are transferred between chains as uint256
* The first 96 bits are the share amount.
* The remaining 160 bits are the address to send the shares to.
* @dev Using a uint256 was chosen because most bridging protocols charge based off the number of
* bytes sent, and packing a uint256 in this way caps it at 32 bytes.
*/

struct Message {
uint256 shareAmount; // The amount of shares to bridge.
address to;
}

function uint256ToMessage(uint256 b) internal pure returns (Message memory m) {
m.shareAmount = uint96(b >> 160);
m.to = address(uint160(b));
}

function messageToUint256(Message memory m) internal pure returns (uint256 b) {
if (m.shareAmount >= 1 << 96) revert MessageLib__ShareAmountOverflow();

b |= m.shareAmount << 160;
b |= uint160(m.to);
}
}

0 comments on commit cd4e86d

Please sign in to comment.