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