Skip to content

Commit 00c1106

Browse files
authored
feat(CA): adding USDS asset support (#981)
1 parent b1d4c12 commit 00c1106

File tree

4 files changed

+130
-8
lines changed

4 files changed

+130
-8
lines changed

integration/chain_orchestrator.test.ts

+41-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ describe('Chain abstraction orchestrator', () => {
3131
usdt_funds[chain_id_arbitrum] = 3_388_000;
3232
usdt_funds[chain_id_optimism] = 1_050_000;
3333

34+
const usds_token_symbol = "USDS";
35+
let usds_funds = {};
36+
// Using string amouns for USDS, as it has 18 decimals
37+
usds_funds[chain_id_optimism] = "902165684795715063";
38+
3439
// Amount to send to Optimism
3540
const amount_to_send = 3_000_000
3641

@@ -42,6 +47,9 @@ describe('Chain abstraction orchestrator', () => {
4247
usdt_contracts[chain_id_optimism] = "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58";
4348
usdt_contracts[chain_id_arbitrum] = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9";
4449
usdt_contracts[chain_id_base] = "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2";
50+
let usds_contracts = {};
51+
usds_contracts[chain_id_optimism] = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1";
52+
usds_contracts[chain_id_arbitrum] = "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1";
4553

4654
let orchestration_id = "";
4755

@@ -283,7 +291,7 @@ describe('Chain abstraction orchestrator', () => {
283291
})
284292

285293
it('bridging routes (routes available, USDT Optimism → USDT Arbitrum)', async () => {
286-
// Sending USDT to Arbitrum, but having the USDT balance on Optimism.
294+
// Sending USDT on Arbitrum, but having the USDT balance on Optimism.
287295
let amount_to_send = usdt_funds[chain_id_arbitrum] + 500_000;
288296

289297
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
@@ -313,6 +321,38 @@ describe('Chain abstraction orchestrator', () => {
313321
expect(data.transactions.length).toBe(2)
314322
})
315323

324+
it('bridging routes (routes available, USDC Base → USDS(DAI) Optimism)', async () => {
325+
// Sending USDS on Optimism, but having the USDC balance on Base.
326+
let amount_to_send = "2802165684795715100";
327+
328+
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
329+
receiver_address,
330+
amount_to_send,
331+
]);
332+
333+
let transactionObj = {
334+
transaction: {
335+
from: from_address_with_funds,
336+
to: usds_contracts[chain_id_optimism],
337+
value: "0x00", // Zero native tokens
338+
input: data_encoded,
339+
chainId: chain_id_optimism,
340+
}
341+
}
342+
343+
let resp: any = await httpClient.post(
344+
`${baseUrl}/v1/ca/orchestrator/route?projectId=${projectId}`,
345+
transactionObj
346+
)
347+
expect(resp.status).toBe(200)
348+
349+
const data = resp.data
350+
expect(typeof data.orchestrationId).toBe('string')
351+
352+
// Expecting 2 transactions in the route
353+
expect(data.transactions.length).toBe(2)
354+
})
355+
316356
it('bridging status', async () => {
317357
let resp: any = await httpClient.get(
318358
`${baseUrl}/v1/ca/orchestrator/status?projectId=${projectId}&orchestrationId=${orchestration_id}`,

src/handlers/chain_agnostic/assets.rs

+21
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ static USDT_CONTRACTS: phf::Map<&'static str, Address> = phf_map! {
3838
"eip155:42161" => address!("Fd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
3939
};
4040

41+
static USDS_CONTRACTS: phf::Map<&'static str, Address> = phf_map! {
42+
// Optimism
43+
"eip155:10" => address!("DA10009cBd5D07dd0CeCc66161FC93D7c9000da1"),
44+
// Arbitrum
45+
"eip155:42161" => address!("DA10009cBd5D07dd0CeCc66161FC93D7c9000da1"),
46+
};
47+
4148
pub static BRIDGING_ASSETS: phf::Map<&'static str, AssetEntry> = phf_map! {
4249
"USDC" => AssetEntry {
4350
metadata: AssetMetadata {
@@ -68,4 +75,18 @@ pub static BRIDGING_ASSETS: phf::Map<&'static str, AssetEntry> = phf_map! {
6875
},
6976
contracts: &USDT_CONTRACTS,
7077
},
78+
"USDS" => AssetEntry {
79+
metadata: AssetMetadata {
80+
decimals: 18,
81+
},
82+
simulation: SimulationParams {
83+
// Must be in sync with the `USDS_CONTRACTS` from above
84+
balance_storage_slots: &phf_map! {
85+
"eip155:10" => 2u64,
86+
"eip155:42161" => 2u64,
87+
},
88+
balance: 99000000000000000000000,
89+
},
90+
contracts: &USDS_CONTRACTS,
91+
},
7192
};

src/handlers/chain_agnostic/mod.rs

+53-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use {
1313
assets::{SimulationParams, BRIDGING_ASSETS},
1414
ethers::{types::H160 as EthersH160, utils::keccak256},
1515
serde::{Deserialize, Serialize},
16-
std::{collections::HashMap, str::FromStr, sync::Arc},
16+
std::{cmp::Ordering, collections::HashMap, str::FromStr, sync::Arc},
1717
tracing::debug,
1818
yttrium::chain_abstraction::api::Transaction,
1919
};
@@ -23,7 +23,7 @@ pub mod route;
2323
pub mod status;
2424

2525
/// How much to multiply the bridging fee amount to cover bridging fee volatility
26-
pub const BRIDGING_FEE_SLIPPAGE: i8 = 75; // 75%
26+
pub const BRIDGING_FEE_SLIPPAGE: i16 = 200; // 200%
2727

2828
/// Bridging timeout in seconds
2929
pub const BRIDGING_TIMEOUT: u64 = 1800; // 30 minutes
@@ -125,6 +125,7 @@ pub struct BridgingAsset {
125125
/// Checking available assets amount for bridging excluding the initial transaction
126126
/// asset, prioritizing the asset with the highest balance or the asset with the
127127
/// same symbol to avoid unnecessary swapping
128+
#[allow(clippy::too_many_arguments)]
128129
pub async fn check_bridging_for_erc20_transfer(
129130
rpc_project_id: String,
130131
session_id: Option<String>,
@@ -135,6 +136,8 @@ pub async fn check_bridging_for_erc20_transfer(
135136
exclude_contract_address: Address,
136137
// Using the same asset as a priority for bridging
137138
token_symbol_priority: String,
139+
// Applying token decimals for the value to compare between different tokens
140+
amount_token_decimals: u8,
138141
) -> Result<Option<BridgingAsset>, RpcError> {
139142
// Check ERC20 tokens balance for each of supported assets
140143
let mut contracts_per_chain: HashMap<(String, String, u8), Vec<String>> = HashMap::new();
@@ -168,7 +171,8 @@ pub async fn check_bridging_for_erc20_transfer(
168171
)
169172
.await?;
170173
for (contract_address, current_balance) in erc20_balances {
171-
if current_balance >= value {
174+
// Check if the balance compared to the transfer value is enough, applied to the transfer token decimals
175+
if convert_amount(current_balance, decimals, amount_token_decimals) >= value {
172176
// Use the priority asset if found
173177
if token_symbol == token_symbol_priority {
174178
return Ok(Some(BridgingAsset {
@@ -314,3 +318,49 @@ pub async fn get_assets_changes_from_simulation(
314318

315319
Ok((asset_changes, gas_used))
316320
}
321+
322+
/// Convert the amount between different decimals
323+
pub fn convert_amount(amount: U256, from_decimals: u8, to_decimals: u8) -> U256 {
324+
match from_decimals.cmp(&to_decimals) {
325+
Ordering::Equal => amount,
326+
Ordering::Greater => {
327+
// Reducing decimals: divide by 10^(from_decimals - to_decimals)
328+
let diff = from_decimals - to_decimals;
329+
let exp = U256::from(diff as u64);
330+
let factor = U256::from(10).pow(exp);
331+
amount / factor
332+
}
333+
Ordering::Less => {
334+
// Increasing decimals: multiply by 10^(to_decimals - from_decimals)
335+
let diff = to_decimals - from_decimals;
336+
let exp = U256::from(diff as u64);
337+
let factor = U256::from(10).pow(exp);
338+
amount * factor
339+
}
340+
}
341+
}
342+
343+
#[cfg(test)]
344+
mod tests {
345+
use super::*;
346+
use std::str::FromStr;
347+
348+
#[test]
349+
fn test_convert_amount() {
350+
let amount = U256::from_str("12345678901234567890").unwrap();
351+
let converted = convert_amount(amount, 18, 18);
352+
assert_eq!(converted, amount);
353+
354+
// Converting 500 USDT (6 decimals) to 18 decimals.
355+
let usdt_amount = U256::from(500_000_000u64);
356+
let converted = convert_amount(usdt_amount, 6, 18);
357+
let expected = U256::from_str("500000000000000000000").unwrap();
358+
assert_eq!(converted, expected);
359+
360+
// Converting 500 DAI (18 decimals) to 6 decimals.
361+
let dai_amount = U256::from_str("500000000000000000000").unwrap();
362+
let converted = convert_amount(dai_amount, 18, 6);
363+
let expected = U256::from(500_000_000u64);
364+
assert_eq!(converted, expected);
365+
}
366+
}

src/handlers/chain_agnostic/route.rs

+15-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use {
22
super::{
3-
super::HANDLER_TASK_METRICS, check_bridging_for_erc20_transfer,
3+
super::HANDLER_TASK_METRICS, check_bridging_for_erc20_transfer, convert_amount,
44
find_supported_bridging_asset, get_assets_changes_from_simulation, BridgingStatus,
55
StorageBridgingItem, BRIDGING_FEE_SLIPPAGE, STATUS_POLLING_INTERVAL,
66
},
@@ -302,7 +302,7 @@ async fn handler_internal(
302302
.add_ca_no_bridging_needed(ChainAbstractionNoBridgingNeededType::SufficientFunds);
303303
return Ok(no_bridging_needed_response.into_response());
304304
}
305-
let erc20_topup_value = asset_transfer_value - erc20_balance;
305+
let mut erc20_topup_value = asset_transfer_value - erc20_balance;
306306

307307
// Check for possible bridging funds by iterating over supported assets
308308
// or return an insufficient funds error
@@ -314,6 +314,7 @@ async fn handler_internal(
314314
initial_transaction.chain_id.clone(),
315315
asset_transfer_contract,
316316
initial_tx_token_symbol.clone(),
317+
initial_tx_token_decimals,
317318
)
318319
.await?
319320
else {
@@ -333,6 +334,13 @@ async fn handler_internal(
333334
let bridge_decimals = bridging_asset.decimals;
334335
let current_bridging_asset_balance = bridging_asset.current_balance;
335336

337+
// Applying decimals differences between initial token and bridging token
338+
erc20_topup_value = convert_amount(
339+
erc20_topup_value,
340+
initial_tx_token_decimals,
341+
bridge_decimals,
342+
);
343+
336344
// Get Quotes for the bridging
337345
let quotes = state
338346
.providers
@@ -374,7 +382,8 @@ async fn handler_internal(
374382
let bridging_amount = serde_json::from_value::<QuoteRoute>(best_route.clone())?.to_amount;
375383
let bridging_amount =
376384
U256::from_str(&bridging_amount).map_err(|_| RpcError::InvalidValue(bridging_amount))?;
377-
let bridging_fee = erc20_topup_value - bridging_amount;
385+
let bridging_fee = erc20_topup_value
386+
- convert_amount(bridging_amount, initial_tx_token_decimals, bridge_decimals);
378387

379388
// Calculate the required bridging topup amount with the bridging fee
380389
// and bridging fee * slippage to cover volatility
@@ -437,7 +446,9 @@ async fn handler_internal(
437446
let bridging_amount = serde_json::from_value::<QuoteRoute>(best_route.clone())?.to_amount;
438447
let bridging_amount =
439448
U256::from_str(&bridging_amount).map_err(|_| RpcError::InvalidValue(bridging_amount))?;
440-
if erc20_topup_value > bridging_amount {
449+
if erc20_topup_value
450+
> convert_amount(bridging_amount, initial_tx_token_decimals, bridge_decimals)
451+
{
441452
error!(
442453
"The final bridging amount:{} is less than the topup amount:{}",
443454
bridging_amount, erc20_topup_value

0 commit comments

Comments
 (0)