From 21807adc85c0e2e9fc33b74791d5afae32322191 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Mon, 13 Jan 2025 15:34:14 +0200 Subject: [PATCH 01/16] feat: initial stargate integration --- core | 1 + crates/gem_evm/src/jsonrpc.rs | 11 + crates/gem_evm/src/lib.rs | 1 + crates/gem_evm/src/stargate/contract.rs | 124 ++++++ crates/gem_evm/src/stargate/mod.rs | 1 + gemstone/src/swapper/mod.rs | 14 +- gemstone/src/swapper/models.rs | 3 + gemstone/src/swapper/stargate/mod.rs | 2 + gemstone/src/swapper/stargate/provider.rs | 448 ++++++++++++++++++++++ 9 files changed, 599 insertions(+), 6 deletions(-) create mode 120000 core create mode 100644 crates/gem_evm/src/stargate/contract.rs create mode 100644 crates/gem_evm/src/stargate/mod.rs create mode 100644 gemstone/src/swapper/stargate/mod.rs create mode 100644 gemstone/src/swapper/stargate/provider.rs diff --git a/core b/core new file mode 120000 index 00000000..5c1a68f3 --- /dev/null +++ b/core @@ -0,0 +1 @@ +../gem-ios/core \ No newline at end of file diff --git a/crates/gem_evm/src/jsonrpc.rs b/crates/gem_evm/src/jsonrpc.rs index b7172118..3e39995f 100644 --- a/crates/gem_evm/src/jsonrpc.rs +++ b/crates/gem_evm/src/jsonrpc.rs @@ -47,6 +47,17 @@ impl TransactionObject { data: format!("0x{}", hex::encode(data)), } } + + pub fn new_call_with_from_value(from: &str, to: &str, value: &str, data: Vec) -> Self { + Self { + from: Some(from.to_string()), + to: to.to_string(), + gas: None, + gas_price: None, + value: Some(value.to_string()), + data: format!("0x{}", hex::encode(data)), + } + } } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/crates/gem_evm/src/lib.rs b/crates/gem_evm/src/lib.rs index b07ce187..ff6aa37f 100644 --- a/crates/gem_evm/src/lib.rs +++ b/crates/gem_evm/src/lib.rs @@ -11,6 +11,7 @@ pub mod jsonrpc; pub mod lido; pub mod multicall3; pub mod permit2; +pub mod stargate; pub mod thorchain; pub mod uniswap; pub mod weth; diff --git a/crates/gem_evm/src/stargate/contract.rs b/crates/gem_evm/src/stargate/contract.rs new file mode 100644 index 00000000..89dc43d5 --- /dev/null +++ b/crates/gem_evm/src/stargate/contract.rs @@ -0,0 +1,124 @@ +use alloy_core::sol; +use serde::{Deserialize, Serialize}; + +sol! { + #[derive(Debug, PartialEq)] + struct LzTxObj { + uint256 dstGasForCall; + uint256 dstNativeAmount; + bytes dstNativeAddr; + } + + /** + * @dev Struct representing token parameters for the OFT send() operation. + */ + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct SendParam { + uint32 dstEid; // Destination endpoint ID. + bytes32 to; // Recipient address. + uint256 amountLD; // Amount to send in local decimals. + uint256 minAmountLD; // Minimum amount to send in local decimals. + bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. + bytes composeMsg; // The composed message for the send() operation. + bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. + } + + /** + * @dev Struct representing OFT limit information. + * @dev These amounts can change dynamically and are up the the specific oft implementation. + */ + #[derive(Debug, PartialEq)] + struct OFTLimit { + uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient. + uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient. + } + + /** + * @dev Struct representing OFT receipt information. + */ + #[derive(Debug, PartialEq)] + struct OFTReceipt { + uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals. + // @dev In non-default implementations, the amountReceivedLD COULD differ from this value. + uint256 amountReceivedLD; // Amount of tokens to be received on the remote side. + } + + #[derive(Debug, PartialEq)] + struct OFTFeeDetail { + int256 feeAmountLD; // Amount of the fee in local decimals. + string description; // Description of the fee. + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct MessagingFee { + uint256 nativeFee; + uint256 lzTokenFee; + } + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct MessagingReceipt { + bytes32 guid; + uint64 nonce; + MessagingFee fee; + } + + + #[derive(Debug, PartialEq, Serialize, Deserialize)] + struct Ticket { + uint56 ticketId; + bytes passenger; + } + + + #[derive(Debug, PartialEq)] + interface IRouter { + function quoteSend( + SendParam calldata _sendParam, + bool _payInLzToken + ) external view returns (MessagingFee memory fee); + + /// @notice Provides a quote for sending OFT to another chain. + /// @dev Implements the IOFT interface + /// @param _sendParam The parameters for the send operation + /// @return limit The information on OFT transfer limits + /// @return oftFeeDetails The details of OFT transaction cost or reward + /// @return receipt The OFT receipt information, indicating how many tokens would be sent and received + function quoteOFT( + SendParam calldata _sendParam + ) external view returns ( + OFTLimit memory limit, + OFTFeeDetail[] memory oftFeeDetails, + OFTReceipt memory receipt + ); + + /// which allows the caller to ride and drive the bus in the same transaction. + /** + * @notice Executes the send() operation. + * @param _sendParam The parameters for the send operation. + * @param _fee The fee information supplied by the caller. + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + * @param _refundAddress The address to receive any excess funds from fees etc. on the src. + * @return receipt The LayerZero messaging receipt from the send() operation. + * @return oftReceipt The OFT receipt information. + * + * @dev MessagingReceipt: LayerZero msg receipt + * - guid: The unique identifier for the sent message. + * - nonce: The nonce of the sent message. + * - fee: The LayerZero fee incurred for the message. + */ + function send( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable returns (MessagingReceipt memory, OFTReceipt memory); + + /// @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode, + /// which allows the caller to ride and drive the bus in the same transaction. + function sendToken( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt, Ticket memory ticket); + } +} diff --git a/crates/gem_evm/src/stargate/mod.rs b/crates/gem_evm/src/stargate/mod.rs new file mode 100644 index 00000000..2943dbb5 --- /dev/null +++ b/crates/gem_evm/src/stargate/mod.rs @@ -0,0 +1 @@ +pub mod contract; diff --git a/gemstone/src/swapper/mod.rs b/gemstone/src/swapper/mod.rs index daf55e16..68d80d2b 100644 --- a/gemstone/src/swapper/mod.rs +++ b/gemstone/src/swapper/mod.rs @@ -17,6 +17,7 @@ pub mod models; pub mod orca; pub mod pancakeswap_aptos; pub mod slippage; +pub mod stargate; pub mod thorchain; pub mod universal_router; @@ -80,12 +81,13 @@ impl GemSwapper { Self { rpc_provider, swappers: vec![ - Box::new(universal_router::UniswapV3::new_uniswap()), - Box::new(universal_router::UniswapV3::new_pancakeswap()), - Box::new(thorchain::ThorChain::default()), - Box::new(jupiter::Jupiter::default()), - Box::new(pancakeswap_aptos::PancakeSwapAptos::default()), - Box::new(across::Across::default()), + //Box::new(universal_router::UniswapV3::new_uniswap()), + //Box::new(universal_router::UniswapV3::new_pancakeswap()), + //Box::new(thorchain::ThorChain::default()), + //Box::new(jupiter::Jupiter::default()), + //Box::new(pancakeswap_aptos::PancakeSwapAptos::default()), + //Box::new(across::Across::default()), + Box::new(stargate::Stargate::default()), ], } } diff --git a/gemstone/src/swapper/models.rs b/gemstone/src/swapper/models.rs index c278588b..59bc5504 100644 --- a/gemstone/src/swapper/models.rs +++ b/gemstone/src/swapper/models.rs @@ -69,6 +69,7 @@ pub enum SwapProvider { Orca, Jupiter, Across, + Stargate, } #[derive(Debug, Clone, PartialEq, uniffi::Enum)] @@ -88,6 +89,7 @@ impl SwapProvider { Self::Orca => "Orca Whirlpool", Self::Jupiter => "Jupiter", Self::Across => "Across v3", + Self::Stargate => "Stargate", } } @@ -100,6 +102,7 @@ impl SwapProvider { Self::Orca => SwapProviderType::OnChain, Self::Jupiter => SwapProviderType::OnChain, Self::Across => SwapProviderType::Bridge, + Self::Stargate => SwapProviderType::Bridge, } } } diff --git a/gemstone/src/swapper/stargate/mod.rs b/gemstone/src/swapper/stargate/mod.rs new file mode 100644 index 00000000..37edf5da --- /dev/null +++ b/gemstone/src/swapper/stargate/mod.rs @@ -0,0 +1,2 @@ +pub mod provider; +pub use provider::Stargate; diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs new file mode 100644 index 00000000..a7af426e --- /dev/null +++ b/gemstone/src/swapper/stargate/provider.rs @@ -0,0 +1,448 @@ +use std::str::FromStr; +use std::sync::Arc; + +use alloy_core::sol_types::SolCall; +use alloy_primitives::{hex, Address, Bytes, FixedBytes, U160, U256, U32}; +use async_trait::async_trait; +use gem_evm::{ + jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, + stargate::contract::{IRouter, MessagingFee, SendParam}, +}; +use primitives::{AssetId, Chain}; +use serde::{Deserialize, Serialize}; + +use crate::{ + debug_println, + network::{jsonrpc_call, AlienProvider, JsonRpcResult}, + swapper::{ + approval::check_approval_erc20, + asset::{BASE_USDC, ETHEREUM_USDC, ETHEREUM_USDT, OPTIMISM_USDC}, + eth_rpc, + slippage::apply_slippage_in_bp, + ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, + SwapperError, + }, +}; + +#[derive(Debug, PartialEq, Serialize, Deserialize)] +struct StargateRouteData { + send_param: SendParam, + fee: MessagingFee, + refund_address: String, +} + +#[derive(Debug, Default)] +pub struct Stargate {} + +impl Stargate { + pub fn get_endpoint_id(&self, chain: &Chain) -> u32 { + match chain { + Chain::Ethereum => 30101u32, + Chain::Optimism => 30111u32, + Chain::Base => 30184u32, + _ => 0u32, + } + } + + pub fn address_to_bytes32(&self, addr: &str) -> FixedBytes<32> { + FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) + } + + pub fn get_pool(&self, asset: &AssetId) -> Option { + match asset.chain { + Chain::Base => match &asset.token_id { + Some(token_id) => Some("0x27a16dc786820b16e5c9028b75b99f6f604b5d26".to_string()), + None => Some("0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string()), + }, + Chain::Optimism => match &asset.token_id { + Some(token_id) => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), + None => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), + }, + _ => None, + } + } +} + +#[async_trait] +impl GemSwapProvider for Stargate { + fn provider(&self) -> SwapProvider { + SwapProvider::Stargate + } + + fn supported_assets(&self) -> Vec { + Chain::all() + .iter() + .map(|chain| match chain { + Chain::Base => SwapChainAsset::Assets(chain.clone(), vec![BASE_USDC.id.clone()]), + Chain::Optimism => SwapChainAsset::Assets(chain.clone(), vec![OPTIMISM_USDC.id.clone()]), + _ => SwapChainAsset::Assets(chain.clone(), vec![]), + }) + .collect() + } + + async fn fetch_quote(&self, request: &SwapQuoteRequest, provider: Arc) -> Result { + println!("request: {:?}", request); + let pool = self.get_pool(&request.from_asset).unwrap(); + let amount_ld = U256::from_str(request.value.as_str()).unwrap(); + let mut send_param = SendParam { + dstEid: self.get_endpoint_id(&request.to_asset.chain), + to: self.address_to_bytes32(request.destination_address.as_str()), + amountLD: amount_ld, + minAmountLD: amount_ld, + extraOptions: Bytes::from_str("0x").unwrap(), + composeMsg: Bytes::from_str("0x").unwrap(), + oftCmd: Bytes::from_str("0x").unwrap(), + }; + + println!("pool: {:?}", pool); + + println!("send_param: {:?}", send_param); + + // Encode call data + let call_data = IRouter::quoteOFTCall { + _sendParam: send_param.clone(), + } + .abi_encode(); + + let call = EthereumRpc::Call(TransactionObject::new_call(pool.as_str(), call_data), BlockParameter::Latest); + let response: JsonRpcResult = jsonrpc_call(&call, provider.clone(), &request.from_asset.chain).await?; + let result = response.take()?; + let hex_data = hex::decode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; + let quote_oft_data = IRouter::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + + println!("quote oft - {:?}", quote_oft_data); + //println!("feeAmount = {}", quote_oft_data.oftFeeDetails[0].feeAmountLD); + send_param.minAmountLD = apply_slippage_in_bp("e_oft_data.receipt.amountReceivedLD, request.options.slippage_bps); + //send_param.minAmountLD = U256::from(99500u32); + + let messaging_fee_calldata = IRouter::quoteSendCall { + _sendParam: send_param.clone(), + _payInLzToken: false, + } + .abi_encode(); + + let messaging_fee_call = EthereumRpc::Call(TransactionObject::new_call(pool.as_str(), messaging_fee_calldata), BlockParameter::Latest); + let messaging_fee_response: JsonRpcResult = jsonrpc_call(&messaging_fee_call, provider.clone(), &request.from_asset.chain).await?; + let messaging_fee_result = messaging_fee_response.take()?; + let messaging_fee_hex_data = hex::decode(messaging_fee_result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; + println!("messagingFee eth_call result: {:?}", messaging_fee_hex_data); + + let messaging_fee_value = + IRouter::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + println!("messagingFee = {:?}", messaging_fee_value); + + let approval = if request.from_asset.is_token() { + check_approval_erc20( + request.wallet_address.clone(), + request.from_asset.token_id.clone().unwrap(), + pool.clone(), + amount_ld, + provider.clone(), + &request.from_asset.chain, + ) + .await? + } else { + ApprovalType::None + }; + + let route_data = StargateRouteData { + send_param: send_param.clone(), + fee: messaging_fee_value.fee, + refund_address: request.wallet_address.to_string(), + }; + + Ok(SwapQuote { + from_value: request.value.clone(), + to_value: quote_oft_data.receipt.amountReceivedLD.to_string(), + data: SwapProviderData { + provider: self.provider(), + routes: vec![SwapRoute { + input: request.from_asset.clone(), + output: request.to_asset.clone(), + route_data: serde_json::to_string(&route_data).unwrap_or_default(), + gas_estimate: None, + }], + suggested_slippage_bps: None, + }, + approval, + request: request.clone(), + }) + } + + async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, data: FetchQuoteData) -> Result { + let pool = self.get_pool("e.request.from_asset).unwrap(); + let route_data: StargateRouteData = serde_json::from_str("e.data.routes.first().unwrap().route_data).map_err(|_| SwapperError::InvalidRoute)?; + let send_calldata = IRouter::sendCall { + _sendParam: route_data.send_param.clone(), + _fee: route_data.fee.clone(), + _refundAddress: Address::from_str(route_data.refund_address.as_str()).unwrap(), + } + .abi_encode(); + // + //let send_calldata = hex::decode("0xc7c7f5b3000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000e021ca97a2f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf24000000000000000000000000000000000000000000000000000000000000759f0000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf2400000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000184ac00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + + println!("Route data - {:?}", route_data); + println!("Calldata - {:?}", send_calldata); + println!("data - {:?}", data); + let mut value_to_send = route_data.fee.nativeFee; + + if quote.request.from_asset.is_native() { + value_to_send += route_data.send_param.amountLD; + } + + let quote_data = SwapQuoteData { + to: pool, + value: value_to_send.to_string(), + data: hex::encode_prefixed(send_calldata.clone()), + }; + println!("Quote data - {:?}", quote_data); + + let hex_value = format!("{:#x}", value_to_send); + println!("hex_value = {:?}", hex_value); + + let tx = TransactionObject::new_call_with_from_value("e.request.wallet_address, "e_data.to, &hex_value, send_calldata); + println!("tx = {:?}", tx); + let gas_limit = eth_rpc::estimate_gas(_provider.clone(), "e.request.from_asset.chain, tx).await?; + debug_println!("gas_limit: {:?}", gas_limit); + + Ok(quote_data) + } + + async fn get_transaction_status(&self, _chain: Chain, _transaction_hash: &str, _provider: Arc) -> Result { + todo!() + } +} + +#[cfg(test)] +mod tests { + use crate::{ + network::{provider::AlienProvider, target::*, *}, + swapper::SwapperError, + }; + use alloy_core::{ + hex::{decode as HexDecode, encode_prefixed as HexEncode}, + primitives::{Address, Bytes, FixedBytes, U160, U256}, + sol_types::{SolCall, SolValue}, + }; + use alloy_primitives::utils::parse_units; + use async_trait::async_trait; + use futures::TryFutureExt; + use gem_evm::{ + jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, + stargate::contract::{ + IRouter::{self, IRouterCalls}, + LzTxObj, SendParam, + }, + }; + use primitives::{Asset, AssetId, Chain}; + use reqwest::Client; + use std::{collections::HashMap, sync::Arc}; + + use super::*; + + #[derive(Debug)] + pub struct NativeProvider { + pub node_config: HashMap, + pub client: Client, + } + + impl NativeProvider { + pub fn new(node_config: HashMap) -> Self { + Self { + node_config, + client: Client::new(), + } + } + } + + #[async_trait] + impl AlienProvider for NativeProvider { + fn get_endpoint(&self, chain: Chain) -> Result { + Ok(self + .node_config + .get(&chain) + .ok_or(AlienError::ResponseError { + msg: "not supported chain".into(), + })? + .to_string()) + } + + async fn request(&self, target: AlienTarget) -> Result { + println!("==> request: url: {:?}, method: {:?}", target.url, target.method); + let mut req = match target.method { + AlienHttpMethod::Get => self.client.get(target.url), + AlienHttpMethod::Post => self.client.post(target.url), + AlienHttpMethod::Put => self.client.put(target.url), + AlienHttpMethod::Delete => self.client.delete(target.url), + AlienHttpMethod::Head => self.client.head(target.url), + AlienHttpMethod::Patch => self.client.patch(target.url), + AlienHttpMethod::Options => todo!(), + }; + if let Some(headers) = target.headers { + for (key, value) in headers.iter() { + req = req.header(key, value); + } + } + if let Some(body) = target.body { + println!("==> request body size: {:?}", body.len()); + println!("==> request body: {:?}", String::from_utf8(body.clone()).unwrap()); + req = req.body(body); + } + + let response = req + .send() + .map_err(|e| AlienError::ResponseError { + msg: format!("reqwest send error: {:?}", e), + }) + .await?; + let bytes = response + .bytes() + .map_err(|e| AlienError::ResponseError { + msg: format!("request error: {:?}", e), + }) + .await?; + + println!("<== response body size: {:?}", bytes.len()); + Ok(bytes.to_vec()) + } + + async fn batch_request(&self, targets: Vec) -> Result, AlienError> { + let mut futures = vec![]; + for target in targets.iter() { + let future = self.request(target.clone()); + futures.push(future); + } + let responses = futures::future::join_all(futures).await; + let error = responses.iter().find_map(|x| x.as_ref().err()); + if let Some(err) = error { + return Err(err.clone()); + } + let responses = responses.into_iter().filter_map(|x| x.ok()).collect(); + Ok(responses) + } + } + + fn address_to_bytes32(addr: &str) -> FixedBytes<32> { + FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) + } + + #[tokio::test] + async fn test_swap_usdc_base_to_usdc_op() -> Result<(), SwapperError> { + let node_config = HashMap::from([(Chain::Base, "https://mainnet.base.org".to_string())]); + let network_provider = Arc::new(NativeProvider::new(node_config)); + + let op_dst_eid = 30111u32; + let amount_ld = U256::from(1_000_000_000u64); + + let mut send_param = SendParam { + dstEid: op_dst_eid, + to: address_to_bytes32("0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24"), + amountLD: amount_ld, + minAmountLD: amount_ld, + extraOptions: Bytes::from_str("0x").unwrap(), + composeMsg: Bytes::from_str("0x").unwrap(), + oftCmd: Bytes::from_str("0x").unwrap(), + }; + println!("send_param: {:?}", send_param); + + // Encode call data + let call_data = IRouter::quoteOFTCall { + _sendParam: send_param.clone(), + } + .abi_encode(); + + let call = EthereumRpc::Call( + TransactionObject::new_call("0x27a16dc786820b16e5c9028b75b99f6f604b5d26", call_data), + BlockParameter::Latest, + ); + let response: JsonRpcResult = jsonrpc_call(&call, network_provider.clone(), &Chain::Base).await?; + let result = response.take()?; + let hex_data = HexDecode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; + println!("quoteLayerZeroFee eth_call result: {:?}", hex_data); + + let value = IRouter::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + + println!("nativeFee = {}", value.receipt.amountSentLD); + println!("zroFee = {}", value.receipt.amountReceivedLD); + println!("feeAmount = {}", value.oftFeeDetails[0].feeAmountLD); + send_param.minAmountLD = value.receipt.amountSentLD; + + let messaging_fee_calldata = IRouter::quoteSendCall { + _sendParam: send_param.clone(), + _payInLzToken: false, + } + .abi_encode(); + + let messaging_fee_call = EthereumRpc::Call( + TransactionObject::new_call("0x27a16dc786820b16e5c9028b75b99f6f604b5d26", messaging_fee_calldata), + BlockParameter::Latest, + ); + let messaging_fee_response: JsonRpcResult = jsonrpc_call(&messaging_fee_call, network_provider.clone(), &Chain::Base).await?; + let messaging_fee_result = messaging_fee_response.take()?; + let messaging_fee_hex_data = HexDecode(messaging_fee_result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; + println!("messagingFee eth_call result: {:?}", messaging_fee_hex_data); + + let messaging_fee_value = + IRouter::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + println!("messagingFee amountSentLD = {}", messaging_fee_value.fee.nativeFee); + println!("messagingFee amountReceivedLD = {}", messaging_fee_value.fee.lzTokenFee); + // + // -------------------------------------------------- + // 2) swap(...) via signed raw transaction + // -------------------------------------------------- + // Hypothetical pool IDs for USDC(Base)->USDC(OP) + //let src_pool_id = U256::from(1); + //let dst_pool_id = U256::from(2); + //let amount_ld = U256::from(50_000_000u64); // 50 USDC + //let min_amount_ld = U256::from(49_000_000u64); + // + //// Refund address + //let refund_address = Address::from_slice(&hex_decode("0000000000000000000000000123456789abCDef0123456789AbCdef01234567").unwrap()); + + //let swap_data = IRouter::swapCall { + // _dstChainId: dst_chain_id, + // _srcPoolId: src_pool_id, + // _dstPoolId: dst_pool_id, + // _refundAddress: + // _amountLD: amount_ld, + // _minAmountLD: min_amount_ld, + // _lzTxParams: lz_obj.clone(), + // _to: to_addr_bytes.clone(), + // _payload: payload.clone(), + //} + //.abi_encode(); + // + //// We need nonce & gasPrice + //// Derive "from" address from the private key if you want to do an actual state-changing tx + //// For brevity, let's just assume we know the address: + //let from_addr_hex = "0x0123456789abCDef0123456789abcDEF012345678"; + //let nonce = get_transaction_count(&client, rpc_url, from_addr_hex).await?; + //let gas_price_biguint = get_gas_price(&client, rpc_url).await?; + //println!("nonce = {}, gasPrice = {}", nonce, gas_price_biguint); + // + //let nonce_u256 = U256::from(nonce); + //let gas_price_u256 = U256::from(gas_price_biguint); + //let gas_limit = U256::from(2_000_000u64); + // + //// The bridging fee is paid in Base ETH, so transaction "value" = `native_fee` + //let tx_value = native_fee; + // + //// Sign a LEGACY TX (many chains use EIP-1559, but this is just a demonstration) + //let raw_tx = sign_legacy_tx( + // chain_id, + // nonce_u256, + // gas_price_u256, + // gas_limit, + // router_addr_20, + // tx_value, + // swap_data, + // &signing_key, + //); + // + //// Send raw TX + //let tx_hash = send_raw_transaction(&client, rpc_url, raw_tx).await?; + //println!("swap() transaction submitted! txHash = {}", tx_hash); + + Ok(()) + } +} From a8027f6825f0a239c8ac2aa2d31174b860aac471 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Tue, 14 Jan 2025 18:50:21 +0200 Subject: [PATCH 02/16] feat(stargate): chain and assets support --- crates/gem_evm/src/stargate/contract.rs | 132 ++++--- gemstone/src/swapper/asset.rs | 62 +++ gemstone/src/swapper/mod.rs | 14 +- gemstone/src/swapper/stargate/endpoint.rs | 297 ++++++++++++++ .../src/swapper/stargate/layer_zero/mod.rs | 1 + .../src/swapper/stargate/layer_zero/scan.rs | 285 ++++++++++++++ gemstone/src/swapper/stargate/mod.rs | 3 + gemstone/src/swapper/stargate/pool.rs | 218 +++++++++++ gemstone/src/swapper/stargate/provider.rs | 361 +++++------------- 9 files changed, 1033 insertions(+), 340 deletions(-) create mode 100644 gemstone/src/swapper/stargate/endpoint.rs create mode 100644 gemstone/src/swapper/stargate/layer_zero/mod.rs create mode 100644 gemstone/src/swapper/stargate/layer_zero/scan.rs create mode 100644 gemstone/src/swapper/stargate/pool.rs diff --git a/crates/gem_evm/src/stargate/contract.rs b/crates/gem_evm/src/stargate/contract.rs index 89dc43d5..2456e201 100644 --- a/crates/gem_evm/src/stargate/contract.rs +++ b/crates/gem_evm/src/stargate/contract.rs @@ -6,47 +6,47 @@ sol! { struct LzTxObj { uint256 dstGasForCall; uint256 dstNativeAmount; - bytes dstNativeAddr; + bytes dstNativeAddr; } /** - * @dev Struct representing token parameters for the OFT send() operation. - */ + * @dev Struct representing token parameters for the OFT send() operation. + */ #[derive(Debug, PartialEq, Serialize, Deserialize)] struct SendParam { - uint32 dstEid; // Destination endpoint ID. - bytes32 to; // Recipient address. - uint256 amountLD; // Amount to send in local decimals. - uint256 minAmountLD; // Minimum amount to send in local decimals. - bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. - bytes composeMsg; // The composed message for the send() operation. - bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. + uint32 dstEid; // Destination endpoint ID. + bytes32 to; // Recipient address. + uint256 amountLD; // Amount to send in local decimals. + uint256 minAmountLD; // Minimum amount to send in local decimals. + bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. + bytes composeMsg; // The composed message for the send() operation. + bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. } /** - * @dev Struct representing OFT limit information. - * @dev These amounts can change dynamically and are up the the specific oft implementation. - */ + * @dev Struct representing OFT limit information. + * @dev These amounts can change dynamically and are up the the specific oft implementation. + */ #[derive(Debug, PartialEq)] struct OFTLimit { - uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient. - uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient. + uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient. + uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient. } /** - * @dev Struct representing OFT receipt information. - */ + * @dev Struct representing OFT receipt information. + */ #[derive(Debug, PartialEq)] struct OFTReceipt { - uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals. - // @dev In non-default implementations, the amountReceivedLD COULD differ from this value. + uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals. + // @dev In non-default implementations, the amountReceivedLD COULD differ from this value. uint256 amountReceivedLD; // Amount of tokens to be received on the remote side. } #[derive(Debug, PartialEq)] struct OFTFeeDetail { - int256 feeAmountLD; // Amount of the fee in local decimals. - string description; // Description of the fee. + int256 feeAmountLD; // Amount of the fee in local decimals. + string description; // Description of the fee. } #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -57,68 +57,76 @@ sol! { #[derive(Debug, PartialEq, Serialize, Deserialize)] struct MessagingReceipt { - bytes32 guid; - uint64 nonce; + bytes32 guid; + uint64 nonce; MessagingFee fee; } - #[derive(Debug, PartialEq, Serialize, Deserialize)] struct Ticket { uint56 ticketId; - bytes passenger; + bytes passenger; } - #[derive(Debug, PartialEq)] - interface IRouter { + interface IStargate { function quoteSend( SendParam calldata _sendParam, - bool _payInLzToken + bool _payInLzToken ) external view returns (MessagingFee memory fee); - /// @notice Provides a quote for sending OFT to another chain. - /// @dev Implements the IOFT interface - /// @param _sendParam The parameters for the send operation - /// @return limit The information on OFT transfer limits - /// @return oftFeeDetails The details of OFT transaction cost or reward - /// @return receipt The OFT receipt information, indicating how many tokens would be sent and received + /** + * @notice Provides a quote for sending OFT to another chain. + * @dev Implements the IOFT interface + * @param _sendParam The parameters for the send operation + * @return limit The information on OFT transfer limits + * @return oftFeeDetails The details of OFT transaction cost or reward + * @return receipt The OFT receipt information, indicating how many tokens would be sent and received + */ function quoteOFT( SendParam calldata _sendParam ) external view returns ( - OFTLimit memory limit, - OFTFeeDetail[] memory oftFeeDetails, - OFTReceipt memory receipt + OFTLimit memory limit, + OFTFeeDetail[] memory oftFeeDetails, + OFTReceipt memory receipt ); - /// which allows the caller to ride and drive the bus in the same transaction. /** - * @notice Executes the send() operation. - * @param _sendParam The parameters for the send operation. - * @param _fee The fee information supplied by the caller. - * - nativeFee: The native fee. - * - lzTokenFee: The lzToken fee. - * @param _refundAddress The address to receive any excess funds from fees etc. on the src. - * @return receipt The LayerZero messaging receipt from the send() operation. - * @return oftReceipt The OFT receipt information. - * - * @dev MessagingReceipt: LayerZero msg receipt - * - guid: The unique identifier for the sent message. - * - nonce: The nonce of the sent message. - * - fee: The LayerZero fee incurred for the message. - */ + * @notice Executes the send() operation. + * @param _sendParam The parameters for the send operation. + * @param _fee The fee information supplied by the caller. + * - nativeFee: The native fee. + * - lzTokenFee: The lzToken fee. + * @param _refundAddress The address to receive any excess funds from fees etc. on the src. + * @return receipt The LayerZero messaging receipt from the send() operation. + * @return oftReceipt The OFT receipt information. + * + * @dev MessagingReceipt: LayerZero msg receipt + * - guid: The unique identifier for the sent message. + * - nonce: The nonce of the sent message. + * - fee: The LayerZero fee incurred for the message. + */ function send( - SendParam calldata _sendParam, - MessagingFee calldata _fee, - address _refundAddress - ) external payable returns (MessagingReceipt memory, OFTReceipt memory); + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable returns ( + MessagingReceipt memory msgReceipt, + OFTReceipt memory oftReceipt + ); - /// @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode, - /// which allows the caller to ride and drive the bus in the same transaction. + /** + * @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode, + * which allows the caller to ride and drive the bus in the same transaction. + */ function sendToken( - SendParam calldata _sendParam, - MessagingFee calldata _fee, - address _refundAddress - ) external payable returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt, Ticket memory ticket); + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) external payable returns ( + MessagingReceipt memory msgReceipt, + OFTReceipt memory oftReceipt, + Ticket memory ticket + ); } } diff --git a/gemstone/src/swapper/asset.rs b/gemstone/src/swapper/asset.rs index d72e6d22..87de49b5 100644 --- a/gemstone/src/swapper/asset.rs +++ b/gemstone/src/swapper/asset.rs @@ -10,6 +10,8 @@ const WBTC_NAME: &str = "Wrapped BTC"; const DAI_SYMBOL: &str = "DAI"; const WETH_NAME: &str = "Wrapped Ether"; const WETH_SYMBOL: &str = "WETH"; +const METH_NAME: &str = "mETH (mETH)"; +const METH_SYMBOL: &str = "mETH"; const CBBTC_NAME: &str = "Coinbase BTC"; const CBBTC_SYMBOL: &str = "cbBTC"; @@ -18,12 +20,19 @@ pub const ETHEREUM_USDC_TOKEN_ID: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606 pub const ETHEREUM_USDT_TOKEN_ID: &str = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; pub const ETHEREUM_WBTC_TOKEN_ID: &str = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; pub const ETHEREUM_DAI_TOKEN_ID: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; +pub const ETHEREUM_METH_TOKEN_ID: &str = "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa"; pub const SMARTCHAIN_USDT_TOKEN_ID: &str = "0x55d398326f99059fF775485246999027B3197955"; pub const SMARTCHAIN_USDC_TOKEN_ID: &str = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; pub const AVALANCHE_USDT_TOKEN_ID: &str = "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"; pub const AVALANCHE_USDC_TOKEN_ID: &str = "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"; pub const BASE_USDC_TOKEN_ID: &str = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; pub const BASE_CBBTC_TOKEN_ID: &str = "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"; +pub const MANTLE_USDC_TOKEN_ID: &str = "0x09bc4e0d864854c6afb6eb9a9cdf58ac190d0df9"; +pub const MANTLE_USDT_TOKEN_ID: &str = "0x201eba5cc46d216ce6dc03f6a759e8e766e956ae"; +pub const MANTLE_WETH_TOKEN_ID: &str = "0xdeaddeaddeaddeaddeaddeaddeaddeaddead1111"; +pub const MANTLE_METH_TOKEN_ID: &str = "0xcda86a272531e8640cd7f1a92c01839911b90bb0"; +pub const SEI_USDC_TOKEN_ID: &str = "0x3894085Ef7Ff0f0aeDf52E2A2704928d1Ec074F1"; +pub const SEI_USDT_TOKEN_ID: &str = "0x0dB9afb4C33be43a0a0e396Fd1383B4ea97aB10a"; lazy_static! { // ethereum @@ -62,6 +71,13 @@ lazy_static! { decimals: 18, asset_type: AssetType::ERC20, }; + pub static ref ETHEREUM_METH: Asset = Asset { + id: AssetId::from_token(Chain::Ethereum, ETHEREUM_METH_TOKEN_ID), + name: METH_NAME.into(), + symbol: METH_SYMBOL.into(), + decimals: 18, + asset_type: AssetType::ERC20, + }; // arbitrum pub static ref ARBITRUM_WETH: Asset = Asset { id: WETH_ARB_ASSET_ID.into(), @@ -226,4 +242,50 @@ lazy_static! { decimals: 6, asset_type: AssetType::ERC20, }; + // mantle + pub static ref MANTLE_USDC: Asset = Asset { + id: AssetId::from_token(Chain::Mantle, MANTLE_USDC_TOKEN_ID), + name: USDC_NAME.to_owned(), + symbol: USDC_SYMBOL.to_owned(), + decimals: 6, + asset_type: AssetType::ERC20, + }; + pub static ref MANTLE_USDT: Asset = Asset { + id: AssetId::from_token(Chain::Mantle, MANTLE_USDT_TOKEN_ID), + name: USDT_NAME.to_owned(), + symbol: USDT_SYMBOL.to_owned(), + decimals: 6, + asset_type: AssetType::ERC20, + }; + pub static ref MANTLE_WETH: Asset = Asset { + id: AssetId::from_token(Chain::Mantle, MANTLE_WETH_TOKEN_ID), + name: WETH_NAME.to_owned(), + symbol: WETH_SYMBOL.to_owned(), + decimals: 18, + asset_type: AssetType::ERC20, + }; + pub static ref MANTLE_METH: Asset = Asset { + id: AssetId::from_token(Chain::Mantle, MANTLE_METH_TOKEN_ID), + name: METH_NAME.to_owned(), + symbol: METH_SYMBOL.to_owned(), + decimals: 18, + asset_type: AssetType::ERC20, + }; + + // sei + pub static ref SEI_USDC: Asset = Asset { + id: AssetId::from_token(Chain::Sei, SEI_USDC_TOKEN_ID), + name: USDC_NAME.to_owned(), + symbol: USDC_SYMBOL.to_owned(), + decimals: 6, + asset_type: AssetType::ERC20, + }; + pub static ref SEI_USDT: Asset = Asset { + id: AssetId::from_token(Chain::Sei, SEI_USDT_TOKEN_ID), + name: USDT_NAME.to_owned(), + symbol: USDT_SYMBOL.to_owned(), + decimals: 6, + asset_type: AssetType::ERC20, + }; + } diff --git a/gemstone/src/swapper/mod.rs b/gemstone/src/swapper/mod.rs index 68d80d2b..dd90f229 100644 --- a/gemstone/src/swapper/mod.rs +++ b/gemstone/src/swapper/mod.rs @@ -81,13 +81,13 @@ impl GemSwapper { Self { rpc_provider, swappers: vec![ - //Box::new(universal_router::UniswapV3::new_uniswap()), - //Box::new(universal_router::UniswapV3::new_pancakeswap()), - //Box::new(thorchain::ThorChain::default()), - //Box::new(jupiter::Jupiter::default()), - //Box::new(pancakeswap_aptos::PancakeSwapAptos::default()), - //Box::new(across::Across::default()), - Box::new(stargate::Stargate::default()), + Box::new(universal_router::UniswapV3::new_uniswap()), + Box::new(universal_router::UniswapV3::new_pancakeswap()), + Box::new(thorchain::ThorChain::default()), + Box::new(jupiter::Jupiter::default()), + Box::new(pancakeswap_aptos::PancakeSwapAptos::default()), + Box::new(across::Across::default()), + Box::new(stargate::Stargate::new()), ], } } diff --git a/gemstone/src/swapper/stargate/endpoint.rs b/gemstone/src/swapper/stargate/endpoint.rs new file mode 100644 index 00000000..2448de26 --- /dev/null +++ b/gemstone/src/swapper/stargate/endpoint.rs @@ -0,0 +1,297 @@ +use lazy_static::lazy_static; +use primitives::{AssetId, Chain}; + +use crate::swapper::{ + asset::{ + ARBITRUM_USDC, ARBITRUM_USDT, ARBITRUM_WETH, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, BASE_WETH, ETHEREUM_METH, ETHEREUM_USDC, ETHEREUM_USDT, + ETHEREUM_WETH, LINEA_WETH, MANTLE_USDC, MANTLE_USDT, OPTIMISM_USDC, OPTIMISM_USDT, OPTIMISM_WETH, POLYGON_USDC, POLYGON_USDT, SEI_USDC, SEI_USDT, + SMARTCHAIN_USDC, SMARTCHAIN_USDT, + }, + SwapChainAsset, SwapperError, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct StargatePool { + pub asset: AssetId, + pub address: String, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct StargateEndpoint { + pub id: Chain, + pub pools: Vec, + pub endpoint_id: u32, + pub chain_asset: SwapChainAsset, +} + +#[derive(Clone, Debug)] +pub struct StargateRoutes { + pub ethereum: StargateEndpoint, + pub base: StargateEndpoint, + pub optimism: StargateEndpoint, + pub arbitrum: StargateEndpoint, + pub polygon: StargateEndpoint, + pub avalanche: StargateEndpoint, + pub linea: StargateEndpoint, + pub smartchain: StargateEndpoint, + pub sei: StargateEndpoint, + //pub metis: StargateEndpoint, + //pub scroll: StargateEndpoint, + pub mantle: StargateEndpoint, + //pub kava: StargateEndpoint, + //pub aurora: StargateEndpoint, + //pub core: StargateEndpoint, +} + +lazy_static! { + pub static ref STARGATE_ROUTES: StargateRoutes = StargateRoutes { + ethereum: StargateEndpoint { + id: Chain::Ethereum, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Ethereum), + address: "0x77b2043768d28E9C9aB44E1aBfC95944bcE57931".to_string(), + }, + StargatePool { + asset: ETHEREUM_USDC.id.clone(), + address: "0xc026395860Db2d07ee33e05fE50ed7bD583189C7".to_string(), + }, + StargatePool { + asset: ETHEREUM_USDT.id.clone(), + address: "0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string(), + }, + StargatePool { + asset: ETHEREUM_METH.id.clone(), + address: "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa".to_string(), + }, + ], + endpoint_id: 30101, + chain_asset: SwapChainAsset::Assets( + Chain::Ethereum, + vec![AssetId::from_chain(Chain::Ethereum), ETHEREUM_USDC.id.clone(), ETHEREUM_USDT.id.clone(), ETHEREUM_METH.id.clone(),] + ), + }, + base: StargateEndpoint { + id: Chain::Base, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Base), + address: "0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string(), + }, + StargatePool { + asset: BASE_USDC.id.clone(), + address: "0x27a16dc786820B16E5c9028b75B99F6f604b5d26".to_string(), + }, + ], + endpoint_id: 30184, + chain_asset: SwapChainAsset::Assets(Chain::Base, vec![BASE_USDC.id.clone(),]), + }, + optimism: StargateEndpoint { + id: Chain::Optimism, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Optimism), + address: "0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string(), + }, + StargatePool { + asset: OPTIMISM_USDC.id.clone(), + address: "0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string(), + }, + StargatePool { + asset: OPTIMISM_USDT.id.clone(), + address: "0x19cFCE47eD54a88614648DC3f19A5980097007dD".to_string(), + }, + ], + endpoint_id: 30111, + chain_asset: SwapChainAsset::Assets( + Chain::Optimism, + vec![OPTIMISM_USDC.id.clone(), OPTIMISM_USDT.id.clone(),] + ), + }, + arbitrum: StargateEndpoint { + id: Chain::Arbitrum, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Arbitrum), + address: "0xA45B5130f36CDcA45667738e2a258AB09f4A5f7F".to_string(), + }, + StargatePool { + asset: ARBITRUM_USDC.id.clone(), + address: "0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string(), + }, + StargatePool { + asset: ARBITRUM_USDT.id.clone(), + address: "0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string(), + }, + ], + endpoint_id: 30110, + chain_asset: SwapChainAsset::Assets( + Chain::Arbitrum, + vec![ARBITRUM_USDC.id.clone(), ARBITRUM_USDT.id.clone(),] + ), + }, + polygon: StargateEndpoint { + id: Chain::Polygon, + pools: vec![ + StargatePool { + asset: POLYGON_USDC.id.clone(), + address: "0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4".to_string(), + }, + StargatePool { + asset: POLYGON_USDT.id.clone(), + address: "0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7".to_string(), + }, + ], + endpoint_id: 30109, + chain_asset: SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDC.id.clone(), POLYGON_USDT.id.clone(),]), + }, + avalanche: StargateEndpoint { + id: Chain::AvalancheC, + pools: vec![ + StargatePool { + asset: AVALANCHE_USDC.id.clone(), + address: "0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47".to_string(), + }, + StargatePool { + asset: AVALANCHE_USDT.id.clone(), + address: "0x12dC9256Acc9895B076f6638D628382881e62CeE".to_string(), + }, + ], + endpoint_id: 30106, + chain_asset: SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDC.id.clone(), AVALANCHE_USDT.id.clone(),]), + }, + linea: StargateEndpoint { + id: Chain::Linea, + pools: vec![StargatePool { + asset: AssetId::from_chain(Chain::Linea), + address: "0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string(), + },], + endpoint_id: 30183, + chain_asset: SwapChainAsset::Assets(Chain::Linea, vec![AssetId::from_chain(Chain::Linea),]), + }, + smartchain: StargateEndpoint { + id: Chain::SmartChain, + pools: vec![ + StargatePool { + asset: SMARTCHAIN_USDC.id.clone(), + address: "0x962Bd449E630b0d928f308Ce63f1A21F02576057".to_string(), + }, + StargatePool { + asset: SMARTCHAIN_USDT.id.clone(), + address: "0x138EB30f73BC423c6455C53df6D89CB01d9eBc63".to_string(), + }, + ], + endpoint_id: 30102, + chain_asset: SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDC.id.clone(), SMARTCHAIN_USDT.id.clone(),]), + }, + sei: StargateEndpoint { + id: Chain::Sei, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Sei), + address: "0x5c386D85b1B82FD9Db681b9176C8a4248bb6345B".to_string(), + }, + StargatePool { + asset: SEI_USDC.id.clone(), + address: "0x45d417612e177672958dC0537C45a8f8d754Ac2E".to_string(), + }, + StargatePool { + asset: SEI_USDT.id.clone(), + address: "0x0dB9afb4C33be43a0a0e396Fd1383B4ea97aB10a".to_string(), + }, + ], + endpoint_id: 30280, + chain_asset: SwapChainAsset::Assets(Chain::Sei, vec![ + SEI_USDC.id.clone(), + SEI_USDT.id.clone(), + ]), + }, + + //metis: StargateEndpoint { + // id: Chain::Metis, + // pools: vec![ + // StargatePool { + // asset: AssetId::from_chain(Chain::Metis), + // address: "0x36ed193dc7160D3858EC250e69D12B03Ca087D08".to_string(), + // }, + // StargatePool { + // asset: AssetId::from_token(Chain::Metis, "0x0"), + // address: "0x4dCBFC0249e8d5032F89D6461218a9D2eFff5125".to_string(), + // }, + // ], + // endpoint_id: 30151, + // chain_asset: SwapChainAsset::Assets(Chain::Metis, vec![]), + //}, + //scroll: StargateEndpoint { + // id: Chain::Scroll, + // pools: vec![ + // StargatePool { + // asset: AssetId::from_chain(Chain::Scroll), + // address: "0xC2b638Cb5042c1B3c5d5C969361fB50569840583".to_string(), + // }, + // StargatePool { + // asset: AssetId::from_token(Chain::Scroll, "0x0"), + // address: "0x3Fc69CC4A842838bCDC9499178740226062b14E4".to_string(), + // }, + // ], + // endpoint_id: 30214, + // chain_asset: SwapChainAsset::Assets(Chain::Scroll, vec![]), + //}, + mantle: StargateEndpoint { + id: Chain::Mantle, + pools: vec![ + StargatePool { + asset: AssetId::from_chain(Chain::Mantle), + address: "0x4c1d3Fc3fC3c177c3b633427c2F769276c547463".to_string(), + }, + StargatePool { + asset: MANTLE_USDC.id.clone(), + address: "0xAc290Ad4e0c891FDc295ca4F0a6214cf6dC6acDC".to_string(), + }, + StargatePool { + asset: MANTLE_USDT.id.clone(), + address: "0xB715B85682B731dB9D5063187C450095c91C57FC".to_string(), + }, + ], + endpoint_id: 30181, + chain_asset: SwapChainAsset::Assets(Chain::Mantle, vec![ + AssetId::from_chain(Chain::Mantle), + MANTLE_USDC.id.clone(), + MANTLE_USDT.id.clone(), + ]), + }, + //kava: StargateEndpoint { + // id: Chain::Kava, + // pools: vec![StargatePool { + // asset: AssetId::from_token(Chain::Kava, "0x0"), + // address: "0x41A5b0470D96656Fb3e8f68A218b39AdBca3420b".to_string(), + // },], + // endpoint_id: 30177, + // chain_asset: SwapChainAsset::Assets(Chain::Kava, vec![]), + //}, + //aurora: StargateEndpoint { + // id: Chain::Aurora, + // pools: vec![StargatePool { + // asset: AssetId::from_token(Chain::Aurora, "0x0"), + // address: "0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string(), + // },], + // endpoint_id: 30211, + // chain_asset: SwapChainAsset::Assets(Chain::Aurora, vec![]), + //}, + //core: StargateEndpoint { + // id: Chain::Core, + // pools: vec![ + // StargatePool { + // asset: AssetId::from_token(Chain::Core, "0x0"), + // address: "0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590".to_string(), + // }, + // StargatePool { + // asset: AssetId::from_token(Chain::Core, "0x0"), + // address: "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B".to_string(), + // }, + // ], + // endpoint_id: 30153, + // chain_asset: SwapChainAsset::Assets(Chain::Core, vec![]), + //}, + }; +} diff --git a/gemstone/src/swapper/stargate/layer_zero/mod.rs b/gemstone/src/swapper/stargate/layer_zero/mod.rs new file mode 100644 index 00000000..ebce89cd --- /dev/null +++ b/gemstone/src/swapper/stargate/layer_zero/mod.rs @@ -0,0 +1 @@ +pub mod scan; diff --git a/gemstone/src/swapper/stargate/layer_zero/scan.rs b/gemstone/src/swapper/stargate/layer_zero/scan.rs new file mode 100644 index 00000000..dc82fed8 --- /dev/null +++ b/gemstone/src/swapper/stargate/layer_zero/scan.rs @@ -0,0 +1,285 @@ +use crate::{ + network::{AlienProvider, AlienTarget}, + swapper::SwapperError, +}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, sync::Arc}; + +#[derive(Debug, Clone)] +pub struct LayerZeroScanApi { + pub url: String, + pub provider: Arc, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessageResponse { + pub data: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Message { + pub pathway: MessagePathway, + pub source: MessageSource, + pub destination: MessageDestination, + pub verification: MessageVerification, + pub guid: String, + pub config: MessageConfig, + pub status: MessageStatus, + pub created: String, + pub updated: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessagePathway { + pub src_eid: u32, + pub dst_eid: u32, + pub sender: MessageParticipant, + pub receiver: MessageParticipant, + pub id: String, + pub nonce: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageParticipant { + pub address: String, + pub id: Option, + pub name: Option, + pub chain: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageSource { + pub status: String, + pub tx: SourceTransaction, + #[serde(rename = "failedTx")] + pub failed_tx: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SourceTransaction { + pub tx_hash: String, + pub block_hash: String, + pub block_number: String, + pub block_timestamp: u64, + pub from: String, + pub block_confirmations: u64, + pub payload: String, + pub value: String, + pub readiness_timestamp: u64, + pub resolved_payload: String, + pub adapter_params: AdapterParams, + pub options: TransactionOptions, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdapterParams { + pub version: String, + pub dst_gas_limit: String, + pub dst_native_gas_transfer_amount: String, + pub dst_native_gas_transfer_address: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TransactionOptions { + #[serde(rename = "lzReceive")] + pub lz_receive: LzReceive, + #[serde(rename = "nativeDrop")] + pub native_drop: Vec, + pub compose: Vec, + pub ordered: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct LzReceive { + pub gas: String, + pub value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NativeDrop { + pub amount: String, + pub receiver: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Compose { + pub index: u64, + pub gas: String, + pub value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageDestination { + pub status: String, + pub tx: DestinationTransaction, + #[serde(rename = "payloadStoredTx")] + pub payload_stored_tx: String, + #[serde(rename = "failedTx")] + pub failed_tx: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DestinationTransaction { + pub tx_hash: String, + pub block_hash: String, + pub block_number: u64, + pub block_timestamp: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageVerification { + pub dvn: Dvn, + pub sealer: Sealer, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Dvn { + pub dvns: HashMap, + pub status: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DvnInfo { + pub tx_hash: String, + pub block_hash: String, + pub block_number: u64, + pub block_timestamp: u64, + pub proof: DvnProof, + pub optional: bool, + pub status: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DvnProof { + #[serde(rename = "packetHeader")] + pub packet_header: String, + #[serde(rename = "payloadHash")] + pub payload_hash: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Sealer { + pub tx: SealerTransaction, + #[serde(rename = "failedTx")] + pub failed_tx: Vec, + pub status: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SealerTransaction { + pub tx_hash: String, + pub block_hash: String, + pub block_number: u64, + pub block_timestamp: u64, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FailedTransaction { + pub tx_hash: String, + pub tx_error: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MessageConfig { + pub error: bool, + pub error_message: String, + pub dvn_config_error: bool, + pub receive_library: Option, + pub send_library: Option, + pub inbound_config: InboundConfig, + pub outbound_config: OutboundConfig, + pub uln_send_version: String, + pub uln_receive_version: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InboundConfig { + pub confirmations: u64, + pub required_dvn_count: u64, + pub optional_dvn_count: u64, + pub optional_dvn_threshold: u64, + pub required_dvns: Vec, + pub required_dvn_names: Vec, + pub optional_dvns: Vec, + pub optional_dvn_names: Vec, + pub executor: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct OutboundConfig { + pub confirmations: u64, + pub required_dvn_count: u64, + pub optional_dvn_count: u64, + pub optional_dvn_threshold: u64, + pub required_dvns: Vec, + pub required_dvn_names: Vec, + pub optional_dvns: Vec, + pub optional_dvn_names: Vec, + pub executor: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum MessageStatusName { + Inflight, + Confirming, + Failed, + Delivered, + Blocked, + PayloadStored, + ApplicationBurned, + ApplicationSkipped, + UnresolvableCommand, + MalformedCommand, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MessageStatus { + pub name: MessageStatusName, + pub message: Option, +} + +impl MessageStatus { + pub fn is_delivered(&self) -> bool { + matches!(self.name, MessageStatusName::Delivered) + } + + pub fn is_failed(&self) -> bool { + matches!(self.name, MessageStatusName::Failed) + } + + pub fn is_pending(&self) -> bool { + matches!( + self.name, + MessageStatusName::Inflight | MessageStatusName::Confirming | MessageStatusName::PayloadStored + ) + } +} + +impl LayerZeroScanApi { + pub fn new(provider: Arc) -> Self { + Self { + url: "https://scan.layerzero-api.com/v1".into(), + provider, + } + } + + pub async fn get_message_by_tx(&self, tx_hash: &str) -> Result { + let url = format!("{}/messages/tx/{}", self.url, tx_hash); + let target = AlienTarget::get(&url); + let response = self.provider.request(target).await?; + serde_json::from_slice(&response).map_err(|e| SwapperError::NetworkError { msg: e.to_string() }) + } +} diff --git a/gemstone/src/swapper/stargate/mod.rs b/gemstone/src/swapper/stargate/mod.rs index 37edf5da..070aa4e8 100644 --- a/gemstone/src/swapper/stargate/mod.rs +++ b/gemstone/src/swapper/stargate/mod.rs @@ -1,2 +1,5 @@ +mod endpoint; +mod layer_zero; +mod pool; pub mod provider; pub use provider::Stargate; diff --git a/gemstone/src/swapper/stargate/pool.rs b/gemstone/src/swapper/stargate/pool.rs new file mode 100644 index 00000000..7617fb68 --- /dev/null +++ b/gemstone/src/swapper/stargate/pool.rs @@ -0,0 +1,218 @@ +use primitives::Chain; + +use crate::swapper::{ + asset::{ + ARBITRUM_USDC, ARBITRUM_USDT, ARBITRUM_WETH, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, BASE_WETH, ETHEREUM_USDC, ETHEREUM_USDT, ETHEREUM_WETH, + LINEA_WETH, OPTIMISM_USDC, OPTIMISM_USDT, OPTIMISM_WETH, POLYGON_USDC, POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, + }, + SwapChainAsset, SwapperError, +}; + +#[derive(Debug, PartialEq)] +pub enum StargatePool { + // ETH + ETH_ETH, + ETH_USDC, + ETH_USDT, + + // BASE + BASE_ETH, + BASE_USDC, + // Note: BASE has no USDT pool + + // OPTIMISM + OPTIMISM_ETH, + OPTIMISM_USDC, + OPTIMISM_USDT, + + // ARBITRUM + ARBITRUM_ETH, + ARBITRUM_USDC, + ARBITRUM_USDT, + + // POLYGON + POLYGON_USDC, + POLYGON_USDT, + // Note: POLYGON has no native ETH pool + + // AVALANCHE + AVALANCHE_USDC, + AVALANCHE_USDT, + // Note: AVALANCHE has no native ETH pool + + // METIS + METIS_ETH, + METIS_USDT, + // Note: METIS has no USDC pool + + // BNB + BNB_USDC, + BNB_USDT, + // Note: BNB has no native ETH pool + + // SCROLL + SCROLL_ETH, + SCROLL_USDC, + + // LINEA + LINEA_ETH, + + // MANTLE + MANTLE_ETH, + MANTLE_USDC, + MANTLE_USDT, +} + +impl StargatePool { + pub fn to_stargate_contract_address(&self) -> Result { + let address = match self { + // Ethereum + StargatePool::ETH_ETH => Some("0x77b2043768d28E9C9aB44E1aBfC95944bcE57931".to_string()), + StargatePool::ETH_USDC => Some("0xc026395860Db2d07ee33e05fE50ed7bD583189C7".to_string()), + StargatePool::ETH_USDT => Some("0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string()), + + // Base + StargatePool::BASE_ETH => Some("0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string()), + StargatePool::BASE_USDC => Some("0x27a16dc786820B16E5c9028b75B99F6f604b5d26".to_string()), + + // Optimism + StargatePool::OPTIMISM_ETH => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), + StargatePool::OPTIMISM_USDC => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), + StargatePool::OPTIMISM_USDT => Some("0x19cFCE47eD54a88614648DC3f19A5980097007dD".to_string()), + + // Arbitrum + StargatePool::ARBITRUM_ETH => Some("0xA45B5130f36CDcA45667738e2a258AB09f4A5f7F".to_string()), + StargatePool::ARBITRUM_USDC => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), + StargatePool::ARBITRUM_USDT => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), + + // Polygon + StargatePool::POLYGON_USDC => Some("0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4".to_string()), + StargatePool::POLYGON_USDT => Some("0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7".to_string()), + + // Avalanche + StargatePool::AVALANCHE_USDC => Some("0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47".to_string()), + StargatePool::AVALANCHE_USDT => Some("0x12dC9256Acc9895B076f6638D628382881e62CeE".to_string()), + + // Metis + StargatePool::METIS_ETH => Some("0x36ed193dc7160D3858EC250e69D12B03Ca087D08".to_string()), + StargatePool::METIS_USDT => Some("0x4dCBFC0249e8d5032F89D6461218a9D2eFff5125".to_string()), + + // BNB Chain + StargatePool::BNB_USDC => Some("0x962Bd449E630b0d928f308Ce63f1A21F02576057".to_string()), + StargatePool::BNB_USDT => Some("0x138EB30f73BC423c6455C53df6D89CB01d9eBc63".to_string()), + + // Scroll + StargatePool::SCROLL_ETH => Some("0xC2b638Cb5042c1B3c5d5C969361fB50569840583".to_string()), + StargatePool::SCROLL_USDC => Some("0x3Fc69CC4A842838bCDC9499178740226062b14E4".to_string()), + + // Linea + StargatePool::LINEA_ETH => Some("0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string()), + + // Mantle + StargatePool::MANTLE_ETH => Some("0x4c1d3Fc3fC3c177c3b633427c2F769276c547463".to_string()), + StargatePool::MANTLE_USDC => Some("0xAc290Ad4e0c891FDc295ca4F0a6214cf6dC6acDC".to_string()), + StargatePool::MANTLE_USDT => Some("0xB715B85682B731dB9D5063187C450095c91C57FC".to_string()), + }; + Ok(address.ok_or(SwapperError::NotImplemented)?) + } + + pub fn endpoint_id(&self) -> u32 { + match self { + // Ethereum (all pools) + StargatePool::ETH_ETH | StargatePool::ETH_USDC | StargatePool::ETH_USDT => 30101, + + // Base (all pools) + StargatePool::BASE_ETH | StargatePool::BASE_USDC => 30184, + + // Optimism (all pools) + StargatePool::OPTIMISM_ETH | StargatePool::OPTIMISM_USDC | StargatePool::OPTIMISM_USDT => 30111, + + // Arbitrum (all pools) + StargatePool::ARBITRUM_ETH | StargatePool::ARBITRUM_USDC | StargatePool::ARBITRUM_USDT => 30110, + + // Polygon (all pools) + StargatePool::POLYGON_USDC | StargatePool::POLYGON_USDT => 30109, + + // Avalanche (all pools) + StargatePool::AVALANCHE_USDC | StargatePool::AVALANCHE_USDT => 30106, + + // Metis (all pools) + StargatePool::METIS_ETH | StargatePool::METIS_USDT => 30151, + + // BNB (all pools) + StargatePool::BNB_USDC | StargatePool::BNB_USDT => 30102, + + // Scroll (all pools) + StargatePool::SCROLL_ETH | StargatePool::SCROLL_USDC => 30214, + + // Linea (all pools) + StargatePool::LINEA_ETH => 30183, + + // Mantle (all pools) + StargatePool::MANTLE_ETH | StargatePool::MANTLE_USDC | StargatePool::MANTLE_USDT => 30181, + } + } + + pub fn from_chain(chain: &Chain) -> Vec { + match chain { + Chain::Ethereum => vec![Self::ETH_ETH, Self::ETH_USDC, Self::ETH_USDT], + Chain::Base => vec![Self::BASE_ETH, Self::BASE_USDC], + Chain::Optimism => vec![Self::OPTIMISM_ETH, Self::OPTIMISM_USDC, Self::OPTIMISM_USDT], + Chain::Arbitrum => vec![Self::ARBITRUM_ETH, Self::ARBITRUM_USDC, Self::ARBITRUM_USDT], + Chain::Polygon => vec![Self::POLYGON_USDC, Self::POLYGON_USDT], + Chain::AvalancheC => vec![Self::AVALANCHE_USDC, Self::AVALANCHE_USDT], + Chain::SmartChain => vec![Self::BNB_USDC, Self::BNB_USDT], + Chain::Linea => vec![Self::LINEA_ETH], + Chain::Mantle => vec![Self::MANTLE_ETH, Self::MANTLE_USDC, Self::MANTLE_USDT], + _ => vec![], + } + } + + pub fn to_swap_chain_asset(&self) -> Result { + let asset = match self { + // Ethereum + StargatePool::ETH_ETH => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_WETH.id.clone()])), + StargatePool::ETH_USDC => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_USDC.id.clone()])), + StargatePool::ETH_USDT => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_USDT.id.clone()])), + + // Base + StargatePool::BASE_ETH => Some(SwapChainAsset::Assets(Chain::Base, vec![BASE_WETH.id.clone()])), + StargatePool::BASE_USDC => Some(SwapChainAsset::Assets(Chain::Base, vec![BASE_USDC.id.clone()])), + + // Optimism + StargatePool::OPTIMISM_ETH => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_WETH.id.clone()])), + StargatePool::OPTIMISM_USDC => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDC.id.clone()])), + StargatePool::OPTIMISM_USDT => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDT.id.clone()])), + + // Arbitrum + StargatePool::ARBITRUM_ETH => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_WETH.id.clone()])), + StargatePool::ARBITRUM_USDC => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDC.id.clone()])), + StargatePool::ARBITRUM_USDT => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDT.id.clone()])), + + // Polygon + StargatePool::POLYGON_USDC => Some(SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDC.id.clone()])), + StargatePool::POLYGON_USDT => Some(SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDT.id.clone()])), + + // Avalanche + StargatePool::AVALANCHE_USDC => Some(SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDC.id.clone()])), + StargatePool::AVALANCHE_USDT => Some(SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDT.id.clone()])), + + // BNB (SmartChain) + StargatePool::BNB_USDC => Some(SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDC.id.clone()])), + StargatePool::BNB_USDT => Some(SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDT.id.clone()])), + + // Linea + StargatePool::LINEA_ETH => Some(SwapChainAsset::Assets(Chain::Linea, vec![LINEA_WETH.id.clone()])), + + // For chains/assets that are not yet supported in our asset constants + StargatePool::METIS_ETH + | StargatePool::METIS_USDT + | StargatePool::SCROLL_ETH + | StargatePool::SCROLL_USDC + | StargatePool::MANTLE_ETH + | StargatePool::MANTLE_USDC + | StargatePool::MANTLE_USDT => None, + }; + Ok(asset.ok_or(SwapperError::NotImplemented)?) + } +} diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index a7af426e..5eb79fd0 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -2,11 +2,11 @@ use std::str::FromStr; use std::sync::Arc; use alloy_core::sol_types::SolCall; -use alloy_primitives::{hex, Address, Bytes, FixedBytes, U160, U256, U32}; +use alloy_primitives::{hex, Address, Bytes, FixedBytes, U160, U256}; use async_trait::async_trait; use gem_evm::{ jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, - stargate::contract::{IRouter, MessagingFee, SendParam}, + stargate::contract::{IStargate, MessagingFee, SendParam}, }; use primitives::{AssetId, Chain}; use serde::{Deserialize, Serialize}; @@ -15,15 +15,16 @@ use crate::{ debug_println, network::{jsonrpc_call, AlienProvider, JsonRpcResult}, swapper::{ - approval::check_approval_erc20, - asset::{BASE_USDC, ETHEREUM_USDC, ETHEREUM_USDT, OPTIMISM_USDC}, - eth_rpc, - slippage::apply_slippage_in_bp, - ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, - SwapperError, + approval::check_approval_erc20, eth_rpc, slippage::apply_slippage_in_bp, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, + SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, }, }; +use super::{ + endpoint::{self, StargateEndpoint, STARGATE_ROUTES}, + layer_zero::scan::LayerZeroScanApi, +}; + #[derive(Debug, PartialEq, Serialize, Deserialize)] struct StargateRouteData { send_param: SendParam, @@ -32,34 +33,45 @@ struct StargateRouteData { } #[derive(Debug, Default)] -pub struct Stargate {} +pub struct Stargate { + pub enpoints: Vec, +} impl Stargate { - pub fn get_endpoint_id(&self, chain: &Chain) -> u32 { - match chain { - Chain::Ethereum => 30101u32, - Chain::Optimism => 30111u32, - Chain::Base => 30184u32, - _ => 0u32, + pub fn new() -> Self { + Self { + enpoints: vec![ + STARGATE_ROUTES.ethereum.clone(), + STARGATE_ROUTES.base.clone(), + STARGATE_ROUTES.optimism.clone(), + STARGATE_ROUTES.arbitrum.clone(), + STARGATE_ROUTES.polygon.clone(), + STARGATE_ROUTES.avalanche.clone(), + STARGATE_ROUTES.linea.clone(), + STARGATE_ROUTES.smartchain.clone(), + STARGATE_ROUTES.sei.clone(), + STARGATE_ROUTES.mantle.clone(), + ], } } + pub fn get_endpoint_id(&self, chain: &Chain) -> Result { + let endpoint = self.enpoints.iter().find(|x| x.id == *chain).ok_or(SwapperError::NotSupportedChain)?; + Ok(endpoint.endpoint_id) + } + pub fn address_to_bytes32(&self, addr: &str) -> FixedBytes<32> { FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) } - pub fn get_pool(&self, asset: &AssetId) -> Option { - match asset.chain { - Chain::Base => match &asset.token_id { - Some(token_id) => Some("0x27a16dc786820b16e5c9028b75b99f6f604b5d26".to_string()), - None => Some("0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string()), - }, - Chain::Optimism => match &asset.token_id { - Some(token_id) => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), - None => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), - }, - _ => None, - } + pub fn get_pool(&self, asset: &AssetId) -> Result { + let endpoint = self.enpoints.iter().find(|x| x.id == asset.chain).ok_or(SwapperError::NotSupportedChain)?; + endpoint + .pools + .iter() + .find(|x| x.asset == *asset) + .map(|x| x.address.clone()) + .ok_or(SwapperError::NotSupportedChain) } } @@ -70,22 +82,32 @@ impl GemSwapProvider for Stargate { } fn supported_assets(&self) -> Vec { - Chain::all() - .iter() - .map(|chain| match chain { - Chain::Base => SwapChainAsset::Assets(chain.clone(), vec![BASE_USDC.id.clone()]), - Chain::Optimism => SwapChainAsset::Assets(chain.clone(), vec![OPTIMISM_USDC.id.clone()]), - _ => SwapChainAsset::Assets(chain.clone(), vec![]), - }) - .collect() + let mut assets = vec![]; + for endpoint in self.enpoints.iter() { + assets.push(endpoint.chain_asset.clone()); + } + + println!("assets: {:?}", assets); + assets } async fn fetch_quote(&self, request: &SwapQuoteRequest, provider: Arc) -> Result { println!("request: {:?}", request); - let pool = self.get_pool(&request.from_asset).unwrap(); + let from_asset = &request.from_asset; + let to_asset = &request.to_asset; + + if from_asset.chain == to_asset.chain { + return Err(SwapperError::NotSupportedChain); + } + + if from_asset.is_native() && !to_asset.is_native() { + return Err(SwapperError::NotSupportedChain); + } + + let pool = self.get_pool(from_asset).unwrap(); let amount_ld = U256::from_str(request.value.as_str()).unwrap(); let mut send_param = SendParam { - dstEid: self.get_endpoint_id(&request.to_asset.chain), + dstEid: self.get_endpoint_id(&to_asset.chain).unwrap(), to: self.address_to_bytes32(request.destination_address.as_str()), amountLD: amount_ld, minAmountLD: amount_ld, @@ -99,7 +121,7 @@ impl GemSwapProvider for Stargate { println!("send_param: {:?}", send_param); // Encode call data - let call_data = IRouter::quoteOFTCall { + let call_data = IStargate::quoteOFTCall { _sendParam: send_param.clone(), } .abi_encode(); @@ -108,14 +130,14 @@ impl GemSwapProvider for Stargate { let response: JsonRpcResult = jsonrpc_call(&call, provider.clone(), &request.from_asset.chain).await?; let result = response.take()?; let hex_data = hex::decode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - let quote_oft_data = IRouter::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + let quote_oft_data = IStargate::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; println!("quote oft - {:?}", quote_oft_data); //println!("feeAmount = {}", quote_oft_data.oftFeeDetails[0].feeAmountLD); send_param.minAmountLD = apply_slippage_in_bp("e_oft_data.receipt.amountReceivedLD, request.options.slippage_bps); //send_param.minAmountLD = U256::from(99500u32); - let messaging_fee_calldata = IRouter::quoteSendCall { + let messaging_fee_calldata = IStargate::quoteSendCall { _sendParam: send_param.clone(), _payInLzToken: false, } @@ -128,7 +150,7 @@ impl GemSwapProvider for Stargate { println!("messagingFee eth_call result: {:?}", messaging_fee_hex_data); let messaging_fee_value = - IRouter::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + IStargate::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; println!("messagingFee = {:?}", messaging_fee_value); let approval = if request.from_asset.is_token() { @@ -151,6 +173,8 @@ impl GemSwapProvider for Stargate { refund_address: request.wallet_address.to_string(), }; + println!("route_data: {:?}", route_data); + Ok(SwapQuote { from_value: request.value.clone(), to_value: quote_oft_data.receipt.amountReceivedLD.to_string(), @@ -172,14 +196,12 @@ impl GemSwapProvider for Stargate { async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, data: FetchQuoteData) -> Result { let pool = self.get_pool("e.request.from_asset).unwrap(); let route_data: StargateRouteData = serde_json::from_str("e.data.routes.first().unwrap().route_data).map_err(|_| SwapperError::InvalidRoute)?; - let send_calldata = IRouter::sendCall { + let send_calldata = IStargate::sendCall { _sendParam: route_data.send_param.clone(), _fee: route_data.fee.clone(), _refundAddress: Address::from_str(route_data.refund_address.as_str()).unwrap(), } .abi_encode(); - // - //let send_calldata = hex::decode("0xc7c7f5b3000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000e021ca97a2f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf24000000000000000000000000000000000000000000000000000000000000759f0000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf2400000000000000000000000000000000000000000000000000000000000186a000000000000000000000000000000000000000000000000000000000000184ac00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); println!("Route data - {:?}", route_data); println!("Calldata - {:?}", send_calldata); @@ -209,240 +231,37 @@ impl GemSwapProvider for Stargate { } async fn get_transaction_status(&self, _chain: Chain, _transaction_hash: &str, _provider: Arc) -> Result { - todo!() + let api = LayerZeroScanApi::new(_provider.clone()); + let response = api.get_message_by_tx(_transaction_hash).await?; + let messages = response.data; + let message = messages.first().ok_or(SwapperError::NetworkError { + msg: "Unable to check transaction status using Stargate Provider: No message found".into(), + })?; + Ok(message.status.is_delivered()) } } #[cfg(test)] mod tests { - use crate::{ - network::{provider::AlienProvider, target::*, *}, - swapper::SwapperError, - }; - use alloy_core::{ - hex::{decode as HexDecode, encode_prefixed as HexEncode}, - primitives::{Address, Bytes, FixedBytes, U160, U256}, - sol_types::{SolCall, SolValue}, - }; - use alloy_primitives::utils::parse_units; - use async_trait::async_trait; - use futures::TryFutureExt; - use gem_evm::{ - jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, - stargate::contract::{ - IRouter::{self, IRouterCalls}, - LzTxObj, SendParam, - }, - }; - use primitives::{Asset, AssetId, Chain}; - use reqwest::Client; - use std::{collections::HashMap, sync::Arc}; - use super::*; - #[derive(Debug)] - pub struct NativeProvider { - pub node_config: HashMap, - pub client: Client, - } - - impl NativeProvider { - pub fn new(node_config: HashMap) -> Self { - Self { - node_config, - client: Client::new(), - } - } - } - - #[async_trait] - impl AlienProvider for NativeProvider { - fn get_endpoint(&self, chain: Chain) -> Result { - Ok(self - .node_config - .get(&chain) - .ok_or(AlienError::ResponseError { - msg: "not supported chain".into(), - })? - .to_string()) - } - - async fn request(&self, target: AlienTarget) -> Result { - println!("==> request: url: {:?}, method: {:?}", target.url, target.method); - let mut req = match target.method { - AlienHttpMethod::Get => self.client.get(target.url), - AlienHttpMethod::Post => self.client.post(target.url), - AlienHttpMethod::Put => self.client.put(target.url), - AlienHttpMethod::Delete => self.client.delete(target.url), - AlienHttpMethod::Head => self.client.head(target.url), - AlienHttpMethod::Patch => self.client.patch(target.url), - AlienHttpMethod::Options => todo!(), - }; - if let Some(headers) = target.headers { - for (key, value) in headers.iter() { - req = req.header(key, value); - } - } - if let Some(body) = target.body { - println!("==> request body size: {:?}", body.len()); - println!("==> request body: {:?}", String::from_utf8(body.clone()).unwrap()); - req = req.body(body); - } - - let response = req - .send() - .map_err(|e| AlienError::ResponseError { - msg: format!("reqwest send error: {:?}", e), - }) - .await?; - let bytes = response - .bytes() - .map_err(|e| AlienError::ResponseError { - msg: format!("request error: {:?}", e), - }) - .await?; - - println!("<== response body size: {:?}", bytes.len()); - Ok(bytes.to_vec()) - } - - async fn batch_request(&self, targets: Vec) -> Result, AlienError> { - let mut futures = vec![]; - for target in targets.iter() { - let future = self.request(target.clone()); - futures.push(future); - } - let responses = futures::future::join_all(futures).await; - let error = responses.iter().find_map(|x| x.as_ref().err()); - if let Some(err) = error { - return Err(err.clone()); - } - let responses = responses.into_iter().filter_map(|x| x.ok()).collect(); - Ok(responses) - } - } - - fn address_to_bytes32(addr: &str) -> FixedBytes<32> { - FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) - } - - #[tokio::test] - async fn test_swap_usdc_base_to_usdc_op() -> Result<(), SwapperError> { - let node_config = HashMap::from([(Chain::Base, "https://mainnet.base.org".to_string())]); - let network_provider = Arc::new(NativeProvider::new(node_config)); - - let op_dst_eid = 30111u32; - let amount_ld = U256::from(1_000_000_000u64); - - let mut send_param = SendParam { - dstEid: op_dst_eid, - to: address_to_bytes32("0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24"), - amountLD: amount_ld, - minAmountLD: amount_ld, - extraOptions: Bytes::from_str("0x").unwrap(), - composeMsg: Bytes::from_str("0x").unwrap(), - oftCmd: Bytes::from_str("0x").unwrap(), - }; - println!("send_param: {:?}", send_param); - - // Encode call data - let call_data = IRouter::quoteOFTCall { - _sendParam: send_param.clone(), - } - .abi_encode(); - - let call = EthereumRpc::Call( - TransactionObject::new_call("0x27a16dc786820b16e5c9028b75b99f6f604b5d26", call_data), - BlockParameter::Latest, - ); - let response: JsonRpcResult = jsonrpc_call(&call, network_provider.clone(), &Chain::Base).await?; - let result = response.take()?; - let hex_data = HexDecode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - println!("quoteLayerZeroFee eth_call result: {:?}", hex_data); - - let value = IRouter::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; - - println!("nativeFee = {}", value.receipt.amountSentLD); - println!("zroFee = {}", value.receipt.amountReceivedLD); - println!("feeAmount = {}", value.oftFeeDetails[0].feeAmountLD); - send_param.minAmountLD = value.receipt.amountSentLD; - - let messaging_fee_calldata = IRouter::quoteSendCall { - _sendParam: send_param.clone(), - _payInLzToken: false, - } - .abi_encode(); - - let messaging_fee_call = EthereumRpc::Call( - TransactionObject::new_call("0x27a16dc786820b16e5c9028b75b99f6f604b5d26", messaging_fee_calldata), - BlockParameter::Latest, + #[test] + fn should_contain_all_endpoints() { + let stargate = Stargate::new(); + assert_eq!( + stargate.enpoints, + vec![ + STARGATE_ROUTES.ethereum.clone(), + STARGATE_ROUTES.base.clone(), + STARGATE_ROUTES.optimism.clone(), + STARGATE_ROUTES.arbitrum.clone(), + STARGATE_ROUTES.polygon.clone(), + STARGATE_ROUTES.avalanche.clone(), + STARGATE_ROUTES.linea.clone(), + STARGATE_ROUTES.smartchain.clone(), + STARGATE_ROUTES.sei.clone(), + STARGATE_ROUTES.mantle.clone(), + ] ); - let messaging_fee_response: JsonRpcResult = jsonrpc_call(&messaging_fee_call, network_provider.clone(), &Chain::Base).await?; - let messaging_fee_result = messaging_fee_response.take()?; - let messaging_fee_hex_data = HexDecode(messaging_fee_result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - println!("messagingFee eth_call result: {:?}", messaging_fee_hex_data); - - let messaging_fee_value = - IRouter::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; - println!("messagingFee amountSentLD = {}", messaging_fee_value.fee.nativeFee); - println!("messagingFee amountReceivedLD = {}", messaging_fee_value.fee.lzTokenFee); - // - // -------------------------------------------------- - // 2) swap(...) via signed raw transaction - // -------------------------------------------------- - // Hypothetical pool IDs for USDC(Base)->USDC(OP) - //let src_pool_id = U256::from(1); - //let dst_pool_id = U256::from(2); - //let amount_ld = U256::from(50_000_000u64); // 50 USDC - //let min_amount_ld = U256::from(49_000_000u64); - // - //// Refund address - //let refund_address = Address::from_slice(&hex_decode("0000000000000000000000000123456789abCDef0123456789AbCdef01234567").unwrap()); - - //let swap_data = IRouter::swapCall { - // _dstChainId: dst_chain_id, - // _srcPoolId: src_pool_id, - // _dstPoolId: dst_pool_id, - // _refundAddress: - // _amountLD: amount_ld, - // _minAmountLD: min_amount_ld, - // _lzTxParams: lz_obj.clone(), - // _to: to_addr_bytes.clone(), - // _payload: payload.clone(), - //} - //.abi_encode(); - // - //// We need nonce & gasPrice - //// Derive "from" address from the private key if you want to do an actual state-changing tx - //// For brevity, let's just assume we know the address: - //let from_addr_hex = "0x0123456789abCDef0123456789abcDEF012345678"; - //let nonce = get_transaction_count(&client, rpc_url, from_addr_hex).await?; - //let gas_price_biguint = get_gas_price(&client, rpc_url).await?; - //println!("nonce = {}, gasPrice = {}", nonce, gas_price_biguint); - // - //let nonce_u256 = U256::from(nonce); - //let gas_price_u256 = U256::from(gas_price_biguint); - //let gas_limit = U256::from(2_000_000u64); - // - //// The bridging fee is paid in Base ETH, so transaction "value" = `native_fee` - //let tx_value = native_fee; - // - //// Sign a LEGACY TX (many chains use EIP-1559, but this is just a demonstration) - //let raw_tx = sign_legacy_tx( - // chain_id, - // nonce_u256, - // gas_price_u256, - // gas_limit, - // router_addr_20, - // tx_value, - // swap_data, - // &signing_key, - //); - // - //// Send raw TX - //let tx_hash = send_raw_transaction(&client, rpc_url, raw_tx).await?; - //println!("swap() transaction submitted! txHash = {}", tx_hash); - - Ok(()) } } From 0680931310776afa8fb765b1418f5f25d999e346 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 11:29:30 +0200 Subject: [PATCH 03/16] chore: remove unused files --- core | 1 - gemstone/src/swapper/stargate/mod.rs | 1 - gemstone/src/swapper/stargate/pool.rs | 218 -------------------------- 3 files changed, 220 deletions(-) delete mode 120000 core delete mode 100644 gemstone/src/swapper/stargate/pool.rs diff --git a/core b/core deleted file mode 120000 index 5c1a68f3..00000000 --- a/core +++ /dev/null @@ -1 +0,0 @@ -../gem-ios/core \ No newline at end of file diff --git a/gemstone/src/swapper/stargate/mod.rs b/gemstone/src/swapper/stargate/mod.rs index 070aa4e8..1b9bc1fc 100644 --- a/gemstone/src/swapper/stargate/mod.rs +++ b/gemstone/src/swapper/stargate/mod.rs @@ -1,5 +1,4 @@ mod endpoint; mod layer_zero; -mod pool; pub mod provider; pub use provider::Stargate; diff --git a/gemstone/src/swapper/stargate/pool.rs b/gemstone/src/swapper/stargate/pool.rs deleted file mode 100644 index 7617fb68..00000000 --- a/gemstone/src/swapper/stargate/pool.rs +++ /dev/null @@ -1,218 +0,0 @@ -use primitives::Chain; - -use crate::swapper::{ - asset::{ - ARBITRUM_USDC, ARBITRUM_USDT, ARBITRUM_WETH, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, BASE_WETH, ETHEREUM_USDC, ETHEREUM_USDT, ETHEREUM_WETH, - LINEA_WETH, OPTIMISM_USDC, OPTIMISM_USDT, OPTIMISM_WETH, POLYGON_USDC, POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, - }, - SwapChainAsset, SwapperError, -}; - -#[derive(Debug, PartialEq)] -pub enum StargatePool { - // ETH - ETH_ETH, - ETH_USDC, - ETH_USDT, - - // BASE - BASE_ETH, - BASE_USDC, - // Note: BASE has no USDT pool - - // OPTIMISM - OPTIMISM_ETH, - OPTIMISM_USDC, - OPTIMISM_USDT, - - // ARBITRUM - ARBITRUM_ETH, - ARBITRUM_USDC, - ARBITRUM_USDT, - - // POLYGON - POLYGON_USDC, - POLYGON_USDT, - // Note: POLYGON has no native ETH pool - - // AVALANCHE - AVALANCHE_USDC, - AVALANCHE_USDT, - // Note: AVALANCHE has no native ETH pool - - // METIS - METIS_ETH, - METIS_USDT, - // Note: METIS has no USDC pool - - // BNB - BNB_USDC, - BNB_USDT, - // Note: BNB has no native ETH pool - - // SCROLL - SCROLL_ETH, - SCROLL_USDC, - - // LINEA - LINEA_ETH, - - // MANTLE - MANTLE_ETH, - MANTLE_USDC, - MANTLE_USDT, -} - -impl StargatePool { - pub fn to_stargate_contract_address(&self) -> Result { - let address = match self { - // Ethereum - StargatePool::ETH_ETH => Some("0x77b2043768d28E9C9aB44E1aBfC95944bcE57931".to_string()), - StargatePool::ETH_USDC => Some("0xc026395860Db2d07ee33e05fE50ed7bD583189C7".to_string()), - StargatePool::ETH_USDT => Some("0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string()), - - // Base - StargatePool::BASE_ETH => Some("0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string()), - StargatePool::BASE_USDC => Some("0x27a16dc786820B16E5c9028b75B99F6f604b5d26".to_string()), - - // Optimism - StargatePool::OPTIMISM_ETH => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), - StargatePool::OPTIMISM_USDC => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), - StargatePool::OPTIMISM_USDT => Some("0x19cFCE47eD54a88614648DC3f19A5980097007dD".to_string()), - - // Arbitrum - StargatePool::ARBITRUM_ETH => Some("0xA45B5130f36CDcA45667738e2a258AB09f4A5f7F".to_string()), - StargatePool::ARBITRUM_USDC => Some("0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string()), - StargatePool::ARBITRUM_USDT => Some("0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string()), - - // Polygon - StargatePool::POLYGON_USDC => Some("0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4".to_string()), - StargatePool::POLYGON_USDT => Some("0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7".to_string()), - - // Avalanche - StargatePool::AVALANCHE_USDC => Some("0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47".to_string()), - StargatePool::AVALANCHE_USDT => Some("0x12dC9256Acc9895B076f6638D628382881e62CeE".to_string()), - - // Metis - StargatePool::METIS_ETH => Some("0x36ed193dc7160D3858EC250e69D12B03Ca087D08".to_string()), - StargatePool::METIS_USDT => Some("0x4dCBFC0249e8d5032F89D6461218a9D2eFff5125".to_string()), - - // BNB Chain - StargatePool::BNB_USDC => Some("0x962Bd449E630b0d928f308Ce63f1A21F02576057".to_string()), - StargatePool::BNB_USDT => Some("0x138EB30f73BC423c6455C53df6D89CB01d9eBc63".to_string()), - - // Scroll - StargatePool::SCROLL_ETH => Some("0xC2b638Cb5042c1B3c5d5C969361fB50569840583".to_string()), - StargatePool::SCROLL_USDC => Some("0x3Fc69CC4A842838bCDC9499178740226062b14E4".to_string()), - - // Linea - StargatePool::LINEA_ETH => Some("0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string()), - - // Mantle - StargatePool::MANTLE_ETH => Some("0x4c1d3Fc3fC3c177c3b633427c2F769276c547463".to_string()), - StargatePool::MANTLE_USDC => Some("0xAc290Ad4e0c891FDc295ca4F0a6214cf6dC6acDC".to_string()), - StargatePool::MANTLE_USDT => Some("0xB715B85682B731dB9D5063187C450095c91C57FC".to_string()), - }; - Ok(address.ok_or(SwapperError::NotImplemented)?) - } - - pub fn endpoint_id(&self) -> u32 { - match self { - // Ethereum (all pools) - StargatePool::ETH_ETH | StargatePool::ETH_USDC | StargatePool::ETH_USDT => 30101, - - // Base (all pools) - StargatePool::BASE_ETH | StargatePool::BASE_USDC => 30184, - - // Optimism (all pools) - StargatePool::OPTIMISM_ETH | StargatePool::OPTIMISM_USDC | StargatePool::OPTIMISM_USDT => 30111, - - // Arbitrum (all pools) - StargatePool::ARBITRUM_ETH | StargatePool::ARBITRUM_USDC | StargatePool::ARBITRUM_USDT => 30110, - - // Polygon (all pools) - StargatePool::POLYGON_USDC | StargatePool::POLYGON_USDT => 30109, - - // Avalanche (all pools) - StargatePool::AVALANCHE_USDC | StargatePool::AVALANCHE_USDT => 30106, - - // Metis (all pools) - StargatePool::METIS_ETH | StargatePool::METIS_USDT => 30151, - - // BNB (all pools) - StargatePool::BNB_USDC | StargatePool::BNB_USDT => 30102, - - // Scroll (all pools) - StargatePool::SCROLL_ETH | StargatePool::SCROLL_USDC => 30214, - - // Linea (all pools) - StargatePool::LINEA_ETH => 30183, - - // Mantle (all pools) - StargatePool::MANTLE_ETH | StargatePool::MANTLE_USDC | StargatePool::MANTLE_USDT => 30181, - } - } - - pub fn from_chain(chain: &Chain) -> Vec { - match chain { - Chain::Ethereum => vec![Self::ETH_ETH, Self::ETH_USDC, Self::ETH_USDT], - Chain::Base => vec![Self::BASE_ETH, Self::BASE_USDC], - Chain::Optimism => vec![Self::OPTIMISM_ETH, Self::OPTIMISM_USDC, Self::OPTIMISM_USDT], - Chain::Arbitrum => vec![Self::ARBITRUM_ETH, Self::ARBITRUM_USDC, Self::ARBITRUM_USDT], - Chain::Polygon => vec![Self::POLYGON_USDC, Self::POLYGON_USDT], - Chain::AvalancheC => vec![Self::AVALANCHE_USDC, Self::AVALANCHE_USDT], - Chain::SmartChain => vec![Self::BNB_USDC, Self::BNB_USDT], - Chain::Linea => vec![Self::LINEA_ETH], - Chain::Mantle => vec![Self::MANTLE_ETH, Self::MANTLE_USDC, Self::MANTLE_USDT], - _ => vec![], - } - } - - pub fn to_swap_chain_asset(&self) -> Result { - let asset = match self { - // Ethereum - StargatePool::ETH_ETH => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_WETH.id.clone()])), - StargatePool::ETH_USDC => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_USDC.id.clone()])), - StargatePool::ETH_USDT => Some(SwapChainAsset::Assets(Chain::Ethereum, vec![ETHEREUM_USDT.id.clone()])), - - // Base - StargatePool::BASE_ETH => Some(SwapChainAsset::Assets(Chain::Base, vec![BASE_WETH.id.clone()])), - StargatePool::BASE_USDC => Some(SwapChainAsset::Assets(Chain::Base, vec![BASE_USDC.id.clone()])), - - // Optimism - StargatePool::OPTIMISM_ETH => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_WETH.id.clone()])), - StargatePool::OPTIMISM_USDC => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDC.id.clone()])), - StargatePool::OPTIMISM_USDT => Some(SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDT.id.clone()])), - - // Arbitrum - StargatePool::ARBITRUM_ETH => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_WETH.id.clone()])), - StargatePool::ARBITRUM_USDC => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDC.id.clone()])), - StargatePool::ARBITRUM_USDT => Some(SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDT.id.clone()])), - - // Polygon - StargatePool::POLYGON_USDC => Some(SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDC.id.clone()])), - StargatePool::POLYGON_USDT => Some(SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDT.id.clone()])), - - // Avalanche - StargatePool::AVALANCHE_USDC => Some(SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDC.id.clone()])), - StargatePool::AVALANCHE_USDT => Some(SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDT.id.clone()])), - - // BNB (SmartChain) - StargatePool::BNB_USDC => Some(SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDC.id.clone()])), - StargatePool::BNB_USDT => Some(SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDT.id.clone()])), - - // Linea - StargatePool::LINEA_ETH => Some(SwapChainAsset::Assets(Chain::Linea, vec![LINEA_WETH.id.clone()])), - - // For chains/assets that are not yet supported in our asset constants - StargatePool::METIS_ETH - | StargatePool::METIS_USDT - | StargatePool::SCROLL_ETH - | StargatePool::SCROLL_USDC - | StargatePool::MANTLE_ETH - | StargatePool::MANTLE_USDC - | StargatePool::MANTLE_USDT => None, - }; - Ok(asset.ok_or(SwapperError::NotImplemented)?) - } -} From 55ae06d063af1e0359f01c962fe74e2ef2bed251 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 11:31:17 +0200 Subject: [PATCH 04/16] chore: removed unused assets and chains --- gemstone/src/swapper/asset.rs | 53 --------- gemstone/src/swapper/stargate/endpoint.rs | 133 ++-------------------- 2 files changed, 8 insertions(+), 178 deletions(-) diff --git a/gemstone/src/swapper/asset.rs b/gemstone/src/swapper/asset.rs index 87de49b5..2f3e0b72 100644 --- a/gemstone/src/swapper/asset.rs +++ b/gemstone/src/swapper/asset.rs @@ -10,8 +10,6 @@ const WBTC_NAME: &str = "Wrapped BTC"; const DAI_SYMBOL: &str = "DAI"; const WETH_NAME: &str = "Wrapped Ether"; const WETH_SYMBOL: &str = "WETH"; -const METH_NAME: &str = "mETH (mETH)"; -const METH_SYMBOL: &str = "mETH"; const CBBTC_NAME: &str = "Coinbase BTC"; const CBBTC_SYMBOL: &str = "cbBTC"; @@ -27,12 +25,6 @@ pub const AVALANCHE_USDT_TOKEN_ID: &str = "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF pub const AVALANCHE_USDC_TOKEN_ID: &str = "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"; pub const BASE_USDC_TOKEN_ID: &str = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"; pub const BASE_CBBTC_TOKEN_ID: &str = "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf"; -pub const MANTLE_USDC_TOKEN_ID: &str = "0x09bc4e0d864854c6afb6eb9a9cdf58ac190d0df9"; -pub const MANTLE_USDT_TOKEN_ID: &str = "0x201eba5cc46d216ce6dc03f6a759e8e766e956ae"; -pub const MANTLE_WETH_TOKEN_ID: &str = "0xdeaddeaddeaddeaddeaddeaddeaddeaddead1111"; -pub const MANTLE_METH_TOKEN_ID: &str = "0xcda86a272531e8640cd7f1a92c01839911b90bb0"; -pub const SEI_USDC_TOKEN_ID: &str = "0x3894085Ef7Ff0f0aeDf52E2A2704928d1Ec074F1"; -pub const SEI_USDT_TOKEN_ID: &str = "0x0dB9afb4C33be43a0a0e396Fd1383B4ea97aB10a"; lazy_static! { // ethereum @@ -242,50 +234,5 @@ lazy_static! { decimals: 6, asset_type: AssetType::ERC20, }; - // mantle - pub static ref MANTLE_USDC: Asset = Asset { - id: AssetId::from_token(Chain::Mantle, MANTLE_USDC_TOKEN_ID), - name: USDC_NAME.to_owned(), - symbol: USDC_SYMBOL.to_owned(), - decimals: 6, - asset_type: AssetType::ERC20, - }; - pub static ref MANTLE_USDT: Asset = Asset { - id: AssetId::from_token(Chain::Mantle, MANTLE_USDT_TOKEN_ID), - name: USDT_NAME.to_owned(), - symbol: USDT_SYMBOL.to_owned(), - decimals: 6, - asset_type: AssetType::ERC20, - }; - pub static ref MANTLE_WETH: Asset = Asset { - id: AssetId::from_token(Chain::Mantle, MANTLE_WETH_TOKEN_ID), - name: WETH_NAME.to_owned(), - symbol: WETH_SYMBOL.to_owned(), - decimals: 18, - asset_type: AssetType::ERC20, - }; - pub static ref MANTLE_METH: Asset = Asset { - id: AssetId::from_token(Chain::Mantle, MANTLE_METH_TOKEN_ID), - name: METH_NAME.to_owned(), - symbol: METH_SYMBOL.to_owned(), - decimals: 18, - asset_type: AssetType::ERC20, - }; - - // sei - pub static ref SEI_USDC: Asset = Asset { - id: AssetId::from_token(Chain::Sei, SEI_USDC_TOKEN_ID), - name: USDC_NAME.to_owned(), - symbol: USDC_SYMBOL.to_owned(), - decimals: 6, - asset_type: AssetType::ERC20, - }; - pub static ref SEI_USDT: Asset = Asset { - id: AssetId::from_token(Chain::Sei, SEI_USDT_TOKEN_ID), - name: USDT_NAME.to_owned(), - symbol: USDT_SYMBOL.to_owned(), - decimals: 6, - asset_type: AssetType::ERC20, - }; } diff --git a/gemstone/src/swapper/stargate/endpoint.rs b/gemstone/src/swapper/stargate/endpoint.rs index 2448de26..2f23800a 100644 --- a/gemstone/src/swapper/stargate/endpoint.rs +++ b/gemstone/src/swapper/stargate/endpoint.rs @@ -34,13 +34,6 @@ pub struct StargateRoutes { pub avalanche: StargateEndpoint, pub linea: StargateEndpoint, pub smartchain: StargateEndpoint, - pub sei: StargateEndpoint, - //pub metis: StargateEndpoint, - //pub scroll: StargateEndpoint, - pub mantle: StargateEndpoint, - //pub kava: StargateEndpoint, - //pub aurora: StargateEndpoint, - //pub core: StargateEndpoint, } lazy_static! { @@ -68,7 +61,12 @@ lazy_static! { endpoint_id: 30101, chain_asset: SwapChainAsset::Assets( Chain::Ethereum, - vec![AssetId::from_chain(Chain::Ethereum), ETHEREUM_USDC.id.clone(), ETHEREUM_USDT.id.clone(), ETHEREUM_METH.id.clone(),] + vec![ + AssetId::from_chain(Chain::Ethereum), + ETHEREUM_USDC.id.clone(), + ETHEREUM_USDT.id.clone(), + ETHEREUM_METH.id.clone(), + ] ), }, base: StargateEndpoint { @@ -103,10 +101,7 @@ lazy_static! { }, ], endpoint_id: 30111, - chain_asset: SwapChainAsset::Assets( - Chain::Optimism, - vec![OPTIMISM_USDC.id.clone(), OPTIMISM_USDT.id.clone(),] - ), + chain_asset: SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDC.id.clone(), OPTIMISM_USDT.id.clone(),]), }, arbitrum: StargateEndpoint { id: Chain::Arbitrum, @@ -125,10 +120,7 @@ lazy_static! { }, ], endpoint_id: 30110, - chain_asset: SwapChainAsset::Assets( - Chain::Arbitrum, - vec![ARBITRUM_USDC.id.clone(), ARBITRUM_USDT.id.clone(),] - ), + chain_asset: SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDC.id.clone(), ARBITRUM_USDT.id.clone(),]), }, polygon: StargateEndpoint { id: Chain::Polygon, @@ -184,114 +176,5 @@ lazy_static! { endpoint_id: 30102, chain_asset: SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDC.id.clone(), SMARTCHAIN_USDT.id.clone(),]), }, - sei: StargateEndpoint { - id: Chain::Sei, - pools: vec![ - StargatePool { - asset: AssetId::from_chain(Chain::Sei), - address: "0x5c386D85b1B82FD9Db681b9176C8a4248bb6345B".to_string(), - }, - StargatePool { - asset: SEI_USDC.id.clone(), - address: "0x45d417612e177672958dC0537C45a8f8d754Ac2E".to_string(), - }, - StargatePool { - asset: SEI_USDT.id.clone(), - address: "0x0dB9afb4C33be43a0a0e396Fd1383B4ea97aB10a".to_string(), - }, - ], - endpoint_id: 30280, - chain_asset: SwapChainAsset::Assets(Chain::Sei, vec![ - SEI_USDC.id.clone(), - SEI_USDT.id.clone(), - ]), - }, - - //metis: StargateEndpoint { - // id: Chain::Metis, - // pools: vec![ - // StargatePool { - // asset: AssetId::from_chain(Chain::Metis), - // address: "0x36ed193dc7160D3858EC250e69D12B03Ca087D08".to_string(), - // }, - // StargatePool { - // asset: AssetId::from_token(Chain::Metis, "0x0"), - // address: "0x4dCBFC0249e8d5032F89D6461218a9D2eFff5125".to_string(), - // }, - // ], - // endpoint_id: 30151, - // chain_asset: SwapChainAsset::Assets(Chain::Metis, vec![]), - //}, - //scroll: StargateEndpoint { - // id: Chain::Scroll, - // pools: vec![ - // StargatePool { - // asset: AssetId::from_chain(Chain::Scroll), - // address: "0xC2b638Cb5042c1B3c5d5C969361fB50569840583".to_string(), - // }, - // StargatePool { - // asset: AssetId::from_token(Chain::Scroll, "0x0"), - // address: "0x3Fc69CC4A842838bCDC9499178740226062b14E4".to_string(), - // }, - // ], - // endpoint_id: 30214, - // chain_asset: SwapChainAsset::Assets(Chain::Scroll, vec![]), - //}, - mantle: StargateEndpoint { - id: Chain::Mantle, - pools: vec![ - StargatePool { - asset: AssetId::from_chain(Chain::Mantle), - address: "0x4c1d3Fc3fC3c177c3b633427c2F769276c547463".to_string(), - }, - StargatePool { - asset: MANTLE_USDC.id.clone(), - address: "0xAc290Ad4e0c891FDc295ca4F0a6214cf6dC6acDC".to_string(), - }, - StargatePool { - asset: MANTLE_USDT.id.clone(), - address: "0xB715B85682B731dB9D5063187C450095c91C57FC".to_string(), - }, - ], - endpoint_id: 30181, - chain_asset: SwapChainAsset::Assets(Chain::Mantle, vec![ - AssetId::from_chain(Chain::Mantle), - MANTLE_USDC.id.clone(), - MANTLE_USDT.id.clone(), - ]), - }, - //kava: StargateEndpoint { - // id: Chain::Kava, - // pools: vec![StargatePool { - // asset: AssetId::from_token(Chain::Kava, "0x0"), - // address: "0x41A5b0470D96656Fb3e8f68A218b39AdBca3420b".to_string(), - // },], - // endpoint_id: 30177, - // chain_asset: SwapChainAsset::Assets(Chain::Kava, vec![]), - //}, - //aurora: StargateEndpoint { - // id: Chain::Aurora, - // pools: vec![StargatePool { - // asset: AssetId::from_token(Chain::Aurora, "0x0"), - // address: "0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string(), - // },], - // endpoint_id: 30211, - // chain_asset: SwapChainAsset::Assets(Chain::Aurora, vec![]), - //}, - //core: StargateEndpoint { - // id: Chain::Core, - // pools: vec![ - // StargatePool { - // asset: AssetId::from_token(Chain::Core, "0x0"), - // address: "0x2F6F07CDcf3588944Bf4C42aC74ff24bF56e7590".to_string(), - // }, - // StargatePool { - // asset: AssetId::from_token(Chain::Core, "0x0"), - // address: "0x45f1A95A4D3f3836523F5c83673c797f4d4d263B".to_string(), - // }, - // ], - // endpoint_id: 30153, - // chain_asset: SwapChainAsset::Assets(Chain::Core, vec![]), - //}, }; } From a9ba9fc4296ca5ae695fb36725a3e1f56d65e3ef Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 11:33:54 +0200 Subject: [PATCH 05/16] chore: removed unused assets --- gemstone/src/swapper/asset.rs | 7 ------- gemstone/src/swapper/stargate/endpoint.rs | 18 ++++-------------- gemstone/src/swapper/stargate/provider.rs | 4 ---- 3 files changed, 4 insertions(+), 25 deletions(-) diff --git a/gemstone/src/swapper/asset.rs b/gemstone/src/swapper/asset.rs index 2f3e0b72..33a6c8a9 100644 --- a/gemstone/src/swapper/asset.rs +++ b/gemstone/src/swapper/asset.rs @@ -63,13 +63,6 @@ lazy_static! { decimals: 18, asset_type: AssetType::ERC20, }; - pub static ref ETHEREUM_METH: Asset = Asset { - id: AssetId::from_token(Chain::Ethereum, ETHEREUM_METH_TOKEN_ID), - name: METH_NAME.into(), - symbol: METH_SYMBOL.into(), - decimals: 18, - asset_type: AssetType::ERC20, - }; // arbitrum pub static ref ARBITRUM_WETH: Asset = Asset { id: WETH_ARB_ASSET_ID.into(), diff --git a/gemstone/src/swapper/stargate/endpoint.rs b/gemstone/src/swapper/stargate/endpoint.rs index 2f23800a..d1bf95ff 100644 --- a/gemstone/src/swapper/stargate/endpoint.rs +++ b/gemstone/src/swapper/stargate/endpoint.rs @@ -3,11 +3,10 @@ use primitives::{AssetId, Chain}; use crate::swapper::{ asset::{ - ARBITRUM_USDC, ARBITRUM_USDT, ARBITRUM_WETH, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, BASE_WETH, ETHEREUM_METH, ETHEREUM_USDC, ETHEREUM_USDT, - ETHEREUM_WETH, LINEA_WETH, MANTLE_USDC, MANTLE_USDT, OPTIMISM_USDC, OPTIMISM_USDT, OPTIMISM_WETH, POLYGON_USDC, POLYGON_USDT, SEI_USDC, SEI_USDT, - SMARTCHAIN_USDC, SMARTCHAIN_USDT, + ARBITRUM_USDC, ARBITRUM_USDT, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, ETHEREUM_USDC, ETHEREUM_USDT, OPTIMISM_USDC, OPTIMISM_USDT, POLYGON_USDC, + POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, }, - SwapChainAsset, SwapperError, + SwapChainAsset, }; #[derive(Clone, Debug, PartialEq)] @@ -53,20 +52,11 @@ lazy_static! { asset: ETHEREUM_USDT.id.clone(), address: "0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string(), }, - StargatePool { - asset: ETHEREUM_METH.id.clone(), - address: "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa".to_string(), - }, ], endpoint_id: 30101, chain_asset: SwapChainAsset::Assets( Chain::Ethereum, - vec![ - AssetId::from_chain(Chain::Ethereum), - ETHEREUM_USDC.id.clone(), - ETHEREUM_USDT.id.clone(), - ETHEREUM_METH.id.clone(), - ] + vec![AssetId::from_chain(Chain::Ethereum), ETHEREUM_USDC.id.clone(), ETHEREUM_USDT.id.clone(),] ), }, base: StargateEndpoint { diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index 5eb79fd0..fa6092bc 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -49,8 +49,6 @@ impl Stargate { STARGATE_ROUTES.avalanche.clone(), STARGATE_ROUTES.linea.clone(), STARGATE_ROUTES.smartchain.clone(), - STARGATE_ROUTES.sei.clone(), - STARGATE_ROUTES.mantle.clone(), ], } } @@ -259,8 +257,6 @@ mod tests { STARGATE_ROUTES.avalanche.clone(), STARGATE_ROUTES.linea.clone(), STARGATE_ROUTES.smartchain.clone(), - STARGATE_ROUTES.sei.clone(), - STARGATE_ROUTES.mantle.clone(), ] ); } From 9177f963c1820295e37875bec595edf13ab69a94 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 15:59:32 +0200 Subject: [PATCH 06/16] feat: added bnb assets decimals transformation and tests --- crates/gem_evm/src/stargate/contract.rs | 27 -- crates/primitives/src/asset.rs | 2 +- gemstone/src/swapper/stargate/endpoint.rs | 61 ++--- .../stargate/layer_zero/mock/message_tx.json | 80 ++++++ .../src/swapper/stargate/layer_zero/scan.rs | 252 +++--------------- gemstone/src/swapper/stargate/provider.rs | 125 ++++++--- gemstone/src/swapper/thorchain/mod.rs | 4 +- gemstone/tests/integration_test.rs | 42 +++ 8 files changed, 277 insertions(+), 316 deletions(-) create mode 100644 gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json diff --git a/crates/gem_evm/src/stargate/contract.rs b/crates/gem_evm/src/stargate/contract.rs index 2456e201..ce1be782 100644 --- a/crates/gem_evm/src/stargate/contract.rs +++ b/crates/gem_evm/src/stargate/contract.rs @@ -2,13 +2,6 @@ use alloy_core::sol; use serde::{Deserialize, Serialize}; sol! { - #[derive(Debug, PartialEq)] - struct LzTxObj { - uint256 dstGasForCall; - uint256 dstNativeAmount; - bytes dstNativeAddr; - } - /** * @dev Struct representing token parameters for the OFT send() operation. */ @@ -62,12 +55,6 @@ sol! { MessagingFee fee; } - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct Ticket { - uint56 ticketId; - bytes passenger; - } - #[derive(Debug, PartialEq)] interface IStargate { function quoteSend( @@ -114,19 +101,5 @@ sol! { MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt ); - - /** - * @dev This function is same as `send` in OFT interface but returns the ticket data if in the bus ride mode, - * which allows the caller to ride and drive the bus in the same transaction. - */ - function sendToken( - SendParam calldata _sendParam, - MessagingFee calldata _fee, - address _refundAddress - ) external payable returns ( - MessagingReceipt memory msgReceipt, - OFTReceipt memory oftReceipt, - Ticket memory ticket - ); } } diff --git a/crates/primitives/src/asset.rs b/crates/primitives/src/asset.rs index 5e048e37..87ffaf85 100644 --- a/crates/primitives/src/asset.rs +++ b/crates/primitives/src/asset.rs @@ -3,7 +3,7 @@ use typeshare::typeshare; use crate::{asset_id::AssetId, asset_type::AssetType, Chain}; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[typeshare(swift = "Sendable")] pub struct Asset { pub id: AssetId, diff --git a/gemstone/src/swapper/stargate/endpoint.rs b/gemstone/src/swapper/stargate/endpoint.rs index d1bf95ff..1f83a6d1 100644 --- a/gemstone/src/swapper/stargate/endpoint.rs +++ b/gemstone/src/swapper/stargate/endpoint.rs @@ -1,17 +1,14 @@ use lazy_static::lazy_static; -use primitives::{AssetId, Chain}; +use primitives::{Asset, Chain}; -use crate::swapper::{ - asset::{ - ARBITRUM_USDC, ARBITRUM_USDT, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, ETHEREUM_USDC, ETHEREUM_USDT, OPTIMISM_USDC, OPTIMISM_USDT, POLYGON_USDC, - POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, - }, - SwapChainAsset, +use crate::swapper::asset::{ + ARBITRUM_USDC, ARBITRUM_USDT, AVALANCHE_USDC, AVALANCHE_USDT, BASE_USDC, ETHEREUM_USDC, ETHEREUM_USDT, OPTIMISM_USDC, OPTIMISM_USDT, POLYGON_USDC, + POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, }; #[derive(Clone, Debug, PartialEq)] pub struct StargatePool { - pub asset: AssetId, + pub asset: Asset, pub address: String, } @@ -20,7 +17,6 @@ pub struct StargateEndpoint { pub id: Chain, pub pools: Vec, pub endpoint_id: u32, - pub chain_asset: SwapChainAsset, } #[derive(Clone, Debug)] @@ -41,130 +37,119 @@ lazy_static! { id: Chain::Ethereum, pools: vec![ StargatePool { - asset: AssetId::from_chain(Chain::Ethereum), + asset: Asset::from_chain(Chain::Ethereum), address: "0x77b2043768d28E9C9aB44E1aBfC95944bcE57931".to_string(), }, StargatePool { - asset: ETHEREUM_USDC.id.clone(), + asset: ETHEREUM_USDC.clone(), address: "0xc026395860Db2d07ee33e05fE50ed7bD583189C7".to_string(), }, StargatePool { - asset: ETHEREUM_USDT.id.clone(), + asset: ETHEREUM_USDT.clone(), address: "0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string(), }, ], endpoint_id: 30101, - chain_asset: SwapChainAsset::Assets( - Chain::Ethereum, - vec![AssetId::from_chain(Chain::Ethereum), ETHEREUM_USDC.id.clone(), ETHEREUM_USDT.id.clone(),] - ), }, base: StargateEndpoint { id: Chain::Base, pools: vec![ StargatePool { - asset: AssetId::from_chain(Chain::Base), + asset: Asset::from_chain(Chain::Base), address: "0xdc181Bd607330aeeBEF6ea62e03e5e1Fb4B6F7C7".to_string(), }, StargatePool { - asset: BASE_USDC.id.clone(), + asset: BASE_USDC.clone(), address: "0x27a16dc786820B16E5c9028b75B99F6f604b5d26".to_string(), }, ], endpoint_id: 30184, - chain_asset: SwapChainAsset::Assets(Chain::Base, vec![BASE_USDC.id.clone(),]), }, optimism: StargateEndpoint { id: Chain::Optimism, pools: vec![ StargatePool { - asset: AssetId::from_chain(Chain::Optimism), + asset: Asset::from_chain(Chain::Optimism), address: "0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string(), }, StargatePool { - asset: OPTIMISM_USDC.id.clone(), + asset: OPTIMISM_USDC.clone(), address: "0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string(), }, StargatePool { - asset: OPTIMISM_USDT.id.clone(), + asset: OPTIMISM_USDT.clone(), address: "0x19cFCE47eD54a88614648DC3f19A5980097007dD".to_string(), }, ], endpoint_id: 30111, - chain_asset: SwapChainAsset::Assets(Chain::Optimism, vec![OPTIMISM_USDC.id.clone(), OPTIMISM_USDT.id.clone(),]), }, arbitrum: StargateEndpoint { id: Chain::Arbitrum, pools: vec![ StargatePool { - asset: AssetId::from_chain(Chain::Arbitrum), + asset: Asset::from_chain(Chain::Arbitrum), address: "0xA45B5130f36CDcA45667738e2a258AB09f4A5f7F".to_string(), }, StargatePool { - asset: ARBITRUM_USDC.id.clone(), + asset: ARBITRUM_USDC.clone(), address: "0xe8CDF27AcD73a434D661C84887215F7598e7d0d3".to_string(), }, StargatePool { - asset: ARBITRUM_USDT.id.clone(), + asset: ARBITRUM_USDT.clone(), address: "0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string(), }, ], endpoint_id: 30110, - chain_asset: SwapChainAsset::Assets(Chain::Arbitrum, vec![ARBITRUM_USDC.id.clone(), ARBITRUM_USDT.id.clone(),]), }, polygon: StargateEndpoint { id: Chain::Polygon, pools: vec![ StargatePool { - asset: POLYGON_USDC.id.clone(), + asset: POLYGON_USDC.clone(), address: "0x9Aa02D4Fae7F58b8E8f34c66E756cC734DAc7fe4".to_string(), }, StargatePool { - asset: POLYGON_USDT.id.clone(), + asset: POLYGON_USDT.clone(), address: "0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7".to_string(), }, ], endpoint_id: 30109, - chain_asset: SwapChainAsset::Assets(Chain::Polygon, vec![POLYGON_USDC.id.clone(), POLYGON_USDT.id.clone(),]), }, avalanche: StargateEndpoint { id: Chain::AvalancheC, pools: vec![ StargatePool { - asset: AVALANCHE_USDC.id.clone(), + asset: AVALANCHE_USDC.clone(), address: "0x5634c4a5FEd09819E3c46D86A965Dd9447d86e47".to_string(), }, StargatePool { - asset: AVALANCHE_USDT.id.clone(), + asset: AVALANCHE_USDT.clone(), address: "0x12dC9256Acc9895B076f6638D628382881e62CeE".to_string(), }, ], endpoint_id: 30106, - chain_asset: SwapChainAsset::Assets(Chain::AvalancheC, vec![AVALANCHE_USDC.id.clone(), AVALANCHE_USDT.id.clone(),]), }, linea: StargateEndpoint { id: Chain::Linea, pools: vec![StargatePool { - asset: AssetId::from_chain(Chain::Linea), + asset: Asset::from_chain(Chain::Linea), address: "0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string(), },], endpoint_id: 30183, - chain_asset: SwapChainAsset::Assets(Chain::Linea, vec![AssetId::from_chain(Chain::Linea),]), }, smartchain: StargateEndpoint { id: Chain::SmartChain, pools: vec![ StargatePool { - asset: SMARTCHAIN_USDC.id.clone(), + asset: SMARTCHAIN_USDC.clone(), address: "0x962Bd449E630b0d928f308Ce63f1A21F02576057".to_string(), }, StargatePool { - asset: SMARTCHAIN_USDT.id.clone(), + asset: SMARTCHAIN_USDT.clone(), address: "0x138EB30f73BC423c6455C53df6D89CB01d9eBc63".to_string(), }, ], endpoint_id: 30102, - chain_asset: SwapChainAsset::Assets(Chain::SmartChain, vec![SMARTCHAIN_USDC.id.clone(), SMARTCHAIN_USDT.id.clone(),]), }, }; } diff --git a/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json b/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json new file mode 100644 index 00000000..0f41ad07 --- /dev/null +++ b/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json @@ -0,0 +1,80 @@ +{ + "data": [ + { + "pathway": { + "srcEid": 108, + "dstEid": 102, + "sender": { + "address": "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa", + "id": "aptos-bridge", + "name": "Aptos Bridge", + "chain": "aptos" + }, + "receiver": { + "address": "0x2762409baa1804d94d8c0bcff8400b78bf915d5b", + "id": "aptos-bridge", + "name": "Aptos Bridge", + "chain": "bsc" + }, + "id": "108-102-0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa-0x2762409baa1804d94d8c0bcff8400b78bf915d5b", + "nonce": 309792 + }, + "source": { + "status": "SUCCEEDED", + "tx": { + "txHash": "0xeb31b3339c4c1bd91597f649fe21ddac2900f301ada3181357a281608a560d47", + "blockHash": "0x722c2003341cce20a5a3d63babd413a1a278c4e943abe2de289cff4f763ec524", + "blockNumber": "275562775", + "blockTimestamp": 1736533130, + "from": "0xc95615aa095c100b18eb6eaa0f0a0f30b9cd96685118a7cbc1a2328a91ca2eda", + "blockConfirmations": 260, + "payload": "0x0100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000ba4d1d35bce0e8f28e5a3403e7a0b996c5d50ac400000000000186a000", + "value": "3870356", + "readinessTimestamp": 1736533186, + "adapterParams": { + "version": "1", + "dstGasLimit": "150000" + } + } + }, + "destination": { + "status": "SUCCEEDED", + "tx": { + "txHash": "0xede2232a3ac249781197fe87281a03f5f6b38ff0e20253e0708db9e1b7ba8391", + "blockHash": "0x3c2023c675a67ef52c8b3577f8653a01dfec1b2f4d519da9bffc2075d585c904", + "blockNumber": 45650646, + "blockTimestamp": 1736533201 + } + }, + "guid": "0x325f35036d836baeac547cd4bdafe838d1c5897fe63a2392e98a6cdbd584b4c7", + "config": { + "error": false, + "receiveLibrary": "0x4D73AdB72bC3DD368966edD0f0b2148401A178E2", + "inboundConfig": { + "blockConfirmation": 260, + "relayerAddress": "0xA27A2cA24DD28Ce14Fb5f5844b59851F03DCf182", + "oracleAddress": "0x5a54fe5234E811466D5366846283323c954310B2", + "executorAddress": "0xA27A2cA24DD28Ce14Fb5f5844b59851F03DCf182", + "proofType": "2", + "utilsVersion": 1, + "proofVersion": "1", + "proofLibraryAddress": "0x28A5536cA9F36c45A9d2AC8d2B62Fc46Fde024B6", + "ulnVersion": "V2" + }, + "outboundConfig": { + "blockConfirmation": 260, + "relayerAddress": "0x1d8727df513fa2a8785d0834e40b34223daff1affc079574082baadb74b66ee4", + "oracleAddress": "0x12e12de0af996d9611b0b78928cd9f4cbf50d94d972043cdd829baa77a78929b", + "proofType": "2" + }, + "ulnSendVersion": "V2" + }, + "status": { + "name": "DELIVERED", + "message": "Destination transaction confirmed" + }, + "created": "2025-01-10T18:18:57.000Z", + "updated": "2025-01-10T18:20:47.000Z" + } + ] +} diff --git a/gemstone/src/swapper/stargate/layer_zero/scan.rs b/gemstone/src/swapper/stargate/layer_zero/scan.rs index dc82fed8..fcb9dda0 100644 --- a/gemstone/src/swapper/stargate/layer_zero/scan.rs +++ b/gemstone/src/swapper/stargate/layer_zero/scan.rs @@ -3,7 +3,7 @@ use crate::{ swapper::SwapperError, }; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, sync::Arc}; +use std::sync::Arc; #[derive(Debug, Clone)] pub struct LayerZeroScanApi { @@ -20,214 +20,7 @@ pub struct MessageResponse { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Message { - pub pathway: MessagePathway, - pub source: MessageSource, - pub destination: MessageDestination, - pub verification: MessageVerification, - pub guid: String, - pub config: MessageConfig, pub status: MessageStatus, - pub created: String, - pub updated: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MessagePathway { - pub src_eid: u32, - pub dst_eid: u32, - pub sender: MessageParticipant, - pub receiver: MessageParticipant, - pub id: String, - pub nonce: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MessageParticipant { - pub address: String, - pub id: Option, - pub name: Option, - pub chain: Option, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MessageSource { - pub status: String, - pub tx: SourceTransaction, - #[serde(rename = "failedTx")] - pub failed_tx: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SourceTransaction { - pub tx_hash: String, - pub block_hash: String, - pub block_number: String, - pub block_timestamp: u64, - pub from: String, - pub block_confirmations: u64, - pub payload: String, - pub value: String, - pub readiness_timestamp: u64, - pub resolved_payload: String, - pub adapter_params: AdapterParams, - pub options: TransactionOptions, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AdapterParams { - pub version: String, - pub dst_gas_limit: String, - pub dst_native_gas_transfer_amount: String, - pub dst_native_gas_transfer_address: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct TransactionOptions { - #[serde(rename = "lzReceive")] - pub lz_receive: LzReceive, - #[serde(rename = "nativeDrop")] - pub native_drop: Vec, - pub compose: Vec, - pub ordered: bool, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct LzReceive { - pub gas: String, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct NativeDrop { - pub amount: String, - pub receiver: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Compose { - pub index: u64, - pub gas: String, - pub value: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MessageDestination { - pub status: String, - pub tx: DestinationTransaction, - #[serde(rename = "payloadStoredTx")] - pub payload_stored_tx: String, - #[serde(rename = "failedTx")] - pub failed_tx: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DestinationTransaction { - pub tx_hash: String, - pub block_hash: String, - pub block_number: u64, - pub block_timestamp: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MessageVerification { - pub dvn: Dvn, - pub sealer: Sealer, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Dvn { - pub dvns: HashMap, - pub status: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct DvnInfo { - pub tx_hash: String, - pub block_hash: String, - pub block_number: u64, - pub block_timestamp: u64, - pub proof: DvnProof, - pub optional: bool, - pub status: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct DvnProof { - #[serde(rename = "packetHeader")] - pub packet_header: String, - #[serde(rename = "payloadHash")] - pub payload_hash: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Sealer { - pub tx: SealerTransaction, - #[serde(rename = "failedTx")] - pub failed_tx: Vec, - pub status: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct SealerTransaction { - pub tx_hash: String, - pub block_hash: String, - pub block_number: u64, - pub block_timestamp: u64, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FailedTransaction { - pub tx_hash: String, - pub tx_error: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MessageConfig { - pub error: bool, - pub error_message: String, - pub dvn_config_error: bool, - pub receive_library: Option, - pub send_library: Option, - pub inbound_config: InboundConfig, - pub outbound_config: OutboundConfig, - pub uln_send_version: String, - pub uln_receive_version: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct InboundConfig { - pub confirmations: u64, - pub required_dvn_count: u64, - pub optional_dvn_count: u64, - pub optional_dvn_threshold: u64, - pub required_dvns: Vec, - pub required_dvn_names: Vec, - pub optional_dvns: Vec, - pub optional_dvn_names: Vec, - pub executor: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct OutboundConfig { - pub confirmations: u64, - pub required_dvn_count: u64, - pub optional_dvn_count: u64, - pub optional_dvn_threshold: u64, - pub required_dvns: Vec, - pub required_dvn_names: Vec, - pub optional_dvns: Vec, - pub optional_dvn_names: Vec, - pub executor: String, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -251,6 +44,7 @@ pub struct MessageStatus { pub message: Option, } +#[allow(dead_code)] impl MessageStatus { pub fn is_delivered(&self) -> bool { matches!(self.name, MessageStatusName::Delivered) @@ -283,3 +77,45 @@ impl LayerZeroScanApi { serde_json::from_slice(&response).map_err(|e| SwapperError::NetworkError { msg: e.to_string() }) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_message_tx() { + let message_tx = include_str!("mock/message_tx.json"); + let message_tx: MessageResponse = serde_json::from_str(message_tx).unwrap(); + assert_eq!(message_tx.data.len(), 1); + + let message = &message_tx.data[0]; + assert!(message.status.is_delivered()); + } + + #[test] + fn test_message_status() { + let status = MessageStatus { + name: MessageStatusName::Delivered, + message: None, + }; + assert!(status.is_delivered()); + assert!(!status.is_failed()); + assert!(!status.is_pending()); + + let status = MessageStatus { + name: MessageStatusName::Failed, + message: None, + }; + assert!(!status.is_delivered()); + assert!(status.is_failed()); + assert!(!status.is_pending()); + + let status = MessageStatus { + name: MessageStatusName::Inflight, + message: None, + }; + assert!(!status.is_delivered()); + assert!(!status.is_failed()); + assert!(status.is_pending()); + } +} diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index fa6092bc..e2b5fef7 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -15,13 +15,13 @@ use crate::{ debug_println, network::{jsonrpc_call, AlienProvider, JsonRpcResult}, swapper::{ - approval::check_approval_erc20, eth_rpc, slippage::apply_slippage_in_bp, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, - SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, + approval::check_approval_erc20, eth_rpc, slippage::apply_slippage_in_bp, thorchain::ThorChain, ApprovalType, FetchQuoteData, GemSwapProvider, + SwapChainAsset, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, }, }; use super::{ - endpoint::{self, StargateEndpoint, STARGATE_ROUTES}, + endpoint::{StargateEndpoint, STARGATE_ROUTES}, layer_zero::scan::LayerZeroScanApi, }; @@ -67,10 +67,18 @@ impl Stargate { endpoint .pools .iter() - .find(|x| x.asset == *asset) + .find(|x| x.asset.id == *asset) .map(|x| x.address.clone()) .ok_or(SwapperError::NotSupportedChain) } + + pub fn get_asset_decimals(&self, asset: &AssetId) -> Result { + let endpoint = self.enpoints.iter().find(|x| x.id == asset.chain).ok_or(SwapperError::NotSupportedChain)?; + match endpoint.pools.iter().find(|x| x.asset.id == *asset) { + Some(pool) => Ok(pool.asset.decimals), + None => Err(SwapperError::NotSupportedChain), + } + } } #[async_trait] @@ -80,17 +88,13 @@ impl GemSwapProvider for Stargate { } fn supported_assets(&self) -> Vec { - let mut assets = vec![]; - for endpoint in self.enpoints.iter() { - assets.push(endpoint.chain_asset.clone()); - } - - println!("assets: {:?}", assets); - assets + self.enpoints + .iter() + .map(|x| SwapChainAsset::Assets(x.id, x.pools.iter().map(|y| y.asset.id.clone()).collect())) + .collect() } async fn fetch_quote(&self, request: &SwapQuoteRequest, provider: Arc) -> Result { - println!("request: {:?}", request); let from_asset = &request.from_asset; let to_asset = &request.to_asset; @@ -102,8 +106,10 @@ impl GemSwapProvider for Stargate { return Err(SwapperError::NotSupportedChain); } - let pool = self.get_pool(from_asset).unwrap(); let amount_ld = U256::from_str(request.value.as_str()).unwrap(); + + let pool = self.get_pool(from_asset).unwrap(); + let mut send_param = SendParam { dstEid: self.get_endpoint_id(&to_asset.chain).unwrap(), to: self.address_to_bytes32(request.destination_address.as_str()), @@ -114,10 +120,6 @@ impl GemSwapProvider for Stargate { oftCmd: Bytes::from_str("0x").unwrap(), }; - println!("pool: {:?}", pool); - - println!("send_param: {:?}", send_param); - // Encode call data let call_data = IStargate::quoteOFTCall { _sendParam: send_param.clone(), @@ -130,26 +132,19 @@ impl GemSwapProvider for Stargate { let hex_data = hex::decode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; let quote_oft_data = IStargate::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; - println!("quote oft - {:?}", quote_oft_data); - //println!("feeAmount = {}", quote_oft_data.oftFeeDetails[0].feeAmountLD); send_param.minAmountLD = apply_slippage_in_bp("e_oft_data.receipt.amountReceivedLD, request.options.slippage_bps); - //send_param.minAmountLD = U256::from(99500u32); let messaging_fee_calldata = IStargate::quoteSendCall { _sendParam: send_param.clone(), _payInLzToken: false, } .abi_encode(); - let messaging_fee_call = EthereumRpc::Call(TransactionObject::new_call(pool.as_str(), messaging_fee_calldata), BlockParameter::Latest); let messaging_fee_response: JsonRpcResult = jsonrpc_call(&messaging_fee_call, provider.clone(), &request.from_asset.chain).await?; let messaging_fee_result = messaging_fee_response.take()?; let messaging_fee_hex_data = hex::decode(messaging_fee_result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - println!("messagingFee eth_call result: {:?}", messaging_fee_hex_data); - let messaging_fee_value = IStargate::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; - println!("messagingFee = {:?}", messaging_fee_value); let approval = if request.from_asset.is_token() { check_approval_erc20( @@ -171,11 +166,18 @@ impl GemSwapProvider for Stargate { refund_address: request.wallet_address.to_string(), }; - println!("route_data: {:?}", route_data); + let thor_chain = ThorChain::default(); + + let from_decimals = self.get_asset_decimals(&request.from_asset)?; + let to_decimals = self.get_asset_decimals(&request.to_asset)?; + + let from_value = request.value.clone(); + let mut to_value = thor_chain.value_from(quote_oft_data.receipt.amountReceivedLD.to_string(), from_decimals); + to_value = thor_chain.value_to(to_value.to_string(), to_decimals); Ok(SwapQuote { - from_value: request.value.clone(), - to_value: quote_oft_data.receipt.amountReceivedLD.to_string(), + from_value: from_value.to_string(), + to_value: to_value.to_string(), data: SwapProviderData { provider: self.provider(), routes: vec![SwapRoute { @@ -191,7 +193,7 @@ impl GemSwapProvider for Stargate { }) } - async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, data: FetchQuoteData) -> Result { + async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, _data: FetchQuoteData) -> Result { let pool = self.get_pool("e.request.from_asset).unwrap(); let route_data: StargateRouteData = serde_json::from_str("e.data.routes.first().unwrap().route_data).map_err(|_| SwapperError::InvalidRoute)?; let send_calldata = IStargate::sendCall { @@ -201,9 +203,6 @@ impl GemSwapProvider for Stargate { } .abi_encode(); - println!("Route data - {:?}", route_data); - println!("Calldata - {:?}", send_calldata); - println!("data - {:?}", data); let mut value_to_send = route_data.fee.nativeFee; if quote.request.from_asset.is_native() { @@ -215,15 +214,6 @@ impl GemSwapProvider for Stargate { value: value_to_send.to_string(), data: hex::encode_prefixed(send_calldata.clone()), }; - println!("Quote data - {:?}", quote_data); - - let hex_value = format!("{:#x}", value_to_send); - println!("hex_value = {:?}", hex_value); - - let tx = TransactionObject::new_call_with_from_value("e.request.wallet_address, "e_data.to, &hex_value, send_calldata); - println!("tx = {:?}", tx); - let gas_limit = eth_rpc::estimate_gas(_provider.clone(), "e.request.from_asset.chain, tx).await?; - debug_println!("gas_limit: {:?}", gas_limit); Ok(quote_data) } @@ -260,4 +250,59 @@ mod tests { ] ); } + + // New tests + #[test] + fn test_get_endpoint_id() { + let stargate = Stargate::new(); + + // Test valid chain + let result = stargate.get_endpoint_id(&Chain::Ethereum); + assert!(result.is_ok()); + + // Test invalid chain + let result = stargate.get_endpoint_id(&Chain::Manta); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } + + #[test] + fn test_address_to_bytes32() { + let stargate = Stargate::new(); + let test_address = "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24"; + let test_result = "0x0000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf24"; + let result = stargate.address_to_bytes32(test_address); + + assert_eq!(result.len(), 32); + assert_eq!(result, FixedBytes::<32>::from_str(test_result).unwrap()); + } + + #[test] + fn test_get_pool() { + let stargate = Stargate::new(); + + // Test with valid asset + let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details + let result = stargate.get_pool(&valid_asset); + assert!(result.is_ok()); + + // Test with invalid asset + let invalid_asset = AssetId::from_chain(Chain::Manta); + let result = stargate.get_pool(&invalid_asset); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } + + #[test] + fn test_get_asset_decimals() { + let stargate = Stargate::new(); + + // Test with valid asset + let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details + let result = stargate.get_asset_decimals(&valid_asset); + assert!(result.is_ok()); + + // Test with invalid asset + let invalid_asset = AssetId::from_chain(Chain::Manta); + let result = stargate.get_asset_decimals(&invalid_asset); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } } diff --git a/gemstone/src/swapper/thorchain/mod.rs b/gemstone/src/swapper/thorchain/mod.rs index 0f36a375..baee923b 100644 --- a/gemstone/src/swapper/thorchain/mod.rs +++ b/gemstone/src/swapper/thorchain/mod.rs @@ -23,7 +23,7 @@ impl ThorChain { memo } - fn value_from(&self, value: String, decimals: i32) -> BigInt { + pub fn value_from(&self, value: String, decimals: i32) -> BigInt { let decimals = decimals - 8; if decimals > 0 { BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow(decimals as u32) @@ -32,7 +32,7 @@ impl ThorChain { } } - fn value_to(&self, value: String, decimals: i32) -> BigInt { + pub fn value_to(&self, value: String, decimals: i32) -> BigInt { let decimals = decimals - 8; if decimals > 0 { BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow((decimals).unsigned_abs()) diff --git a/gemstone/tests/integration_test.rs b/gemstone/tests/integration_test.rs index 2c38728d..39f80752 100644 --- a/gemstone/tests/integration_test.rs +++ b/gemstone/tests/integration_test.rs @@ -10,6 +10,7 @@ mod tests { }; use primitives::{AssetId, Chain}; use reqwest::Client; + use stargate::Stargate; use std::{collections::HashMap, sync::Arc, time::SystemTime}; pub fn print_json(bytes: &[u8]) { @@ -179,4 +180,45 @@ mod tests { Ok(()) } + + #[tokio::test] + async fn test_stargate_quote() -> Result<(), SwapperError> { + let swap_provider = Stargate::new(); + let network_provider = Arc::new(NativeProvider::default()); + let mut options = GemSwapOptions { + slippage_bps: 100, + fee: Some(SwapReferralFees::evm(SwapReferralFee { + bps: 25, + address: "0x0D9DAB1A248f63B0a48965bA8435e4de7497a3dC".into(), + })), + preferred_providers: vec![], + }; + options.fee.as_mut().unwrap().evm_bridge = SwapReferralFee { + bps: 25, + address: "0x0D9DAB1A248f63B0a48965bA8435e4de7497a3dC".into(), + }; + + let request = SwapQuoteRequest { + from_asset: AssetId::from_chain(Chain::Optimism), + to_asset: AssetId::from_chain(Chain::Base), + wallet_address: "0x514BCb1F9AAbb904e6106Bd1052B66d2706dBbb7".into(), + destination_address: "0x514BCb1F9AAbb904e6106Bd1052B66d2706dBbb7".into(), + value: "20000000000000000".into(), // 0.02 ETH + mode: GemSwapMode::ExactIn, + options, + }; + + let now = SystemTime::now(); + let quote = swap_provider.fetch_quote(&request, network_provider.clone()).await?; + let elapsed = SystemTime::now().duration_since(now).unwrap(); + + println!("<== elapsed: {:?}", elapsed); + println!("<== quote: {:?}", quote); + assert!(quote.to_value.parse::().unwrap() > 0); + + let quote_data = swap_provider.fetch_quote_data("e, network_provider.clone(), FetchQuoteData::None).await?; + println!("<== quote_data: {:?}", quote_data); + + Ok(()) + } } From bcf8b16ebb50bc5704fda01bf5fbfb018f0365b7 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 16:08:11 +0200 Subject: [PATCH 07/16] chore: stargate contract docs --- crates/gem_evm/src/stargate/contract.rs | 105 +++++++++++++----------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/crates/gem_evm/src/stargate/contract.rs b/crates/gem_evm/src/stargate/contract.rs index ce1be782..201988e9 100644 --- a/crates/gem_evm/src/stargate/contract.rs +++ b/crates/gem_evm/src/stargate/contract.rs @@ -2,52 +2,49 @@ use alloy_core::sol; use serde::{Deserialize, Serialize}; sol! { - /** - * @dev Struct representing token parameters for the OFT send() operation. - */ + /// Parameters for the OFT send() operation #[derive(Debug, PartialEq, Serialize, Deserialize)] struct SendParam { - uint32 dstEid; // Destination endpoint ID. - bytes32 to; // Recipient address. - uint256 amountLD; // Amount to send in local decimals. - uint256 minAmountLD; // Minimum amount to send in local decimals. - bytes extraOptions; // Additional options supplied by the caller to be used in the LayerZero message. - bytes composeMsg; // The composed message for the send() operation. - bytes oftCmd; // The OFT command to be executed, unused in default OFT implementations. + uint32 dstEid; + bytes32 to; + uint256 amountLD; + uint256 minAmountLD; + bytes extraOptions; + bytes composeMsg; + bytes oftCmd; } - /** - * @dev Struct representing OFT limit information. - * @dev These amounts can change dynamically and are up the the specific oft implementation. - */ + /// OFT limit information + /// + /// These amounts can change dynamically and are determined by the specific OFT implementation #[derive(Debug, PartialEq)] struct OFTLimit { - uint256 minAmountLD; // Minimum amount in local decimals that can be sent to the recipient. - uint256 maxAmountLD; // Maximum amount in local decimals that can be sent to the recipient. + uint256 minAmountLD; + uint256 maxAmountLD; } - /** - * @dev Struct representing OFT receipt information. - */ + /// OFT receipt information containing details about sent and received amounts #[derive(Debug, PartialEq)] struct OFTReceipt { - uint256 amountSentLD; // Amount of tokens ACTUALLY debited from the sender in local decimals. - // @dev In non-default implementations, the amountReceivedLD COULD differ from this value. - uint256 amountReceivedLD; // Amount of tokens to be received on the remote side. + uint256 amountSentLD; + uint256 amountReceivedLD; } + /// Detailed information about OFT fees #[derive(Debug, PartialEq)] struct OFTFeeDetail { - int256 feeAmountLD; // Amount of the fee in local decimals. - string description; // Description of the fee. + int256 feeAmountLD; + string description; } + /// Structure containing messaging fee information #[derive(Debug, PartialEq, Serialize, Deserialize)] struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; } + /// Receipt for messaging operations #[derive(Debug, PartialEq, Serialize, Deserialize)] struct MessagingReceipt { bytes32 guid; @@ -55,21 +52,35 @@ sol! { MessagingFee fee; } + /// Interface for Stargate cross-chain operations #[derive(Debug, PartialEq)] interface IStargate { + /// Provides a quote for messaging fees + /// + /// # Arguments + /// + /// * `_sendParam` - Parameters for the send operation + /// * `_payInLzToken` - Flag indicating whether to pay in LayerZero tokens + /// + /// # Returns + /// + /// * `fee` - Messaging fee information function quoteSend( SendParam calldata _sendParam, bool _payInLzToken ) external view returns (MessagingFee memory fee); - /** - * @notice Provides a quote for sending OFT to another chain. - * @dev Implements the IOFT interface - * @param _sendParam The parameters for the send operation - * @return limit The information on OFT transfer limits - * @return oftFeeDetails The details of OFT transaction cost or reward - * @return receipt The OFT receipt information, indicating how many tokens would be sent and received - */ + /// Provides a quote for sending OFT to another chain + /// + /// # Arguments + /// + /// * `_sendParam` - Parameters for the send operation + /// + /// # Returns + /// + /// * `limit` - Information on OFT transfer limits + /// * `oftFeeDetails` - Details of OFT transaction cost or reward + /// * `receipt` - OFT receipt information indicating token amounts function quoteOFT( SendParam calldata _sendParam ) external view returns ( @@ -78,21 +89,21 @@ sol! { OFTReceipt memory receipt ); - /** - * @notice Executes the send() operation. - * @param _sendParam The parameters for the send operation. - * @param _fee The fee information supplied by the caller. - * - nativeFee: The native fee. - * - lzTokenFee: The lzToken fee. - * @param _refundAddress The address to receive any excess funds from fees etc. on the src. - * @return receipt The LayerZero messaging receipt from the send() operation. - * @return oftReceipt The OFT receipt information. - * - * @dev MessagingReceipt: LayerZero msg receipt - * - guid: The unique identifier for the sent message. - * - nonce: The nonce of the sent message. - * - fee: The LayerZero fee incurred for the message. - */ + /// Executes the send operation + /// + /// # Arguments + /// + /// * `_sendParam` - Parameters for the send operation + /// * `_fee` - Fee information containing native and LayerZero token fees + /// * `_refundAddress` - Address to receive any excess funds from fees on the source chain + /// + /// # Returns + /// + /// * `msgReceipt` - LayerZero messaging receipt containing: + /// - guid: Unique identifier for the message + /// - nonce: Message nonce + /// - fee: LayerZero fee details + /// * `oftReceipt` - OFT receipt with sent and received amount information function send( SendParam calldata _sendParam, MessagingFee calldata _fee, From 3293549a03f9bee89362518684e3e3c4cc10d00b Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Wed, 15 Jan 2025 16:16:01 +0200 Subject: [PATCH 08/16] chore: clippy --- gemstone/src/swapper/stargate/provider.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index e2b5fef7..c09af36f 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -12,11 +12,10 @@ use primitives::{AssetId, Chain}; use serde::{Deserialize, Serialize}; use crate::{ - debug_println, network::{jsonrpc_call, AlienProvider, JsonRpcResult}, swapper::{ - approval::check_approval_erc20, eth_rpc, slippage::apply_slippage_in_bp, thorchain::ThorChain, ApprovalType, FetchQuoteData, GemSwapProvider, - SwapChainAsset, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, + approval::check_approval_erc20, slippage::apply_slippage_in_bp, thorchain::ThorChain, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, + SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, }, }; From 85e90741b7b13fc6035b18bc62852766bd0a92a9 Mon Sep 17 00:00:00 2001 From: Ivan Kuchmenko <44723550+kuchmenko@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:59:50 +0200 Subject: [PATCH 09/16] Update gemstone/src/swapper/models.rs Co-authored-by: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> --- gemstone/src/swapper/models.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemstone/src/swapper/models.rs b/gemstone/src/swapper/models.rs index 59bc5504..acc9719c 100644 --- a/gemstone/src/swapper/models.rs +++ b/gemstone/src/swapper/models.rs @@ -89,7 +89,7 @@ impl SwapProvider { Self::Orca => "Orca Whirlpool", Self::Jupiter => "Jupiter", Self::Across => "Across v3", - Self::Stargate => "Stargate", + Self::Stargate => "Stargate v2", } } From 358e4509486a59c11ee75292a679968ea9c696a5 Mon Sep 17 00:00:00 2001 From: Ivan Kuchmenko <44723550+kuchmenko@users.noreply.github.com> Date: Thu, 16 Jan 2025 12:01:11 +0200 Subject: [PATCH 10/16] Update gemstone/src/swapper/stargate/provider.rs Co-authored-by: 0xh3rman <119309671+0xh3rman@users.noreply.github.com> --- gemstone/src/swapper/stargate/provider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index c09af36f..ccef6466 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -217,7 +217,7 @@ impl GemSwapProvider for Stargate { Ok(quote_data) } - async fn get_transaction_status(&self, _chain: Chain, _transaction_hash: &str, _provider: Arc) -> Result { + async fn get_transaction_status(&self, _chain: Chain, transaction_hash: &str, provider: Arc) -> Result { let api = LayerZeroScanApi::new(_provider.clone()); let response = api.get_message_by_tx(_transaction_hash).await?; let messages = response.data; From c0d0b44731832dd25ad522e41c4cc6b9e9d4bb7a Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Thu, 16 Jan 2025 16:50:50 +0200 Subject: [PATCH 11/16] chore: pr comments refactoring --- Untitled | 2 + gemstone/src/swapper/asset.rs | 1 - gemstone/src/swapper/mod.rs | 1 + gemstone/src/swapper/stargate/client.rs | 202 ++++++++++++++++ gemstone/src/swapper/stargate/endpoint.rs | 27 ++- gemstone/src/swapper/stargate/mod.rs | 1 + gemstone/src/swapper/stargate/provider.rs | 263 ++++++--------------- gemstone/src/swapper/thorchain/mod.rs | 58 ----- gemstone/src/swapper/thorchain/provider.rs | 5 +- gemstone/src/swapper/utils.rs | 66 ++++++ gemstone/tests/integration_test.rs | 4 +- 11 files changed, 370 insertions(+), 260 deletions(-) create mode 100644 Untitled create mode 100644 gemstone/src/swapper/stargate/client.rs create mode 100644 gemstone/src/swapper/utils.rs diff --git a/Untitled b/Untitled new file mode 100644 index 00000000..d148bc8e --- /dev/null +++ b/Untitled @@ -0,0 +1,2 @@ + + diff --git a/gemstone/src/swapper/asset.rs b/gemstone/src/swapper/asset.rs index 33a6c8a9..9f93e790 100644 --- a/gemstone/src/swapper/asset.rs +++ b/gemstone/src/swapper/asset.rs @@ -18,7 +18,6 @@ pub const ETHEREUM_USDC_TOKEN_ID: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606 pub const ETHEREUM_USDT_TOKEN_ID: &str = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; pub const ETHEREUM_WBTC_TOKEN_ID: &str = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; pub const ETHEREUM_DAI_TOKEN_ID: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; -pub const ETHEREUM_METH_TOKEN_ID: &str = "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa"; pub const SMARTCHAIN_USDT_TOKEN_ID: &str = "0x55d398326f99059fF775485246999027B3197955"; pub const SMARTCHAIN_USDC_TOKEN_ID: &str = "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d"; pub const AVALANCHE_USDT_TOKEN_ID: &str = "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"; diff --git a/gemstone/src/swapper/mod.rs b/gemstone/src/swapper/mod.rs index dd90f229..8fdc173a 100644 --- a/gemstone/src/swapper/mod.rs +++ b/gemstone/src/swapper/mod.rs @@ -8,6 +8,7 @@ mod chainlink; mod custom_types; mod eth_rpc; mod permit2_data; +mod utils; mod weth_address; pub mod across; diff --git a/gemstone/src/swapper/stargate/client.rs b/gemstone/src/swapper/stargate/client.rs new file mode 100644 index 00000000..6d32dc79 --- /dev/null +++ b/gemstone/src/swapper/stargate/client.rs @@ -0,0 +1,202 @@ +use std::{collections::HashMap, str::FromStr, sync::Arc}; + +use alloy_core::sol_types::SolCall; +use alloy_primitives::{hex, Address, Bytes, FixedBytes, U160, U256}; +use gem_evm::{ + jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, + stargate::contract::{IStargate, MessagingFee, OFTReceipt, SendParam}, +}; +use primitives::{AssetId, Chain}; + +use crate::{ + network::{jsonrpc_call, AlienProvider, JsonRpcResult}, + swapper::{SwapQuoteRequest, SwapperError}, +}; + +use super::endpoint::{StargateEndpoint, StargatePool}; + +#[derive(Debug)] +pub struct StargateOFTQuote { + pub receipt: OFTReceipt, +} + +#[derive(Debug, Default)] +pub struct StargateClient { + endpoints: Vec, + chain_endpoints: HashMap, + pools: HashMap, +} + +impl StargateClient { + pub fn from_endpoints(endpoints: Vec) -> Self { + let mut chain_endpoints = HashMap::new(); + let mut pools = HashMap::new(); + + for endpoint in endpoints.iter() { + chain_endpoints.insert(endpoint.id, endpoint.clone()); + for pool in endpoint.pools.iter() { + pools.insert(pool.asset.id.clone(), pool.clone()); + } + } + + Self { + endpoints, + chain_endpoints, + pools, + } + } + + pub fn address_to_bytes32(&self, addr: &str) -> FixedBytes<32> { + FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) + } + + pub fn get_endpoints(&self) -> Vec<&StargateEndpoint> { + self.endpoints.iter().collect() + } + + pub fn get_endpoint_by_chain(&self, chain: &Chain) -> Result<&StargateEndpoint, SwapperError> { + self.chain_endpoints.get(chain).ok_or(SwapperError::NotSupportedChain) + } + + pub fn get_pool_by_asset_id(&self, asset: &AssetId) -> Result<&StargatePool, SwapperError> { + self.pools.get(asset).ok_or(SwapperError::NotSupportedChain) + } + + pub fn get_decimals_by_asset_id(&self, asset: &AssetId) -> Result { + self.get_pool_by_asset_id(asset).map(|p| p.asset.decimals) + } + + pub fn build_send_param(&self, request: &SwapQuoteRequest) -> Result { + let from_asset = &request.from_asset; + let to_asset = &request.to_asset; + + if from_asset.chain == to_asset.chain { + return Err(SwapperError::NotSupportedPair); + } + + if from_asset.is_native() && !to_asset.is_native() { + return Err(SwapperError::NotSupportedPair); + } + + let amount_ld = U256::from_str(request.value.as_str()).unwrap(); + + let destination_endpoint = self.get_endpoint_by_chain(&to_asset.chain)?; + + Ok(SendParam { + dstEid: destination_endpoint.endpoint_id, + to: self.address_to_bytes32(request.destination_address.as_str()), + amountLD: amount_ld, + minAmountLD: amount_ld, + extraOptions: Bytes::new(), + composeMsg: Bytes::new(), + oftCmd: Bytes::new(), + }) + } + + pub async fn quote_oft(&self, pool: &StargatePool, send_param: &SendParam, provider: Arc) -> Result { + let calldata = IStargate::quoteOFTCall { + _sendParam: send_param.clone(), + } + .abi_encode(); + + let call = EthereumRpc::Call(TransactionObject::new_call(pool.address.as_str(), calldata), BlockParameter::Latest); + let response: JsonRpcResult = jsonrpc_call(&call, provider, &pool.asset.chain()).await?; + let decoded = hex::decode(response.take()?).map_err(|e| SwapperError::NetworkError { + msg: format!("Unable to hex decode quote oft response: {:?}", e.to_string()), + })?; + let returns = IStargate::quoteOFTCall::abi_decode_returns(&decoded, true).map_err(|e| SwapperError::ABIError { + msg: format!("Unable to abi decode quote oft response: {:?}", e.to_string()), + })?; + + Ok(StargateOFTQuote { receipt: returns.receipt }) + } + + pub async fn quote_send(&self, pool: &StargatePool, send_param: &SendParam, provider: Arc) -> Result { + let calldata = IStargate::quoteSendCall { + _sendParam: send_param.clone(), + _payInLzToken: false, + } + .abi_encode(); + + let call = EthereumRpc::Call(TransactionObject::new_call(pool.address.as_str(), calldata), BlockParameter::Latest); + let response: JsonRpcResult = jsonrpc_call(&call, provider, &pool.asset.chain()).await?; + let decoded = hex::decode(response.take()?).map_err(|e| SwapperError::NetworkError { + msg: format!("Unable to hex decode quote send response: {:?}", e.to_string()), + })?; + let returns = IStargate::quoteSendCall::abi_decode_returns(&decoded, true).map_err(|e| SwapperError::ABIError { + msg: format!("Unable to abi decode quote send response: {:?}", e.to_string()), + })?; + + Ok(returns.fee) + } + + pub fn send(&self, send_param: &SendParam, fee: &MessagingFee, refund_address: &Address) -> Vec { + IStargate::sendCall { + _sendParam: send_param.clone(), + _fee: fee.clone(), + _refundAddress: *refund_address, + } + .abi_encode() + } +} + +#[cfg(test)] +mod tests { + use crate::swapper::stargate::endpoint::STARGATE_ROUTES; + + use super::*; + + #[test] + fn test_get_endpoint_id() { + let stargate = StargateClient::from_endpoints(vec![STARGATE_ROUTES.ethereum.clone()]); + + // Test valid chain + let result = stargate.get_endpoint_by_chain(&Chain::Ethereum); + assert!(result.is_ok()); + + // Test invalid chain + let result = stargate.get_endpoint_by_chain(&Chain::Manta); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } + + #[test] + fn test_address_to_bytes32() { + let stargate = StargateClient::from_endpoints(vec![STARGATE_ROUTES.ethereum.clone()]); + let test_address = "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24"; + let test_result = "0x0000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf24"; + let result = stargate.address_to_bytes32(test_address); + + assert_eq!(result.len(), 32); + assert_eq!(result, FixedBytes::<32>::from_str(test_result).unwrap()); + } + + #[test] + fn test_get_pool() { + let stargate = StargateClient::from_endpoints(vec![STARGATE_ROUTES.ethereum.clone()]); + + // Test with valid asset + let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details + let result = stargate.get_pool_by_asset_id(&valid_asset); + assert!(result.is_ok()); + + // Test with invalid asset + let invalid_asset = AssetId::from_chain(Chain::Manta); + let result = stargate.get_pool_by_asset_id(&invalid_asset); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } + + #[test] + fn test_get_asset_decimals() { + let stargate = StargateClient::from_endpoints(vec![STARGATE_ROUTES.ethereum.clone()]); + + // Test with valid asset + let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details + let result = stargate.get_decimals_by_asset_id(&valid_asset); + assert!(result.is_ok()); + + // Test with invalid asset + let invalid_asset = AssetId::from_chain(Chain::Manta); + let result = stargate.get_decimals_by_asset_id(&invalid_asset); + assert!(matches!(result, Err(SwapperError::NotSupportedChain))); + } +} diff --git a/gemstone/src/swapper/stargate/endpoint.rs b/gemstone/src/swapper/stargate/endpoint.rs index 1f83a6d1..9427369a 100644 --- a/gemstone/src/swapper/stargate/endpoint.rs +++ b/gemstone/src/swapper/stargate/endpoint.rs @@ -6,6 +6,15 @@ use crate::swapper::asset::{ POLYGON_USDT, SMARTCHAIN_USDC, SMARTCHAIN_USDT, }; +pub const ENDPOINT_ID_ETHEREUM: u32 = 30101; +pub const ENDPOINT_ID_BASE: u32 = 30184; +pub const ENDPOINT_ID_OPTIMISM: u32 = 30111; +pub const ENDPOINT_ID_ARBITRUM: u32 = 30110; +pub const ENDPOINT_ID_POLYGON: u32 = 30109; +pub const ENDPOINT_ID_AVALANCHE: u32 = 30106; +pub const ENDPOINT_ID_LINEA: u32 = 30183; +pub const ENDPOINT_ID_SMARTCHAIN: u32 = 30102; + #[derive(Clone, Debug, PartialEq)] pub struct StargatePool { pub asset: Asset, @@ -49,7 +58,7 @@ lazy_static! { address: "0x933597a323Eb81cAe705C5bC29985172fd5A3973".to_string(), }, ], - endpoint_id: 30101, + endpoint_id: ENDPOINT_ID_ETHEREUM, }, base: StargateEndpoint { id: Chain::Base, @@ -63,7 +72,7 @@ lazy_static! { address: "0x27a16dc786820B16E5c9028b75B99F6f604b5d26".to_string(), }, ], - endpoint_id: 30184, + endpoint_id: ENDPOINT_ID_BASE, }, optimism: StargateEndpoint { id: Chain::Optimism, @@ -81,7 +90,7 @@ lazy_static! { address: "0x19cFCE47eD54a88614648DC3f19A5980097007dD".to_string(), }, ], - endpoint_id: 30111, + endpoint_id: ENDPOINT_ID_OPTIMISM, }, arbitrum: StargateEndpoint { id: Chain::Arbitrum, @@ -99,7 +108,7 @@ lazy_static! { address: "0xcE8CcA271Ebc0533920C83d39F417ED6A0abB7D0".to_string(), }, ], - endpoint_id: 30110, + endpoint_id: ENDPOINT_ID_ARBITRUM, }, polygon: StargateEndpoint { id: Chain::Polygon, @@ -113,7 +122,7 @@ lazy_static! { address: "0xd47b03ee6d86Cf251ee7860FB2ACf9f91B9fD4d7".to_string(), }, ], - endpoint_id: 30109, + endpoint_id: ENDPOINT_ID_POLYGON, }, avalanche: StargateEndpoint { id: Chain::AvalancheC, @@ -127,15 +136,15 @@ lazy_static! { address: "0x12dC9256Acc9895B076f6638D628382881e62CeE".to_string(), }, ], - endpoint_id: 30106, + endpoint_id: ENDPOINT_ID_AVALANCHE, }, linea: StargateEndpoint { id: Chain::Linea, pools: vec![StargatePool { asset: Asset::from_chain(Chain::Linea), address: "0x81F6138153d473E8c5EcebD3DC8Cd4903506B075".to_string(), - },], - endpoint_id: 30183, + }], + endpoint_id: ENDPOINT_ID_LINEA, }, smartchain: StargateEndpoint { id: Chain::SmartChain, @@ -149,7 +158,7 @@ lazy_static! { address: "0x138EB30f73BC423c6455C53df6D89CB01d9eBc63".to_string(), }, ], - endpoint_id: 30102, + endpoint_id: ENDPOINT_ID_SMARTCHAIN, }, }; } diff --git a/gemstone/src/swapper/stargate/mod.rs b/gemstone/src/swapper/stargate/mod.rs index 1b9bc1fc..2278a516 100644 --- a/gemstone/src/swapper/stargate/mod.rs +++ b/gemstone/src/swapper/stargate/mod.rs @@ -1,3 +1,4 @@ +mod client; mod endpoint; mod layer_zero; pub mod provider; diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index ccef6466..a82959ec 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -1,28 +1,22 @@ use std::str::FromStr; use std::sync::Arc; -use alloy_core::sol_types::SolCall; -use alloy_primitives::{hex, Address, Bytes, FixedBytes, U160, U256}; +use alloy_primitives::{hex, Address}; use async_trait::async_trait; -use gem_evm::{ - jsonrpc::{BlockParameter, EthereumRpc, TransactionObject}, - stargate::contract::{IStargate, MessagingFee, SendParam}, -}; -use primitives::{AssetId, Chain}; +use gem_evm::stargate::contract::{MessagingFee, SendParam}; +use primitives::Chain; use serde::{Deserialize, Serialize}; use crate::{ - network::{jsonrpc_call, AlienProvider, JsonRpcResult}, + debug_println, + network::AlienProvider, swapper::{ - approval::check_approval_erc20, slippage::apply_slippage_in_bp, thorchain::ThorChain, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, + approval::check_approval_erc20, slippage::apply_slippage_in_bp, utils::converter, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, }, }; -use super::{ - endpoint::{StargateEndpoint, STARGATE_ROUTES}, - layer_zero::scan::LayerZeroScanApi, -}; +use super::{client::StargateClient, endpoint::STARGATE_ROUTES, layer_zero::scan::LayerZeroScanApi}; #[derive(Debug, PartialEq, Serialize, Deserialize)] struct StargateRouteData { @@ -33,50 +27,25 @@ struct StargateRouteData { #[derive(Debug, Default)] pub struct Stargate { - pub enpoints: Vec, + client: StargateClient, } impl Stargate { pub fn new() -> Self { - Self { - enpoints: vec![ - STARGATE_ROUTES.ethereum.clone(), - STARGATE_ROUTES.base.clone(), - STARGATE_ROUTES.optimism.clone(), - STARGATE_ROUTES.arbitrum.clone(), - STARGATE_ROUTES.polygon.clone(), - STARGATE_ROUTES.avalanche.clone(), - STARGATE_ROUTES.linea.clone(), - STARGATE_ROUTES.smartchain.clone(), - ], - } - } - - pub fn get_endpoint_id(&self, chain: &Chain) -> Result { - let endpoint = self.enpoints.iter().find(|x| x.id == *chain).ok_or(SwapperError::NotSupportedChain)?; - Ok(endpoint.endpoint_id) - } - - pub fn address_to_bytes32(&self, addr: &str) -> FixedBytes<32> { - FixedBytes::<32>::from(U256::from(U160::from_str(addr).unwrap())) - } - - pub fn get_pool(&self, asset: &AssetId) -> Result { - let endpoint = self.enpoints.iter().find(|x| x.id == asset.chain).ok_or(SwapperError::NotSupportedChain)?; - endpoint - .pools - .iter() - .find(|x| x.asset.id == *asset) - .map(|x| x.address.clone()) - .ok_or(SwapperError::NotSupportedChain) - } - - pub fn get_asset_decimals(&self, asset: &AssetId) -> Result { - let endpoint = self.enpoints.iter().find(|x| x.id == asset.chain).ok_or(SwapperError::NotSupportedChain)?; - match endpoint.pools.iter().find(|x| x.asset.id == *asset) { - Some(pool) => Ok(pool.asset.decimals), - None => Err(SwapperError::NotSupportedChain), - } + let endpoints = vec![ + STARGATE_ROUTES.ethereum.clone(), + STARGATE_ROUTES.base.clone(), + STARGATE_ROUTES.optimism.clone(), + STARGATE_ROUTES.arbitrum.clone(), + STARGATE_ROUTES.polygon.clone(), + STARGATE_ROUTES.avalanche.clone(), + STARGATE_ROUTES.linea.clone(), + STARGATE_ROUTES.smartchain.clone(), + ]; + + let client = StargateClient::from_endpoints(endpoints); + + Self { client } } } @@ -87,70 +56,27 @@ impl GemSwapProvider for Stargate { } fn supported_assets(&self) -> Vec { - self.enpoints + self.client + .get_endpoints() .iter() .map(|x| SwapChainAsset::Assets(x.id, x.pools.iter().map(|y| y.asset.id.clone()).collect())) .collect() } async fn fetch_quote(&self, request: &SwapQuoteRequest, provider: Arc) -> Result { - let from_asset = &request.from_asset; - let to_asset = &request.to_asset; + let pool = self.client.get_pool_by_asset_id(&request.from_asset)?; + let mut send_param = self.client.build_send_param(request)?; - if from_asset.chain == to_asset.chain { - return Err(SwapperError::NotSupportedChain); - } - - if from_asset.is_native() && !to_asset.is_native() { - return Err(SwapperError::NotSupportedChain); - } - - let amount_ld = U256::from_str(request.value.as_str()).unwrap(); - - let pool = self.get_pool(from_asset).unwrap(); - - let mut send_param = SendParam { - dstEid: self.get_endpoint_id(&to_asset.chain).unwrap(), - to: self.address_to_bytes32(request.destination_address.as_str()), - amountLD: amount_ld, - minAmountLD: amount_ld, - extraOptions: Bytes::from_str("0x").unwrap(), - composeMsg: Bytes::from_str("0x").unwrap(), - oftCmd: Bytes::from_str("0x").unwrap(), - }; - - // Encode call data - let call_data = IStargate::quoteOFTCall { - _sendParam: send_param.clone(), - } - .abi_encode(); - - let call = EthereumRpc::Call(TransactionObject::new_call(pool.as_str(), call_data), BlockParameter::Latest); - let response: JsonRpcResult = jsonrpc_call(&call, provider.clone(), &request.from_asset.chain).await?; - let result = response.take()?; - let hex_data = hex::decode(result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - let quote_oft_data = IStargate::quoteOFTCall::abi_decode_returns(&hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; - - send_param.minAmountLD = apply_slippage_in_bp("e_oft_data.receipt.amountReceivedLD, request.options.slippage_bps); - - let messaging_fee_calldata = IStargate::quoteSendCall { - _sendParam: send_param.clone(), - _payInLzToken: false, - } - .abi_encode(); - let messaging_fee_call = EthereumRpc::Call(TransactionObject::new_call(pool.as_str(), messaging_fee_calldata), BlockParameter::Latest); - let messaging_fee_response: JsonRpcResult = jsonrpc_call(&messaging_fee_call, provider.clone(), &request.from_asset.chain).await?; - let messaging_fee_result = messaging_fee_response.take()?; - let messaging_fee_hex_data = hex::decode(messaging_fee_result).map_err(|e| SwapperError::NetworkError { msg: e.to_string() })?; - let messaging_fee_value = - IStargate::quoteSendCall::abi_decode_returns(&messaging_fee_hex_data, true).map_err(|e| SwapperError::ABIError { msg: e.to_string() })?; + let oft_quote = self.client.quote_oft(pool, &send_param, provider.clone()).await?; + send_param.minAmountLD = apply_slippage_in_bp(&oft_quote.receipt.amountReceivedLD, request.options.slippage_bps); + let messaging_fee = self.client.quote_send(pool, &send_param, provider.clone()).await?; let approval = if request.from_asset.is_token() { check_approval_erc20( request.wallet_address.clone(), request.from_asset.token_id.clone().unwrap(), - pool.clone(), - amount_ld, + pool.address.clone(), + send_param.amountLD, provider.clone(), &request.from_asset.chain, ) @@ -161,21 +87,17 @@ impl GemSwapProvider for Stargate { let route_data = StargateRouteData { send_param: send_param.clone(), - fee: messaging_fee_value.fee, + fee: messaging_fee, refund_address: request.wallet_address.to_string(), }; - let thor_chain = ThorChain::default(); - - let from_decimals = self.get_asset_decimals(&request.from_asset)?; - let to_decimals = self.get_asset_decimals(&request.to_asset)?; - - let from_value = request.value.clone(); - let mut to_value = thor_chain.value_from(quote_oft_data.receipt.amountReceivedLD.to_string(), from_decimals); - to_value = thor_chain.value_to(to_value.to_string(), to_decimals); + let from_decimals = self.client.get_decimals_by_asset_id(&request.from_asset)?; + let to_decimals = self.client.get_decimals_by_asset_id(&request.to_asset)?; + let mut to_value = converter::value_from(oft_quote.receipt.amountReceivedLD.to_string(), from_decimals); + to_value = converter::value_to(to_value.to_string(), to_decimals); Ok(SwapQuote { - from_value: from_value.to_string(), + from_value: request.value.to_string(), to_value: to_value.to_string(), data: SwapProviderData { provider: self.provider(), @@ -193,14 +115,13 @@ impl GemSwapProvider for Stargate { } async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, _data: FetchQuoteData) -> Result { - let pool = self.get_pool("e.request.from_asset).unwrap(); + let pool = self.client.get_pool_by_asset_id("e.request.from_asset)?; let route_data: StargateRouteData = serde_json::from_str("e.data.routes.first().unwrap().route_data).map_err(|_| SwapperError::InvalidRoute)?; - let send_calldata = IStargate::sendCall { - _sendParam: route_data.send_param.clone(), - _fee: route_data.fee.clone(), - _refundAddress: Address::from_str(route_data.refund_address.as_str()).unwrap(), - } - .abi_encode(); + let send_calldata = self.client.send( + &route_data.send_param, + &route_data.fee, + &Address::from_str(route_data.refund_address.as_str()).unwrap(), + ); let mut value_to_send = route_data.fee.nativeFee; @@ -209,7 +130,7 @@ impl GemSwapProvider for Stargate { } let quote_data = SwapQuoteData { - to: pool, + to: pool.address.clone(), value: value_to_send.to_string(), data: hex::encode_prefixed(send_calldata.clone()), }; @@ -218,8 +139,8 @@ impl GemSwapProvider for Stargate { } async fn get_transaction_status(&self, _chain: Chain, transaction_hash: &str, provider: Arc) -> Result { - let api = LayerZeroScanApi::new(_provider.clone()); - let response = api.get_message_by_tx(_transaction_hash).await?; + let api = LayerZeroScanApi::new(provider.clone()); + let response = api.get_message_by_tx(transaction_hash).await?; let messages = response.data; let message = messages.first().ok_or(SwapperError::NetworkError { msg: "Unable to check transaction status using Stargate Provider: No message found".into(), @@ -236,72 +157,38 @@ mod tests { fn should_contain_all_endpoints() { let stargate = Stargate::new(); assert_eq!( - stargate.enpoints, + stargate.client.get_endpoints(), vec![ - STARGATE_ROUTES.ethereum.clone(), - STARGATE_ROUTES.base.clone(), - STARGATE_ROUTES.optimism.clone(), - STARGATE_ROUTES.arbitrum.clone(), - STARGATE_ROUTES.polygon.clone(), - STARGATE_ROUTES.avalanche.clone(), - STARGATE_ROUTES.linea.clone(), - STARGATE_ROUTES.smartchain.clone(), + &STARGATE_ROUTES.ethereum, + &STARGATE_ROUTES.base, + &STARGATE_ROUTES.optimism, + &STARGATE_ROUTES.arbitrum, + &STARGATE_ROUTES.polygon, + &STARGATE_ROUTES.avalanche, + &STARGATE_ROUTES.linea, + &STARGATE_ROUTES.smartchain, ] ); } - // New tests - #[test] - fn test_get_endpoint_id() { - let stargate = Stargate::new(); - - // Test valid chain - let result = stargate.get_endpoint_id(&Chain::Ethereum); - assert!(result.is_ok()); - - // Test invalid chain - let result = stargate.get_endpoint_id(&Chain::Manta); - assert!(matches!(result, Err(SwapperError::NotSupportedChain))); - } - - #[test] - fn test_address_to_bytes32() { - let stargate = Stargate::new(); - let test_address = "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24"; - let test_result = "0x0000000000000000000000000655c6abda5e2a5241aa08486bd50cf7d475cf24"; - let result = stargate.address_to_bytes32(test_address); - - assert_eq!(result.len(), 32); - assert_eq!(result, FixedBytes::<32>::from_str(test_result).unwrap()); - } - - #[test] - fn test_get_pool() { - let stargate = Stargate::new(); - - // Test with valid asset - let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details - let result = stargate.get_pool(&valid_asset); - assert!(result.is_ok()); - - // Test with invalid asset - let invalid_asset = AssetId::from_chain(Chain::Manta); - let result = stargate.get_pool(&invalid_asset); - assert!(matches!(result, Err(SwapperError::NotSupportedChain))); - } - - #[test] - fn test_get_asset_decimals() { - let stargate = Stargate::new(); - - // Test with valid asset - let valid_asset = AssetId::from_chain(Chain::Ethereum); // Add appropriate asset details - let result = stargate.get_asset_decimals(&valid_asset); - assert!(result.is_ok()); - - // Test with invalid asset - let invalid_asset = AssetId::from_chain(Chain::Manta); - let result = stargate.get_asset_decimals(&invalid_asset); - assert!(matches!(result, Err(SwapperError::NotSupportedChain))); - } + //#[test] + //fn test_same_chain_quote() { + // let stargate = Stargate::new(); + // let request = SwapQuoteRequest { + // wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + // from_asset: AssetId::from_chain(Chain::Ethereum), + // to_asset: ETHEREUM_USDT.id.clone(), + // value: U256::from(1).to_string(), + // mode: GemSwapMode::ExactIn, + // destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + // options: GemSwapOptions { + // slippage_bps: 100, + // fee: Some(SwapReferralFees::default()), + // preferred_providers: vec![], + // }, + // }; + // + // let result = stargate.fetch_quote(&request); + // assert!(matches!(result, Err(SwapperError::NotSupportedPair))); + //} } diff --git a/gemstone/src/swapper/thorchain/mod.rs b/gemstone/src/swapper/thorchain/mod.rs index baee923b..59d3d466 100644 --- a/gemstone/src/swapper/thorchain/mod.rs +++ b/gemstone/src/swapper/thorchain/mod.rs @@ -5,8 +5,6 @@ mod model; mod provider; use chain::THORChainName; -use num_bigint::BigInt; -use std::str::FromStr; #[derive(Debug, Default)] pub struct ThorChain {} @@ -22,24 +20,6 @@ impl ThorChain { } memo } - - pub fn value_from(&self, value: String, decimals: i32) -> BigInt { - let decimals = decimals - 8; - if decimals > 0 { - BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow(decimals as u32) - } else { - BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow(decimals.unsigned_abs()) - } - } - - pub fn value_to(&self, value: String, decimals: i32) -> BigInt { - let decimals = decimals - 8; - if decimals > 0 { - BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow((decimals).unsigned_abs()) - } else { - BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow((decimals).unsigned_abs()) - } - } } #[cfg(test)] @@ -57,42 +37,4 @@ mod tests { let result = thorchain.data(THORChainName::Bitcoin, memo.clone()); assert_eq!(result, memo); } - - #[test] - fn test_value_from() { - let thorchain = ThorChain::default(); - - let value = "1000000000".to_string(); - - let result = thorchain.value_from(value.clone(), 18); - assert_eq!(result, BigInt::from_str("0").unwrap()); - - let result = thorchain.value_from(value.clone(), 10); - assert_eq!(result, BigInt::from_str("10000000").unwrap()); - - let result = thorchain.value_from(value.clone(), 6); - assert_eq!(result, BigInt::from_str("100000000000").unwrap()); - - let result = thorchain.value_from(value.clone(), 8); - assert_eq!(result, BigInt::from(1000000000)); - } - - #[test] - fn test_value_to() { - let thorchain = ThorChain::default(); - - let value = "10000000".to_string(); - - let result = thorchain.value_to(value.clone(), 18); - assert_eq!(result, BigInt::from_str("100000000000000000").unwrap()); - - let result = thorchain.value_to(value.clone(), 10); - assert_eq!(result, BigInt::from(1000000000)); - - let result = thorchain.value_to(value.clone(), 6); - assert_eq!(result, BigInt::from(100000)); - - let result = thorchain.value_to(value.clone(), 8); - assert_eq!(result, BigInt::from(10000000)); - } } diff --git a/gemstone/src/swapper/thorchain/provider.rs b/gemstone/src/swapper/thorchain/provider.rs index 40977120..0c6f14de 100644 --- a/gemstone/src/swapper/thorchain/provider.rs +++ b/gemstone/src/swapper/thorchain/provider.rs @@ -6,6 +6,7 @@ use crate::swapper::asset::{ AVALANCHE_USDC, AVALANCHE_USDT, BASE_CBBTC, BASE_USDC, ETHEREUM_DAI, ETHEREUM_USDC, ETHEREUM_USDT, ETHEREUM_WBTC, SMARTCHAIN_USDC, SMARTCHAIN_USDT, }; use crate::swapper::thorchain::client::ThorChainSwapClient; +use crate::swapper::utils::converter; use crate::swapper::{ApprovalType, FetchQuoteData, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError}; use crate::swapper::{GemSwapProvider, SwapChainAsset}; use alloy_core::sol_types::SolCall; @@ -57,7 +58,7 @@ impl GemSwapProvider for ThorChain { let from_asset = THORChainAsset::from_asset_id(request.clone().from_asset).ok_or(SwapperError::NotSupportedAsset)?; let to_asset = THORChainAsset::from_asset_id(request.clone().to_asset).ok_or(SwapperError::NotSupportedAsset)?; - let value = self.value_from(request.clone().value, from_asset.decimals as i32); + let value = converter::value_from(request.clone().value, from_asset.decimals as i32); let fee = request.options.clone().fee.unwrap_or_default().thorchain; let quote = client @@ -73,7 +74,7 @@ impl GemSwapProvider for ThorChain { ) .await?; - let to_value = self.value_to(quote.expected_amount_out, to_asset.decimals as i32); + let to_value = converter::value_to(quote.expected_amount_out, to_asset.decimals as i32); let approval: ApprovalType = { if from_asset.use_evm_router() { diff --git a/gemstone/src/swapper/utils.rs b/gemstone/src/swapper/utils.rs new file mode 100644 index 00000000..d11bd064 --- /dev/null +++ b/gemstone/src/swapper/utils.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; + +use num_bigint::BigInt; + +pub mod converter { + use super::*; + + pub fn value_from(value: String, decimals: i32) -> BigInt { + let decimals = decimals - 8; + if decimals > 0 { + BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow(decimals as u32) + } else { + BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow(decimals.unsigned_abs()) + } + } + + pub fn value_to(value: String, decimals: i32) -> BigInt { + let decimals = decimals - 8; + if decimals > 0 { + BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow((decimals).unsigned_abs()) + } else { + BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow((decimals).unsigned_abs()) + } + } +} + +#[cfg(test)] +mod tests { + use super::converter; + use num_bigint::BigInt; + use std::str::FromStr; + + #[test] + fn test_value_from() { + let value = "1000000000".to_string(); + + let result = converter::value_from(value.clone(), 18); + assert_eq!(result, BigInt::from_str("0").unwrap()); + + let result = converter::value_from(value.clone(), 10); + assert_eq!(result, BigInt::from_str("10000000").unwrap()); + + let result = converter::value_from(value.clone(), 6); + assert_eq!(result, BigInt::from_str("100000000000").unwrap()); + + let result = converter::value_from(value.clone(), 8); + assert_eq!(result, BigInt::from(1000000000)); + } + + #[test] + fn test_value_to() { + let value = "10000000".to_string(); + + let result = converter::value_to(value.clone(), 18); + assert_eq!(result, BigInt::from_str("100000000000000000").unwrap()); + + let result = converter::value_to(value.clone(), 10); + assert_eq!(result, BigInt::from(1000000000)); + + let result = converter::value_to(value.clone(), 6); + assert_eq!(result, BigInt::from(100000)); + + let result = converter::value_to(value.clone(), 8); + assert_eq!(result, BigInt::from(10000000)); + } +} diff --git a/gemstone/tests/integration_test.rs b/gemstone/tests/integration_test.rs index 39f80752..a7a676bd 100644 --- a/gemstone/tests/integration_test.rs +++ b/gemstone/tests/integration_test.rs @@ -201,8 +201,8 @@ mod tests { let request = SwapQuoteRequest { from_asset: AssetId::from_chain(Chain::Optimism), to_asset: AssetId::from_chain(Chain::Base), - wallet_address: "0x514BCb1F9AAbb904e6106Bd1052B66d2706dBbb7".into(), - destination_address: "0x514BCb1F9AAbb904e6106Bd1052B66d2706dBbb7".into(), + wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".into(), + destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".into(), value: "20000000000000000".into(), // 0.02 ETH mode: GemSwapMode::ExactIn, options, From daba5dc9d243c1709438038bfbfc60b0ea801480 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Thu, 16 Jan 2025 17:05:37 +0200 Subject: [PATCH 12/16] fix: added tests for domain logic --- gemstone/src/swapper/models.rs | 2 +- gemstone/src/swapper/stargate/client.rs | 13 +-- gemstone/src/swapper/stargate/provider.rs | 98 ++++++++++++++++++----- 3 files changed, 79 insertions(+), 34 deletions(-) diff --git a/gemstone/src/swapper/models.rs b/gemstone/src/swapper/models.rs index acc9719c..e0aa0e7f 100644 --- a/gemstone/src/swapper/models.rs +++ b/gemstone/src/swapper/models.rs @@ -7,7 +7,7 @@ use std::fmt::Debug; static DEFAULT_SLIPPAGE_BPS: u32 = 300; -#[derive(Debug, thiserror::Error, uniffi::Error)] +#[derive(Debug, thiserror::Error, uniffi::Error, PartialEq)] pub enum SwapperError { #[error("Not supported chain")] NotSupportedChain, diff --git a/gemstone/src/swapper/stargate/client.rs b/gemstone/src/swapper/stargate/client.rs index 6d32dc79..47311737 100644 --- a/gemstone/src/swapper/stargate/client.rs +++ b/gemstone/src/swapper/stargate/client.rs @@ -67,20 +67,9 @@ impl StargateClient { } pub fn build_send_param(&self, request: &SwapQuoteRequest) -> Result { - let from_asset = &request.from_asset; - let to_asset = &request.to_asset; - - if from_asset.chain == to_asset.chain { - return Err(SwapperError::NotSupportedPair); - } - - if from_asset.is_native() && !to_asset.is_native() { - return Err(SwapperError::NotSupportedPair); - } - let amount_ld = U256::from_str(request.value.as_str()).unwrap(); - let destination_endpoint = self.get_endpoint_by_chain(&to_asset.chain)?; + let destination_endpoint = self.get_endpoint_by_chain(&request.to_asset.chain)?; Ok(SendParam { dstEid: destination_endpoint.endpoint_id, diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index a82959ec..2fcd9a53 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -8,7 +8,6 @@ use primitives::Chain; use serde::{Deserialize, Serialize}; use crate::{ - debug_println, network::AlienProvider, swapper::{ approval::check_approval_erc20, slippage::apply_slippage_in_bp, utils::converter, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, @@ -64,6 +63,17 @@ impl GemSwapProvider for Stargate { } async fn fetch_quote(&self, request: &SwapQuoteRequest, provider: Arc) -> Result { + let from_asset = &request.from_asset; + let to_asset = &request.to_asset; + + if from_asset.chain == to_asset.chain { + return Err(SwapperError::NotSupportedPair); + } + + if from_asset.is_native() && !to_asset.is_native() { + return Err(SwapperError::NotSupportedPair); + } + let pool = self.client.get_pool_by_asset_id(&request.from_asset)?; let mut send_param = self.client.build_send_param(request)?; @@ -151,6 +161,17 @@ impl GemSwapProvider for Stargate { #[cfg(test)] mod tests { + use std::time::Duration; + + use alloy_primitives::U256; + use primitives::AssetId; + + use crate::{ + config::swap_config::SwapReferralFees, + network::mock::AlienProviderMock, + swapper::{asset::BASE_USDC, GemSwapMode, GemSwapOptions}, + }; + use super::*; #[test] @@ -171,24 +192,59 @@ mod tests { ); } - //#[test] - //fn test_same_chain_quote() { - // let stargate = Stargate::new(); - // let request = SwapQuoteRequest { - // wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), - // from_asset: AssetId::from_chain(Chain::Ethereum), - // to_asset: ETHEREUM_USDT.id.clone(), - // value: U256::from(1).to_string(), - // mode: GemSwapMode::ExactIn, - // destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), - // options: GemSwapOptions { - // slippage_bps: 100, - // fee: Some(SwapReferralFees::default()), - // preferred_providers: vec![], - // }, - // }; - // - // let result = stargate.fetch_quote(&request); - // assert!(matches!(result, Err(SwapperError::NotSupportedPair))); - //} + #[tokio::test] + async fn test_same_chain_quote() { + let stargate = Stargate::new(); + let request = SwapQuoteRequest { + wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + from_asset: AssetId::from_chain(Chain::Ethereum), + to_asset: AssetId::from_chain(Chain::Ethereum), + value: U256::from(1).to_string(), + mode: GemSwapMode::ExactIn, + destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + options: GemSwapOptions { + slippage_bps: 100, + fee: Some(SwapReferralFees::default()), + preferred_providers: vec![], + }, + }; + + let mock = AlienProviderMock { + response: String::from("Hello"), + timeout: Duration::from_millis(100), + }; + + let result = stargate.fetch_quote(&request, Arc::new(mock)).await; + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), SwapperError::NotSupportedPair); + } + + #[tokio::test] + async fn test_native_to_erc20_quote() { + let stargate = Stargate::new(); + let request = SwapQuoteRequest { + wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + from_asset: AssetId::from_chain(Chain::Ethereum), + to_asset: BASE_USDC.id.clone(), + value: U256::from(1).to_string(), + mode: GemSwapMode::ExactIn, + destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), + options: GemSwapOptions { + slippage_bps: 100, + fee: Some(SwapReferralFees::default()), + preferred_providers: vec![], + }, + }; + + let mock = AlienProviderMock { + response: String::from("Hello"), + timeout: Duration::from_millis(100), + }; + + let result = stargate.fetch_quote(&request, Arc::new(mock)).await; + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), SwapperError::NotSupportedPair); + } } From 25fed9abb980bde88878c529d26d2a5d5c189ed6 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Fri, 17 Jan 2025 09:37:12 +0200 Subject: [PATCH 13/16] chore: clippy fix --- Untitled | 2 -- crates/storage/src/database/mod.rs | 2 +- crates/storage/src/database/price.rs | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 Untitled diff --git a/Untitled b/Untitled deleted file mode 100644 index d148bc8e..00000000 --- a/Untitled +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/crates/storage/src/database/mod.rs b/crates/storage/src/database/mod.rs index e544b1dc..b04041f3 100644 --- a/crates/storage/src/database/mod.rs +++ b/crates/storage/src/database/mod.rs @@ -7,7 +7,7 @@ pub mod subscription; use crate::models::asset::AssetLink; use crate::models::*; -use crate::schema::{prices_assets, transactions_addresses}; +use crate::schema::transactions_addresses; use chrono::{DateTime, NaiveDateTime}; use diesel::associations::HasTable; use diesel::dsl::count; diff --git a/crates/storage/src/database/price.rs b/crates/storage/src/database/price.rs index b94364bd..4d094a2c 100644 --- a/crates/storage/src/database/price.rs +++ b/crates/storage/src/database/price.rs @@ -1,4 +1,4 @@ -use crate::schema::{prices, prices_assets}; +use crate::schema::prices_assets; use crate::{models::*, DatabaseClient}; use chrono::NaiveDateTime; use diesel::associations::HasTable; From 9a4605ea8f7cd94243ad678cf97495bc56a6f711 Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Fri, 17 Jan 2025 10:06:27 +0200 Subject: [PATCH 14/16] chore: improve crypto value converter readabilty --- .../primitives/src/crypto_value_converter.rs | 33 +++++++++---------- crates/primitives/src/lib.rs | 2 ++ gemstone/src/swapper/mod.rs | 1 - gemstone/src/swapper/stargate/provider.rs | 10 +++--- gemstone/src/swapper/thorchain/provider.rs | 7 ++-- 5 files changed, 26 insertions(+), 27 deletions(-) rename gemstone/src/swapper/utils.rs => crates/primitives/src/crypto_value_converter.rs (52%) diff --git a/gemstone/src/swapper/utils.rs b/crates/primitives/src/crypto_value_converter.rs similarity index 52% rename from gemstone/src/swapper/utils.rs rename to crates/primitives/src/crypto_value_converter.rs index d11bd064..76156501 100644 --- a/gemstone/src/swapper/utils.rs +++ b/crates/primitives/src/crypto_value_converter.rs @@ -1,32 +1,31 @@ +use num_bigint::BigInt; use std::str::FromStr; -use num_bigint::BigInt; - -pub mod converter { - use super::*; +pub struct CryptoValueConverter {} +impl CryptoValueConverter { pub fn value_from(value: String, decimals: i32) -> BigInt { let decimals = decimals - 8; if decimals > 0 { - BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow(decimals as u32) + BigInt::from_str(&value).unwrap() / BigInt::from(10).pow(decimals as u32) } else { - BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow(decimals.unsigned_abs()) + BigInt::from_str(&value).unwrap() * BigInt::from(10).pow(decimals.unsigned_abs()) } } pub fn value_to(value: String, decimals: i32) -> BigInt { let decimals = decimals - 8; if decimals > 0 { - BigInt::from_str(value.as_str()).unwrap() * BigInt::from(10).pow((decimals).unsigned_abs()) + BigInt::from_str(&value).unwrap() * BigInt::from(10).pow((decimals).unsigned_abs()) } else { - BigInt::from_str(value.as_str()).unwrap() / BigInt::from(10).pow((decimals).unsigned_abs()) + BigInt::from_str(&value).unwrap() / BigInt::from(10).pow((decimals).unsigned_abs()) } } } #[cfg(test)] mod tests { - use super::converter; + use super::*; use num_bigint::BigInt; use std::str::FromStr; @@ -34,16 +33,16 @@ mod tests { fn test_value_from() { let value = "1000000000".to_string(); - let result = converter::value_from(value.clone(), 18); + let result = CryptoValueConverter::value_from(value.clone(), 18); assert_eq!(result, BigInt::from_str("0").unwrap()); - let result = converter::value_from(value.clone(), 10); + let result = CryptoValueConverter::value_from(value.clone(), 10); assert_eq!(result, BigInt::from_str("10000000").unwrap()); - let result = converter::value_from(value.clone(), 6); + let result = CryptoValueConverter::value_from(value.clone(), 6); assert_eq!(result, BigInt::from_str("100000000000").unwrap()); - let result = converter::value_from(value.clone(), 8); + let result = CryptoValueConverter::value_from(value.clone(), 8); assert_eq!(result, BigInt::from(1000000000)); } @@ -51,16 +50,16 @@ mod tests { fn test_value_to() { let value = "10000000".to_string(); - let result = converter::value_to(value.clone(), 18); + let result = CryptoValueConverter::value_to(value.clone(), 18); assert_eq!(result, BigInt::from_str("100000000000000000").unwrap()); - let result = converter::value_to(value.clone(), 10); + let result = CryptoValueConverter::value_to(value.clone(), 10); assert_eq!(result, BigInt::from(1000000000)); - let result = converter::value_to(value.clone(), 6); + let result = CryptoValueConverter::value_to(value.clone(), 6); assert_eq!(result, BigInt::from(100000)); - let result = converter::value_to(value.clone(), 8); + let result = CryptoValueConverter::value_to(value.clone(), 8); assert_eq!(result, BigInt::from(10000000)); } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 84d164b3..8035b018 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -18,6 +18,8 @@ pub use self::price::{Price, PriceFull}; pub mod asset; pub mod config; pub use self::config::{ConfigResponse, ConfigVersions, Release}; +pub mod crypto_value_converter; +pub use self::crypto_value_converter::CryptoValueConverter; pub mod currency; pub use self::asset::Asset; pub mod asset_id; diff --git a/gemstone/src/swapper/mod.rs b/gemstone/src/swapper/mod.rs index 8fdc173a..dd90f229 100644 --- a/gemstone/src/swapper/mod.rs +++ b/gemstone/src/swapper/mod.rs @@ -8,7 +8,6 @@ mod chainlink; mod custom_types; mod eth_rpc; mod permit2_data; -mod utils; mod weth_address; pub mod across; diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index 2fcd9a53..dfa80956 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -4,14 +4,14 @@ use std::sync::Arc; use alloy_primitives::{hex, Address}; use async_trait::async_trait; use gem_evm::stargate::contract::{MessagingFee, SendParam}; -use primitives::Chain; +use primitives::{Chain, CryptoValueConverter}; use serde::{Deserialize, Serialize}; use crate::{ network::AlienProvider, swapper::{ - approval::check_approval_erc20, slippage::apply_slippage_in_bp, utils::converter, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, - SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, + approval::check_approval_erc20, slippage::apply_slippage_in_bp, ApprovalType, FetchQuoteData, GemSwapProvider, SwapChainAsset, SwapProvider, + SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError, }, }; @@ -103,8 +103,8 @@ impl GemSwapProvider for Stargate { let from_decimals = self.client.get_decimals_by_asset_id(&request.from_asset)?; let to_decimals = self.client.get_decimals_by_asset_id(&request.to_asset)?; - let mut to_value = converter::value_from(oft_quote.receipt.amountReceivedLD.to_string(), from_decimals); - to_value = converter::value_to(to_value.to_string(), to_decimals); + let mut to_value = CryptoValueConverter::value_from(oft_quote.receipt.amountReceivedLD.to_string(), from_decimals); + to_value = CryptoValueConverter::value_to(to_value.to_string(), to_decimals); Ok(SwapQuote { from_value: request.value.to_string(), diff --git a/gemstone/src/swapper/thorchain/provider.rs b/gemstone/src/swapper/thorchain/provider.rs index 0c6f14de..6019b8ed 100644 --- a/gemstone/src/swapper/thorchain/provider.rs +++ b/gemstone/src/swapper/thorchain/provider.rs @@ -6,7 +6,6 @@ use crate::swapper::asset::{ AVALANCHE_USDC, AVALANCHE_USDT, BASE_CBBTC, BASE_USDC, ETHEREUM_DAI, ETHEREUM_USDC, ETHEREUM_USDT, ETHEREUM_WBTC, SMARTCHAIN_USDC, SMARTCHAIN_USDT, }; use crate::swapper::thorchain::client::ThorChainSwapClient; -use crate::swapper::utils::converter; use crate::swapper::{ApprovalType, FetchQuoteData, SwapProvider, SwapProviderData, SwapQuote, SwapQuoteData, SwapQuoteRequest, SwapRoute, SwapperError}; use crate::swapper::{GemSwapProvider, SwapChainAsset}; use alloy_core::sol_types::SolCall; @@ -14,7 +13,7 @@ use alloy_primitives::Address; use alloy_primitives::U256; use async_trait::async_trait; use gem_evm::thorchain::contracts::RouterInterface; -use primitives::Chain; +use primitives::{Chain, CryptoValueConverter}; use std::str::FromStr; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; @@ -58,7 +57,7 @@ impl GemSwapProvider for ThorChain { let from_asset = THORChainAsset::from_asset_id(request.clone().from_asset).ok_or(SwapperError::NotSupportedAsset)?; let to_asset = THORChainAsset::from_asset_id(request.clone().to_asset).ok_or(SwapperError::NotSupportedAsset)?; - let value = converter::value_from(request.clone().value, from_asset.decimals as i32); + let value = CryptoValueConverter::value_from(request.clone().value, from_asset.decimals as i32); let fee = request.options.clone().fee.unwrap_or_default().thorchain; let quote = client @@ -74,7 +73,7 @@ impl GemSwapProvider for ThorChain { ) .await?; - let to_value = converter::value_to(quote.expected_amount_out, to_asset.decimals as i32); + let to_value = CryptoValueConverter::value_to(quote.expected_amount_out, to_asset.decimals as i32); let approval: ApprovalType = { if from_asset.use_evm_router() { From 890bfc7f7b8822851741e926a03c5908b9ea6a5e Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Mon, 20 Jan 2025 14:29:32 +0200 Subject: [PATCH 15/16] chore: pr comments --- .../stargate/layer_zero/mock/message_tx.json | 80 ------------ .../src/swapper/stargate/layer_zero/mod.rs | 1 - .../src/swapper/stargate/layer_zero/scan.rs | 121 ------------------ gemstone/src/swapper/stargate/mod.rs | 1 - gemstone/src/swapper/stargate/provider.rs | 82 ++++-------- 5 files changed, 27 insertions(+), 258 deletions(-) delete mode 100644 gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json delete mode 100644 gemstone/src/swapper/stargate/layer_zero/mod.rs delete mode 100644 gemstone/src/swapper/stargate/layer_zero/scan.rs diff --git a/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json b/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json deleted file mode 100644 index 0f41ad07..00000000 --- a/gemstone/src/swapper/stargate/layer_zero/mock/message_tx.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "data": [ - { - "pathway": { - "srcEid": 108, - "dstEid": 102, - "sender": { - "address": "0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa", - "id": "aptos-bridge", - "name": "Aptos Bridge", - "chain": "aptos" - }, - "receiver": { - "address": "0x2762409baa1804d94d8c0bcff8400b78bf915d5b", - "id": "aptos-bridge", - "name": "Aptos Bridge", - "chain": "bsc" - }, - "id": "108-102-0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa-0x2762409baa1804d94d8c0bcff8400b78bf915d5b", - "nonce": 309792 - }, - "source": { - "status": "SUCCEEDED", - "tx": { - "txHash": "0xeb31b3339c4c1bd91597f649fe21ddac2900f301ada3181357a281608a560d47", - "blockHash": "0x722c2003341cce20a5a3d63babd413a1a278c4e943abe2de289cff4f763ec524", - "blockNumber": "275562775", - "blockTimestamp": 1736533130, - "from": "0xc95615aa095c100b18eb6eaa0f0a0f30b9cd96685118a7cbc1a2328a91ca2eda", - "blockConfirmations": 260, - "payload": "0x0100000000000000000000000055d398326f99059ff775485246999027b3197955000000000000000000000000ba4d1d35bce0e8f28e5a3403e7a0b996c5d50ac400000000000186a000", - "value": "3870356", - "readinessTimestamp": 1736533186, - "adapterParams": { - "version": "1", - "dstGasLimit": "150000" - } - } - }, - "destination": { - "status": "SUCCEEDED", - "tx": { - "txHash": "0xede2232a3ac249781197fe87281a03f5f6b38ff0e20253e0708db9e1b7ba8391", - "blockHash": "0x3c2023c675a67ef52c8b3577f8653a01dfec1b2f4d519da9bffc2075d585c904", - "blockNumber": 45650646, - "blockTimestamp": 1736533201 - } - }, - "guid": "0x325f35036d836baeac547cd4bdafe838d1c5897fe63a2392e98a6cdbd584b4c7", - "config": { - "error": false, - "receiveLibrary": "0x4D73AdB72bC3DD368966edD0f0b2148401A178E2", - "inboundConfig": { - "blockConfirmation": 260, - "relayerAddress": "0xA27A2cA24DD28Ce14Fb5f5844b59851F03DCf182", - "oracleAddress": "0x5a54fe5234E811466D5366846283323c954310B2", - "executorAddress": "0xA27A2cA24DD28Ce14Fb5f5844b59851F03DCf182", - "proofType": "2", - "utilsVersion": 1, - "proofVersion": "1", - "proofLibraryAddress": "0x28A5536cA9F36c45A9d2AC8d2B62Fc46Fde024B6", - "ulnVersion": "V2" - }, - "outboundConfig": { - "blockConfirmation": 260, - "relayerAddress": "0x1d8727df513fa2a8785d0834e40b34223daff1affc079574082baadb74b66ee4", - "oracleAddress": "0x12e12de0af996d9611b0b78928cd9f4cbf50d94d972043cdd829baa77a78929b", - "proofType": "2" - }, - "ulnSendVersion": "V2" - }, - "status": { - "name": "DELIVERED", - "message": "Destination transaction confirmed" - }, - "created": "2025-01-10T18:18:57.000Z", - "updated": "2025-01-10T18:20:47.000Z" - } - ] -} diff --git a/gemstone/src/swapper/stargate/layer_zero/mod.rs b/gemstone/src/swapper/stargate/layer_zero/mod.rs deleted file mode 100644 index ebce89cd..00000000 --- a/gemstone/src/swapper/stargate/layer_zero/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod scan; diff --git a/gemstone/src/swapper/stargate/layer_zero/scan.rs b/gemstone/src/swapper/stargate/layer_zero/scan.rs deleted file mode 100644 index fcb9dda0..00000000 --- a/gemstone/src/swapper/stargate/layer_zero/scan.rs +++ /dev/null @@ -1,121 +0,0 @@ -use crate::{ - network::{AlienProvider, AlienTarget}, - swapper::SwapperError, -}; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; - -#[derive(Debug, Clone)] -pub struct LayerZeroScanApi { - pub url: String, - pub provider: Arc, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct MessageResponse { - pub data: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Message { - pub status: MessageStatus, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "SCREAMING_SNAKE_CASE")] -pub enum MessageStatusName { - Inflight, - Confirming, - Failed, - Delivered, - Blocked, - PayloadStored, - ApplicationBurned, - ApplicationSkipped, - UnresolvableCommand, - MalformedCommand, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct MessageStatus { - pub name: MessageStatusName, - pub message: Option, -} - -#[allow(dead_code)] -impl MessageStatus { - pub fn is_delivered(&self) -> bool { - matches!(self.name, MessageStatusName::Delivered) - } - - pub fn is_failed(&self) -> bool { - matches!(self.name, MessageStatusName::Failed) - } - - pub fn is_pending(&self) -> bool { - matches!( - self.name, - MessageStatusName::Inflight | MessageStatusName::Confirming | MessageStatusName::PayloadStored - ) - } -} - -impl LayerZeroScanApi { - pub fn new(provider: Arc) -> Self { - Self { - url: "https://scan.layerzero-api.com/v1".into(), - provider, - } - } - - pub async fn get_message_by_tx(&self, tx_hash: &str) -> Result { - let url = format!("{}/messages/tx/{}", self.url, tx_hash); - let target = AlienTarget::get(&url); - let response = self.provider.request(target).await?; - serde_json::from_slice(&response).map_err(|e| SwapperError::NetworkError { msg: e.to_string() }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_message_tx() { - let message_tx = include_str!("mock/message_tx.json"); - let message_tx: MessageResponse = serde_json::from_str(message_tx).unwrap(); - assert_eq!(message_tx.data.len(), 1); - - let message = &message_tx.data[0]; - assert!(message.status.is_delivered()); - } - - #[test] - fn test_message_status() { - let status = MessageStatus { - name: MessageStatusName::Delivered, - message: None, - }; - assert!(status.is_delivered()); - assert!(!status.is_failed()); - assert!(!status.is_pending()); - - let status = MessageStatus { - name: MessageStatusName::Failed, - message: None, - }; - assert!(!status.is_delivered()); - assert!(status.is_failed()); - assert!(!status.is_pending()); - - let status = MessageStatus { - name: MessageStatusName::Inflight, - message: None, - }; - assert!(!status.is_delivered()); - assert!(!status.is_failed()); - assert!(status.is_pending()); - } -} diff --git a/gemstone/src/swapper/stargate/mod.rs b/gemstone/src/swapper/stargate/mod.rs index 2278a516..c7f32144 100644 --- a/gemstone/src/swapper/stargate/mod.rs +++ b/gemstone/src/swapper/stargate/mod.rs @@ -1,5 +1,4 @@ mod client; mod endpoint; -mod layer_zero; pub mod provider; pub use provider::Stargate; diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index dfa80956..5a33a66f 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::sync::Arc; -use alloy_primitives::{hex, Address}; +use alloy_primitives::{hex, Address, U256}; use async_trait::async_trait; use gem_evm::stargate::contract::{MessagingFee, SendParam}; use primitives::{Chain, CryptoValueConverter}; @@ -15,7 +15,7 @@ use crate::{ }, }; -use super::{client::StargateClient, endpoint::STARGATE_ROUTES, layer_zero::scan::LayerZeroScanApi}; +use super::{client::StargateClient, endpoint::STARGATE_ROUTES}; #[derive(Debug, PartialEq, Serialize, Deserialize)] struct StargateRouteData { @@ -66,27 +66,28 @@ impl GemSwapProvider for Stargate { let from_asset = &request.from_asset; let to_asset = &request.to_asset; - if from_asset.chain == to_asset.chain { - return Err(SwapperError::NotSupportedPair); - } - if from_asset.is_native() && !to_asset.is_native() { return Err(SwapperError::NotSupportedPair); } let pool = self.client.get_pool_by_asset_id(&request.from_asset)?; - let mut send_param = self.client.build_send_param(request)?; + let send_param = self.client.build_send_param(request)?; let oft_quote = self.client.quote_oft(pool, &send_param, provider.clone()).await?; - send_param.minAmountLD = apply_slippage_in_bp(&oft_quote.receipt.amountReceivedLD, request.options.slippage_bps); - let messaging_fee = self.client.quote_send(pool, &send_param, provider.clone()).await?; + let min_amount_ld = apply_slippage_in_bp(&oft_quote.receipt.amountReceivedLD, request.options.slippage_bps); + let final_send_param = SendParam { + amountLD: send_param.amountLD, + minAmountLD: min_amount_ld, + ..send_param + }; + let messaging_fee = self.client.quote_send(pool, &final_send_param, provider.clone()).await?; let approval = if request.from_asset.is_token() { check_approval_erc20( request.wallet_address.clone(), request.from_asset.token_id.clone().unwrap(), pool.address.clone(), - send_param.amountLD, + final_send_param.amountLD, provider.clone(), &request.from_asset.chain, ) @@ -96,7 +97,7 @@ impl GemSwapProvider for Stargate { }; let route_data = StargateRouteData { - send_param: send_param.clone(), + send_param: final_send_param.clone(), fee: messaging_fee, refund_address: request.wallet_address.to_string(), }; @@ -127,35 +128,34 @@ impl GemSwapProvider for Stargate { async fn fetch_quote_data(&self, quote: &SwapQuote, _provider: Arc, _data: FetchQuoteData) -> Result { let pool = self.client.get_pool_by_asset_id("e.request.from_asset)?; let route_data: StargateRouteData = serde_json::from_str("e.data.routes.first().unwrap().route_data).map_err(|_| SwapperError::InvalidRoute)?; - let send_calldata = self.client.send( + let calldata = self.client.send( &route_data.send_param, &route_data.fee, &Address::from_str(route_data.refund_address.as_str()).unwrap(), ); - let mut value_to_send = route_data.fee.nativeFee; - - if quote.request.from_asset.is_native() { - value_to_send += route_data.send_param.amountLD; - } + let amount = if quote.request.from_asset.is_native() { + route_data.send_param.amountLD + } else { + U256::ZERO + }; + let value = route_data.fee.nativeFee + amount; let quote_data = SwapQuoteData { to: pool.address.clone(), - value: value_to_send.to_string(), - data: hex::encode_prefixed(send_calldata.clone()), + value: value.to_string(), + data: hex::encode_prefixed(calldata.clone()), }; + println!("value: {:?}", value); + println!("fee: {:?}", quote_data); + Ok(quote_data) } - async fn get_transaction_status(&self, _chain: Chain, transaction_hash: &str, provider: Arc) -> Result { - let api = LayerZeroScanApi::new(provider.clone()); - let response = api.get_message_by_tx(transaction_hash).await?; - let messages = response.data; - let message = messages.first().ok_or(SwapperError::NetworkError { - msg: "Unable to check transaction status using Stargate Provider: No message found".into(), - })?; - Ok(message.status.is_delivered()) + async fn get_transaction_status(&self, _chain: Chain, _transaction_hash: &str, _provider: Arc) -> Result { + // TODO: implement onchain scanner solution + todo!() } } @@ -192,34 +192,6 @@ mod tests { ); } - #[tokio::test] - async fn test_same_chain_quote() { - let stargate = Stargate::new(); - let request = SwapQuoteRequest { - wallet_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), - from_asset: AssetId::from_chain(Chain::Ethereum), - to_asset: AssetId::from_chain(Chain::Ethereum), - value: U256::from(1).to_string(), - mode: GemSwapMode::ExactIn, - destination_address: "0x0655c6AbdA5e2a5241aa08486bd50Cf7d475CF24".to_string(), - options: GemSwapOptions { - slippage_bps: 100, - fee: Some(SwapReferralFees::default()), - preferred_providers: vec![], - }, - }; - - let mock = AlienProviderMock { - response: String::from("Hello"), - timeout: Duration::from_millis(100), - }; - - let result = stargate.fetch_quote(&request, Arc::new(mock)).await; - - assert!(result.is_err()); - assert_eq!(result.unwrap_err(), SwapperError::NotSupportedPair); - } - #[tokio::test] async fn test_native_to_erc20_quote() { let stargate = Stargate::new(); From fb52837c517fac588bb86cdf0b89cd9d677e37aa Mon Sep 17 00:00:00 2001 From: kuchmenko Date: Mon, 20 Jan 2025 14:34:15 +0200 Subject: [PATCH 16/16] chore: improved code style --- gemstone/src/swapper/stargate/provider.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gemstone/src/swapper/stargate/provider.rs b/gemstone/src/swapper/stargate/provider.rs index 5a33a66f..80fdb1fb 100644 --- a/gemstone/src/swapper/stargate/provider.rs +++ b/gemstone/src/swapper/stargate/provider.rs @@ -71,14 +71,14 @@ impl GemSwapProvider for Stargate { } let pool = self.client.get_pool_by_asset_id(&request.from_asset)?; - let send_param = self.client.build_send_param(request)?; + let initial_send_param = self.client.build_send_param(request)?; - let oft_quote = self.client.quote_oft(pool, &send_param, provider.clone()).await?; + let oft_quote = self.client.quote_oft(pool, &initial_send_param, provider.clone()).await?; let min_amount_ld = apply_slippage_in_bp(&oft_quote.receipt.amountReceivedLD, request.options.slippage_bps); let final_send_param = SendParam { - amountLD: send_param.amountLD, + amountLD: initial_send_param.amountLD, minAmountLD: min_amount_ld, - ..send_param + ..initial_send_param }; let messaging_fee = self.client.quote_send(pool, &final_send_param, provider.clone()).await?;