Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions contracts/contracts/ccip/ccipsend_executor/contract.tolk
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "@stdlib/gas-payments"

import "types"
import "messages"
import "storage"
Expand All @@ -7,10 +9,8 @@ import "../onramp/messages"
import "../../lib/utils"
import "../router/messages"
import "../fee_quoter/types"
import "@stdlib/gas-payments"
import "../common/types"
import "../token_registry/messages"
import "../pools/messages"
import "../lib/token_pool/messages"

tolk 1.4.1

Expand Down
2 changes: 1 addition & 1 deletion contracts/contracts/ccip/ccipsend_executor/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "types"
import "../onramp/messages"
import "../fee_quoter/messages"
import "../token_registry/messages"
import "../pools/messages"
import "../lib/token_pool/messages"

type CCIPSendExecutor_InMessage =
| CCIPSendExecutor_Execute
Expand Down
30 changes: 30 additions & 0 deletions contracts/contracts/ccip/lib/token_pool/messages.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import "../../common/types"
import "types"

type TokenPool_InMessage =
| TokenPool_LockOrBurn
| TokenPool_ReleaseOrMint
;

/// Incoming messages

struct (0x7dd8f942) TokenPool_LockOrBurn {
tokenAmount: TokenAmount, // TODO LockOrBurn doesn't need to know the token address
notify: address,
}

struct (0x351f77e3) TokenPool_ReleaseOrMint {
queryId: uint64;
request: Cell<TokenPool_ReleaseOrMintInV1>;
requestedFinalityConfig: uint32;
replyTo: address? = null;
}

/// Outgoing messages

struct (0x4c700579) TokenPool_NotifySuccessfulLockOrBurn {}

struct (0xe0e882f5) TokenPool_ReleaseOrMintFinished {
queryId: uint64;
out: Cell<TokenPool_ReleaseOrMintOutV1>; // TBD do we need this info?
}
128 changes: 128 additions & 0 deletions contracts/contracts/ccip/lib/token_pool/types.tolk
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import "../../rmn_remote/lib"
import "../../common/types"

// SPDX-License-Identifier: BUSL-1.1

const TOKEN_POOL_WAIT_FOR_FINALITY_FLAG: uint32 = 0;
const TOKEN_POOL_DEFAULT_FINALITY: uint32 = TOKEN_POOL_WAIT_FOR_FINALITY_FLAG;
/// The division factor for bps. This also represents the maximum bps fee.
const TOKEN_POOL_BPS_DIVIDER: uint256 = 10000;

// The number of bytes in the return data for a pool v1 releaseOrMint call.
// This should match the size of the ReleaseOrMintOutV1 struct.
const CCIP_POOL_V1_RET_BYTES = 32;

// The default max number of bytes in the return data for a pool v1 lockOrBurn call.
// This data can be used to send information to the destination chain token pool. Can be overwritten
// in the TokenTransferFeeConfig.destBytesOverhead if more data is required.
const CCIP_LOCK_OR_BURN_V1_RET_BYTES = 32;

struct TokenPool_DynamicConfig {
router: address;
rateLimitAdmin: address?;
feeAdmin: address?;
}

struct TokenPool_MirroredPolicy {
onRamps: map<uint64, address>;
offRamps: map<uint64, address>;
cursedSubjects: CursedSubjects;
}

struct TokenPool_RampUpdate {
remoteChainSelector: uint64;
onRamp: address? = null;
offRamp: address? = null;
}

/// Struct with args for setting the token transfer fee configurations for a destination chain and a set of tokens.
struct TokenPool_TokenTransferFeeConfigArgs {
destChainSelector: uint64; // Destination chain selector.
tokenTransferFeeConfig: TokenPool_TokenTransferFeeConfig; // Token transfer fee configuration.
}

// TODO: triage new types below

struct TokenPool_LockOrBurnPrepared {
feeAmount: uint256;
destTokenAmount: uint256;
out: TokenPool_LockOrBurnOutV1;
}

struct TokenPool_ReleaseOrMintPrepared {
requestedFinalityConfig: uint32;
localAmount: uint256;
out: TokenPool_ReleaseOrMintOutV1;
}

// IPoolV2

struct TokenPool_TokenTransferFeeConfig {
destGasOverhead: uint32; // Gas charged to execute the token transfer on the destination chain.
destBytesOverhead: uint32; // Data availability bytes.
finalityFeeUSDCents: uint256; // Fee to charge for token transfer with default (wait-for-finality) finality, multiples of 0.01 USD.
fastFinalityFeeUSDCents: uint256; // Fee to charge for token transfer with fast finality (FTF), multiples of 0.01 USD.
// The following two fee is deducted from the transferred asset, not added on top.
finalityTransferFeeBps: uint16; // Fee in basis points for default finality transfers [0-10_000].
fastFinalityTransferFeeBps: uint16; // Fee in basis points for custom finality transfers [0-10_000].
isEnabled: bool; // Whether this config is enabled.
}

