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

feat: Open/Close Solana contract swap accounts #5321

Closed
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
5 changes: 5 additions & 0 deletions foreign-chains/solana/sol-prim/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,8 @@ pub const LAMPORTS_PER_SIGNATURE: u64 = 5000u64;
pub const NONCE_ACCOUNT_LENGTH: u64 = 80u64;

pub const SOL_USDC_DECIMAL: u8 = 6u8;

// todo: confirm this
ramizhasan111 marked this conversation as resolved.
Show resolved Hide resolved
pub const MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES: usize = 10;
pub const MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS: u32 = 14400; // 1 day
pub const NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES: usize = 4; // revisit this
7 changes: 7 additions & 0 deletions state-chain/chains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#![feature(split_array)]

use core::{fmt::Display, iter::Step};
use sol::api::ContractSwapAccountAndSender;
use sp_std::marker::PhantomData;

use crate::{
Expand Down Expand Up @@ -485,6 +486,12 @@ pub trait RegisterRedemption: ApiCall<<Ethereum as Chain>::ChainCrypto> {
) -> Self;
}

pub trait CloseSolanaContractSwapAccounts: ApiCall<<Solana as Chain>::ChainCrypto> {
fn new_unsigned(
accounts: Vec<ContractSwapAccountAndSender>,
) -> Result<Self, SolanaTransactionBuildingError>;
}

#[derive(Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub enum AllBatchError {
/// Empty transaction - the call is not required.
Expand Down
9 changes: 7 additions & 2 deletions state-chain/chains/src/sol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ pub mod transaction_builder;
pub use crate::assets::sol::Asset as SolAsset;
use crate::benchmarking_value::BenchmarkValue;
pub use sol_prim::{
consts::{LAMPORTS_PER_SIGNATURE, MAX_TRANSACTION_LENGTH, MICROLAMPORTS_PER_LAMPORT},
consts::{
LAMPORTS_PER_SIGNATURE, MAX_BATCH_SIZE_OF_CONTRACT_SWAP_ACCOUNT_CLOSURES,
MAX_TRANSACTION_LENGTH, MAX_WAIT_BLOCKS_FOR_SWAP_ACCOUNT_CLOSURE_APICALLS,
MICROLAMPORTS_PER_LAMPORT,
NONCE_AVAILABILITY_THRESHOLD_FOR_INITIATING_SWAP_ACCOUNT_CLOSURES,
},
pda::{Pda as DerivedAddressBuilder, PdaError as AddressDerivationError},
Address as SolAddress, Amount as SolAmount, ComputeLimit as SolComputeLimit, Digest as SolHash,
Signature as SolSignature, SlotNumber as SolBlockNumber,
Expand Down Expand Up @@ -146,7 +151,7 @@ pub mod compute_units_costs {
pub const COMPUTE_UNITS_PER_ROTATION: SolComputeLimit = 8_000u32;
pub const COMPUTE_UNITS_PER_SET_GOV_KEY: SolComputeLimit = 15_000u32;
pub const COMPUTE_UNITS_PER_BUMP_DERIVATION: SolComputeLimit = 2_000u32;
pub const COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS: SolComputeLimit = 10_000u32;
pub const COMPUTE_UNITS_PER_CLOSE_CONTRACT_SWAP_ACCOUNTS: SolComputeLimit = 10_000u32;
pub const COMPUTE_UNITS_PER_CLOSE_ACCOUNT: SolComputeLimit = 10_000u32;

pub const MIN_COMPUTE_PRICE: SolAmount = 10u64;
Expand Down
29 changes: 21 additions & 8 deletions state-chain/chains/src/sol/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ use crate::{
SolAsset, SolHash, SolTransaction, SolanaCrypto,
},
AllBatch, AllBatchError, ApiCall, CcmChannelMetadata, Chain, ChainCrypto, ChainEnvironment,
ConsolidateCall, ConsolidationError, ExecutexSwapAndCall, ExecutexSwapAndCallError,
FetchAssetParams, ForeignChainAddress, SetAggKeyWithAggKey, SetGovKeyWithAggKey, Solana,
TransferAssetParams, TransferFallback, TransferFallbackError,
CloseSolanaContractSwapAccounts, ConsolidateCall, ConsolidationError, ExecutexSwapAndCall,
ExecutexSwapAndCallError, FetchAssetParams, ForeignChainAddress, SetAggKeyWithAggKey,
SetGovKeyWithAggKey, Solana, TransferAssetParams, TransferFallback, TransferFallbackError,
};

use cf_primitives::{EgressId, ForeignChain};
Expand All @@ -36,7 +36,12 @@ pub struct ApiEnvironment;
pub struct CurrentAggKey;

pub type DurableNonceAndAccount = (SolAddress, SolHash);
pub type EventAccountAndSender = (SolAddress, SolAddress);

#[derive(Clone, Encode, Decode, PartialEq, Debug, TypeInfo, Copy, Eq)]
pub struct ContractSwapAccountAndSender {
pub contract_swap_account: SolAddress,
pub swap_sender: SolAddress,
}

/// Super trait combining all Environment lookups required for the Solana chain.
/// Also contains some calls for easy data retrieval.
Expand Down Expand Up @@ -373,8 +378,8 @@ impl<Environment: SolanaEnvironment> SolanaApi<Environment> {
})
}

