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);
30pub const TRADE_ACCOUNT_BYTECODE: &[u8] =
31 include_bytes!("../artifacts/trade-account/trade-account.bin");
32pub const TRADE_ACCOUNT_STORAGE: &[u8] =
33 include_bytes!("../artifacts/trade-account/trade-account-storage_slots.json");
34pub const TRADE_ACCOUNT_PROXY_BYTECODE: &[u8] =
35 include_bytes!("../artifacts/trade-account-proxy/trade-account-proxy.bin");
36pub const TRADE_ACCOUNT_PROXY_STORAGE: &[u8] = include_bytes!(
37 "../artifacts/trade-account-proxy/trade-account-proxy-storage_slots.json"
38);
39pub const TRADE_ACCOUNT_ORACLE_BYTECODE: &[u8] =
40 include_bytes!("../artifacts/trade-account-oracle/trade-account-oracle.bin");
41pub const TRADE_ACCOUNT_ORACLE_STORAGE: &[u8] = include_bytes!(
42 "../artifacts/trade-account-oracle/trade-account-oracle-storage_slots.json"
43);
44
45#[derive(Clone)]
48pub struct TradeAccountDeployConfig {
49 pub trade_account_bytecode: Vec<u8>,
51 pub oracle_bytecode: Vec<u8>,
53 pub proxy_bytecode: Vec<u8>,
55 pub trade_account_storage_slots: Vec<StorageSlot>,
57 pub oracle_storage_slots: Vec<StorageSlot>,
59 pub proxy_storage_slots: Vec<StorageSlot>,
61 pub max_words_per_blob: usize,
63 pub salt: Salt,
65}
66
67impl TradeAccountDeployConfig {}
68
69impl Default for TradeAccountDeployConfig {
70 fn default() -> Self {
71 Self {
72 trade_account_bytecode: TRADE_ACCOUNT_BYTECODE.to_vec(),
73 oracle_bytecode: TRADE_ACCOUNT_ORACLE_BYTECODE.to_vec(),
74 proxy_bytecode: TRADE_ACCOUNT_PROXY_BYTECODE.to_vec(),
75 trade_account_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_STORAGE)
76 .unwrap(),
77 oracle_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_ORACLE_STORAGE)
78 .unwrap(),
79 proxy_storage_slots: serde_json::from_slice(TRADE_ACCOUNT_PROXY_STORAGE)
80 .unwrap(),
81 max_words_per_blob: 100_000,
82 salt: Salt::default(),
83 }
84 }
85}
86
87#[derive(Clone)]
90pub struct TradeAccountDeploy<W> {
91 pub oracle: TradingAccountOracle<W>,
93 pub oracle_id: ContractId,
95 pub trade_account_blob_id: BlobId,
97 pub deployer_wallet: W,
99 pub proxy: Option<TradingAccountProxy<W>>,
101 pub proxy_id: Option<ContractId>,
103}
104
105pub struct TradeAccountBlob {
106 pub id: BlobId,
108 pub exists: bool,
110 pub blob: Blob,
112}
113
114impl<W> TradeAccountDeploy<W>
115where
116 W: Account + Clone,
117{
118 pub fn change_wallet(mut self, wallet: &W) -> Self {
119 self.deployer_wallet = wallet.clone();
120 self.oracle = self.oracle.with_account(wallet.clone());
121
122 if let Some(proxy) = self.proxy {
123 self.proxy = Some(proxy.with_account(wallet.clone()));
124 }
125
126 self
127 }
128
129 pub async fn from_oracle_id(
130 deployer_wallet: &W,
131 oracle_id: ContractId,
132 ) -> Result<Self>
133 where
134 W: Account + Clone,
135 {
136 let oracle: TradingAccountOracle<W> =
137 TradingAccountOracle::new(oracle_id, deployer_wallet.clone());
138 let trade_account_blob_id = oracle
139 .methods()
140 .get_trade_account_impl()
141 .simulate(Execution::state_read_only())
142 .await?
143 .value
144 .ok_or_else(|| anyhow::anyhow!("Trade account implementation not set"))?
145 .into();
146 Ok(Self {
147 oracle,
148 oracle_id,
149 trade_account_blob_id,
150 deployer_wallet: deployer_wallet.clone(),
151 proxy: None,
152 proxy_id: None,
153 })
154 }
155
156 pub fn trade_account_blob_from_config(
157 config: &TradeAccountDeployConfig,
158 ) -> Result<Blob> {
159 let blobs = Contract::regular(
160 config.trade_account_bytecode.clone(),
161 config.salt,
162 config.trade_account_storage_slots.clone(),
163 )
164 .convert_to_loader(config.max_words_per_blob)?
165 .blobs()
166 .to_vec();
167 let blob = blobs[0].clone();
168 Ok(blob)
169 }
170
171 pub fn trade_account_proxy_blob_from_config(
172 config: &TradeAccountDeployConfig,
173 ) -> Result<Blob> {
174 let blobs = Contract::regular(
175 config.proxy_bytecode.clone(),
176 config.salt,
177 config.proxy_storage_slots.clone(),
178 )
179 .convert_to_loader(config.max_words_per_blob)?
180 .blobs()
181 .to_vec();
182 let blob = blobs[0].clone();
183 Ok(blob)
184 }
185
186 pub async fn trade_account_blob(
187 deployer_wallet: &W,
188 config: &TradeAccountDeployConfig,
189 ) -> Result<TradeAccountBlob> {
190 let blob = Self::trade_account_blob_from_config(config)?;
191 let blob_id = blob.id();
192 let blob_exists = deployer_wallet.try_provider()?.blob_exists(blob_id).await?;
193
194 Ok(TradeAccountBlob {
195 id: blob_id,
196 exists: blob_exists,
197 blob: blob.clone(),
198 })
199 }
200
201 pub async fn deploy_trade_account_blob(
212 deployer_wallet: &W,
213 config: &DeployConfig,
214 ) -> Result<BlobId> {
215 match config {
216 DeployConfig::Latest(config) => {
217 let trade_account_blob =
218 Self::trade_account_blob(deployer_wallet, config).await?;
219 if !trade_account_blob.exists {
220 let mut builder = BlobTransactionBuilder::default()
221 .with_blob(trade_account_blob.blob.clone());
222
223 deployer_wallet.adjust_for_fee(&mut builder, 0).await?;
224 deployer_wallet.add_witnesses(&mut builder)?;
225
226 let tx = builder.build(&deployer_wallet.try_provider()?).await?;
227
228 deployer_wallet
229 .try_provider()?
230 .send_transaction_and_await_commit(tx)
231 .await?
232 .check(None)?;
233 }
234 Ok(trade_account_blob.id)
235 }
236 }
237 }
238
239 pub async fn deploy_oracle(
251 deployer_wallet: &W,
252 trade_account_blob_id: &BlobId,
253 config: &DeployConfig,
254 ) -> Result<(TradingAccountOracle<W>, ContractId)> {
255 match config {
256 DeployConfig::Latest(config) => {
257 let contract = Contract::regular(
258 config.oracle_bytecode.clone(),
259 config.salt,
260 config.oracle_storage_slots.clone(),
261 )
262 .with_configurables(TradingAccountOracleConfigurables::default())
263 .with_salt(config.salt);
264 let contract_id = contract.contract_id();
265 let instance =
266 TradingAccountOracle::new(contract_id, deployer_wallet.clone());
267 let contract_exists = deployer_wallet
268 .try_provider()?
269 .contract_exists(&contract_id)
270 .await?;
271
272 if !contract_exists {
273 contract
274 .deploy(deployer_wallet, TxPolicies::default())
275 .await?;
276 instance
277 .methods()
278 .initialize(
279 Identity::Address(deployer_wallet.address()),
280 ContractId::from(*trade_account_blob_id),
281 )
282 .call()
283 .await?;
284 }
285
286 Ok((instance, contract_id))
287 }
288 }
289 }
290
291 pub fn trade_account_contract(
292 oracle_id: &ContractId,
293 owner_identity: &Identity,
294 config: &DeployConfig,
295 ) -> Result<Contract<fuels::programs::contract::Regular>> {
296 match config {
297 DeployConfig::Latest(config) => {
298 let configurables = TradingAccountProxyConfigurables::default()
299 .with_ORACLE_CONTRACT_ID(*oracle_id)?
300 .with_INITIAL_OWNER(State::Initialized(*owner_identity))?;
301 let contract = Contract::regular(
302 config.proxy_bytecode.clone(),
303 config.salt,
304 config.proxy_storage_slots.clone(),
305 )
306 .with_configurables(configurables);
307 Ok(contract)
308 }
309 }
310 }
311
312 pub async fn deploy_proxy(
325 deployer_wallet: &Wallet,
326 owner_identity: &Identity,
327 oracle_id: ContractId,
328 config: &DeployConfig,
329 call_option: &CallOption,
330 ) -> Result<(TradingAccountProxy<Wallet>, ContractId)>
331 where
332 W: Account + Clone,
333 {
334 let contract = Self::trade_account_contract(&oracle_id, owner_identity, config)?;
335 let result = deployer_wallet
336 .try_provider()?
337 .contract_exists(&contract.contract_id())
338 .await?;
339
340 let id = if !result {
341 match call_option {
342 CallOption::AwaitBlock => {
343 contract
344 .deploy(deployer_wallet, TxPolicies::default())
345 .await?
346 .contract_id
347 }
348 CallOption::AwaitPreconfirmation(ops) => {
349 let result = contract
350 .almost_sync_deploy(
351 deployer_wallet,
352 &ops.data_builder,
353 &ops.utxo_manager,
354 &ops.tx_config,
355 )
356 .await?;
357
358 if let Some(tx_id) = result.tx_id {
361 deployer_wallet
362 .try_provider()?
363 .client()
364 .await_transaction_commit(&tx_id)
365 .await?;
366 }
367 result.contract_id
368 }
369 }
370 } else {
371 contract.contract_id()
372 };
373
374 let proxy = TradingAccountProxy::new(id, deployer_wallet.clone());
375
376 let response = proxy
377 .methods()
378 .proxy_owner()
379 .simulate(Execution::state_read_only())
380 .await?;
381
382 if response.value == State::Uninitialized {
383 let call_handler =
384 proxy.methods().initialize().with_contract_ids(&[oracle_id]);
385
386 match call_option {
387 CallOption::AwaitBlock => {
388 call_handler.call().await?;
389 }
390 CallOption::AwaitPreconfirmation(ops) => {
391 call_handler
392 .almost_sync_call(
393 &ops.data_builder,
394 &ops.utxo_manager,
395 &ops.tx_config,
396 )
397 .await?
398 .tx_status?;
399 }
400 }
401 }
402
403 Ok((proxy, id))
404 }
405
406 pub async fn deploy(
423 deployer_wallet: &W,
424 config: &DeployConfig,
425 ) -> Result<TradeAccountDeploy<W>> {
426 let trade_account_blob_id =
427 Self::deploy_trade_account_blob(deployer_wallet, config).await?;
428 let (oracle, oracle_id) =
429 Self::deploy_oracle(deployer_wallet, &trade_account_blob_id, config).await?;
430 let trade_account_blob_id = oracle
431 .methods()
432 .get_trade_account_impl()
433 .simulate(Execution::state_read_only())
434 .await?
435 .value
436 .unwrap();
437
438 Ok(TradeAccountDeploy {
439 oracle,
440 oracle_id,
441 trade_account_blob_id: trade_account_blob_id.into(),
442 deployer_wallet: deployer_wallet.clone(),
443 proxy: None,
444 proxy_id: None,
445 })
446 }
447}
448
449pub enum DeployConfig {
450 Latest(TradeAccountDeployConfig),
451}
452
453impl DeployConfig {
454 pub fn config(&self) -> &TradeAccountDeployConfig {
455 match self {
456 DeployConfig::Latest(config) => config,
457 }
458 }
459}
460
461impl TradeAccountDeploy<Wallet> {
462 pub async fn deploy_with_account(
463 &self,
464 owner_identity: &Identity,
465 config: &DeployConfig,
466 call_option: &CallOption,
467 ) -> Result<Self> {
468 let (proxy, proxy_id) = Self::deploy_proxy(
469 &self.deployer_wallet,
470 owner_identity,
471 self.oracle_id,
472 config,
473 call_option,
474 )
475 .await?;
476 Ok(Self {
477 proxy: Some(proxy),
478 proxy_id: Some(proxy_id),
479 oracle: self.oracle.clone(),
480 oracle_id: self.oracle_id,
481 trade_account_blob_id: self.trade_account_blob_id,
482 deployer_wallet: self.deployer_wallet.clone(),
483 })
484 }
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use fuels::test_helpers::{
491 WalletsConfig,
492 launch_custom_provider_and_get_wallets,
493 };
494
495 #[tokio::test]
496 async fn test_trade_account_sdk() {
497 let mut wallets = launch_custom_provider_and_get_wallets(
499 WalletsConfig::new(Some(1), Some(1), Some(1_000_000_000)),
500 None,
501 None,
502 )
503 .await
504 .unwrap();
505 let wallet = wallets.pop().unwrap();
506
507 let config = DeployConfig::Latest(TradeAccountDeployConfig::default());
509 let deployment = TradeAccountDeploy::deploy(&wallet, &config)
510 .await
511 .unwrap()
512 .deploy_with_account(
513 &wallet.address().into(),
514 &config,
515 &CallOption::AwaitBlock,
516 )
517 .await
518 .unwrap();
519
520 let provider = wallet.try_provider().unwrap();
522
523 let oracle_contract_info = provider
525 .contract_exists(&deployment.oracle_id)
526 .await
527 .unwrap();
528 assert!(oracle_contract_info, "Oracle contract should exist");
529
530 let proxy_contract_info = provider
532 .contract_exists(&deployment.proxy_id.unwrap())
533 .await
534 .unwrap();
535 assert!(proxy_contract_info, "Proxy contract should exist");
536
537 let stored_blob_id = deployment
539 .oracle
540 .methods()
541 .get_trade_account_impl()
542 .simulate(Execution::state_read_only())
543 .await
544 .unwrap()
545 .value;
546
547 assert_eq!(
548 deployment.trade_account_blob_id,
549 BlobId::from(stored_blob_id.unwrap()),
550 "Trade account blob ID should match"
551 );
552 }
553}