Skip to content

Commit 70e8426

Browse files
authoredJan 15, 2025··
feat(CA): adding USDT asset to the chain abstraction (#882)
* feat(CA): adding USDT chain abstraction support * fix: updating the integration tests
1 parent b151271 commit 70e8426

File tree

3 files changed

+254
-76
lines changed

3 files changed

+254
-76
lines changed
 

‎integration/chain_orchestrator.test.ts

+130-28
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,47 @@ describe('Chain abstraction orchestrator', () => {
99
'function approve(address spender, uint256 amount) public returns (bool)'
1010
]);
1111

12-
// Address with 3 USDC on Base chain
12+
// Funding address
1313
const from_address_with_funds = "0x2aae531a81461f029cd55cb46703211c9227ba05";
14-
const usdc_funds_on_base = 3_000_000;
15-
const usdc_funds_on_optimism = 1_057_151;
14+
15+
// Receiver address
16+
const receiver_address = "0x739ff389c8eBd9339E69611d46Eec6212179BB67";
17+
18+
// Supported chains
19+
const chain_id_optimism = "eip155:10";
20+
const chain_id_base = "eip155:8453";
21+
const chain_id_arbitrum = "eip155:42161";
22+
23+
// Current funds on different chains
1624
const usdc_token_symbol = "USDC";
25+
let usdc_funds = {};
26+
usdc_funds[chain_id_base] = 3_000_000;
27+
usdc_funds[chain_id_optimism] = 1_057_151;
28+
29+
const usdt_token_symbol = "USDT";
30+
let usdt_funds = {};
31+
usdt_funds[chain_id_arbitrum] = 3_388_000;
32+
usdt_funds[chain_id_optimism] = 1_050_000;
33+
1734
// Amount to send to Optimism
1835
const amount_to_send = 3_000_000
1936
// Amount bridging slippage
2037
const amount_slippage = 2; // +2% topup
21-
// How much needs to be topped up
22-
const amount_to_topup = Math.round(amount_to_send - usdc_funds_on_optimism);
23-
const amount_to_topup_with_fees = Math.round(((amount_to_topup * amount_slippage) / 100) + amount_to_topup);
24-
25-
const receiver_address = "0x739ff389c8eBd9339E69611d46Eec6212179BB67";
26-
const chain_id_optimism = "eip155:10";
27-
const usdc_contract_optimism = "0x0b2c639c533813f4aa9d7837caf62653d097ff85";
28-
const chain_id_base = "eip155:8453";
29-
const usdc_contract_base = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
38+
39+
let usdc_contracts = {};
40+
usdc_contracts[chain_id_optimism] = "0x0b2c639c533813f4aa9d7837caf62653d097ff85";
41+
usdc_contracts[chain_id_base] = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
42+
usdc_contracts[chain_id_arbitrum] = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
43+
let usdt_contracts = {};
44+
usdt_contracts[chain_id_optimism] = "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58";
45+
usdt_contracts[chain_id_arbitrum] = "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9";
46+
usdt_contracts[chain_id_base] = "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2";
3047

3148
let orchestration_id = "";
3249

3350
it('bridging unavailable (insufficient funds)', async () => {
3451
// Having the USDC balance on Base chain less then the amount to send
35-
const amount_to_send_in_decimals = usdc_funds_on_base + 10_000_000
52+
const amount_to_send_in_decimals = usdc_funds[chain_id_base] + 10_000_000
3653
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
3754
receiver_address,
3855
amount_to_send_in_decimals,
@@ -41,7 +58,7 @@ describe('Chain abstraction orchestrator', () => {
4158
let transactionObj = {
4259
transaction: {
4360
from: from_address_with_funds,
44-
to: usdc_contract_optimism,
61+
to: usdc_contracts[chain_id_optimism],
4562
value: "0x00", // Zero native tokens
4663
input: data_encoded,
4764
chainId: chain_id_optimism,
@@ -58,7 +75,7 @@ describe('Chain abstraction orchestrator', () => {
5875

5976
it('bridging unavailable (empty wallet)', async () => {
6077
// Checking an empty wallet
61-
const amount_to_send_in_decimals = usdc_funds_on_base
78+
const amount_to_send_in_decimals = usdc_funds[chain_id_base]
6279
const empty_wallet_address = ethers.Wallet.createRandom().address
6380
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
6481
receiver_address,
@@ -68,7 +85,7 @@ describe('Chain abstraction orchestrator', () => {
6885
let transactionObj = {
6986
transaction: {
7087
from: empty_wallet_address,
71-
to: usdc_contract_optimism,
88+
to: usdc_contracts[chain_id_optimism],
7289
value: "0x00", // Zero native tokens
7390
input: data_encoded,
7491
chainId: chain_id_optimism,
@@ -94,7 +111,7 @@ describe('Chain abstraction orchestrator', () => {
94111
let transactionObj = {
95112
transaction: {
96113
from: from_address_with_funds,
97-
to: usdc_contract_optimism,
114+
to: usdc_contracts[chain_id_optimism],
98115
value: "0x00", // Zero native tokens
99116
input: data_encoded,
100117
chainId: chain_id_optimism,
@@ -110,8 +127,14 @@ describe('Chain abstraction orchestrator', () => {
110127

111128
})
112129

113-
it('bridging routes (routes available)', async () => {
114-
// Sending USDC to Optimism, but having the USDC balance on Base chain
130+
it('bridging routes (routes available, USDC Optimism ⇄ USDT Arbitrum)', async () => {
131+
// Sending USDC to Optimism, but having the maximum balance of USDT on Arbitrum chain
132+
// which expected to be used for bridging
133+
134+
// How much needs to be topped up
135+
const amount_to_topup = Math.round(amount_to_send - usdc_funds[chain_id_optimism]);
136+
const amount_to_topup_with_fees = Math.round(((amount_to_topup * amount_slippage) / 100) + amount_to_topup);
137+
115138
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
116139
receiver_address,
117140
amount_to_send,
@@ -120,7 +143,7 @@ describe('Chain abstraction orchestrator', () => {
120143
let transactionObj = {
121144
transaction: {
122145
from: from_address_with_funds,
123-
to: usdc_contract_optimism,
146+
to: usdc_contracts[chain_id_optimism],
124147
value: "0x00", // Zero native tokens
125148
input: data_encoded,
126149
chainId: chain_id_optimism,
@@ -140,31 +163,31 @@ describe('Chain abstraction orchestrator', () => {
140163

141164
// First transaction expected to be the approval transaction
142165
const approvalTransaction = data.transactions[0]
143-
expect(approvalTransaction.chainId).toBe(chain_id_base)
166+
expect(approvalTransaction.chainId).toBe(chain_id_arbitrum)
144167
expect(approvalTransaction.nonce).not.toBe("0x00")
145168
expect(() => BigInt(approvalTransaction.gasLimit)).not.toThrow();
146169
const decodedData = erc20Interface.decodeFunctionData('approve', approvalTransaction.input);
147170
if (decodedData.amount < BigInt(amount_to_topup_with_fees)) {
148171
throw new Error(`Expected amount is lower then the minimal required`);
149172
}
150173

151-
// Second transaction expected to be the bridging to the Base
174+
// Second transaction expected to be the bridging to the Arbitrum
152175
const bridgingTransaction = data.transactions[1]
153-
expect(bridgingTransaction.chainId).toBe(chain_id_base)
176+
expect(bridgingTransaction.chainId).toBe(chain_id_arbitrum)
154177
expect(bridgingTransaction.nonce).not.toBe("0x00")
155178
expect(() => BigInt(approvalTransaction.gasLimit)).not.toThrow();
156179

157180
// Check for the initialTransaction
158181
const initialTransaction = data.initialTransaction;
159182
expect(initialTransaction.from).toBe(from_address_with_funds.toLowerCase());
160-
expect(initialTransaction.to).toBe(usdc_contract_optimism.toLowerCase());
183+
expect(initialTransaction.to).toBe(usdc_contracts[chain_id_optimism].toLowerCase());
161184
expect(initialTransaction.gasLimit).not.toBe("0x00");
162185

163186
// Check the metadata fundingFrom
164187
const fundingFrom = data.metadata.fundingFrom[0]
165-
expect(fundingFrom.chainId).toBe(chain_id_base)
166-
expect(fundingFrom.symbol).toBe(usdc_token_symbol)
167-
expect(fundingFrom.tokenContract).toBe(usdc_contract_base)
188+
expect(fundingFrom.chainId).toBe(chain_id_arbitrum)
189+
expect(fundingFrom.symbol).toBe(usdt_token_symbol)
190+
expect(fundingFrom.tokenContract).toBe(usdt_contracts[chain_id_arbitrum].toLowerCase())
168191
if (BigInt(fundingFrom.amount) <= BigInt(amount_to_topup_with_fees)) {
169192
throw new Error(`Expected amount is lower then the minimal required`);
170193
}
@@ -175,7 +198,86 @@ describe('Chain abstraction orchestrator', () => {
175198
const initialTransactionMetadata = data.metadata.initialTransaction
176199
expect(initialTransactionMetadata.symbol).toBe(usdc_token_symbol)
177200
expect(initialTransactionMetadata.transferTo).toBe(receiver_address.toLowerCase())
178-
expect(initialTransactionMetadata.tokenContract).toBe(usdc_contract_optimism.toLowerCase())
201+
expect(initialTransactionMetadata.tokenContract).toBe(usdc_contracts[chain_id_optimism].toLowerCase())
202+
203+
// Check the metadata checkIn
204+
expect(typeof data.metadata.checkIn).toBe('number')
205+
206+
// Set the Orchestration ID for the next test
207+
orchestration_id = data.orchestrationId;
208+
})
209+
210+
it('bridging routes (routes available, USDT Optimism ⇄ USDT Arbitrum)', async () => {
211+
// Sending USDT to Optimism, but having the USDT balance on Arbitrum.
212+
213+
// How much needs to be topped up
214+
const amount_to_topup = Math.round(amount_to_send - usdt_funds[chain_id_optimism]);
215+
const amount_to_topup_with_fees = Math.round(((amount_to_topup * amount_slippage) / 100) + amount_to_topup);
216+
217+
const data_encoded = erc20Interface.encodeFunctionData('transfer', [
218+
receiver_address,
219+
amount_to_send,
220+
]);
221+
222+
let transactionObj = {
223+
transaction: {
224+
from: from_address_with_funds,
225+
to: usdt_contracts[chain_id_optimism],
226+
value: "0x00", // Zero native tokens
227+
input: data_encoded,
228+
chainId: chain_id_optimism,
229+
}
230+
}
231+
232+
let resp: any = await httpClient.post(
233+
`${baseUrl}/v1/ca/orchestrator/route?projectId=${projectId}`,
234+
transactionObj
235+
)
236+
expect(resp.status).toBe(200)
237+
238+
const data = resp.data
239+
expect(typeof data.orchestrationId).toBe('string')
240+
// Expecting 2 transactions in the route
241+
expect(data.transactions.length).toBe(2)
242+
243+
// First transaction expected to be the approval transaction
244+
const approvalTransaction = data.transactions[0]
245+
expect(approvalTransaction.chainId).toBe(chain_id_arbitrum)
246+
expect(approvalTransaction.nonce).not.toBe("0x00")
247+
expect(() => BigInt(approvalTransaction.gasLimit)).not.toThrow();
248+
const decodedData = erc20Interface.decodeFunctionData('approve', approvalTransaction.input);
249+
if (decodedData.amount < BigInt(amount_to_topup_with_fees)) {
250+
throw new Error(`Expected amount is lower then the minimal required`);
251+
}
252+
253+
// Second transaction expected to be the bridging to the Arbitrum
254+
const bridgingTransaction = data.transactions[1]
255+
expect(bridgingTransaction.chainId).toBe(chain_id_arbitrum)
256+
expect(bridgingTransaction.nonce).not.toBe("0x00")
257+
expect(() => BigInt(approvalTransaction.gasLimit)).not.toThrow();
258+
259+
// Check for the initialTransaction
260+
const initialTransaction = data.initialTransaction;
261+
expect(initialTransaction.from).toBe(from_address_with_funds.toLowerCase());
262+
expect(initialTransaction.to).toBe(usdt_contracts[chain_id_optimism].toLowerCase());
263+
expect(initialTransaction.gasLimit).not.toBe("0x00");
264+
265+
// Check the metadata fundingFrom
266+
const fundingFrom = data.metadata.fundingFrom[0]
267+
expect(fundingFrom.chainId).toBe(chain_id_arbitrum)
268+
expect(fundingFrom.symbol).toBe(usdt_token_symbol)
269+
expect(fundingFrom.tokenContract).toBe(usdt_contracts[chain_id_arbitrum].toLowerCase())
270+
if (BigInt(fundingFrom.amount) <= BigInt(amount_to_topup_with_fees)) {
271+
throw new Error(`Expected amount is lower then the minimal required`);
272+
}
273+
if (BigInt(fundingFrom.bridgingFee) != BigInt(fundingFrom.amount - amount_to_topup)){
274+
throw new Error(`Expected bridging fee is incorrect. `);
275+
}
276+
// Check the initialTransaction metadata
277+
const initialTransactionMetadata = data.metadata.initialTransaction
278+
expect(initialTransactionMetadata.symbol).toBe(usdt_token_symbol)
279+
expect(initialTransactionMetadata.transferTo).toBe(receiver_address.toLowerCase())
280+
expect(initialTransactionMetadata.tokenContract).toBe(usdt_contracts[chain_id_optimism].toLowerCase())
179281

180282
// Check the metadata checkIn
181283
expect(typeof data.metadata.checkIn).toBe('number')

‎src/handlers/chain_agnostic/assets.rs

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
use {
2+
alloy::primitives::{address, Address},
3+
phf::phf_map,
4+
};
5+
6+
pub struct AssetMetadata {
7+
pub decimals: u8,
8+
}
9+
10+
/// Asset simulation parameters to override the asset's balance state
11+
pub struct SimulationParams {
12+
/// Asset contract balance storage slot number
13+
pub balance_storage_slot: u64,
14+
/// Balance override for the asset
15+
pub balance: u128,
16+
}
17+
18+
pub struct AssetEntry {
19+
pub metadata: AssetMetadata,
20+
pub simulation: SimulationParams,
21+
/// Asset contracts per CAIP-2 chain ID
22+
pub contracts: &'static phf::Map<&'static str, Address>,
23+
}
24+
25+
static USDC_CONTRACTS: phf::Map<&'static str, Address> = phf_map! {
26+
// Optimism
27+
"eip155:10" => address!("0b2c639c533813f4aa9d7837caf62653d097ff85"),
28+
// Base
29+
"eip155:8453" => address!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
30+
// Arbitrum
31+
"eip155:42161" => address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
32+
};
33+
34+
static USDT_CONTRACTS: phf::Map<&'static str, Address> = phf_map! {
35+
// Optimism
36+
"eip155:10" => address!("94b008aA00579c1307B0EF2c499aD98a8ce58e58"),
37+
// Base
38+
"eip155:8453" => address!("fde4C96c8593536E31F229EA8f37b2ADa2699bb2"),
39+
// Arbitrum
40+
"eip155:42161" => address!("Fd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
41+
};
42+
43+
pub static BRIDGING_ASSETS: phf::Map<&'static str, AssetEntry> = phf_map! {
44+
"USDC" => AssetEntry {
45+
metadata: AssetMetadata {
46+
decimals: 6,
47+
},
48+
simulation: SimulationParams {
49+
balance_storage_slot: 9,
50+
balance: 99000000000,
51+
},
52+
contracts: &USDC_CONTRACTS,
53+
},
54+
"USDT" => AssetEntry {
55+
metadata: AssetMetadata {
56+
decimals: 6,
57+
},
58+
simulation: SimulationParams {
59+
balance_storage_slot: 0,
60+
balance: 99000000000,
61+
},
62+
contracts: &USDT_CONTRACTS,
63+
},
64+
};

‎src/handlers/chain_agnostic/mod.rs

+60-48
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,16 @@ use {
99
utils::crypto::get_erc20_contract_balance,
1010
Metrics,
1111
},
12-
alloy::primitives::{address, Address, B256, U256},
12+
alloy::primitives::{Address, B256, U256},
13+
assets::{SimulationParams, BRIDGING_ASSETS},
1314
ethers::{types::H160 as EthersH160, utils::keccak256},
14-
phf::phf_map,
1515
serde::{Deserialize, Serialize},
1616
std::{collections::HashMap, str::FromStr, sync::Arc},
1717
tracing::debug,
1818
yttrium::chain_abstraction::api::Transaction,
1919
};
2020

21+
pub mod assets;
2122
pub mod route;
2223
pub mod status;
2324

@@ -27,19 +28,6 @@ pub const BRIDGING_AMOUNT_SLIPPAGE: i8 = 50; // 50%
2728
/// Bridging timeout in seconds
2829
pub const BRIDGING_TIMEOUT: u64 = 1800; // 30 minutes
2930

30-
/// Available assets for Bridging
31-
pub static BRIDGING_AVAILABLE_ASSETS: phf::Map<&'static str, phf::Map<&'static str, Address>> = phf_map! {
32-
"USDC" => phf_map! {
33-
// Optimism
34-
"eip155:10" => address!("0b2c639c533813f4aa9d7837caf62653d097ff85"),
35-
// Base
36-
"eip155:8453" => address!("833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
37-
// Arbitrum
38-
"eip155:42161" => address!("af88d065e77c8cC2239327C5EDb3A432268e5831"),
39-
},
40-
};
41-
pub const USDC_DECIMALS: u8 = 6;
42-
4331
/// The status polling interval in ms for the client
4432
pub const STATUS_POLLING_INTERVAL: u64 = 3000; // 3 seconds
4533

@@ -66,25 +54,34 @@ pub enum BridgingStatus {
6654
Error,
6755
}
6856

69-
/// Return available assets contracts addresses for the given chain_id
70-
pub fn get_bridging_assets_contracts_for_chain(chain_id: &str) -> Vec<String> {
71-
BRIDGING_AVAILABLE_ASSETS
57+
/// Return available assets names and contract addresses for the given chain_id
58+
pub fn get_bridging_assets_contracts_for_chain(chain_id: &str) -> Vec<(String, Address)> {
59+
BRIDGING_ASSETS
7260
.entries()
73-
.filter_map(|(_token_symbol, chain_map)| {
74-
chain_map
61+
.filter_map(|(token_symbol, asset_entry)| {
62+
asset_entry
63+
.contracts
7564
.entries()
7665
.find(|(chain, _)| **chain == chain_id)
77-
.map(|(_, contract_address)| contract_address.to_string())
66+
.map(|(_, contract_address)| (token_symbol.to_string(), *contract_address))
7867
})
7968
.collect()
8069
}
8170

71+
/// Returns simulation params for the bridging asset
72+
pub fn get_simulation_params_for_asset(asset_name: &str) -> Option<&SimulationParams> {
73+
BRIDGING_ASSETS
74+
.entries()
75+
.find(|(name, _)| **name == asset_name)
76+
.map(|(_, asset_entry)| &asset_entry.simulation)
77+
}
78+
8279
/// Check is the address is supported bridging asset and return the token symbol and decimals
8380
pub fn find_supported_bridging_asset(chain_id: &str, contract: Address) -> Option<(String, u8)> {
84-
for (symbol, chain_map) in BRIDGING_AVAILABLE_ASSETS.entries() {
85-
for (chain, contract_address) in chain_map.entries() {
81+
for (symbol, asset_entry) in BRIDGING_ASSETS.entries() {
82+
for (chain, contract_address) in asset_entry.contracts.entries() {
8683
if *chain == chain_id && contract == *contract_address {
87-
return Some((symbol.to_string(), USDC_DECIMALS));
84+
return Some((symbol.to_string(), asset_entry.metadata.decimals));
8885
}
8986
}
9087
}
@@ -133,20 +130,25 @@ pub async fn check_bridging_for_erc20_transfer(
133130
exclude_contract_address: Address,
134131
) -> Result<Option<BridgingAsset>, RpcError> {
135132
// Check ERC20 tokens balance for each of supported assets
136-
let mut contracts_per_chain: HashMap<(String, String), Vec<String>> = HashMap::new();
137-
for (token_symbol, chain_map) in BRIDGING_AVAILABLE_ASSETS.entries() {
138-
for (chain_id, contract_address) in chain_map.entries() {
133+
let mut contracts_per_chain: HashMap<(String, String, u8), Vec<String>> = HashMap::new();
134+
for (token_symbol, asset_entry) in BRIDGING_ASSETS.entries() {
135+
for (chain_id, contract_address) in asset_entry.contracts.entries() {
139136
if *chain_id == exclude_chain_id && *contract_address == exclude_contract_address {
140137
continue;
141138
}
142139
contracts_per_chain
143-
.entry((token_symbol.to_string(), (*chain_id).to_string()))
140+
.entry((
141+
token_symbol.to_string(),
142+
(*chain_id).to_string(),
143+
asset_entry.metadata.decimals,
144+
))
144145
.or_default()
145146
.push((*contract_address).to_string());
146147
}
147148
}
148-
// Making the check for each chain_id
149-
for ((token_symbol, chain_id), contracts) in contracts_per_chain {
149+
// Making the check for each chain_id and use the asset with the highest balance
150+
let mut bridging_asset_found = None;
151+
for ((token_symbol, chain_id, decimals), contracts) in contracts_per_chain {
150152
let erc20_balances = check_erc20_balances(
151153
rpc_project_id.clone(),
152154
sender,
@@ -159,18 +161,27 @@ pub async fn check_bridging_for_erc20_transfer(
159161
.await?;
160162
for (contract_address, current_balance) in erc20_balances {
161163
if current_balance >= value {
162-
return Ok(Some(BridgingAsset {
163-
chain_id,
164-
token_symbol,
164+
// Use the asset with the highest found balance
165+
if let Some(BridgingAsset {
166+
current_balance: existing_balance,
167+
..
168+
}) = &bridging_asset_found
169+
{
170+
if current_balance <= *existing_balance {
171+
continue;
172+
}
173+
}
174+
bridging_asset_found = Some(BridgingAsset {
175+
chain_id: chain_id.clone(),
176+
token_symbol: token_symbol.clone(),
165177
contract_address,
166178
current_balance,
167-
// We are supporting only USDC for now which have a fixed decimals
168-
decimals: USDC_DECIMALS,
169-
}));
179+
decimals,
180+
});
170181
}
171182
}
172183
}
173-
Ok(None)
184+
Ok(bridging_asset_found)
174185
}
175186

176187
/// Compute the simulation state override balance for a given balance
@@ -207,23 +218,24 @@ pub async fn get_assets_changes_from_simulation(
207218
metrics: Arc<Metrics>,
208219
) -> Result<(Vec<Erc20AssetChange>, u64), RpcError> {
209220
// Fill the state overrides for the source address for each of the supported
210-
// assets on the initial tx chain
221+
// assets on the initial tx chain for the balance slot
211222
let state_overrides = {
212223
let mut state_overrides = HashMap::new();
213224
let assets_contracts =
214225
get_bridging_assets_contracts_for_chain(&transaction.chain_id.clone());
215226
let mut account_state = HashMap::new();
216-
account_state.insert(
217-
// Since we are using only USDC for the bridging,
218-
// we can hardcode the storage slot for the contract which is 9
219-
compute_simulation_storage_slot(transaction.from, 9),
220-
compute_simulation_balance(99000000000),
221-
);
222-
for contract in assets_contracts {
223-
state_overrides.insert(
224-
Address::from_str(&contract).unwrap_or_default(),
225-
account_state.clone(),
227+
for (asset_name, asset_contract) in assets_contracts {
228+
let Some(simulation_params) = get_simulation_params_for_asset(&asset_name) else {
229+
continue;
230+
};
231+
account_state.insert(
232+
compute_simulation_storage_slot(
233+
transaction.from,
234+
simulation_params.balance_storage_slot,
235+
),
236+
compute_simulation_balance(simulation_params.balance),
226237
);
238+
state_overrides.insert(asset_contract, account_state.clone());
227239
}
228240
state_overrides
229241
};

0 commit comments

Comments
 (0)
Please sign in to comment.