1#[cfg(test)]
8use crate::prop::ProlongPeriod;
9use crate::{
10 blob_loader,
11 prop::{
12 PRICE_FEED_BYTECODE,
13 PRICE_FEED_PROXY_BYTECODE,
14 PRICE_FEED_PROXY_STORAGE,
15 PRICE_FEED_STORAGE,
16 PROP_ACCOUNT_BYTECODE,
17 PROP_ACCOUNT_ORACLE_BYTECODE,
18 PROP_ACCOUNT_ORACLE_PROXY_BYTECODE,
19 PROP_ACCOUNT_ORACLE_PROXY_STORAGE,
20 PROP_ACCOUNT_ORACLE_STORAGE,
21 PROP_ACCOUNT_PROXY_BYTECODE,
22 PROP_ACCOUNT_PROXY_STORAGE,
23 PROP_ACCOUNT_STORAGE,
24 PROP_MARGIN_POOL_BYTECODE,
25 PROP_MARGIN_POOL_PROXY_BYTECODE,
26 PROP_MARGIN_POOL_PROXY_STORAGE,
27 PROP_MARGIN_POOL_STORAGE,
28 PriceFeedContract,
29 PriceFeedContractConfigurables,
30 PriceFeedProxyContract,
31 PriceFeedProxyContractConfigurables,
32 PropAccountContract,
33 PropAccountOracleContract,
34 PropAccountOracleContractConfigurables,
35 PropAccountOracleProxyContract,
36 PropAccountOracleProxyContractConfigurables,
37 PropAccountProxyContract,
38 PropAccountProxyContractConfigurables,
39 PropMarginPoolContract,
40 PropMarginPoolContractConfigurables,
41 PropMarginPoolProxyContract,
42 PropMarginPoolProxyContractConfigurables,
43 State,
44 },
45 trade_account_registry::{
46 State as TradeAccountRegistryState,
47 TRADE_ACCOUNT_REGISTER_BYTECODE,
48 TRADE_ACCOUNT_REGISTER_PROXY_BYTECODE,
49 TRADE_ACCOUNT_REGISTER_PROXY_STORAGE,
50 TRADE_ACCOUNT_REGISTER_STORAGE,
51 TradeAccountRegistry,
52 TradeAccountRegistryConfigurables,
53 TradeAccountRegistryDeployConfig,
54 TradeAccountRegistryManager,
55 TradeAccountRegistryProxy,
56 TradeAccountRegistryProxyConfigurables,
57 },
58};
59use anyhow::{
60 Context,
61 Result,
62 ensure,
63};
64use fuels::{
65 core::{
66 Configurable,
67 Configurables,
68 },
69 prelude::*,
70 programs::contract::Regular,
71 tx::StorageSlot,
72 types::{
73 Address,
74 AssetId,
75 ContractId,
76 Identity,
77 transaction_builders::Blob,
78 },
79};
80
81#[derive(Clone, Debug)]
87pub struct ExistingRegistry {
88 pub registry_id: ContractId,
90 pub trade_account_oracle_id: ContractId,
92 pub trial_trade_account_oracle_id: ContractId,
94}
95
96#[derive(Clone, Debug)]
98pub struct PropDeployConfig {
99 pub collateral_asset: AssetId,
101 pub collateral_decimals: u8,
103 pub max_tier_books: u64,
105 pub base_repay_fee_ppm: u64,
109 pub owner: Option<Identity>,
112 pub proxy_owner: Option<Identity>,
114 pub cosigner: Option<Address>,
116 pub platform_payout: Option<Identity>,
118 pub liquidator: Option<Identity>,
120 pub salt: Salt,
122 pub max_words_per_blob: usize,
124 pub existing_registry: Option<ExistingRegistry>,
127 pub existing_price_feed: Option<ContractId>,
131 pub max_offchain_age_seconds: Option<u64>,
135 pub existing_pool: Option<ContractId>,
142}
143
144impl PropDeployConfig {
145 pub fn new(collateral_asset: AssetId) -> Self {
146 Self {
147 collateral_asset,
148 collateral_decimals: 6,
149 max_tier_books: 80,
150 base_repay_fee_ppm: 100,
151 owner: None,
152 proxy_owner: None,
153 cosigner: None,
154 platform_payout: None,
155 liquidator: None,
156 salt: Salt::default(),
157 max_words_per_blob: 10_000,
158 existing_registry: None,
159 existing_price_feed: None,
160 max_offchain_age_seconds: None,
161 existing_pool: None,
162 }
163 }
164}
165
166#[derive(Clone)]
168pub struct PropDeployment<W> {
169 pub oracle: PropAccountOracleContract<W>,
171 pub oracle_proxy: PropAccountOracleProxyContract<W>,
172 pub oracle_id: ContractId,
173 pub oracle_blob_id: BlobId,
174 pub price_feed: PriceFeedContract<W>,
176 pub price_feed_id: ContractId,
177 pub registry: TradeAccountRegistry<W>,
179 pub registry_proxy: TradeAccountRegistryProxy<W>,
180 pub registry_id: ContractId,
181 pub registry_blob_id: BlobId,
182 pub pool: PropMarginPoolContract<W>,
184 pub pool_proxy: PropMarginPoolProxyContract<W>,
185 pub pool_id: ContractId,
186 pub pool_blob_id: BlobId,
187 pub account_blob_id: BlobId,
189 pub account_proxy_blob_id: BlobId,
191 pub account_salt: Salt,
193 pub deployer_wallet: W,
194}
195
196impl<W> PropDeployment<W>
197where
198 W: Account + Clone,
199{
200 pub async fn deploy(deployer_wallet: &W, config: &PropDeployConfig) -> Result<Self> {
206 ensure!(
207 config.collateral_asset != AssetId::zeroed(),
208 "prop collateral asset cannot be zero"
209 );
210 ensure!(
211 config.collateral_decimals <= 18,
212 "prop collateral decimals cannot exceed 18"
213 );
214 ensure!(
215 config.max_tier_books != 0,
216 "prop max tier books cannot be zero"
217 );
218 ensure!(
219 config.max_words_per_blob != 0,
220 "prop loader blob size cannot be zero"
221 );
222
223 let deployer = Identity::Address(deployer_wallet.address());
224 let owner = config.owner.unwrap_or(deployer);
225 let proxy_owner = config.proxy_owner.unwrap_or(deployer);
226 let cosigner = config.cosigner.unwrap_or_else(|| {
233 tracing::warn!(
234 "prop deploy: no cosigner given - defaulting to the deployer. \
235 The backend must run MARGIN_COSIGNER_KEY for this address or \
236 margin stays inert."
237 );
238 deployer_wallet.address()
239 });
240 let platform_payout = config.platform_payout.unwrap_or(deployer);
241 let liquidator = config.liquidator.unwrap_or_else(|| {
242 tracing::warn!(
243 "prop deploy: no liquidator given - defaulting to the deployer, \
244 which will receive the residue of every forced exit."
245 );
246 deployer
247 });
248
249 let account_blob_id = upload_implementation(
250 deployer_wallet,
251 PROP_ACCOUNT_BYTECODE,
252 PROP_ACCOUNT_STORAGE,
253 Configurables::default(),
254 config,
255 )
256 .await
257 .context("deploy prop-account implementation blob")?;
258
259 let account_proxy_blob_id = blob_loader::upload_loader_blobs(
262 deployer_wallet,
263 vec![],
264 Blob::new(PROP_ACCOUNT_PROXY_BYTECODE.to_vec()),
265 )
266 .await
267 .context("deploy raw prop-account proxy blob")?;
268
269 let oracle_bootstrap_blob_id = upload_implementation(
273 deployer_wallet,
274 PROP_ACCOUNT_ORACLE_BYTECODE,
275 PROP_ACCOUNT_ORACLE_STORAGE,
276 Configurables::default(),
277 config,
278 )
279 .await
280 .context("deploy prop-account oracle implementation blob")?;
281 let oracle_proxy_configurables =
282 PropAccountOracleProxyContractConfigurables::default()
283 .with_INITIAL_OWNER(State::Initialized(proxy_owner))?
284 .with_INITIAL_TARGET(ContractId::from(oracle_bootstrap_blob_id))?;
285 let oracle_proxy_contract = regular_contract_with_implementation_storage(
286 PROP_ACCOUNT_ORACLE_PROXY_BYTECODE,
287 PROP_ACCOUNT_ORACLE_PROXY_STORAGE,
288 PROP_ACCOUNT_ORACLE_STORAGE,
289 config.salt,
290 )?
291 .with_configurables(oracle_proxy_configurables);
292 let (oracle_id, _) = deploy_regular(deployer_wallet, oracle_proxy_contract)
293 .await
294 .context("deploy prop-account oracle proxy")?;
295 let oracle_proxy =
296 PropAccountOracleProxyContract::new(oracle_id, deployer_wallet.clone());
297 let oracle = PropAccountOracleContract::new(oracle_id, deployer_wallet.clone());
298 let oracle_proxy_target = oracle_proxy
302 .methods()
303 .proxy_target()
304 .simulate(Execution::state_read_only())
305 .await
306 .context("read prop-account oracle proxy target")?
307 .value;
308 if oracle_proxy_target.is_none() {
309 oracle_proxy
310 .methods()
311 .initialize_proxy()
312 .call()
313 .await
314 .context("initialize prop-account oracle proxy")?;
315 }
316
317 let (price_feed, price_feed_id) = match config.existing_price_feed {
321 Some(price_feed_id) => (
322 PriceFeedContract::new(price_feed_id, deployer_wallet.clone()),
323 price_feed_id,
324 ),
325 None => {
326 let feed_bootstrap_blob_id = upload_implementation(
331 deployer_wallet,
332 PRICE_FEED_BYTECODE,
333 PRICE_FEED_STORAGE,
334 Configurables::default(),
335 config,
336 )
337 .await
338 .context("deploy price-feed bootstrap implementation blob")?;
339 let feed_proxy_configurables =
340 PriceFeedProxyContractConfigurables::default()
341 .with_INITIAL_OWNER(State::Initialized(proxy_owner))?
342 .with_INITIAL_TARGET(ContractId::from(feed_bootstrap_blob_id))?;
343 let feed_proxy_contract = regular_contract_with_implementation_storage(
344 PRICE_FEED_PROXY_BYTECODE,
345 PRICE_FEED_PROXY_STORAGE,
346 PRICE_FEED_STORAGE,
347 config.salt,
348 )?
349 .with_configurables(feed_proxy_configurables);
350 let (price_feed_id, _) =
351 deploy_regular(deployer_wallet, feed_proxy_contract)
352 .await
353 .context("deploy price-feed proxy")?;
354 let feed_proxy =
355 PriceFeedProxyContract::new(price_feed_id, deployer_wallet.clone());
356 let feed_proxy_target = feed_proxy
357 .methods()
358 .proxy_target()
359 .simulate(Execution::state_read_only())
360 .await
361 .context("read price-feed proxy target")?
362 .value;
363 if feed_proxy_target.is_none() {
364 feed_proxy
365 .methods()
366 .initialize_proxy()
367 .call()
368 .await
369 .context("initialize price-feed proxy")?;
370 }
371 upgrade_price_feed_implementation(
372 deployer_wallet,
373 price_feed_id,
374 owner,
375 config,
376 )
377 .await?;
378 let price_feed =
379 PriceFeedContract::new(price_feed_id, deployer_wallet.clone());
380 let feed_is_uninitialized = price_feed
381 .methods()
382 .owner()
383 .simulate(Execution::state_read_only())
384 .await
385 .context("read price-feed initialization state")?
386 .value
387 == State::Uninitialized;
388 if feed_is_uninitialized {
389 price_feed
390 .methods()
391 .initialize()
392 .call()
393 .await
394 .context("initialize price-feed")?;
395 }
396 let owner_can_publish = price_feed
402 .methods()
403 .has_role(PRICE_SUBMITTER_ROLE, owner)
404 .simulate(Execution::state_read_only())
405 .await
406 .context("read the price feed's submitter role")?
407 .value;
408 if !owner_can_publish {
409 price_feed
410 .methods()
411 .add_publisher(owner)
412 .call()
413 .await
414 .context("grant the price feed's owner the submitter role")?;
415 }
416 (price_feed, price_feed_id)
417 }
418 };
419
420 if let Some(seconds) = config.max_offchain_age_seconds {
425 let current = price_feed
426 .methods()
427 .get_max_offchain_age()
428 .simulate(Execution::state_read_only())
429 .await
430 .context("read the price feed's submission-age bound")?
431 .value;
432 if current != seconds {
433 price_feed
434 .methods()
435 .set_max_offchain_age(seconds)
436 .call()
437 .await
438 .context("set the price feed's submission-age bound")?;
439 }
440 }
441
442 let [oracle_offset, parent_offset, index_offset] = prop_account_proxy_offsets()?;
443 let prop_registry_configurables = |base: TradeAccountRegistryConfigurables| {
444 Ok::<_, anyhow::Error>(
445 base.with_PROP_ACCOUNT_ORACLE_CONTRACT_ID(oracle_id)?
446 .with_DEFAULT_PROP_ACCOUNT_PROXY(ContractId::from(
447 account_proxy_blob_id,
448 ))?
449 .with_PROP_ORACLE_CONFIG_OFFSET(oracle_offset)?
450 .with_PROP_PARENT_CONFIG_OFFSET(parent_offset)?
451 .with_PROP_INDEX_CONFIG_OFFSET(index_offset)?,
452 )
453 };
454 let (registry_id, registry_blob_id) = match &config.existing_registry {
455 Some(existing) => {
456 let manager = TradeAccountRegistryManager::new(
461 deployer_wallet.clone(),
462 existing.registry_id,
463 );
464 let deploy_config = TradeAccountRegistryDeployConfig {
465 registry_config: prop_registry_configurables(
466 TradeAccountRegistryConfigurables::default(),
467 )?,
468 ..Default::default()
469 };
470 let registry_blob_id = TradeAccountRegistryManager::deploy_register_blob(
474 deployer_wallet,
475 existing.trade_account_oracle_id,
476 existing.trial_trade_account_oracle_id,
477 &deploy_config,
478 )
479 .await
480 .context("build upgraded shared trade-account registry blob")?;
481 let current_target = manager
482 .registry_proxy
483 .methods()
484 .proxy_target()
485 .simulate(Execution::state_read_only())
486 .await
487 .context("read shared trade-account registry proxy target")?
488 .value;
489 if current_target != Some(ContractId::from(registry_blob_id)) {
490 manager
491 .registry_proxy
492 .methods()
493 .set_proxy_target(ContractId::from(registry_blob_id))
494 .call()
495 .await
496 .context("upgrade shared trade-account registry for prop")?;
497 }
498 (existing.registry_id, registry_blob_id)
499 }
500 None => {
501 let registry_configurables = prop_registry_configurables(
502 TradeAccountRegistryConfigurables::default()
503 .with_INITIAL_OWNER(TradeAccountRegistryState::Initialized(
504 owner,
505 ))?
506 .with_INITIAL_TRIAL_TRADE_ACCOUNT_CREATOR(owner)?,
507 )?;
508 let registry_blob_id = upload_implementation(
509 deployer_wallet,
510 TRADE_ACCOUNT_REGISTER_BYTECODE,
511 TRADE_ACCOUNT_REGISTER_STORAGE,
512 registry_configurables,
513 config,
514 )
515 .await
516 .context("deploy shared trade-account registry implementation blob")?;
517
518 let registry_proxy_configurables =
519 TradeAccountRegistryProxyConfigurables::default()
520 .with_INITIAL_OWNER(TradeAccountRegistryState::Initialized(
521 proxy_owner,
522 ))?
523 .with_INITIAL_TARGET(ContractId::from(registry_blob_id))?;
524 let registry_proxy_contract = regular_contract(
525 TRADE_ACCOUNT_REGISTER_PROXY_BYTECODE,
526 TRADE_ACCOUNT_REGISTER_PROXY_STORAGE,
527 config.salt,
528 )?
529 .with_configurables(registry_proxy_configurables);
530 let (registry_id, registry_proxy_is_new) =
531 deploy_regular(deployer_wallet, registry_proxy_contract)
532 .await
533 .context("deploy shared trade-account registry proxy")?;
534 let registry_proxy =
535 TradeAccountRegistryProxy::new(registry_id, deployer_wallet.clone());
536 let registry =
537 TradeAccountRegistry::new(registry_id, deployer_wallet.clone());
538 if registry_proxy_is_new {
539 registry_proxy
540 .methods()
541 .initialize_proxy()
542 .call()
543 .await
544 .context("initialize shared trade-account registry proxy")?;
545 registry
546 .methods()
547 .initialize()
548 .call()
549 .await
550 .context("initialize shared trade-account registry")?;
551 }
552 (registry_id, registry_blob_id)
553 }
554 };
555 let registry_proxy =
556 TradeAccountRegistryProxy::new(registry_id, deployer_wallet.clone());
557 let registry = TradeAccountRegistry::new(registry_id, deployer_wallet.clone());
558
559 let pool_configurables = PropMarginPoolContractConfigurables::default()
560 .with_INITIAL_ADMIN(owner)?
561 .with_INITIAL_REGISTRY(registry_id)?
562 .with_INITIAL_PRICE_FEED(price_feed_id)?
563 .with_INITIAL_PLATFORM_PAYOUT(platform_payout)?
564 .with_INITIAL_LIQUIDATOR(liquidator)?
565 .with_COLLATERAL_ASSET(config.collateral_asset)?
566 .with_COLLATERAL_DECIMALS(config.collateral_decimals)?
567 .with_MAX_TIER_BOOKS(config.max_tier_books)?
568 .with_BASE_REPAY_FEE_PPM(config.base_repay_fee_ppm)?;
569 let pool_blob_id = upload_implementation(
570 deployer_wallet,
571 PROP_MARGIN_POOL_BYTECODE,
572 PROP_MARGIN_POOL_STORAGE,
573 pool_configurables,
574 config,
575 )
576 .await
577 .context("deploy prop margin-pool implementation blob")?;
578
579 let pool_id = match config.existing_pool {
592 Some(pool_id) => pool_id,
593 None => {
594 let pool_bootstrap_blob_id = upload_implementation(
595 deployer_wallet,
596 PROP_MARGIN_POOL_BYTECODE,
597 PROP_MARGIN_POOL_STORAGE,
598 Configurables::default(),
599 config,
600 )
601 .await
602 .context("deploy prop margin-pool bootstrap implementation blob")?;
603 let pool_proxy_configurables =
604 PropMarginPoolProxyContractConfigurables::default()
605 .with_INITIAL_OWNER(State::Initialized(proxy_owner))?
606 .with_INITIAL_TARGET(ContractId::from(pool_bootstrap_blob_id))?;
607 let pool_proxy_contract = regular_contract_with_implementation_storage(
608 PROP_MARGIN_POOL_PROXY_BYTECODE,
609 PROP_MARGIN_POOL_PROXY_STORAGE,
610 PROP_MARGIN_POOL_STORAGE,
611 config.salt,
612 )?
613 .with_configurables(pool_proxy_configurables);
614 let (pool_id, _) = deploy_regular(deployer_wallet, pool_proxy_contract)
615 .await
616 .context("deploy prop margin-pool proxy")?;
617 pool_id
618 }
619 };
620 let pool_proxy =
621 PropMarginPoolProxyContract::new(pool_id, deployer_wallet.clone());
622 let pool = PropMarginPoolContract::new(pool_id, deployer_wallet.clone());
623 let pool_proxy_target = pool_proxy
624 .methods()
625 .proxy_target()
626 .simulate(Execution::state_read_only())
627 .await
628 .context("read prop margin-pool proxy target")?
629 .value;
630 if pool_proxy_target.is_none() {
631 pool_proxy
632 .methods()
633 .initialize_proxy()
634 .call()
635 .await
636 .context("initialize prop margin-pool proxy")?;
637 }
638 let pool_target = pool_proxy
642 .methods()
643 .proxy_target()
644 .simulate(Execution::state_read_only())
645 .await
646 .context("read configured prop margin-pool proxy target")?
647 .value;
648 if pool_target != Some(ContractId::from(pool_blob_id)) {
649 pool_proxy
650 .methods()
651 .set_proxy_target(ContractId::from(pool_blob_id))
652 .call()
653 .await
654 .context("activate configured prop margin-pool implementation")?;
655 }
656
657 let oracle_configurables = PropAccountOracleContractConfigurables::default()
658 .with_INITIAL_OWNER(owner)?
659 .with_INITIAL_PROP_ACCOUNT_IMPL(ContractId::from(account_blob_id))?
660 .with_INITIAL_COSIGNER(cosigner)?
661 .with_INITIAL_PROP_MARGIN_POOL(pool_id)?;
662 let oracle_blob_id = upload_implementation(
663 deployer_wallet,
664 PROP_ACCOUNT_ORACLE_BYTECODE,
665 PROP_ACCOUNT_ORACLE_STORAGE,
666 oracle_configurables,
667 config,
668 )
669 .await
670 .context("deploy configured prop-account oracle implementation blob")?;
671 let oracle_target = oracle_proxy
672 .methods()
673 .proxy_target()
674 .simulate(Execution::state_read_only())
675 .await
676 .context("read prop-account oracle proxy target")?
677 .value;
678 if oracle_target != Some(ContractId::from(oracle_blob_id)) {
679 oracle_proxy
680 .methods()
681 .set_proxy_target(ContractId::from(oracle_blob_id))
682 .call()
683 .await
684 .context("activate configured prop-account oracle implementation")?;
685 }
686
687 let oracle_is_uninitialized = oracle
688 .methods()
689 .owner()
690 .simulate(Execution::state_read_only())
691 .await
692 .context("read prop-account oracle initialization state")?
693 .value
694 == State::Uninitialized;
695 if oracle_is_uninitialized {
696 oracle
697 .methods()
698 .initialize()
699 .call()
700 .await
701 .context("initialize prop-account oracle")?;
702 }
703
704 let deployed_collateral = pool
707 .methods()
708 .collateral_asset()
709 .simulate(Execution::state_read_only())
710 .await
711 .context("read deployed prop margin-pool collateral")?
712 .value;
713 ensure!(
714 deployed_collateral == config.collateral_asset,
715 "prop margin-pool collateral configurable mismatch"
716 );
717 ensure!(
718 pool.methods()
719 .collateral_decimals()
720 .simulate(Execution::state_read_only())
721 .await
722 .context("read deployed prop margin-pool collateral decimals")?
723 .value
724 == config.collateral_decimals,
725 "prop margin-pool decimals configurable mismatch"
726 );
727 ensure!(
728 pool.methods()
729 .max_tier_books()
730 .simulate(Execution::state_read_only())
731 .await
732 .context("read deployed prop margin-pool book limit")?
733 .value
734 == config.max_tier_books,
735 "prop margin-pool book-limit configurable mismatch"
736 );
737 let pool_is_initialized = pool
738 .methods()
739 .is_initialized()
740 .simulate(Execution::state_read_only())
741 .await
742 .context("read deployed prop margin-pool initialization state")?
743 .value;
744 if !pool_is_initialized {
745 pool.methods()
746 .initialize()
747 .call()
748 .await
749 .context("initialize prop margin pool")?;
750 }
751
752 Ok(Self {
753 oracle,
754 oracle_proxy,
755 oracle_id,
756 oracle_blob_id,
757 price_feed,
758 price_feed_id,
759 registry,
760 registry_proxy,
761 registry_id,
762 registry_blob_id,
763 pool,
764 pool_proxy,
765 pool_id,
766 pool_blob_id,
767 account_blob_id,
768 account_proxy_blob_id,
769 account_salt: config.salt,
770 deployer_wallet: deployer_wallet.clone(),
771 })
772 }
773
774 pub async fn deploy_account(
779 &self,
780 parent_caller: &W,
781 parent: Identity,
782 index: u64,
783 ) -> Result<PropAccountProxyContract<W>> {
784 ensure!(
785 parent != Identity::Address(Address::zeroed()),
786 "prop-account parent cannot be zero"
787 );
788
789 let configurables = PropAccountProxyContractConfigurables::default()
790 .with_ORACLE_CONTRACT_ID(self.oracle_id)?
791 .with_PARENT(parent)?
792 .with_INDEX(index)?;
793 let child_contract = regular_contract(
794 PROP_ACCOUNT_PROXY_BYTECODE,
795 PROP_ACCOUNT_PROXY_STORAGE,
796 self.account_salt,
797 )?
798 .with_configurables(configurables);
799 let (child_id, _) = deploy_regular(&self.deployer_wallet, child_contract)
800 .await
801 .context("deploy prop-account child")?;
802
803 let child = PropAccountProxyContract::new(child_id, self.deployer_wallet.clone());
804 if !self
805 .registry
806 .methods()
807 .prop_is_valid(child_id)
808 .simulate(Execution::state_read_only())
809 .await?
810 .value
811 {
812 TradeAccountRegistry::new(self.registry_id, parent_caller.clone())
813 .methods()
814 .prop_register_contract(child_id, parent, index)
815 .with_contract_ids(&[self.oracle_id, child_id, self.pool_id])
816 .call()
817 .await
818 .context("register prop-account child")?;
819 }
820
821 Ok(child)
822 }
823
824 pub fn account(&self, account_id: ContractId) -> PropAccountContract<W> {
826 PropAccountContract::new(account_id, self.deployer_wallet.clone())
827 }
828}
829
830pub fn prop_account_proxy_offsets() -> Result<[u64; 3]> {
838 fn only_offset(configurables: Configurables, name: &str) -> Result<u64> {
839 let offsets: Vec<Configurable> = configurables.offsets_with_data;
840 ensure!(
841 offsets.len() == 1,
842 "expected one {name} configurable, got {}",
843 offsets.len()
844 );
845 Ok(offsets[0].offset)
846 }
847
848 let oracle: Configurables = PropAccountProxyContractConfigurables::default()
849 .with_ORACLE_CONTRACT_ID(ContractId::zeroed())?
850 .into();
851 let parent: Configurables = PropAccountProxyContractConfigurables::default()
852 .with_PARENT(Identity::Address(Address::zeroed()))?
853 .into();
854 let index: Configurables = PropAccountProxyContractConfigurables::default()
855 .with_INDEX(0)?
856 .into();
857
858 Ok([
859 only_offset(oracle, "oracle")?,
860 only_offset(parent, "parent")?,
861 only_offset(index, "index")?,
862 ])
863}
864
865fn storage_slots(bytes: &[u8]) -> Result<Vec<StorageSlot>> {
866 serde_json::from_slice(bytes).context("decode contract storage slots")
867}
868
869fn regular_contract(
870 bytecode: &[u8],
871 storage: &[u8],
872 salt: Salt,
873) -> Result<Contract<Regular>> {
874 Ok(Contract::regular(
875 bytecode.to_vec(),
876 salt,
877 storage_slots(storage)?,
878 ))
879}
880
881fn regular_contract_with_implementation_storage(
882 bytecode: &[u8],
883 proxy_storage: &[u8],
884 implementation_storage: &[u8],
885 salt: Salt,
886) -> Result<Contract<Regular>> {
887 let mut slots = storage_slots(proxy_storage)?;
888 for implementation_slot in storage_slots(implementation_storage)? {
889 ensure!(
890 !slots
891 .iter()
892 .any(|proxy_slot| proxy_slot.key() == implementation_slot.key()),
893 "proxy and implementation storage slots collide"
894 );
895 slots.push(implementation_slot);
896 }
897 Ok(Contract::regular(bytecode.to_vec(), salt, slots))
898}
899
900pub async fn upgrade_price_feed_implementation<W>(
910 deployer_wallet: &W,
911 price_feed_id: ContractId,
912 owner: Identity,
913 config: &PropDeployConfig,
914) -> Result<ContractId>
915where
916 W: Account + Clone,
917{
918 let feed_proxy = PriceFeedProxyContract::new(price_feed_id, deployer_wallet.clone());
919 let current = feed_proxy
920 .methods()
921 .proxy_target()
922 .simulate(Execution::state_read_only())
923 .await
924 .context(
925 "read the price feed's proxy target - an unproxied feed cannot be \
926 upgraded in place",
927 )?
928 .value;
929 let feed_blob_id = upload_implementation(
930 deployer_wallet,
931 PRICE_FEED_BYTECODE,
932 PRICE_FEED_STORAGE,
933 PriceFeedContractConfigurables::default()
934 .with_INITIAL_OWNER(State::Initialized(owner))?,
935 config,
936 )
937 .await
938 .context("deploy configured price-feed implementation blob")?;
939 let feed_blob_id = ContractId::from(feed_blob_id);
940 if current != Some(feed_blob_id) {
941 tracing::info!(
942 "Upgrade price feed implementation from {current:?} to {feed_blob_id:?}"
943 );
944 feed_proxy
945 .methods()
946 .set_proxy_target(feed_blob_id)
947 .call()
948 .await
949 .context("activate configured price-feed implementation")?;
950 }
951 Ok(feed_blob_id)
952}
953
954pub const PRICE_SUBMITTER_ROLE: u64 = 2;
957
958async fn upload_implementation<W>(
959 deployer_wallet: &W,
960 bytecode: &[u8],
961 storage: &[u8],
962 configurables: impl Into<Configurables>,
963 config: &PropDeployConfig,
964) -> Result<BlobId>
965where
966 W: Account,
967{
968 let (data_blobs, loader_blob) = blob_loader::build_loader_blobs(
969 bytecode.to_vec(),
970 config.salt,
971 storage_slots(storage)?,
972 configurables,
973 config.max_words_per_blob,
974 )?;
975 blob_loader::upload_loader_blobs(deployer_wallet, data_blobs, loader_blob).await
976}
977
978async fn deploy_regular<W>(
979 deployer_wallet: &W,
980 contract: Contract<Regular>,
981) -> Result<(ContractId, bool)>
982where
983 W: Account,
984{
985 let contract_id = contract.contract_id();
986 let is_new = !deployer_wallet
987 .try_provider()?
988 .contract_exists(&contract_id)
989 .await?;
990 if is_new {
991 contract
992 .deploy(deployer_wallet, TxPolicies::default())
993 .await?;
994 }
995 Ok((contract_id, is_new))
996}
997
998#[cfg(test)]
999static PROP_DEPLOY_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
1000
1001#[cfg(test)]
1002mod tests {
1003 use super::*;
1004 use crate::{
1005 order_book_deploy::{
1006 OrderArgs,
1007 OrderBookConfigurables,
1008 OrderBookDeploy,
1009 OrderBookDeployConfig,
1010 OrderType,
1011 },
1012 prop::{
1013 AbsorbedFundsTransferred,
1014 MarginPoolPauseChanged,
1015 MarginPoolPlatformPayoutChanged,
1016 MarginPoolPriceFeedChanged,
1017 MarginPoolRegistryChanged,
1018 MarginTradeAccountWithdrawn,
1019 PriceInput,
1020 PropOrderBookCleanup,
1021 SessionClosed,
1022 SettlementReason,
1023 TierParams,
1024 },
1025 };
1026 use fuels::test_helpers::{
1027 AssetConfig,
1028 WalletsConfig,
1029 launch_custom_provider_and_get_wallets,
1030 };
1031 use std::time::{
1032 SystemTime,
1033 UNIX_EPOCH,
1034 };
1035
1036 fn assert_recent_timestamp(timestamp: u64) {
1037 let now = SystemTime::now()
1038 .duration_since(UNIX_EPOCH)
1039 .expect("system time predates the Unix epoch")
1040 .as_secs();
1041 assert!(
1042 timestamp.abs_diff(now) <= 1,
1043 "event timestamp {timestamp} is not close to {now}"
1044 );
1045 }
1046
1047 #[tokio::test]
1048 async fn deploys_independent_prop_system_and_registers_child() {
1049 let _test_guard = PROP_DEPLOY_TEST_LOCK.lock().await;
1050 let collateral_asset = AssetId::new([1; 32]);
1051 let base_asset = AssetId::new([2; 32]);
1052 let initial_balance = 10_000_000_000u64;
1053 let mut wallets = launch_custom_provider_and_get_wallets(
1054 WalletsConfig::new_multiple_assets(
1055 2,
1056 vec![
1057 AssetConfig {
1058 id: AssetId::default(),
1059 num_coins: 2,
1060 coin_amount: initial_balance,
1061 },
1062 AssetConfig {
1063 id: collateral_asset,
1064 num_coins: 2,
1065 coin_amount: initial_balance,
1066 },
1067 AssetConfig {
1068 id: base_asset,
1069 num_coins: 2,
1070 coin_amount: initial_balance,
1071 },
1072 ],
1073 ),
1074 None,
1075 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
1076 )
1077 .await
1078 .unwrap();
1079 let user = wallets.pop().unwrap();
1080 let deployer = wallets.pop().unwrap();
1081
1082 let deployment = PropDeployment::deploy(&deployer, &{
1083 let mut config = PropDeployConfig::new(collateral_asset);
1086 config.max_offchain_age_seconds = Some(10_000_000_000);
1087 config
1088 })
1089 .await
1090 .unwrap();
1091
1092 let oracle_target = deployment
1093 .oracle
1094 .methods()
1095 .get_prop_account_impl()
1096 .simulate(Execution::state_read_only())
1097 .await
1098 .unwrap()
1099 .value;
1100 assert_eq!(
1101 oracle_target,
1102 Some(ContractId::from(deployment.account_blob_id))
1103 );
1104 assert_eq!(
1105 deployment
1106 .oracle
1107 .methods()
1108 .get_prop_margin_pool()
1109 .simulate(Execution::state_read_only())
1110 .await
1111 .unwrap()
1112 .value,
1113 Some(deployment.pool_id)
1114 );
1115 assert_eq!(
1116 deployment
1117 .registry
1118 .methods()
1119 .get_prop_oracle_id()
1120 .simulate(Execution::state_read_only())
1121 .await
1122 .unwrap()
1123 .value,
1124 deployment.oracle_id
1125 );
1126 assert_eq!(
1127 deployment
1128 .registry
1129 .methods()
1130 .get_prop_pool_id()
1131 .with_contract_ids(&[deployment.oracle_id])
1132 .simulate(Execution::state_read_only())
1133 .await
1134 .unwrap()
1135 .value,
1136 deployment.pool_id
1137 );
1138 assert_eq!(
1139 deployment
1140 .pool
1141 .methods()
1142 .registry()
1143 .simulate(Execution::state_read_only())
1144 .await
1145 .unwrap()
1146 .value,
1147 deployment.registry_id
1148 );
1149 assert_eq!(
1150 deployment
1151 .pool
1152 .methods()
1153 .price_feed()
1154 .simulate(Execution::state_read_only())
1155 .await
1156 .unwrap()
1157 .value,
1158 deployment.price_feed_id
1159 );
1160 let deployer_identity = Identity::Address(deployer.address());
1161
1162 let inventory_amount = 1_234;
1165 deployer
1166 .force_transfer_to_contract(
1167 deployment.pool.contract_id(),
1168 inventory_amount,
1169 collateral_asset,
1170 TxPolicies::default(),
1171 )
1172 .await
1173 .unwrap();
1174 assert_eq!(
1175 deployment
1176 .pool
1177 .get_balances()
1178 .await
1179 .unwrap()
1180 .get(&collateral_asset)
1181 .copied()
1182 .unwrap_or_default(),
1183 inventory_amount,
1184 );
1185
1186 let registry_update = deployment
1187 .pool
1188 .methods()
1189 .set_registry(deployment.oracle_id)
1190 .call()
1191 .await
1192 .unwrap();
1193 let registry_events = registry_update
1194 .decode_logs_with_type::<MarginPoolRegistryChanged>()
1195 .unwrap();
1196 assert_recent_timestamp(registry_events[0].timestamp.unix);
1197 assert_eq!(
1198 registry_events,
1199 vec![MarginPoolRegistryChanged {
1200 old_registry: deployment.registry_id,
1201 new_registry: deployment.oracle_id,
1202 timestamp: registry_events[0].timestamp.clone(),
1203 }]
1204 );
1205 assert_eq!(
1206 deployment
1207 .pool
1208 .methods()
1209 .registry()
1210 .simulate(Execution::state_read_only())
1211 .await
1212 .unwrap()
1213 .value,
1214 deployment.oracle_id
1215 );
1216 deployment
1217 .pool
1218 .methods()
1219 .set_registry(deployment.registry_id)
1220 .call()
1221 .await
1222 .unwrap();
1223
1224 let price_feed_update = deployment
1225 .pool
1226 .methods()
1227 .set_price_feed(deployment.oracle_id)
1228 .call()
1229 .await
1230 .unwrap();
1231 let price_feed_events = price_feed_update
1232 .decode_logs_with_type::<MarginPoolPriceFeedChanged>()
1233 .unwrap();
1234 assert_recent_timestamp(price_feed_events[0].timestamp.unix);
1235 assert_eq!(
1236 price_feed_events,
1237 vec![MarginPoolPriceFeedChanged {
1238 old_price_feed: deployment.price_feed_id,
1239 new_price_feed: deployment.oracle_id,
1240 timestamp: price_feed_events[0].timestamp.clone(),
1241 }]
1242 );
1243 assert_eq!(
1244 deployment
1245 .pool
1246 .methods()
1247 .price_feed()
1248 .simulate(Execution::state_read_only())
1249 .await
1250 .unwrap()
1251 .value,
1252 deployment.oracle_id
1253 );
1254 deployment
1255 .pool
1256 .methods()
1257 .set_price_feed(deployment.price_feed_id)
1258 .call()
1259 .await
1260 .unwrap();
1261
1262 let platform_payout = Identity::Address(user.address());
1263 let payout_update = deployment
1264 .pool
1265 .methods()
1266 .set_platform_payout(platform_payout)
1267 .call()
1268 .await
1269 .unwrap();
1270 let payout_events = payout_update
1271 .decode_logs_with_type::<MarginPoolPlatformPayoutChanged>()
1272 .unwrap();
1273 assert_recent_timestamp(payout_events[0].timestamp.unix);
1274 assert_eq!(
1275 payout_events,
1276 vec![MarginPoolPlatformPayoutChanged {
1277 old_platform_payout: deployer_identity,
1278 new_platform_payout: platform_payout,
1279 timestamp: payout_events[0].timestamp.clone(),
1280 }]
1281 );
1282 deployment
1283 .pool
1284 .methods()
1285 .set_platform_payout(deployer_identity)
1286 .call()
1287 .await
1288 .unwrap();
1289
1290 let pause_response = deployment.pool.methods().pause().call().await.unwrap();
1291 let pause_events = pause_response
1292 .decode_logs_with_type::<MarginPoolPauseChanged>()
1293 .unwrap();
1294 assert_recent_timestamp(pause_events[0].timestamp.unix);
1295 assert_eq!(pause_events.len(), 1);
1296 assert!(pause_events[0].paused);
1297
1298 let unpause_response = deployment.pool.methods().unpause().call().await.unwrap();
1299 let unpause_events = unpause_response
1300 .decode_logs_with_type::<MarginPoolPauseChanged>()
1301 .unwrap();
1302 assert_recent_timestamp(unpause_events[0].timestamp.unix);
1303 assert_eq!(unpause_events.len(), 1);
1304 assert!(!unpause_events[0].paused);
1305
1306 let user_pool = PropMarginPoolContract::new(deployment.pool_id, user.clone());
1307 assert!(
1308 user_pool
1309 .methods()
1310 .set_registry(deployment.oracle_id)
1311 .call()
1312 .await
1313 .is_err()
1314 );
1315
1316 assert!(
1317 deployment
1318 .pool
1319 .methods()
1320 .has_role(0, deployer_identity)
1321 .simulate(Execution::state_read_only())
1322 .await
1323 .unwrap()
1324 .value
1325 );
1326 let order_book_configurables = OrderBookConfigurables::default()
1327 .with_MAKER_FEE(0u64.into())
1328 .unwrap()
1329 .with_TAKER_FEE(0u64.into())
1330 .unwrap()
1331 .with_MIN_ORDER(1)
1332 .unwrap()
1333 .with_DUST(0)
1334 .unwrap();
1335 let order_book_config =
1336 OrderBookDeployConfig::with_configurables(order_book_configurables);
1337 let order_book = OrderBookDeploy::deploy(
1338 &deployer,
1339 base_asset,
1340 collateral_asset,
1341 &order_book_config,
1342 )
1343 .await
1344 .unwrap();
1345 let mut second_order_book_config = order_book_config.clone();
1346 second_order_book_config.salt = Salt::from([1u8; 32]);
1347 let second_order_book = OrderBookDeploy::deploy(
1348 &deployer,
1349 base_asset,
1350 collateral_asset,
1351 &second_order_book_config,
1352 )
1353 .await
1354 .unwrap();
1355
1356 deployment
1357 .price_feed
1358 .methods()
1359 .set_asset_decimals(collateral_asset, 6)
1360 .call()
1361 .await
1362 .unwrap();
1363 deployment
1364 .price_feed
1365 .methods()
1366 .set_asset_decimals(base_asset, 9)
1367 .call()
1368 .await
1369 .unwrap();
1370 deployment
1371 .price_feed
1372 .methods()
1373 .publish_prices(vec![
1374 PriceInput {
1375 asset: collateral_asset,
1376 bid: 1_000_000_000_000_000_000u64.into(),
1377 ask: 1_000_000_000_000_000_000u64.into(),
1378 timestamp: 1,
1380 },
1381 PriceInput {
1382 asset: base_asset,
1383 bid: 2_000_000_000_000_000_000u64.into(),
1384 ask: 2_000_000_000_000_000_000u64.into(),
1385 timestamp: 1,
1386 },
1387 ])
1388 .call()
1389 .await
1390 .unwrap();
1391 assert!(
1392 deployment
1393 .price_feed
1394 .methods()
1395 .has_price(collateral_asset)
1396 .simulate(Execution::state_read_only())
1397 .await
1398 .unwrap()
1399 .value
1400 );
1401
1402 let tier_params = TierParams {
1403 line: 10_000_000,
1404 leverage: 5,
1405 duration: 86_400,
1406 maintenance_bps: 250,
1407 open_buffer_bps: 375,
1408 liq_price_factor: 9_900,
1409 prolong_fee: [0, 21_000, 76_000, 738_000],
1412 max_credit_line_bps: 20_000,
1413 max_price_age: 60,
1414 open_fee: 0,
1415 profit_share_bps: 1_000,
1416 price_band_bps: 1_000,
1417 };
1418 assert!(
1419 deployment
1420 .pool
1421 .methods()
1422 .publish_tier_version(
1423 1,
1424 TierParams {
1425 leverage: 0,
1426 ..tier_params.clone()
1427 },
1428 vec![order_book.contract_id],
1429 )
1430 .call()
1431 .await
1432 .is_err()
1433 );
1434 deployment
1435 .pool
1436 .methods()
1437 .publish_tier_version(
1438 1,
1439 tier_params,
1440 vec![order_book.contract_id, second_order_book.contract_id],
1441 )
1442 .with_contract_ids(&[
1443 order_book.contract_id,
1444 second_order_book.contract_id,
1445 deployment.price_feed_id,
1446 ])
1447 .call()
1448 .await
1449 .unwrap();
1450
1451 let tier = deployment
1452 .pool
1453 .methods()
1454 .get_tier(1, 1)
1455 .simulate(Execution::state_read_only())
1456 .await
1457 .unwrap()
1458 .value
1459 .expect("published tier version");
1460 assert_eq!(tier.line, 10_000_000);
1461 assert_eq!(
1462 deployment
1463 .pool
1464 .methods()
1465 .current_tier_version(1)
1466 .simulate(Execution::state_read_only())
1467 .await
1468 .unwrap()
1469 .value,
1470 Some(1)
1471 );
1472 assert_eq!(
1473 deployment
1474 .pool
1475 .methods()
1476 .tier_books(1, 1)
1477 .simulate(Execution::state_read_only())
1478 .await
1479 .unwrap()
1480 .value,
1481 vec![order_book.contract_id, second_order_book.contract_id]
1482 );
1483 let tier_assets = deployment
1484 .pool
1485 .methods()
1486 .tier_assets(1, 1)
1487 .simulate(Execution::state_read_only())
1488 .await
1489 .unwrap()
1490 .value;
1491 assert_eq!(tier_assets.len(), 2);
1492 assert!(tier_assets.contains(&base_asset));
1493 assert!(tier_assets.contains(&collateral_asset));
1494 assert!(
1495 deployment
1496 .pool
1497 .methods()
1498 .get_tier(1, 2)
1499 .simulate(Execution::state_read_only())
1500 .await
1501 .unwrap()
1502 .value
1503 .is_none()
1504 );
1505 assert!(
1506 deployment
1507 .pool
1508 .methods()
1509 .tier_books(1, 2)
1510 .simulate(Execution::state_read_only())
1511 .await
1512 .is_err()
1513 );
1514 assert!(
1515 deployment
1516 .pool
1517 .methods()
1518 .tier_assets(1, 2)
1519 .simulate(Execution::state_read_only())
1520 .await
1521 .is_err()
1522 );
1523
1524 deployment
1525 .price_feed
1526 .methods()
1527 .set_asset_decimals(base_asset, 8)
1528 .call()
1529 .await
1530 .unwrap();
1531 deployment
1532 .price_feed
1533 .methods()
1534 .publish_prices(vec![PriceInput {
1535 asset: base_asset,
1538 bid: 2_000_000_000_000_000_000u64.into(),
1539 ask: 2_000_000_000_000_000_000u64.into(),
1540 timestamp: 2,
1541 }])
1542 .call()
1543 .await
1544 .unwrap();
1545 assert!(
1546 deployment
1547 .pool
1548 .methods()
1549 .set_price_feed(deployment.price_feed_id)
1550 .with_contracts(&[&deployment.price_feed])
1551 .call()
1552 .await
1553 .is_err()
1554 );
1555 deployment
1556 .price_feed
1557 .methods()
1558 .set_asset_decimals(base_asset, 9)
1559 .call()
1560 .await
1561 .unwrap();
1562 deployment
1563 .price_feed
1564 .methods()
1565 .publish_prices(vec![PriceInput {
1566 asset: base_asset,
1567 bid: 2_000_000_000_000_000_000u64.into(),
1568 ask: 2_000_000_000_000_000_000u64.into(),
1569 timestamp: 3,
1570 }])
1571 .call()
1572 .await
1573 .unwrap();
1574
1575 let parent = Identity::Address(user.address());
1576 let registration_error = deployment
1577 .deploy_account(&deployer, parent, 7)
1578 .await
1579 .expect_err("a caller other than the configured parent must be rejected");
1580 assert!(
1581 format!("{registration_error:?}").contains("NotParent"),
1582 "unexpected registration error: {registration_error:?}"
1583 );
1584 let child = deployment.deploy_account(&user, parent, 7).await.unwrap();
1585 assert!(
1586 deployment
1587 .registry
1588 .methods()
1589 .prop_is_valid(child.contract_id())
1590 .simulate(Execution::state_read_only())
1591 .await
1592 .unwrap()
1593 .value
1594 );
1595 assert_eq!(
1596 child
1597 .methods()
1598 .parent()
1599 .simulate(Execution::state_read_only())
1600 .await
1601 .unwrap()
1602 .value,
1603 parent
1604 );
1605 assert_eq!(
1606 child
1607 .methods()
1608 .index()
1609 .simulate(Execution::state_read_only())
1610 .await
1611 .unwrap()
1612 .value,
1613 7
1614 );
1615 assert_eq!(
1616 child
1617 .methods()
1618 .pool()
1619 .with_contract_ids(&[deployment.oracle_id])
1620 .simulate(Execution::state_read_only())
1621 .await
1622 .unwrap()
1623 .value,
1624 deployment.pool_id
1625 );
1626 assert_eq!(
1627 child
1628 .methods()
1629 .oracle()
1630 .simulate(Execution::state_read_only())
1631 .await
1632 .unwrap()
1633 .value,
1634 deployment.oracle_id
1635 );
1636
1637 let collateral = 2_000_000;
1638 let account = PropAccountContract::new(child.contract_id(), user.clone());
1639 let missing_payment_error = account
1640 .methods()
1641 .start_session(1, collateral, ProlongPeriod::SixHours)
1642 .with_contract_ids(&[
1643 deployment.oracle_id,
1644 deployment.pool_id,
1645 deployment.registry_id,
1646 ])
1647 .call()
1648 .await
1649 .unwrap_err();
1650 assert!(
1651 missing_payment_error
1652 .to_string()
1653 .contains("PaymentAmountMismatch"),
1654 "unexpected missing-payment error: {missing_payment_error:#}"
1655 );
1656 let mismatched_payment_error = account
1657 .methods()
1658 .start_session(1, collateral, ProlongPeriod::SixHours)
1659 .call_params(CallParameters::new(
1660 collateral - 1,
1661 collateral_asset,
1662 u64::MAX,
1663 ))
1664 .unwrap()
1665 .with_contract_ids(&[
1666 deployment.oracle_id,
1667 deployment.pool_id,
1668 deployment.registry_id,
1669 ])
1670 .call()
1671 .await
1672 .unwrap_err();
1673 assert!(
1674 mismatched_payment_error
1675 .to_string()
1676 .contains("PaymentAmountMismatch"),
1677 "unexpected mismatched-payment error: {mismatched_payment_error:#}"
1678 );
1679 let wrong_asset_error = account
1680 .methods()
1681 .start_session(1, collateral, ProlongPeriod::SixHours)
1682 .call_params(CallParameters::new(collateral, base_asset, u64::MAX))
1683 .unwrap()
1684 .with_contract_ids(&[
1685 deployment.oracle_id,
1686 deployment.pool_id,
1687 deployment.registry_id,
1688 ])
1689 .call()
1690 .await
1691 .unwrap_err();
1692 assert!(
1693 wrong_asset_error.to_string().contains("WrongAsset"),
1694 "unexpected wrong-asset error: {wrong_asset_error:#}"
1695 );
1696
1697 let collateral_dust = 17;
1698 let base_dust = 23;
1699 user.force_transfer_to_contract(
1700 child.contract_id(),
1701 collateral_dust,
1702 collateral_asset,
1703 TxPolicies::default(),
1704 )
1705 .await
1706 .unwrap();
1707 user.force_transfer_to_contract(
1708 child.contract_id(),
1709 base_dust,
1710 base_asset,
1711 TxPolicies::default(),
1712 )
1713 .await
1714 .unwrap();
1715 account
1716 .methods()
1717 .start_session(1, collateral, ProlongPeriod::SixHours)
1718 .call_params(CallParameters::new(collateral, collateral_asset, u64::MAX))
1719 .unwrap()
1720 .with_contract_ids(&[
1721 deployment.oracle_id,
1722 deployment.pool_id,
1723 deployment.registry_id,
1724 ])
1725 .with_variable_output_policy(VariableOutputPolicy::Exactly(2))
1726 .call()
1727 .await
1728 .unwrap();
1729 let provider = user.provider();
1730 assert_eq!(
1731 provider
1732 .get_contract_asset_balance(&child.contract_id(), &collateral_asset)
1733 .await
1734 .unwrap(),
1735 0
1736 );
1737 assert_eq!(
1738 provider
1739 .get_contract_asset_balance(&child.contract_id(), &base_asset)
1740 .await
1741 .unwrap(),
1742 0
1743 );
1744
1745 assert!(
1746 deployment
1747 .pool
1748 .methods()
1749 .is_call_allowed(child.contract_id(), order_book.contract_id)
1750 .simulate(Execution::state_read_only())
1751 .await
1752 .unwrap()
1753 .value
1754 );
1755 assert!(
1756 deployment
1757 .pool
1758 .methods()
1759 .is_call_allowed(child.contract_id(), second_order_book.contract_id)
1760 .simulate(Execution::state_read_only())
1761 .await
1762 .unwrap()
1763 .value
1764 );
1765 assert!(
1766 !deployment
1767 .pool
1768 .methods()
1769 .is_call_allowed(child.contract_id(), ContractId::new([0x99; 32]))
1770 .simulate(Execution::state_read_only())
1771 .await
1772 .unwrap()
1773 .value
1774 );
1775
1776 let session = deployment
1777 .pool
1778 .methods()
1779 .get_session(child.contract_id())
1780 .simulate(Execution::state_read_only())
1781 .await
1782 .unwrap()
1783 .value
1784 .expect("prop session opened");
1785 assert_eq!(session.session_id, 1);
1786 assert_eq!(session.credit_line, 10_000_000);
1787 assert_eq!(session.collateral, collateral);
1788
1789 assert!(
1790 deployment
1791 .pool
1792 .methods()
1793 .min_sellable(child.contract_id(), base_asset)
1794 .with_contracts(&[&order_book.order_book, &second_order_book.order_book,])
1795 .simulate(Execution::state_read_only())
1796 .await
1797 .is_err()
1798 );
1799
1800 let best_bid = 2_000_000;
1801 let bid_quantity = 1_000_000_000;
1802 order_book
1803 .order_book
1804 .methods()
1805 .create_order(OrderArgs {
1806 price: best_bid,
1807 quantity: bid_quantity,
1808 order_type: OrderType::Spot,
1809 })
1810 .call_params(CallParameters::new(
1811 bid_quantity * best_bid / 1_000_000_000,
1812 collateral_asset,
1813 u64::MAX,
1814 ))
1815 .unwrap()
1816 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1817 .call()
1818 .await
1819 .unwrap();
1820 assert_eq!(
1821 order_book
1822 .order_book
1823 .methods()
1824 .get_base_decimals()
1825 .simulate(Execution::state_read_only())
1826 .await
1827 .unwrap()
1828 .value,
1829 1_000_000_000
1830 );
1831 assert_eq!(
1832 deployment
1833 .pool
1834 .methods()
1835 .min_sellable(child.contract_id(), base_asset)
1836 .with_contracts(&[&order_book.order_book, &second_order_book.order_book,])
1837 .simulate(Execution::state_read_only())
1838 .await
1839 .unwrap()
1840 .value,
1841 Some(500)
1842 );
1843
1844 let close = account
1845 .methods()
1846 .close_session(Vec::<PropOrderBookCleanup>::new())
1847 .with_contracts(&[
1848 &deployment.oracle,
1849 &deployment.pool,
1850 &order_book.order_book,
1851 &second_order_book.order_book,
1852 ])
1853 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1854 .call()
1855 .await
1856 .unwrap();
1857 let close_events = close.decode_logs_with_type::<SessionClosed>().unwrap();
1858 assert!(
1859 close
1860 .decode_logs_with_type::<AbsorbedFundsTransferred>()
1861 .unwrap()
1862 .is_empty()
1863 );
1864 assert_recent_timestamp(close_events[0].timestamp.unix);
1865 assert_eq!(
1866 close_events,
1867 vec![SessionClosed {
1868 account: child.contract_id(),
1869 session_id: 1,
1870 reason: SettlementReason::UserClose,
1871 v: 10_000_000,
1872 v_is_negative: false,
1873 v_liq: 10_000_000,
1874 v_liq_is_negative: false,
1875 profit_abs: 0,
1876 profit_is_negative: false,
1877 fees_accrued: 0,
1878 platform_total: 0,
1879 user_net: collateral,
1880 bad_debt: 0,
1881 cancelled_debt: vec![],
1882 payout_parent: vec![(collateral_asset, collateral)],
1883 payout_platform: vec![],
1884 timestamp: close_events[0].timestamp.clone(),
1885 }]
1886 );
1887 assert!(
1888 !deployment
1889 .pool
1890 .methods()
1891 .has_session(child.contract_id())
1892 .simulate(Execution::state_read_only())
1893 .await
1894 .unwrap()
1895 .value
1896 );
1897
1898 let excess_collateral = 25_000_000;
1899 account
1900 .methods()
1901 .start_session(1, excess_collateral, ProlongPeriod::SixHours)
1902 .call_params(CallParameters::new(
1903 excess_collateral,
1904 collateral_asset,
1905 u64::MAX,
1906 ))
1907 .unwrap()
1908 .with_contract_ids(&[
1909 deployment.oracle_id,
1910 deployment.pool_id,
1911 deployment.registry_id,
1912 ])
1913 .call()
1914 .await
1915 .unwrap();
1916 let excess_session = deployment
1917 .pool
1918 .methods()
1919 .get_session(child.contract_id())
1920 .simulate(Execution::state_read_only())
1921 .await
1922 .unwrap()
1923 .value
1924 .expect("excess-collateral session opened");
1925 assert_eq!(excess_session.session_id, 2);
1926 assert_eq!(excess_session.collateral, excess_collateral);
1927 assert_eq!(excess_session.credit_line, 20_000_000);
1928
1929 let excess_close = account
1930 .methods()
1931 .close_session(Vec::<PropOrderBookCleanup>::new())
1932 .with_contracts(&[
1933 &deployment.oracle,
1934 &deployment.pool,
1935 &order_book.order_book,
1936 &second_order_book.order_book,
1937 ])
1938 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1939 .call()
1940 .await
1941 .unwrap();
1942 let excess_close_events = excess_close
1943 .decode_logs_with_type::<SessionClosed>()
1944 .unwrap();
1945 assert_recent_timestamp(excess_close_events[0].timestamp.unix);
1946 assert_eq!(
1947 excess_close_events,
1948 vec![SessionClosed {
1949 account: child.contract_id(),
1950 session_id: 2,
1951 reason: SettlementReason::UserClose,
1952 v: 33_000_000,
1953 v_is_negative: false,
1954 v_liq: 33_000_000,
1955 v_liq_is_negative: false,
1956 profit_abs: 0,
1957 profit_is_negative: false,
1958 fees_accrued: 0,
1959 platform_total: 0,
1960 user_net: excess_collateral,
1961 bad_debt: 0,
1962 cancelled_debt: vec![],
1963 payout_parent: vec![(collateral_asset, excess_collateral)],
1964 payout_platform: vec![],
1965 timestamp: excess_close_events[0].timestamp.clone(),
1966 }]
1967 );
1968
1969 let withdrawal = 1_000;
1970 user.force_transfer_to_contract(
1971 child.contract_id(),
1972 withdrawal,
1973 collateral_asset,
1974 TxPolicies::default(),
1975 )
1976 .await
1977 .unwrap();
1978 let balance_before = user.get_asset_balance(&collateral_asset).await.unwrap();
1979 let withdraw_response = account
1980 .methods()
1981 .withdraw(collateral_asset, withdrawal)
1982 .with_contract_ids(&[deployment.oracle_id, deployment.pool_id])
1983 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1984 .call()
1985 .await
1986 .unwrap();
1987 let withdraw_events = withdraw_response
1988 .decode_logs_with_type::<MarginTradeAccountWithdrawn>()
1989 .unwrap();
1990 assert_recent_timestamp(withdraw_events[0].timestamp.unix);
1991 assert_eq!(
1992 withdraw_events,
1993 vec![MarginTradeAccountWithdrawn {
1994 account: child.contract_id(),
1995 parent,
1996 asset_id: collateral_asset,
1997 amount: withdrawal,
1998 timestamp: withdraw_events[0].timestamp.clone(),
1999 }]
2000 );
2001 assert_eq!(
2002 user.get_asset_balance(&collateral_asset).await.unwrap(),
2003 balance_before + withdrawal as u128
2004 );
2005 }
2006}
2007
2008#[cfg(test)]
2009#[path = "prop_deploy_tests.rs"]
2010mod integration_tests;