Skip to content
Merged
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
15 changes: 4 additions & 11 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ jobs:
# https://doc.rust-lang.org/beta/unstable-book/language-features/doc-cfg.html which allows an
# API be documented as only available in some specific platforms.
#

runs-on: ubuntu-latest
runs-on: ${{ vars.RUNNER }}
name: doc
steps:
- uses: actions/checkout@v5
Expand All @@ -114,17 +113,11 @@ jobs:
with:
cache-on-failure: "true"
cache-all-crates: "true"
cache-workspace-crates: "true"
workspaces: |
. -> target
# Specifies what to use as the backend providing cache
# Can be set to "github", "buildjet", or "warpbuild"
# default: "github"
cache-bin: "true"
- name: Install rust
uses: dtolnay/rust-toolchain@nightly
- name: Install cargo-docs-rs
uses: dtolnay/install@cargo-docs-rs
run: |
cargo +nightly install cargo-docs-rs
- name: cargo docs-rs
run: |
./scripts/docsrs.sh
Expand Down Expand Up @@ -191,4 +184,4 @@ jobs:
TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }}
RPC_URL: ${{ secrets.RPC_URL }}
run: |
cargo run -p circle-cctp -- bridge 0x626189bcDE2392ea088Ea0BD32336994CA9a0DCc
cargo run -p circle-cctp -- burn 0x626189bcDE2392ea088Ea0BD32336994CA9a0DCc
5 changes: 3 additions & 2 deletions encoders/circle-message-transmitter-v2-encoder/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use {
crate::{accounts::MessageSent, types::ReclaimEventAccountParams},
solana_account::Account,
solana_client::nonblocking::rpc_client::RpcClient,
solana_pubkey::Pubkey,
solana_rpc_client_api::client_error::Result as ClientResult,
std::{
Expand All @@ -13,15 +14,15 @@ pub const TOKEN_MINTER_PROGRAM_ID: Pubkey =
solana_pubkey::pubkey!("CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe");

#[async_trait::async_trait]
pub trait ReclaimAccountRpcState {
pub trait ReclaimAccountRpcState: Send + Sync {
async fn get_reclaim_accounts(&self, owner: &Pubkey) -> ClientResult<Vec<(Pubkey, Account)>>;
async fn get_reclaim_account_signature(&self, account: &Pubkey)
-> ClientResult<Option<String>>;
}

/// The fee account is owned by Circle and is used to pay for CCTP fees.
#[async_trait::async_trait]
pub trait FeeRecipientFetcher {
pub trait FeeRecipientFetcher: Send + Sync {
async fn get_fee_recipient_token_account(
&self,
circle_usdc_address: &Pubkey,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use {
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
},
solana_pubkey::Pubkey,
solana_rpc_client::nonblocking::rpc_client::RpcClient,
};

// https://github.com/circlefin/solana-cctp-contracts/blob/03f7dec786eb9affa68688954f62917edeed2e35/programs/v2/message-transmitter-v2/src/state.rs#L56
Expand Down Expand Up @@ -60,11 +59,14 @@ impl<T: AsRef<RpcClient> + Send + Sync> ReclaimAccountRpcState for T {
}
}

#[tracing::instrument(level = "info", skip(rpc))]
pub async fn find_claimable_accounts<T: ReclaimAccountRpcState>(
owner: &Pubkey,
rpc: &T,
) -> ClientResult<ReclaimAccountStatus> {
tracing::debug!("calling RPC for reclaim accounts");
let accounts = rpc.get_reclaim_accounts(owner).await?;
tracing::debug!("found {} accounts", accounts.len());
let mut claimable = ReclaimAccountStatus::new(*owner);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
Expand Down
2 changes: 1 addition & 1 deletion examples/circle-cctp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ solana-rpc-client = { workspace = true }
solana-rpc-client-api = { workspace = true }
solana-signer = { workspace = true }
solana-system-interface = { workspace = true }
soly = { git = "https://github.com/CarteraMesh/soly.git", branch = "main" }
soly = { version = "0.1.1-rc.1" }
spl-memo = { workspace = true }
spl-token = { workspace = true }
tokio = { workspace = true, features = ["full"] }
Expand Down
19 changes: 16 additions & 3 deletions examples/circle-cctp/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use clap::{Args, Parser, Subcommand};
use {
clap::{Args, Parser, Subcommand},
std::fmt::Debug,
};

#[derive(Parser)]
#[command(name = "circle-cctp")]
Expand All @@ -8,7 +11,7 @@ pub struct Cli {
}

#[derive(Args)]
pub struct BridgeArgs {
pub struct BurnArgs {
#[arg(long, default_value = "10")]
pub amount: u64,
#[arg(long, default_value = "6")]
Expand All @@ -18,9 +21,19 @@ pub struct BridgeArgs {
pub destination: String,
}

impl Debug for BurnArgs {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[amount={},dest={},chain={}]",
self.amount, self.destination, self.destination_chain
)
}
}

#[derive(Subcommand)]
pub enum Commands {
Bridge(BridgeArgs),
Burn(BurnArgs),
Reclaim,
Recv { tx_hash: String },
}
86 changes: 39 additions & 47 deletions examples/circle-cctp/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use {
crate::command::BridgeArgs,
crate::command::BurnArgs,
alloy_primitives::Address,
anyhow::{Ok, Result},
clap::Parser,
nitrogen_circle_message_transmitter_v2_encoder::{
ID as MESSAGE_TRANSMITTER_PROGRAM_ID,
helpers::{receive_message_helpers, reclaim_event_account_helpers},
instructions::reclaim_event_account,
types::ReclaimEventAccountParams,
},
nitrogen_circle_token_messenger_minter_v2_encoder::{
ID as TOKEN_MINTER_PROGRAM_ID,
Expand All @@ -25,8 +23,8 @@ use {
BlockHashCacheProvider,
LookupTableCacheProvider,
SimpleCacheProvider,
SolanaRpcProvider,
TraceNativeProvider,
SolanaRpcProviderNative,
TraceRpcNativeProvider,
TransactionBuilder,
},
std::{env, time::Duration},
Expand All @@ -50,10 +48,7 @@ async fn fetch_attestation(
attestation::get_attestation_with_retry(sig, chain).await
}

async fn reclaim<T: SolanaRpcProvider + AsRef<RpcClient> + Send + Sync>(
rpc: &T,
owner: Keypair,
) -> Result<()> {
async fn reclaim<T: SolanaRpcProviderNative>(rpc: &T, owner: Keypair) -> Result<()> {
let reclaim_accounts =
reclaim_event_account_helpers::find_claimable_accounts(&owner.pubkey(), rpc).await?;
info!("reclaim accounts {reclaim_accounts}");
Expand All @@ -74,20 +69,9 @@ async fn reclaim<T: SolanaRpcProvider + AsRef<RpcClient> + Send + Sync>(
tracing::warn!("Skipping account with no signature");
continue;
}
let sig = account.signature.unwrap_or_default();
let sig = account.signature.clone().unwrap();
let (attest, message) = fetch_attestation(sig, None).await?;
let reclaim_account = reclaim_event_account(
ReclaimEventAccountParams::builder()
.attestation(attest)
.destination_message(message)
.build(),
);
let reclaim_tx = TransactionBuilder::from(
reclaim_account
.accounts(owner.pubkey(), account.address)
.instruction(),
);

let reclaim_tx: TransactionBuilder = account.instruction((attest, message)).into();
let reclaim_tx = match (units, fee) {
(Some(units), Some(fee)) => {
reclaim_tx.prepend_compute_budget_instructions(units, fee)?
Expand Down Expand Up @@ -155,12 +139,14 @@ fn get_keypair() -> Result<Keypair> {
}

#[allow(unused_variables)]
async fn evm_sol(args: BridgeArgs, owner: Keypair, rpc: TraceNativeProvider) -> Result<()> {
async fn evm_sol<T: SolanaRpcProviderNative>(args: BurnArgs, owner: Keypair, rpc: T) -> Result<()> {
info!("TBD sending to sol");
Ok(())
}

async fn sol_evm(args: BridgeArgs, owner: Keypair, rpc: TraceNativeProvider) -> Result<()> {
async fn sol_evm<T: SolanaRpcProviderNative>(args: BurnArgs, owner: Keypair, rpc: T) -> Result<()> {
let span = tracing::info_span!("sol_evm", args =? args);
let _g = span.enter();
info!("burning...");
let message_sent_event_account = Keypair::new();
let evm_addr: Address = Address::parse_checksummed(args.destination, None)?;
Expand Down Expand Up @@ -211,6 +197,30 @@ async fn sol_evm(args: BridgeArgs, owner: Keypair, rpc: TraceNativeProvider) ->
Ok(())
}

fn cached_rpc(rpc: RpcClient) -> impl SolanaRpcProviderNative {
let rpc: TraceRpcNativeProvider = rpc.into();
let hash_cache = BlockHashCacheProvider::new(rpc.clone(), Duration::from_secs(30));
let lookup_cache = LookupTableCacheProvider::builder()
.inner(rpc.clone())
.lookup_cache(
soly::Cache::builder()
.time_to_live(Duration::from_secs(86400))
.build(),
)
.negative_cache(
soly::Cache::builder()
.time_to_live(Duration::from_secs(600))
.build(),
)
.build();

SimpleCacheProvider::builder()
.inner(rpc.clone())
.blockhash_cache(hash_cache.into())
.lookup_cache(lookup_cache.into())
.build()
}

#[allow(clippy::expect_fun_call)]
#[tokio::main]
pub async fn main() -> Result<()> {
Expand All @@ -228,37 +238,19 @@ pub async fn main() -> Result<()> {

let url = env::var("RPC_URL").expect("RPC_URL is not set");
info!("using RPC {url}");
let rpc: TraceNativeProvider =
RpcClient::new_with_commitment(url, CommitmentConfig::finalized()).into();
let rpc = cached_rpc(RpcClient::new_with_commitment(
url,
CommitmentConfig::finalized(),
));
match cli.command {
command::Commands::Bridge(args) => {
command::Commands::Burn(args) => {
if !args.to_sol {
sol_evm(args, owner, rpc).await
} else {
evm_sol(args, owner, rpc).await
}
}
command::Commands::Reclaim => {
let hash_cache = BlockHashCacheProvider::new(rpc.clone(), Duration::from_secs(30));
let lookup_cache = LookupTableCacheProvider::builder()
.inner(rpc.clone())
.lookup_cache(
soly::Cache::builder()
.time_to_live(Duration::from_secs(86400))
.build(),
)
.negative_cache(
soly::Cache::builder()
.time_to_live(Duration::from_secs(600))
.build(),
)
.build();

let rpc = SimpleCacheProvider::builder()
.inner(rpc.clone())
.blockhash_cache(hash_cache.into())
.lookup_cache(lookup_cache.into())
.build();
reclaim(&rpc, owner).await?;
Ok(())
}
Expand Down
Loading