diff --git a/contracts/on-chain-claim/src/config.rs b/contracts/on-chain-claim/src/config.rs index 6319c402..c68cf88f 100644 --- a/contracts/on-chain-claim/src/config.rs +++ b/contracts/on-chain-claim/src/config.rs @@ -1,8 +1,11 @@ -use multiversx_sc::imports::*; - use crate::address_info::*; +use multiversx_sc::imports::*; pub const MAX_REPAIR_GAP: u64 = 5; +pub const FIRST_SEASON_ID: usize = 1; +pub const FIRST_SEASON_START_EPOCH: u64 = 1400; + +type Epoch = u64; #[multiversx_sc::module] pub trait ConfigModule { @@ -24,32 +27,84 @@ pub trait ConfigModule { #[view(getAddressInfo)] fn get_address_info(&self, address: &ManagedAddress) -> AddressInfo { + let current_season_id: usize = self.get_current_season(); + let address_info_by_season_mapper = self.address_info_by_season(address, current_season_id); + + if !address_info_by_season_mapper.is_empty() { + return address_info_by_season_mapper.get(); + } + let address_info_mapper = self.address_info(address); + if current_season_id == FIRST_SEASON_ID && !address_info_mapper.is_empty() { + return address_info_mapper.get(); + } + + AddressInfo::default() + } + + #[view(getAddressInfoBySeason)] + fn get_address_info_by_season( + &self, + address: &ManagedAddress, + season_id: usize, + ) -> AddressInfo { + let address_info_by_season_mapper = self.address_info_by_season(address, season_id); - if address_info_mapper.is_empty() { - return AddressInfo::default(); + if !address_info_by_season_mapper.is_empty() { + return address_info_by_season_mapper.get(); } - address_info_mapper.get() + let address_info_mapper = self.address_info(address); + if season_id == FIRST_SEASON_ID && !address_info_mapper.is_empty() { + return address_info_mapper.get(); + } + + AddressInfo::default() } #[view(canBeRepaired)] fn can_be_repaired(&self, address: &ManagedAddress) -> bool { - let address_info_mapper = self.address_info(address); - if address_info_mapper.is_empty() { + let address_info = self.get_address_info(address); + if address_info.total_epochs_claimed == 0 { return false; } - let address_info = address_info_mapper.get(); let current_epoch = self.blockchain().get_block_epoch(); let missed_epochs = self.get_missed_epochs(current_epoch, address_info.last_epoch_claimed); missed_epochs > 0 && missed_epochs <= MAX_REPAIR_GAP } + #[view(getCurrentSeason)] + fn get_current_season(&self) -> usize { + let current_epoch = self.blockchain().get_block_epoch(); + let seasons = self.seasons(); + + let last_season_starting_epoch = seasons.get(seasons.len()); + if last_season_starting_epoch <= current_epoch { + return seasons.len(); + } + + seasons.len() - 1 + } + #[storage_mapper("address_info")] fn address_info(&self, address: &ManagedAddress) -> SingleValueMapper; + #[storage_mapper("address_info_by_season")] + fn address_info_by_season( + &self, + address: &ManagedAddress, + season_id: usize, + ) -> SingleValueMapper; + + /** + * This parameter is referring to the start epoch of the season. + */ + #[view(getSeasons)] + #[storage_mapper("seasons")] + fn seasons(&self) -> VecMapper; + #[view(getRepairStreakPayment)] #[storage_mapper("repair_streak_payment")] fn repair_streak_payment(&self) -> SingleValueMapper; diff --git a/contracts/on-chain-claim/src/contract.rs b/contracts/on-chain-claim/src/contract.rs index 14b596a3..b63665d5 100644 --- a/contracts/on-chain-claim/src/contract.rs +++ b/contracts/on-chain-claim/src/contract.rs @@ -2,6 +2,8 @@ #![allow(unused_attributes)] pub use address_info::AddressInfo; +use config::{FIRST_SEASON_ID, FIRST_SEASON_START_EPOCH}; + use multiversx_sc::imports::*; pub mod address_info; @@ -21,10 +23,26 @@ pub trait OnChainClaimContract: let caller = self.blockchain().get_caller(); self.add_admin(caller); + + self.seasons().push(&FIRST_SEASON_START_EPOCH); } #[upgrade] - fn upgrade(&self) {} + fn upgrade(&self) { + if self.seasons().is_empty() { + self.seasons().push(&FIRST_SEASON_START_EPOCH); + } + } + + fn migrate_legacy_address_info(&self, address: &ManagedAddress) { + if self.address_info(address).is_empty() { + return; + } + + let address_info = self.address_info(address).take(); + self.address_info_by_season(address, FIRST_SEASON_ID) + .set(address_info); + } #[endpoint(claim)] fn claim(&self) { @@ -35,36 +53,22 @@ pub trait OnChainClaimContract: ); self.require_same_shard(&caller); + self.migrate_legacy_address_info(&caller); + let current_epoch = self.blockchain().get_block_epoch(); + let current_season_id = self.get_current_season(); - let address_info_mapper = self.address_info(&caller); - if address_info_mapper.is_empty() { + let address_info_by_season_mapper = self.address_info_by_season(&caller, current_season_id); + if address_info_by_season_mapper.is_empty() { let address_info = AddressInfo::new_with_epoch(current_epoch); - self.address_info(&caller).set(&address_info); + self.address_info_by_season(&caller, current_season_id) + .set(&address_info); self.new_claim_event(&caller, &address_info); return; } - address_info_mapper.update(|address_info| { - require!( - address_info.last_epoch_claimed < current_epoch, - "epoch already claimed" - ); - - if address_info.last_epoch_claimed + 1 == current_epoch { - address_info.current_streak += 1; - } else { - address_info.current_streak = 1; - } - - address_info.total_epochs_claimed += 1; - address_info.last_epoch_claimed = current_epoch; - - if address_info.best_streak < address_info.current_streak { - address_info.best_streak = address_info.current_streak; - } - - self.new_claim_event(&caller, address_info); + address_info_by_season_mapper.update(|address_info| { + self.increment_address_info(address_info, current_epoch); }); } @@ -78,6 +82,8 @@ pub trait OnChainClaimContract: ); self.require_same_shard(&caller); + self.migrate_legacy_address_info(&caller); + let payment = self.call_value().single_esdt(); let repair_streak_payment = self.repair_streak_payment().get(); require!( @@ -86,33 +92,17 @@ pub trait OnChainClaimContract: ); let current_epoch = self.blockchain().get_block_epoch(); - - let address_info_mapper = self.address_info(&caller); + let current_season_id = self.get_current_season(); + let address_info = self.get_address_info(&caller); require!( - !address_info_mapper.is_empty(), + address_info.total_epochs_claimed > 0, "can't repair streak for address" ); - address_info_mapper.update(|address_info| { - let missed_epochs = - self.get_missed_epochs(current_epoch, address_info.last_epoch_claimed); - - // Allow MAX_REPAIR_GAP + 1 in order to not have failed transaction when the user sends the claimAndRepair transaction - // in the last round of the allowed epoch. From UI, we allow MAX_REPAIR_GAP = 5 (using canBeRepaired view) - require!( - missed_epochs > 0 && missed_epochs <= MAX_REPAIR_GAP + 1, - "can't repair streak for current epoch" - ); - - address_info.current_streak += missed_epochs + 1; - address_info.total_epochs_claimed += missed_epochs + 1; - address_info.last_epoch_claimed = current_epoch; - if address_info.best_streak < address_info.current_streak { - address_info.best_streak = address_info.current_streak; - } - - self.new_claim_and_repair_event(&caller, address_info); + let address_info_by_season_mapper = self.address_info_by_season(&caller, current_season_id); + address_info_by_season_mapper.update(|address_info| { + self.repair_address_info_streak(&caller, address_info, current_epoch); }); self.send().esdt_local_burn( @@ -122,9 +112,36 @@ pub trait OnChainClaimContract: ); } + #[endpoint(addSeason)] + fn add_season(&self, epoch: u64) { + self.require_caller_is_admin(); + + let mut seasons = self.seasons(); + + if seasons.is_empty() { + seasons.push(&epoch); + return; + } + + let current_epoch = self.blockchain().get_block_epoch(); + let last_season_starting_epoch = seasons.get(seasons.len()); + + require!( + last_season_starting_epoch < current_epoch, + "last season must start before the current epoch" + ); + + require!( + current_epoch < epoch, + "new season must start after the last season" + ); + seasons.push(&epoch); + } + #[endpoint(updateState)] fn update_state( &self, + season_id: usize, address: &ManagedAddress, current_streak: u64, last_epoch_claimed: u64, @@ -133,6 +150,13 @@ pub trait OnChainClaimContract: ) { self.require_caller_is_admin(); self.require_same_shard(address); + let current_season_id = self.get_current_season(); + require!( + current_season_id == season_id, + "season must be the current season" + ); + + self.migrate_legacy_address_info(address); let address_info = AddressInfo::new( current_streak, @@ -140,7 +164,9 @@ pub trait OnChainClaimContract: total_epochs_claimed, best_streak, ); - self.address_info(address).set(&address_info); + + self.address_info_by_season(address, current_season_id) + .set(&address_info); self.new_update_state_event(address, &address_info); } @@ -176,4 +202,49 @@ pub trait OnChainClaimContract: ); self.repair_streak_payment().set(payment); } + + fn increment_address_info(&self, address_info: &mut AddressInfo, current_epoch: u64) { + require!( + address_info.last_epoch_claimed < current_epoch, + "epoch already claimed" + ); + + if address_info.last_epoch_claimed + 1 == current_epoch { + address_info.current_streak += 1; + } else { + address_info.current_streak = 1; + } + + address_info.total_epochs_claimed += 1; + address_info.last_epoch_claimed = current_epoch; + + if address_info.best_streak < address_info.current_streak { + address_info.best_streak = address_info.current_streak; + } + } + + fn repair_address_info_streak( + &self, + caller: &ManagedAddress, + address_info: &mut AddressInfo, + current_epoch: u64, + ) { + let missed_epochs = self.get_missed_epochs(current_epoch, address_info.last_epoch_claimed); + + // Allow MAX_REPAIR_GAP + 1 in order to not have failed transaction when the user sends the claimAndRepair transaction + // in the last round of the allowed epoch. From UI, we allow MAX_REPAIR_GAP = 5 (using canBeRepaired view) + require!( + missed_epochs > 0 && missed_epochs <= MAX_REPAIR_GAP + 1, + "can't repair streak for current epoch" + ); + + address_info.current_streak += missed_epochs + 1; + address_info.total_epochs_claimed += missed_epochs + 1; + address_info.last_epoch_claimed = current_epoch; + if address_info.best_streak < address_info.current_streak { + address_info.best_streak = address_info.current_streak; + } + + self.new_claim_and_repair_event(caller, address_info); + } } diff --git a/contracts/on-chain-claim/tests/tests.rs b/contracts/on-chain-claim/tests/tests.rs index 4060bc17..420e62fa 100644 --- a/contracts/on-chain-claim/tests/tests.rs +++ b/contracts/on-chain-claim/tests/tests.rs @@ -1,140 +1,197 @@ use config::ConfigModule; -use imports::{ - EsdtLocalRole, MxscPath, TestAddress, TestEsdtTransfer, TestSCAddress, TestTokenIdentifier, -}; -use multiversx_sc::types::{BigUint, EsdtTokenPayment, ManagedAddress}; +use multiversx_sc::types::{BigUint, EsdtTokenPayment, ManagedAddress, TokenIdentifier}; use multiversx_sc_modules::only_admin::OnlyAdminModule; -use multiversx_sc_scenario::*; +use multiversx_sc_scenario::{scenario_model::*, *}; use on_chain_claim::*; -const ON_CHAIN_CLAIM_PATH: MxscPath = MxscPath::new("mxsc:output/on-chain-claim.mxsc.json"); -const XREPAIR_TOKEN_2: TestTokenIdentifier = TestTokenIdentifier::new("XREPAIRRR-abcdef"); -const XREPAIR_TOKEN: TestTokenIdentifier = TestTokenIdentifier::new("XREPAIR-abcdef"); +const ON_CHAIN_CLAIM_PATH_EXPR: &str = "mxsc:output/on-chain-claim.mxsc.json"; +const TOKEN_IDENTIFIER: &str = "XREPAIR-abcdef"; +const OTHER_TOKEN_IDENTIFIER_EXPR: &str = "str:XREPAIRRR-abcdef"; +const TOKEN_IDENTIFIER_EXPR: &str = "str:XREPAIR-abcdef"; const TOKEN_NONCE: u64 = 1; -const USER: TestAddress = TestAddress::new("user"); -const OWNER: TestAddress = TestAddress::new("owner"); -const SC_ADDR: TestSCAddress = TestSCAddress::new("on-chain-claim"); +const USER1_ADDR: &str = "address:user1"; +const OWNER_ADDR: &str = "address:owner"; +const SC_ADDR: &str = "sc:on-chain-claim"; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); - blockchain.set_current_dir_from_workspace("contracts/on-chain-claim"); - blockchain.register_contract(ON_CHAIN_CLAIM_PATH, on_chain_claim::ContractBuilder); - + blockchain.register_contract( + "mxsc:output/on-chain-claim.mxsc.json", + on_chain_claim::ContractBuilder, + ); blockchain } -fn setup_with_one_token(world: &mut ScenarioWorld) { - let roles: Vec = vec![EsdtLocalRole::NftBurn.name().to_string()]; - - world.account(OWNER).nonce(1); - world - .account(USER) - .nonce(1) - .esdt_nft_balance(XREPAIR_TOKEN, TOKEN_NONCE, 1, ()); - world - .account(SC_ADDR) - .nonce(1) - .code(ON_CHAIN_CLAIM_PATH) - .owner(OWNER) - .esdt_roles(XREPAIR_TOKEN, roles); - - world.current_block().block_epoch(20); - world.new_address(OWNER, TOKEN_NONCE, SC_ADDR); -} - -fn setup_with_two_token(world: &mut ScenarioWorld) { - let roles: Vec = vec![EsdtLocalRole::NftBurn.name().to_string()]; - - world.account(OWNER).nonce(1); - world - .account(USER) - .nonce(1) - .esdt_nft_balance(XREPAIR_TOKEN, TOKEN_NONCE, 100, ()) - .esdt_nft_balance(XREPAIR_TOKEN_2, TOKEN_NONCE, 1, ()); - world - .account(SC_ADDR) - .nonce(1) - .code(ON_CHAIN_CLAIM_PATH) - .owner(OWNER) - .esdt_roles(XREPAIR_TOKEN, roles); - - world.current_block().block_epoch(20); - world.new_address(OWNER, TOKEN_NONCE, SC_ADDR); -} - #[test] fn check_token_identifier() { let mut world = world(); - setup_with_one_token(&mut world); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); + + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let payment = EsdtTokenPayment::new( - XREPAIR_TOKEN.to_token_identifier(), - 1u64, - BigUint::from(1u64), - ); - sc.repair_streak_payment().set(payment); - }); + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let payment = EsdtTokenPayment::new( + TokenIdentifier::from(TOKEN_IDENTIFIER), + 1u64, + BigUint::from(1u64), + ); + sc.repair_streak_payment().set(payment); + }, + ); } #[test] fn check_before_claim() { let mut world = world(); - setup_with_one_token(&mut world); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info_mapper = sc.address_info(managed_address); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; - assert!(address_info_mapper.is_empty()); - }) + world + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + + assert!(address_info.total_epochs_claimed == 0); + }); } #[test] fn check_update_state() { let mut world = world(); - setup_with_one_token(&mut world); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info_mapper = sc.address_info(managed_address); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - assert!(address_info_mapper.is_empty()); - }); - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = ManagedAddress::from(OWNER.to_address()); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; - sc.add_admin(managed_address); - }); - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - sc.update_state(managed_address, 5u64, 21u64, 7u64, 5u64); - }); world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + + assert!(address_info.total_epochs_claimed == 0); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + sc.update_state(1usize, managed_address, 5u64, 21u64, 7u64, 5u64); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 5); assert_eq!(address_info.last_epoch_claimed, 21); @@ -145,62 +202,89 @@ fn check_update_state() { #[test] fn check_after_claim() { let mut world = world(); - setup_with_one_token(&mut world); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim(); - }); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 1); assert_eq!(address_info.last_epoch_claimed, 20); - }); - - world.current_block().block_epoch(21); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim(); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 2); assert_eq!(address_info.total_epochs_claimed, 2); assert_eq!(address_info.last_epoch_claimed, 21); - }); - - world.current_block().block_epoch(25); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim(); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(25)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 3); @@ -211,304 +295,354 @@ fn check_after_claim() { #[test] fn check_claim_and_repair() { let mut world = world(); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - setup_with_two_token(&mut world); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let payment = EsdtTokenPayment::new( - XREPAIR_TOKEN.to_token_identifier(), - 1u64, - BigUint::from(1u64), - ); - sc.repair_streak_payment().set(payment); - }); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info_mapper = sc.address_info(managed_address); - - assert!(address_info_mapper.is_empty()); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .with_result(ExpectMessage("can't repair streak for address")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info_mapper = sc.address_info(managed_address); - - assert!(address_info_mapper.is_empty()); - }); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim(); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new() + .nonce(1) + .esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "100", + Option::Some(()), + ) + .esdt_nft_balance( + OTHER_TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let payment = EsdtTokenPayment::new( + TokenIdentifier::from(TOKEN_IDENTIFIER), + 1u64, + BigUint::from(1u64), + ); + sc.repair_streak_payment().set(payment); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + + assert!(address_info.total_epochs_claimed == 0); + }) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("can't repair streak for address"); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + + assert!(address_info.total_epochs_claimed == 0); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 1); assert_eq!(address_info.last_epoch_claimed, 20); - }); - - world.current_block().block_epoch(21); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .with_result(ExpectMessage("can't repair streak for current epoch")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, TOKEN_NONCE, "1") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("can't repair streak for current epoch"); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 1); assert_eq!(address_info.last_epoch_claimed, 20); - }); - - world.current_block().block_epoch(22); - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN_2, TOKEN_NONCE, 1)) - .with_result(ExpectMessage("Bad payment token/amount")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 2)) - .with_result(ExpectMessage("Bad payment token/amount")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 2)) - .with_result(ExpectMessage("Bad payment token/amount")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); - + }) + .set_state_step(SetStateStep::new().block_epoch(22)) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(OTHER_TOKEN_IDENTIFIER_EXPR, 1, "1") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("Bad payment token/amount"); + }, + ) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, TOKEN_NONCE, "2") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("Bad payment token/amount"); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1"), + |sc| { + sc.claim_and_repair(); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 3); assert_eq!(address_info.total_epochs_claimed, 3); assert_eq!(address_info.last_epoch_claimed, 22); assert_eq!(address_info.best_streak, 3); - }); - - world.current_block().block_epoch(28); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); - + }) + .set_state_step(SetStateStep::new().block_epoch(28)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1"), + |sc| { + sc.claim_and_repair(); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 9); assert_eq!(address_info.total_epochs_claimed, 9); assert_eq!(address_info.last_epoch_claimed, 28); assert_eq!(address_info.best_streak, 9); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .with_result(ExpectMessage("can't repair streak for current epoch")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); + }) + .set_state_step(SetStateStep::new().block_epoch(36)) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, TOKEN_NONCE, "1") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("can't repair streak for current epoch"); + }, + ); } #[test] fn test_best_streak() { let mut world = world(); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - setup_with_one_token(&mut world); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let payment = EsdtTokenPayment::new( - XREPAIR_TOKEN.to_token_identifier(), - 1u64, - BigUint::from(1u64), - ); - sc.repair_streak_payment().set(payment); - }); - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = ManagedAddress::from(OWNER.to_address()); - - sc.add_admin(managed_address); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let payment = EsdtTokenPayment::new( + TokenIdentifier::from(TOKEN_IDENTIFIER), + 1u64, + BigUint::from(1u64), + ); + sc.repair_streak_payment().set(payment); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { let repair_streak_payment = sc.repair_streak_payment().get(); - assert_eq!( - repair_streak_payment.token_identifier, - XREPAIR_TOKEN.to_token_identifier() - ); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + let identifier = TokenIdentifier::from(TOKEN_IDENTIFIER); + assert_eq!(repair_streak_payment.token_identifier, identifier); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 1); assert_eq!(address_info.last_epoch_claimed, 20); assert_eq!(address_info.best_streak, 1); - }); - - world.current_block().block_epoch(21); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 2); assert_eq!(address_info.total_epochs_claimed, 2); assert_eq!(address_info.last_epoch_claimed, 21); assert_eq!(address_info.best_streak, 2); - }); - - world.current_block().block_epoch(25); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(25)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 3); assert_eq!(address_info.last_epoch_claimed, 25); assert_eq!(address_info.best_streak, 2); - }); - - world.current_block().block_epoch(26); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(26)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 2); assert_eq!(address_info.total_epochs_claimed, 4); assert_eq!(address_info.last_epoch_claimed, 26); assert_eq!(address_info.best_streak, 2); - }); - - world.current_block().block_epoch(27); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .set_state_step(SetStateStep::new().block_epoch(27)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 3); assert_eq!(address_info.total_epochs_claimed, 5); assert_eq!(address_info.last_epoch_claimed, 27); @@ -519,183 +653,412 @@ fn test_best_streak() { #[test] fn on_chain_claim_whitebox() { let mut world = world(); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); - setup_with_one_token(&mut world); - - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let payment = EsdtTokenPayment::new( - XREPAIR_TOKEN.to_token_identifier(), - 1u64, - BigUint::from(1u64), - ); - sc.repair_streak_payment().set(payment); - }); - - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = ManagedAddress::from(OWNER.to_address()); - - sc.add_admin(managed_address); - }); + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let payment = EsdtTokenPayment::new( + TokenIdentifier::from(TOKEN_IDENTIFIER), + 1u64, + BigUint::from(1u64), + ); + sc.repair_streak_payment().set(payment); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { let repair_streak_payment = sc.repair_streak_payment().get(); - assert_eq!( - repair_streak_payment.token_identifier, - XREPAIR_TOKEN.to_token_identifier() - ); - }); - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + let identifier = TokenIdentifier::from(TOKEN_IDENTIFIER); + assert_eq!(repair_streak_payment.token_identifier, identifier); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 1); assert_eq!(address_info.last_epoch_claimed, 20); - - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(!can_be_repaired); - }); - - world.current_block().block_epoch(21); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(!can_be_repaired); - }); - - world.current_block().block_epoch(22); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .set_state_step(SetStateStep::new().block_epoch(22)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(can_be_repaired); - }); - - world.current_block().block_epoch(26); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .set_state_step(SetStateStep::new().block_epoch(26)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(can_be_repaired); - }); - - world.current_block().block_epoch(27); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .set_state_step(SetStateStep::new().block_epoch(27)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(!can_be_repaired); - }); - - world.current_block().block_epoch(28); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .set_state_step(SetStateStep::new().block_epoch(28)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(!can_be_repaired); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .with_result(ExpectMessage("can't repair streak for current epoch")) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - - world - .tx() - .from(USER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| sc.claim()); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .whitebox_call_check( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1") + .no_expect(), + |sc| { + sc.claim_and_repair(); + }, + |r| { + r.assert_user_error("can't repair streak for current epoch"); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 1); assert_eq!(address_info.total_epochs_claimed, 2); assert_eq!(address_info.last_epoch_claimed, 28); assert_eq!(address_info.best_streak, 1); - }); - world - .tx() - .from(OWNER) - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - sc.update_state(managed_address, 5u64, 21u64, 7u64, 5u64); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + sc.update_state(1usize, managed_address, 5u64, 21u64, 7u64, 5u64); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); let can_be_repaired = sc.can_be_repaired(managed_address); assert!(!can_be_repaired); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 5); assert_eq!(address_info.total_epochs_claimed, 7); assert_eq!(address_info.last_epoch_claimed, 21); assert_eq!(address_info.best_streak, 5); - }); - world - .tx() - .from(USER) - .to(SC_ADDR) - .esdt(TestEsdtTransfer(XREPAIR_TOKEN, TOKEN_NONCE, 1)) - .whitebox(on_chain_claim::contract_obj, |sc| { - sc.claim_and_repair(); - }); - world - .query() - .to(SC_ADDR) - .whitebox(on_chain_claim::contract_obj, |sc| { - let managed_address = &ManagedAddress::from(USER.to_address()); - let address_info = sc.address_info(managed_address).get(); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1"), + |sc| { + sc.claim_and_repair(); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); assert_eq!(address_info.current_streak, 12); assert_eq!(address_info.total_epochs_claimed, 14); assert_eq!(address_info.last_epoch_claimed, 28); assert_eq!(address_info.best_streak, 12); }); } + +#[test] +fn on_chain_claim_seasons_whitebox() { + let mut world = world(); + let on_chain_claim_whitebox = WhiteboxContract::new(SC_ADDR, on_chain_claim::contract_obj); + let on_chain_claim_code = world.code_expression(ON_CHAIN_CLAIM_PATH_EXPR); + + let roles: Vec = vec!["ESDTRoleNFTBurn".to_string()]; + + world + .set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDR, Account::new().nonce(1)) + .put_account( + USER1_ADDR, + Account::new().nonce(1).esdt_nft_balance( + TOKEN_IDENTIFIER_EXPR, + TOKEN_NONCE, + "1", + Option::Some(()), + ), + ) + .put_account( + SC_ADDR, + Account::new() + .nonce(1) + .code(&on_chain_claim_code) + .owner(OWNER_ADDR) + .esdt_roles(TOKEN_IDENTIFIER_EXPR, roles), + ) + .block_epoch(20) + .new_address(OWNER_ADDR, TOKEN_NONCE, SC_ADDR), + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let payment = EsdtTokenPayment::new( + TokenIdentifier::from(TOKEN_IDENTIFIER), + 1u64, + BigUint::from(1u64), + ); + sc.repair_streak_payment().set(payment); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(SC_ADDR), + |sc| { + let address = AddressValue::from(OWNER_ADDR).to_address(); + let managed_address = ManagedAddress::from(address); + + sc.add_admin(managed_address); + }, + ) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(0u64), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + assert_eq!(sc.seasons().len(), 1); + assert_eq!(sc.seasons().get(1), 0); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let repair_streak_payment = sc.repair_streak_payment().get(); + let identifier = TokenIdentifier::from(TOKEN_IDENTIFIER); + assert_eq!(repair_streak_payment.token_identifier, identifier); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 20); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 1usize); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 20); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| sc.add_season(21), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let current_season_id = sc.get_current_season(); + assert_eq!(current_season_id, 1); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let current_season_id = sc.get_current_season(); + assert_eq!(current_season_id, 2); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let can_be_repaired = sc.can_be_repaired(managed_address); + assert!(!can_be_repaired); + }) + .set_state_step(SetStateStep::new().block_epoch(21)) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(USER1_ADDR), + |sc| sc.claim(), + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 1usize); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 20); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 2usize); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 21); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 21); + }) + .set_state_step(SetStateStep::new().block_epoch(23)) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let can_be_repaired = sc.can_be_repaired(managed_address); + assert!(can_be_repaired); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new() + .from(USER1_ADDR) + .to(SC_ADDR) + .esdt_transfer(TOKEN_IDENTIFIER_EXPR, 1, "1"), + |sc| { + sc.claim_and_repair(); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let current_season_id = sc.get_current_season(); + assert_eq!(current_season_id, 2); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 1usize); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 20); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 2usize); + assert_eq!(address_info.current_streak, 3); + assert_eq!(address_info.total_epochs_claimed, 3); + assert_eq!(address_info.last_epoch_claimed, 23); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + assert_eq!(address_info.current_streak, 3); + assert_eq!(address_info.total_epochs_claimed, 3); + assert_eq!(address_info.last_epoch_claimed, 23); + assert_eq!(address_info.best_streak, 3); + }) + .whitebox_call( + &on_chain_claim_whitebox, + ScCallStep::new().from(OWNER_ADDR), + |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + sc.update_state(2usize, managed_address, 2u64, 21u64, 2u64, 2u64); + }, + ) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 1usize); + assert_eq!(address_info.current_streak, 1); + assert_eq!(address_info.total_epochs_claimed, 1); + assert_eq!(address_info.last_epoch_claimed, 20); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info_by_season(managed_address, 2usize); + assert_eq!(address_info.current_streak, 2); + assert_eq!(address_info.total_epochs_claimed, 2); + assert_eq!(address_info.last_epoch_claimed, 21); + assert_eq!(address_info.best_streak, 2); + }) + .whitebox_query(&on_chain_claim_whitebox, |sc| { + let address = AddressValue::from(USER1_ADDR).to_address(); + let managed_address = &ManagedAddress::from(address); + let address_info = sc.get_address_info(managed_address); + assert_eq!(address_info.current_streak, 2); + assert_eq!(address_info.total_epochs_claimed, 2); + assert_eq!(address_info.last_epoch_claimed, 21); + assert_eq!(address_info.best_streak, 2); + }); +} diff --git a/contracts/on-chain-claim/wasm/src/lib.rs b/contracts/on-chain-claim/wasm/src/lib.rs index 49880da9..3b351ef7 100644 --- a/contracts/on-chain-claim/wasm/src/lib.rs +++ b/contracts/on-chain-claim/wasm/src/lib.rs @@ -6,9 +6,9 @@ // Init: 1 // Upgrade: 1 -// Endpoints: 11 +// Endpoints: 15 // Async Callback (empty): 1 -// Total number of exported functions: 14 +// Total number of exported functions: 18 #![no_std] @@ -22,10 +22,14 @@ multiversx_sc_wasm_adapter::endpoints! { upgrade => upgrade claim => claim claimAndRepair => claim_and_repair + addSeason => add_season updateState => update_state setRepairStreakPayment => set_repair_streak_payment getAddressInfo => get_address_info + getAddressInfoBySeason => get_address_info_by_season canBeRepaired => can_be_repaired + getCurrentSeason => get_current_season + getSeasons => seasons getRepairStreakPayment => repair_streak_payment isAdmin => is_admin addAdmin => add_admin