diff --git a/Anchor.toml b/Anchor.toml index b5c355d7..256a1ad1 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -1,27 +1,28 @@ +[toolchain] + [features] seeds = false skip-lint = false [programs.devnet] -restaking = "8n3FHwYxFgQCQc2FNFkwDUf9mcqupxXcCvgfHbApMLv3" -solana_ibc = "2HLLVco5HvwWriNbUhmVwA2pCetRkpgrqwnjcsZdyTKT" +bridge-escrow = "A5ygmioT2hWFnxpPapY3XyDjwwfMDhnSP1Yxoynd5hs4" +restaking = "JEG7nDQFRPFjjFgWVXaA25EkcXGVjma2Cy56C976sXhk" +solana_ibc = "EUiJni1BZFL3BhaWcS7NTJmqWyh8NwRLjBzrcGBvxKU4" [programs.localnet] -restaking = "8n3FHwYxFgQCQc2FNFkwDUf9mcqupxXcCvgfHbApMLv3" -solana_ibc = "2HLLVco5HvwWriNbUhmVwA2pCetRkpgrqwnjcsZdyTKT" +bridge-escrow = "A5ygmioT2hWFnxpPapY3XyDjwwfMDhnSP1Yxoynd5hs4" +restaking = "JEG7nDQFRPFjjFgWVXaA25EkcXGVjma2Cy56C976sXhk" +solana_ibc = "EUiJni1BZFL3BhaWcS7NTJmqWyh8NwRLjBzrcGBvxKU4" [registry] url = "https://api.apr.dev" [provider] -cluster = "localnet" +cluster = "Localnet" wallet = "~/.config/solana/id.json" [workspace] -members = [ - "solana/restaking/programs/restaking", - "solana/solana-ibc/programs/solana-ibc" -] +members = ["solana/bridge-escrow/programs/bridge-escrow", "solana/restaking/programs/restaking", "solana/solana-ibc/programs/solana-ibc"] [scripts] test = "./solana-test.sh" diff --git a/Cargo.lock b/Cargo.lock index 1fa0dbe5..68f04bb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1018,6 +1018,49 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bridge-escrow" +version = "0.0.3" +dependencies = [ + "anchor-client", + "anchor-lang 0.29.0", + "anchor-spl", + "anyhow", + "base64 0.21.7", + "bytemuck", + "cf-guest", + "derive_more", + "guestchain", + "hex-literal", + "ibc", + "ibc-client-tendermint-types", + "ibc-proto", + "ibc-testkit", + "insta", + "itertools", + "lib", + "linear-map", + "memory", + "primitive-types", + "prost", + "serde", + "serde_json", + "solana-allocator", + "solana-ibc", + "solana-signature-verifier", + "solana-trie", + "solana-write-account", + "spl-associated-token-account", + "spl-token", + "stdx", + "strum 0.25.0", + "tendermint", + "tendermint-light-client-verifier", + "trie-ids", + "uint", + "wasm", +] + [[package]] name = "brotli" version = "3.4.0" @@ -7700,7 +7743,6 @@ dependencies = [ "anchor-lang 0.29.0", "anchor-spl", "base64 0.21.7", - "bincode", "borsh 0.10.3", "bs58 0.5.1", "clap 4.4.18", diff --git a/Cargo.toml b/Cargo.toml index 99502293..4995027c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ rust-version = "1.71.0" members = [ "common/*", "solana/allocator", + "solana/bridge-escrow/programs/*", "solana/restaking/programs/*", "solana/signature-verifier", "solana/solana-ibc/programs/*", diff --git a/common/cf-guest/src/client/impls.rs b/common/cf-guest/src/client/impls.rs index 48e84a82..84c19c30 100644 --- a/common/cf-guest/src/client/impls.rs +++ b/common/cf-guest/src/client/impls.rs @@ -237,7 +237,6 @@ impl ibc::ClientStateCommon for ClientState { } } - impl From for ibc::ClientError { fn from(err: proof::VerifyError) -> Self { use ibc::CommitmentError::EncodingFailure; @@ -396,7 +395,6 @@ where } } - impl ClientState { pub fn do_update_state( &self, @@ -641,12 +639,10 @@ impl ClientState { } } - fn error(msg: impl ToString) -> ibc::ClientError { ibc::ClientError::Other { description: msg.to_string() } } - /// Checks client id’s client type is what’s expected and then parses the id as /// `ClientIdx`. /// @@ -666,7 +662,6 @@ fn parse_client_id(client_id: &ibc::ClientId) -> Result { Err(ibc::ClientError::ClientSpecific { description }) } - #[test] fn test_verify_client_type() { use core::str::FromStr; diff --git a/common/cf-guest/src/client/tests.rs b/common/cf-guest/src/client/tests.rs index 0ee5045b..defd655e 100644 --- a/common/cf-guest/src/client/tests.rs +++ b/common/cf-guest/src/client/tests.rs @@ -131,7 +131,6 @@ fn test_header_misbehaviour() { add_block(&mut ctx, Some(&bad_block_2), 25 * HOUR, 2, Ok(true), None); } - /// Tests Misbehaviour client messages. /// /// Only verification and checking for misbehaviour are tested. No state @@ -302,7 +301,6 @@ impl TestContext { } } - impl guestchain::Verifier for TestContext { fn verify( &self, diff --git a/common/cf-guest/src/header.rs b/common/cf-guest/src/header.rs index e7df4978..7d440726 100644 --- a/common/cf-guest/src/header.rs +++ b/common/cf-guest/src/header.rs @@ -169,7 +169,6 @@ impl Header { } } - proto_utils::define_wrapper! { proto: proto::Header, wrapper: Header where diff --git a/common/cf-guest/src/message.rs b/common/cf-guest/src/message.rs index 43099250..9c94d2bb 100644 --- a/common/cf-guest/src/message.rs +++ b/common/cf-guest/src/message.rs @@ -15,7 +15,6 @@ pub enum ClientMessage { Misbehaviour(Misbehaviour), } - // Conversions directly to and from the Message enum. impl From> for Message { @@ -48,7 +47,6 @@ impl TryFrom<&Message> for ClientMessage { } } - // Conversions directly into the Message enum from variant types. impl From> for Message { @@ -67,7 +65,6 @@ impl From<&Misbehaviour> for Message { fn from(msg: &Misbehaviour) -> Self { Self::Misbehaviour(msg.into()) } } - // Conversion into ClientMessage proto from variant types. impl From> for proto::ClientMessage { @@ -90,7 +87,6 @@ impl From<&Misbehaviour> for proto::ClientMessage { } } - // And finally, conversions between proto and Rust type impl From> for proto::ClientMessage { @@ -125,7 +121,6 @@ impl TryFrom<&proto::ClientMessage> } } - proto_utils::define_wrapper! { proto: proto::ClientMessage, wrapper: ClientMessage where diff --git a/common/cf-guest/src/proof.rs b/common/cf-guest/src/proof.rs index d1d710e9..7f82c2bf 100644 --- a/common/cf-guest/src/proof.rs +++ b/common/cf-guest/src/proof.rs @@ -34,7 +34,6 @@ impl IbcProof { } } - #[derive(Clone, Debug, PartialEq, Eq, derive_more::From)] pub enum GenerateError { /// State root in block header and root of trie don’t match. @@ -104,7 +103,6 @@ pub fn generate( Ok(IbcProof { proof, root, value }) } - #[derive( Clone, Debug, PartialEq, Eq, derive_more::From, derive_more::Display, )] @@ -253,7 +251,6 @@ pub fn verify( } } - #[test] fn test_proofs() { use core::str::FromStr; diff --git a/common/lib/src/hash.rs b/common/lib/src/hash.rs index 1cd98c57..5e43efc9 100644 --- a/common/lib/src/hash.rs +++ b/common/lib/src/hash.rs @@ -241,7 +241,6 @@ mod imp { } } - /// Builder for the cryptographic hash. /// /// The builder calculates the digest of bytes that it’s fed using the diff --git a/common/proto-utils/src/lib.rs b/common/proto-utils/src/lib.rs index 28a912fc..c7708995 100644 --- a/common/proto-utils/src/lib.rs +++ b/common/proto-utils/src/lib.rs @@ -35,7 +35,6 @@ pub trait AnyConvert: Sized { fn try_from_any(type_url: &str, value: &[u8]) -> Result; } - /// Error during decoding of a protocol message. #[derive(Clone, PartialEq, Eq, derive_more::From)] pub enum DecodeError { @@ -106,7 +105,6 @@ impl From for ibc_core_client_context::types::error::ClientError { } } - /// Defines common associated symbols and conversions for a proto message type. #[macro_export] macro_rules! define_message { @@ -230,7 +228,6 @@ macro_rules! define_message { }; } - /// Implements conversion between given type and Any message. /// /// Specified type must implement [`AnyConvert`]. @@ -281,7 +278,6 @@ macro_rules! impl_from_to_any { }; } - /// Defines a wrapper type for a raw protocol message type. // TODO(mina86): Add definition of tests. #[macro_export] diff --git a/common/sealable-trie/src/bits.rs b/common/sealable-trie/src/bits.rs index dbf016c5..94262159 100644 --- a/common/sealable-trie/src/bits.rs +++ b/common/sealable-trie/src/bits.rs @@ -762,8 +762,6 @@ impl core::cmp::PartialEq for Slice<'_> { fn eq(&self, other: &Owned) -> bool { self == &other.as_slice() } } - - impl TryFrom> for Vec { type Error = MisalignedSlice; #[inline] @@ -795,7 +793,6 @@ impl TryFrom for Vec { } } - impl fmt::Display for Slice<'_> { fn fmt(&self, fmtr: &mut fmt::Formatter<'_>) -> fmt::Result { use ascii::AsciiChar; diff --git a/common/sealable-trie/src/bits/concat.rs b/common/sealable-trie/src/bits/concat.rs index 48e9d7b6..602bf8be 100644 --- a/common/sealable-trie/src/bits/concat.rs +++ b/common/sealable-trie/src/bits/concat.rs @@ -4,7 +4,6 @@ use lib::u3::U3; use super::{Owned, Slice}; - /// Trying to concatenate slices which result in slice whose size is too large. /// /// Slice’s length must not overflow u16. @@ -87,14 +86,12 @@ impl From for Error { fn from(_: MisalignedSlice) -> Error { Error::Misaligned } } - pub trait Concat { type Error: Into; fn concat_impl(prefix: Self, suffix: Rhs) -> Result; } - impl<'a> Concat> for bool { type Error = SliceTooLong; @@ -215,7 +212,6 @@ pub(super) fn extend_impl( Ok(()) } - /// Checks that concatenating two slices produces slice whose length doesn’t /// overflow `u16`. pub(super) fn check_length( @@ -255,7 +251,6 @@ fn check_alignment_and_length( Ok(check_length(pre_len, suf_len)?) } - #[test] fn test_unshift() { for offset in U3::all() { diff --git a/common/sealable-trie/src/bits/ext_key.rs b/common/sealable-trie/src/bits/ext_key.rs index decdf326..21f284b8 100644 --- a/common/sealable-trie/src/bits/ext_key.rs +++ b/common/sealable-trie/src/bits/ext_key.rs @@ -140,8 +140,6 @@ impl fmt::Debug for Chunks<'_> { } } - - impl<'a> core::iter::Iterator for Chunks<'a> { type Item = ExtKey<'a>; diff --git a/common/sealable-trie/src/nodes.rs b/common/sealable-trie/src/nodes.rs index 517fa9d5..c5a230da 100644 --- a/common/sealable-trie/src/nodes.rs +++ b/common/sealable-trie/src/nodes.rs @@ -338,7 +338,6 @@ impl<'a, S> ValueRef<'a, S> { } } - // ============================================================================= // PartialEq diff --git a/common/sealable-trie/src/proof/serialisation.rs b/common/sealable-trie/src/proof/serialisation.rs index 031c9dcc..a3e4cdef 100644 --- a/common/sealable-trie/src/proof/serialisation.rs +++ b/common/sealable-trie/src/proof/serialisation.rs @@ -286,7 +286,6 @@ fn invalid_data(msg: String) -> io::Error { io::Error::new(io::ErrorKind::InvalidData, msg) } - #[test] fn test_item_borsh() { #[track_caller] diff --git a/common/sealable-trie/src/trie.rs b/common/sealable-trie/src/trie.rs index c1b9c132..42efdb3a 100644 --- a/common/sealable-trie/src/trie.rs +++ b/common/sealable-trie/src/trie.rs @@ -381,7 +381,6 @@ impl> Trie { } } - #[cfg(test)] impl Trie> { /// Creates a test trie using a TestAllocator with given capacity. diff --git a/common/sealable-trie/src/trie/tests.rs b/common/sealable-trie/src/trie/tests.rs index a9e93640..9c134bcf 100644 --- a/common/sealable-trie/src/trie/tests.rs +++ b/common/sealable-trie/src/trie/tests.rs @@ -309,7 +309,6 @@ impl<'a> KeyGen<'a> for RandKeys<'a> { fn count(&self) -> Option { Some(self.count) } } - #[test] fn stress_test() { let count = lib::test_utils::get_iteration_count(500); @@ -444,7 +443,6 @@ impl core::fmt::Debug for Key { } } - trait KeyGen<'a> { fn next(&mut self, known: &HashMap) -> Option<&'a [u8]>; fn count(&self) -> Option; @@ -470,7 +468,6 @@ impl<'a, I: Iterator> KeyGen<'a> for IterKeyGen { fn count(&self) -> Option { self.0.size_hint().1 } } - struct TestTrie { trie: super::Trie>, mapping: HashMap, diff --git a/common/trie-ids/src/path_info.rs b/common/trie-ids/src/path_info.rs index 7637b54b..7fdfe214 100644 --- a/common/trie-ids/src/path_info.rs +++ b/common/trie-ids/src/path_info.rs @@ -227,7 +227,6 @@ impl PathInfo { } } - #[test] fn test_try_from_path() { use std::str::FromStr; diff --git a/common/trie-ids/src/trie_key.rs b/common/trie-ids/src/trie_key.rs index 3af358af..82c12831 100644 --- a/common/trie-ids/src/trie_key.rs +++ b/common/trie-ids/src/trie_key.rs @@ -1,7 +1,6 @@ use super::path::SequencePath; use super::{ibc, ids}; - /// A key used for indexing entries in the provable storage. /// /// The key is built from IBC storage paths. The first byte is a tag (see @@ -277,7 +276,6 @@ impl From for TrieKey { } } - /// Component of a [`TrieKey`]. /// /// A `TrieKey` is constructed by concatenating a sequence of components. @@ -368,7 +366,6 @@ impl AsComponent for (T, U) { } } - #[test] fn test_encoding() { use std::str::FromStr; diff --git a/solana-test.sh b/solana-test.sh index 5d37e362..525045f2 100755 --- a/solana-test.sh +++ b/solana-test.sh @@ -10,5 +10,6 @@ cd ../.. solana program deploy target/deploy/write.so solana program deploy target/deploy/sigverify.so cargo test --lib -- --nocapture --include-ignored ::anchor -find solana/restaking/tests/ -name '*.ts' \ - -exec yarn run ts-mocha -p ./tsconfig.json -t 1000000 {} + +cargo test --lib -- --nocapture --include-ignored ::escrow +# find solana/restaking/tests/ -name '*.ts' \ +# -exec yarn run ts-mocha -p ./tsconfig.json -t 1000000 {} + diff --git a/solana/allocator/src/ptr.rs b/solana/allocator/src/ptr.rs index f612726b..d10899df 100644 --- a/solana/allocator/src/ptr.rs +++ b/solana/allocator/src/ptr.rs @@ -26,7 +26,6 @@ pub(super) fn range(start: *mut u8, size: usize) -> core::ops::Range<*mut u8> { start..start.wrapping_add(size) } - /// Copies `size` bytes from `src` to `dst`. /// /// # Safety diff --git a/solana/bridge-escrow/migrations/deploy.ts b/solana/bridge-escrow/migrations/deploy.ts new file mode 100644 index 00000000..82fb175f --- /dev/null +++ b/solana/bridge-escrow/migrations/deploy.ts @@ -0,0 +1,12 @@ +// Migrations are an early feature. Currently, they're nothing more than this +// single deploy script that's invoked from the CLI, injecting a provider +// configured from the workspace's Anchor.toml. + +const anchor = require("@coral-xyz/anchor"); + +module.exports = async function (provider) { + // Configure client to use the provider. + anchor.setProvider(provider); + + // Add your deploy script here. +}; diff --git a/solana/bridge-escrow/programs/bridge-escrow/Cargo.toml b/solana/bridge-escrow/programs/bridge-escrow/Cargo.toml new file mode 100644 index 00000000..a61403b7 --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "bridge-escrow" +description = "Created with Anchor" +edition = "2021" +version.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] +name = "bridge_escrow" + +[features] +# added so that we can compile this along with `solana-ibc` with mocks features. Currently unused. +mocks = [] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anyhow.workspace = true +anchor-lang.workspace = true +anchor-spl = { workspace = true, features = ["metadata"] } +base64.workspace = true +bytemuck = { workspace = true, features = ["must_cast", "zeroable_atomics"] } +derive_more.workspace = true +hex-literal.workspace = true +ibc-client-tendermint-types.workspace = true +ibc-proto.workspace = true +ibc-testkit = { workspace = true, optional = true } +ibc.workspace = true +linear-map.workspace = true +primitive-types.workspace = true +prost.workspace = true +serde.workspace = true +serde_json.workspace = true +spl-associated-token-account.workspace = true +spl-token.workspace = true +strum.workspace = true +tendermint-light-client-verifier.workspace = true +tendermint.workspace = true +uint.workspace = true + +guestchain.workspace = true +cf-guest.workspace = true +lib = { workspace = true, features = ["solana-program"] } +memory.workspace = true +solana-allocator = { workspace = true, optional = true } +solana-signature-verifier = { workspace = true, features = ["guest", "library"] } +solana-trie.workspace = true +stdx.workspace = true +trie-ids = { workspace = true, features = ["borsh"] } +wasm = { workspace = true } +itertools = "0.10.5" +solana-ibc = { workspace = true, features = ["cpi"] } + +[dev-dependencies] +anchor-client.workspace = true +anyhow.workspace = true +ibc-testkit.workspace = true +insta.workspace = true + +lib = { workspace = true, features = ["test_utils"] } +solana-write-account = { workspace = true, features = ["library"] } diff --git a/solana/bridge-escrow/programs/bridge-escrow/Xargo.toml b/solana/bridge-escrow/programs/bridge-escrow/Xargo.toml new file mode 100644 index 00000000..475fb71e --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/solana/bridge-escrow/programs/bridge-escrow/src/bridge.rs b/solana/bridge-escrow/programs/bridge-escrow/src/bridge.rs new file mode 100644 index 00000000..19abefb0 --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/src/bridge.rs @@ -0,0 +1,245 @@ +use std::str::FromStr; + +use anchor_lang::prelude::*; +use anchor_spl::token::{CloseAccount, MintTo}; +use ibc::apps::transfer::types::msgs::transfer::MsgTransfer; +use ibc::apps::transfer::types::packet::PacketData; +use ibc::apps::transfer::types::{PrefixedCoin, PrefixedDenom}; +use ibc::core::channel::types::timeout::TimeoutHeight; +use ibc::core::host::types::identifiers::{ChannelId, PortId}; +use ibc::core::primitives::Timestamp; +use ibc::primitives::Signer as IbcSigner; +use lib::hash::CryptoHash; +use solana_ibc::cpi::accounts::SendTransfer; +use solana_ibc::cpi::send_transfer; + +use crate::{ + ErrorCode, OnTimeout, SplTokenTransfer, DUMMY_TOKEN_TRANSFER_AMOUNT, +}; + +pub fn bridge_transfer( + accounts: BridgeTransferAccounts<'_>, + custom_memo: String, + hashed_full_denom: CryptoHash, + signer_seeds: &[&[&[u8]]], +) -> Result<()> { + let receiver_token_account = accounts.receiver_token_account; + + // Mint dummy tokens so that they can transferred + let mint_acc = MintTo { + mint: accounts.token_mint.clone(), + to: receiver_token_account.clone(), + authority: accounts.auctioneer_state, + }; + + let cpi_ctx = CpiContext::new_with_signer( + accounts.token_program.clone(), + mint_acc, + signer_seeds, + ); + + anchor_spl::token::mint_to(cpi_ctx, DUMMY_TOKEN_TRANSFER_AMOUNT)?; + + // Cross-chain transfer + memo + let transfer_ctx = CpiContext::new(accounts.ibc_program, SendTransfer { + sender: accounts.sender.clone(), + receiver: Some(accounts.receiver), + storage: accounts.storage, + trie: accounts.trie, + chain: accounts.chain, + mint_authority: Some(accounts.mint_authority), + token_mint: Some(accounts.token_mint.clone()), + escrow_account: Some(accounts.escrow_account), + receiver_token_account: Some(receiver_token_account.clone()), + fee_collector: Some(accounts.fee_collector), + token_program: Some(accounts.token_program.clone()), + system_program: accounts.system_program, + }); + + let memo = "{\"forward\":{\"receiver\":\"\ + 0x4c22af5da4a849a8f39be00eb1b44676ac5c9060\",\"port\":\"\ + transfer\",\"channel\":\"channel-52\",\"timeout\":\ + 600000000000000,\"next\":{\"memo\":\"my-custom-msg\"}}}" + .to_string(); + let memo = memo.replace("my-custom-msg", &custom_memo); + + // MsgTransfer + let msg = MsgTransfer { + port_id_on_a: PortId::from_str("transfer").unwrap(), + chan_id_on_a: ChannelId::from_str("channel-1").unwrap(), + packet_data: PacketData { + token: PrefixedCoin { + denom: PrefixedDenom::from_str( + &accounts.token_mint.key().to_string(), + ) + .unwrap(), // token only owned by this PDA + amount: DUMMY_TOKEN_TRANSFER_AMOUNT.into(), + }, + sender: IbcSigner::from(accounts.sender.key().to_string()), + receiver: String::from("pfm").into(), + memo: memo.into(), + }, + timeout_height_on_b: TimeoutHeight::Never, + timeout_timestamp_on_b: Timestamp::from_nanoseconds(u64::MAX).unwrap(), + }; + + send_transfer(transfer_ctx, hashed_full_denom, msg)?; + + // Close the dummy token account. + let close_accs = CloseAccount { + account: receiver_token_account, + destination: accounts.sender.clone(), + authority: accounts.sender, + }; + + let cpi_ctx = CpiContext::new(accounts.token_program, close_accs); + + // anchor_spl::token::close_account(cpi_ctx)?; + + Ok(()) +} + +pub struct BridgeTransferAccounts<'info> { + pub sender: AccountInfo<'info>, + pub auctioneer_state: AccountInfo<'info>, + pub receiver: AccountInfo<'info>, + pub storage: AccountInfo<'info>, + pub trie: AccountInfo<'info>, + pub chain: AccountInfo<'info>, + pub mint_authority: AccountInfo<'info>, + pub token_mint: AccountInfo<'info>, + pub escrow_account: AccountInfo<'info>, + pub receiver_token_account: AccountInfo<'info>, + pub fee_collector: AccountInfo<'info>, + pub ibc_program: AccountInfo<'info>, + pub token_program: AccountInfo<'info>, + pub system_program: AccountInfo<'info>, +} + +impl<'info> TryFrom<&mut SplTokenTransfer<'info>> + for BridgeTransferAccounts<'info> +{ + type Error = anchor_lang::error::Error; + + fn try_from(accounts: &mut SplTokenTransfer<'info>) -> Result { + Ok(Self { + sender: accounts.solver.to_account_info(), + auctioneer_state: accounts.auctioneer_state.to_account_info(), + receiver: accounts + .receiver + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + storage: accounts + .storage + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + trie: accounts + .trie + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + chain: accounts + .chain + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + mint_authority: accounts + .mint_authority + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + token_mint: accounts + .token_mint + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + escrow_account: accounts + .escrow_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + receiver_token_account: accounts + .receiver_token_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + fee_collector: accounts + .fee_collector + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + ibc_program: accounts + .ibc_program + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + token_program: accounts.token_program.to_account_info(), + system_program: accounts.system_program.to_account_info(), + }) + } +} + +impl<'info> TryFrom<&mut OnTimeout<'info>> for BridgeTransferAccounts<'info> { + type Error = anchor_lang::error::Error; + + fn try_from(accounts: &mut OnTimeout<'info>) -> Result { + Ok(Self { + sender: accounts.caller.to_account_info(), + auctioneer_state: accounts.auctioneer_state.to_account_info(), + receiver: accounts + .receiver + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + storage: accounts + .storage + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + trie: accounts + .trie + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + chain: accounts + .chain + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + mint_authority: accounts + .mint_authority + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + token_mint: accounts + .token_mint + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + escrow_account: accounts + .escrow_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + receiver_token_account: accounts + .receiver_token_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + fee_collector: accounts + .fee_collector + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + ibc_program: accounts + .ibc_program + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .to_account_info(), + token_program: accounts.token_program.to_account_info(), + system_program: accounts.system_program.to_account_info(), + }) + } +} diff --git a/solana/bridge-escrow/programs/bridge-escrow/src/events.rs b/solana/bridge-escrow/programs/bridge-escrow/src/events.rs new file mode 100644 index 00000000..20167e96 --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/src/events.rs @@ -0,0 +1,119 @@ +use anchor_lang::prelude::borsh; +use anchor_lang::solana_program::log; +use anchor_lang::solana_program::pubkey::Pubkey; + +use crate::Intent; + +/// Events that can be emitted by the program. +/// +/// The events are logged in their borsh-serialised form. +/// +/// The events names are similar to the function names that emit them +/// to remain the consistency. +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] + +pub enum Event { + EscrowFunds(EscrowFunds), + StoreIntent(StoreIntent), + OnReceiveTransfer(OnReceiveTransfer), + SendFundsToUser(SendFundsToUser), + OnTimeout(OnTimeout), +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] +pub struct EscrowFunds { + pub amount: u64, + pub sender: Pubkey, + pub token_mint: Pubkey, +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] +pub struct StoreIntent { + pub intent: Intent, +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] +pub struct OnReceiveTransfer { + pub amount: u64, + pub solver: Pubkey, +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] +pub struct SendFundsToUser { + pub amount: u64, + pub receiver: Pubkey, + pub token_mint: Pubkey, + pub intent_id: String, + /// The solver on source chain who would receive + /// the escrowed amount when the intent is acknowledged. + /// + /// Would be `None` in case of single domain transfer. + pub solver_out: Option, +} + +#[derive( + Clone, + Debug, + PartialEq, + Eq, + borsh::BorshSerialize, + borsh::BorshDeserialize, + derive_more::From, +)] +pub struct OnTimeout { + pub amount: u64, + pub token_mint: String, + pub intent_id: String, +} + +impl Event { + pub fn emit(&self) -> Result<(), String> { + borsh::BorshSerialize::try_to_vec(self) + .map(|data| log::sol_log_data(&[data.as_slice()])) + .map_err(|err| err.to_string()) + } +} + +pub fn emit(event: impl Into) -> Result<(), String> { + event.into().emit() +} diff --git a/solana/bridge-escrow/programs/bridge-escrow/src/lib.rs b/solana/bridge-escrow/programs/bridge-escrow/src/lib.rs new file mode 100644 index 00000000..08838806 --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/src/lib.rs @@ -0,0 +1,840 @@ +use std::str::FromStr; + +use anchor_lang::prelude::*; +use anchor_lang::solana_program; +use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::token; +use anchor_spl::token::{Mint, Token, TokenAccount, Transfer as SplTransfer}; +use lib::hash::CryptoHash; + +// const DUMMY: &str = "0x36dd1bfe89d409f869fabbe72c3cf72ea8b460f6"; +const BRIDGE_CONTRACT_PUBKEY: &str = + "2HLLVco5HvwWriNbUhmVwA2pCetRkpgrqwnjcsZdyTKT"; + +const AUCTIONEER_SEED: &[u8] = b"auctioneer"; +const INTENT_SEED: &[u8] = b"intent"; +const DUMMY_SEED: &[u8] = b"dummy"; + +const DUMMY_TOKEN_TRANSFER_AMOUNT: u64 = 1_000_000_000; + +pub mod bridge; +pub mod events; +#[cfg(test)] +mod tests; + +declare_id!("yAJJJMZmjWSQjvq8WuARKygH8KJkeQTXB5BGJBJcR4T"); + +#[program] +pub mod bridge_escrow { + use super::*; + + /// Sets the authority and creates a token mint which would be used to + /// send acknowledgements to the counterparty chain. The token doesnt have + /// any value is just used to transfer messages. + pub fn initialize(ctx: Context) -> Result<()> { + // store the auctioneer + let auctioneer = &mut ctx.accounts.auctioneer; + auctioneer.authority = *ctx.accounts.authority.key; + Ok(()) + } + + /// Escrows the user funds on the source chain + /// + /// The funds are stored in token account owned by the auctioneer state PDA. Right now + /// all the deposits are present in a single pool. But we would want to deposit the funds + /// in seperate account so that we dont touch the funds of other users. + pub fn escrow_and_store_intent( + ctx: Context, + amount: u64, + new_intent: IntentPayload + ) -> Result<()> { + // Step 1: Check the conditions (translated from Solidity) + + require!( + ctx.accounts.intent.user_in == "", + ErrorCode::IntentAlreadyExists + ); + + require!( + new_intent.winner_solver == Pubkey::default(), + ErrorCode::WinnerSolverMustBeEmpty + ); + + require!( + new_intent.user_in == ctx.accounts.user.key().to_string(), + ErrorCode::SrcUserNotSender + ); + + // Step 2: Escrow the funds (same as before) + + let cpi_accounts = SplTransfer { + from: ctx.accounts.user_token_account.to_account_info(), + to: ctx.accounts.escrow_token_account.to_account_info(), + authority: ctx.accounts.user.to_account_info(), + }; + + let cpi_program = ctx.accounts.token_program.to_account_info(); + let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts); + + token::transfer(cpi_ctx, amount)?; + + events::emit(events::Event::EscrowFunds(events::EscrowFunds { + amount, + sender: ctx.accounts.user.key(), + token_mint: ctx.accounts.token_mint.key(), + })).map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + + // Step 3: Store the intent (same as before) + + let intent = &mut ctx.accounts.intent; + let current_timestamp = Clock::get()?.unix_timestamp as u64; + + require!( + current_timestamp < new_intent.timeout_timestamp_in_sec, + ErrorCode::InvalidTimeout + ); + + intent.intent_id = new_intent.intent_id.clone(); + intent.user_in = new_intent.user_in.clone(); + intent.user_out = new_intent.user_out; + intent.token_in = new_intent.token_in.clone(); + intent.amount_in = new_intent.amount_in; + intent.token_out = new_intent.token_out.clone(); + intent.timeout_timestamp_in_sec = new_intent.timeout_timestamp_in_sec; + intent.creation_timestamp_in_sec = current_timestamp; + intent.amount_out = new_intent.amount_out.clone(); + intent.winner_solver = new_intent.winner_solver; + intent.single_domain = new_intent.single_domain; + + events::emit(events::Event::StoreIntent(events::StoreIntent { + intent: Intent { + intent_id: new_intent.intent_id, + user_in: new_intent.user_in, + user_out: new_intent.user_out, + token_in: new_intent.token_in, + amount_in: new_intent.amount_in, + token_out: new_intent.token_out, + amount_out: new_intent.amount_out, + winner_solver: new_intent.winner_solver, + creation_timestamp_in_sec: current_timestamp, + timeout_timestamp_in_sec: new_intent.timeout_timestamp_in_sec, + single_domain: new_intent.single_domain, + }, + })).map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + + Ok(()) + } + + pub fn update_auction_data( + ctx: Context, + intent_id: String, + amount_out: String, + winner_solver: Pubkey, + ) -> Result<()> { + // Retrieve the intent from the provided context + let intent = &mut ctx.accounts.intent; + + // Ensure that the auctioneer is the signer + require!( + ctx.accounts.auctioneer.authority == ctx.accounts.authority.key(), + ErrorCode::Unauthorized + ); + + // Verify that the intent ID matches the expected one + require!( + intent.intent_id == intent_id, + ErrorCode::IntentDoesNotExist + ); + + // Update the auction data + intent.amount_out = amount_out; + intent.winner_solver = winner_solver; + + // Emit an event for the auction data update (optional) + // events::emit(events::Event::UpdateAuctionData { + // intent_id, + // amount_out, + // winner_solver: winner_solver.to_string(), + // }) + // .map_err(|err| { + // msg!("{}", err); + // ErrorCode::InvalidEventFormat + // })?; + + Ok(()) + } + + /// The memo should contain the token mint address, amount and solver address + /// seperated by commas. Right now this method can only be called by the + /// auctioneer. + /// + /// TODO: Modify the method such that the method can only be called by + /// the solana-ibc bridge contract. This would then remove the trust factor + /// from the auctioneer. + pub fn on_receive_transfer( + ctx: Context, + memo: String, + ) -> Result<()> { + // Split and extract memo fields + let parts: Vec<&str> = memo.split(',').collect(); + require!(parts.len() == 7, ErrorCode::InvalidMemoFormat); // Ensure memo has 7 parts + + // Memo format: , , , , , , + let withdraw_user_flag: bool = parts[0].parse().map_err(|_| ErrorCode::InvalidWithdrawFlag)?; + let intent_id = parts[1]; + let from = Pubkey::from_str(parts[2]).map_err(|_| ErrorCode::BadPublickey)?; + let token_mint = Pubkey::from_str(parts[3]).map_err(|_| ErrorCode::BadPublickey)?; + let to = Pubkey::from_str(parts[4]).map_err(|_| ErrorCode::BadPublickey)?; + let amount: u64 = parts[5].parse().map_err(|_| ErrorCode::InvalidAmount)?; + // let solver_out = Pubkey::from_str(parts[6]).map_err(|_| ErrorCode::BadPublickey)?; + + // Retrieve the intent from the provided context + let intent = &mut ctx.accounts.intent; + + // Validate the intent + require!(intent.intent_id == intent_id, ErrorCode::IntentDoesNotExist); + + let seeds = &[ + AUCTIONEER_SEED, + core::slice::from_ref(&ctx.bumps.auctioneer_state), + ]; + let seeds = seeds.as_ref(); + let signer_seeds = core::slice::from_ref(&seeds); + + if withdraw_user_flag { + // Case 1: User withdrawal + require!( + intent.user_out == from, + ErrorCode::IntentMismatchFromUser + ); + + // Transfer tokens from the escrow account to the user's token account + let cpi_accounts = SplTransfer { + from: ctx.accounts.escrow_token_account.to_account_info(), + to: ctx.accounts.solver_token_account.to_account_info(), + authority: ctx.accounts.auctioneer_state.to_account_info(), + }; + + let cpi_program = ctx.accounts.token_program.to_account_info(); + let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); + + token::transfer(cpi_ctx, amount)?; + + } else { + // Case 2: Solver transaction + require!( + intent.winner_solver == from, + ErrorCode::IntentMismatchFromSolver + ); + require!( + intent.token_out == token_mint.to_string(), + ErrorCode::InvalidTokenOut + ); + require!( + intent.user_out == to, + ErrorCode::IntentMismatchToUser + ); + require!( + intent.amount_out.parse::().unwrap() <= amount, + ErrorCode::InsufficientAmount + ); + + // Transfer tokens from the escrow account to the solver's token account + let cpi_accounts = SplTransfer { + from: ctx.accounts.escrow_token_account.to_account_info(), + to: ctx.accounts.solver_token_account.to_account_info(), + authority: ctx.accounts.auctioneer_state.to_account_info(), + }; + + let cpi_program = ctx.accounts.token_program.to_account_info(); + let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds); + + token::transfer(cpi_ctx, amount)?; + } + + // Emit event based on the action performed + // events::emit(events::Event::OnReceiveTransfer( + // events::OnReceiveTransfer { + // amount, + // solver: if withdraw_user_flag { + // ctx.accounts.solver_token_account.owner + // } else { + // ctx.accounts.solver_token_account.owner + // }, + // }, + // )) + // .map_err(|err| { + // msg!("{}", err); + // ErrorCode::InvalidEventFormat + // })?; + + Ok(()) + } + + + + // this function is called by Solver + #[allow(unused_variables)] + pub fn send_funds_to_user( + ctx: Context, + intent_id: String, + amount_out: u64, + solver_out: Option, + single_domain: bool + ) -> Result<()> { + let accounts = ctx.accounts; + + let token_program = &accounts.token_program; + let solver = &accounts.solver; + + // Transfer tokens from Solver to User + let cpi_accounts = SplTransfer { + from: accounts.solver_token_out_account.to_account_info().clone(), + to: accounts.user_token_out_account.to_account_info().clone(), + authority: solver.to_account_info().clone(), + }; + let cpi_program = token_program.to_account_info(); + token::transfer( + CpiContext::new(cpi_program, cpi_accounts), + amount_out, + )?; + + let bump = ctx.bumps.auctioneer_state; + let seeds = &[AUCTIONEER_SEED, core::slice::from_ref(&bump)]; + let seeds = seeds.as_ref(); + let signer_seeds = core::slice::from_ref(&seeds); + + if single_domain { + let intent = accounts.intent.clone(); + require!( + *accounts.solver.key == intent.winner_solver, + ErrorCode::Unauthorized + ); + + let amount_out = intent + .amount_out + .parse::() + .map_err(|_| ErrorCode::InvalidAmount)?; + + // Transfer tokens from Auctioneer to Solver + let auctioneer_token_in_account = accounts + .auctioneer_token_in_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)?; + let solver_token_in_account = accounts + .solver_token_in_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)?; + + let cpi_accounts = SplTransfer { + from: auctioneer_token_in_account.to_account_info(), + to: solver_token_in_account.to_account_info(), + authority: accounts.auctioneer_state.to_account_info(), + }; + let cpi_program = token_program.to_account_info(); + token::transfer( + CpiContext::new_with_signer( + cpi_program, + cpi_accounts, + signer_seeds, + ), + intent.amount_in, + )?; + + events::emit(events::Event::SendFundsToUser( + events::SendFundsToUser { + amount: amount_out, + receiver: intent.user_out, + token_mint: accounts.token_out.key(), + intent_id, + solver_out, + }, + )) + .map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + } else { + let solver_out = + solver_out.ok_or(ErrorCode::InvalidSolverAddress)?; + let token_mint = accounts + .token_mint + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .key(); + + let hashed_full_denom = + CryptoHash::digest(token_mint.to_string().as_bytes()); + + // bool withdraw_user, + // string intentId, + // string from, + // string token, + // string to, + // string amount, + // string solver_out + + let my_custom_memo = format!( + "0,{},{},{},{},{},{}", + intent_id, + accounts.solver.key, + accounts.token_out.key(), + accounts.user_token_out_account.owner, + amount_out, + solver_out + ); + bridge::bridge_transfer( + accounts.try_into()?, + my_custom_memo, + hashed_full_denom, + signer_seeds, + )?; + + events::emit(events::Event::SendFundsToUser( + events::SendFundsToUser { + amount: amount_out, + receiver: accounts.user_token_out_account.owner, + token_mint: accounts.token_out.key(), + intent_id, + solver_out: Some(solver_out), + }, + )) + .map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + } + + Ok(()) + } + + /// If the intent has not been solved, then the funds can be withdrawn by + /// the user after the timeout period has passed. + /// + /// For the cross chain intents, a message is sent to the source chain to unlock + /// the funds. + pub fn user_cancel_intent( + ctx: Context, + intent_id: String, + ) -> Result<()> { + let authority = &ctx.accounts.caller.key(); + + let intent = &ctx.accounts.intent.clone(); + let current_time = Clock::get()?.unix_timestamp as u64; + require!( + current_time >= intent.timeout_timestamp_in_sec, + ErrorCode::IntentNotTimedOut + ); + + let bump = ctx.bumps.auctioneer_state; + let signer_seeds = &[AUCTIONEER_SEED, &[bump]]; + let signer_seeds = signer_seeds.as_ref(); + let signer_seeds = core::slice::from_ref(&signer_seeds); + + if intent.single_domain { + let user_token_account = ctx + .accounts + .user_token_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)?; + let escrow_token_account = ctx + .accounts + .escrow_token_account + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)?; + require!( + user_token_account.owner == *authority, + ErrorCode::Unauthorized + ); + + // Unescrow the tokens + let cpi_accounts = SplTransfer { + from: escrow_token_account.to_account_info(), + to: user_token_account.to_account_info(), + authority: ctx.accounts.auctioneer_state.to_account_info(), + }; + + let cpi_ctx = CpiContext::new_with_signer( + ctx.accounts.token_program.to_account_info(), + cpi_accounts, + signer_seeds, + ); + anchor_spl::token::transfer(cpi_ctx, intent.amount_in)?; + + events::emit(events::Event::OnTimeout(events::OnTimeout { + amount: intent.amount_in, + token_mint: intent.token_in.clone(), + intent_id, + })) + .map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + } else { + // Send a cross domain message to the source chain to unlock the funds + let my_custom_memo = format!( + "{},{}", + intent_id, authority + ); + let token_mint = ctx + .accounts + .token_mint + .as_ref() + .ok_or(ErrorCode::AccountsNotPresent)? + .key(); + + let hashed_full_denom = + CryptoHash::digest(token_mint.to_string().as_bytes()); + + bridge::bridge_transfer( + ctx.accounts.try_into()?, + my_custom_memo, + hashed_full_denom, + signer_seeds, + )?; + + events::emit(events::Event::OnTimeout(events::OnTimeout { + amount: intent.amount_in, + token_mint: intent.token_in.clone(), + intent_id, + })) + .map_err(|err| { + msg!("{}", err); + ErrorCode::InvalidEventFormat + })?; + } + + Ok(()) + } +} + +// Define the Auctioneer account +#[account] +pub struct Auctioneer { + pub authority: Pubkey, +} + +// Define the Intent account with space calculation +#[account] +#[derive(Debug, PartialEq, Eq, InitSpace)] +pub struct Intent { + #[max_len(20)] + pub intent_id: String, + // User on source chain + #[max_len(40)] + pub user_in: String, + // User on destination chain + pub user_out: Pubkey, + #[max_len(40)] + pub token_in: String, + pub amount_in: u64, + #[max_len(20)] + pub token_out: String, + #[max_len(20)] + pub amount_out: String, + pub winner_solver: Pubkey, + // Timestamp when the intent was created + pub creation_timestamp_in_sec: u64, + pub timeout_timestamp_in_sec: u64, + pub single_domain: bool, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Debug)] +pub struct IntentPayload { + pub intent_id: String, + pub user_in: String, + pub user_out: Pubkey, + pub token_in: String, + pub amount_in: u64, + pub token_out: String, + pub amount_out: String, + pub winner_solver: Pubkey, + pub timeout_timestamp_in_sec: u64, + pub single_domain: bool, +} + +// Define the context for initializing the program +#[derive(Accounts)] +#[instruction()] +pub struct Initialize<'info> { + #[account(mut)] + pub authority: Signer<'info>, + #[account(init, seeds = [AUCTIONEER_SEED], bump, payer = authority, space = 8 + 32)] + pub auctioneer: Account<'info, Auctioneer>, + + #[account(init, payer = authority, seeds = [DUMMY_SEED], bump, mint::decimals = 9, mint::authority = auctioneer)] + pub token_mint: Account<'info, Mint>, + + pub system_program: Program<'info, System>, + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, +} + +#[derive(Accounts)] +#[instruction(intent_id: String)] +pub struct ReceiveTransferContext<'info> { + #[account(mut)] + pub authority: Signer<'info>, + + #[account(seeds = [AUCTIONEER_SEED], bump, has_one = authority)] + pub auctioneer_state: Account<'info, Auctioneer>, + + pub token_mint: Account<'info, Mint>, + #[account(mut, token::mint = token_mint, token::authority = auctioneer_state)] + pub escrow_token_account: Account<'info, TokenAccount>, + #[account(mut, token::mint = token_mint)] + pub solver_token_account: Account<'info, TokenAccount>, + + #[account(address = solana_program::sysvar::instructions::ID)] + /// CHECK: Used for getting the caller program id to verify if the right + /// program is calling the method. + pub instruction: UncheckedAccount<'info>, + + pub token_program: Program<'info, Token>, + + // New Intent account addition + #[account( + mut, + seeds = [b"intent", intent_id.as_bytes()], + bump, + constraint = intent.user_in == authority.key().to_string() + )] + pub intent: Account<'info, Intent>, +} + +#[derive(Accounts)] +#[instruction(intent_id: String)] +pub struct UpdateAuctionData<'info> { + #[account(mut)] + pub authority: Signer<'info>, + + #[account( + seeds = [AUCTIONEER_SEED], + bump, + has_one = authority // Ensures that the authority is the auctioneer + )] + pub auctioneer: Account<'info, Auctioneer>, + + #[account(mut, seeds = [INTENT_SEED, intent_id.as_bytes()], bump)] + pub intent: Account<'info, Intent>, +} + + +// Accounts for transferring SPL tokens +#[derive(Accounts)] +#[instruction(intent_id: String)] +pub struct SplTokenTransfer<'info> { + // Intent reading + #[account(mut, close = auctioneer, seeds = [INTENT_SEED, intent_id.as_bytes()], bump)] + pub intent: Box>, + #[account(seeds = [AUCTIONEER_SEED], bump)] + pub auctioneer_state: Box>, + #[account(mut)] + pub solver: Signer<'info>, + + #[account(mut, address = auctioneer_state.authority)] + /// CHECK: + pub auctioneer: UncheckedAccount<'info>, + + pub token_in: Option>>, + pub token_out: Box>, + + // Program (Escrow) -> Solver SPL Token Transfer Accounts + #[account(mut, token::mint = token_in, token::authority = auctioneer_state)] + pub auctioneer_token_in_account: Option>>, + #[account(mut, token::authority = solver, token::mint = token_in)] + pub solver_token_in_account: Option>>, + + // Solver -> User SPL Token Transfer Accounts + #[account(mut, token::authority = solver, token::mint = token_out)] + pub solver_token_out_account: Box>, + #[account(mut, token::mint = token_out)] + pub user_token_out_account: Box>, + + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, + + // The accounts below are only needed for cross chain intents + + // Cross-chain Transfer Accounts + #[account(address = Pubkey::from_str(BRIDGE_CONTRACT_PUBKEY).unwrap())] + /// CHECK: + pub ibc_program: Option>, + #[account(mut)] + /// CHECK: + pub receiver: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut)] + pub storage: Option>, + /// CHECK: + #[account(mut)] + pub trie: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut)] + pub chain: Option>, + /// CHECK: + #[account(mut)] + pub mint_authority: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut, seeds = [DUMMY_SEED], bump)] + pub token_mint: Option>>, + /// CHECK: + #[account(mut)] + pub escrow_account: Option>, + /// CHECK: validated by solana-ibc program + #[account( + init_if_needed, + payer = solver, + associated_token::mint = token_mint, + associated_token::authority = solver + )] + pub receiver_token_account: Option>>, + /// CHECK: + #[account(mut)] + pub fee_collector: Option>, +} + +#[derive(Accounts)] +#[instruction(intent_payload: IntentPayload)] +pub struct EscrowAndStoreIntent<'info> { + // From EscrowFunds + #[account(mut)] + pub user: Signer<'info>, + #[account(mut, token::authority = user, token::mint = token_mint)] + pub user_token_account: Account<'info, TokenAccount>, + #[account(seeds = [AUCTIONEER_SEED], bump)] + pub auctioneer_state: Account<'info, Auctioneer>, + pub token_mint: Account<'info, Mint>, + #[account(init_if_needed, payer = user, associated_token::mint = token_mint, associated_token::authority = auctioneer_state)] + pub escrow_token_account: Account<'info, TokenAccount>, + + // From StoreIntent + #[account(init, seeds = [INTENT_SEED, intent_payload.intent_id.as_bytes()], bump, payer = user, space = 3000)] + pub intent: Account<'info, Intent>, + + // Shared Programs + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, +} + + +#[derive(Accounts)] +#[instruction(intent_id: String)] +pub struct OnTimeout<'info> { + #[account(mut)] + pub caller: Signer<'info>, + + #[account(seeds = [AUCTIONEER_SEED], bump, constraint = auctioneer_state.authority == *auctioneer.key)] + pub auctioneer_state: Account<'info, Auctioneer>, + #[account(mut)] + /// CHECK: + pub auctioneer: UncheckedAccount<'info>, + #[account(mut, close = auctioneer, seeds = [INTENT_SEED, intent_id.as_bytes()], bump)] + pub intent: Account<'info, Intent>, + + // Single domain transfer accounts + pub token_in: Option>, + #[account(mut, token::mint = token_mint)] + pub user_token_account: Option>, + #[account(mut, token::mint = token_mint, token::authority = auctioneer_state)] + pub escrow_token_account: Option>, + + // Cross-chain Transfer Accounts + #[account(address = Pubkey::from_str(BRIDGE_CONTRACT_PUBKEY).unwrap())] + /// CHECK: + pub ibc_program: Option>, + #[account(mut)] + /// CHECK: + pub receiver: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut)] + pub storage: Option>, + /// CHECK: + #[account(mut)] + pub trie: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut)] + pub chain: Option>, + /// CHECK: + #[account(mut)] + pub mint_authority: Option>, + /// CHECK: validated by solana-ibc program + #[account(mut, seeds = [DUMMY_SEED], bump)] + pub token_mint: Option>>, + /// CHECK: + #[account(mut)] + pub escrow_account: Option>, + /// CHECK: validated by solana-ibc program + #[account(init_if_needed, payer = caller, associated_token::mint = token_mint, associated_token::authority = caller)] + pub receiver_token_account: Option>>, + /// CHECK: + #[account(mut)] + pub fee_collector: Option>, + + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, + pub system_program: Program<'info, System>, +} + +// Define custom errors +#[error_code] +pub enum ErrorCode { + #[msg("You are not authorized to perform this action.")] + Unauthorized, + #[msg("The provided bridge contract is invalid.")] + InvalidBridgeContract, + #[msg("Invalid token address in the memo.")] + InvalidTokenAddress, + #[msg("Invalid solver address in the memo.")] + InvalidSolverAddress, + #[msg("Invalid amount in the memo.")] + InvalidAmount, + #[msg("Token transfer failed.")] + TransferFailed, + #[msg("Denom is not DUMMY token")] + InvalidDenom, + #[msg("Timeout is lesser than the current time")] + InvalidTimeout, + #[msg("Intent hasnt timed out yet")] + IntentNotTimedOut, + #[msg("Solana ibc accounts not present")] + AccountsNotPresent, + #[msg("Invalid solver out address")] + InvalidSolverOutAddress, + #[msg("Invalid hashed full denom")] + InvalidHashedFullDenom, + #[msg("Invalid Event format. Check logs for more")] + InvalidEventFormat, + #[msg("Unable to parse public key from string")] + BadPublickey, + #[msg("Intent already exists.")] + IntentAlreadyExists, + #[msg("WinnerSolver must be an empty string.")] + WinnerSolverMustBeEmpty, + #[msg("Source user does not match the sender.")] + SrcUserNotSender, + #[msg("Invalid withdraw flag format.")] + InvalidWithdrawFlag, + #[msg("Intent does not exist.")] + IntentDoesNotExist, + #[msg("Intent mismatch: 'from' address does not match expected intent.dstUser.")] + IntentMismatchFromUser, + #[msg("Intent mismatch: 'from' address does not match expected intent.winnerSolver.")] + IntentMismatchFromSolver, + #[msg("Intent mismatch: 'to' address does not match expected intent.dstUser.")] + IntentMismatchToUser, + #[msg("Insufficient amount provided.")] + InsufficientAmount, + #[msg("Invalid Memo format")] + InvalidMemoFormat, + #[msg("Invalid token out")] + InvalidTokenOut +} diff --git a/solana/bridge-escrow/programs/bridge-escrow/src/tests.rs b/solana/bridge-escrow/programs/bridge-escrow/src/tests.rs new file mode 100644 index 00000000..1322ac6c --- /dev/null +++ b/solana/bridge-escrow/programs/bridge-escrow/src/tests.rs @@ -0,0 +1,536 @@ +use std::rc::Rc; +use std::thread::sleep; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use anchor_client::solana_client::rpc_client::RpcClient; +use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; +use anchor_client::solana_sdk::commitment_config::CommitmentConfig; +use anchor_client::solana_sdk::compute_budget::ComputeBudgetInstruction; +use anchor_client::solana_sdk::pubkey::Pubkey; +use anchor_client::solana_sdk::signature::{ + read_keypair_file, Keypair, Signature, Signer as SolanaSigner, +}; +use anchor_client::{Client, Cluster}; +use anchor_lang::system_program; +use anchor_spl::associated_token::{self, get_associated_token_address}; +use anyhow::Result; +use spl_token::instruction::initialize_mint2; +use spl_token::solana_program::system_instruction::create_account; + +use crate::IntentPayload; + +const MINT_AMOUNT: u64 = 1_000_000_000; +const TRANSFER_AMOUNT: u64 = 1_000_000; + +fn airdrop(client: &RpcClient, account: Pubkey, lamports: u64) -> Signature { + let balance_before = client.get_balance(&account).unwrap(); + println!("This is balance before {}", balance_before); + let airdrop_signature = client.request_airdrop(&account, lamports).unwrap(); + sleep(Duration::from_secs(2)); + println!("This is airdrop signature {}", airdrop_signature); + + let balance_after = client.get_balance(&account).unwrap(); + println!("This is balance after {}", balance_after); + assert_eq!(balance_before + lamports, balance_after); + airdrop_signature +} + +#[test] +#[ignore = "Requires local validator to run"] +fn escrow_bridge_program() -> Result<()> { + // Setup the client and wallet + let auctioneer = + Rc::new(read_keypair_file("../../../solana-ibc/keypair.json").unwrap()); + + let client = Client::new_with_options( + Cluster::Localnet, + auctioneer.clone(), + CommitmentConfig::processed(), + ); + + let program = client.program(crate::ID)?; + let sol_rpc_client = program.rpc(); + + let lamports = 2_000_000_000; + + let solver = Rc::new(Keypair::new()); + let user = Rc::new(Keypair::new()); + let token_in_keypair = Keypair::new(); + let token_in = token_in_keypair.pubkey(); + let token_out_keypair = Keypair::new(); + let token_out = token_out_keypair.pubkey(); + + let program_rpc = program.rpc(); + + // Below is for Devnet/Mainnet + + // println!("User {:?}", user.to_bytes()); + // println!("Solver {:?}", solver.to_bytes()); + + // let blockhash = program_rpc.get_latest_blockhash()?; + // let user_transfer_ix = + // transfer(&auctioneer, &user.pubkey(), LAMPORTS_PER_SOL / 10, blockhash); + // let solver_transfer_ix = transfer( + // &auctioneer, + // &solver.pubkey(), + // LAMPORTS_PER_SOL / 10, + // blockhash, + // ); + + // let user_transfer_sig = + // program_rpc.send_and_confirm_transaction(&user_transfer_ix)?; + // let solver_transfer_sig = + // program_rpc.send_and_confirm_transaction(&solver_transfer_ix)?; + + // println!("User transfer signature: {}", user_transfer_sig); + // println!("Solver transfer signature: {}", solver_transfer_sig); + + airdrop(&program_rpc, auctioneer.pubkey(), lamports); + airdrop(&program_rpc, user.pubkey(), lamports); + airdrop(&program_rpc, solver.pubkey(), lamports); + + let auctioneer_state = + Pubkey::find_program_address(&[crate::AUCTIONEER_SEED], &crate::ID).0; + + /* + * Creating Token In Mint + */ + println!("\nCreating a token in mint"); + + let create_token_in_account_ix = create_account( + &auctioneer.pubkey(), + &token_in, + sol_rpc_client.get_minimum_balance_for_rent_exemption(82).unwrap(), + 82, + &anchor_spl::token::ID, + ); + + let create_token_in_mint_ix = initialize_mint2( + &anchor_spl::token::ID, + &token_in, + &auctioneer.pubkey(), + Some(&auctioneer.pubkey()), + 6, + ) + .expect("invalid mint instruction"); + + let create_token_acc_ix = spl_associated_token_account::instruction::create_associated_token_account(&auctioneer.pubkey(), &solver.pubkey(), &token_in, &anchor_spl::token::ID); + let create_token_acc_ix_2 = spl_associated_token_account::instruction::create_associated_token_account(&auctioneer.pubkey(), &user.pubkey(), &token_in, &anchor_spl::token::ID); + let user_token_in_addr = + get_associated_token_address(&user.pubkey(), &token_in); + let mint_ix = spl_token::instruction::mint_to( + &anchor_spl::token::ID, + &token_in, + &user_token_in_addr, + &auctioneer.pubkey(), + &[&auctioneer.pubkey()], + MINT_AMOUNT, + ) + .unwrap(); + + let tx = program + .request() + .instruction(create_token_in_account_ix) + .instruction(create_token_in_mint_ix) + .instruction(create_token_acc_ix) + .instruction(create_token_acc_ix_2) + .instruction(mint_ix) + .payer(auctioneer.clone()) + .signer(&*auctioneer) + .signer(&token_in_keypair) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + })?; + + println!(" Signature: {}", tx); + + /* + * Creating Token Out Mint + */ + println!("\nCreating a token out mint"); + + let create_token_out_account_ix = create_account( + &auctioneer.pubkey(), + &token_out, + sol_rpc_client.get_minimum_balance_for_rent_exemption(82).unwrap(), + 82, + &anchor_spl::token::ID, + ); + + let create_token_out_mint_ix = initialize_mint2( + &anchor_spl::token::ID, + &token_out, + &auctioneer.pubkey(), + Some(&auctioneer.pubkey()), + 6, + ) + .expect("invalid mint instruction"); + + let create_token_acc_ix = spl_associated_token_account::instruction::create_associated_token_account(&auctioneer.pubkey(), &solver.pubkey(), &token_out, &anchor_spl::token::ID); + let create_token_acc_ix_2 = spl_associated_token_account::instruction::create_associated_token_account(&auctioneer.pubkey(), &user.pubkey(), &token_out, &anchor_spl::token::ID); + let solver_token_out_addr = + get_associated_token_address(&solver.pubkey(), &token_out); + let mint_ix = spl_token::instruction::mint_to( + &anchor_spl::token::ID, + &token_out, + &solver_token_out_addr, + &auctioneer.pubkey(), + &[&auctioneer.pubkey()], + MINT_AMOUNT, + ) + .unwrap(); + + let tx = program + .request() + .instruction(create_token_out_account_ix) + .instruction(create_token_out_mint_ix) + .instruction(create_token_acc_ix) + .instruction(create_token_acc_ix_2) + .instruction(mint_ix) + .payer(auctioneer.clone()) + .signer(&*auctioneer) + .signer(&token_out_keypair) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + })?; + + println!(" Signature: {}", tx); + + let dummy_token_mint = + Pubkey::find_program_address(&[crate::DUMMY_SEED], &crate::ID).0; + + // Initialize the progroam to define the auctioneer + println!("\nInitializing the program"); + let sig = program + .request() + .accounts(crate::accounts::Initialize { + authority: auctioneer.pubkey(), + auctioneer: auctioneer_state, + token_mint: dummy_token_mint, + system_program: anchor_lang::solana_program::system_program::ID, + token_program: anchor_spl::token::ID, + associated_token_program: anchor_spl::associated_token::ID, + }) + .args(crate::instruction::Initialize {}) + .payer(auctioneer.clone()) + .signer(&*auctioneer) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }) + .unwrap(); + println!(" Signature: {}", sig); + + let token_in_escrow_addr = + get_associated_token_address(&auctioneer_state, &token_in); + + // Escrow user funds and store intent (combined) + println!("\nEscrow user funds and store intent for single domain"); + + let intent_id = "123456789".to_string(); + // Define amount_out (this would typically come from the intent) + let amount_out = 10000; // This should match what’s expected in the intent + + // Find the Program Derived Address (PDA) for the intent account + let intent_state = Pubkey::find_program_address( + &[crate::INTENT_SEED, intent_id.as_bytes()], + &crate::ID, + ) + .0; + + // Get the current timestamp + let current_timestamp = + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + + // Define the new intent payload + let new_intent = IntentPayload { + intent_id: intent_id.clone(), + user_in: user.pubkey().to_string(), // Must match the ctx.accounts.user key in the contract + user_out: user.pubkey(), + token_in: token_in.to_string(), + amount_in: TRANSFER_AMOUNT, + token_out: token_out.to_string(), + amount_out: amount_out.to_string(), // Amount out as a string + timeout_timestamp_in_sec: current_timestamp + 10000, // Arbitrary timeout + winner_solver: Pubkey::default(), // Ensure winner_solver is empty at first + single_domain: true, + }; + + // Call the escrow_and_store_intent() function + let sig = program + .request() + .accounts(crate::accounts::EscrowAndStoreIntent { + user: user.pubkey(), + user_token_account: user_token_in_addr, + auctioneer_state, + token_mint: token_in, + escrow_token_account: token_in_escrow_addr, + intent: intent_state, + token_program: anchor_spl::token::ID, + associated_token_program: associated_token::ID, + system_program: system_program::ID, + }) + .args(crate::instruction::EscrowAndStoreIntent { + amount: TRANSFER_AMOUNT, + new_intent, + }) + .payer(user.clone()) + .signer(&*user) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }) + .unwrap(); + + println!(" Signature: {}", sig); + + // Test for the update_auction_data function (only auctioneer can call this) + println!("\nTesting update_auction_data function"); + + // New auction data + let amount_out = 10_000; // This should match what’s expected in the intent + let winner_solver = solver.pubkey(); // A new winner solver public key + + // Only auctioneer should be able to call this function + let sig = program + .request() + .accounts(crate::accounts::UpdateAuctionData { + authority: auctioneer.pubkey(), // Must be auctioneer + auctioneer: auctioneer_state, + intent: intent_state, + }) + .args(crate::instruction::UpdateAuctionData { + intent_id: intent_id.clone(), + amount_out: amount_out.clone().to_string(), + winner_solver: winner_solver, + }) + .payer(auctioneer.clone()) + .signer(&*auctioneer) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + }) + .unwrap(); + + println!(" Signature: {}", sig); + + // Send funds to user (single domain) + println!("\nSend funds to user single domain"); + + // Get the associated token addresses for the solver and the user + let solver_token_in_addr = get_associated_token_address(&solver.pubkey(), &token_in); + let user_token_out_addr = get_associated_token_address(&user.pubkey(), &token_out); + + // Call the `send_funds_to_user` function + let sig = program + .request() + .accounts(crate::accounts::SplTokenTransfer { + solver: solver.pubkey(), + intent: intent_state, + auctioneer_state, + auctioneer: auctioneer.pubkey(), + token_in: Some(token_in), + token_out, + auctioneer_token_in_account: Some(token_in_escrow_addr), + solver_token_in_account: Some(solver_token_in_addr), + solver_token_out_account: solver_token_out_addr, + user_token_out_account: user_token_out_addr, + token_program: anchor_spl::token::ID, + associated_token_program: anchor_spl::associated_token::ID, + system_program: anchor_lang::solana_program::system_program::ID, + // Cross-chain related fields, set to None since it's single domain + ibc_program: None, + receiver: None, + storage: None, + trie: None, + chain: None, + mint_authority: None, + token_mint: None, + escrow_account: None, + receiver_token_account: None, + fee_collector: None, + }) + .args(crate::instruction::SendFundsToUser { + intent_id: intent_id.clone(), + amount_out, // Pass the amount to transfer to the user + solver_out: None, // Set to None since this is a single domain transaction + single_domain: true, // Indicating that this is a single domain transaction + }) + .payer(solver.clone()) + .signer(&*solver) + .send_with_spinner_and_config(RpcSendTransactionConfig { + skip_preflight: true, + ..Default::default() + }) + .unwrap(); + + println!(" Signature: {}", sig); + + // // Send funds to user ( cross domain ) + // println!("\nSend funds to user cross domain"); + + // let hashed_full_denom = + // lib::hash::CryptoHash::digest(&dummy_token_mint.to_string().as_bytes()); + + // println!("\nNative token mint {}", dummy_token_mint); + // println!("hashed full denom {}", hashed_full_denom); + + // // Derive the necessary accounts + // let (storage, _bump_storage) = Pubkey::find_program_address( + // &[solana_ibc::SOLANA_IBC_STORAGE_SEED], + // &solana_ibc::ID, + // ); + // let (trie, _bump_trie) = + // Pubkey::find_program_address(&[solana_ibc::TRIE_SEED], &solana_ibc::ID); + // let (chain, _bump_chain) = Pubkey::find_program_address( + // &[solana_ibc::CHAIN_SEED], + // &solana_ibc::ID, + // ); + // let (mint_authority, _bump_mint_authority) = Pubkey::find_program_address( + // &[solana_ibc::MINT_ESCROW_SEED], + // &solana_ibc::ID, + // ); + // let (escrow_account, _bump_escrow_account) = Pubkey::find_program_address( + // &[solana_ibc::ESCROW, &hashed_full_denom.as_slice()], + // &solana_ibc::ID, + // ); + + // let (fee_collector, _bump_fee_collector) = + // Pubkey::find_program_address(&[solana_ibc::FEE_SEED], &solana_ibc::ID); + + // let receiver_token_account = + // get_associated_token_address(&solver.pubkey(), &dummy_token_mint); + + // // Build and send the transaction to call send_funds_to_user + // println!("\nSending funds to user"); + // let sig = program + // .request() + // .instruction(ComputeBudgetInstruction::set_compute_unit_limit( + // 1_000_000, + // )) + // .accounts(crate::accounts::SplTokenTransfer { + // intent: intent_state, + // auctioneer_state, + // solver: solver.pubkey(), + // auctioneer: auctioneer.pubkey(), + // token_in: None, + // token_out, + // auctioneer_token_in_account: None, + // solver_token_in_account: None, + // solver_token_out_account: solver_token_out_addr, + // user_token_out_account: user_token_out_addr, + // token_program: anchor_spl::token::ID, + // associated_token_program: anchor_spl::associated_token::ID, + // system_program: anchor_lang::solana_program::system_program::ID, + // ibc_program: Some(solana_ibc::ID), + // receiver: Some(user.pubkey()), + // storage: Some(storage), + // trie: Some(trie), + // chain: Some(chain), + // mint_authority: Some(mint_authority), + // token_mint: Some(dummy_token_mint), + // escrow_account: Some(escrow_account), + // receiver_token_account: Some(receiver_token_account), + // fee_collector: Some(fee_collector), + // }) + // .args(crate::instruction::SendFundsToUser { + // intent_id: intent_id.clone(), + // hashed_full_denom: Some(hashed_full_denom), + // // Solver out doesnt matter for this test + // solver_out: Some(Pubkey::new_unique().to_string()), + // }) + // .payer(solver.clone()) + // .signer(&*solver) + // .send_with_spinner_and_config(RpcSendTransactionConfig { + // skip_preflight: true, + // ..RpcSendTransactionConfig::default() + // })?; + // println!(" Signature: {sig}"); + + // // on receive funds + // let sig = program + // .request() + // .accounts(crate::accounts::ReceiveTransferContext { + // auctioneer_state, + // authority: auctioneer.pubkey(), + // escrow_token_account: token_in_escrow_addr, + // token_mint: token_in, + // solver_token_account: solver_token_in_addr, + // token_program: anchor_spl::token::ID, + // instruction: crate::solana_program::sysvar::instructions::ID, + // }) + // .args(crate::instruction::OnReceiveTransfer { memo: "".to_string() }) + // .payer(auctioneer.clone()) + // .signer(&*auctioneer) + // .send_with_spinner_and_config(RpcSendTransactionConfig { + // skip_preflight: true, + // ..RpcSendTransactionConfig::default() + // })?; + // println!(" Signature: {sig}"); + + // // on timeout (single domain) + // let sig = program + // .request() + // .accounts(crate::accounts::OnTimeout { + // caller: auctioneer.pubkey(), + // auctioneer_state, + // intent: intent_state, + // auctioneer: auctioneer.pubkey(), + // token_in: Some(token_in), + // user_token_account: Some(user_token_out_addr), + // escrow_token_account: Some(token_in_escrow_addr), + // token_program: anchor_spl::token::ID, + // associated_token_program: anchor_spl::associated_token::ID, + // system_program: anchor_lang::solana_program::system_program::ID, + // ibc_program: None, + // storage: None, + // trie: None, + // chain: None, + // mint_authority: None, + // token_mint: None, + // escrow_account: None, + // receiver_token_account: None, + // fee_collector: None, + // receiver: None, + // }) + // .args(crate::instruction::OnTimeout { intent_id: intent_id.clone() }) + // .payer(auctioneer.clone()) + // .signer(&*auctioneer) + // .send_with_spinner_and_config(RpcSendTransactionConfig::default())?; + // println!(" Signature: {sig}"); + + // // on timeout (cross domain) + // let sig = program + // .request() + // .accounts(crate::accounts::OnTimeout { + // caller: auctioneer.pubkey(), + // auctioneer_state, + // intent: intent_state, + // auctioneer: auctioneer.pubkey(), + // token_in: None, + // user_token_account: None, + // escrow_token_account: None, + // token_program: anchor_spl::token::ID, + // associated_token_program: anchor_spl::associated_token::ID, + // system_program: anchor_lang::solana_program::system_program::ID, + // ibc_program: Some(solana_ibc::ID), + // storage: Some(storage), + // trie: Some(trie), + // chain: Some(chain), + // mint_authority: Some(mint_authority), + // token_mint: Some(dummy_token_mint), + // escrow_account: Some(escrow_account), + // receiver_token_account: Some(receiver_token_account), + // fee_collector: Some(fee_collector), + // receiver: Some(user.pubkey()), + // }) + // .args(crate::instruction::OnTimeout { intent_id: intent_id.clone() }) + // .payer(auctioneer.clone()) + // .signer(&*auctioneer) + // .send_with_spinner_and_config(RpcSendTransactionConfig::default())?; + // println!(" Signature: {sig}"); + + Ok(()) +} diff --git a/solana/restaking/programs/restaking/src/lib.rs b/solana/restaking/programs/restaking/src/lib.rs index dcbd306e..444778dc 100644 --- a/solana/restaking/programs/restaking/src/lib.rs +++ b/solana/restaking/programs/restaking/src/lib.rs @@ -16,7 +16,7 @@ use constants::{ VAULT_PARAMS_SEED, VAULT_SEED, }; -declare_id!("8n3FHwYxFgQCQc2FNFkwDUf9mcqupxXcCvgfHbApMLv3"); +declare_id!("JEG7nDQFRPFjjFgWVXaA25EkcXGVjma2Cy56C976sXhk"); #[program] pub mod restaking { diff --git a/solana/signature-verifier/src/api.rs b/solana/signature-verifier/src/api.rs index aaf64e1b..ec1fd351 100644 --- a/solana/signature-verifier/src/api.rs +++ b/solana/signature-verifier/src/api.rs @@ -4,7 +4,6 @@ use solana_program::pubkey::Pubkey; type Result = core::result::Result; - /// A signature hash as stored in the [`SignaturesAccount`]. /// /// When the signature verifier program confirms that a signature has been @@ -73,7 +72,6 @@ impl<'a> From<&crate::ed25519_program::Entry<'a>> for SignatureHash { } } - /// Wrapper around signatures account created by the verifier program. #[derive(Clone, Copy, derive_more::Deref, derive_more::DerefMut)] pub struct SignaturesAccount<'a, 'info>(pub(crate) &'a AccountInfo<'info>); @@ -201,7 +199,6 @@ pub(crate) fn find_sighash( // .map(SignatureHash::wrap_ref)) // } - #[test] fn test_ed25519() { let sig1 = SignatureHash::new_ed25519(&[11; 32], &[12; 64], b"foo"); diff --git a/solana/signature-verifier/src/ed25519_program.rs b/solana/signature-verifier/src/ed25519_program.rs index 269eeac7..aaff3458 100644 --- a/solana/signature-verifier/src/ed25519_program.rs +++ b/solana/signature-verifier/src/ed25519_program.rs @@ -145,7 +145,6 @@ fn write_slice(dst: &mut [MaybeUninit], src: &[u8]) { dst.copy_from_slice(src) } - /// Creates a new iterator over signatures in given Ed25519 native program /// instruction data. /// diff --git a/solana/signature-verifier/src/program.rs b/solana/signature-verifier/src/program.rs index 9335bc74..20e8079b 100644 --- a/solana/signature-verifier/src/program.rs +++ b/solana/signature-verifier/src/program.rs @@ -91,7 +91,6 @@ fn process_instruction<'a>( } } - /// Handles the Update operation. fn handle_update( ctx: Context, @@ -134,7 +133,6 @@ fn handle_update( ctx.signatures.write_count_and_sort(count) } - /// Extracts signatures from a call to Ed25519 native program. /// /// If the `instruction` doesn’t correspond to call to the Ed25519 signature @@ -307,7 +305,6 @@ impl<'a, 'info> Context<'a, 'info> { } } - /// Reads given object from the start of the slice advancing it. /// /// Returns an error if slice is too short. diff --git a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs index 46f3d4b3..fb228367 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/consensus_state.rs @@ -112,7 +112,6 @@ impl AnyConsensusState { } } - impl From for AnyConsensusState { fn from(state: ibc::tm::types::ConsensusState) -> Self { Self::Tendermint(state.into()) diff --git a/solana/solana-ibc/programs/solana-ibc/src/events.rs b/solana/solana-ibc/programs/solana-ibc/src/events.rs index 5adb28ce..d1a6090f 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/events.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/events.rs @@ -147,7 +147,6 @@ pub fn emit<'a>(event: impl Into>) -> Result<(), String> { event.into().emit() } - /// Defines Copy-on-Write wrapper for specified type. /// /// Due to limited interface of the [`alloc::borrow::Cow`] type, we need @@ -240,7 +239,6 @@ pub fn bytes(value: &[u8]) -> alloc::borrow::Cow<'_, [u8]> { alloc::borrow::Cow::Borrowed(value) } - #[cfg(test)] // insta uses open to read the snapshot file which is not available when running // through Miri. diff --git a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs index 57087833..772eea68 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/execution_context.rs @@ -118,7 +118,6 @@ impl IbcStorage<'_, '_> { } } - impl ibc::ExecutionContext for IbcStorage<'_, '_> { /// Does nothing in the current implementation. /// diff --git a/solana/solana-ibc/programs/solana-ibc/src/ix_data_account.rs b/solana/solana-ibc/programs/solana-ibc/src/ix_data_account.rs index 27119034..43d45f8b 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/ix_data_account.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/ix_data_account.rs @@ -129,7 +129,6 @@ impl anchor_lang::InstructionData for Instruction { fn data(&self) -> Vec { Vec::new() } } - #[test] fn test_get_ix_data() { assert_eq!( diff --git a/solana/solana-ibc/programs/solana-ibc/src/mocks.rs b/solana/solana-ibc/programs/solana-ibc/src/mocks.rs index 53489f09..a0014c84 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/mocks.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/mocks.rs @@ -3,7 +3,6 @@ use anchor_lang::prelude::*; use crate::ibc::ExecutionContext; use crate::{ibc, storage, MockDeliver}; - pub(crate) fn mock_deliver<'a, 'info>( ctx: Context<'a, 'a, 'a, 'info, MockDeliver<'info>>, port_id: ibc::PortId, @@ -78,7 +77,6 @@ pub(crate) fn mock_deliver<'a, 'info>( ) .unwrap(); - // For Client on Chain A store .store_connection( diff --git a/solana/solana-ibc/programs/solana-ibc/src/storage.rs b/solana/solana-ibc/programs/solana-ibc/src/storage.rs index a57fac5c..c71e038e 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/storage.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/storage.rs @@ -235,7 +235,6 @@ impl<'a> core::ops::DerefMut for ClientMut<'a> { fn deref_mut(&mut self) -> &mut ClientStore { self.store } } - #[derive(Clone, Debug, borsh::BorshSerialize, borsh::BorshDeserialize)] /// Information about a specific `(port, channel)`. pub struct PortChannelStore { @@ -321,7 +320,6 @@ pub struct Asset { pub effective_decimals_on_sol: u8, } - impl PrivateStorage { /// Returns number of known clients; or counter for the next client. pub fn client_counter(&self) -> u64 { diff --git a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs index 7c0624d4..44fba5c2 100644 --- a/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs +++ b/solana/solana-ibc/programs/solana-ibc/src/validation_context.rs @@ -9,7 +9,6 @@ use crate::consensus_state::AnyConsensusState; use crate::ibc::{self, ConsensusState}; use crate::storage::{self, IbcStorage}; - type Result = core::result::Result; impl ibc::ValidationContext for IbcStorage<'_, '_> { @@ -296,7 +295,6 @@ impl IbcStorage<'_, '_> { } } - impl ibc::ClientValidationContext for IbcStorage<'_, '_> { fn update_meta( &self, diff --git a/solana/trie/src/alloc.rs b/solana/trie/src/alloc.rs index b57927e4..f812eeb6 100644 --- a/solana/trie/src/alloc.rs +++ b/solana/trie/src/alloc.rs @@ -62,7 +62,6 @@ impl Allocator { } } - /// Address within the trie data. /// /// The value is never zero and when converting from [`Ptr`] always aligned to diff --git a/solana/trie/src/header.rs b/solana/trie/src/header.rs index 323ed967..1efe19da 100644 --- a/solana/trie/src/header.rs +++ b/solana/trie/src/header.rs @@ -81,7 +81,6 @@ impl Header { } } - #[test] fn test_header_encoding() { const ONE: CryptoHash = CryptoHash([1; 32]); diff --git a/solana/trie/src/lib.rs b/solana/trie/src/lib.rs index ae06d695..05a34e30 100644 --- a/solana/trie/src/lib.rs +++ b/solana/trie/src/lib.rs @@ -15,7 +15,6 @@ pub use account::ResizableAccount; pub use data_ref::DataRef; pub use sealable_trie::Trie; - /// Trie stored in a Solana account. #[derive(Debug)] pub struct TrieAccount( @@ -117,7 +116,6 @@ impl core::ops::DerefMut for TrieAccount { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } - #[test] fn test_trie_sanity() { const ONE: lib::hash::CryptoHash = lib::hash::CryptoHash([1; 32]); diff --git a/solana/write-account/src/program.rs b/solana/write-account/src/program.rs index becf0b76..b97021d3 100644 --- a/solana/write-account/src/program.rs +++ b/solana/write-account/src/program.rs @@ -66,7 +66,6 @@ fn process_instruction<'a>( } } - /// Handles the Write operation. fn handle_write( program_id: &Pubkey, @@ -151,7 +150,6 @@ fn setup_write_account( } } - /// Handles Free operation. fn handle_free(accounts: Accounts) -> Result { { @@ -168,7 +166,6 @@ fn handle_free(accounts: Accounts) -> Result { accounts.write.realloc(0, false) } - /// Accounts used when processing instruction. #[derive(Clone, Copy)] struct Accounts<'a, 'info> { @@ -238,7 +235,6 @@ impl<'a, 'info> Accounts<'a, 'info> { } } - /// Reads given object from the start of the slice advancing it. /// /// Returns an error if slice is too short.