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

solana: support any extra args on ccip_send #488

Merged
merged 12 commits into from
Jan 30, 2025
107 changes: 5 additions & 102 deletions chains/solana/contracts/programs/ccip-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,106 +2,6 @@

Collapsed Router + OnRamp + OffRamp Contracts Implementation for CCIP in SVM.

## Messages

### Initialization

`initialize`

1. Initialize the Config PDA with the SVM Chain Selector, the Default Extra Args and the Supported Destination Chain Selectors and the Sequence Number with 0.

#### Initialization Accounts

1. `PDA("config", program_id)`: **Init**, it is used to store the default configuration for Extra Args, the SVM Chain Selector and the supported list of Destination Chain Selectors.
1. `signer`: The sender of the message.

### Update Config

`add_chain_selector`, `remove_chain_selector`, `update_svm_chain_selector`, `update_default_gas_limit` & `update_default_allow_out_of_order_execution`

1. Update the Config PDA with the SVM Chain Selector.
1. Update the Config PDA with the Default Extra Args.
1. Init/Close the Chain State PDA with the Supported Destination Chain Selectors (add/remove).

#### Update Config Accounts

1. `PDA("config", program_id)`: **Mut**, it is used to store the default configuration for Extra Args, the SVM Chain Selector and the supported list of Destination Chain Selectors.
1. `PDA("chain_state", chain_selector, program_id)`: **Init/Close**, it stores the latest sequence number per destination chain [only for `add_chain_selector` & `remove_chain_selector`].
1. `signer`: The sender of the message.

### Send Message

`ccipSend`

1. Check if the Destination Chain Selector is supported (enforced by the `chain_state` account).
1. Emit `CCIPMessageSent` event.
1. Update the Sequence Number.

#### Send Message Accounts

1. `PDA("config", program_id)`: **Read only**, it is used to read the default configuration for Extra Args, the SVM Chain Selector and the supported list of Destination Chain Selectors.
1. `PDA("chain_state", chain_selector, program_id)`: **Mut**, increases the latest sequence number per destination chain.
1. `signer`: The sender of the message.

### Commit Report

`commit`

1. Check if the Source Chain Selector is supported.
1. Check if the Interval is valid.
1. Check if the Merkle Root is not empty and is new.
1. Update the Source Chain Reports with the new report.
1. Emit `CommitReportAccepted` event.
1. Emit `Transmitted` event.

#### Commit Report Accounts

1. `PDA("chain_state", chain_selector, program_id)`: **Read only**, checks if the Source Chain Selector is supported.
1. `PDA("commit_report", chain_selector, merkle_root, program_id)`: **Init**, stores the Merkle Root for the new Report.
1. `signer`: The sender of the message.

### Execute Report

`executeReport`

1. Validates that the report was commited
1. Validates that the report was not executed (if not emit `SkippedAlreadyExecutedMessage` or `AlreadyAttempted` events)
1. Validates that the Merkle Proof is correct
1. Executes the message
1. Emit `ExecutionStateChanged` event

#### Execute Report Accounts

1. `PDA("config", program_id)`: **Read only**, checks if the Chain Selectors are supported.
1. `PDA("commit_report", chain_selector, merkle_root, program_id)`: **Mut**, verifies the Merkle Root for the Report and updates state.
1. `signer`: The sender of the message.

## Future Work

