Skip to content

Commit

Permalink
feat: flow limits (#519)
Browse files Browse the repository at this point in the history
* feat: trait for dealing with rkyv PDAs

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* feat: flow limits

Add functionality to handle flow limits on interchain transfers.  As the
feature was developed, adjustments had to be made to other parts of ITS,
specially code related to role-management.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* chore: remove outdated role-management tests

The role-management crate doesn't process the instructions anymore as
handling who has rights to deal with management is dependent on the use
case and is done outside of the crate. Thus, these test cases are
outdated.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* refactor(flow_limit): extract and reuse

General improvements for code reuse.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* fix: make linter happy

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* fix(flow_limits): wrong check logic

We want to make sure only FLOW_LIMITER role is changed and thus we
need to make sure that's the role passed in the instruction by returning
an error in case it's not.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* fix: remove dbg statement

This was for troubleshooting purpose and serves no use anymore.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* chore: add comment to flow_limits test cases

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

* chore(flow-limits): add error message

This helps troubleshooting in case of errors.

Signed-off-by: Guilherme Felipe da Silva <[email protected]>

---------

Signed-off-by: Guilherme Felipe da Silva <[email protected]>
  • Loading branch information
frenzox authored Nov 21, 2024
1 parent 7e65d6c commit 32d919a
Show file tree
Hide file tree
Showing 27 changed files with 1,986 additions and 664 deletions.
1 change: 1 addition & 0 deletions solana/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions solana/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ exhaustive_enums = "allow"
exhaustive_structs = "allow"
expect_used = "allow"
future_not_send = "allow"
host_endian_bytes = "allow"
implicit_return = "allow"
missing_docs_in_private_items = "allow"
missing_inline_in_public_items = "allow"
Expand Down
1 change: 1 addition & 0 deletions solana/helpers/its-instruction-builder/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ workspace = true
[dependencies]
axelar-rkyv-encoding.workspace = true
axelar-solana-its = { workspace = true, features = ["no-entrypoint"] }
bincode.workspace = true
interchain-token-transfer-gmp.workspace = true
solana-client.workspace = true
solana-sdk.workspace = true
Expand Down
13 changes: 13 additions & 0 deletions solana/helpers/its-instruction-builder/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use axelar_solana_its::instructions::{Bumps, ItsGmpInstructionInputs};
use axelar_solana_its::state::token_manager::ArchivedTokenManager;
use interchain_token_transfer_gmp::GMPPayload;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::clock::Clock;
use solana_sdk::instruction::Instruction;
use solana_sdk::program_error::ProgramError;
use solana_sdk::pubkey::Pubkey;
use solana_sdk::sysvar::clock;

/// Creates a [`InterchainTokenServiceInstruction::ItsGmpPayload`] instruction.
///
Expand All @@ -36,6 +38,15 @@ where
.token_id()
.map_err(|_err| ProgramError::InvalidArgument)?,
);

let clock_account = rpc_client
.get_account(&clock::id())
.await
.map_err(|_err| ProgramError::InvalidAccountData)?;
let clock: Clock = bincode::deserialize(&clock_account.data)
.map_err(|_err| ProgramError::InvalidAccountData)?;
let timestamp = clock.unix_timestamp;

let (token_manager_pda, token_manager_pda_bump) =
axelar_solana_its::find_token_manager_pda(&interchain_token_pda);
let (mint, token_program) =
Expand All @@ -45,6 +56,7 @@ where
its_root_pda_bump,
interchain_token_pda_bump,
token_manager_pda_bump,
..Default::default()
});

let inputs = ItsGmpInstructionInputs::builder()
Expand All @@ -56,6 +68,7 @@ where
.token_program(token_program)
.mint_opt(mint)
.bumps_opt(bumps)
.timestamp(timestamp)
.build();

axelar_solana_its::instructions::its_gmp_payload(inputs)
Expand Down
2 changes: 1 addition & 1 deletion solana/helpers/program-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"
crate-type = ["cdylib", "lib"]

[dependencies]
rkyv.workspace = true
rkyv = { workspace = true, features = ["validation"] }
solana-program.workspace = true

[dev-dependencies]
110 changes: 109 additions & 1 deletion solana/helpers/program-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
//! Program utility functions

use std::borrow::Borrow;
use std::cell::Ref;
use std::io::Write;