enum TokenPool_MessageDirection: uint8 {
Outbound = 0
Inbound = 1
}

struct TokenPool_Transfer<S, R> {
id : uint256; // Unique transfer ID for this TP operation.
details: Cell<TokenPool_TransferDetails<S, R>>; // Details of the transfer.
}

struct TokenPool_TransferDetails<S, R> {
receiver: R; // The recipient of the tokens on the destination chain. For EVM source chains, this is abi-encoded (32 bytes).
remoteChainSelector: uint64; // ─╮ The chain ID of the destination chain.
originalSender: S; // ─╯ The original sender of the tx on the source chain.
amount: uint256; // The amount of tokens to process, denominated in the source token's decimals.
localToken: address; // The address on this chain of the token to process.
}

// Typed transfers for both directions
type TokenPool_LockOrBurnTransfer = TokenPool_Transfer<
address, // sender addr
Cell<CrossChainAddress>, // receiver addr
>;
type TokenPool_ReleaseOrMintTransfer = TokenPool_Transfer<
Cell<CrossChainAddress>, // sender addr
address // receiver addr
>;

struct TokenPool_LockOrBurnInV1 {
transfer: TokenPool_LockOrBurnTransfer; // The transfer id/details for the lock or burn operation.
}

struct TokenPool_LockOrBurnOutV1 {
// The address of the destination token, abi encoded in the case of EVM chains.
// This value is UNTRUSTED as any pool owner can return whatever value they want.
destTokenAddress: Cell<CrossChainAddress>;
// Optional pool data to be transferred to the destination chain. Be default this is capped at
// CCIP_LOCK_OR_BURN_V1_RET_BYTES bytes. If more data is required, the TokenTransferFeeConfig.destBytesOverhead
// has to be set for the specific token.
destPoolData: cell;
}

struct TokenPool_ReleaseOrMintInV1 {
transfer: TokenPool_ReleaseOrMintTransfer; // The transfer id/details for the release or mint operation.
/// @dev WARNING: sourcePoolAddress should be checked prior to any processing of funds. Make sure it matches the
/// expected pool address for the given remoteChainSelector.
sourcePoolAddress: Cell<CrossChainAddress>; // The address of the source pool, abi encoded in the case of EVM chains.
sourcePoolData: cell?; // The data received from the source pool to process the release or mint.
/// @dev WARNING: offchainTokenData is untrusted data.
offchainTokenData: cell?; // The offchain data to process the release or mint.
}

struct TokenPool_ReleaseOrMintOutV1 {
// The number of tokens released or minted on the destination chain, denominated in the local token's decimals.
// This value is expected to be equal to the ReleaseOrMintInV1.amount in the case where the source and destination
// chain have the same number of decimals.
destinationAmount: uint256;
}
35 changes: 35 additions & 0 deletions contracts/contracts/ccip/offramp/contract.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import "../merkle_root/storage"
import "../receive_executor/messages"
import "../receive_executor/storage"
import "../router/messages"
import "../token_registry/types"
import "../lib/token_pool/messages"

