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)]
1068#[path = "prop_legacy_fixtures.rs"]
1069mod legacy_fixtures;
1070
1071#[cfg(test)]
1072mod tests {
1073 use super::*;
1074 use crate::{
1075 order_book_deploy::{
1076 OrderArgs,
1077 OrderBookConfigurables,
1078 OrderBookDeploy,
1079 OrderBookDeployConfig,
1080 OrderType,
1081 },
1082 prop::{
1083 MarginPoolPauseChanged,
1084 MarginPoolPlatformPayoutChanged,
1085 MarginPoolPriceFeedChanged,
1086 MarginPoolRegistryChanged,
1087 MarginTradeAccountWithdrawn,
1088 PriceInput,
1089 PropOrderBookCleanup,
1090 SessionClosed,
1091 SessionSeized,
1092 SettlementReason,
1093 TierParams,
1094 },
1095 };
1096 use fuels::test_helpers::{
1097 AssetConfig,
1098 WalletsConfig,
1099 launch_custom_provider_and_get_wallets,
1100 };
1101 use std::time::{
1102 SystemTime,
1103 UNIX_EPOCH,
1104 };
1105
1106 fn assert_recent_timestamp(timestamp: u64) {
1107 let now = SystemTime::now()
1108 .duration_since(UNIX_EPOCH)
1109 .expect("system time predates the Unix epoch")
1110 .as_secs();
1111 assert!(
1118 timestamp.abs_diff(now) <= 120,
1119 "event timestamp {timestamp} is not close to {now}"
1120 );
1121 }
1122
1123 #[tokio::test]
1124 async fn deploys_independent_prop_system_and_registers_child() {
1125 let collateral_asset = AssetId::new([1; 32]);
1126 let base_asset = AssetId::new([2; 32]);
1127 let initial_balance = 10_000_000_000u64;
1128 let mut wallets = launch_custom_provider_and_get_wallets(
1129 WalletsConfig::new_multiple_assets(
1130 2,
1131 vec![
1132 AssetConfig {
1133 id: AssetId::default(),
1134 num_coins: 2,
1135 coin_amount: initial_balance,
1136 },
1137 AssetConfig {
1138 id: collateral_asset,
1139 num_coins: 2,
1140 coin_amount: initial_balance,
1141 },
1142 AssetConfig {
1143 id: base_asset,
1144 num_coins: 2,
1145 coin_amount: initial_balance,
1146 },
1147 ],
1148 ),
1149 None,
1150 Some(::fuels::test_helpers::ChainConfig::local_testnet()),
1151 )
1152 .await
1153 .unwrap();
1154 let user = wallets.pop().unwrap();
1155 let deployer = wallets.pop().unwrap();
1156
1157 let deployment = PropDeployment::deploy(&deployer, &{
1158 let mut config = PropDeployConfig::new(collateral_asset);
1161 config.max_offchain_age_seconds = Some(10_000_000_000);
1162 config
1163 })
1164 .await
1165 .unwrap();
1166
1167 let oracle_target = deployment
1168 .oracle
1169 .methods()
1170 .get_prop_account_impl()
1171 .simulate(Execution::state_read_only())
1172 .await
1173 .unwrap()
1174 .value;
1175 assert_eq!(
1176 oracle_target,
1177 Some(ContractId::from(deployment.account_blob_id))
1178 );
1179 assert_eq!(
1180 deployment
1181 .oracle
1182 .methods()
1183 .get_prop_margin_pool()
1184 .simulate(Execution::state_read_only())
1185 .await
1186 .unwrap()
1187 .value,
1188 Some(deployment.pool_id)
1189 );
1190 assert_eq!(
1191 deployment
1192 .registry
1193 .methods()
1194 .get_prop_oracle_id()
1195 .simulate(Execution::state_read_only())
1196 .await
1197 .unwrap()
1198 .value,
1199 deployment.oracle_id
1200 );
1201 assert_eq!(
1202 deployment
1203 .registry
1204 .methods()
1205 .get_prop_pool_id()
1206 .with_contract_ids(&[deployment.oracle_id])
1207 .simulate(Execution::state_read_only())
1208 .await
1209 .unwrap()
1210 .value,
1211 deployment.pool_id
1212 );
1213 assert_eq!(
1214 deployment
1215 .pool
1216 .methods()
1217 .registry()
1218 .simulate(Execution::state_read_only())
1219 .await
1220 .unwrap()
1221 .value,
1222 deployment.registry_id
1223 );
1224 assert_eq!(
1225 deployment
1226 .pool
1227 .methods()
1228 .price_feed()
1229 .simulate(Execution::state_read_only())
1230 .await
1231 .unwrap()
1232 .value,
1233 deployment.price_feed_id
1234 );
1235 let deployer_identity = Identity::Address(deployer.address());
1236
1237 let inventory_amount = 1_234;
1240 deployer
1241 .force_transfer_to_contract(
1242 deployment.pool.contract_id(),
1243 inventory_amount,
1244 collateral_asset,
1245 TxPolicies::default(),
1246 )
1247 .await
1248 .unwrap();
1249 assert_eq!(
1250 deployment
1251 .pool
1252 .get_balances()
1253 .await
1254 .unwrap()
1255 .get(&collateral_asset)
1256 .copied()
1257 .unwrap_or_default(),
1258 inventory_amount,
1259 );
1260
1261 let registry_update = deployment
1262 .pool
1263 .methods()
1264 .set_registry(deployment.oracle_id)
1265 .call()
1266 .await
1267 .unwrap();
1268 let registry_events = registry_update
1269 .decode_logs_with_type::<MarginPoolRegistryChanged>()
1270 .unwrap();
1271 assert_recent_timestamp(registry_events[0].timestamp.unix);
1272 assert_eq!(
1273 registry_events,
1274 vec![MarginPoolRegistryChanged {
1275 old_registry: deployment.registry_id,
1276 new_registry: deployment.oracle_id,
1277 timestamp: registry_events[0].timestamp.clone(),
1278 }]
1279 );
1280 assert_eq!(
1281 deployment
1282 .pool
1283 .methods()
1284 .registry()
1285 .simulate(Execution::state_read_only())
1286 .await
1287 .unwrap()
1288 .value,
1289 deployment.oracle_id
1290 );
1291 deployment
1292 .pool
1293 .methods()
1294 .set_registry(deployment.registry_id)
1295 .call()
1296 .await
1297 .unwrap();
1298
1299 let price_feed_update = deployment
1300 .pool
1301 .methods()
1302 .set_price_feed(deployment.oracle_id)
1303 .call()
1304 .await
1305 .unwrap();
1306 let price_feed_events = price_feed_update
1307 .decode_logs_with_type::<MarginPoolPriceFeedChanged>()
1308 .unwrap();
1309 assert_recent_timestamp(price_feed_events[0].timestamp.unix);
1310 assert_eq!(
1311 price_feed_events,
1312 vec![MarginPoolPriceFeedChanged {
1313 old_price_feed: deployment.price_feed_id,
1314 new_price_feed: deployment.oracle_id,
1315 timestamp: price_feed_events[0].timestamp.clone(),
1316 }]
1317 );
1318 assert_eq!(
1319 deployment
1320 .pool
1321 .methods()
1322 .price_feed()
1323 .simulate(Execution::state_read_only())
1324 .await
1325 .unwrap()
1326 .value,
1327 deployment.oracle_id
1328 );
1329 deployment
1330 .pool
1331 .methods()
1332 .set_price_feed(deployment.price_feed_id)
1333 .call()
1334 .await
1335 .unwrap();
1336
1337 let platform_payout = Identity::Address(user.address());
1338 let payout_update = deployment
1339 .pool
1340 .methods()
1341 .set_platform_payout(platform_payout)
1342 .call()
1343 .await
1344 .unwrap();
1345 let payout_events = payout_update
1346 .decode_logs_with_type::<MarginPoolPlatformPayoutChanged>()
1347 .unwrap();
1348 assert_recent_timestamp(payout_events[0].timestamp.unix);
1349 assert_eq!(
1350 payout_events,
1351 vec![MarginPoolPlatformPayoutChanged {
1352 old_platform_payout: deployer_identity,
1353 new_platform_payout: platform_payout,
1354 timestamp: payout_events[0].timestamp.clone(),
1355 }]
1356 );
1357 deployment
1358 .pool
1359 .methods()
1360 .set_platform_payout(deployer_identity)
1361 .call()
1362 .await
1363 .unwrap();
1364
1365 let pause_response = deployment.pool.methods().pause().call().await.unwrap();
1366 let pause_events = pause_response
1367 .decode_logs_with_type::<MarginPoolPauseChanged>()
1368 .unwrap();
1369 assert_recent_timestamp(pause_events[0].timestamp.unix);
1370 assert_eq!(pause_events.len(), 1);
1371 assert!(pause_events[0].paused);
1372
1373 let unpause_response = deployment.pool.methods().unpause().call().await.unwrap();
1374 let unpause_events = unpause_response
1375 .decode_logs_with_type::<MarginPoolPauseChanged>()
1376 .unwrap();
1377 assert_recent_timestamp(unpause_events[0].timestamp.unix);
1378 assert_eq!(unpause_events.len(), 1);
1379 assert!(!unpause_events[0].paused);
1380
1381 let user_pool = PropMarginPoolContract::new(deployment.pool_id, user.clone());
1382 assert!(
1383 user_pool
1384 .methods()
1385 .set_registry(deployment.oracle_id)
1386 .call()
1387 .await
1388 .is_err()
1389 );
1390
1391 assert!(
1392 deployment
1393 .pool
1394 .methods()
1395 .has_role(0, deployer_identity)
1396 .simulate(Execution::state_read_only())
1397 .await
1398 .unwrap()
1399 .value
1400 );
1401 let order_book_configurables = OrderBookConfigurables::default()
1402 .with_MAKER_FEE(0u64.into())
1403 .unwrap()
1404 .with_TAKER_FEE(0u64.into())
1405 .unwrap()
1406 .with_MIN_ORDER(1)
1407 .unwrap()
1408 .with_DUST(0)
1409 .unwrap();
1410 let order_book_config =
1411 OrderBookDeployConfig::with_configurables(order_book_configurables);
1412 let order_book = OrderBookDeploy::deploy(
1413 &deployer,
1414 base_asset,
1415 collateral_asset,
1416 &order_book_config,
1417 )
1418 .await
1419 .unwrap();
1420 let mut second_order_book_config = order_book_config.clone();
1421 second_order_book_config.salt = Salt::from([1u8; 32]);
1422 let second_order_book = OrderBookDeploy::deploy(
1423 &deployer,
1424 base_asset,
1425 collateral_asset,
1426 &second_order_book_config,
1427 )
1428 .await
1429 .unwrap();
1430
1431 deployment
1432 .price_feed
1433 .methods()
1434 .set_asset_decimals(collateral_asset, 6)
1435 .call()
1436 .await
1437 .unwrap();
1438 deployment
1439 .price_feed
1440 .methods()
1441 .set_asset_decimals(base_asset, 9)
1442 .call()
1443 .await
1444 .unwrap();
1445 deployment
1446 .price_feed
1447 .methods()
1448 .publish_prices(vec![
1449 PriceInput {
1450 asset: collateral_asset,
1451 bid: 1_000_000_000_000_000_000u64.into(),
1452 ask: 1_000_000_000_000_000_000u64.into(),
1453 timestamp: 1,
1455 },
1456 PriceInput {
1457 asset: base_asset,
1458 bid: 2_000_000_000_000_000_000u64.into(),
1459 ask: 2_000_000_000_000_000_000u64.into(),
1460 timestamp: 1,
1461 },
1462 ])
1463 .call()
1464 .await
1465 .unwrap();
1466 assert!(
1467 deployment
1468 .price_feed
1469 .methods()
1470 .has_price(collateral_asset)
1471 .simulate(Execution::state_read_only())
1472 .await
1473 .unwrap()
1474 .value
1475 );
1476
1477 let tier_params = TierParams {
1478 line: 10_000_000,
1479 leverage: 5,
1480 duration: 86_400,
1481 maintenance_bps: 250,
1482 open_buffer_bps: 375,
1483 liq_price_factor: 9_900,
1484 prolong_fee: [0, 21_000, 76_000, 738_000],
1487 max_credit_line_bps: 20_000,
1488 max_price_age: 60,
1489 open_fee: 0,
1490 profit_share_bps: 1_000,
1491 price_band_bps: 1_000,
1492 };
1493 let error = deployment
1495 .pool
1496 .methods()
1497 .publish_tier_version(1, tier_params.clone(), vec![order_book.contract_id])
1498 .call()
1499 .await
1500 .unwrap_err();
1501 assert!(error.to_string().contains("Disabled"), "{error:#}");
1502 legacy_fixtures::seed_legacy_tier(
1503 &deployment,
1504 1,
1505 tier_params,
1506 vec![order_book.contract_id, second_order_book.contract_id],
1507 )
1508 .await
1509 .unwrap();
1510
1511 let tier = deployment
1512 .pool
1513 .methods()
1514 .get_tier(1, 1)
1515 .simulate(Execution::state_read_only())
1516 .await
1517 .unwrap()
1518 .value
1519 .expect("seeded historical tier version");
1520 assert_eq!(tier.line, 10_000_000);
1521 assert_eq!(
1522 deployment
1523 .pool
1524 .methods()
1525 .current_tier_version(1)
1526 .simulate(Execution::state_read_only())
1527 .await
1528 .unwrap()
1529 .value,
1530 Some(1)
1531 );
1532 assert_eq!(
1533 deployment
1534 .pool
1535 .methods()
1536 .tier_books(1, 1)
1537 .simulate(Execution::state_read_only())
1538 .await
1539 .unwrap()
1540 .value,
1541 vec![order_book.contract_id, second_order_book.contract_id]
1542 );
1543 let tier_assets = deployment
1544 .pool
1545 .methods()
1546 .tier_assets(1, 1)
1547 .simulate(Execution::state_read_only())
1548 .await
1549 .unwrap()
1550 .value;
1551 assert_eq!(tier_assets.len(), 2);
1552 assert!(tier_assets.contains(&base_asset));
1553 assert!(tier_assets.contains(&collateral_asset));
1554 assert!(
1555 deployment
1556 .pool
1557 .methods()
1558 .get_tier(1, 2)
1559 .simulate(Execution::state_read_only())
1560 .await
1561 .unwrap()
1562 .value
1563 .is_none()
1564 );
1565 assert!(
1566 deployment
1567 .pool
1568 .methods()
1569 .tier_books(1, 2)
1570 .simulate(Execution::state_read_only())
1571 .await
1572 .is_err()
1573 );
1574 assert!(
1575 deployment
1576 .pool
1577 .methods()
1578 .tier_assets(1, 2)
1579 .simulate(Execution::state_read_only())
1580 .await
1581 .is_err()
1582 );
1583
1584 deployment
1585 .price_feed
1586 .methods()
1587 .set_asset_decimals(base_asset, 8)
1588 .call()
1589 .await
1590 .unwrap();
1591 deployment
1592 .price_feed
1593 .methods()
1594 .publish_prices(vec![PriceInput {
1595 asset: base_asset,
1598 bid: 2_000_000_000_000_000_000u64.into(),
1599 ask: 2_000_000_000_000_000_000u64.into(),
1600 timestamp: 2,
1601 }])
1602 .call()
1603 .await
1604 .unwrap();
1605 assert!(
1606 deployment
1607 .pool
1608 .methods()
1609 .set_price_feed(deployment.price_feed_id)
1610 .with_contracts(&[&deployment.price_feed])
1611 .call()
1612 .await
1613 .is_err()
1614 );
1615 deployment
1616 .price_feed
1617 .methods()
1618 .set_asset_decimals(base_asset, 9)
1619 .call()
1620 .await
1621 .unwrap();
1622 deployment
1623 .price_feed
1624 .methods()
1625 .publish_prices(vec![PriceInput {
1626 asset: base_asset,
1627 bid: 2_000_000_000_000_000_000u64.into(),
1628 ask: 2_000_000_000_000_000_000u64.into(),
1629 timestamp: 3,
1630 }])
1631 .call()
1632 .await
1633 .unwrap();
1634
1635 let parent = Identity::Address(user.address());
1636 let registration_error = deployment
1637 .deploy_account(&deployer, parent, 7)
1638 .await
1639 .expect_err("a caller other than the configured parent must be rejected");
1640 assert!(
1641 format!("{registration_error:?}").contains("NotParent"),
1642 "unexpected registration error: {registration_error:?}"
1643 );
1644 let child = deployment.deploy_account(&user, parent, 7).await.unwrap();
1645 assert!(
1646 deployment
1647 .registry
1648 .methods()
1649 .prop_is_valid(child.contract_id())
1650 .simulate(Execution::state_read_only())
1651 .await
1652 .unwrap()
1653 .value
1654 );
1655 assert_eq!(
1656 child
1657 .methods()
1658 .parent()
1659 .simulate(Execution::state_read_only())
1660 .await
1661 .unwrap()
1662 .value,
1663 parent
1664 );
1665 assert_eq!(
1666 child
1667 .methods()
1668 .index()
1669 .simulate(Execution::state_read_only())
1670 .await
1671 .unwrap()
1672 .value,
1673 7
1674 );
1675 assert_eq!(
1676 child
1677 .methods()
1678 .pool()
1679 .with_contract_ids(&[deployment.oracle_id])
1680 .simulate(Execution::state_read_only())
1681 .await
1682 .unwrap()
1683 .value,
1684 deployment.pool_id
1685 );
1686 assert_eq!(
1687 child
1688 .methods()
1689 .oracle()
1690 .simulate(Execution::state_read_only())
1691 .await
1692 .unwrap()
1693 .value,
1694 deployment.oracle_id
1695 );
1696
1697 let collateral = 2_000_000;
1698 let account = PropAccountContract::new(child.contract_id(), user.clone());
1699 let missing_payment_error = account
1700 .methods()
1701 .start_session(1, collateral, ProlongPeriod::SixHours)
1702 .with_contract_ids(&[
1703 deployment.oracle_id,
1704 deployment.pool_id,
1705 deployment.registry_id,
1706 ])
1707 .call()
1708 .await
1709 .unwrap_err();
1710 assert!(
1711 missing_payment_error
1712 .to_string()
1713 .contains("PaymentAmountMismatch"),
1714 "unexpected missing-payment error: {missing_payment_error:#}"
1715 );
1716 let mismatched_payment_error = account
1717 .methods()
1718 .start_session(1, collateral, ProlongPeriod::SixHours)
1719 .call_params(CallParameters::new(
1720 collateral - 1,
1721 collateral_asset,
1722 u64::MAX,
1723 ))
1724 .unwrap()
1725 .with_contract_ids(&[
1726 deployment.oracle_id,
1727 deployment.pool_id,
1728 deployment.registry_id,
1729 ])
1730 .call()
1731 .await
1732 .unwrap_err();
1733 assert!(
1734 mismatched_payment_error
1735 .to_string()
1736 .contains("PaymentAmountMismatch"),
1737 "unexpected mismatched-payment error: {mismatched_payment_error:#}"
1738 );
1739 let wrong_asset_error = account
1740 .methods()
1741 .start_session(1, collateral, ProlongPeriod::SixHours)
1742 .call_params(CallParameters::new(collateral, base_asset, u64::MAX))
1743 .unwrap()
1744 .with_contract_ids(&[
1745 deployment.oracle_id,
1746 deployment.pool_id,
1747 deployment.registry_id,
1748 ])
1749 .call()
1750 .await
1751 .unwrap_err();
1752 assert!(
1753 wrong_asset_error.to_string().contains("WrongAsset"),
1754 "unexpected wrong-asset error: {wrong_asset_error:#}"
1755 );
1756
1757 let collateral_dust = 17;
1758 let base_dust = 23;
1759 user.force_transfer_to_contract(
1760 child.contract_id(),
1761 collateral_dust,
1762 collateral_asset,
1763 TxPolicies::default(),
1764 )
1765 .await
1766 .unwrap();
1767 user.force_transfer_to_contract(
1768 child.contract_id(),
1769 base_dust,
1770 base_asset,
1771 TxPolicies::default(),
1772 )
1773 .await
1774 .unwrap();
1775 account
1776 .methods()
1777 .start_session(1, collateral, ProlongPeriod::SixHours)
1778 .call_params(CallParameters::new(collateral, collateral_asset, u64::MAX))
1779 .unwrap()
1780 .with_contract_ids(&[
1781 deployment.oracle_id,
1782 deployment.pool_id,
1783 deployment.registry_id,
1784 ])
1785 .with_variable_output_policy(VariableOutputPolicy::Exactly(2))
1786 .call()
1787 .await
1788 .unwrap();
1789 let provider = user.provider();
1790 assert_eq!(
1791 provider
1792 .get_contract_asset_balance(&child.contract_id(), &collateral_asset)
1793 .await
1794 .unwrap(),
1795 0
1796 );
1797 assert_eq!(
1798 provider
1799 .get_contract_asset_balance(&child.contract_id(), &base_asset)
1800 .await
1801 .unwrap(),
1802 0
1803 );
1804
1805 assert!(
1806 deployment
1807 .pool
1808 .methods()
1809 .is_call_allowed(child.contract_id(), order_book.contract_id)
1810 .simulate(Execution::state_read_only())
1811 .await
1812 .unwrap()
1813 .value
1814 );
1815 assert!(
1816 deployment
1817 .pool
1818 .methods()
1819 .is_call_allowed(child.contract_id(), second_order_book.contract_id)
1820 .simulate(Execution::state_read_only())
1821 .await
1822 .unwrap()
1823 .value
1824 );
1825 assert!(
1826 !deployment
1827 .pool
1828 .methods()
1829 .is_call_allowed(child.contract_id(), ContractId::new([0x99; 32]))
1830 .simulate(Execution::state_read_only())
1831 .await
1832 .unwrap()
1833 .value
1834 );
1835
1836 let session = deployment
1837 .pool
1838 .methods()
1839 .get_session(child.contract_id())
1840 .simulate(Execution::state_read_only())
1841 .await
1842 .unwrap()
1843 .value
1844 .expect("prop session opened");
1845 assert_eq!(session.session_id, 1);
1846 assert_eq!(session.credit_line, 10_000_000);
1847 assert_eq!(session.collateral, collateral);
1848
1849 assert!(
1850 deployment
1851 .pool
1852 .methods()
1853 .min_sellable(child.contract_id(), base_asset)
1854 .with_contracts(&[&order_book.order_book, &second_order_book.order_book,])
1855 .simulate(Execution::state_read_only())
1856 .await
1857 .is_err()
1858 );
1859
1860 let best_bid = 2_000_000;
1861 let bid_quantity = 1_000_000_000;
1862 order_book
1863 .order_book
1864 .methods()
1865 .create_order(OrderArgs {
1866 price: best_bid,
1867 quantity: bid_quantity,
1868 order_type: OrderType::Spot,
1869 })
1870 .call_params(CallParameters::new(
1871 bid_quantity * best_bid / 1_000_000_000,
1872 collateral_asset,
1873 u64::MAX,
1874 ))
1875 .unwrap()
1876 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1877 .call()
1878 .await
1879 .unwrap();
1880 assert_eq!(
1881 order_book
1882 .order_book
1883 .methods()
1884 .get_base_decimals()
1885 .simulate(Execution::state_read_only())
1886 .await
1887 .unwrap()
1888 .value,
1889 1_000_000_000
1890 );
1891 assert_eq!(
1892 deployment
1893 .pool
1894 .methods()
1895 .min_sellable(child.contract_id(), base_asset)
1896 .with_contracts(&[&order_book.order_book, &second_order_book.order_book,])
1897 .simulate(Execution::state_read_only())
1898 .await
1899 .unwrap()
1900 .value,
1901 Some(500)
1902 );
1903
1904 let close = account
1905 .methods()
1906 .close_session(Vec::<PropOrderBookCleanup>::new())
1907 .with_contracts(&[
1908 &deployment.oracle,
1909 &deployment.pool,
1910 &order_book.order_book,
1911 &second_order_book.order_book,
1912 ])
1913 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1914 .call()
1915 .await
1916 .unwrap();
1917 let close_events = close.decode_logs_with_type::<SessionClosed>().unwrap();
1918 assert!(
1919 close
1920 .decode_logs_with_type::<SessionSeized>()
1921 .unwrap()
1922 .is_empty()
1923 );
1924 assert_recent_timestamp(close_events[0].timestamp.unix);
1925 assert_eq!(
1926 close_events,
1927 vec![SessionClosed {
1928 account: child.contract_id(),
1929 session_id: 1,
1930 reason: SettlementReason::UserClose,
1931 v: 10_000_000,
1932 v_is_negative: false,
1933 v_liq: 10_000_000,
1934 v_liq_is_negative: false,
1935 profit_abs: 0,
1936 profit_is_negative: false,
1937 fees_accrued: 0,
1938 platform_total: 0,
1939 user_net: collateral,
1940 bad_debt: 0,
1941 cancelled_debt: vec![],
1942 payout_parent: vec![(collateral_asset, collateral)],
1943 payout_platform: vec![],
1944 timestamp: close_events[0].timestamp.clone(),
1945 }]
1946 );
1947 assert!(
1948 !deployment
1949 .pool
1950 .methods()
1951 .has_session(child.contract_id())
1952 .simulate(Execution::state_read_only())
1953 .await
1954 .unwrap()
1955 .value
1956 );
1957
1958 let excess_collateral = 25_000_000;
1959 account
1960 .methods()
1961 .start_session(1, excess_collateral, ProlongPeriod::SixHours)
1962 .call_params(CallParameters::new(
1963 excess_collateral,
1964 collateral_asset,
1965 u64::MAX,
1966 ))
1967 .unwrap()
1968 .with_contract_ids(&[
1969 deployment.oracle_id,
1970 deployment.pool_id,
1971 deployment.registry_id,
1972 ])
1973 .call()
1974 .await
1975 .unwrap();
1976 let excess_session = deployment
1977 .pool
1978 .methods()
1979 .get_session(child.contract_id())
1980 .simulate(Execution::state_read_only())
1981 .await
1982 .unwrap()
1983 .value
1984 .expect("excess-collateral session opened");
1985 assert_eq!(excess_session.session_id, 2);
1986 assert_eq!(excess_session.collateral, excess_collateral);
1987 assert_eq!(excess_session.credit_line, 20_000_000);
1988
1989 let excess_close = account
1990 .methods()
1991 .close_session(Vec::<PropOrderBookCleanup>::new())
1992 .with_contracts(&[
1993 &deployment.oracle,
1994 &deployment.pool,
1995 &order_book.order_book,
1996 &second_order_book.order_book,
1997 ])
1998 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
1999 .call()
2000 .await
2001 .unwrap();
2002 let excess_close_events = excess_close
2003 .decode_logs_with_type::<SessionClosed>()
2004 .unwrap();
2005 assert_recent_timestamp(excess_close_events[0].timestamp.unix);
2006 assert_eq!(
2007 excess_close_events,
2008 vec![SessionClosed {
2009 account: child.contract_id(),
2010 session_id: 2,
2011 reason: SettlementReason::UserClose,
2012 v: 33_000_000,
2013 v_is_negative: false,
2014 v_liq: 33_000_000,
2015 v_liq_is_negative: false,
2016 profit_abs: 0,
2017 profit_is_negative: false,
2018 fees_accrued: 0,
2019 platform_total: 0,
2020 user_net: excess_collateral,
2021 bad_debt: 0,
2022 cancelled_debt: vec![],
2023 payout_parent: vec![(collateral_asset, excess_collateral)],
2024 payout_platform: vec![],
2025 timestamp: excess_close_events[0].timestamp.clone(),
2026 }]
2027 );
2028
2029 let withdrawal = 1_000;
2030 user.force_transfer_to_contract(
2031 child.contract_id(),
2032 withdrawal,
2033 collateral_asset,
2034 TxPolicies::default(),
2035 )
2036 .await
2037 .unwrap();
2038 let balance_before = user.get_asset_balance(&collateral_asset).await.unwrap();
2039 let withdraw_response = account
2040 .methods()
2041 .withdraw(collateral_asset, withdrawal)
2042 .with_contract_ids(&[deployment.oracle_id, deployment.pool_id])
2043 .with_variable_output_policy(VariableOutputPolicy::Exactly(1))
2044 .call()
2045 .await
2046 .unwrap();
2047 let withdraw_events = withdraw_response
2048 .decode_logs_with_type::<MarginTradeAccountWithdrawn>()
2049 .unwrap();
2050 assert_recent_timestamp(withdraw_events[0].timestamp.unix);
2051 assert_eq!(
2052 withdraw_events,
2053 vec![MarginTradeAccountWithdrawn {
2054 account: child.contract_id(),
2055 parent,
2056 asset_id: collateral_asset,
2057 amount: withdrawal,
2058 timestamp: withdraw_events[0].timestamp.clone(),
2059 }]
2060 );
2061 assert_eq!(
2062 user.get_asset_balance(&collateral_asset).await.unwrap(),
2063 balance_before + withdrawal as u128
2064 );
2065 }
2066}
2067
2068#[cfg(test)]
2069#[path = "prop_deploy_tests.rs"]
2070mod integration_tests;