From 1c43c5390a528a74cd16ecba000164eb076c2c9e Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 21 Sep 2023 10:48:14 +0300 Subject: [PATCH 1/5] rewards-distributin blackbox full --- .../rewards_distribution_blackbox_test.rs | 389 ++++++++++++++++++ .../test_compute_brackets_legacy_test.rs | 57 --- .../test_raffle_and_claim_legacy_test.rs | 219 ---------- 3 files changed, 389 insertions(+), 276 deletions(-) create mode 100644 contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs delete mode 100644 contracts/examples/rewards-distribution/tests/test_compute_brackets_legacy_test.rs delete mode 100644 contracts/examples/rewards-distribution/tests/test_raffle_and_claim_legacy_test.rs diff --git a/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs b/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs new file mode 100644 index 0000000000..2be570f49a --- /dev/null +++ b/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs @@ -0,0 +1,389 @@ +mod mock_seed_nft_minter; +mod utils; + +use std::iter::zip; + +use multiversx_sc::{ + codec::multi_types::MultiValue2, + storage::mappers::SingleValue, + types::{ + Address, BigUint, EgldOrEsdtTokenIdentifier, ManagedVec, MultiValueEncoded, TokenIdentifier, + }, +}; +use multiversx_sc_scenario::{ + api::StaticApi, + scenario_model::{ + Account, AddressValue, CheckAccount, CheckStateStep, ScCallStep, ScDeployStep, ScQueryStep, + SetStateStep, TypedResponse, + }, + testing_framework::TxTokenTransfer, + ContractInfo, DebugApi, ScenarioWorld, WhiteboxContract, +}; + +use crate::mock_seed_nft_minter::ProxyTrait as _; +use rewards_distribution::{ + Bracket, ContractObj, ProxyTrait as _, RewardsDistribution, DIVISION_SAFETY_CONSTANT, +}; + +const NFT_TOKEN_ID: &[u8] = b"NFT-123456"; +const NFT_TOKEN_ID_EXPR: &str = "str:NFT-123456"; + +const ALICE_ADDRESS_EXPR: &str = "address:alice"; +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const REWARDS_DISTRIBUTION_ADDRESS_EXPR: &str = "sc:rewards-distribution"; +const REWARDS_DISTRIBUTION_PATH_EXPR: &str = "file:output/rewards-distribution.wasm"; +const SEED_NFT_MINTER_ADDRESS_EXPR: &str = "sc:seed-nft-minter"; +const SEED_NFT_MINTER_PATH_EXPR: &str = "file:../seed-nft-minter/output/seed-nft-minter.wasm"; + +type RewardsDistributionContract = ContractInfo>; +type SeedNFTMinterContract = ContractInfo>; + +fn world() -> ScenarioWorld { + let mut blockchain = ScenarioWorld::new(); + blockchain.set_current_dir_from_workspace("contracts/examples/rewards-distribution"); + + blockchain.register_contract( + REWARDS_DISTRIBUTION_PATH_EXPR, + rewards_distribution::ContractBuilder, + ); + blockchain.register_contract( + SEED_NFT_MINTER_PATH_EXPR, + mock_seed_nft_minter::ContractBuilder, + ); + blockchain +} + +struct RewardsDistributionTestState { + world: ScenarioWorld, + seed_nft_minter_address: Address, + seed_nft_minter_contract: SeedNFTMinterContract, + rewards_distribution_contract: RewardsDistributionContract, + rewards_distribution_whitebox: WhiteboxContract>, +} + +impl RewardsDistributionTestState { + fn new() -> Self { + let mut world = world(); + + world.set_state_step( + SetStateStep::new().put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)), + ); + + let seed_nft_minter_address = AddressValue::from(SEED_NFT_MINTER_ADDRESS_EXPR).to_address(); + + let seed_nft_minter_contract = SeedNFTMinterContract::new(SEED_NFT_MINTER_ADDRESS_EXPR); + let rewards_distribution_contract = + RewardsDistributionContract::new(REWARDS_DISTRIBUTION_ADDRESS_EXPR); + let rewards_distribution_whitebox = WhiteboxContract::new( + REWARDS_DISTRIBUTION_ADDRESS_EXPR, + rewards_distribution::contract_obj, + ); + + Self { + world, + seed_nft_minter_address, + seed_nft_minter_contract, + rewards_distribution_contract, + rewards_distribution_whitebox, + } + } + + fn deploy_seed_nft_minter_contract(&mut self) -> &mut Self { + let seed_nft_miinter_code = self.world.code_expression(SEED_NFT_MINTER_PATH_EXPR); + + self.world.sc_deploy( + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(seed_nft_miinter_code) + .call( + self.seed_nft_minter_contract + .init(TokenIdentifier::from_esdt_bytes(NFT_TOKEN_ID)), + ), + ); + + self.world.sc_call( + ScCallStep::new() + .from(OWNER_ADDRESS_EXPR) + .call(self.seed_nft_minter_contract.set_nft_count(10_000u64)), + ); + + self + } + + fn deploy_rewards_distribution_contract(&mut self) -> &mut Self { + let rewards_distribution_code = self.world.code_expression(REWARDS_DISTRIBUTION_PATH_EXPR); + + let brackets_vec = &[ + (10, 2_000), + (90, 6_000), + (400, 7_000), + (2_500, 10_000), + (25_000, 35_000), + (72_000, 40_000), + ]; + let mut brackets = ManagedVec::::new(); + for (index_percent, bracket_reward_percent) in brackets_vec.iter().cloned() { + brackets.push(Bracket { + index_percent, + bracket_reward_percent, + }); + } + self.world.sc_deploy( + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(rewards_distribution_code) + .call( + self.rewards_distribution_contract + .init(self.seed_nft_minter_address.clone(), brackets), + ), + ); + + self + } +} + +#[test] +fn test_compute_brackets() { + let mut state = RewardsDistributionTestState::new(); + + let rewards_distribution_code = state.world.code_expression(REWARDS_DISTRIBUTION_PATH_EXPR); + + state.world.set_state_step( + SetStateStep::new().put_account( + REWARDS_DISTRIBUTION_ADDRESS_EXPR, + Account::new() + .nonce(1) + .owner(OWNER_ADDRESS_EXPR) + .code(rewards_distribution_code.clone()) + .balance("0"), + ), + ); + + state.world.whitebox_call( + &state.rewards_distribution_whitebox, + ScCallStep::new().from(OWNER_ADDRESS_EXPR), + |sc| { + let brackets = utils::to_brackets(&[ + (10, 2_000), + (90, 6_000), + (400, 7_000), + (2_500, 10_000), + (25_000, 35_000), + (72_000, 40_000), + ]); + + let computed_brackets = sc.compute_brackets(brackets, 10_000); + + let expected_values = vec![ + (1, 2_000 * DIVISION_SAFETY_CONSTANT), + (10, 6_000 * DIVISION_SAFETY_CONSTANT / (10 - 1)), + (50, 7_000 * DIVISION_SAFETY_CONSTANT / (50 - 10)), + (300, 10_000 * DIVISION_SAFETY_CONSTANT / (300 - 50)), + (2_800, 35_000 * DIVISION_SAFETY_CONSTANT / (2_800 - 300)), + (10_000, 40_000 * DIVISION_SAFETY_CONSTANT / (10_000 - 2_800)), + ]; + + assert_eq!(computed_brackets.len(), expected_values.len()); + for (computed, expected) in zip(computed_brackets.iter(), expected_values) { + let (expected_end_index, expected_reward_percent) = expected; + assert_eq!(computed.end_index, expected_end_index); + assert_eq!(computed.nft_reward_percent, expected_reward_percent); + } + }, + ); +} + +#[test] +fn test_raffle_and_claim() { + let mut state = RewardsDistributionTestState::new(); + + state.world.set_state_step( + SetStateStep::new() + .put_account( + ALICE_ADDRESS_EXPR, + Account::new() + .nonce(1) + .balance("2_070_000_000") + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 1, "1", Option::<&[u8]>::None) + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 2, "1", Option::<&[u8]>::None) + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 3, "1", Option::<&[u8]>::None) + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 4, "1", Option::<&[u8]>::None) + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 5, "1", Option::<&[u8]>::None) + .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 6, "1", Option::<&[u8]>::None), + ) + .new_address(OWNER_ADDRESS_EXPR, 1, SEED_NFT_MINTER_ADDRESS_EXPR) + .new_address(OWNER_ADDRESS_EXPR, 3, REWARDS_DISTRIBUTION_ADDRESS_EXPR), + ); + + state + .deploy_seed_nft_minter_contract() + .deploy_rewards_distribution_contract(); + + // deposit royalties + state.world.sc_call( + ScCallStep::new() + .from(ALICE_ADDRESS_EXPR) + .egld_value("2_070_000_000") + .call(state.rewards_distribution_contract.deposit_royalties()), + ); + + // run the raffle + // OperationCompletionStatus encoding / decoding ???? to use result + state.world.sc_call( + ScCallStep::new() + .from(ALICE_ADDRESS_EXPR) + .call(state.rewards_distribution_contract.raffle()), + ); + + let mut rewards: Vec> = Vec::new(); + // post-raffle reward amount frequency checksstate + for nonce in 1u64..=10_000u64 { + state.world.sc_call_use_result( + ScCallStep::new().from(ALICE_ADDRESS_EXPR).call( + state + .rewards_distribution_contract + .compute_claimable_amount( + 0u64, + &EgldOrEsdtTokenIdentifier::egld(), + 0u64, + nonce, + ), + ), + |r: TypedResponse>| rewards.push(r.result.unwrap()), + ); + } + + assert_eq!(rewards.len() as u64, 10_000u64); + + // check that the reward amounts match in frequency + let expected_reward_amounts = [ + (41_400_000, 1), + (13_799_999, 9), + (3_622_500, 40), + (828_000, 250), + (289_800, 2500), + (114_999, 7200), + ]; + + let total_expected_count: u64 = expected_reward_amounts.iter().map(|(_, count)| count).sum(); + assert_eq!(total_expected_count, 10_000u64); + + for (amount, expected_count) in expected_reward_amounts { + let expected_amount = amount as u64; + assert_eq!( + rewards + .iter() + .filter(|value| *value == &expected_amount) + .count(), + expected_count as usize + ); + } + + let nft_nonces: [u64; 6] = [1, 2, 3, 4, 5, 6]; + // claim the rewards + // TODO: change after multi_esdt_transfer implementation + let _nft_payments: Vec = nft_nonces + .iter() + .map(|nonce| TxTokenTransfer { + token_identifier: NFT_TOKEN_ID.to_vec(), + nonce: *nonce, + value: 1u64.into(), + }) + .collect(); + + let expected_rewards: [u64; 6] = [289_800, 114_999, 114_999, 289_800, 114_999, 114_999]; + + for (nonce, expected_reward) in std::iter::zip(nft_nonces, expected_rewards) { + state.world.sc_call_use_result( + ScCallStep::new().from(ALICE_ADDRESS_EXPR).call( + state + .rewards_distribution_contract + .compute_claimable_amount( + 0u64, + &EgldOrEsdtTokenIdentifier::egld(), + 0u64, + nonce, + ), + ), + |r: TypedResponse>| { + assert_eq!(r.result.unwrap().to_u64().unwrap(), expected_reward); + }, + ); + } + + // claim rewards + let mut reward_tokens: MultiValueEncoded< + StaticApi, + MultiValue2, u64>, + > = MultiValueEncoded::new(); + reward_tokens.push((EgldOrEsdtTokenIdentifier::egld(), 0).into()); + state.world.sc_call( + ScCallStep::new() + .from(ALICE_ADDRESS_EXPR) + .esdt_transfer(NFT_TOKEN_ID_EXPR, 1, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 2, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 3, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 4, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 5, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 6, "1") + .call( + state + .rewards_distribution_contract + .claim_rewards(0u64, 0u64, reward_tokens), + ), + ); + + // check that the rewards were claimed + for nonce in nft_nonces.iter() { + state.world.sc_query( + ScQueryStep::new() + .call(state.rewards_distribution_contract.was_claimed( + 0u64, + &EgldOrEsdtTokenIdentifier::egld(), + 0u64, + nonce, + )) + .expect_value(SingleValue::from(true)), + ); + } + + // confirm the received amount matches the sum of the queried rewards + let alice_balance_after_claim: u64 = expected_rewards.iter().sum(); + let balance_expr = alice_balance_after_claim.to_string(); + + state + .world + .check_state_step(CheckStateStep::new().put_account( + ALICE_ADDRESS_EXPR, + CheckAccount::new().balance(balance_expr.as_str()), + )); + + // a second claim with the same nfts should succeed, but return no more rewards + let mut reward_tokens: MultiValueEncoded< + StaticApi, + MultiValue2, u64>, + > = MultiValueEncoded::new(); + reward_tokens.push((EgldOrEsdtTokenIdentifier::egld(), 0).into()); + state.world.sc_call( + ScCallStep::new() + .from(ALICE_ADDRESS_EXPR) + .esdt_transfer(NFT_TOKEN_ID_EXPR, 1, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 2, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 3, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 4, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 5, "1") + .esdt_transfer(NFT_TOKEN_ID_EXPR, 6, "1") + .call( + state + .rewards_distribution_contract + .claim_rewards(0u64, 0u64, reward_tokens), + ), + ); + + state + .world + .check_state_step(CheckStateStep::new().put_account( + ALICE_ADDRESS_EXPR, + CheckAccount::new().balance(balance_expr.as_str()), + )); +} diff --git a/contracts/examples/rewards-distribution/tests/test_compute_brackets_legacy_test.rs b/contracts/examples/rewards-distribution/tests/test_compute_brackets_legacy_test.rs deleted file mode 100644 index d10c2e06da..0000000000 --- a/contracts/examples/rewards-distribution/tests/test_compute_brackets_legacy_test.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![allow(deprecated)] // TODO: migrate tests - -use core::iter::zip; - -use multiversx_sc_scenario::{rust_biguint, testing_framework::BlockchainStateWrapper, DebugApi}; -use rewards_distribution::{RewardsDistribution, DIVISION_SAFETY_CONSTANT}; - -mod utils; - -multiversx_sc::imports!(); - -#[test] -fn test_compute_brackets() { - DebugApi::dummy(); - - let mut wrapper = BlockchainStateWrapper::new(); - - let owner = wrapper.create_user_account(&rust_biguint!(0u64)); - - let rewards_distribution_sc = wrapper.create_sc_account( - &rust_biguint!(0u64), - Some(&owner), - rewards_distribution::contract_obj, - "rewards-distribution.wasm", - ); - - wrapper - .execute_tx(&owner, &rewards_distribution_sc, &rust_biguint!(0), |sc| { - let brackets = utils::to_brackets(&[ - (10, 2_000), - (90, 6_000), - (400, 7_000), - (2_500, 10_000), - (25_000, 35_000), - (72_000, 40_000), - ]); - - let computed_brackets = sc.compute_brackets(brackets, 10_000); - - let expected_values = vec![ - (1, 2_000 * DIVISION_SAFETY_CONSTANT), - (10, 6_000 * DIVISION_SAFETY_CONSTANT / (10 - 1)), - (50, 7_000 * DIVISION_SAFETY_CONSTANT / (50 - 10)), - (300, 10_000 * DIVISION_SAFETY_CONSTANT / (300 - 50)), - (2_800, 35_000 * DIVISION_SAFETY_CONSTANT / (2_800 - 300)), - (10_000, 40_000 * DIVISION_SAFETY_CONSTANT / (10_000 - 2_800)), - ]; - - assert_eq!(computed_brackets.len(), expected_values.len()); - for (computed, expected) in zip(computed_brackets.iter(), expected_values) { - let (expected_end_index, expected_reward_percent) = expected; - assert_eq!(computed.end_index, expected_end_index); - assert_eq!(computed.nft_reward_percent, expected_reward_percent); - } - }) - .assert_ok(); -} diff --git a/contracts/examples/rewards-distribution/tests/test_raffle_and_claim_legacy_test.rs b/contracts/examples/rewards-distribution/tests/test_raffle_and_claim_legacy_test.rs deleted file mode 100644 index b60de2941e..0000000000 --- a/contracts/examples/rewards-distribution/tests/test_raffle_and_claim_legacy_test.rs +++ /dev/null @@ -1,219 +0,0 @@ -#![allow(deprecated)] // TODO: migrate tests - -use multiversx_sc::{ - codec::multi_types::MultiValue2, - types::{BigUint, EgldOrEsdtTokenIdentifier, MultiValueEncoded, OperationCompletionStatus}, -}; -use multiversx_sc_scenario::{ - managed_token_id, rust_biguint, - testing_framework::{BlockchainStateWrapper, TxTokenTransfer}, - DebugApi, -}; -use rewards_distribution::RewardsDistribution as _; -mod mock_seed_nft_minter; -use mock_seed_nft_minter::MockSeedNftMinter as _; - -mod utils; - -#[test] -fn test_raffle_and_claim() { - DebugApi::dummy(); - - let mut wrapper = BlockchainStateWrapper::new(); - - let full_reward_amount = rust_biguint!(2_070_000_000u64); - let nft_count = 10_000u64; - - let owner = wrapper.create_user_account(&rust_biguint!(0)); - let alice = wrapper.create_user_account(&full_reward_amount); - - let nft_token_id = b"NFT-123456"; - - let nft_balance = rust_biguint!(1); - let nft_attributes: [u8; 0] = []; - - let nft_nonces = [1, 2, 3, 4, 5, 6]; - for nft_nonce in nft_nonces { - wrapper.set_nft_balance( - &alice, - b"NFT-123456", - nft_nonce, - &nft_balance, - &nft_attributes, - ); - } - - let seed_nft_minter_mock_sc = wrapper.create_sc_account( - &rust_biguint!(0), - Some(&owner), - mock_seed_nft_minter::contract_obj, - "seed nft minter mock", - ); - - // setup the mock contract - wrapper - .execute_tx(&alice, &seed_nft_minter_mock_sc, &rust_biguint!(0), |sc| { - sc.init(managed_token_id!(nft_token_id)); - - sc.set_nft_count(nft_count); - }) - .assert_ok(); - - let rewards_distribution_sc = wrapper.create_sc_account( - &rust_biguint!(0), - Some(&owner), - rewards_distribution::contract_obj, - "rewards distribution", - ); - - // setup the rewards distribution contract - wrapper - .execute_tx(&alice, &rewards_distribution_sc, &rust_biguint!(0), |sc| { - let brackets = utils::to_brackets(&[ - (10, 2_000), - (90, 6_000), - (400, 7_000), - (2_500, 10_000), - (25_000, 35_000), - (72_000, 40_000), - ]); - sc.init(seed_nft_minter_mock_sc.address_ref().into(), brackets); - }) - .assert_ok(); - - // deposit the royalties - wrapper - .execute_tx( - &alice, - &rewards_distribution_sc, - &full_reward_amount, - |sc| { - sc.deposit_royalties(); - }, - ) - .assert_ok(); - - // run the raffle - wrapper - .execute_tx(&alice, &rewards_distribution_sc, &rust_biguint!(0), |sc| { - let completion_status = sc.raffle(); - - assert_eq!(completion_status, OperationCompletionStatus::Completed); - }) - .assert_ok(); - - // post-raffle reward amount frequency checks - - wrapper - .execute_tx(&alice, &rewards_distribution_sc, &rust_biguint!(0), |sc| { - // collect the claimable amounts - let raffle_id = 0; - let mut rewards: Vec> = Vec::new(); - - for nonce in 1u64..=nft_count { - let amount = sc.compute_claimable_amount( - raffle_id, - &EgldOrEsdtTokenIdentifier::egld(), - 0, - nonce, - ); - rewards.push(amount); - } - - assert_eq!(rewards.len() as u64, nft_count); - - // check that the reward amounts match in frequency - let expected_reward_amounts = [ - (41_400_000, 1), - (13_799_999, 9), - (3_622_500, 40), - (828_000, 250), - (289_800, 2500), - (114_999, 7200), - ]; - - let total_expected_count: u64 = - expected_reward_amounts.iter().map(|(_, count)| count).sum(); - assert_eq!(total_expected_count, nft_count); - - for (amount, expected_count) in expected_reward_amounts { - let expected_amount = amount as u64; - assert_eq!( - rewards - .iter() - .filter(|value| *value == &expected_amount) - .count(), - expected_count as usize - ); - } - }) - .assert_ok(); - - // claim the rewards - - let nft_payments: Vec = nft_nonces - .iter() - .map(|nonce| TxTokenTransfer { - token_identifier: nft_token_id.to_vec(), - nonce: *nonce, - value: rust_biguint!(1), - }) - .collect(); - - let expected_rewards = [114_999, 114_999, 114_999, 828_000, 114_999, 114_999]; - wrapper - .execute_esdt_multi_transfer(&alice, &rewards_distribution_sc, &nft_payments, |sc| { - // get and check the claimable reward amounts for each NFT (sample the few first values) - let raffle_id = 0; - assert_eq!(nft_nonces.len(), expected_rewards.len()); - for (nonce, expected_reward) in std::iter::zip(nft_nonces, expected_rewards) { - let rewards = sc.compute_claimable_amount( - raffle_id, - &EgldOrEsdtTokenIdentifier::egld(), - 0, - nonce, - ); - assert_eq!(rewards, expected_reward); - } - - // claim the rewards - let reward_id_range_start = 0; - let reward_id_range_end = 0; - let mut reward_tokens: MultiValueEncoded< - DebugApi, - MultiValue2, u64>, - > = MultiValueEncoded::new(); - reward_tokens.push((EgldOrEsdtTokenIdentifier::egld(), 0).into()); - sc.claim_rewards(reward_id_range_start, reward_id_range_end, reward_tokens); - - // check that the flags which mark claimed rewards were set - for nonce in nft_nonces { - let was_claimed = sc - .was_claimed(raffle_id, &EgldOrEsdtTokenIdentifier::egld(), 0, nonce) - .get(); - assert!(was_claimed); - } - }) - .assert_ok(); - - // confirm the received amount matches the sum of the queried rewards - let alice_balance_after_claim: u64 = expected_rewards.iter().sum(); - wrapper.check_egld_balance(&alice, &rust_biguint!(alice_balance_after_claim)); - - // a second claim with the same nfts should succeed, but return no more rewards - wrapper - .execute_esdt_multi_transfer(&alice, &rewards_distribution_sc, &nft_payments, |sc| { - let reward_id_range_start = 0; - let reward_id_range_end = 0; - let mut reward_tokens: MultiValueEncoded< - DebugApi, - MultiValue2, u64>, - > = MultiValueEncoded::new(); - reward_tokens.push((EgldOrEsdtTokenIdentifier::egld(), 0).into()); - sc.claim_rewards(reward_id_range_start, reward_id_range_end, reward_tokens); - }) - .assert_ok(); - - // check that a second claim does not modify the balance - wrapper.check_egld_balance(&alice, &rust_biguint!(alice_balance_after_claim)); -} From 82e0cdccc31e97c2b9d729a28547b595022cb9e4 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 21 Sep 2023 15:48:44 +0300 Subject: [PATCH 2/5] operation completion status enum top encode/top decode + unit test --- .../types/io/operation_completion_status.rs | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/framework/base/src/types/io/operation_completion_status.rs b/framework/base/src/types/io/operation_completion_status.rs index 9971094725..b5a154dcce 100644 --- a/framework/base/src/types/io/operation_completion_status.rs +++ b/framework/base/src/types/io/operation_completion_status.rs @@ -1,10 +1,14 @@ +use multiversx_sc_codec::{ + DecodeError, DecodeErrorHandler, TopDecode, TopDecodeInput, TopEncode, TopEncodeOutput, +}; + use crate::{ abi::{ ExplicitEnumVariantDescription, TypeAbi, TypeContents, TypeDescription, TypeDescriptionContainer, TypeName, }, api::ManagedTypeApi, - codec::{CodecFrom, EncodeErrorHandler, TopEncodeMulti, TopEncodeMultiOutput}, + codec::{CodecFrom, EncodeErrorHandler}, types::ManagedBuffer, }; @@ -37,13 +41,33 @@ impl OperationCompletionStatus { } } -impl TopEncodeMulti for OperationCompletionStatus { - fn multi_encode_or_handle_err(&self, output: &mut O, h: H) -> Result<(), H::HandledErr> +impl TopEncode for OperationCompletionStatus { + fn top_encode_or_handle_err(&self, output: O, _h: H) -> Result<(), H::HandledErr> where - O: TopEncodeMultiOutput, + O: TopEncodeOutput, H: EncodeErrorHandler, { - output.push_single_value(&self.output_bytes(), h) + output.set_slice_u8(self.output_bytes()); + Ok(()) + } +} + +impl TopDecode for OperationCompletionStatus { + fn top_decode_or_handle_err(input: I, h: H) -> Result + where + I: TopDecodeInput, + H: DecodeErrorHandler, + { + let mut buffer = [0u8; 16]; + input.into_max_size_buffer(&mut buffer, h)?; + + if buffer.starts_with(COMPLETED_STR.as_bytes()) { + Ok(OperationCompletionStatus::Completed) + } else if buffer.starts_with(INTERRUPTED_STR.as_bytes()) { + Ok(OperationCompletionStatus::InterruptedBeforeOutOfGas) + } else { + Err(h.handle_error(DecodeError::INVALID_VALUE)) + } } } @@ -83,6 +107,8 @@ impl TypeAbi for OperationCompletionStatus { #[cfg(test)] mod tests { + use multiversx_sc_codec::test_util::check_top_encode_decode; + use super::*; #[test] @@ -92,4 +118,16 @@ mod tests { assert!(!OperationCompletionStatus::InterruptedBeforeOutOfGas.is_completed()); assert!(OperationCompletionStatus::InterruptedBeforeOutOfGas.is_interrupted()); } + + #[test] + fn test_codec_decode_operation_completion() { + check_top_encode_decode( + OperationCompletionStatus::Completed, + COMPLETED_STR.as_bytes(), + ); + check_top_encode_decode( + OperationCompletionStatus::InterruptedBeforeOutOfGas, + INTERRUPTED_STR.as_bytes(), + ); + } } From bdf353f7fa804b9bb475356c4bf97ed46ac254b8 Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 21 Sep 2023 15:49:21 +0300 Subject: [PATCH 3/5] extend sc call step with tx_hash and multi_esdt_tokens --- .../src/scenario/model/step/sc_call_step.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/framework/scenario/src/scenario/model/step/sc_call_step.rs b/framework/scenario/src/scenario/model/step/sc_call_step.rs index 0747f7d491..b221da3167 100644 --- a/framework/scenario/src/scenario/model/step/sc_call_step.rs +++ b/framework/scenario/src/scenario/model/step/sc_call_step.rs @@ -90,11 +90,30 @@ impl ScCallStep { self } + pub fn multi_esdt_transfer(mut self, tokens: T) -> Self + where T: IntoIterator { + if self.tx.egld_value.value > 0u32.into() { + panic!("Cannot transfer both EGLD and ESDT"); + } + + self.tx.esdt_value.extend(tokens); + + self + } + pub fn function(mut self, expr: &str) -> Self { self.tx.function = expr.to_string(); self } + pub fn tx_hash(mut self, tx_hash_expr: T) -> Self + where + H256: From, + { + self.explicit_tx_hash = Some(tx_hash_expr.into()); + self + } + pub fn argument(mut self, expr: A) -> Self where BytesValue: From, From 397360ea5142a62eac2f5e60d617b70192c221fb Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 21 Sep 2023 15:49:57 +0300 Subject: [PATCH 4/5] refactor tests --- .../rewards_distribution_blackbox_test.rs | 68 +++++++------------ 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs b/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs index 2be570f49a..4534b1fc51 100644 --- a/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs +++ b/contracts/examples/rewards-distribution/tests/rewards_distribution_blackbox_test.rs @@ -7,16 +7,16 @@ use multiversx_sc::{ codec::multi_types::MultiValue2, storage::mappers::SingleValue, types::{ - Address, BigUint, EgldOrEsdtTokenIdentifier, ManagedVec, MultiValueEncoded, TokenIdentifier, + Address, BigUint, EgldOrEsdtTokenIdentifier, ManagedVec, MultiValueEncoded, + OperationCompletionStatus, TokenIdentifier, }, }; use multiversx_sc_scenario::{ api::StaticApi, scenario_model::{ Account, AddressValue, CheckAccount, CheckStateStep, ScCallStep, ScDeployStep, ScQueryStep, - SetStateStep, TypedResponse, + SetStateStep, TxESDT, TypedResponse, }, - testing_framework::TxTokenTransfer, ContractInfo, DebugApi, ScenarioWorld, WhiteboxContract, }; @@ -197,20 +197,25 @@ fn test_compute_brackets() { fn test_raffle_and_claim() { let mut state = RewardsDistributionTestState::new(); + let nft_nonces: [u64; 6] = [1, 2, 3, 4, 5, 6]; + let nft_payments: Vec = nft_nonces + .iter() + .map(|nonce| TxESDT { + esdt_token_identifier: NFT_TOKEN_ID.into(), + nonce: (*nonce).into(), + esdt_value: 1u64.into(), + }) + .collect(); + + let mut alice_account = Account::new().nonce(1).balance("2_070_000_000"); + for nonce in nft_nonces.iter() { + alice_account = + alice_account.esdt_nft_balance(NFT_TOKEN_ID_EXPR, *nonce, "1", Option::<&[u8]>::None); + } + state.world.set_state_step( SetStateStep::new() - .put_account( - ALICE_ADDRESS_EXPR, - Account::new() - .nonce(1) - .balance("2_070_000_000") - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 1, "1", Option::<&[u8]>::None) - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 2, "1", Option::<&[u8]>::None) - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 3, "1", Option::<&[u8]>::None) - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 4, "1", Option::<&[u8]>::None) - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 5, "1", Option::<&[u8]>::None) - .esdt_nft_balance(NFT_TOKEN_ID_EXPR, 6, "1", Option::<&[u8]>::None), - ) + .put_account(ALICE_ADDRESS_EXPR, alice_account) .new_address(OWNER_ADDRESS_EXPR, 1, SEED_NFT_MINTER_ADDRESS_EXPR) .new_address(OWNER_ADDRESS_EXPR, 3, REWARDS_DISTRIBUTION_ADDRESS_EXPR), ); @@ -228,11 +233,12 @@ fn test_raffle_and_claim() { ); // run the raffle - // OperationCompletionStatus encoding / decoding ???? to use result state.world.sc_call( ScCallStep::new() .from(ALICE_ADDRESS_EXPR) - .call(state.rewards_distribution_contract.raffle()), + .tx_hash(&[0u8; 32]) // blockchain rng is deterministic, so we can use a fixed hash + .call(state.rewards_distribution_contract.raffle()) + .expect_value(OperationCompletionStatus::Completed), ); let mut rewards: Vec> = Vec::new(); @@ -279,19 +285,7 @@ fn test_raffle_and_claim() { ); } - let nft_nonces: [u64; 6] = [1, 2, 3, 4, 5, 6]; - // claim the rewards - // TODO: change after multi_esdt_transfer implementation - let _nft_payments: Vec = nft_nonces - .iter() - .map(|nonce| TxTokenTransfer { - token_identifier: NFT_TOKEN_ID.to_vec(), - nonce: *nonce, - value: 1u64.into(), - }) - .collect(); - - let expected_rewards: [u64; 6] = [289_800, 114_999, 114_999, 289_800, 114_999, 114_999]; + let expected_rewards = [114_999, 114_999, 114_999, 828_000, 114_999, 114_999]; for (nonce, expected_reward) in std::iter::zip(nft_nonces, expected_rewards) { state.world.sc_call_use_result( @@ -320,12 +314,7 @@ fn test_raffle_and_claim() { state.world.sc_call( ScCallStep::new() .from(ALICE_ADDRESS_EXPR) - .esdt_transfer(NFT_TOKEN_ID_EXPR, 1, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 2, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 3, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 4, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 5, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 6, "1") + .multi_esdt_transfer(nft_payments.clone()) .call( state .rewards_distribution_contract @@ -367,12 +356,7 @@ fn test_raffle_and_claim() { state.world.sc_call( ScCallStep::new() .from(ALICE_ADDRESS_EXPR) - .esdt_transfer(NFT_TOKEN_ID_EXPR, 1, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 2, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 3, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 4, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 5, "1") - .esdt_transfer(NFT_TOKEN_ID_EXPR, 6, "1") + .multi_esdt_transfer(nft_payments.clone()) .call( state .rewards_distribution_contract From f7ba2fe3eb64eeb2bd522ffb635341d7b771ac2a Mon Sep 17 00:00:00 2001 From: Ovidiu Stinga Date: Thu, 21 Sep 2023 17:34:53 +0300 Subject: [PATCH 5/5] crowdfunding-esdt blackbox full --- .../tests/crowdfunding_esdt_blackbox_test.rs | 423 +++++++++--------- .../crowdfunding_esdt_whitebox_legacy_test.rs | 322 ------------- 2 files changed, 220 insertions(+), 525 deletions(-) delete mode 100644 contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_whitebox_legacy_test.rs diff --git a/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_blackbox_test.rs b/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_blackbox_test.rs index 66311ab731..57b3ef2dd7 100644 --- a/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_blackbox_test.rs +++ b/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_blackbox_test.rs @@ -1,246 +1,263 @@ -use crowdfunding_esdt::*; -use multiversx_sc::types::EgldOrEsdtTokenIdentifier; -use multiversx_sc_scenario::{api::StaticApi, scenario_model::*, *}; +use crowdfunding_esdt::{ProxyTrait as _, Status}; +use multiversx_sc::{ + storage::mappers::SingleValue, + types::{Address, EgldOrEsdtTokenIdentifier}, +}; +use multiversx_sc_scenario::{ + api::StaticApi, + scenario_model::{ + Account, AddressValue, CheckAccount, CheckStateStep, ScCallStep, ScDeployStep, ScQueryStep, + SetStateStep, TxExpect, + }, + ContractInfo, ScenarioWorld, +}; +use num_bigint::BigUint; -const CF_PATH_EXPR: &str = "file:output/crowdfunding-esdt.wasm"; +const CF_DEADLINE: u64 = 7 * 24 * 60 * 60; // 1 week in seconds +const CF_TOKEN_ID: &[u8] = b"CROWD-123456"; +const CF_TOKEN_ID_EXPR: &str = "str:CROWD-123456"; +const CROWDFUNDING_ESDT_ADDRESS_EXPR: &str = "sc:crowdfunding-esdt"; +const CROWDFUNDING_ESDT_PATH_EXPR: &str = "file:output/crowdfunding-esdt.wasm"; +const FIRST_USER_ADDRESS_EXPR: &str = "address:first-user"; +const OWNER_ADDRESS_EXPR: &str = "address:owner"; +const SECOND_USER_ADDRESS_EXPR: &str = "address:second-user"; + +type CrowdfundingESDTContract = ContractInfo>; fn world() -> ScenarioWorld { let mut blockchain = ScenarioWorld::new(); blockchain.set_current_dir_from_workspace("contracts/examples/crowdfunding-esdt"); - blockchain.register_contract(CF_PATH_EXPR, crowdfunding_esdt::ContractBuilder); + blockchain.register_contract( + CROWDFUNDING_ESDT_PATH_EXPR, + crowdfunding_esdt::ContractBuilder, + ); blockchain } -#[test] -fn crowdfunding_scenario_rust_test() { - let mut world = world(); - let cf_code = world.code_expression(CF_PATH_EXPR); - - let owner_addr = "address:owner"; - let first_user_addr = "address:user1"; - let second_user_addr = "address:user2"; - - let deadline: u64 = 7 * 24 * 60 * 60; // 1 week in seconds - let cf_token_id_value = "CROWD-123456"; // when passing as argument - let cf_token_id = "str:CROWD-123456"; // when specifying the token transfer - let mut cf_sc = ContractInfo::>::new("sc:crowdfunding"); - - // setup owner and crowdfunding SC - world.start_trace().set_state_step( - SetStateStep::new() - .put_account(owner_addr, Account::new()) - .new_address(owner_addr, 0, &cf_sc), - ); +struct CrowdfundingESDTTestState { + world: ScenarioWorld, + crowdfunding_esdt_contract: CrowdfundingESDTContract, + first_user_address: Address, + second_user_address: Address, +} - world.sc_deploy( - ScDeployStep::new() - .from(owner_addr) - .code(cf_code) - .call(cf_sc.init( - 2_000u32, - deadline, - EgldOrEsdtTokenIdentifier::esdt(cf_token_id_value), - )), - ); +impl CrowdfundingESDTTestState { + fn new() -> Self { + let mut world = world(); - // setup user accounts - world - .set_state_step(SetStateStep::new().put_account( - first_user_addr, - Account::new().esdt_balance(cf_token_id, 1_000u64), - )) - .set_state_step(SetStateStep::new().put_account( - second_user_addr, - Account::new().esdt_balance(cf_token_id, 1_000u64), - )); - - // first user deposit - world - .sc_call( - ScCallStep::new() - .from(first_user_addr) - .to(&cf_sc) - .esdt_transfer(cf_token_id, 0u64, 1_000u64) - .call(cf_sc.fund()), - ) - .check_state_step( - CheckStateStep::new() + world.set_state_step( + SetStateStep::new() + .put_account(OWNER_ADDRESS_EXPR, Account::new().nonce(1)) + .new_address(OWNER_ADDRESS_EXPR, 1, CROWDFUNDING_ESDT_ADDRESS_EXPR) .put_account( - first_user_addr, - CheckAccount::new().esdt_balance(cf_token_id, 0u64), + FIRST_USER_ADDRESS_EXPR, + Account::new() + .nonce(1) + .balance("1_000") + .esdt_balance(CF_TOKEN_ID_EXPR, "1_000"), ) .put_account( - &cf_sc, - CheckAccount::new().esdt_balance(cf_token_id, 1_000u64), + SECOND_USER_ADDRESS_EXPR, + Account::new() + .nonce(1) + .esdt_balance(CF_TOKEN_ID_EXPR, "1_000"), ), ); - // second user deposit - world - .sc_call( - ScCallStep::new() - .from(second_user_addr) - .to(&cf_sc) - .esdt_transfer(cf_token_id, 0u64, 500u64) - .call(cf_sc.fund()), - ) - .check_state_step( - CheckStateStep::new() - .put_account( - second_user_addr, - CheckAccount::new().esdt_balance(cf_token_id, 500u64), - ) - .put_account( - &cf_sc, - CheckAccount::new().esdt_balance(cf_token_id, 1_500u64), - ), + let crowdfunding_esdt_contract = + CrowdfundingESDTContract::new(CROWDFUNDING_ESDT_ADDRESS_EXPR); + + let first_user_address = AddressValue::from(FIRST_USER_ADDRESS_EXPR).to_address(); + let second_user_address = AddressValue::from(SECOND_USER_ADDRESS_EXPR).to_address(); + + Self { + world, + crowdfunding_esdt_contract, + first_user_address, + second_user_address, + } + } + + fn deploy(&mut self) -> &mut Self { + let crowdfunding_esdt_code = self.world.code_expression(CROWDFUNDING_ESDT_PATH_EXPR); + + self.world.sc_deploy( + ScDeployStep::new() + .from(OWNER_ADDRESS_EXPR) + .code(crowdfunding_esdt_code) + .call(self.crowdfunding_esdt_contract.init( + 2_000u32, + CF_DEADLINE, + EgldOrEsdtTokenIdentifier::esdt(CF_TOKEN_ID), + )), ); - // get status before - let status: Status = cf_sc - .status() - .into_vm_query() - .expect(TxExpect::ok().result("")) - .execute(&mut world); - assert_eq!(status, Status::FundingPeriod); - - // deadline passed - world.set_state_step(SetStateStep::new().block_timestamp(deadline)); - - // get status after deadline - let status: Status = cf_sc - .status() - .into_vm_query() - .expect(TxExpect::ok().result("2")) - .execute(&mut world); - assert_eq!(status, Status::Failed); - - // test failed campaign - - // owner claim - failed campaign - nothing is transferred - world - .sc_call( + self + } + + fn fund(&mut self, address: &str, amount: &str) -> &mut Self { + self.world.sc_call( ScCallStep::new() - .from(owner_addr) - .to(&cf_sc) - .call(cf_sc.claim()), - ) - .check_state_step( - CheckStateStep::new() - .put_account( - owner_addr, - CheckAccount::new().esdt_balance(cf_token_id, 0u64), - ) - .put_account( - &cf_sc, - CheckAccount::new().esdt_balance(cf_token_id, 1_500u64), - ), + .from(address) + .esdt_transfer(CF_TOKEN_ID_EXPR, 0, amount) + .call(self.crowdfunding_esdt_contract.fund()), ); - // first user claim - failed campaign - world - .sc_call( - ScCallStep::new() - .from(first_user_addr) - .to(&cf_sc) - .call(cf_sc.claim()), - ) - .check_state_step( - CheckStateStep::new() - .put_account( - first_user_addr, - CheckAccount::new().esdt_balance(cf_token_id, 1_000u64), - ) - .put_account( - &cf_sc, - CheckAccount::new().esdt_balance(cf_token_id, 500u64), - ), + self + } + + fn check_deposit(&mut self, donor: Address, amount: u64) -> &mut Self { + self.world.sc_query( + ScQueryStep::new() + .call(self.crowdfunding_esdt_contract.deposit(&donor)) + .expect_value(SingleValue::from(BigUint::from(amount))), + ); + + self + } + + fn check_status(&mut self, expected_value: Status) -> &mut Self { + self.world.sc_query( + ScQueryStep::new() + .call(self.crowdfunding_esdt_contract.status()) + .expect_value(expected_value), ); - // second user claim - failed campaign - world - .sc_call( + self + } + + fn claim(&mut self, address: &str) -> &mut Self { + self.world.sc_call( ScCallStep::new() - .from(second_user_addr) - .to(&cf_sc) - .call(cf_sc.claim()), - ) - .check_state_step( - CheckStateStep::new() - .put_account( - second_user_addr, - CheckAccount::new().esdt_balance(cf_token_id, 1_000u64), - ) - .put_account(&cf_sc, CheckAccount::new().esdt_balance(cf_token_id, 0u64)), + .from(address) + .call(self.crowdfunding_esdt_contract.claim()), ); - // test successful campaign + self + } - world.set_state_step(SetStateStep::new().block_timestamp(deadline / 2)); + fn check_esdt_balance(&mut self, address_expr: &str, balance_expr: &str) -> &mut Self { + self.world + .check_state_step(CheckStateStep::new().put_account( + address_expr, + CheckAccount::new().esdt_balance(CF_TOKEN_ID_EXPR, balance_expr), + )); - // first user deposit - world.sc_call( - ScCallStep::new() - .from(first_user_addr) - .to(&cf_sc) - .esdt_transfer(cf_token_id, 0u64, 1_000u64) - .call(cf_sc.fund()), - ); + self + } + + fn set_block_timestamp(&mut self, block_timestamp_expr: u64) -> &mut Self { + self.world + .set_state_step(SetStateStep::new().block_timestamp(block_timestamp_expr)); + + self + } +} + +#[test] +fn test_fund() { + let mut state = CrowdfundingESDTTestState::new(); + state.deploy(); + + state.fund(FIRST_USER_ADDRESS_EXPR, "1000"); + state.check_deposit(state.first_user_address.clone(), 1_000); +} - // second user deposit - world.sc_call( +#[test] +fn test_status() { + let mut state = CrowdfundingESDTTestState::new(); + state.deploy(); + + state.check_status(Status::FundingPeriod); +} + +#[test] +fn test_sc_error() { + let mut state = CrowdfundingESDTTestState::new(); + state.deploy(); + + state.world.sc_call( ScCallStep::new() - .from(second_user_addr) - .to(&cf_sc) - .esdt_transfer(cf_token_id, 0u64, 1_000u64) - .call(cf_sc.fund()), + .from(FIRST_USER_ADDRESS_EXPR) + .egld_value("1_000") + .call(state.crowdfunding_esdt_contract.fund()) + .expect(TxExpect::user_error("str:wrong token")), ); + state.world.sc_query( + ScQueryStep::new() + .call( + state + .crowdfunding_esdt_contract + .deposit(&state.first_user_address), + ) + .expect(TxExpect::ok().result("0x")), + ); +} - let status: Status = cf_sc - .status() - .into_vm_query() - .expect(TxExpect::ok().result("")) - .execute(&mut world); - assert_eq!(status, Status::FundingPeriod); +#[test] +fn test_successful_cf() { + let mut state = CrowdfundingESDTTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS_EXPR, "1_000"); + state.check_deposit(state.first_user_address.clone(), 1_000); + + // second user fund + state.fund(SECOND_USER_ADDRESS_EXPR, "1_000"); + state.check_deposit(state.second_user_address.clone(), 1_000); - world.set_state_step(SetStateStep::new().block_timestamp(deadline)); + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + 1); - let status: Status = cf_sc - .status() - .into_vm_query() - .expect(TxExpect::ok().result("1")) - .execute(&mut world); - assert_eq!(status, Status::Successful); + // check status successful + state.check_status(Status::Successful); - // first user try claim - successful campaign - world.sc_call( + // user try claim + state.world.sc_call( ScCallStep::new() - .from(first_user_addr) - .to(&cf_sc) - .call(cf_sc.claim()) - .expect(TxExpect::err( - 4, + .from(FIRST_USER_ADDRESS_EXPR) + .call(state.crowdfunding_esdt_contract.claim()) + .expect(TxExpect::user_error( "str:only owner can claim successful funding", )), ); - // owner claim successful campaign - world - .sc_call( - ScCallStep::new() - .from(owner_addr) - .to(&cf_sc) - .call(cf_sc.claim()), - ) - .check_state_step( - CheckStateStep::new() - .put_account( - owner_addr, - CheckAccount::new().esdt_balance(cf_token_id, 2_000u64), - ) - .put_account(cf_sc, CheckAccount::new().esdt_balance(cf_token_id, 0u64)), - ); + // owner claim + state.claim(OWNER_ADDRESS_EXPR); + + state.check_esdt_balance(OWNER_ADDRESS_EXPR, "2_000"); + state.check_esdt_balance(FIRST_USER_ADDRESS_EXPR, "0"); + state.check_esdt_balance(SECOND_USER_ADDRESS_EXPR, "0"); +} + +#[test] +fn test_failed_cf() { + let mut state = CrowdfundingESDTTestState::new(); + state.deploy(); + + // first user fund + state.fund(FIRST_USER_ADDRESS_EXPR, "300"); + state.check_deposit(state.first_user_address.clone(), 300u64); + + // second user fund + state.fund(SECOND_USER_ADDRESS_EXPR, "600"); + state.check_deposit(state.second_user_address.clone(), 600u64); + + // set block timestamp after deadline + state.set_block_timestamp(CF_DEADLINE + 1); + + // check status failed + state.check_status(Status::Failed); + + // first user claim + state.claim(FIRST_USER_ADDRESS_EXPR); + + // second user claim + state.claim(SECOND_USER_ADDRESS_EXPR); - world.write_scenario_trace("scenarios-gen/crowdfunding_rust.scen.json"); + state.check_esdt_balance(OWNER_ADDRESS_EXPR, "0"); + state.check_esdt_balance(FIRST_USER_ADDRESS_EXPR, "1_000"); + state.check_esdt_balance(SECOND_USER_ADDRESS_EXPR, "1_000"); } diff --git a/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_whitebox_legacy_test.rs b/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_whitebox_legacy_test.rs deleted file mode 100644 index 87289e7a40..0000000000 --- a/contracts/examples/crowdfunding-esdt/tests/crowdfunding_esdt_whitebox_legacy_test.rs +++ /dev/null @@ -1,322 +0,0 @@ -#![allow(deprecated)] // TODO: migrate tests - -use crowdfunding_esdt::*; -use multiversx_sc::types::{Address, EgldOrEsdtTokenIdentifier}; -use multiversx_sc_scenario::{ - managed_address, managed_biguint, managed_token_id, rust_biguint, testing_framework::*, - DebugApi, -}; - -const CF_TOKEN_ID: &[u8] = b"CROWD-123456"; -const CF_DEADLINE: u64 = 7 * 24 * 60 * 60; // 1 week in seconds -const WASM_PATH: &str = "output/crowdfunding-esdt.wasm"; - -struct CrowdfundingSetup -where - CrowdfundingObjBuilder: 'static + Copy + Fn() -> crowdfunding_esdt::ContractObj, -{ - pub blockchain_wrapper: BlockchainStateWrapper, - pub owner_address: Address, - pub first_user_address: Address, - pub second_user_address: Address, - pub cf_wrapper: - ContractObjWrapper, CrowdfundingObjBuilder>, -} - -fn setup_crowdfunding( - cf_builder: CrowdfundingObjBuilder, -) -> CrowdfundingSetup -where - CrowdfundingObjBuilder: 'static + Copy + Fn() -> crowdfunding_esdt::ContractObj, -{ - let rust_zero = rust_biguint!(0u64); - let mut blockchain_wrapper = BlockchainStateWrapper::new(); - let owner_address = blockchain_wrapper.create_user_account(&rust_zero); - let first_user_address = blockchain_wrapper.create_user_account(&rust_zero); - let second_user_address = blockchain_wrapper.create_user_account(&rust_zero); - let cf_wrapper = blockchain_wrapper.create_sc_account( - &rust_zero, - Some(&owner_address), - cf_builder, - WASM_PATH, - ); - - blockchain_wrapper.set_esdt_balance(&first_user_address, CF_TOKEN_ID, &rust_biguint!(1_000)); - blockchain_wrapper.set_esdt_balance(&second_user_address, CF_TOKEN_ID, &rust_biguint!(1_000)); - - blockchain_wrapper - .execute_tx(&owner_address, &cf_wrapper, &rust_zero, |sc| { - let target = managed_biguint!(2_000); - let token_id = managed_token_id!(CF_TOKEN_ID); - - sc.init( - target, - CF_DEADLINE, - EgldOrEsdtTokenIdentifier::esdt(token_id), - ); - }) - .assert_ok(); - - blockchain_wrapper.add_mandos_set_account(cf_wrapper.address_ref()); - - CrowdfundingSetup { - blockchain_wrapper, - owner_address, - first_user_address, - second_user_address, - cf_wrapper, - } -} - -#[test] -fn init_test() { - let cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - cf_setup - .blockchain_wrapper - .write_mandos_output("_generated_init.scen.json"); -} - -#[test] -fn fund_test() { - let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - let b_wrapper = &mut cf_setup.blockchain_wrapper; - let user_addr = &cf_setup.first_user_address; - - b_wrapper - .execute_esdt_transfer( - user_addr, - &cf_setup.cf_wrapper, - CF_TOKEN_ID, - 0, - &rust_biguint!(1_000), - |sc| { - sc.fund(); - - let user_deposit = sc.deposit(&managed_address!(user_addr)).get(); - let expected_deposit = managed_biguint!(1_000); - assert_eq!(user_deposit, expected_deposit); - }, - ) - .assert_ok(); - - let mut sc_call = ScCallMandos::new(user_addr, cf_setup.cf_wrapper.address_ref(), "fund"); - sc_call.add_esdt_transfer(CF_TOKEN_ID, 0, &rust_biguint!(1_000)); - - let expect = TxExpectMandos::new(0); - b_wrapper.add_mandos_sc_call(sc_call, Some(expect)); - - cf_setup - .blockchain_wrapper - .write_mandos_output("_generated_fund.scen.json"); -} - -#[test] -fn status_test() { - let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - let b_wrapper = &mut cf_setup.blockchain_wrapper; - - b_wrapper - .execute_query(&cf_setup.cf_wrapper, |sc| { - let status = sc.status(); - assert_eq!(status, Status::FundingPeriod); - }) - .assert_ok(); - - let sc_query = ScQueryMandos::new(cf_setup.cf_wrapper.address_ref(), "status"); - let mut expect = TxExpectMandos::new(0); - expect.add_out_value(&Status::FundingPeriod); - - b_wrapper.add_mandos_sc_query(sc_query, Some(expect)); - - cf_setup - .blockchain_wrapper - .write_mandos_output("_generated_query_status.scen.json"); -} - -#[test] -fn test_sc_error() { - let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - let b_wrapper = &mut cf_setup.blockchain_wrapper; - let user_addr = &cf_setup.first_user_address; - - b_wrapper.set_egld_balance(user_addr, &rust_biguint!(1_000)); - - b_wrapper - .execute_tx( - user_addr, - &cf_setup.cf_wrapper, - &rust_biguint!(1_000), - |sc| { - sc.fund(); - }, - ) - .assert_user_error("wrong token"); - - b_wrapper - .execute_tx(user_addr, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { - let user_deposit = sc.deposit(&managed_address!(user_addr)).get(); - let expected_deposit = managed_biguint!(0); - assert_eq!(user_deposit, expected_deposit); - }) - .assert_ok(); - - let mut sc_call = ScCallMandos::new(user_addr, cf_setup.cf_wrapper.address_ref(), "fund"); - sc_call.add_egld_value(&rust_biguint!(1_000)); - - let mut expect = TxExpectMandos::new(4); - expect.set_message("wrong token"); - - b_wrapper.add_mandos_sc_call(sc_call, Some(expect)); - - cf_setup - .blockchain_wrapper - .write_mandos_output("_generated_sc_err.scen.json"); -} - -#[test] -fn test_successful_cf() { - let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - let b_wrapper = &mut cf_setup.blockchain_wrapper; - let owner = &cf_setup.owner_address; - let first_user = &cf_setup.first_user_address; - let second_user = &cf_setup.second_user_address; - - // first user fund - b_wrapper - .execute_esdt_transfer( - first_user, - &cf_setup.cf_wrapper, - CF_TOKEN_ID, - 0, - &rust_biguint!(1_000), - |sc| { - sc.fund(); - - let user_deposit = sc.deposit(&managed_address!(first_user)).get(); - let expected_deposit = managed_biguint!(1_000); - assert_eq!(user_deposit, expected_deposit); - }, - ) - .assert_ok(); - - // second user fund - b_wrapper - .execute_esdt_transfer( - second_user, - &cf_setup.cf_wrapper, - CF_TOKEN_ID, - 0, - &rust_biguint!(1_000), - |sc| { - sc.fund(); - - let user_deposit = sc.deposit(&managed_address!(second_user)).get(); - let expected_deposit = managed_biguint!(1_000); - assert_eq!(user_deposit, expected_deposit); - }, - ) - .assert_ok(); - - // set block timestamp after deadline - b_wrapper.set_block_timestamp(CF_DEADLINE + 1); - - // check status - b_wrapper - .execute_query(&cf_setup.cf_wrapper, |sc| { - let status = sc.status(); - assert_eq!(status, Status::Successful); - }) - .assert_ok(); - - // user try claim - b_wrapper - .execute_tx(first_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { - sc.claim(); - }) - .assert_user_error("only owner can claim successful funding"); - - // owner claim - b_wrapper - .execute_tx(owner, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { - sc.claim(); - }) - .assert_ok(); - - b_wrapper.check_esdt_balance(owner, CF_TOKEN_ID, &rust_biguint!(2_000)); - b_wrapper.check_esdt_balance(first_user, CF_TOKEN_ID, &rust_biguint!(0)); - b_wrapper.check_esdt_balance(second_user, CF_TOKEN_ID, &rust_biguint!(0)); -} - -#[test] -fn test_failed_cf() { - let mut cf_setup = setup_crowdfunding(crowdfunding_esdt::contract_obj); - let b_wrapper = &mut cf_setup.blockchain_wrapper; - let owner = &cf_setup.owner_address; - let first_user = &cf_setup.first_user_address; - let second_user = &cf_setup.second_user_address; - - // first user fund - b_wrapper - .execute_esdt_transfer( - first_user, - &cf_setup.cf_wrapper, - CF_TOKEN_ID, - 0, - &rust_biguint!(300), - |sc| { - sc.fund(); - - let user_deposit = sc.deposit(&managed_address!(first_user)).get(); - let expected_deposit = managed_biguint!(300); - assert_eq!(user_deposit, expected_deposit); - }, - ) - .assert_ok(); - - // second user fund - b_wrapper - .execute_esdt_transfer( - second_user, - &cf_setup.cf_wrapper, - CF_TOKEN_ID, - 0, - &rust_biguint!(600), - |sc| { - sc.fund(); - - let user_deposit = sc.deposit(&managed_address!(second_user)).get(); - let expected_deposit = managed_biguint!(600); - assert_eq!(user_deposit, expected_deposit); - }, - ) - .assert_ok(); - - // set block timestamp after deadline - b_wrapper.set_block_timestamp(CF_DEADLINE + 1); - - // check status - b_wrapper - .execute_query(&cf_setup.cf_wrapper, |sc| { - let status = sc.status(); - assert_eq!(status, Status::Failed); - }) - .assert_ok(); - - // first user claim - b_wrapper - .execute_tx(first_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { - sc.claim(); - }) - .assert_ok(); - - // second user claim - b_wrapper - .execute_tx(second_user, &cf_setup.cf_wrapper, &rust_biguint!(0), |sc| { - sc.claim(); - }) - .assert_ok(); - - b_wrapper.check_esdt_balance(owner, CF_TOKEN_ID, &rust_biguint!(0)); - b_wrapper.check_esdt_balance(first_user, CF_TOKEN_ID, &rust_biguint!(1_000)); - b_wrapper.check_esdt_balance(second_user, CF_TOKEN_ID, &rust_biguint!(1_000)); -}