1use crate::{
2 CallOption,
3 blob_loader,
4 contract_ext::ContractExt,
5};
6use anyhow::Result;
7use fuel_core_client::client::FuelClient;
8use fuels::{
9 core::Configurables,
10 prelude::*,
11 tx::StorageSlot,
12 types::{
13 Address,
14 AssetId,
15 ContractId,
16 Identity,
17 },
18};
19
20abigen!(
21 Contract(
22 name = "TrialTradingAccountOracle",
23 abi = "artifacts/trial-trade-account-oracle/trial-trade-account-oracle-abi.json"
24 ),
25 Contract(
26 name = "TrialTradingAccount",
27 abi = "artifacts/trial-trade-account/trial-trade-account-abi.json"
28 ),
29 Contract(
30 name = "TrialTradingAccountProxy",
31 abi = "artifacts/trial-trade-account-proxy/trial-trade-account-proxy-abi.json",
32 ),
33);
34
35pub const TRIAL_TRADE_ACCOUNT_BYTECODE: &[u8] =
36 include_bytes!("../artifacts/trial-trade-account/trial-trade-account.bin");
37pub const TRIAL_TRADE_ACCOUNT_STORAGE: &[u8] = include_bytes!(
38 "../artifacts/trial-trade-account/trial-trade-account-storage_slots.json"
39);
40pub const TRIAL_TRADE_ACCOUNT_PROXY_BYTECODE: &[u8] = include_bytes!(
41 "../artifacts/trial-trade-account-proxy/trial-trade-account-proxy.bin"
42);
43pub const TRIAL_TRADE_ACCOUNT_PROXY_STORAGE: &[u8] = include_bytes!(
44 "../artifacts/trial-trade-account-proxy/trial-trade-account-proxy-storage_slots.json"
45);
46pub const TRIAL_TRADE_ACCOUNT_ORACLE_BYTECODE: &[u8] = include_bytes!(
47 "../artifacts/trial-trade-account-oracle/trial-trade-account-oracle.bin"
48);
49pub const TRIAL_TRADE_ACCOUNT_ORACLE_STORAGE: &[u8] = include_bytes!(
50 "../artifacts/trial-trade-account-oracle/trial-trade-account-oracle-storage_slots.json"
51);
52pub const DEFAULT_TRIAL_DURATION: u64 = 604800;
53pub const DEFAULT_LIQUIDATION_THRESHOLD_AMOUNT: u64 = 0;
54pub const DEFAULT_TRIAL_USER_PROFIT_SHARE_BPS: u64 = 0;
55
56#[derive(Clone)]
57pub struct TrialTradeAccountDeployConfig {
58 pub trial_trade_account_bytecode: Vec<u8>,
59 pub oracle_bytecode: Vec<u8>,
60 pub oracle_storage_slots: Vec<StorageSlot>,
61 pub proxy_bytecode: Vec<u8>,
62 pub trial_trade_account_storage_slots: Vec<StorageSlot>,
63 pub proxy_storage_slots: Vec<StorageSlot>,
64 pub trial_trade_account_config: TrialTradingAccountConfigurables,
65 pub proxy_config: TrialTradingAccountProxyConfigurables,
66 pub cosigner: Option<Address>,
67 pub trial_duration: u64,
68 pub liquidation_threshold_amount: u64,
69 pub liquidation_threshold_asset_id: AssetId,
70 pub trial_allowed_order_book_ids: Vec<ContractId>,
71 pub trial_user_profit_share_bps: u64,
72 pub trial_platform_payout_identity: Identity,
73 pub max_words_per_blob: usize,
74 pub salt: Salt,
75}
76
77impl Default for TrialTradeAccountDeployConfig {
78 fn default() -> Self {
79 Self {
80 trial_trade_account_bytecode: TRIAL_TRADE_ACCOUNT_BYTECODE.to_vec(),
81 oracle_bytecode: TRIAL_TRADE_ACCOUNT_ORACLE_BYTECODE.to_vec(),
82 oracle_storage_slots: serde_json::from_slice(
83 TRIAL_TRADE_ACCOUNT_ORACLE_STORAGE,
84 )
85 .unwrap(),
86 proxy_bytecode: TRIAL_TRADE_ACCOUNT_PROXY_BYTECODE.to_vec(),
87 trial_trade_account_storage_slots: serde_json::from_slice(
88 TRIAL_TRADE_ACCOUNT_STORAGE,
89 )
90 .unwrap(),
91 proxy_storage_slots: serde_json::from_slice(
92 TRIAL_TRADE_ACCOUNT_PROXY_STORAGE,
93 )
94 .unwrap(),
95 trial_trade_account_config: TrialTradingAccountConfigurables::default(),
96 proxy_config: TrialTradingAccountProxyConfigurables::default(),
97 cosigner: None,
98 trial_duration: DEFAULT_TRIAL_DURATION,
99 liquidation_threshold_amount: DEFAULT_LIQUIDATION_THRESHOLD_AMOUNT,
100 liquidation_threshold_asset_id: AssetId::zeroed(),
101 trial_allowed_order_book_ids: Vec::new(),
102 trial_user_profit_share_bps: DEFAULT_TRIAL_USER_PROFIT_SHARE_BPS,
103 trial_platform_payout_identity: Identity::Address(Address::zeroed()),
104 max_words_per_blob: 10_000,
105 salt: Salt::default(),
106 }
107 }
108}
109
110impl TrialTradeAccountDeployConfig {
111 pub fn with_oracle_id(mut self, oracle_id: ContractId) -> Result<Self> {
112 self.trial_trade_account_config = self
113 .trial_trade_account_config
114 .with_ORACLE_CONTRACT_ID(oracle_id)?;
115 self.proxy_config = self.proxy_config.with_ORACLE_CONTRACT_ID(oracle_id)?;
116 Ok(self)
117 }
118
119 pub fn with_cosigner(mut self, cosigner: Address) -> Self {
120 self.cosigner = Some(cosigner);
121 self
122 }
123
124 pub fn with_trial_duration(mut self, trial_duration: u64) -> Self {
125 self.trial_duration = trial_duration;
126 self
127 }
128
129 pub fn with_liquidation_threshold_amount(
130 mut self,
131 liquidation_threshold_amount: u64,
132 ) -> Self {
133 self.liquidation_threshold_amount = liquidation_threshold_amount;
134 self
135 }
136
137 pub fn with_liquidation_threshold_asset_id(
138 mut self,
139 liquidation_threshold_asset_id: AssetId,
140 ) -> Self {
141 self.liquidation_threshold_asset_id = liquidation_threshold_asset_id;
142 self
143 }
144
145 pub fn with_trial_allowed_order_book_ids(
146 mut self,
147 trial_allowed_order_book_ids: Vec<ContractId>,
148 ) -> Self {
149 self.trial_allowed_order_book_ids = trial_allowed_order_book_ids;
150 self
151 }
152
153 pub fn with_trial_user_profit_share_bps(
154 mut self,
155 trial_user_profit_share_bps: u64,
156 ) -> Self {
157 self.trial_user_profit_share_bps = trial_user_profit_share_bps;
158 self
159 }
160
161 pub fn with_trial_platform_payout_identity(
162 mut self,
163 trial_platform_payout_identity: Identity,
164 ) -> Self {
165 self.trial_platform_payout_identity = trial_platform_payout_identity;
166 self
167 }
168
169 pub fn with_initial_session_id(mut self, session_id: Identity) -> Result<Self> {
170 self.proxy_config = self.proxy_config.with_INITIAL_SESSION_ID(session_id)?;
171 Ok(self)
172 }
173
174 pub fn with_trial_trade_account_registry(
175 mut self,
176 registry_id: ContractId,
177 ) -> Result<Self> {
178 self.proxy_config = self
179 .proxy_config
180 .with_TRIAL_TRADE_ACCOUNT_REGISTRY(registry_id)?;
181 Ok(self)
182 }
183}
184
185#[derive(Clone)]
186pub struct TrialTradeAccountDeploy<W> {
187 pub oracle: TrialTradingAccountOracle<W>,
188 pub oracle_id: ContractId,
189 pub trial_trade_account_blob_id: BlobId,
190 pub deployer_wallet: W,
191 pub proxy: Option<TrialTradingAccountProxy<W>>,
192 pub proxy_id: Option<ContractId>,
193}
194
195pub struct TrialTradeAccountBlob {
196 pub id: BlobId,
197 pub exists: bool,
198 pub blob: Blob,
199 pub data_blobs: Vec<Blob>,
200}
201
202impl<W> TrialTradeAccountDeploy<W>
203where
204 W: Account + Clone,
205{
206 pub fn change_wallet(mut self, wallet: &W) -> Self {
207 self.deployer_wallet = wallet.clone();
208 self.oracle = self.oracle.with_account(wallet.clone());
209
210 if let Some(proxy) = self.proxy {
211 self.proxy = Some(proxy.with_account(wallet.clone()));
212 }
213
214 self
215 }
216
217 pub async fn from_oracle_id(
218 deployer_wallet: &W,
219 oracle_id: ContractId,
220 ) -> Result<Self> {
221 let oracle = TrialTradingAccountOracle::new(oracle_id, deployer_wallet.clone());
222 let trial_trade_account_blob_id = oracle
223 .methods()
224 .get_trial_account_impl()
225 .simulate(Execution::state_read_only())
226 .await?
227 .value
228 .ok_or_else(|| anyhow::anyhow!("Trial trade account implementation not set"))?
229 .into();
230
231 Ok(Self {
232 oracle,
233 oracle_id,
234 trial_trade_account_blob_id,
235 deployer_wallet: deployer_wallet.clone(),
236 proxy: None,
237 proxy_id: None,
238 })
239 }
240
241 pub fn trial_trade_account_blob_from_config(
242 config: &TrialTradeAccountDeployConfig,
243 ) -> Result<(Vec<Blob>, Blob)> {
244 blob_loader::build_loader_blobs(
245 config.trial_trade_account_bytecode.clone(),
246 config.salt,
247 config.trial_trade_account_storage_slots.clone(),
248 config.trial_trade_account_config.clone(),
249 config.max_words_per_blob,
250 )
251 }
252
253 pub fn trial_trade_account_proxy_blob_from_config(
254 config: &TrialTradeAccountDeployConfig,
255 ) -> Result<(Vec<Blob>, Blob)> {
256 blob_loader::build_loader_blobs(
257 config.proxy_bytecode.clone(),
258 config.salt,
259 config.proxy_storage_slots.clone(),
260 Configurables::from(config.proxy_config.clone()),
261 config.max_words_per_blob,
262 )
263 }
264
265 pub async fn trial_trade_account_blob(
266 deployer_wallet: &W,
267 config: &TrialTradeAccountDeployConfig,
268 ) -> Result<TrialTradeAccountBlob> {
269 let (data_blobs, loader_blob) =
270 Self::trial_trade_account_blob_from_config(config)?;
271 let loader_blob_id = loader_blob.id();
272 let loader_blob_exists = deployer_wallet
273 .try_provider()?
274 .blob_exists(loader_blob_id)
275 .await?;
276
277 Ok(TrialTradeAccountBlob {
278 id: loader_blob_id,
279 exists: loader_blob_exists,
280 blob: loader_blob,
281 data_blobs,
282 })
283 }
284
285 pub async fn deploy_trial_trade_account_blob(
286 deployer_wallet: &W,
287 config: &DeployConfig,
288 ) -> Result<BlobId> {
289 match config {
290 DeployConfig::Latest(config) => {
291 let trial_trade_account_blob =
292 Self::trial_trade_account_blob(deployer_wallet, config).await?;
293 blob_loader::upload_loader_blobs(
294 deployer_wallet,
295 trial_trade_account_blob.data_blobs,
296 trial_trade_account_blob.blob,
297 )
298 .await
299 }
300 }
301 }
302
303 pub async fn deploy_oracle(
307 deployer_wallet: &W,
308 config: &DeployConfig,
309 ) -> Result<(TrialTradingAccountOracle<W>, ContractId)> {
310 match config {
311 DeployConfig::Latest(config) => {
312 let contract = Contract::regular(
313 config.oracle_bytecode.clone(),
314 config.salt,
315 config.oracle_storage_slots.clone(),
316 )
317 .with_configurables(TrialTradingAccountOracleConfigurables::default())
318 .with_salt(config.salt);
319 let contract_id = contract.contract_id();
320 let instance =
321 TrialTradingAccountOracle::new(contract_id, deployer_wallet.clone());
322 let contract_exists = deployer_wallet
323 .try_provider()?
324 .contract_exists(&contract_id)
325 .await?;
326
327 if !contract_exists {
328 contract
329 .deploy(deployer_wallet, TxPolicies::default())
330 .await?;
331 instance
332 .methods()
333 .initialize(Identity::Address(deployer_wallet.address()))
334 .call()
335 .await?;
336 }
337
338 Ok((instance, contract_id))
339 }
340 }
341 }
342
343 pub async fn deploy(
347 deployer_wallet: &W,
348 config: &DeployConfig,
349 ) -> Result<TrialTradeAccountDeploy<W>> {
350 let (_, oracle_id) = Self::deploy_oracle(deployer_wallet, config).await?;
351 Self::deploy_to_oracle(deployer_wallet, oracle_id, config).await
352 }
353
354 pub async fn deploy_to_oracle(
355 deployer_wallet: &W,
356 oracle_id: ContractId,
357 config: &DeployConfig,
358 ) -> Result<TrialTradeAccountDeploy<W>> {
359 let config = match config {
360 DeployConfig::Latest(config) => {
361 DeployConfig::Latest(config.clone().with_oracle_id(oracle_id)?)
362 }
363 };
364 let trial_trade_account_blob_id =
365 Self::deploy_trial_trade_account_blob(deployer_wallet, &config).await?;
366 let oracle = TrialTradingAccountOracle::new(oracle_id, deployer_wallet.clone());
367 let implementation = ContractId::from(trial_trade_account_blob_id);
368 let current_impl = oracle
369 .methods()
370 .get_trial_account_impl()
371 .simulate(Execution::state_read_only())
372 .await?
373 .value;
374
375 if current_impl != Some(implementation) {
376 oracle
377 .methods()
378 .set_trial_account_impl(implementation)
379 .call()
380 .await?;
381 }
382
383 if let Some(cosigner) = config.config().cosigner {
384 let current_cosigner = oracle
385 .methods()
386 .get_cosigner()
387 .simulate(Execution::state_read_only())
388 .await?
389 .value;
390
391 if current_cosigner != Some(cosigner) {
392 oracle.methods().set_cosigner(cosigner).call().await?;
393 }
394 }
395
396 Ok(TrialTradeAccountDeploy {
397 oracle,
398 oracle_id,
399 trial_trade_account_blob_id,
400 deployer_wallet: deployer_wallet.clone(),
401 proxy: None,
402 proxy_id: None,
403 })
404 }
405}
406
407impl TrialTradeAccountDeploy<Wallet> {
408 pub fn trial_trade_account_contract(
409 oracle_id: &ContractId,
410 config: &DeployConfig,
411 ) -> Result<Contract<fuels::programs::contract::Regular>> {
412 match config {
413 DeployConfig::Latest(config) => {
414 let configurables = config
415 .proxy_config
416 .clone()
417 .with_ORACLE_CONTRACT_ID(*oracle_id)?;
418 let contract = Contract::regular(
419 config.proxy_bytecode.clone(),
420 config.salt,
421 config.proxy_storage_slots.clone(),
422 )
423 .with_configurables(configurables);
424 Ok(contract)
425 }
426 }
427 }
428
429 pub async fn deploy_proxy(
430 deployer_wallet: &Wallet,
431 oracle_id: ContractId,
432 config: &DeployConfig,
433 call_option: &CallOption,
434 dry_run_client: Option<&FuelClient>,
435 submit_clients: &[FuelClient],
436 ) -> Result<(TrialTradingAccountProxy<Wallet>, ContractId, Option<u64>)> {
437 let contract = Self::trial_trade_account_contract(&oracle_id, config)?;
438 let already_deployed = deployer_wallet
439 .try_provider()?
440 .contract_exists(&contract.contract_id())
441 .await?;
442
443 let (id, total_gas) = if !already_deployed {
444 match call_option {
445 CallOption::AwaitBlock => {
446 let id = contract
447 .deploy(deployer_wallet, TxPolicies::default())
448 .await?
449 .contract_id;
450 (id, None)
451 }
452 CallOption::AwaitPreconfirmation(ops) => {
453 let result = contract
454 .almost_sync_deploy(
455 deployer_wallet,
456 &ops.data_builder,
457 &ops.utxo_manager,
458 &ops.tx_config,
459 submit_clients,
460 )
461 .await?;
462
463 let mut total_gas = None;
464 if let Some(tx_id) = result.tx_id {
465 let status = deployer_wallet
466 .try_provider()?
467 .client()
468 .await_transaction_commit(&tx_id)
469 .await?;
470 if let fuel_core_client::client::types::TransactionStatus::Success {
471 total_gas: gas,
472 ..
473 } = status
474 {
475 total_gas = Some(gas);
476 }
477 }
478 (result.contract_id, total_gas)
479 }
480 }
481 } else {
482 (contract.contract_id(), None)
483 };
484
485 let proxy = TrialTradingAccountProxy::new(id, deployer_wallet.clone());
486
487 let _ = dry_run_client;
488
489 Ok((proxy, id, total_gas))
490 }
491
492 pub async fn deploy_with_account(
496 &self,
497 config: &DeployConfig,
498 call_option: &CallOption,
499 dry_run_client: Option<&FuelClient>,
500 submit_clients: &[FuelClient],
501 ) -> Result<(Self, Option<u64>)> {
502 let (proxy, proxy_id, total_gas) = Self::deploy_proxy(
503 &self.deployer_wallet,
504 self.oracle_id,
505 config,
506 call_option,
507 dry_run_client,
508 submit_clients,
509 )
510 .await?;
511 Ok((
512 Self {
513 proxy: Some(proxy),
514 proxy_id: Some(proxy_id),
515 oracle: self.oracle.clone(),
516 oracle_id: self.oracle_id,
517 trial_trade_account_blob_id: self.trial_trade_account_blob_id,
518 deployer_wallet: self.deployer_wallet.clone(),
519 },
520 total_gas,
521 ))
522 }
523}
524
525#[derive(Clone)]
526pub enum DeployConfig {
527 Latest(TrialTradeAccountDeployConfig),
528}
529
530impl DeployConfig {
531 pub fn config(&self) -> &TrialTradeAccountDeployConfig {
532 match self {
533 DeployConfig::Latest(config) => config,
534 }
535 }
536}