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