1use crate::{
2 CallOption,
3 call_handler_ext::CallHandlerExt,
4 contract_ext::ContractExt,
5};
6use anyhow::Result;
7use fuels::{
8 prelude::*,
9 tx::StorageSlot,
10 types::{
11 ContractId,
12 Identity,
13 },
14};
15
16abigen!(
17 Contract(
18 name = "TradingAccountOracle",
19 abi = "artifacts/trade-account-oracle/trade-account-oracle-abi.json"
20 ),
21 Contract(
22 name = "TradingAccount",
23 abi = "artifacts/trade-account/trade-account-abi.json"
24 ),
25 Contract(
26 name = "TradingAccountProxy",
27 abi = "artifacts/trade-account-proxy/trade-account-proxy-abi.json",
28 ),
29 Contract(
30 name = "TradingAccountProxy0_69_1",
31 abi = "artifacts/0.69.1/trade-account-proxy/artifacts/release/trade-account-proxy-abi.json",
32 ),
33);
34pub const TRADE_ACCOUNT_BYTECODE: &[u8] =
35 include_bytes!("../artifacts/trade-account/trade-account.bin");
36pub const TRADE_ACCOUNT_STORAGE: &[u8] =
37 include_bytes!("../artifacts/trade-account/trade-account-storage_slots.json");
38pub const TRADE_ACCOUNT_PROXY_BYTECODE: &[u8] =
39 include_bytes!("../artifacts/trade-account-proxy/trade-account-proxy.bin");
40pub const TRADE_ACCOUNT_PROXY_STORAGE: &[u8] = include_bytes!(
41 "../artifacts/trade-account-proxy/trade-account-proxy-storage_slots.json"
42);
43pub const TRADE_ACCOUNT_ORACLE_BYTECODE: &[u8] =
44 include_bytes!("../artifacts/trade-account-oracle/trade-account-oracle.bin");
45pub const TRADE_ACCOUNT_ORACLE_STORAGE: &[u8] = include_bytes!(
46 "../artifacts/trade-account-oracle/trade-account-oracle-storage_slots.json"
47);
48
49#[derive(Clone)]
52pub struct TradeAccountDeployConfig {
53 pub trade_account_bytecode: Vec<u8>,
55 pub oracle_bytecode: Vec<u8>,
57 pub proxy_bytecode: Vec<u8>,
59 pub trade_account_storage_slots: Vec<StorageSlot>,
61 pub oracle_storage_slots: Vec<StorageSlot>,
63 pub proxy_storage_slots: Vec<StorageSlot>,
65 pub max_words_per_blob: usize,
67 pub salt: Salt,
69}
70
71impl TradeAccountDeployConfig {
72 pub fn old_0_69_1() -> Self {
73 pub const TRADE_ACCOUNT_BYTECODE_0_69_1: &[u8] = include_bytes!(
74 "../artifacts/0.69.1/trade-account/artifacts/release/trade-account.bin"
75 );
76 pub const TRADE_ACCOUNT_STORAGE_0_69_1: &[u8] = include_bytes!(
77 "../artifacts/0.69.1/trade-account/artifacts/release/trade-account-storage_slots.json"
78 );
79 pub const TRADE_ACCOUNT_PROXY_BYTECODE_0_69_1: &[u8] = include_bytes!(
80 "../artifacts/0.69.1/trade-account-proxy/artifacts/release/trade-account-proxy.bin"
81 );
82 pub const TRADE_ACCOUNT_PROXY_STORAGE_0_69_1: &[u8] = include_bytes!(
83 "../artifacts/0.69.1/trade-account-proxy/artifacts/release/trade-account-proxy-storage_slots.json"
84 );
85 pub const TRADE_ACCOUNT_ORACLE_BYTECODE_0_69_1: &[u8] = include_bytes!(
86 "../artifacts/0.69.1/trade-account-oracle/artifacts/release/trade-account-oracle.bin"
87 );
88 pub const TRADE_ACCOUNT_ORACLE_STORAGE_0_69_1: &[u8] = include_bytes!(
89 "../artifacts/0.69.1/trade-account-oracle/artifacts/release/trade-account-oracle-storage_slots.json"
90 );
91
92 Self {
93 trade_account_bytecode: TRADE_ACCOUNT_BYTECODE_0_69_1.to_vec(),
94 oracle_bytecode: TRADE_ACCOUNT_ORACLE_BYTECODE_0_69_1.to_vec(),
95 proxy_bytecode: TRADE_ACCOUNT_PROXY_BYTECODE_0_69_1.to_vec(),
96 trade_account_storage_slots: serde_json::from_slice(
97 TRADE_ACCOUNT_STORAGE_0_69_1,
98 )
99 .unwrap(),
100 oracle_storage_slots: serde_json::from_slice(
101 TRADE_ACCOUNT_ORACLE_STORAGE_0_69_1,
102 )
103 .unwrap(),
104 proxy_storage_slots: serde_json::from_slice(
105 TRADE_ACCOUNT_PROXY_STORAGE_0_69_1,
106 )
107 .unwrap(),
108 max_words_per_blob: 10_000,
109 salt: Salt::default(),
110 }
111 }
112}
113
114impl Default for TradeAccountDeployConfig {
115 fn default() -> Self {
116 Self {
117 trade_account_bytecode: TRADE_ACCOUNT_BYTECODE.to_vec(),
118 oracle_bytecode: TRADE_ACCOUNT_ORACLE_BYTECODE.to_vec(),
119 proxy_bytecode: TRADE_ACCOUNT_PROXY_BYTECODE.to_vec(),
120 trade_account_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_STORAGE)
121 .unwrap(),
122 oracle_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_ORACLE_STORAGE)
123 .unwrap(),
124 proxy_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_PROXY_STORAGE)
125 .unwrap(),
126 max_words_per_blob: 100_000,
127 salt: Salt::default(),
128 }
129 }
130}
131
132#[derive(Clone)]
135pub struct TradeAccountDeploy<W> {
136 pub oracle: TradingAccountOracle<W>,
138 pub oracle_id: ContractId,
140 pub trade_account_blob_id: BlobId,
142 pub deployer_wallet: W,
144 pub proxy: Option<TradingAccountProxy<W>>,
146 pub proxy_id: Option<ContractId>,
148}
149
150pub struct TradeAccountBlob {
151 pub id: BlobId,
153 pub exists: bool,
155 pub blob: Blob,
157}
158
159impl<W> TradeAccountDeploy<W>
160where
161 W: Account + Clone,
162{
163 pub fn change_wallet(mut self, wallet: &W) -> Self {
164 self.deployer_wallet = wallet.clone();
165 self.oracle = self.oracle.with_account(wallet.clone());
166
167 if let Some(proxy) = self.proxy {
168 self.proxy = Some(proxy.with_account(wallet.clone()));
169 }
170
171 self
172 }
173
174 pub async fn from_oracle_id(
175 deployer_wallet: &W,
176 oracle_id: ContractId,
177 ) -> Result<Self>
178 where
179 W: Account + Clone,
180 {
181 let oracle: TradingAccountOracle<W> =
182 TradingAccountOracle::new(oracle_id, deployer_wallet.clone());
183 let trade_account_blob_id = oracle
184 .methods()
185 .get_trade_account_impl()
186 .simulate(Execution::state_read_only())
187 .await?
188 .value
189 .ok_or_else(|| anyhow::anyhow!("Trade account implementation not set"))?
190 .into();
191 Ok(Self {
192 oracle,
193 oracle_id,
194 trade_account_blob_id,
195 deployer_wallet: deployer_wallet.clone(),
196 proxy: None,
197 proxy_id: None,
198 })
199 }
200
201 pub fn trade_account_blob_from_config(
202 config: &TradeAccountDeployConfig,
203 ) -> Result<Blob> {
204 let blobs = Contract::regular(
205 config.trade_account_bytecode.clone(),
206 config.salt,
207 config.trade_account_storage_slots.clone(),
208 )
209 .convert_to_loader(config.max_words_per_blob)?
210 .blobs()
211 .to_vec();
212 let blob = blobs[0].clone();
213 Ok(blob)
214 }
215
216 pub fn trade_account_proxy_blob_from_config(
217 config: &TradeAccountDeployConfig,
218 ) -> Result<Blob> {
219 let blobs = Contract::regular(
220 config.proxy_bytecode.clone(),
221 config.salt,
222 config.proxy_storage_slots.clone(),
223 )
224 .convert_to_loader(config.max_words_per_blob)?
225 .blobs()
226 .to_vec();
227 let blob = blobs[0].clone();
228 Ok(blob)
229 }
230
231 pub async fn trade_account_blob(
232 deployer_wallet: &W,
233 config: &TradeAccountDeployConfig,
234 ) -> Result<TradeAccountBlob> {
235 let blob = Self::trade_account_blob_from_config(config)?;
236 let blob_id = blob.id();
237 let blob_exists = deployer_wallet.try_provider()?.blob_exists(blob_id).await?;
238
239 Ok(TradeAccountBlob {
240 id: blob_id,
241 exists: blob_exists,
242 blob: blob.clone(),
243 })
244 }
245
246 pub async fn deploy_trade_account_blob(
257 deployer_wallet: &W,
258 config: &DeployConfig,
259 ) -> Result<BlobId> {
260 match config {
261 DeployConfig::Old0_69_1(config) | DeployConfig::Latest(config) => {
262 let trade_account_blob =
263 Self::trade_account_blob(deployer_wallet, config).await?;
264 if !trade_account_blob.exists {
265 let mut builder = BlobTransactionBuilder::default()
266 .with_blob(trade_account_blob.blob.clone());
267
268 deployer_wallet.adjust_for_fee(&mut builder, 0).await?;
269 deployer_wallet.add_witnesses(&mut builder)?;
270
271 let tx = builder.build(&deployer_wallet.try_provider()?).await?;
272
273 deployer_wallet
274 .try_provider()?
275 .send_transaction_and_await_commit(tx)
276 .await?
277 .check(None)?;
278 }
279 Ok(trade_account_blob.id)
280 }
281 }
282 }
283
284 pub async fn deploy_oracle(
296 deployer_wallet: &W,
297 trade_account_blob_id: &BlobId,
298 config: &DeployConfig,
299 ) -> Result<(TradingAccountOracle<W>, ContractId)> {
300 match config {
301 DeployConfig::Old0_69_1(_) => Err(anyhow::anyhow!(
302 "Deployment with old 0.69.1 config is not supported for oracle"
303 )),
304 DeployConfig::Latest(config) => {
305 let contract = Contract::regular(
306 config.oracle_bytecode.clone(),
307 config.salt,
308 config.oracle_storage_slots.clone(),
309 )
310 .with_configurables(TradingAccountOracleConfigurables::default())
311 .with_salt(config.salt);
312 let contract_id = contract.contract_id();
313 let instance =
314 TradingAccountOracle::new(contract_id, deployer_wallet.clone());
315 let contract_exists = deployer_wallet
316 .try_provider()?
317 .contract_exists(&contract_id)
318 .await?;
319
320 if !contract_exists {
321 contract
322 .deploy(deployer_wallet, TxPolicies::default())
323 .await?;
324 instance
325 .methods()
326 .initialize(
327 Identity::Address(deployer_wallet.address()),
328 ContractId::from(*trade_account_blob_id),
329 )
330 .call()
331 .await?;
332 }
333
334 Ok((instance, contract_id))
335 }
336 }
337 }
338
339 pub fn trade_account_contract(
340 oracle_id: &ContractId,
341 owner_identity: &Identity,
342 config: &DeployConfig,
343 ) -> Result<Contract<fuels::programs::contract::Regular>> {
344 match config {
345 DeployConfig::Old0_69_1(config) => {
346 let configurables = TradingAccountProxy0_69_1Configurables::default()
347 .with_ORACLE_CONTRACT_ID(*oracle_id)?
348 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?;
349 let contract = Contract::regular(
350 config.proxy_bytecode.clone(),
351 config.salt,
352 config.proxy_storage_slots.clone(),
353 )
354 .with_configurables(configurables);
355 Ok(contract)
356 }
357 DeployConfig::Latest(config) => {
358 let configurables = TradingAccountProxyConfigurables::default()
359 .with_ORACLE_CONTRACT_ID(*oracle_id)?
360 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?;
361 let contract = Contract::regular(
362 config.proxy_bytecode.clone(),
363 config.salt,
364 config.proxy_storage_slots.clone(),
365 )
366 .with_configurables(configurables);
367 Ok(contract)
368 }
369 }
370 }
371
372 pub async fn deploy_proxy(
385 deployer_wallet: &Wallet,
386 owner_identity: &Identity,
387 oracle_id: ContractId,
388 config: &DeployConfig,
389 call_option: &CallOption,
390 ) -> Result<(TradingAccountProxy<Wallet>, ContractId)>
391 where
392 W: Account + Clone,
393 {
394 let contract = Self::trade_account_contract(&oracle_id, owner_identity, config)?;
395 let result = deployer_wallet
396 .try_provider()?
397 .contract_exists(&contract.contract_id())
398 .await?;
399
400 let id = if !result {
401 match call_option {
402 CallOption::AwaitBlock => {
403 contract
404 .deploy(deployer_wallet, TxPolicies::default())
405 .await?
406 .contract_id
407 }
408 CallOption::AwaitPreconfirmation(ops) => {
409 let result = contract
410 .almost_sync_deploy(
411 deployer_wallet,
412 &ops.data_builder,
413 &ops.utxo_manager,
414 &ops.tx_config,
415 )
416 .await?;
417
418 if let Some(tx_id) = result.tx_id {
421 deployer_wallet
422 .try_provider()?
423 .client()
424 .await_transaction_commit(&tx_id)
425 .await?;
426 }
427 result.contract_id
428 }
429 }
430 } else {
431 contract.contract_id()
432 };
433
434 let proxy = TradingAccountProxy::new(id, deployer_wallet.clone());
435
436 let response = proxy
437 .methods()
438 .proxy_owner()
439 .simulate(Execution::state_read_only())
440 .await?;
441
442 if response.value == State::Uninitialized {
443 let call_handler =
444 proxy.methods().initialize().with_contract_ids(&[oracle_id]);
445
446 match call_option {
447 CallOption::AwaitBlock => {
448 call_handler.call().await?;
449 }
450 CallOption::AwaitPreconfirmation(ops) => {
451 call_handler
452 .almost_sync_call(
453 &ops.data_builder,
454 &ops.utxo_manager,
455 &ops.tx_config,
456 )
457 .await?
458 .tx_status?;
459 }
460 }
461 }
462
463 Ok((proxy, id))
464 }
465
466 pub async fn deploy(
483 deployer_wallet: &W,
484 config: &DeployConfig,
485 ) -> Result<TradeAccountDeploy<W>> {
486 let trade_account_blob_id =
487 Self::deploy_trade_account_blob(deployer_wallet, config).await?;
488 let (oracle, oracle_id) =
489 Self::deploy_oracle(deployer_wallet, &trade_account_blob_id, config).await?;
490 let trade_account_blob_id = oracle
491 .methods()
492 .get_trade_account_impl()
493 .simulate(Execution::state_read_only())
494 .await?
495 .value
496 .unwrap();
497
498 Ok(TradeAccountDeploy {
499 oracle,
500 oracle_id,
501 trade_account_blob_id: trade_account_blob_id.into(),
502 deployer_wallet: deployer_wallet.clone(),
503 proxy: None,
504 proxy_id: None,
505 })
506 }
507}
508
509pub enum DeployConfig {
510 Old0_69_1(TradeAccountDeployConfig),
512 Latest(TradeAccountDeployConfig),
513}
514
515impl DeployConfig {
516 pub fn config(&self) -> &TradeAccountDeployConfig {
517 match self {
518 DeployConfig::Old0_69_1(config) | DeployConfig::Latest(config) => config,
519 }
520 }
521}
522
523impl TradeAccountDeploy<Wallet> {
524 pub async fn deploy_with_account(
525 &self,
526 owner_identity: &Identity,
527 config: &DeployConfig,
528 call_option: &CallOption,
529 ) -> Result<Self> {
530 let (proxy, proxy_id) = Self::deploy_proxy(
531 &self.deployer_wallet,
532 owner_identity,
533 self.oracle_id,
534 config,
535 call_option,
536 )
537 .await?;
538 Ok(Self {
539 proxy: Some(proxy),
540 proxy_id: Some(proxy_id),
541 oracle: self.oracle.clone(),
542 oracle_id: self.oracle_id,
543 trade_account_blob_id: self.trade_account_blob_id,
544 deployer_wallet: self.deployer_wallet.clone(),
545 })
546 }
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552 use fuels::test_helpers::{
553 WalletsConfig,
554 launch_custom_provider_and_get_wallets,
555 };
556
557 #[tokio::test]
558 async fn test_trade_account_sdk() {
559 let mut wallets = launch_custom_provider_and_get_wallets(
561 WalletsConfig::new(Some(1), Some(1), Some(1_000_000_000)),
562 None,
563 None,
564 )
565 .await
566 .unwrap();
567 let wallet = wallets.pop().unwrap();
568
569 let config = DeployConfig::Latest(TradeAccountDeployConfig::default());
571 let deployment = TradeAccountDeploy::deploy(&wallet, &config)
572 .await
573 .unwrap()
574 .deploy_with_account(
575 &wallet.address().into(),
576 &config,
577 &CallOption::AwaitBlock,
578 )
579 .await
580 .unwrap();
581
582 let provider = wallet.try_provider().unwrap();
584
585 let oracle_contract_info = provider
587 .contract_exists(&deployment.oracle_id)
588 .await
589 .unwrap();
590 assert!(oracle_contract_info, "Oracle contract should exist");
591
592 let proxy_contract_info = provider
594 .contract_exists(&deployment.proxy_id.unwrap())
595 .await
596 .unwrap();
597 assert!(proxy_contract_info, "Proxy contract should exist");
598
599 let stored_blob_id = deployment
601 .oracle
602 .methods()
603 .get_trade_account_impl()
604 .simulate(Execution::state_read_only())
605 .await
606 .unwrap()
607 .value;
608
609 assert_eq!(
610 deployment.trade_account_blob_id,
611 BlobId::from(stored_blob_id.unwrap()),
612 "Trade account blob ID should match"
613 );
614 }
615}