import "messages"
import "errors"
Expand Down Expand Up @@ -66,6 +68,12 @@ fun onInternalMessage(in:InMessage) {
assertSenderIsReceiverExecutor(st, msg.execId, in.senderAddress);
onDispatchValidated(st, msg)
}
// Sender must be ReceiveExecutor
OffRamp_ReleaseOrMint => {
val st = lazy Storage.load();
assertSenderIsReceiverExecutor(st, msg.execId, in.senderAddress);
onReleaseOrMint(in.senderAddress, msg)
}
// Permissionless
OffRamp_ManuallyExecute => {
onManuallyExecute(msg, in.valueCoins)
Expand Down Expand Up @@ -438,6 +446,21 @@ fun blockMsg(st: Storage, execId: uint192, receiver: address) {
receiveBounced.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
}

fun onReleaseOrMint(sendExecutor: address, msg: OffRamp_ReleaseOrMint) {
val releaseOrMintMsg = createMessage({
bounce: BounceMode.RichBounce,
value: 0,
dest: msg.tokenPool,
body: TokenPool_ReleaseOrMint {
queryId: msg.queryId,
request: msg.request,
requestedFinalityConfig: msg.requestedFinalityConfig,
replyTo: sendExecutor,
}
});
releaseOrMintMsg.send(SEND_MODE_CARRY_ALL_REMAINING_MESSAGE_VALUE);
}

// Sets a new OCR3 configuration for the contract. Only the contract owner can call this function.
fun onSetOCR3Config(msg: OCR3Base_SetOCR3Config) {
var st = lazy Storage.load();
Expand Down Expand Up @@ -800,6 +823,8 @@ fun onExecuteValidated(st: Storage, msg: OffRamp_ExecuteValidated, sender: addre

val executorAddress: AutoDeployAddress = getReceiverExecutorDeployAddress(st, execId);

val tokenAdminRegistry = getTokenAdminRegistry(st, message.tokenAmounts);

// Check if this is the first time the exec flow is running given that in that case we need to
// initialize the executor
if (msg.executionState == ExecutionState.Untouched) {
Expand All @@ -815,6 +840,7 @@ fun onExecuteValidated(st: Storage, msg: OffRamp_ExecuteValidated, sender: addre
message: message.toCell(),
root: sender,
execId,
tokenAdminRegistry,
}.toCell(),
}
},
Expand All @@ -838,6 +864,15 @@ fun onExecuteValidated(st: Storage, msg: OffRamp_ExecuteValidated, sender: addre
initExecMsg.send(SEND_MODE_PAY_FEES_SEPARATELY);
}

fun getTokenAdminRegistry(st: Storage, tokenAmounts: SnakedCell<Any2TVMTokenTransfer>?): Cell<address>? {
if (tokenAmounts == null) {
return null;
}
val tokenTransfer = tokenAmounts.getTokenTransfer(Error.UnsupportedNumberOfTokens);
val registryAddress = TokenRegistry_DeriveAddress( st.deployables.load().deployer, tokenTransfer.token).calculateAddress();
return registryAddress.toCell();
}

/*
Retrieves the Router address associated with a given sourceChainSelector.
Throws an error if the source chain is not enabled in the stored configs.
Expand Down
1 change: 1 addition & 0 deletions contracts/contracts/ccip/offramp/errors.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ enum Error {
OnRampAddressMismatch
EmptyCommitReport
MerkleRootCannotBeZero
UnsupportedNumberOfTokens
}
9 changes: 9 additions & 0 deletions contracts/contracts/ccip/offramp/messages.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import "../merkle_root/messages"
import "../receive_executor/messages"
import "../fee_quoter/messages"
import "../router/messages"
import "../lib/token_pool/types"

type OffRamp_InMessage =
| OffRamp_Commit
| OffRamp_Execute
| OffRamp_ExecuteValidated
| OffRamp_ManuallyExecute
| OffRamp_DispatchValidated
| OffRamp_ReleaseOrMint
| OffRamp_UpdateSourceChainConfigs
| OffRamp_CCIPReceiveConfirm
| OffRamp_CCIPReceiveBounced
Expand Down Expand Up @@ -119,6 +121,13 @@ struct (0xa015e0e2) OffRamp_UpdateDeployables {
merkleRootCode: cell?;
}

struct (0x351f77e3) OffRamp_ReleaseOrMint {
queryId: uint64
tokenPool: address
requestedFinalityConfig: uint32
request: Cell<TokenPool_ReleaseOrMintInV1>
}

struct OffRamp_Costs{}

fun OffRamp_Costs.executeValidated(): int {
Expand Down
15 changes: 12 additions & 3 deletions contracts/contracts/ccip/offramp/types.tolk
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ struct Any2TVMRampMessage {
// maybe mark these amounts as slice remaining then parse them by hand to avoid requiring this to be a map<> at send time?
}

// Helper to extract the single token transfer from the message, and assert that there is only one token transfer
fun SnakedCell<Any2TVMTokenTransfer>.getTokenTransfer(self, error: int): Any2TVMTokenTransfer {
var it = self.iter();
assert (!it.empty(), error);
val token = it.next();
assert (it.empty(), error);
return token
}

fun Any2TVMRampMessage.generateMessageId(self, metadataHash: uint256): uint256 {
// Loosely based on https://github.com/smartcontractkit/chainlink-ccip/blob/2d2ce6abb3f7626dbd3008e4232da4faa839feff/chains/evm/contracts/libraries/Internal.sol#L113-L134
return beginCell()
Expand Down Expand Up @@ -99,10 +108,10 @@ struct MerkleRoot {

struct Any2TVMTokenTransfer {
sourcePoolAddress: Cell<CrossChainAddress>;
destPoolAddress: address;
destGasAmount: uint32;
token: address;
destGasAmount: uint32; // TBD: This field was removed in CCIP 2.0 and it's purpose/utility in TON is unclear.
extraData: cell;
amount: uint256;
amount: coins; // TBD: Does this change break any message ID we set in the past? It would but we didn't support token transfers then
}

struct OffRamp_Deployables {
Expand Down
1 change: 0 additions & 1 deletion contracts/contracts/ccip/pools/messages.tolk

This file was deleted.

Loading
Loading