- _EMIT_: When Emitting `ExecutionStateChanged` events in the execute report, there are two values that are not being correctly populated: `message_id` & `new_state`.
- _EXTRA ARGS_:
- What should we do when they are empty? Not serialize them in the hash? Now it's not allowed to be empty in the client.
- Fix override for extra args: now it only works for `gasLimit`, it should work for `allowOutOfOrderExecution` too.
- Decide if it makes sense to have a param named `gasLimit`
- _TYPES_
- [blocked] Use `pub type Selector = u64;` instead of just `u64` in `ccip_send` args. It seems like there are some issues when parsing that in the Typescript tests, not only as a param in messages but also inside the Message struct.
- Anchor v0.29.0 bug that requires parameter naming to match type - https://github.com/coral-xyz/anchor/issues/2820
- Anchor-Go does not support code generation for aliased types
- Attempted changes ([#92](https://github.com/smartcontractkit/chainlink-internal-integrations/pull/92/commits/2c700c430a78f3e63831d7cd0565bcc7206a1eeb)) for future reference
- Use `[u128; 2]` to store the `Interval` in the `commit` message, this way we will be able to handle a maximum of 128 messages per report to be compatible with EVM.
- _ENABLE/DISABLE CHAINS_: Currently you can add/remove chain selectors, but maybe we need a way to enable/disable them (and only as destination or source).
- _UNIFY ERRORS_: Review the type of errors, and decide if we should have more granularity of them.
- _EXTERNAL EXECUTION_: Understand and documents the limitations over the external execution of the messages.

## Future Work (Production Readiness)

- _FEES_: Add fees for the OnRamp flow [WIP]
- _TOKEN POOLS_: Add the flow related to sending tokens and burning/minting in the Token Pools
- _RMN_: Add the flow related to RMN and cursed/blessed lanes or messages
- _RATE LIMIT_: Add rate limit for the ccipSend/executeReports messages
- _NONCES_: Use nonces for the ccipSend message per user, so they can execute ordered transactions on destination chain.
- _ORDERED EXECUTION_: Validate nonces when executing reports in the Off Ramp.

## Testing

- The Rust Tests execute logic specific to the program, to run them use the command
Expand All @@ -110,8 +10,11 @@ Collapsed Router + OnRamp + OffRamp Contracts Implementation for CCIP in SVM.
cargo test
```

- The Anchor Tests are in the `tests` folder, written in Typescript and use the `@project-serum/solana-web3` library. The tests are run in a local network, so the tests are fast and don't require any real SVM network. To run them, use the command
- The Golang Tests are in the `../contracts/tests` folder, using generated bindings from `gagliardetto/anchor-go` library. The tests are run in a local network, so the tests are fast and don't require any real SVM network. To run them, use the command

```bash
anchor test
anchor build # build contract artifacts
go test ./... -count=1 -failfast
```

Note: the [solana-test-validator](https://docs.anza.xyz/cli/examples/test-validator) must be installed
59 changes: 59 additions & 0 deletions chains/solana/contracts/programs/ccip-router/src/extra_args.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use anchor_lang::prelude::*;

use crate::DestChainConfig;

// NOTE: this file contains ExtraArgs for SVM -> remote chains
// these are onramp specific extraArgs for ccipSend only

// bytes4(keccak256("CCIP EVMExtraArgsV2"));
pub const EVM_EXTRA_ARGS_V2_TAG: u32 = 0x181dcf10;

// bytes4(keccak256("CCIP SVMExtraArgsV1"));
pub const SVM_EXTRA_ARGS_V1_TAG: u32 = 0x1f3b3aba;

#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
pub struct EVMExtraArgsV2 {
pub gas_limit: u128, // message gas limit for EVM execution
pub allow_out_of_order_execution: bool, // user configurable OOO execution that must match with the DestChainConfig.EnforceOutOfOrderExecution
}

impl EVMExtraArgsV2 {
pub fn serialize_with_tag(&self) -> Vec<u8> {
let mut buffer = EVM_EXTRA_ARGS_V2_TAG.to_be_bytes().to_vec();
let mut data = self.try_to_vec().unwrap();

buffer.append(&mut data);
buffer
}
pub fn default_config(cfg: &DestChainConfig) -> EVMExtraArgsV2 {
EVMExtraArgsV2 {
gas_limit: cfg.default_tx_gas_limit as u128,
..Default::default()
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this line do? Doesn't the DestChainConfig have the default value for OOO as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not quite, the DestChain config has EnforceOutOfOrder to make sure that the user sets out of order - otherwise, we don't validate

and on the EVM side, if EnforceOutOfOrder = true then it requires the user to explicitly set it rather than use a default

}
}
}

#[derive(Clone, AnchorSerialize, AnchorDeserialize, Default)]
pub struct SVMExtraArgsV1 {
pub compute_units: u32, // compute units are used by the offchain to add computeUnitLimits to the execute stage
pub account_is_writable_bitmap: u64,
pub allow_out_of_order_execution: bool, // SVM chains require OOO, but users are required to explicitly state OOO is allowed in extraArgs
pub token_receiver: [u8; 32], // cannot be 0-address if tokens are sent in message
pub accounts: Vec<[u8; 32]>, // account list for messaging on remote SVM chain
}

impl SVMExtraArgsV1 {
pub fn serialize_with_tag(&self) -> Vec<u8> {
let mut buffer = SVM_EXTRA_ARGS_V1_TAG.to_be_bytes().to_vec();
let mut data = self.try_to_vec().unwrap();

buffer.append(&mut data);
buffer
}
pub fn default_config(cfg: &DestChainConfig) -> SVMExtraArgsV1 {
SVMExtraArgsV1 {
compute_units: cfg.default_tx_gas_limit,
..Default::default()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,32 +165,6 @@ pub fn update_svm_chain_selector(
Ok(())
}

pub fn update_default_gas_limit(
ctx: Context<UpdateConfigCCIPRouter>,
new_gas_limit: u128,
) -> Result<()> {
let mut config = ctx.accounts.config.load_mut()?;

config.default_gas_limit = new_gas_limit;

Ok(())
}

pub fn update_default_allow_out_of_order_execution(
ctx: Context<UpdateConfigCCIPRouter>,
new_allow_out_of_order_execution: bool,
) -> Result<()> {
let mut config = ctx.accounts.config.load_mut()?;

let mut v = 0_u8;
if new_allow_out_of_order_execution {
v = 1;
}
config.default_allow_out_of_order_execution = v;

Ok(())
}

pub fn update_enable_manual_execution_after(
ctx: Context<UpdateConfigCCIPRouter>,
new_enable_manual_execution_after: i64,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::{
};

use super::messages::ramps::validate_svm2any;
use super::onramp::ProcessedExtraArgs;
use super::pools::CCIP_LOCK_OR_BURN_V1_RET_BYTES;
use super::price_math::get_validated_token_price;
use super::price_math::{Exponential, Usd18Decimals};
Expand All @@ -32,13 +33,14 @@ pub const SVM_2_EVM_MESSAGE_FIXED_BYTES: U256 = U256::new(32 * 15);
/// uint32 destGasAmount takes 1 slot.
pub const SVM_2_EVM_MESSAGE_FIXED_BYTES_PER_TOKEN: U256 = U256::new(32 * ((2 * 3) + 3));

// fee_for_msg returns the required fee for ccipSend and validated extraArgs parameters for the destination chain family
pub fn fee_for_msg(
message: &SVM2AnyMessage,
dest_chain: &DestChain,
fee_token_config: &BillingTokenConfig,
additional_token_configs: &[Option<BillingTokenConfig>],
additional_token_configs_for_dest_chain: &[PerChainPerTokenConfig],
) -> Result<SVMTokenAmount> {
) -> Result<(SVMTokenAmount, ProcessedExtraArgs)> {
let fee_token = if message.fee_token == Pubkey::default() {
native_mint::ID // Wrapped SOL
} else {
Expand All @@ -52,7 +54,7 @@ pub fn fee_for_msg(
additional_token_configs_for_dest_chain.len() == message.token_amounts.len(),
CcipRouterError::InvalidInputsMissingTokenConfig
);
validate_svm2any(message, dest_chain, fee_token_config)?;
let processed_extra_args = validate_svm2any(message, dest_chain, fee_token_config)?;

let fee_token_price = get_validated_token_price(fee_token_config)?;
let PackedPrice {
Expand All @@ -67,12 +69,7 @@ pub fn fee_for_msg(
additional_token_configs_for_dest_chain,
)?;

let gas_limit = U256::new(
message
.extra_args
.gas_limit
.unwrap_or_else(|| dest_chain.config.default_tx_gas_limit.into()),
);
let gas_limit = U256::new(processed_extra_args.gas_limit);

// Calculate calldata gas cost while accounting for EIP-7623 variable calldata gas pricing
// This logic works for EVMs post Pectra upgrade, while being backwards compatible with pre-Pectra EVMs.
Expand Down Expand Up @@ -127,10 +124,13 @@ pub fn fee_for_msg(
.try_into()
.map_err(|_| CcipRouterError::InvalidTokenPrice)?;

Ok(SVMTokenAmount {
token: fee_token,
amount: fee_token_amount,
})
Ok((
SVMTokenAmount {
token: fee_token,
amount: fee_token_amount,
},
processed_extra_args,
))
}

fn data_availability_cost(
Expand Down Expand Up @@ -423,7 +423,8 @@ pub mod tests {
&[],
&[]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
amount: 48282184443231661
Expand All @@ -444,7 +445,8 @@ pub mod tests {
&[],
&[]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
// Increases proportionally to the network fee component of the sum
Expand Down Expand Up @@ -504,7 +506,8 @@ pub mod tests {
&[Some(token_config)],
&[per_chain_per_token]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
amount: 52911699750913573,
Expand Down Expand Up @@ -538,7 +541,8 @@ pub mod tests {
&[Some(another_token_config)],
&[another_per_chain_per_token_config]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
// Increases proportionally to the min_fee
Expand Down Expand Up @@ -575,7 +579,8 @@ pub mod tests {
&[Some(another_token_config.clone())],
&[another_per_chain_per_token_config.clone()]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
amount: 36654452956230811
Expand All @@ -593,7 +598,8 @@ pub mod tests {
&[Some(another_token_config)],
&[another_per_chain_per_token_config]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
// Slight increase in price
Expand Down Expand Up @@ -628,7 +634,8 @@ pub mod tests {
&[None],
&[another_per_chain_per_token_config.clone()]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
amount: 35758615812975735
Expand All @@ -646,7 +653,8 @@ pub mod tests {
&[None],
&[another_per_chain_per_token_config.clone()]
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
amount: 35758615812975735
Expand Down Expand Up @@ -682,7 +690,8 @@ pub mod tests {
&tokens,
&per_chains
)
.unwrap(),
.unwrap()
.0,
SVMTokenAmount {
token: native_mint::ID,
// Increases proportionally to the number of tokens
Expand Down
Loading
Loading