Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IBC Hooks: add hooks to call bridge escrow program #403

Merged
merged 11 commits into from
Nov 1, 2024
Merged
9 changes: 7 additions & 2 deletions solana/solana-ibc/programs/solana-ibc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ pub const WSOL_ADDRESS: &str = "So11111111111111111111111111111111111111112";
pub const MINIMUM_FEE_ACCOUNT_BALANCE: u64 =
solana_program::native_token::LAMPORTS_PER_SOL;

pub const BRIDGE_ESCROW_PROGRAM_ID: &str =
"AhfoGVmS19tvkEG2hBuZJ1D6qYEjyFmXZ1qPoFD6H4Mj";
pub const HOOK_TOKEN_ADDRESS: &str =
"0x36dd1bfe89d409f869fabbe72c3cf72ea8b460f6";
mina86 marked this conversation as resolved.
Show resolved Hide resolved

declare_id!("2HLLVco5HvwWriNbUhmVwA2pCetRkpgrqwnjcsZdyTKT");

#[cfg(not(feature = "mocks"))]
Expand Down Expand Up @@ -472,8 +477,8 @@ pub mod solana_ibc {
/// doesnt exists.
///
/// Would panic if it doesnt match the one that is in the packet
pub fn send_transfer(
ctx: Context<SendTransfer>,
pub fn send_transfer<'a, 'info>(
ctx: Context<'a, 'a, 'a, 'info, SendTransfer<'info>>,
hashed_full_denom: CryptoHash,
msg: ibc::MsgTransfer,
) -> Result<()> {
Expand Down
5 changes: 5 additions & 0 deletions solana/solana-ibc/programs/solana-ibc/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ pub struct TransferAccounts<'a> {
pub mint_authority: Option<AccountInfo<'a>>,
pub token_program: Option<AccountInfo<'a>>,
pub fee_collector: Option<AccountInfo<'a>>,
/// Contains the list of accounts required for the hooks
/// if present
pub remaining_accounts: Vec<AccountInfo<'a>>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -544,6 +547,7 @@ macro_rules! from_ctx {
};
($ctx:expr, with accounts) => {{
let accounts = &$ctx.accounts;
let remaining_accounts = &$ctx.remaining_accounts;
let accounts = TransferAccounts {
sender: Some(accounts.sender.as_ref().to_account_info()),
receiver: accounts
Expand Down Expand Up @@ -574,6 +578,7 @@ macro_rules! from_ctx {
.fee_collector
.as_deref()
.map(ToAccountInfo::to_account_info),
remaining_accounts: remaining_accounts.to_vec()
};
$crate::storage::from_ctx!($ctx, accounts = accounts)
}};
Expand Down
116 changes: 109 additions & 7 deletions solana/solana-ibc/programs/solana-ibc/src/transfer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::result::Result;
use std::str;
use std::str::{self, FromStr};

use anchor_lang::prelude::*;
use serde::{Deserialize, Serialize};
use spl_token::solana_program::instruction::Instruction;
use spl_token::solana_program::program::invoke;

use crate::ibc;
use crate::ibc::apps::transfer::types::packet::PacketData;
use crate::ibc::apps::transfer::types::proto::transfer::v2::FungibleTokenPacketData;
use crate::storage::IbcStorage;
use crate::{ibc, BRIDGE_ESCROW_PROGRAM_ID, HOOK_TOKEN_ADDRESS};

pub(crate) mod impls;

Expand Down Expand Up @@ -142,12 +144,112 @@ impl ibc::Module for IbcStorage<'_, '_> {
.into_bytes(),
..packet.clone()
};
let (extras, ack) = ibc::apps::transfer::module::on_recv_packet_execute(
self,
&maybe_ft_packet,
);
let ack_status = str::from_utf8(ack.as_bytes())
let (extras, mut ack) =
ibc::apps::transfer::module::on_recv_packet_execute(
self,
&maybe_ft_packet,
);
let cloned_ack = ack.clone();
let ack_status = str::from_utf8(cloned_ack.as_bytes())
mina86 marked this conversation as resolved.
Show resolved Hide resolved
.expect("Invalid acknowledgement string");
let status = serde_json::from_slice::<ibc::AcknowledgementStatus>(
ack.as_bytes(),
);
mina86 marked this conversation as resolved.
Show resolved Hide resolved
let success = if let Ok(status) = status {
status.is_successful()
} else {
ack = ibc::AcknowledgementStatus::error(
ibc::TokenTransferError::AckDeserialization.into(),
)
.into();
false
};
fn call_bridge_escrow(
accounts: &[AccountInfo],
data: Vec<u8>,
) -> Result<(), ibc::AcknowledgementStatus> {
// Perform hooks
let data = match serde_json::from_slice::<PacketData>(&data) {
Ok(data) => data,
Err(_) => {
return Err(ibc::AcknowledgementStatus::error(
ibc::TokenTransferError::PacketDataDeserialization
.into(),
));
}
};
mina86 marked this conversation as resolved.
Show resolved Hide resolved
// The hook would only be called if the transferred token is the one we are interested in
if data.token.denom.base_denom.as_str() == HOOK_TOKEN_ADDRESS {
mina86 marked this conversation as resolved.
Show resolved Hide resolved
// The memo is a string and the structure is as follow:
// "<number of accounts>,<AccountKey1> ..... <AccountKeyN>,<intent_id>,<memo>"
//
// The relayer would parse the memo and pass the relevant accounts
// The intent_id and memo needs to be stripped
let memo = data.memo.as_ref();
let (accounts_size, rest) = memo.split_once(',').ok_or(
ibc::AcknowledgementStatus::error(
ibc::TokenTransferError::Other(
"Invalid memo".to_string(),
)
.into(),
),
)?;
// This is the 8 byte discriminant since the program is written in
// anchor. it is hash of "<namespace>:<function_name>" which is
// "global:on_receive_transfer" respectively.
let instruction_discriminant: Vec<u8> =
vec![149, 112, 68, 208, 4, 206, 248, 125];
mina86 marked this conversation as resolved.
Show resolved Hide resolved
let values = rest.split(',').collect::<Vec<&str>>();
mina86 marked this conversation as resolved.
Show resolved Hide resolved
let (_passed_accounts, ix_data) =
values.split_at(accounts_size.parse::<usize>().unwrap());
let intent_id = ix_data.first().ok_or(
ibc::AcknowledgementStatus::error(
ibc::TokenTransferError::Other(
"Invalid memo".to_string(),
)
.into(),
),
)?;
let memo = ix_data[1..].join(",");
let mut instruction_data = instruction_discriminant;
instruction_data.extend_from_slice(intent_id.as_bytes());
instruction_data.extend_from_slice(memo.as_bytes());
mina86 marked this conversation as resolved.
Show resolved Hide resolved

let bridge_escrow_program_id =
Pubkey::from_str(BRIDGE_ESCROW_PROGRAM_ID).unwrap();

let account_metas = accounts
.iter()
.map(|account| AccountMeta {
pubkey: *account.key,
is_signer: account.is_signer,
is_writable: account.is_writable,
})
.collect::<Vec<AccountMeta>>();
let instruction = Instruction::new_with_bytes(
bridge_escrow_program_id,
&instruction_data,
account_metas,
);

invoke(&instruction, accounts).map_err(|err| {
ibc::AcknowledgementStatus::error(
ibc::TokenTransferError::Other(err.to_string()).into(),
)
})?;
msg!("Hook: Bridge escrow call successful");
}
Ok(())
}

if success {
let store = self.borrow();
let accounts = &store.accounts.remaining_accounts;
let result = call_bridge_escrow(accounts, maybe_ft_packet.data);
if let Err(status) = result {
ack = status.into();
}
}
msg!("ibc::Packet acknowledgement: {}", ack_status);
(extras, ack)
}
Expand Down
Loading