diff --git a/Cargo.lock b/Cargo.lock index 61001b0..18e33f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2474,7 +2474,7 @@ dependencies = [ [[package]] name = "tonlib" version = "0.15.0" -source = "git+https://github.com/oraichain/tonlib-rs.git?rev=f9f15c8#f9f15c81294fb749b807c3275f0104735278501a" +source = "git+https://github.com/oraichain/tonlib-rs.git?rev=fea807086556bed79c004e63f83753e8b234a250#fea807086556bed79c004e63f83753e8b234a250" dependencies = [ "async-trait", "base64 0.22.1", @@ -2508,7 +2508,7 @@ dependencies = [ [[package]] name = "tonlib-sys" version = "2024.6.0" -source = "git+https://github.com/oraichain/tonlib-rs.git?rev=f9f15c8#f9f15c81294fb749b807c3275f0104735278501a" +source = "git+https://github.com/oraichain/tonlib-rs.git?rev=fea807086556bed79c004e63f83753e8b234a250#fea807086556bed79c004e63f83753e8b234a250" dependencies = [ "cmake", ] diff --git a/Cargo.toml b/Cargo.toml index 3358e6f..6015475 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ cw-tonbridge-validator = { path = "./contracts/tonbridge_validator" } oraiswap = { git = "https://github.com/oraichain/oraiswap.git", rev = "f2c1cec" } cw20-ics20-msg = { git = "https://github.com/oraichain/ibc-bridge-wasm.git", rev = "0e0258c" } -tonlib = { git = "https://github.com/oraichain/tonlib-rs.git", rev = "f9f15c8", default-features = false } +tonlib = { git = "https://github.com/oraichain/tonlib-rs.git", rev = "fea807086556bed79c004e63f83753e8b234a250", default-features = false } cw-multi-test = "0.16.6" [patch.crates-io] diff --git a/contracts/tonbridge_bridge/src/bridge.rs b/contracts/tonbridge_bridge/src/bridge.rs index 202dc1f..70d16b2 100644 --- a/contracts/tonbridge_bridge/src/bridge.rs +++ b/contracts/tonbridge_bridge/src/bridge.rs @@ -8,6 +8,7 @@ use cw20_ics20_msg::{ amount::{convert_local_to_remote, convert_remote_to_local, Amount}, helper::{denom_to_asset_info, parse_asset_info_denom}, }; + use oraiswap::{ asset::{Asset, AssetInfo}, router::{RouterController, SwapOperation}, @@ -16,31 +17,32 @@ use std::ops::Mul; use tonbridge_bridge::{ msg::{BridgeToTonMsg, FeeData}, parser::{get_key_ics20_ibc_denom, parse_ibc_wasm_port_id}, - state::{MappingMetadata, PacketReceive, Ratio, SendPacket, Status}, -}; -use tonbridge_parser::{ - to_bytes32, - transaction_parser::{ITransactionParser, TransactionParser}, - types::BridgePacketData, - OPCODE_1, OPCODE_2, + state::{MappingMetadata, Ratio, ReceivePacket, TimeoutSendPacket}, }; +use tonbridge_parser::{to_bytes32, types::BridgePacketData, OPCODE_1, OPCODE_2}; use tonbridge_validator::wrapper::ValidatorWrapper; use tonlib::{ cell::{BagOfCells, Cell}, - responses::MessageType, + responses::{MaybeRefData, MessageType, Transaction, TransactionMessage}, }; use crate::{ channel::{decrease_channel_balance, increase_channel_balance}, error::ContractError, - helper::is_expired, + helper::{ + build_ack_commitment, build_bridge_to_ton_commitment, + build_receive_packet_timeout_commitment, is_expired, + }, state::{ - ics20_denoms, CONFIG, LAST_PACKET_SEQ, PACKET_RECEIVE, PROCESSED_TXS, SEND_PACKET, - TOKEN_FEE, + ics20_denoms, ACK_COMMITMENT, CONFIG, LAST_PACKET_SEQ, PROCESSED_TXS, + SEND_PACKET_COMMITMENT, TIMEOUT_RECEIVE_PACKET, TIMEOUT_RECEIVE_PACKET_COMMITMENT, + TIMEOUT_SEND_PACKET, TOKEN_FEE, }, }; -const DEFAULT_TIMEOUT: u64 = 3600; // 3600s +pub const DEFAULT_TIMEOUT: u64 = 3600; // 3600s +pub const RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER: u32 = 0x0da5c1c4; // crc32("ops::ack_timeout") +pub const SEND_TO_TON_MAGIC_NUMBER: u32 = 0x2e89be5b; // crc32("op::send_to_ton") #[cw_serde] pub struct Bridge { @@ -56,22 +58,45 @@ impl Bridge { } impl Bridge { + pub fn validate_transaction_out_msg( + out_msg: MaybeRefData, + bridge_adapter_addr: String, + ) -> Option { + if out_msg.data.is_none() { + return None; + } + let out_msg = out_msg.data.unwrap(); + if out_msg.info.msg_type != MessageType::ExternalOut as u8 { + return None; + } + // verify source of tx is bridge adapter contract + if out_msg.info.src.to_string() != bridge_adapter_addr { + return None; + } + + if out_msg.body.cell_ref.is_none() { + return None; + } + let cell = out_msg.body.cell_ref.unwrap().0; + if cell.is_none() { + return None; + } + + // body cell + Some(cell.unwrap().cell) + } + pub fn read_transaction( &self, - deps: DepsMut, - env: &Env, - contract_address: &str, + storage: &mut dyn Storage, + querier: &QuerierWrapper, tx_proof: &[u8], tx_boc: &[u8], - ) -> Result<(Vec, Vec), ContractError> { - let mut cosmos_msgs: Vec = vec![]; - let mut attrs: Vec = vec![]; - let config = CONFIG.load(deps.storage)?; - + ) -> Result { let tx_cells = BagOfCells::parse(tx_boc)?; let tx_root = tx_cells.single_root()?; let transaction = Cell::load_transaction(tx_root, &mut 0, &mut tx_root.parser())?; - let transaction_hash = to_bytes32(&HexBinary::from(transaction.hash))?; + let transaction_hash = to_bytes32(&HexBinary::from(transaction.hash.clone()))?; let tx_proof_cells = BagOfCells::parse(tx_proof)?; let tx_proof_cell_first_ref = tx_proof_cells.single_root()?.reference(0)?; @@ -79,7 +104,7 @@ impl Bridge { let is_root_hash_verified = self .validator - .is_verified_block(&deps.querier, HexBinary::from(root_hash))?; + .is_verified_block(querier, HexBinary::from(root_hash))?; if !is_root_hash_verified { return Err(ContractError::Std(StdError::generic_err( @@ -120,7 +145,7 @@ impl Bridge { } let is_tx_processed = PROCESSED_TXS - .may_load(deps.storage, &transaction_hash)? + .may_load(storage, &transaction_hash)? .unwrap_or(false); if is_tx_processed { @@ -129,93 +154,41 @@ impl Bridge { ))); } - PROCESSED_TXS.save(deps.storage, &transaction_hash, &true)?; - let tx_parser = TransactionParser::default(); - - let storage = deps.storage; - let api = deps.api; - let querier = deps.querier; - for out_msg in transaction.out_msgs.into_values() { - if out_msg.data.is_none() { - deps.api.debug("empty out_msg data"); - continue; - } - let out_msg = out_msg.data.unwrap(); - if out_msg.info.msg_type != MessageType::ExternalOut as u8 { - deps.api.debug("msg type is not external out"); - continue; - } - - if out_msg.body.cell_ref.is_none() { - deps.api.debug("cell ref is none when reading transaction"); - continue; - } - let cell = out_msg.body.cell_ref.unwrap().0; - if cell.is_none() { - deps.api.debug("any cell is empty when reading transaction"); - } - - // verify source of tx is bridge adapter contract - if out_msg.info.src.to_string() != config.bridge_adapter { - deps.api - .debug("this tx is not from bridge_adapter contract"); - continue; - } - - let cell = cell.unwrap().cell; - let packet_data = tx_parser.parse_packet_data(&cell)?.to_pretty()?; - - let mapping = ics20_denoms().load( - storage, - &get_key_ics20_ibc_denom( - &parse_ibc_wasm_port_id(contract_address), - &packet_data.src_channel, - &packet_data.src_denom, - ), - )?; - - let mut res = - Bridge::handle_packet_receive(env, storage, api, &querier, packet_data, mapping)?; - cosmos_msgs.append(&mut res.0); - attrs.append(&mut res.1); - } - Ok((cosmos_msgs, attrs)) + PROCESSED_TXS.save(storage, &transaction_hash, &true)?; + Ok(transaction) } pub fn handle_packet_receive( - env: &Env, storage: &mut dyn Storage, api: &dyn Api, querier: &QuerierWrapper, + current_timestamp: u64, data: BridgePacketData, mapping: MappingMetadata, ) -> Result<(Vec, Vec), ContractError> { let config = CONFIG.load(storage)?; - let mut packet_receive = PacketReceive { + let receive_packet: ReceivePacket = ReceivePacket { + magic: RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER, seq: data.seq, - timeout: data.timeout, + timeout_timestamp: data.timeout_timestamp, + src_sender: data.src_sender.clone(), src_denom: data.src_denom.clone(), src_channel: data.src_channel.clone(), amount: data.amount, - dest_denom: data.dest_denom.clone(), - dest_channel: data.dest_channel.clone(), - dest_receiver: data.dest_receiver.clone(), - orai_address: data.orai_address.clone(), - status: Status::Success, }; - // check packet timeout - if is_expired(env, data.timeout) { - packet_receive.status = Status::Timeout; - PACKET_RECEIVE.save(storage, packet_receive.seq, &packet_receive)?; - return Ok(( - vec![], - vec![ - attr("action", "bridge_to_cosmos"), - attr("status", "timeout"), - attr("packet_receive", format!("{:?}", packet_receive)), - ], - )); + // check packet timeout + if is_expired(current_timestamp, data.timeout_timestamp) { + TIMEOUT_RECEIVE_PACKET.save(storage, receive_packet.seq, &receive_packet)?; + // must store timeout commitment + let commitment = build_receive_packet_timeout_commitment(data.seq)?; + TIMEOUT_RECEIVE_PACKET_COMMITMENT.save( + storage, + receive_packet.seq, + &to_bytes32(&HexBinary::from(commitment))?, + )?; + + return Ok((vec![], vec![attr("status", "timeout")])); } // increase first increase_channel_balance(storage, &data.src_channel, &data.src_denom, data.amount)?; @@ -233,6 +206,7 @@ impl Bridge { ); let fee_data = Bridge::process_deduct_fee(storage, querier, api, data.src_denom, to_send)?; + let local_amount = fee_data.deducted_amount; if !fee_data.token_fee.is_empty() { cosmos_msgs.push( @@ -250,23 +224,21 @@ impl Bridge { } let attributes: Vec = vec![ - attr("action", "bridge_to_cosmos"), attr("status", "success"), - attr("packet_receive", format!("{:?}", packet_receive)), attr("dest_receiver", recipient.as_str()), - attr("local_amount", fee_data.deducted_amount.to_string()), + attr("local_amount", local_amount.to_string()), attr("relayer_fee", fee_data.relayer_fee.amount().to_string()), attr("token_fee", fee_data.token_fee.amount().to_string()), ]; // if the fees have consumed all user funds, we send all the fees to our token fee receiver - if fee_data.deducted_amount.is_zero() { + if local_amount.is_zero() { return Ok((cosmos_msgs, attributes)); } let msg = Asset { info: mapping.asset_info.clone(), - amount: fee_data.deducted_amount, + amount: local_amount, }; if mapping.opcode == OPCODE_1 { let msg = match msg.info { @@ -278,7 +250,7 @@ impl Bridge { AssetInfo::Token { contract_addr } => { Cw20Contract(contract_addr).call(Cw20ExecuteMsg::Mint { recipient: recipient.to_string(), - amount: fee_data.deducted_amount, + amount: local_amount, }) } }?; @@ -287,6 +259,14 @@ impl Bridge { cosmos_msgs.push(msg.into_msg(None, querier, recipient)?); } + // store ack commitment + let commitment = build_ack_commitment(receive_packet.seq)?; + ACK_COMMITMENT.save( + storage, + receive_packet.seq, + &to_bytes32(&HexBinary::from(commitment))?, + )?; + Ok((cosmos_msgs, attributes)) } @@ -295,14 +275,11 @@ impl Bridge { env: Env, msg: BridgeToTonMsg, amount: Amount, - _sender: Addr, + sender: Addr, ) -> Result { - let timeout = msg + let timeout_timestamp = msg .timeout .unwrap_or(env.block.time.seconds() + DEFAULT_TIMEOUT); - if is_expired(&env, timeout) { - return Err(ContractError::Expired {}); - } let config = CONFIG.load(deps.storage)?; let denom_key = get_key_ics20_ibc_denom( @@ -344,8 +321,9 @@ impl Bridge { ) } + let local_amount = fee_data.deducted_amount; let remote_amount = convert_local_to_remote( - fee_data.deducted_amount, + local_amount, mapping.remote_decimals, mapping.asset_info_decimals, )?; @@ -357,20 +335,34 @@ impl Bridge { remote_amount, )?; - // store to pending packet transfer - let last_packet_seq = LAST_PACKET_SEQ.may_load(deps.storage)?.unwrap_or_default() + 1; - //FIXME: store timeout to send_packet - SEND_PACKET.save( + let commitment = build_bridge_to_ton_commitment( + last_packet_seq, + mapping.crc_src, + sender.as_str(), + &msg.to, + &msg.denom, + remote_amount, + timeout_timestamp, + )?; + SEND_PACKET_COMMITMENT.save( + deps.storage, + last_packet_seq, + &to_bytes32(&HexBinary::from(commitment))?, + )?; + + // this packet is saved just in case we need to refund the sender due to timeout + TIMEOUT_SEND_PACKET.save( deps.storage, last_packet_seq, - &SendPacket { - sequence: last_packet_seq, - to: msg.to.clone(), - denom: msg.denom.clone(), - amount: remote_amount, - crc_src: msg.crc_src, + &TimeoutSendPacket { + sender: sender.to_string(), + local_refund_asset: Asset { + info: mapping.asset_info, + amount: local_amount, + }, + timeout_timestamp, }, )?; LAST_PACKET_SEQ.save(deps.storage, &last_packet_seq)?; @@ -381,11 +373,11 @@ impl Bridge { ("action", "bridge_to_ton"), ("dest_receiver", &msg.to), ("dest_denom", &msg.denom), - ("local_amount", &fee_data.deducted_amount.to_string()), - ("crc_src", &msg.crc_src.to_string()), + ("local_amount", &local_amount.to_string()), + ("crc_src", &mapping.crc_src.to_string()), ("relayer_fee", &fee_data.relayer_fee.amount().to_string()), ("token_fee", &fee_data.token_fee.amount().to_string()), - ("timeout", &timeout.to_owned().to_string()), + ("timeout", &timeout_timestamp.to_owned().to_string()), ("remote_amount", &remote_amount.to_string()), ("seq", &last_packet_seq.to_string()), ])) diff --git a/contracts/tonbridge_bridge/src/contract.rs b/contracts/tonbridge_bridge/src/contract.rs index 9a2cf22..2175d08 100644 --- a/contracts/tonbridge_bridge/src/contract.rs +++ b/contracts/tonbridge_bridge/src/contract.rs @@ -1,6 +1,9 @@ #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{from_binary, to_binary, Addr, Empty, Order, StdError, Uint128}; +use cosmwasm_std::{ + attr, from_binary, to_binary, Addr, Api, Attribute, CosmosMsg, Empty, Order, QuerierWrapper, + StdError, Storage, Uint128, +}; use cosmwasm_std::{Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult}; use cw20::Cw20ReceiveMsg; use cw20_ics20_msg::amount::Amount; @@ -12,15 +15,19 @@ use tonbridge_bridge::msg::{ PairQuery, QueryMsg, UpdatePairMsg, }; use tonbridge_bridge::parser::{get_key_ics20_ibc_denom, parse_ibc_wasm_port_id}; -use tonbridge_bridge::state::{Config, MappingMetadata, SendPacket, TokenFee}; +use tonbridge_bridge::state::{Config, MappingMetadata, ReceivePacket, TokenFee}; use tonbridge_parser::to_bytes32; -use tonlib::cell::Cell; +use tonbridge_parser::transaction_parser::{get_channel_id, ITransactionParser, TransactionParser}; +use tonlib::cell::{BagOfCells, Cell, TonCellError}; +use tonlib::responses::{MaybeRefData, TransactionMessage}; -use crate::bridge::Bridge; +use crate::bridge::{Bridge, RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER}; use crate::error::ContractError; +use crate::helper::is_expired; use crate::state::{ - ics20_denoms, CONFIG, OWNER, PROCESSED_TXS, REMOTE_INITIATED_CHANNEL_STATE, SEND_PACKET, - TOKEN_FEE, + ics20_denoms, CONFIG, OWNER, PROCESSED_TXS, REMOTE_INITIATED_CHANNEL_STATE, + SEND_PACKET_COMMITMENT, TIMEOUT_RECEIVE_PACKET, TIMEOUT_RECEIVE_PACKET_COMMITMENT, + TIMEOUT_SEND_PACKET, TOKEN_FEE, }; #[cfg_attr(not(feature = "library"), entry_point)] @@ -88,7 +95,17 @@ pub fn execute( Bridge::handle_bridge_to_ton(deps, env, msg, amount, info.sender) } ExecuteMsg::Receive(msg) => execute_receive(deps, env, info, msg), - ExecuteMsg::SubmitBridgeToTonInfo { data } => execute_submit_bridge_to_ton_info(deps, data), + ExecuteMsg::ProcessTimeoutSendPacket { + masterchain_header_proof, + tx_boc, + tx_proof_unreceived, + } => { + process_timeout_send_packet(deps, masterchain_header_proof, tx_proof_unreceived, tx_boc) + } + ExecuteMsg::ProcessTimeoutRecievePacket { receive_packet } => { + process_timeout_receive_packet(deps, receive_packet) + } + ExecuteMsg::Acknowledgment { tx_proof, tx_boc } => acknowledgment(deps, tx_proof, tx_boc), } } @@ -107,6 +124,7 @@ pub fn execute_update_owner( ])) } +#[allow(clippy::too_many_arguments)] pub fn execute_update_config( deps: DepsMut, info: MessageInfo, @@ -164,17 +182,83 @@ pub fn read_transaction( ) -> Result { let config = CONFIG.load(deps.storage)?; let bridge = Bridge::new(config.validator_contract_addr); - let res = bridge.read_transaction( - deps, - &env, - env.contract.address.as_str(), + let mut cosmos_msgs: Vec = vec![]; + let mut attrs: Vec = vec![]; + let transaction = bridge.read_transaction( + deps.storage, + &deps.querier, + tx_proof.as_slice(), + tx_boc.as_slice(), + )?; + let tx_parser = TransactionParser::default(); + for out_msg in transaction.out_msgs.into_values() { + let cell = Bridge::validate_transaction_out_msg(out_msg, config.bridge_adapter.clone()); + if cell.is_none() { + continue; + } + let cell = cell.unwrap(); + let packet_data = tx_parser.parse_packet_data(&cell)?.to_pretty()?; + + let mapping = ics20_denoms().load( + deps.storage, + &get_key_ics20_ibc_denom( + &parse_ibc_wasm_port_id(env.contract.address.as_str()), + &packet_data.src_channel, + &packet_data.src_denom, + ), + )?; + + let mut res = Bridge::handle_packet_receive( + deps.storage, + deps.api, + &deps.querier, + env.block.time.seconds(), + packet_data, + mapping, + )?; + cosmos_msgs.append(&mut res.0); + attrs.append(&mut res.1); + + // we assume that one transaction only has one matching external out msg. After handling it -> we stop reading + break; + } + Ok(Response::new() + .add_messages(cosmos_msgs) + .add_attributes(vec![("action", "bridge_to_cosmos")]) + .add_attributes(attrs)) +} + +pub fn acknowledgment( + deps: DepsMut, + tx_proof: HexBinary, + tx_boc: HexBinary, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let bridge = Bridge::new(config.validator_contract_addr); + + let mut attrs: Vec = vec![]; + let transaction = bridge.read_transaction( + deps.storage, + &deps.querier, tx_proof.as_slice(), tx_boc.as_slice(), )?; + let tx_parser = TransactionParser::default(); + for out_msg in transaction.out_msgs.into_values() { + let cell = Bridge::validate_transaction_out_msg(out_msg, config.bridge_adapter.clone()); + if cell.is_none() { + continue; + } + let cell = cell.unwrap(); + let seq = tx_parser.parse_ack_data(&cell)?; + + // remove packet commitment + SEND_PACKET_COMMITMENT.remove(deps.storage, seq); + attrs.push(attr("seq", &seq.to_string())); + } Ok(Response::new() - .add_messages(res.0) - .add_attributes(vec![("action", "read_transaction")]) - .add_attributes(res.1)) + .add_attributes(vec![("action", "acknowledgment")]) + .add_attributes(attrs)) } pub fn update_mapping_pair( @@ -203,6 +287,7 @@ pub fn update_mapping_pair( remote_decimals: msg.remote_decimals, asset_info_decimals: msg.local_asset_info_decimals, opcode: to_bytes32(&msg.opcode)?, + crc_src: msg.crc_src, }, )?; Ok(Response::new().add_attributes(vec![("action", "update_mapping_pair")])) @@ -245,41 +330,138 @@ pub fn execute_receive( Bridge::handle_bridge_to_ton(deps, env, msg, amount, sender) } -pub fn execute_submit_bridge_to_ton_info( +pub fn process_timeout_send_packet( deps: DepsMut, - boc: HexBinary, + latest_masterchain_header_proof: HexBinary, + tx_proof_unreceived: HexBinary, + tx_boc: HexBinary, +) -> Result { + let config = CONFIG.load(deps.storage)?; + let bridge = Bridge::new(config.validator_contract_addr); + + let header_cells = BagOfCells::parse_hex(&latest_masterchain_header_proof.to_hex())?; + let block_cell = header_cells.single_root()?.reference(0)?; + + // TODO: need to update latest client state of TON every time we have a new incoming tx + // so that we can verify the logical time against the latest_masterchain_header_proof + let block_info = Cell::load_block(block_cell)?; + let masterchain_block_latest_timestamp = + block_info.info.to_owned().unwrap_or_default().gen_utime; + + let transaction = bridge.read_transaction( + deps.storage, + &deps.querier, + tx_proof_unreceived.as_slice(), + tx_boc.as_slice(), + )?; + + for out_msg in transaction.out_msgs.into_values() { + let refund_msgs = build_timeout_send_packet_refund_msgs( + deps.storage, + deps.api, + &deps.querier, + out_msg, + config.bridge_adapter.clone(), + masterchain_block_latest_timestamp, + )?; + if refund_msgs.is_empty() { + continue; + } + + return Ok(Response::new() + .add_attribute("action", "process_timeout_send_packet") + .add_messages(refund_msgs)); + } + Err(ContractError::Std(StdError::generic_err( + "The given transaction has no timeout message", + ))) +} + +// called when there is a TON bridge transaction to CW, but it has expired +// -> relayer triggers this function for the TON side to refund to the sender. +pub fn process_timeout_receive_packet( + deps: DepsMut, + data: HexBinary, ) -> Result { let mut cell = Cell::default(); - cell.data = boc.as_slice().to_vec(); + cell.data = data.as_slice().to_vec(); cell.bit_len = cell.data.len() * 8; let mut parser = cell.parser(); + let magic_number = parser.load_u32(32)?; + if magic_number != RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER { + return Err(ContractError::TonCellError( + TonCellError::cell_parser_error("Not a receive packet timeout"), + )); + } let seq = parser.load_u64(64)?; - let to = parser.load_address()?; - let denom = parser.load_address()?; + let src_sender = parser.load_address()?; + let src_denom = parser.load_address()?; + // assume that the largest channel id is 65536 = 2^16 + let src_channel_num = parser.load_u16(16)?; let amount = u128::from_be_bytes(parser.load_bytes(16)?.as_slice().try_into()?); - let crc_src = parser.load_u32(32)?; + let timeout_timestamp = parser.load_u64(64)?; + + let timeout_receive_packet = TIMEOUT_RECEIVE_PACKET.load(deps.storage, seq)?; - let send_packet = SEND_PACKET.load(deps.storage, seq)?; - if send_packet.ne(&SendPacket { - sequence: seq, - to: to.to_string(), - denom: denom.to_string(), + if timeout_receive_packet.ne(&ReceivePacket { + magic: magic_number, + seq, + timeout_timestamp, + src_sender: src_sender.to_base64_url(), + src_denom: src_denom.to_base64_url(), + src_channel: get_channel_id(src_channel_num), amount: Uint128::from(amount), - crc_src, }) { - return Err(ContractError::Std(StdError::generic_err( - "Invalid send_packet", - ))); + return Err(ContractError::InvalidSendPacketBoc {}); } // after finished verifying the boc, we remove the packet to prevent replay attack - SEND_PACKET.remove(deps.storage, seq); + TIMEOUT_RECEIVE_PACKET.remove(deps.storage, seq); + TIMEOUT_RECEIVE_PACKET_COMMITMENT.remove(deps.storage, seq); - Ok(Response::new() - .add_attribute("action", "submit_bridge_to_ton_info") - .add_attribute("data", boc.to_hex())) + Ok(Response::new().add_attribute("action", "process_timeout_recv_packet")) +} + +pub fn build_timeout_send_packet_refund_msgs( + storage: &mut dyn Storage, + api: &dyn Api, + querier: &QuerierWrapper, + out_msg: MaybeRefData, + bridge_adapter: String, + latest_masterchain_block_timestamp: u32, +) -> Result, ContractError> { + let tx_parser = TransactionParser::default(); + let cell = Bridge::validate_transaction_out_msg(out_msg, bridge_adapter); + if cell.is_none() { + return Ok(vec![]); + } + let cell = cell.unwrap(); + let packet_seq_timeout = tx_parser.parse_send_packet_timeout_data(&cell)?; + + let timeout_packet = TIMEOUT_SEND_PACKET.may_load(storage, packet_seq_timeout)?; + + // no-op to prevent error spamming from the relayer + if timeout_packet.is_none() { + return Ok(vec![]); + } + let timeout_packet = timeout_packet.unwrap(); + if !is_expired( + latest_masterchain_block_timestamp as u64, + timeout_packet.timeout_timestamp, + ) { + return Err(ContractError::NotExpired {}); + } + let refund_msg = timeout_packet.local_refund_asset.into_msg( + None, + querier, + api.addr_validate(&timeout_packet.sender)?, + )?; + TIMEOUT_SEND_PACKET.remove(storage, packet_seq_timeout); + SEND_PACKET_COMMITMENT.remove(storage, packet_seq_timeout); + + Ok(vec![refund_msg]) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -293,6 +475,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::IsTxProcessed { tx_hash } => to_binary(&is_tx_processed(deps, tx_hash)?), QueryMsg::ChannelStateData { channel_id } => to_binary(&query_channel(deps, channel_id)?), QueryMsg::PairMapping { key } => to_binary(&get_mapping_from_key(deps, key)?), + QueryMsg::QueryTimeoutReceivePackets {} => to_binary(&query_timeout_receive_packets(deps)?), } } @@ -331,6 +514,13 @@ pub fn query_channel(deps: Deps, channel_id: String) -> StdResult StdResult> { + TIMEOUT_RECEIVE_PACKET + .range(deps.storage, None, None, Order::Ascending) + .map(|data| -> StdResult { Ok(data?.1) }) + .collect() +} + fn get_mapping_from_key(deps: Deps, ibc_denom: String) -> StdResult { let result = ics20_denoms().load(deps.storage, &ibc_denom)?; Ok(PairQuery { diff --git a/contracts/tonbridge_bridge/src/error.rs b/contracts/tonbridge_bridge/src/error.rs index 09ef063..bfa66f1 100644 --- a/contracts/tonbridge_bridge/src/error.rs +++ b/contracts/tonbridge_bridge/src/error.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ConversionOverflowError, OverflowError, StdError}; use cw_controllers::AdminError; use cw_utils::PaymentError; use thiserror::Error; -use tonlib::cell::TonCellError; +use tonlib::{address::TonAddressParseError, cell::TonCellError}; #[derive(Error, Debug)] pub enum ContractError { @@ -29,6 +29,9 @@ pub enum ContractError { #[error("{0}")] TryFromSliceError(#[from] TryFromSliceError), + #[error("{0}")] + TonAddressParseError(#[from] TonAddressParseError), + #[error("Unauthorized")] Unauthorized {}, @@ -38,6 +41,15 @@ pub enum ContractError { #[error("Invalid funds")] InvalidFund {}, - #[error("Packet timeout period has expired")] + #[error("Packet has expired due to timeout")] Expired {}, + + #[error("Packet timeout has not been reached for timestamp")] + NotExpired {}, + + #[error("The send packet still exists. Cannot process timeout")] + SendPacketExists {}, + + #[error("The BOC does not match with the send packet")] + InvalidSendPacketBoc {}, } diff --git a/contracts/tonbridge_bridge/src/helper.rs b/contracts/tonbridge_bridge/src/helper.rs index 5df4415..45c0198 100644 --- a/contracts/tonbridge_bridge/src/helper.rs +++ b/contracts/tonbridge_bridge/src/helper.rs @@ -1,5 +1,117 @@ -use cosmwasm_std::Env; +use cosmwasm_std::Uint128; +use tonbridge_parser::transaction_parser::ACK_MAGIC_NUMBER; +use tonlib::{address::TonAddress, cell::CellBuilder}; -pub fn is_expired(env: &Env, timestamp: u64) -> bool { - env.block.time.seconds() > timestamp +use crate::{ + bridge::{RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER, SEND_TO_TON_MAGIC_NUMBER}, + error::ContractError, +}; + +pub fn is_expired(now: u64, timestamp: u64) -> bool { + now > timestamp +} + +pub fn build_bridge_to_ton_commitment( + seq: u64, + crc_src: u32, + sender: &str, + to: &str, + denom: &str, + amount: Uint128, + timeout_timestamp: u64, +) -> Result, ContractError> { + let mut cell_builder = CellBuilder::new(); + cell_builder.store_slice(&seq.to_be_bytes())?; // seq + cell_builder.store_slice(&SEND_TO_TON_MAGIC_NUMBER.to_be_bytes())?; // opcode + cell_builder.store_slice(&crc_src.to_be_bytes())?; // crc_src + cell_builder.store_address(&TonAddress::from_base64_std(to)?)?; // receiver + cell_builder.store_address(&TonAddress::from_base64_std(denom)?)?; // remote denom + cell_builder.store_slice(&amount.to_be_bytes())?; // remote amount + cell_builder.store_slice(&timeout_timestamp.to_be_bytes())?; // timeout timestamp + + let sender_ref = CellBuilder::new().store_string(sender)?.build()?; + cell_builder.store_reference(&sender_ref.to_arc())?; //the first ref is sender address + + let commitment: Vec = cell_builder.build()?.cell_hash()?; + Ok(commitment) +} + +pub fn build_receive_packet_timeout_commitment(seq: u64) -> Result, ContractError> { + let mut cell_builder = CellBuilder::new(); + cell_builder.store_slice(&seq.to_be_bytes())?; // seq + cell_builder.store_slice(&RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER.to_be_bytes())?; // opcode + + let commitment: Vec = cell_builder.build()?.cell_hash()?; + Ok(commitment) +} + +pub fn build_ack_commitment(seq: u64) -> Result, ContractError> { + let mut cell_builder = CellBuilder::new(); + cell_builder.store_slice(&seq.to_be_bytes())?; // seq + cell_builder.store_slice(&ACK_MAGIC_NUMBER.to_be_bytes())?; // opcode + + let commitment: Vec = cell_builder.build()?.cell_hash()?; + Ok(commitment) +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::Uint128; + + use crate::helper::build_ack_commitment; + + use super::{ + build_bridge_to_ton_commitment, build_receive_packet_timeout_commitment, is_expired, + }; + + #[test] + fn test_is_expired() { + assert_eq!(is_expired(1, 2), false); + assert_eq!(is_expired(2, 1), true); + assert_eq!(is_expired(1, 1), false); + } + + #[test] + fn test_build_receive_packet_timeout_commitment() { + let commitment = build_receive_packet_timeout_commitment(1).unwrap(); + assert_eq!( + commitment, + vec![ + 147, 141, 120, 107, 189, 160, 37, 224, 20, 139, 119, 27, 41, 112, 235, 158, 243, + 86, 229, 168, 245, 240, 137, 105, 219, 215, 18, 219, 240, 56, 252, 91 + ] + ) + } + #[test] + fn test_build_ack_commitment() { + let commitment = build_ack_commitment(1).unwrap(); + assert_eq!( + commitment, + vec![ + 179, 254, 186, 2, 145, 250, 132, 167, 133, 98, 70, 99, 164, 49, 39, 41, 170, 131, + 214, 113, 92, 87, 140, 74, 254, 68, 4, 123, 121, 0, 37, 237 + ] + ) + } + + #[test] + fn test_build_bridge_to_ton_commitment() { + let commitment = build_bridge_to_ton_commitment( + 1, + 1576711861, + "orai15un8msx3n5zf9ahlxmfeqd2kwa5wm0nrpxer304m9nd5q6qq0g6sku5pdd", + "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT", + "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT", + Uint128::from(10000000000u128), + 1719945916, + ) + .unwrap(); + assert_eq!( + commitment, + vec![ + 63, 40, 35, 40, 51, 244, 221, 14, 185, 75, 201, 9, 155, 210, 56, 172, 89, 229, 231, + 115, 162, 252, 191, 220, 53, 159, 222, 186, 91, 104, 158, 67 + ] + ) + } } diff --git a/contracts/tonbridge_bridge/src/state.rs b/contracts/tonbridge_bridge/src/state.rs index 15da297..b41ec53 100644 --- a/contracts/tonbridge_bridge/src/state.rs +++ b/contracts/tonbridge_bridge/src/state.rs @@ -2,7 +2,7 @@ use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, MultiIndex}; use cw_controllers::Admin; use tonbridge_bridge::state::{ - ChannelState, Config, MappingMetadata, PacketReceive, Ratio, SendPacket, + ChannelState, Config, MappingMetadata, Ratio, ReceivePacket, TimeoutSendPacket, }; use tonbridge_parser::types::Bytes32; @@ -16,11 +16,13 @@ pub const TOKEN_FEE: Map<&str, Ratio> = Map::new("token_fee"); pub const CONFIG: Item = Item::new("config"); -pub const SEND_PACKET: Map = Map::new("send_packet"); - +pub const TIMEOUT_SEND_PACKET: Map = Map::new("timeout_send_packet"); pub const LAST_PACKET_SEQ: Item = Item::new("last_packet_seq"); - -pub const PACKET_RECEIVE: Map = Map::new("packet_receive"); +pub const TIMEOUT_RECEIVE_PACKET: Map = Map::new("receive_packet"); +pub const SEND_PACKET_COMMITMENT: Map = Map::new("send_packet_commitment"); // cell hash of send_packet +pub const TIMEOUT_RECEIVE_PACKET_COMMITMENT: Map = + Map::new("timeout_receive_packet_commitment"); // cell hash of TIMEOUT_RECEIVE_PACKET +pub const ACK_COMMITMENT: Map = Map::new("ack_commitment"); // =============================== Reference from: https://github.com/oraichain/ibc-bridge-wasm.git /// This channel state is used when a REMOTE chain initiates ibc transfer to LOCAL chain diff --git a/contracts/tonbridge_bridge/src/testing/bridge.rs b/contracts/tonbridge_bridge/src/testing/bridge.rs index b642801..2b3adcb 100644 --- a/contracts/tonbridge_bridge/src/testing/bridge.rs +++ b/contracts/tonbridge_bridge/src/testing/bridge.rs @@ -1,5 +1,3 @@ -use std::ops::Add; - use cosmwasm_std::{ attr, coin, testing::{mock_dependencies, mock_env, mock_info}, @@ -9,23 +7,143 @@ use cosmwasm_std::{ use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20ReceiveMsg}; use cw20_ics20_msg::amount::Amount; use cw_multi_test::Executor; -use oraiswap::asset::AssetInfo; + +use oraiswap::{asset::AssetInfo, router::RouterController}; use tonbridge_bridge::{ msg::{ BridgeToTonMsg, ChannelResponse, ExecuteMsg, InstantiateMsg, QueryMsg as BridgeQueryMsg, UpdatePairMsg, }, - state::{Ratio, SendPacket, TokenFee}, + state::{Config, MappingMetadata, Ratio, TokenFee}, +}; +use tonbridge_parser::{types::BridgePacketData, OPCODE_2}; +use tonlib::{ + address::TonAddress, + responses::{AnyCell, MaybeRefData, MessageType, TransactionMessage}, }; use crate::{ + bridge::{Bridge, DEFAULT_TIMEOUT}, channel::increase_channel_balance, - contract::{execute, execute_submit_bridge_to_ton_info, instantiate}, + contract::{execute, instantiate}, error::ContractError, - state::SEND_PACKET, + helper::build_ack_commitment, + state::{ACK_COMMITMENT, CONFIG, TOKEN_FEE}, testing::mock::{new_mock_app, MockApp}, }; +#[test] +fn test_validate_transaction_out_msg() { + let mut maybe_ref = MaybeRefData::default(); + let bridge_addr = "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res, None); + let mut transaction_message = TransactionMessage::default(); + transaction_message.info.msg_type = MessageType::ExternalIn as u8; + maybe_ref.data = Some(transaction_message.clone()); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res, None); + transaction_message.info.msg_type = MessageType::ExternalOut as u8; + maybe_ref.data = Some(transaction_message.clone()); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res, None); + transaction_message.info.src = TonAddress::from_base64_url(&bridge_addr.clone()).unwrap(); + maybe_ref.data = Some(transaction_message.clone()); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res, None); + + transaction_message.body.cell_ref = Some((None, None)); + maybe_ref.data = Some(transaction_message.clone()); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res, None); + + let any_cell = AnyCell::default(); + transaction_message.body.cell_ref = Some((Some(any_cell.clone()), None)); + maybe_ref.data = Some(transaction_message.clone()); + let res = Bridge::validate_transaction_out_msg(maybe_ref.clone(), bridge_addr.clone()); + assert_eq!(res.unwrap(), any_cell.cell); +} + +#[test] +fn test_handle_packet_receive() { + let mut deps = mock_dependencies(); + let deps_mut = deps.as_mut(); + let storage = deps_mut.storage; + let api = deps_mut.api; + let querier = deps_mut.querier; + let env = mock_env(); + let current_timestamp = env.block.time.seconds() + DEFAULT_TIMEOUT; + let mut bridge_packet_data = BridgePacketData::default(); + bridge_packet_data.amount = Uint128::from(1000000000u128); + bridge_packet_data.src_denom = "orai".to_string(); + bridge_packet_data.dest_denom = "orai".to_string(); + bridge_packet_data.orai_address = "orai_address".to_string(); + let mapping = MappingMetadata { + asset_info: AssetInfo::NativeToken { + denom: "orai".to_string(), + }, + remote_decimals: 6, + asset_info_decimals: 6, + opcode: OPCODE_2, + crc_src: 3724195509, + }; + CONFIG + .save( + storage, + &Config { + validator_contract_addr: Addr::unchecked("validator"), + bridge_adapter: "bridge_adapter".to_string(), + relayer_fee_token: AssetInfo::NativeToken { + denom: "orai".to_string(), + }, + relayer_fee: Uint128::from(100000u128), + token_fee_receiver: Addr::unchecked("token_fee_receiver"), + relayer_fee_receiver: Addr::unchecked("relayer_fee_receiver"), + swap_router_contract: RouterController("router".to_string()), + }, + ) + .unwrap(); + TOKEN_FEE + .save( + storage, + "orai", + &Ratio { + nominator: 1, + denominator: 1000, + }, + ) + .unwrap(); + + // case 1: timeout + let res = Bridge::handle_packet_receive( + storage, + api, + &querier, + current_timestamp, + bridge_packet_data.clone(), + mapping.clone(), + ) + .unwrap(); + + assert_eq!(res.0.len(), 0); + assert_eq!(res.1[0].value, "timeout".to_string()); + + // case 2: happy case + bridge_packet_data.timeout_timestamp = current_timestamp; + Bridge::handle_packet_receive( + storage, + api, + &querier, + current_timestamp, + bridge_packet_data.clone(), + mapping, + ) + .unwrap(); + + let commitment = ACK_COMMITMENT.load(deps.as_ref().storage, 0).unwrap(); + assert_eq!(commitment, build_ack_commitment(0).unwrap().as_slice()); +} + #[test] fn test_read_transaction() { let MockApp { @@ -37,9 +155,32 @@ fn test_read_transaction() { .. } = new_mock_app(); - let tx_boc = HexBinary::from_hex("b5ee9c72010210010002a00003b5704f1a9d989d4054ca72c292ffdf220c45c592fba86b562654fc500b6efdcc0a1000014c775004781596aa8bae813b9e6a71ade2ba8a393b7b1fff5c20db8414268e761e80f445466000014c774a4ba016675543a00034671e79e80102030201e00405008272c22a17f9d66afb94f83e04c02edc5abb7f2a15486ef4beaa703990dbfadb3b4085457ef326f4ecbbe9d81236ead8479f8765194636e87e84ca27eff6a7ec1f1d02170447c90ec90dd418656798110e0f01b16801ed89e454ebd04155a7ef579cecc7ff77907f2288f16bb339766711298f1f775700013c6a766275015329cb0a4bff7c883117164beea1ad589953f1402dbbf7302850ec90dd400613faa00000298ee9a50184cceaa864c0060101df080118af35dc850000000000000000070155ffff801397b648216d9f2f44369a4d6a5d42c41146f4cbc66093a35ba780f4e6a405714e071afd498d00010a019fe000278d4ecc4ea02a653961497fef910622e2c97dd435ab132a7e2805b77ee6050b006b6841e5c7db57b8d076dfa4368dea08e132f2917969b5920fbd8229dc6560d7000014c7750047826675543a60090153801397b648216d9f2f44369a4d6a5d42c41146f4cbc66093a35ba780f4e6a405714e071afd498d0000100a04000c0b0c0d00126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478009e43758c3d090000000000000000008c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc986db784c36dbc000000000000200000000000390f7bed18b3fc226db7ad9ac1961b38a37b80e826f33ccabfa03d8405819e6ca41902b2c").unwrap(); + // update bridge adapter contract + app.execute( + owner.clone(), + cosmwasm_std::CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { + contract_addr: bridge_addr.to_string(), + msg: to_binary(&tonbridge_bridge::msg::ExecuteMsg::UpdateConfig { + validator_contract_addr: None, + bridge_adapter: Some( + "EQDZfQX89gMo3HAiW1tSK9visb2gouUvDCt6PODo3qkXKeox".to_string(), + ), + relayer_fee_token: None, + token_fee_receiver: None, + relayer_fee_receiver: None, + relayer_fee: None, + swap_router_contract: None, + token_fee: None, + }) + .unwrap(), + funds: vec![], + }), + ) + .unwrap(); - let tx_proof = HexBinary::from_hex("b5ee9c7201020e010002cd00094603b4107e11da299213c78b889ec423fe1c7de98b508a4fdd113c6990b307235d80001d01241011ef55aafffffffd0203040502a09bc7a987000000008401014ceab100000000020000000000000000000000006675543a000014c775004780000014c7750047831736f9b3000446e401361bf701361bd7c400000007000000000000002e06072848010169df3a129570f135f49a71d8b483fa4c1c482f3f66ed85120a88d1b12fa9d16500012848010140bc4dd799a511514e2389685f05400ff4552fd0742fa5bcffc54f5628ba2728001c23894a33f6fde40b062e4f9ca75cdd7575e0d2ad61010f65e76ead272c60375bbdf85721963d37da28343e390d3d0fcc100f52754cdd13a8e4b655b0b6d5953c09f2f928d8ce4008090a0098000014c774e1c30401361bf750761c553cb279919d5c01370e223caddc8aed39f253c97e2067fab0d970edb84dfe70fb36e9e9e40e03596ed1ede5e16b95c4ab61817ceac5e86ca43fd7b4480098000014c774f10543014ceab0eadce00e65f2d6771561346ad31884c67e036c6aa63d14ba694d6affdf684810f750e961923988298da0b1dbe93abce9756cc3ef6b72dfd97531c7ce6199cabb28480101a7f5bf430102522e84d0b8b108a45efc71925ce0c6c591ae5ac50e7ead9baa15000828480101db58517b1e79f67b35742f301a407f7edf1b95fae995d949f62cbeb17f10e0e60009210799c79e7a0b22a5a0009e353b313a80a994e58525ffbe44188b8b25f750d6ac4ca9f8a016ddfb98142671e79e504f1a9d989d4054ca72c292ffdf220c45c592fba86b562654fc500b6efdcc0a1a000000a63ba8023c099c79e7a00c0d28480101f12edcfd2cb61fdee42d31cd884d21f92891e1ef9072f3ba4dded90ea5a09f380006008272c22a17f9d66afb94f83e04c02edc5abb7f2a15486ef4beaa703990dbfadb3b4085457ef326f4ecbbe9d81236ead8479f8765194636e87e84ca27eff6a7ec1f1d").unwrap(); + let tx_boc = HexBinary::from_hex("b5ee9c720102140100036f0003b57d97d05fcf60328dc70225b5b522bdbe2b1bda0a2e52f0c2b7a3ce0e8dea9172900001513ba0d5d85fc0a773e4754705ea84026db44fbeaaca1e2baf0dd5dedfe39db629d4958734300001513ba0d5d8166828e58000546a6690680102030201e00405008272dcb41d4bf971f06092f6eecdbf898756d673d77735a4e84ae7d12925d9b7c0baf82aaff12d7b49ab7b07f3867ae213de3521afd2baa482b651cc317c41c049b2021504091cee16fc18681bfa11121301b16801ed89e454ebd04155a7ef579cecc7ff77907f2288f16bb339766711298f1f775700365f417f3d80ca371c0896d6d48af6f8ac6f6828b94bc30ade8f383a37aa45ca51cee16fc006175b3800002a27741abb08cd051cb0c0060201dd090a0118af35dc850000000000000000070261ffff800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034abd1a94a20002fbc2afd93dc58010e080043800536affe20d6af471ee32332b9ebfa93e271bd0d924f1e3bc5f0dce4860a07c5100101200b0101200c00c94801b2fa0bf9ec0651b8e044b6b6a457b7c5637b4145ca5e1856f479c1d1bd522e53000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a11cdc586800608235a00002a27741abb0ccd051cb06a993b6d800000000000000040019fe006cbe82fe7b01946e38112dada915edf158ded05172978615bd1e70746f548b94b003cb93f68a17ddadc9e1033a70011995f47a9ca4b7f154529567ed2a85365ea3600001513ba0d5d8766828e58600d01bd4b9c032d000000000000000017de157ec9ee2c00800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034b000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a000017a35294400200e0400100f101100126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478009e4530ac3d09000000000000000000c200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98b33304c4952cc000000000004000000000004d7b30c9a07a17122303e6553a62e1d950e281e9004aa667753aa60229513e7b041d0516c").unwrap(); + + let tx_proof = HexBinary::from_hex("b5ee9c720102120100032c00094603a3cb391146df5705d742be39ac98c524e855432deb2ee6b1f82fe6f799fcef2d002001241011ef55aafffffffd0203040502a09bc7a9870000000084010151a1d4000000000200000000c00000000000000066828e5800001513ba0d5d8000001513ba0d5d88837ff5db0004556a013aba55013ab763c40000000800000000000001ee060728480101f3dc120bc609815b58aef43dbe778b2f3093f4b0dad0696d679493c152257f370001284801014dae1684965192171ff59dac6926af8723434cb423fe202778ac41f464950d3d001f23894a33f6fd5480343a842774e357cd56897609f3c5c8588c8b3c283f5b18285bb8807be560d6d6de6dc2167c056be8dbe0b6730d78710e2f5a23925744e5d42bb06f83f2d94008090a009800001513b9eed904013aba55b50e708866382195a989f99d8da25bc54877f2aec6daaaf75f7cf3e9972a145ae5440af7377ce89a83f2d8358dbed3178fcb2a0ade86abcb6bcbf90182b59535009800001513b9fe1b410151a1d3212289c6d0fb1539609cdd394bc23531bcba82f753e5a9dbd9a6cee888c4030d886e635a5ed24d2f249a2d9d0b6dda7ebf13d4d3fa6793d8d8fd99d79506b47a284801015b7e35806d90337174c4de9d01f675ea8383c808cb3d9c6a0e6fbba5085b7616001d28480101010eda12c8af2956eb710ab448f85c98be497f5fc519d2ad69e7601fb34566e4001c2109a00ba6e01a0b220b6d005d3700d00c0d23a3bf72fa0bf9ec0651b8e044b6b6a457b7c5637b4145ca5e1856f479c1d1bd522e5272b46f05d97d05fcf60328dc70225b5b522bdbe2b1bda0a2e52f0c2b7a3ce0e8dea917299e80000a89dd06aec0e568de100e0f10284801011d043a2d0fe2149600d727199be18bdaccd18347e5ed02fd482949d5c3f22432001b284801010f817018a69a0ce9a6de89a7faad4f9af47269dc9bad30be7d99560538bf20280008210964d4cd20d011008272ab568aad25dc4660ab5eb95d50378160501acbb2cbfa2ca5ff51e04f32008c8cf82aaff12d7b49ab7b07f3867ae213de3521afd2baa482b651cc317c41c049b228480101ab3fae260d0c12845931495c10fadc8b0335224544311638d48dd60d3d6eefa50007").unwrap(); let opcode = HexBinary::from_hex("0000000000000000000000000000000000000000000000000000000000000002") @@ -52,13 +193,14 @@ fn test_read_transaction() { msg: to_binary(&tonbridge_bridge::msg::ExecuteMsg::UpdateMappingPair( UpdatePairMsg { local_channel_id: "channel-0".to_string(), - denom: "EQCcvbJBC2z5eiG00mtS6hYgijemXjMEnRrdPAenNSAringl".to_string(), + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), local_asset_info: AssetInfo::Token { contract_addr: Addr::unchecked(cw20_addr.clone()), }, remote_decimals: 6, local_asset_info_decimals: 6, opcode, + crc_src: 3724195509, }, )) .unwrap(), @@ -85,7 +227,7 @@ fn test_read_transaction() { // shard block with block hash let block_hash = - HexBinary::from_hex("b4107e11da299213c78b889ec423fe1c7de98b508a4fdd113c6990b307235d80") + HexBinary::from_hex("a3cb391146df5705d742be39ac98c524e855432deb2ee6b1f82fe6f799fcef2d") .unwrap(); // set verified for simplicity @@ -132,12 +274,12 @@ fn test_read_transaction() { res, ChannelResponse { balances: vec![Amount::Native(coin( - 1000000000000000, - "EQCcvbJBC2z5eiG00mtS6hYgijemXjMEnRrdPAenNSAringl" + 1000000000000, + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB" ))], total_sent: vec![Amount::Native(coin( - 1000000000000000, - "EQCcvbJBC2z5eiG00mtS6hYgijemXjMEnRrdPAenNSAringl" + 1000000000000, + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB" ))], } ); @@ -146,6 +288,7 @@ fn test_read_transaction() { #[test] fn test_bridge_native_to_ton() { let mut deps = mock_dependencies(); + let denom = "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB"; instantiate( deps.as_mut(), mock_env(), @@ -172,31 +315,14 @@ fn test_bridge_native_to_ton() { ExecuteMsg::BridgeToTon(BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "EQAcXN7ZRk927VwlwN66AHubcd-6X3VhiESEWsE2k63AICIN".to_string(), - crc_src: 82134516, + denom: denom.to_string(), timeout: None, }), ) .unwrap_err(); assert_eq!(err.to_string(), "No funds sent"); - // case 2: failed, invalid timeout - let err = execute( - deps.as_mut(), - mock_env(), - mock_info("sender", &vec![coin(10000, "orai")]), - ExecuteMsg::BridgeToTon(BridgeToTonMsg { - local_channel_id: "channel-0".to_string(), - to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "EQAcXN7ZRk927VwlwN66AHubcd-6X3VhiESEWsE2k63AICIN".to_string(), - crc_src: 82134516, - timeout: Some(100), - }), - ) - .unwrap_err(); - assert_eq!(err.to_string(), ContractError::Expired {}.to_string()); - - // case 3: failed, not mapping pair + // case 2: failed, not mapping pair execute( deps.as_mut(), mock_env(), @@ -204,8 +330,7 @@ fn test_bridge_native_to_ton() { ExecuteMsg::BridgeToTon(BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "EQAcXN7ZRk927VwlwN66AHubcd-6X3VhiESEWsE2k63AICIN".to_string(), - crc_src: 82134516, + denom: denom.to_string(), timeout: None, }), ) @@ -220,13 +345,14 @@ fn test_bridge_native_to_ton() { mock_info("owner", &vec![]), ExecuteMsg::UpdateMappingPair(UpdatePairMsg { local_channel_id: "channel-0".to_string(), - denom: "orai_ton".to_string(), + denom: denom.to_string(), local_asset_info: AssetInfo::NativeToken { denom: "orai".to_string(), }, remote_decimals: 6, local_asset_info_decimals: 6, opcode, + crc_src: 3724195509, }), ) .unwrap(); @@ -239,8 +365,7 @@ fn test_bridge_native_to_ton() { ExecuteMsg::BridgeToTon(BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: denom.to_string(), timeout: None, }), ) @@ -255,8 +380,7 @@ fn test_bridge_native_to_ton() { ExecuteMsg::BridgeToTon(BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: denom.to_string(), timeout: None, }), ) @@ -267,7 +391,7 @@ fn test_bridge_native_to_ton() { increase_channel_balance( deps.as_mut().storage, "channel-0", - "orai_ton", + denom, Uint128::from(1000000000u128), ) .unwrap(); @@ -278,8 +402,7 @@ fn test_bridge_native_to_ton() { ExecuteMsg::BridgeToTon(BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: denom.to_string(), timeout: None, }), ) @@ -293,9 +416,9 @@ fn test_bridge_native_to_ton() { "dest_receiver", "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT" ), - ("dest_denom", "orai_ton"), + ("dest_denom", denom), ("local_amount", "10000"), - ("crc_src", "82134516"), + ("crc_src", &3724195509u32.to_string()), ("relayer_fee", "0"), ("token_fee", "0"), ( @@ -343,13 +466,14 @@ fn test_bridge_cw20_to_ton() { mock_info("owner", &vec![]), ExecuteMsg::UpdateMappingPair(UpdatePairMsg { local_channel_id: "channel-0".to_string(), - denom: "orai_ton".to_string(), + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), local_asset_info: AssetInfo::Token { contract_addr: Addr::unchecked("usdt"), }, remote_decimals: 6, local_asset_info_decimals: 6, opcode, + crc_src: 3724195509, }), ) .unwrap(); @@ -357,7 +481,7 @@ fn test_bridge_cw20_to_ton() { increase_channel_balance( deps.as_mut().storage, "channel-0", - "orai_ton", + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB", Uint128::from(1000000000u128), ) .unwrap(); @@ -372,8 +496,7 @@ fn test_bridge_cw20_to_ton() { msg: to_binary(&BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), timeout: None, }) .unwrap(), @@ -389,9 +512,12 @@ fn test_bridge_cw20_to_ton() { "dest_receiver", "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT" ), - attr("dest_denom", "orai_ton"), + attr( + "dest_denom", + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB" + ), attr("local_amount", "10000"), - attr("crc_src", "82134516"), + attr("crc_src", &3724195509u32.to_string()), attr("relayer_fee", "0"), attr("token_fee", "0"), attr( @@ -409,47 +535,6 @@ fn test_bridge_cw20_to_ton() { ); } -#[test] -fn test_submit_bridge_to_ton_info() { - let mut deps = mock_dependencies(); - SEND_PACKET - .save( - deps.as_mut().storage, - 1, - &SendPacket { - sequence: 1, - to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "EQAcXN7ZRk927VwlwN66AHubcd-6X3VhiESEWsE2k63AICIN".to_string(), - amount: Uint128::from(10000000000u128), - crc_src: 82134516, - }, - ) - .unwrap(); - - // seq = 2 - let data_err = "000000000000000280002255D73E3A5C1A9589F0AECE31E97B54B261AC3D7D16D4F1068FDF9D4B4E18300071737B65193DDBB57097037AE801EE6DC77EE97DD5862112116B04DA4EB70080000000000000000000000009502F9000139517D2"; - let res = - execute_submit_bridge_to_ton_info(deps.as_mut(), HexBinary::from_hex(data_err).unwrap()) - .unwrap_err(); - assert!(res.to_string().contains("SendPacket not found")); - - // verify success - let data = "000000000000000180002255D73E3A5C1A9589F0AECE31E97B54B261AC3D7D16D4F1068FDF9D4B4E18300071737B65193DDBB57097037AE801EE6DC77EE97DD5862112116B04DA4EB70080000000000000000000000009502F9000139517D2"; - let res = execute_submit_bridge_to_ton_info(deps.as_mut(), HexBinary::from_hex(data).unwrap()) - .unwrap(); - assert_eq!( - res.attributes, - vec![ - ("action", "submit_bridge_to_ton_info"), - ("data", &data.to_lowercase()) - ] - ); - - // after submit, not found send_packet - let packet = SEND_PACKET.may_load(deps.as_ref().storage, 1).unwrap(); - assert_eq!(packet, None); -} - #[test] fn test_bridge_to_ton_with_fee() { let mut deps = mock_dependencies(); @@ -480,13 +565,14 @@ fn test_bridge_to_ton_with_fee() { mock_info("owner", &vec![]), ExecuteMsg::UpdateMappingPair(UpdatePairMsg { local_channel_id: "channel-0".to_string(), - denom: "orai_ton".to_string(), + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), local_asset_info: AssetInfo::Token { contract_addr: Addr::unchecked("orai"), }, remote_decimals: 6, local_asset_info_decimals: 6, opcode, + crc_src: 3724195509, }), ) .unwrap(); @@ -505,7 +591,7 @@ fn test_bridge_to_ton_with_fee() { relayer_fee: None, swap_router_contract: None, token_fee: Some(vec![TokenFee { - token_denom: "orai_ton".to_string(), + token_denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), ratio: Ratio { nominator: 1, denominator: 1000, @@ -518,7 +604,7 @@ fn test_bridge_to_ton_with_fee() { increase_channel_balance( deps.as_mut().storage, "channel-0", - "orai_ton", + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB", Uint128::from(1000000000u128), ) .unwrap(); @@ -533,8 +619,7 @@ fn test_bridge_to_ton_with_fee() { msg: to_binary(&BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), timeout: None, }) .unwrap(), @@ -572,9 +657,12 @@ fn test_bridge_to_ton_with_fee() { "dest_receiver", "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT" ), - attr("dest_denom", "orai_ton"), + attr( + "dest_denom", + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB" + ), attr("local_amount", "8990"), - attr("crc_src", "82134516"), + attr("crc_src", &3724195509u32.to_string()), attr("relayer_fee", "1000"), attr("token_fee", "10"), attr( @@ -622,8 +710,7 @@ fn test_bridge_to_ton_with_fee() { msg: to_binary(&BridgeToTonMsg { local_channel_id: "channel-0".to_string(), to: "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(), - denom: "orai_ton".to_string(), - crc_src: 82134516, + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), timeout: None, }) .unwrap(), @@ -650,9 +737,12 @@ fn test_bridge_to_ton_with_fee() { "dest_receiver", "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT" ), - attr("dest_denom", "orai_ton"), + attr( + "dest_denom", + "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB" + ), attr("local_amount", "9990"), - attr("crc_src", "82134516"), + attr("crc_src", &3724195509u32.to_string()), attr("relayer_fee", "0"), attr("token_fee", "10"), attr( @@ -680,10 +770,32 @@ fn test_bridge_ton_to_orai_with_fee() { validator_addr, .. } = new_mock_app(); + // update bridge adapter contract + app.execute( + owner.clone(), + cosmwasm_std::CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { + contract_addr: bridge_addr.to_string(), + msg: to_binary(&tonbridge_bridge::msg::ExecuteMsg::UpdateConfig { + validator_contract_addr: None, + bridge_adapter: Some( + "EQDZfQX89gMo3HAiW1tSK9visb2gouUvDCt6PODo3qkXKeox".to_string(), + ), + relayer_fee_token: None, + token_fee_receiver: None, + relayer_fee_receiver: None, + relayer_fee: None, + swap_router_contract: None, + token_fee: None, + }) + .unwrap(), + funds: vec![], + }), + ) + .unwrap(); - let tx_boc = HexBinary::from_hex("b5ee9c72010210010002a00003b5704f1a9d989d4054ca72c292ffdf220c45c592fba86b562654fc500b6efdcc0a1000014c775004781596aa8bae813b9e6a71ade2ba8a393b7b1fff5c20db8414268e761e80f445466000014c774a4ba016675543a00034671e79e80102030201e00405008272c22a17f9d66afb94f83e04c02edc5abb7f2a15486ef4beaa703990dbfadb3b4085457ef326f4ecbbe9d81236ead8479f8765194636e87e84ca27eff6a7ec1f1d02170447c90ec90dd418656798110e0f01b16801ed89e454ebd04155a7ef579cecc7ff77907f2288f16bb339766711298f1f775700013c6a766275015329cb0a4bff7c883117164beea1ad589953f1402dbbf7302850ec90dd400613faa00000298ee9a50184cceaa864c0060101df080118af35dc850000000000000000070155ffff801397b648216d9f2f44369a4d6a5d42c41146f4cbc66093a35ba780f4e6a405714e071afd498d00010a019fe000278d4ecc4ea02a653961497fef910622e2c97dd435ab132a7e2805b77ee6050b006b6841e5c7db57b8d076dfa4368dea08e132f2917969b5920fbd8229dc6560d7000014c7750047826675543a60090153801397b648216d9f2f44369a4d6a5d42c41146f4cbc66093a35ba780f4e6a405714e071afd498d0000100a04000c0b0c0d00126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478009e43758c3d090000000000000000008c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc986db784c36dbc000000000000200000000000390f7bed18b3fc226db7ad9ac1961b38a37b80e826f33ccabfa03d8405819e6ca41902b2c").unwrap(); + let tx_boc = HexBinary::from_hex("b5ee9c720102140100036f0003b57d97d05fcf60328dc70225b5b522bdbe2b1bda0a2e52f0c2b7a3ce0e8dea9172900001513ba0d5d85fc0a773e4754705ea84026db44fbeaaca1e2baf0dd5dedfe39db629d4958734300001513ba0d5d8166828e58000546a6690680102030201e00405008272dcb41d4bf971f06092f6eecdbf898756d673d77735a4e84ae7d12925d9b7c0baf82aaff12d7b49ab7b07f3867ae213de3521afd2baa482b651cc317c41c049b2021504091cee16fc18681bfa11121301b16801ed89e454ebd04155a7ef579cecc7ff77907f2288f16bb339766711298f1f775700365f417f3d80ca371c0896d6d48af6f8ac6f6828b94bc30ade8f383a37aa45ca51cee16fc006175b3800002a27741abb08cd051cb0c0060201dd090a0118af35dc850000000000000000070261ffff800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034abd1a94a20002fbc2afd93dc58010e080043800536affe20d6af471ee32332b9ebfa93e271bd0d924f1e3bc5f0dce4860a07c5100101200b0101200c00c94801b2fa0bf9ec0651b8e044b6b6a457b7c5637b4145ca5e1856f479c1d1bd522e53000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a11cdc586800608235a00002a27741abb0ccd051cb06a993b6d800000000000000040019fe006cbe82fe7b01946e38112dada915edf158ded05172978615bd1e70746f548b94b003cb93f68a17ddadc9e1033a70011995f47a9ca4b7f154529567ed2a85365ea3600001513ba0d5d8766828e58600d01bd4b9c032d000000000000000017de157ec9ee2c00800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034b000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a000017a35294400200e0400100f101100126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478009e4530ac3d09000000000000000000c200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98b33304c4952cc000000000004000000000004d7b30c9a07a17122303e6553a62e1d950e281e9004aa667753aa60229513e7b041d0516c").unwrap(); - let tx_proof = HexBinary::from_hex("b5ee9c7201020e010002cd00094603b4107e11da299213c78b889ec423fe1c7de98b508a4fdd113c6990b307235d80001d01241011ef55aafffffffd0203040502a09bc7a987000000008401014ceab100000000020000000000000000000000006675543a000014c775004780000014c7750047831736f9b3000446e401361bf701361bd7c400000007000000000000002e06072848010169df3a129570f135f49a71d8b483fa4c1c482f3f66ed85120a88d1b12fa9d16500012848010140bc4dd799a511514e2389685f05400ff4552fd0742fa5bcffc54f5628ba2728001c23894a33f6fde40b062e4f9ca75cdd7575e0d2ad61010f65e76ead272c60375bbdf85721963d37da28343e390d3d0fcc100f52754cdd13a8e4b655b0b6d5953c09f2f928d8ce4008090a0098000014c774e1c30401361bf750761c553cb279919d5c01370e223caddc8aed39f253c97e2067fab0d970edb84dfe70fb36e9e9e40e03596ed1ede5e16b95c4ab61817ceac5e86ca43fd7b4480098000014c774f10543014ceab0eadce00e65f2d6771561346ad31884c67e036c6aa63d14ba694d6affdf684810f750e961923988298da0b1dbe93abce9756cc3ef6b72dfd97531c7ce6199cabb28480101a7f5bf430102522e84d0b8b108a45efc71925ce0c6c591ae5ac50e7ead9baa15000828480101db58517b1e79f67b35742f301a407f7edf1b95fae995d949f62cbeb17f10e0e60009210799c79e7a0b22a5a0009e353b313a80a994e58525ffbe44188b8b25f750d6ac4ca9f8a016ddfb98142671e79e504f1a9d989d4054ca72c292ffdf220c45c592fba86b562654fc500b6efdcc0a1a000000a63ba8023c099c79e7a00c0d28480101f12edcfd2cb61fdee42d31cd884d21f92891e1ef9072f3ba4dded90ea5a09f380006008272c22a17f9d66afb94f83e04c02edc5abb7f2a15486ef4beaa703990dbfadb3b4085457ef326f4ecbbe9d81236ead8479f8765194636e87e84ca27eff6a7ec1f1d").unwrap(); + let tx_proof = HexBinary::from_hex("b5ee9c720102120100032c00094603a3cb391146df5705d742be39ac98c524e855432deb2ee6b1f82fe6f799fcef2d002001241011ef55aafffffffd0203040502a09bc7a9870000000084010151a1d4000000000200000000c00000000000000066828e5800001513ba0d5d8000001513ba0d5d88837ff5db0004556a013aba55013ab763c40000000800000000000001ee060728480101f3dc120bc609815b58aef43dbe778b2f3093f4b0dad0696d679493c152257f370001284801014dae1684965192171ff59dac6926af8723434cb423fe202778ac41f464950d3d001f23894a33f6fd5480343a842774e357cd56897609f3c5c8588c8b3c283f5b18285bb8807be560d6d6de6dc2167c056be8dbe0b6730d78710e2f5a23925744e5d42bb06f83f2d94008090a009800001513b9eed904013aba55b50e708866382195a989f99d8da25bc54877f2aec6daaaf75f7cf3e9972a145ae5440af7377ce89a83f2d8358dbed3178fcb2a0ade86abcb6bcbf90182b59535009800001513b9fe1b410151a1d3212289c6d0fb1539609cdd394bc23531bcba82f753e5a9dbd9a6cee888c4030d886e635a5ed24d2f249a2d9d0b6dda7ebf13d4d3fa6793d8d8fd99d79506b47a284801015b7e35806d90337174c4de9d01f675ea8383c808cb3d9c6a0e6fbba5085b7616001d28480101010eda12c8af2956eb710ab448f85c98be497f5fc519d2ad69e7601fb34566e4001c2109a00ba6e01a0b220b6d005d3700d00c0d23a3bf72fa0bf9ec0651b8e044b6b6a457b7c5637b4145ca5e1856f479c1d1bd522e5272b46f05d97d05fcf60328dc70225b5b522bdbe2b1bda0a2e52f0c2b7a3ce0e8dea917299e80000a89dd06aec0e568de100e0f10284801011d043a2d0fe2149600d727199be18bdaccd18347e5ed02fd482949d5c3f22432001b284801010f817018a69a0ce9a6de89a7faad4f9af47269dc9bad30be7d99560538bf20280008210964d4cd20d011008272ab568aad25dc4660ab5eb95d50378160501acbb2cbfa2ca5ff51e04f32008c8cf82aaff12d7b49ab7b07f3867ae213de3521afd2baa482b651cc317c41c049b228480101ab3fae260d0c12845931495c10fadc8b0335224544311638d48dd60d3d6eefa50007").unwrap(); let opcode = HexBinary::from_hex("0000000000000000000000000000000000000000000000000000000000000002") @@ -705,7 +817,7 @@ fn test_bridge_ton_to_orai_with_fee() { relayer_fee: Some(Uint128::from(1000u128)), swap_router_contract: None, token_fee: Some(vec![TokenFee { - token_denom: "EQCcvbJBC2z5eiG00mtS6hYgijemXjMEnRrdPAenNSAringl".to_string(), + token_denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), ratio: Ratio { nominator: 1, denominator: 1000, @@ -725,13 +837,14 @@ fn test_bridge_ton_to_orai_with_fee() { msg: to_binary(&tonbridge_bridge::msg::ExecuteMsg::UpdateMappingPair( UpdatePairMsg { local_channel_id: "channel-0".to_string(), - denom: "EQCcvbJBC2z5eiG00mtS6hYgijemXjMEnRrdPAenNSAringl".to_string(), + denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), local_asset_info: AssetInfo::Token { contract_addr: Addr::unchecked(cw20_addr.clone()), }, remote_decimals: 6, local_asset_info_decimals: 6, opcode, + crc_src: 3724195509, }, )) .unwrap(), @@ -742,7 +855,7 @@ fn test_bridge_ton_to_orai_with_fee() { // shard block with block hash let block_hash = - HexBinary::from_hex("b4107e11da299213c78b889ec423fe1c7de98b508a4fdd113c6990b307235d80") + HexBinary::from_hex("a3cb391146df5705d742be39ac98c524e855432deb2ee6b1f82fe6f799fcef2d") .unwrap(); // set verified for simplicity @@ -794,5 +907,5 @@ fn test_bridge_ton_to_orai_with_fee() { }, ) .unwrap(); - assert_eq!(token_fee_balance.balance, Uint128::from(1000000000000u128)); + assert_eq!(token_fee_balance.balance, Uint128::from(1000000000u128)); } diff --git a/contracts/tonbridge_bridge/src/testing/contract.rs b/contracts/tonbridge_bridge/src/testing/contract.rs index 7bd31d6..48fe769 100644 --- a/contracts/tonbridge_bridge/src/testing/contract.rs +++ b/contracts/tonbridge_bridge/src/testing/contract.rs @@ -5,17 +5,33 @@ use cosmwasm_std::{ }; use cw20_ics20_msg::amount::Amount; use cw_multi_test::Executor; -use oraiswap::{asset::AssetInfo, router::RouterController}; +use oraiswap::{ + asset::{Asset, AssetInfo}, + router::RouterController, +}; use tonbridge_bridge::{ msg::{ChannelResponse, DeletePairMsg, PairQuery, QueryMsg as BridgeQueryMsg, UpdatePairMsg}, parser::{get_key_ics20_ibc_denom, parse_ibc_wasm_port_id}, - state::{Config, MappingMetadata, Ratio, TokenFee}, + state::{Config, MappingMetadata, Ratio, ReceivePacket, TimeoutSendPacket, TokenFee}, +}; +use tonbridge_parser::{ + to_bytes32, transaction_parser::SEND_PACKET_TIMEOUT_MAGIC_NUMBER, EMPTY_HASH, +}; +use tonlib::{ + address::TonAddress, + cell::{CellBuilder, TonCellError}, + responses::{AnyCell, MaybeRefData, MessageType, TransactionMessage}, }; -use tonbridge_parser::to_bytes32; use crate::{ + bridge::{RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER, SEND_TO_TON_MAGIC_NUMBER}, channel::{decrease_channel_balance, increase_channel_balance}, - contract::query, + contract::{ + build_timeout_send_packet_refund_msgs, is_tx_processed, process_timeout_receive_packet, + query, + }, + error::ContractError, + state::{PROCESSED_TXS, TIMEOUT_RECEIVE_PACKET, TIMEOUT_SEND_PACKET}, }; use super::mock::{new_mock_app, MockApp}; @@ -254,6 +270,7 @@ fn test_register_mapping_pair() { remote_decimals: 6, local_asset_info_decimals: 6, opcode: opcode.clone(), + crc_src: 3724195509, }, )) .unwrap(), @@ -277,6 +294,7 @@ fn test_register_mapping_pair() { remote_decimals: 6, local_asset_info_decimals: 6, opcode: opcode.clone(), + crc_src: 3724195509, }, )) .unwrap(), @@ -310,7 +328,8 @@ fn test_register_mapping_pair() { }, remote_decimals: 6, asset_info_decimals: 6, - opcode: to_bytes32(&opcode).unwrap() + opcode: to_bytes32(&opcode).unwrap(), + crc_src: 3724195509 } } ); @@ -407,3 +426,244 @@ fn test_update_channel_balance() { ) .unwrap_err(); } + +#[test] +fn test_build_timeout_send_packet_refund_msgs() { + let mut deps = mock_dependencies(); + let deps_mut = deps.as_mut(); + let mut out_msg: MaybeRefData = MaybeRefData::default(); + let env = mock_env(); + let latest_timestamp = env.block.time.seconds(); + let bridge_addr = "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(); + let sender = "orai1rchnkdpsxzhquu63y6r4j4t57pnc9w8ehdhedx"; + let seq = 1u64; + + // case 1: out msg is invalid -> empty res + let res = build_timeout_send_packet_refund_msgs( + deps_mut.storage, + deps_mut.api, + &deps_mut.querier, + out_msg.clone(), + bridge_addr.clone(), + latest_timestamp as u32, + ) + .unwrap(); + assert_eq!(res.len(), 0); + + let mut transaction_message = TransactionMessage::default(); + transaction_message.info.msg_type = MessageType::ExternalOut as u8; + transaction_message.info.src = TonAddress::from_base64_url(&bridge_addr.clone()).unwrap(); + let mut any_cell = AnyCell::default(); + let mut cell_builder = CellBuilder::new(); + cell_builder + .store_slice(&SEND_PACKET_TIMEOUT_MAGIC_NUMBER.to_be_bytes()) + .unwrap(); + // sequence + cell_builder.store_slice(&seq.to_be_bytes()).unwrap(); + let cell = cell_builder.build().unwrap(); + any_cell.cell = cell; + transaction_message.body.cell_ref = Some((Some(any_cell.clone()), None)); + out_msg.data = Some(transaction_message.clone()); + + // case 2: timeout packet not found -> no-op + let res = build_timeout_send_packet_refund_msgs( + deps_mut.storage, + deps_mut.api, + &deps_mut.querier, + out_msg.clone(), + bridge_addr.clone(), + latest_timestamp as u32, + ) + .unwrap(); + assert_eq!(res.len(), 0); + + // case 3: packet has not timed out yet + TIMEOUT_SEND_PACKET + .save( + deps_mut.storage, + seq, + &TimeoutSendPacket { + local_refund_asset: Asset { + info: AssetInfo::NativeToken { + denom: "orai".to_string(), + }, + amount: Uint128::zero(), + }, + sender: bridge_addr.clone(), + timeout_timestamp: latest_timestamp + 1, + }, + ) + .unwrap(); + + let err = build_timeout_send_packet_refund_msgs( + deps_mut.storage, + deps_mut.api, + &deps_mut.querier, + out_msg.clone(), + bridge_addr.clone(), + latest_timestamp as u32, + ) + .unwrap_err(); + assert_eq!(err.to_string(), ContractError::NotExpired {}.to_string()); + + // case 4: happy case + TIMEOUT_SEND_PACKET + .save( + deps_mut.storage, + seq, + &TimeoutSendPacket { + local_refund_asset: Asset { + info: AssetInfo::NativeToken { + denom: "orai".to_string(), + }, + amount: Uint128::zero(), + }, + sender: sender.to_string(), + timeout_timestamp: latest_timestamp - 1, + }, + ) + .unwrap(); + let res = build_timeout_send_packet_refund_msgs( + deps_mut.storage, + deps_mut.api, + &deps_mut.querier, + out_msg.clone(), + bridge_addr.clone(), + latest_timestamp as u32, + ) + .unwrap(); + assert_eq!(res.len(), 1); +} + +#[test] +fn test_process_timeout_receive_packet_not_a_receive_packet_timeout() { + let mut deps = mock_dependencies(); + let deps_mut = deps.as_mut(); + let mut cell_builder = CellBuilder::new(); + cell_builder + .store_slice(&SEND_TO_TON_MAGIC_NUMBER.to_be_bytes()) + .unwrap(); + let cell = cell_builder.build().unwrap(); + let res = process_timeout_receive_packet(deps_mut, HexBinary::from(cell.data)).unwrap_err(); + assert_eq!( + res.to_string(), + ContractError::TonCellError(TonCellError::cell_parser_error( + "Not a receive packet timeout" + )) + .to_string(), + ); +} + +#[test] +fn test_process_timeout_receive_packet_invalid_boc() { + let mut deps = mock_dependencies(); + let deps_mut = deps.as_mut(); + let src_sender = "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(); + let seq = 1; + let timeout_timestamp = 1; + + TIMEOUT_RECEIVE_PACKET + .save( + deps_mut.storage, + 1, + &ReceivePacket { + magic: RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER, + seq: seq.clone(), + timeout_timestamp, + src_sender: src_sender.clone(), + src_denom: src_sender.clone(), + src_channel: "channel-0".to_string(), + amount: Uint128::one(), + }, + ) + .unwrap(); + + let mut cell_builder = CellBuilder::new(); + cell_builder + .store_slice(&RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER.to_be_bytes()) + .unwrap(); + // sequence + cell_builder.store_slice(&1u64.to_be_bytes()).unwrap(); + cell_builder + .store_address(&TonAddress::from_base64_url(&src_sender).unwrap()) + .unwrap(); + cell_builder + .store_address(&TonAddress::from_base64_url(&src_sender).unwrap()) + .unwrap(); + cell_builder.store_slice(&10u16.to_be_bytes()).unwrap(); + cell_builder.store_slice(&1u128.to_be_bytes()).unwrap(); + cell_builder + .store_slice(&timeout_timestamp.to_be_bytes()) + .unwrap(); + let cell = cell_builder.build().unwrap(); + + let res = process_timeout_receive_packet(deps_mut, HexBinary::from(cell.data)).unwrap_err(); + assert_eq!( + res.to_string(), + ContractError::InvalidSendPacketBoc {}.to_string(), + ); +} + +#[test] +fn test_process_timeout_receive_packet_happy_case() { + let mut deps = mock_dependencies(); + let src_sender = "EQABEq658dLg1KxPhXZxj0vapZMNYevotqeINH786lpwwSnT".to_string(); + let seq = 1; + let timeout_timestamp = 1; + + TIMEOUT_RECEIVE_PACKET + .save( + deps.as_mut().storage, + 1, + &ReceivePacket { + magic: RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER, + seq: seq.clone(), + timeout_timestamp, + src_sender: src_sender.clone(), + src_denom: src_sender.clone(), + src_channel: "channel-0".to_string(), + amount: Uint128::one(), + }, + ) + .unwrap(); + + let mut cell_builder = CellBuilder::new(); + cell_builder + .store_slice(&RECEIVE_PACKET_TIMEOUT_MAGIC_NUMBER.to_be_bytes()) + .unwrap(); + // sequence + cell_builder.store_slice(&1u64.to_be_bytes()).unwrap(); + cell_builder + .store_address(&TonAddress::from_base64_url(&src_sender).unwrap()) + .unwrap(); + cell_builder + .store_address(&TonAddress::from_base64_url(&src_sender).unwrap()) + .unwrap(); + cell_builder.store_slice(&0u16.to_be_bytes()).unwrap(); + cell_builder.store_slice(&1u128.to_be_bytes()).unwrap(); + cell_builder + .store_slice(&timeout_timestamp.to_be_bytes()) + .unwrap(); + let cell = cell_builder.build().unwrap(); + + process_timeout_receive_packet(deps.as_mut(), HexBinary::from(cell.data)).unwrap(); + + let timeout_packet = TIMEOUT_RECEIVE_PACKET + .may_load(deps.as_mut().storage, 1) + .unwrap(); + assert_eq!(timeout_packet.is_none(), true); +} + +#[test] +fn test_is_tx_processed() { + let deps = mock_dependencies(); + let result = is_tx_processed(deps.as_ref(), HexBinary::from(EMPTY_HASH)).unwrap(); + assert_eq!(result, false); + + let mut deps = mock_dependencies(); + PROCESSED_TXS + .save(deps.as_mut().storage, &EMPTY_HASH, &true) + .unwrap(); + let result = is_tx_processed(deps.as_ref(), HexBinary::from(EMPTY_HASH)).unwrap(); + assert_eq!(result, true); +} diff --git a/contracts/tonbridge_validator/src/signature_validator.rs b/contracts/tonbridge_validator/src/signature_validator.rs index 408551f..dbf44eb 100644 --- a/contracts/tonbridge_validator/src/signature_validator.rs +++ b/contracts/tonbridge_validator/src/signature_validator.rs @@ -245,7 +245,7 @@ impl ISignatureValidator for SignatureValidator { let candidates_for_validator_set = get_signature_candidate_validators(storage)?; // if current validator_set is empty, check caller // else check votes - if val_set.len() == 0 { + if val_set.is_empty() { return Err(StdError::generic_err("current validator_set is empty")); } if val_set[0].weight == 0 { @@ -326,18 +326,24 @@ impl ISignatureValidator for SignatureValidator { } let block_extra = block_extra.unwrap(); - let mut key_block_vals = KeyBlockValidators::default(); - - key_block_vals.current = - SignatureValidator::load_validator_from_config_param(&block_extra.custom.config, 34)?; - key_block_vals.previous = - SignatureValidator::load_validator_from_config_param(&block_extra.custom.config, 32) - .ok() - .unwrap_or_default(); - key_block_vals.next = - SignatureValidator::load_validator_from_config_param(&block_extra.custom.config, 36) - .ok() - .unwrap_or_default(); + let key_block_vals = KeyBlockValidators { + current: SignatureValidator::load_validator_from_config_param( + &block_extra.custom.config, + 34, + )?, + previous: SignatureValidator::load_validator_from_config_param( + &block_extra.custom.config, + 32, + ) + .ok() + .unwrap_or_default(), + next: SignatureValidator::load_validator_from_config_param( + &block_extra.custom.config, + 36, + ) + .ok() + .unwrap_or_default(), + }; Ok(key_block_vals) } diff --git a/contracts/tonbridge_validator/src/validator.rs b/contracts/tonbridge_validator/src/validator.rs index 1992570..af583c0 100644 --- a/contracts/tonbridge_validator/src/validator.rs +++ b/contracts/tonbridge_validator/src/validator.rs @@ -124,7 +124,7 @@ impl Validator { )?; if current_weight * 3 <= self.signature_validator.sum_largest_total_weights * 2 { - return Err(ContractError::Std(StdError::generic_err(&format!( + return Err(ContractError::Std(StdError::generic_err(format!( "not enough votes to verify block. Wanted {:?}; has {:?}", self.signature_validator.sum_largest_total_weights * 2, current_weight * 3, @@ -176,11 +176,14 @@ impl Validator { let extra = block.extra.unwrap().custom.shards; for shards in extra.values() { for shard in shards { - let mut verified_block = VerifiedBlockInfo::default(); - verified_block.verified = true; - verified_block.seq_no = shard.seqno; - verified_block.start_lt = shard.start_lt; - verified_block.end_lt = shard.end_lt; + let verified_block = VerifiedBlockInfo { + verified: true, + seq_no: shard.seqno, + start_lt: shard.start_lt, + end_lt: shard.end_lt, + ..Default::default() + }; + VERIFIED_BLOCKS.save( storage, shard.root_hash.as_slice().try_into()?, @@ -203,10 +206,13 @@ impl Validator { } let prev_ref = block.info.unwrap().prev_ref; if let Some(prev_blk) = prev_ref.first_prev { - let mut verified_block = VerifiedBlockInfo::default(); - verified_block.verified = true; - verified_block.seq_no = prev_blk.seqno; - verified_block.end_lt = prev_blk.end_lt; + let verified_block = VerifiedBlockInfo { + verified: true, + seq_no: prev_blk.seqno, + end_lt: prev_blk.end_lt, + ..Default::default() + }; + VERIFIED_BLOCKS.save( storage, prev_blk.root_hash.as_slice().try_into()?, @@ -214,10 +220,13 @@ impl Validator { )?; } if let Some(prev_blk) = prev_ref.second_prev { - let mut verified_block = VerifiedBlockInfo::default(); - verified_block.verified = true; - verified_block.seq_no = prev_blk.seqno; - verified_block.end_lt = prev_blk.end_lt; + let verified_block = VerifiedBlockInfo { + verified: true, + seq_no: prev_blk.seqno, + end_lt: prev_blk.end_lt, + ..Default::default() + }; + VERIFIED_BLOCKS.save( storage, prev_blk.root_hash.as_slice().try_into()?, diff --git a/packages/bridge/src/msg.rs b/packages/bridge/src/msg.rs index 7181cd6..4d60eff 100644 --- a/packages/bridge/src/msg.rs +++ b/packages/bridge/src/msg.rs @@ -4,7 +4,7 @@ use cw20::Cw20ReceiveMsg; use cw20_ics20_msg::amount::Amount; use oraiswap::asset::AssetInfo; -use crate::state::{MappingMetadata, TokenFee}; +use crate::state::{MappingMetadata, ReceivePacket, TokenFee}; #[cw_serde] pub struct InstantiateMsg { @@ -27,9 +27,6 @@ pub enum ExecuteMsg { DeleteMappingPair(DeletePairMsg), BridgeToTon(BridgeToTonMsg), Receive(Cw20ReceiveMsg), - SubmitBridgeToTonInfo { - data: HexBinary, - }, UpdateOwner { new_owner: Addr, }, @@ -43,6 +40,18 @@ pub enum ExecuteMsg { swap_router_contract: Option, token_fee: Option>, }, + ProcessTimeoutSendPacket { + masterchain_header_proof: HexBinary, + tx_proof_unreceived: HexBinary, + tx_boc: HexBinary, // in hex form + }, + ProcessTimeoutRecievePacket { + receive_packet: HexBinary, + }, + Acknowledgment { + tx_proof: HexBinary, + tx_boc: HexBinary, // in hex form + }, } #[cw_serde] @@ -55,6 +64,7 @@ pub struct UpdatePairMsg { pub remote_decimals: u8, pub local_asset_info_decimals: u8, pub opcode: HexBinary, + pub crc_src: u32, } #[cw_serde] @@ -69,7 +79,6 @@ pub struct BridgeToTonMsg { pub local_channel_id: String, // default channel-0 pub to: String, pub denom: String, - pub crc_src: u32, pub timeout: Option, } @@ -93,6 +102,8 @@ pub enum QueryMsg { TokenFee { remote_token_denom: String }, #[returns(PairQuery)] PairMapping { key: String }, + #[returns(Vec)] + QueryTimeoutReceivePackets {}, } #[cw_serde] diff --git a/packages/bridge/src/parser.rs b/packages/bridge/src/parser.rs index 34020a3..1d6e605 100644 --- a/packages/bridge/src/parser.rs +++ b/packages/bridge/src/parser.rs @@ -1,7 +1,3 @@ -use cosmwasm_std::StdResult; - -use crate::msg::Ics20Packet; - pub fn get_key_ics20_ibc_denom(port_id: &str, channel_id: &str, denom: &str) -> String { format!("{}/{}/{}", port_id, channel_id, denom) } @@ -9,8 +5,3 @@ pub fn get_key_ics20_ibc_denom(port_id: &str, channel_id: &str, denom: &str) -> pub fn parse_ibc_wasm_port_id(contract_addr: &str) -> String { format!("wasm.{}", contract_addr) } - -pub fn parse_packet_boc_to_ics_20(_packet_boc: &[u8]) -> StdResult { - // TODO: parse packet boc to ics20 packet - Ok(Ics20Packet::default()) -} diff --git a/packages/bridge/src/state.rs b/packages/bridge/src/state.rs index 5dbb5b7..820e7df 100644 --- a/packages/bridge/src/state.rs +++ b/packages/bridge/src/state.rs @@ -1,6 +1,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Uint128}; -use oraiswap::{asset::AssetInfo, router::RouterController}; +use oraiswap::{ + asset::{Asset, AssetInfo}, + router::RouterController, +}; use tonbridge_parser::types::Bytes32; #[cw_serde] @@ -10,6 +13,7 @@ pub struct MappingMetadata { pub remote_decimals: u8, pub asset_info_decimals: u8, pub opcode: Bytes32, + pub crc_src: u32, // to determine the source of token } #[cw_serde] @@ -49,30 +53,18 @@ pub struct Config { } #[cw_serde] -pub struct SendPacket { - pub sequence: u64, - pub to: String, - pub denom: String, - pub amount: Uint128, - pub crc_src: u32, -} - -#[cw_serde] -pub enum Status { - Success, - Error, - Timeout, +pub struct TimeoutSendPacket { + pub local_refund_asset: Asset, + pub sender: String, + pub timeout_timestamp: u64, } #[cw_serde] -pub struct PacketReceive { +pub struct ReceivePacket { + pub magic: u32, // crc32 pub seq: u64, - pub timeout: u64, + pub timeout_timestamp: u64, + pub src_sender: String, pub src_denom: String, pub src_channel: String, pub amount: Uint128, - pub dest_denom: String, - pub dest_channel: String, - pub dest_receiver: String, - pub orai_address: String, - pub status: Status, } diff --git a/packages/parser/src/transaction_parser.rs b/packages/parser/src/transaction_parser.rs index 64bb8cf..f8fe848 100644 --- a/packages/parser/src/transaction_parser.rs +++ b/packages/parser/src/transaction_parser.rs @@ -5,6 +5,16 @@ use crate::types::BridgePacketDataRaw; pub trait ITransactionParser { fn parse_packet_data(&self, cell: &Cell) -> Result; + fn parse_send_packet_timeout_data(&self, cell: &Cell) -> Result; + fn parse_ack_data(&self, cell: &Cell) -> Result; +} + +pub const SEND_PACKET_TIMEOUT_MAGIC_NUMBER: u32 = 0x540CE379; // crc32("src::timeout_send_packet") +pub const RECEIVE_PACKET_MAGIC_NUMBER: u32 = 0xa64c12a3; // crc32("op::send_to_cosmos") +pub const ACK_MAGIC_NUMBER: u32 = 0x3acb0e2; // crc32("ops::ack_success") + +pub fn get_channel_id(channel_num: u16) -> String { + format!("channel-{:?}", channel_num) } #[cw_serde] @@ -15,9 +25,18 @@ impl ITransactionParser for TransactionParser { fn parse_packet_data(&self, cell: &Cell) -> Result { let mut parser = cell.parser(); + let magic_number = parser.load_u32(32)?; + if magic_number != RECEIVE_PACKET_MAGIC_NUMBER { + return Err(TonCellError::cell_parser_error( + "Not a receive packet from TON to CW", + )); + } let packet_seq = parser.load_u64(64)?; - let timeout = parser.load_u64(64)?; + let timeout_timestamp = parser.load_u64(64)?; let source_denom = parser.load_address()?; + let src_sender = parser.load_address()?; + // assume that the largest channel id is 65536 = 2^16 + let src_channel_num = parser.load_u16(16)?; let amount = parser.load_coins()?; let mut des_denom: Vec = vec![]; @@ -37,9 +56,10 @@ impl ITransactionParser for TransactionParser { Ok(BridgePacketDataRaw { seq: packet_seq, - timeout: timeout, + timeout_timestamp, src_denom: source_denom, - src_channel: "channel-0".as_bytes().to_vec(), // FIXME: get src_channel from body data + src_sender, + src_channel: get_channel_id(src_channel_num).into_bytes(), // FIXME: get src_channel from body data amount: amount.to_str_radix(10), dest_denom: des_denom, dest_channel: des_channel, @@ -47,6 +67,26 @@ impl ITransactionParser for TransactionParser { orai_address, }) } + + fn parse_send_packet_timeout_data(&self, cell: &Cell) -> Result { + let mut parser = cell.parser(); + let magic_number = parser.load_u32(32)?; + if magic_number != SEND_PACKET_TIMEOUT_MAGIC_NUMBER { + return Err(TonCellError::cell_parser_error("Not a send packet timeout")); + } + let packet_seq = parser.load_u64(64)?; + Ok(packet_seq) + } + + fn parse_ack_data(&self, cell: &Cell) -> Result { + let mut parser = cell.parser(); + let magic_number = parser.load_u32(32)?; + if magic_number != ACK_MAGIC_NUMBER { + return Err(TonCellError::cell_parser_error("Not a ack")); + } + let packet_seq = parser.load_u64(64)?; + Ok(packet_seq) + } } #[cfg(test)] @@ -73,7 +113,7 @@ mod tests { #[test] fn test_load_packet_data_from_tx() { - let tx_boc = HexBinary::from_hex("b5ee9c7241020f010002980003b57c2f3e8d5279ced802ed49fa27fc78194d56eae6cdc21a22cf5cc39774b05f1ed0000000002625a005748504d3d21169b400bf4b45fafc84e0b89be22fc30b54016952e427a6124a600000000023493406673aca10003471c45708010b0c0201e0020401c968004c0b3c62139d1c03b60918dec4768befcc45df7da6a2de6d219a915d73bdf09d0030bcfa3549e73b600bb527e89ff1e065355bab9b3708688b3d730e5dd2c17c7b50e9cb612006319f3a0000000004a62f82cce75942579aee42800000000000000040030151ffff800f7d34e75873470fe40e79d23384bc61e73f2da1d5482d94b2542ea6986994012a9b10b18401070101df05019fe006179f46a93ce76c0176a4fd13fe3c0ca6ab757366e10d1167ae61cbba582f8f6b00e9d61545a0d3728a449b52e821594c643d7f41db8be13e70a4f303234894ce220000000002625a016673aca16006014f800f7d34e75873470fe40e79d23384bc61e73f2da1d5482d94b2542ea6986994012a9b10b18400100704000908090a00126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478008272e17b490aee33d9121cc94f41e2a7182fc52631695ec15d53e6dce10296efa2a828968d90e5706ae641bfc36b017fb5c6574ee8b0b1773d52f2df17946936b214021504090e9cb612186d82fc110d0e009e43758c3bd9f400000000000000008c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc991056c4c882b6000000000000200000000000399a721ca1ff45fa142362f0a8490913b28005e7c14605de003f0c5dfc915c4f841902aac8e9a424d").unwrap(); + let tx_boc = HexBinary::from_hex("b5ee9c720102140100036f0003b57d97d05fcf60328dc70225b5b522bdbe2b1bda0a2e52f0c2b7a3ce0e8dea9172900001513ba0d5d85fc0a773e4754705ea84026db44fbeaaca1e2baf0dd5dedfe39db629d4958734300001513ba0d5d8166828e58000546a6690680102030201e00405008272dcb41d4bf971f06092f6eecdbf898756d673d77735a4e84ae7d12925d9b7c0baf82aaff12d7b49ab7b07f3867ae213de3521afd2baa482b651cc317c41c049b2021504091cee16fc18681bfa11121301b16801ed89e454ebd04155a7ef579cecc7ff77907f2288f16bb339766711298f1f775700365f417f3d80ca371c0896d6d48af6f8ac6f6828b94bc30ade8f383a37aa45ca51cee16fc006175b3800002a27741abb08cd051cb0c0060201dd090a0118af35dc850000000000000000070261ffff800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034abd1a94a20002fbc2afd93dc58010e080043800536affe20d6af471ee32332b9ebfa93e271bd0d924f1e3bc5f0dce4860a07c5100101200b0101200c00c94801b2fa0bf9ec0651b8e044b6b6a457b7c5637b4145ca5e1856f479c1d1bd522e53000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a11cdc586800608235a00002a27741abb0ccd051cb06a993b6d800000000000000040019fe006cbe82fe7b01946e38112dada915edf158ded05172978615bd1e70746f548b94b003cb93f68a17ddadc9e1033a70011995f47a9ca4b7f154529567ed2a85365ea3600001513ba0d5d8766828e58600d01bd4b9c032d000000000000000017de157ec9ee2c00800722ce79faef732792855db51f4a0e589748492ca4cada73bb8a6ab5dd23d034b000a6d5ffc41ad5e8e3dc6466573d7f527c4e37a1b249e3c778be1b9c90c140f8a000017a35294400200e0400100f101100126368616e6e656c2d31000000566f726169317263686e6b647073787a687175753633793672346a34743537706e6339773865686468656478009e4530ac3d09000000000000000000c200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006fc98b33304c4952cc000000000004000000000004d7b30c9a07a17122303e6553a62e1d950e281e9004aa667753aa60229513e7b041d0516c").unwrap(); let tx_cells = BagOfCells::parse(&tx_boc).unwrap(); let tx_root = tx_cells.single_root().unwrap(); @@ -97,10 +137,11 @@ mod tests { packet_data.to_pretty().unwrap(), BridgePacketData { seq: 0, - timeout: 0, - src_denom: "EQB76ac6w5o4fyBzzpGcJeMPOfltDqpBbKWSoXU0w0ygCYVs".to_string(), + timeout_timestamp: 1719835742000000000, + src_sender: "EQAptX_xBrV6OPcZGZXPX9SfE43obJJ48d4vhuckMFA-KKbJ".to_string(), + src_denom: "EQA5FnPP13uZPJQq7aj6UHLEukJJZSZW053cU1Wu6R6BpYYB".to_string(), src_channel: "channel-0".to_string(), - amount: Uint128::from_str("333000000000").unwrap(), + amount: Uint128::from_str("1000000000000").unwrap(), dest_denom: "".to_string(), dest_channel: "channel-1".to_string(), dest_receiver: "".to_string(), diff --git a/packages/parser/src/types.rs b/packages/parser/src/types.rs index 5c8295f..cab57dd 100644 --- a/packages/parser/src/types.rs +++ b/packages/parser/src/types.rs @@ -158,7 +158,8 @@ pub struct KeyBlockValidators { #[derive(Default, Clone)] pub struct BridgePacketDataRaw { pub seq: u64, - pub timeout: u64, + pub timeout_timestamp: u64, + pub src_sender: TonlibTonAddress, pub src_denom: TonlibTonAddress, pub src_channel: Vec, pub amount: String, @@ -172,7 +173,8 @@ impl BridgePacketDataRaw { pub fn to_pretty(self) -> StdResult { Ok(BridgePacketData { seq: self.seq, - timeout: self.timeout, + timeout_timestamp: self.timeout_timestamp, + src_sender: self.src_sender.to_base64_url(), src_denom: self.src_denom.to_string(), src_channel: String::from_utf8(self.src_channel)?, amount: Uint128::from_str(&self.amount)?, @@ -188,7 +190,8 @@ impl BridgePacketDataRaw { #[derive(Default)] pub struct BridgePacketData { pub seq: u64, - pub timeout: u64, + pub timeout_timestamp: u64, + pub src_sender: String, pub src_denom: String, pub src_channel: String, pub amount: Uint128,