use rkyv::de::deserializers::SharedDeserializeMap;
use rkyv::ser::serializers::AllocSerializer;
use rkyv::validation::validators::DefaultValidator;
use rkyv::{Archive, CheckBytes, Deserialize, Serialize};
use rkyv::{Archive, CheckBytes, Deserialize, Infallible, Serialize};
use solana_program::account_info::AccountInfo;
use solana_program::clock::Clock;
use solana_program::entrypoint::ProgramResult;
Expand Down Expand Up @@ -406,18 +407,125 @@ pub fn transfer_lamports(
Ok(())
}

/// Convenience trait to store and load rkyv serialized data to/from an account.
pub trait StorableArchive<const N: usize>: Archive + Serialize<AllocSerializer<N>>
where
Self: Sized + Clone,
Self::Archived: Deserialize<Self, Infallible>,
{
/// Initializes an account with the current object serialized data.
fn init<'a>(
&self,
program_id: &Pubkey,
system_account: &AccountInfo<'a>,
payer: &AccountInfo<'a>,
into: &AccountInfo<'a>,
signer_seeds: &[&[u8]],
) -> ProgramResult {
init_rkyv_pda::<N, Self>(
payer,
into,
program_id,
system_account,
self.clone(),
signer_seeds,
)
}

/// Stores the current object serialized data into the destination account.
/// The account must have been initialized beforehand.
fn store(&self, destination: &AccountInfo<'_>) -> ProgramResult {
let mut account_data = destination.try_borrow_mut_data()?;
let data = rkyv::to_bytes::<_, N>(self).map_err(|_err| ProgramError::InvalidAccountData)?;

account_data.copy_from_slice(&data);

Ok(())
}

/// Loads the account data and deserializes it.
fn load(program_id: &Pubkey, source_account: &AccountInfo<'_>) -> Result<Self, ProgramError> {
let account_data = source_account.try_borrow_data()?;
let archived =
check_rkyv_initialized_pda::<Self>(program_id, source_account, &account_data)?;

archived
.deserialize(&mut Infallible)
.map_err(|_err| ProgramError::InvalidAccountData)
}

/// Loads the account data and returns a view on the non-desialized data.
fn load_readonly<'a>(
program_id: &Pubkey,
source_account: &'a AccountInfo<'a>,
) -> Result<Ref<'a, <Self as rkyv::Archive>::Archived>, ProgramError> {
let data = source_account.try_borrow_data()?;

Ok(Ref::map(data, |inner| {
check_rkyv_initialized_pda::<Self>(program_id, source_account, inner)
.expect("Invalid PDA account")
}))
}
}

#[cfg(test)]
#[allow(clippy::std_instead_of_core)]
#[allow(clippy::legacy_numeric_constants)]
mod tests {
use std::u64;

use solana_program::clock::Epoch;

use super::*;

#[derive(Archive, Deserialize, Serialize, Debug, Eq, PartialEq, Clone)]
#[archive(compare(PartialEq))]
#[archive_attr(derive(Debug, PartialEq, Eq))]
#[repr(C)]
struct Test {
a: u32,
b: u64,
}

impl StorableArchive<0> for Test {}

#[test]
fn u_256_le_conversion_to_i64() {
let u256_t = from_u64_to_u256_le_bytes(u64::MAX);
let conv = checked_from_u256_le_bytes_to_u64(&u256_t).unwrap();
assert_eq!(u64::MAX, conv);
}

#[test]
fn test_load_store() {
let existing_data = Test { a: 0, b: 0 };
let mut initialized_buffer = rkyv::to_bytes::<_, 1024>(&existing_data).unwrap();
let mut lamports = 100;
let pubkey = Pubkey::new_unique();
let owner = Pubkey::new_unique();
let acc = AccountInfo::new(
&pubkey,
false,
false,
&mut lamports,
&mut initialized_buffer,
&owner,
false,
Epoch::default(),
);

let test = Test { a: 32, b: u64::MAX };

test.store(&acc).unwrap();

let archived = Test::load_readonly(&owner, &acc).unwrap();

assert_eq!(archived.a, 32);
assert_eq!(archived.b, u64::MAX);

let deserialized = Test::load(&owner, &acc).unwrap();

assert_eq!(deserialized.a, 32);
assert_eq!(deserialized.b, u64::MAX);
}
}
3 changes: 3 additions & 0 deletions solana/helpers/role-management/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ edition = "2021"
[lib]
crate-type = ["cdylib", "lib"]

[lints]
workspace = true

[dependencies]
bitflags.workspace = true
program-utils.workspace = true
Expand Down
Loading

0 comments on commit 32d919a

Please sign in to comment.