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