pub fn batch_close_event_accounts(
event_accounts: Vec<EventAccountAndSender>,
pub fn batch_close_contract_swap_accounts(
contract_swap_accounts: Vec<ContractSwapAccountAndSender>,
) -> Result<Self, SolanaTransactionBuildingError> {
// Lookup environment variables, such as aggkey and durable nonce.
let agg_key = Environment::current_agg_key()?;
Expand All @@ -383,8 +388,8 @@ impl<Environment: SolanaEnvironment> SolanaApi<Environment> {
let durable_nonce = Environment::nonce_account()?;

// Build the transaction
let transaction = SolanaTransactionBuilder::close_event_accounts(
event_accounts,
let transaction = SolanaTransactionBuilder::close_contract_swap_accounts(
contract_swap_accounts,
sol_api_environment.vault_program_data_account,
sol_api_environment.swap_endpoint_program,
sol_api_environment.swap_endpoint_program_data_account,
Expand Down Expand Up @@ -510,6 +515,14 @@ impl<Env: 'static> TransferFallback<Solana> for SolanaApi<Env> {
}
}

impl<Env: 'static + SolanaEnvironment> CloseSolanaContractSwapAccounts for SolanaApi<Env> {
fn new_unsigned(
accounts: Vec<ContractSwapAccountAndSender>,
) -> Result<Self, SolanaTransactionBuildingError> {
Self::batch_close_contract_swap_accounts(accounts)
}
}

impl<Environment: SolanaEnvironment> SetGovKeyWithAggKey<SolanaCrypto> for SolanaApi<Environment> {
fn new_unsigned(
_maybe_old_key: Option<<SolanaCrypto as ChainCrypto>::GovKey>,
Expand Down
95 changes: 48 additions & 47 deletions state-chain/chains/src/sol/sol_tx_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,8 +871,9 @@ impl FromStr for Hash {
pub mod sol_test_values {
use crate::{
sol::{
signing_key::SolSigningKey, sol_tx_core::signer::Signer, SolAddress, SolAmount,
SolAsset, SolCcmAccounts, SolCcmAddress, SolComputeLimit, SolHash,
api::ContractSwapAccountAndSender, signing_key::SolSigningKey,
sol_tx_core::signer::Signer, SolAddress, SolAmount, SolAsset, SolCcmAccounts,
SolCcmAddress, SolComputeLimit, SolHash,
},
CcmChannelMetadata, CcmDepositMetadata, ForeignChain, ForeignChainAddress,
};
Expand Down Expand Up @@ -910,51 +911,51 @@ pub mod sol_test_values {
const_address("35uYgHdfZQT4kHkaaXQ6ZdCkK5LFrsk43btTLbGCRCNT");
pub const SWAP_ENDPOINT_PROGRAM_DATA_ACCOUNT: SolAddress =
const_address("2tmtGLQcBd11BMiE9B1tAkQXwmPNgR79Meki2Eme4Ec9");
pub const EVENT_AND_SENDER_ACCOUNTS: [(SolAddress, SolAddress); 11] = [
(
const_address("2cHcSNtikMpjxJfwwoYL3udpy7hedRExyhakk2eZ6cYA"),
const_address("7tVhSXxGfZyHQem8MdZVB6SoRsrvV4H8h1rX6hwBuvEA"),
),
(
const_address("6uuU1NFyThN3KJpU9mYXkGSmd8Qgncmd9aYAWYN71VkC"),
const_address("P3GYr1Z67jdBVimzFjMXQpeuew5TY5txoZ9CvqASpaP"),
),
(
const_address("DmAom3kp2ZKk9cnbWEsnbkLHkp3sx9ef1EX6GWj1JRUB"),
const_address("CS7yX5TKX36ugF4bycmVQ5vqB2ZbNVC5tvtrtLP92GDW"),
),
(
const_address("CJSdHgxwHLEbTsxKsJk9UyJxUEgku2UC9GXRTzR2ieSh"),
const_address("2taCR53epDtdrFZBxzKcbmv3cb5Umc5x9k2YCjmTDAnH"),
),
(
const_address("7DGwjsQEFA7XzZS9z5YbMhYGzWJSh5T78hRrU47RDTd2"),
const_address("FDPzoZj951Hq92jhoFdyzAVyUjyXhL8VEnqBhyjsDhow"),
),
(
const_address("A6yYXUmZHa32mcFRnwwq8ZQKCEYUn9ewF1vWn2wsXN5a"),
const_address("9bNNNU9B52VPVGm6zRccwPEexDHD1ntndD2aNu2un3ca"),
),
(
const_address("2F3365PULNzt7moa9GgHARy7Lumj5ptDQF7wDt6xeuHK"),
const_address("4m5t38fJsvULKaPyWZKWjzfbvnzBGL86BTRNk5vLLUrh"),
),
(
const_address("8sCBWv9tzdf2iC4GNj61UBN6TZpzsLP5Ppv9x1ENX4HT"),
const_address("A3P5kfRU1vgZn7GjNMomS8ye6GHsoHC4JoVNUotMbDPE"),
),
(
const_address("3b1FkNvnvKJ4TzKeft7wA47VfYpjkoHPE4ER13ZTNecX"),
const_address("ERwuPnX66dCZqj85kH9QQJmwcVrzcczBnu8onJY2R7tG"),
),
(
const_address("Bnrp9X562krXVfaY8FnwJa3Mxp1gbDCrvGNW1qc99rKe"),
const_address("2aoZg41FFnTBnuHpkfHdFsCuPz8DhN4dsUW5386XwE8g"),
),
(
const_address("EuLceVgXMaJNPT7C88pnL7DRWcf1poy9BCeWY1GL8Agd"),
const_address("G1iXMtwUU76JGau9cJm6N8wBTmcsvyXuJcC7PtfU1TXZ"),
),
pub const EVENT_AND_SENDER_ACCOUNTS: [ContractSwapAccountAndSender; 11] = [
ContractSwapAccountAndSender {
contract_swap_account: const_address("2cHcSNtikMpjxJfwwoYL3udpy7hedRExyhakk2eZ6cYA"),
swap_sender: const_address("7tVhSXxGfZyHQem8MdZVB6SoRsrvV4H8h1rX6hwBuvEA"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("6uuU1NFyThN3KJpU9mYXkGSmd8Qgncmd9aYAWYN71VkC"),
swap_sender: const_address("P3GYr1Z67jdBVimzFjMXQpeuew5TY5txoZ9CvqASpaP"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("DmAom3kp2ZKk9cnbWEsnbkLHkp3sx9ef1EX6GWj1JRUB"),
swap_sender: const_address("CS7yX5TKX36ugF4bycmVQ5vqB2ZbNVC5tvtrtLP92GDW"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("CJSdHgxwHLEbTsxKsJk9UyJxUEgku2UC9GXRTzR2ieSh"),
swap_sender: const_address("2taCR53epDtdrFZBxzKcbmv3cb5Umc5x9k2YCjmTDAnH"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("7DGwjsQEFA7XzZS9z5YbMhYGzWJSh5T78hRrU47RDTd2"),
swap_sender: const_address("FDPzoZj951Hq92jhoFdyzAVyUjyXhL8VEnqBhyjsDhow"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("A6yYXUmZHa32mcFRnwwq8ZQKCEYUn9ewF1vWn2wsXN5a"),
swap_sender: const_address("9bNNNU9B52VPVGm6zRccwPEexDHD1ntndD2aNu2un3ca"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("2F3365PULNzt7moa9GgHARy7Lumj5ptDQF7wDt6xeuHK"),
swap_sender: const_address("4m5t38fJsvULKaPyWZKWjzfbvnzBGL86BTRNk5vLLUrh"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("8sCBWv9tzdf2iC4GNj61UBN6TZpzsLP5Ppv9x1ENX4HT"),
swap_sender: const_address("A3P5kfRU1vgZn7GjNMomS8ye6GHsoHC4JoVNUotMbDPE"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("3b1FkNvnvKJ4TzKeft7wA47VfYpjkoHPE4ER13ZTNecX"),
swap_sender: const_address("ERwuPnX66dCZqj85kH9QQJmwcVrzcczBnu8onJY2R7tG"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("Bnrp9X562krXVfaY8FnwJa3Mxp1gbDCrvGNW1qc99rKe"),
swap_sender: const_address("2aoZg41FFnTBnuHpkfHdFsCuPz8DhN4dsUW5386XwE8g"),
},
ContractSwapAccountAndSender {
contract_swap_account: const_address("EuLceVgXMaJNPT7C88pnL7DRWcf1poy9BCeWY1GL8Agd"),
swap_sender: const_address("G1iXMtwUU76JGau9cJm6N8wBTmcsvyXuJcC7PtfU1TXZ"),
},
];
pub const RAW_KEYPAIR: [u8; 32] = [
6, 151, 150, 20, 145, 210, 176, 113, 98, 200, 192, 80, 73, 63, 133, 232, 208, 124, 81, 213,
Expand Down
38 changes: 21 additions & 17 deletions state-chain/chains/src/sol/transaction_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ use sol_prim::consts::{

use crate::{
sol::{
api::{DurableNonceAndAccount, EventAccountAndSender, SolanaTransactionBuildingError},
api::{
ContractSwapAccountAndSender, DurableNonceAndAccount, SolanaTransactionBuildingError,
},
compute_units_costs::{
compute_limit_with_buffer, BASE_COMPUTE_UNITS_PER_TX,
COMPUTE_UNITS_PER_BUMP_DERIVATION, COMPUTE_UNITS_PER_CLOSE_ACCOUNT,
COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS, COMPUTE_UNITS_PER_FETCH_NATIVE,
COMPUTE_UNITS_PER_CLOSE_CONTRACT_SWAP_ACCOUNTS, COMPUTE_UNITS_PER_FETCH_NATIVE,
COMPUTE_UNITS_PER_FETCH_TOKEN, COMPUTE_UNITS_PER_ROTATION,
COMPUTE_UNITS_PER_SET_GOV_KEY, COMPUTE_UNITS_PER_TRANSFER_NATIVE,
COMPUTE_UNITS_PER_TRANSFER_TOKEN,
Expand Down Expand Up @@ -445,34 +447,36 @@ impl SolanaTransactionBuilder {

/// Creates an instruction to close a number of open event swap accounts created via program
/// swap.
pub fn close_event_accounts(
event_accounts: Vec<EventAccountAndSender>,
pub fn close_contract_swap_accounts(
contract_swap_accounts: Vec<ContractSwapAccountAndSender>,
vault_program_data_account: SolAddress,
swap_endpoint_program: SolAddress,
swap_endpoint_data_account: SolAddress,
agg_key: SolAddress,
durable_nonce: DurableNonceAndAccount,
compute_price: SolAmount,
) -> Result<SolTransaction, SolanaTransactionBuildingError> {
let number_of_accounts = event_accounts.len();
let event_and_sender_vec: Vec<AccountMeta> = event_accounts
let number_of_accounts = contract_swap_accounts.len();
let swap_and_sender_vec: Vec<AccountMeta> = contract_swap_accounts
.into_iter()
.flat_map(|(event_account, payee)| vec![event_account, payee])
// Both event account and payee should be writable and non-signers
.flat_map(|ContractSwapAccountAndSender { contract_swap_account, swap_sender }| {
vec![contract_swap_account, swap_sender]
})
// Both swap account and payee should be writable and non-signers
.map(|address| AccountMeta::new(address.into(), false))
.collect();

let instructions = vec![SwapEndpointProgram::with_id(swap_endpoint_program)
.close_event_accounts(vault_program_data_account, agg_key, swap_endpoint_data_account)
.with_remaining_accounts(event_and_sender_vec)];
.with_remaining_accounts(swap_and_sender_vec)];

Self::build(
instructions,
durable_nonce,
agg_key.into(),
compute_price,
compute_limit_with_buffer(
COMPUTE_UNITS_PER_CLOSE_EVENT_ACCOUNTS +
COMPUTE_UNITS_PER_CLOSE_CONTRACT_SWAP_ACCOUNTS +
COMPUTE_UNITS_PER_CLOSE_ACCOUNT * number_of_accounts as u32,
),
)
Expand Down Expand Up @@ -721,11 +725,11 @@ mod test {
}

#[test]
fn can_close_event_accounts() {
fn can_close_contract_swap_accounts() {
let env = api_env();
let event_accounts = vec![EVENT_AND_SENDER_ACCOUNTS[0]];
let transaction = SolanaTransactionBuilder::close_event_accounts(
event_accounts,
let contract_swap_accounts = vec![EVENT_AND_SENDER_ACCOUNTS[0]];
let transaction = SolanaTransactionBuilder::close_contract_swap_accounts(
contract_swap_accounts,
env.vault_program_data_account,
env.swap_endpoint_program,
env.swap_endpoint_program_data_account,
Expand All @@ -735,18 +739,18 @@ mod test {
)
.unwrap();

// Serialized tx built in `close_event_accounts` test
// Serialized tx built in `close_contract_swap_accounts` test
let expected_serialized_tx = hex_literal::hex!("01026e2d4bdca9e638b59507a70ea62ad88f098ffb25df028a19288702698fdf6d1cf77618b2123c0205a8e8d272ba8ea645b7e75c606ca3aa4356b65fa52ca20b0100050af79d5e026f12edc6443a534b2cdd5072233989b415d7596573e743f3e5b386fb17e5cc1f4d51a40626e11c783b75a45a4922615ecd7f5320b9d4d46481a196a317eb2b10d3377bda2bc7bea65bec6b8372f4fc3463ec2cd6f9fde4b2c633d1921c1f0efc91eeb48bb80c90cf97775cd5d843a96f16500266cee2c20d053152d2665730decf59d4cd6db8437dab77302287431eb7562b5997601851a0eab6946f00000000000000000000000000000000000000000000000000000000000000000306466fe5211732ffecadba72c39be7bc8ce5bbc5f7126b2c439b3a4000000006a7d517192c568ee08a845f73d29788cf035c3145b21ab344d8062ea94000000e14940a2247d0a8a33650d7dfe12d269ecabce61c1219b5a6dcdb6961026e091ef91c791d2aa8492c90f12540abd10056ce5dd8d9ab08461476c1dcc1622938c27e9074fac5e8d36cf04f94a0606fdd8ddbb420e99a489c7915ce5699e4890004050302070004040000000600090340420f000000000006000502307500000905080003010408a5663d01b94dbd79").to_vec();

test_constructed_transaction(transaction, expected_serialized_tx);
}

#[test]
fn can_close_max_event_accounts() {
fn can_close_max_contract_swap_accounts() {
let env = api_env();

// We can close 11 accounts without reaching the transaction length limit.
let transaction = SolanaTransactionBuilder::close_event_accounts(
let transaction = SolanaTransactionBuilder::close_contract_swap_accounts(
EVENT_AND_SENDER_ACCOUNTS.to_vec(),
env.vault_program_data_account,
env.swap_endpoint_program,
Expand Down
Loading