diff --git a/src/admin.rs b/src/admin.rs index 2f5f2e9..05db018 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -5,8 +5,9 @@ use crate::{ compensation_cache::{self, CompensationCache}, }, errors::{ + ERR_ALREADY_ACTIVE, ERR_ALREADY_INACTIVE, ERR_ALREADY_IN_STORAGE, ERR_COMPENSATION_NOT_FOUND, ERR_INVALID_PENALTY_VALUE, ERR_INVALID_TIMESTAMP, - ERR_INVALID_TOKEN_IDENTIFIER, ERR_NOT_PRIVILEGED, + ERR_INVALID_TOKEN_IDENTIFIER, ERR_NOT_IN_STORAGE, ERR_NOT_PRIVILEGED, }, events, only_privileged, storage::{self, PenaltyType}, @@ -19,6 +20,22 @@ multiversx_sc::derive_imports!(); pub trait AdminModule: crate::config::ConfigModule + storage::StorageModule + events::EventsModule { + #[endpoint(initiateBond)] + fn initiate_bond_for_address( + &self, + address: ManagedAddress, + token_identifier: TokenIdentifier, + nonce: u64, + ) { + only_privileged!(self, ERR_NOT_PRIVILEGED); + + let bond_id = self + .bonds_ids() + .get_id_or_insert((token_identifier.clone(), nonce)); + + self.address_bonds(&address).insert(bond_id); + } + #[endpoint(setBlacklist)] fn add_to_black_list( &self, @@ -35,6 +52,12 @@ pub trait AdminModule: self.add_to_blacklist_event(&compensation_id, &addresses); for address in addresses.into_iter() { + if self + .compensation_blacklist(compensation_id) + .contains(&address) + { + sc_panic!(ERR_ALREADY_IN_STORAGE); + } self.compensation_blacklist(compensation_id).insert(address); } } @@ -55,6 +78,12 @@ pub trait AdminModule: self.remove_from_blacklist_event(&compensation_id, &addresses); for address in addresses.into_iter() { + if !self + .compensation_blacklist(compensation_id) + .contains(&address) + { + sc_panic!(ERR_NOT_IN_STORAGE); + } self.compensation_blacklist(compensation_id) .swap_remove(&address); } @@ -90,6 +119,10 @@ pub trait AdminModule: ) { only_privileged!(self, ERR_NOT_PRIVILEGED); + if penalty != PenaltyType::Custom { + require!(custom_penalty.is_none(), ERR_INVALID_PENALTY_VALUE); + } + let bond_id = self .bonds_ids() .get_id_non_zero((token_identifier.clone(), nonce)); @@ -154,6 +187,10 @@ pub trait AdminModule: #[endpoint(setContractStateActive)] fn set_contract_state_active(&self) { only_privileged!(self, ERR_NOT_PRIVILEGED); + require!( + self.contract_state().get() == State::Inactive, + ERR_ALREADY_ACTIVE + ); self.contract_state().set(State::Active); self.contract_state_event(State::Active); } @@ -161,6 +198,10 @@ pub trait AdminModule: #[endpoint(setContractStateInactive)] fn set_contract_state_inactive(&self) { only_privileged!(self, ERR_NOT_PRIVILEGED); + require!( + self.contract_state().get() == State::Active, + ERR_ALREADY_INACTIVE + ); self.contract_state().set(State::Inactive); self.contract_state_event(State::Inactive); } @@ -170,6 +211,9 @@ pub trait AdminModule: only_privileged!(self, ERR_NOT_PRIVILEGED); self.set_accepted_callers_event(&callers); for caller in callers.into_iter() { + if self.accepted_callers().contains(&caller) { + sc_panic!(ERR_ALREADY_IN_STORAGE) + } self.accepted_callers().insert(caller); } } @@ -179,6 +223,9 @@ pub trait AdminModule: only_privileged!(self, ERR_NOT_PRIVILEGED); self.remove_accepted_callers_event(&callers); for caller in callers.into_iter() { + if !self.accepted_callers().contains(&caller) { + sc_panic!(ERR_NOT_IN_STORAGE) + } self.accepted_callers().swap_remove(&caller); } } @@ -194,11 +241,16 @@ pub trait AdminModule: self.bond_payment_token().set(token_identifier); } - #[endpoint(setPeriodsBonds)] - fn set_lock_periods_with_bonds(&self, args: MultiValueEncoded>) { + #[endpoint(addPeriodsBonds)] + fn add_lock_periods_with_bonds(&self, args: MultiValueEncoded>) { only_privileged!(self, ERR_NOT_PRIVILEGED); for input in args.into_iter() { let (lock_period, bond) = input.into_tuple(); + + if self.lock_periods().contains(&lock_period) { + sc_panic!(ERR_ALREADY_IN_STORAGE); + } + self.set_period_and_bond_event(&lock_period, &bond); self.lock_periods().insert(lock_period); self.lock_period_bond_amount(lock_period).set(bond); @@ -209,6 +261,9 @@ pub trait AdminModule: fn remove_lock_periods_with_bonds(&self, lock_periods: MultiValueEncoded) { only_privileged!(self, ERR_NOT_PRIVILEGED); for period in lock_periods.into_iter() { + if !self.lock_periods().contains(&period) { + sc_panic!(ERR_NOT_IN_STORAGE); + } self.remove_period_and_bond_event(&period, &self.lock_period_bond_amount(period).get()); self.lock_periods().remove(&period); self.lock_period_bond_amount(period).clear(); diff --git a/src/config.rs b/src/config.rs index ce64db4..c40a805 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use crate::{events, storage}; +use crate::{errors::ERR_ALREADY_IN_STORAGE, events, storage}; multiversx_sc::imports!(); multiversx_sc::derive_imports!(); @@ -29,17 +29,16 @@ pub trait ConfigModule: storage::StorageModule + events::EventsModule { #[endpoint(setAdministrator)] fn set_administrator(&self, administrator: ManagedAddress) { self.set_administrator_event(&administrator); + + if !self.administrator().is_empty() { + require!( + administrator != self.administrator().get(), + ERR_ALREADY_IN_STORAGE + ); + } self.administrator().set(administrator); } - #[view(getContractState)] - #[storage_mapper("contract_state")] - fn contract_state(&self) -> SingleValueMapper; - - #[view(getAdministrator)] - #[storage_mapper("administrator")] - fn administrator(&self) -> SingleValueMapper; - #[inline] fn is_contract_owner(&self, address: &ManagedAddress) -> bool { &(self.blockchain().get_owner_address()) == address @@ -81,4 +80,40 @@ pub trait ConfigModule: storage::StorageModule + events::EventsModule { fn is_state_active(&self, state: State) -> bool { state == State::Active } + + #[view(getContractState)] + #[storage_mapper("contract_state")] + fn contract_state(&self) -> SingleValueMapper; + + #[view(getAdministrator)] + #[storage_mapper("administrator")] + fn administrator(&self) -> SingleValueMapper; + + #[view(getAcceptedCallers)] + #[storage_mapper("accepted_callers")] + fn accepted_callers(&self) -> UnorderedSetMapper; + + #[view(getBondPaymentToken)] + #[storage_mapper("bond_payment_token")] + fn bond_payment_token(&self) -> SingleValueMapper; + + #[view(getLockPeriods)] + #[storage_mapper("lock_periods")] + fn lock_periods(&self) -> SetMapper; + + #[view(getLockPeriodBondAmount)] + #[storage_mapper("lock_period_bond_amount")] + fn lock_period_bond_amount(&self, lock_period: u64) -> SingleValueMapper; + + #[view(getMinimumPenalty)] + #[storage_mapper("minimum_penalty")] + fn minimum_penalty(&self) -> SingleValueMapper; + + #[view(getMaximumPenalty)] + #[storage_mapper("maximum_penalty")] + fn maximum_penalty(&self) -> SingleValueMapper; + + #[view(getWithdrawPenalty)] + #[storage_mapper("withdraw_penalty")] + fn withdraw_penalty(&self) -> SingleValueMapper; } diff --git a/src/errors.rs b/src/errors.rs index 23842b2..d13aad5 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -15,3 +15,7 @@ pub const ERR_INVALID_TIMELINE_TO_REFUND: &str = "Invalid timeline to refund"; pub const ERR_REFUND_NOT_FOUND: &str = "Refund not found"; pub const ERR_INVALID_TIMESTAMP: &str = "Invalid timestamp"; pub const ERR_PENALTIES_EXCEED_WITHDRAWAL_AMOUNT: &str = "Penalties exceed withdrawal amount"; +pub const ERR_ALREADY_IN_STORAGE: &str = "Already in storage"; +pub const ERR_NOT_IN_STORAGE: &str = "Not in storage"; +pub const ERR_ALREADY_ACTIVE: &str = "Already active"; +pub const ERR_ALREADY_INACTIVE: &str = "Already inactive"; diff --git a/src/events.rs b/src/events.rs index 5413368..393469a 100644 --- a/src/events.rs +++ b/src/events.rs @@ -1,5 +1,3 @@ -// [TO DO] implement events for endpoints - use multiversx_sc::types::MultiValueEncoded; use crate::{ diff --git a/src/lib.rs b/src/lib.rs index 1f189df..7cd05c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ pub trait LifeBondingContract: original_caller: ManagedAddress, token_identifier: TokenIdentifier, nonce: u64, - lock_period: u64, //seconds + lock_period_seconds: u64, ) { require_contract_ready!(self, ERR_CONTRACT_NOT_READY); let caller = self.blockchain().get_caller(); @@ -85,36 +85,27 @@ pub trait LifeBondingContract: ERR_INVALID_TOKEN_IDENTIFIER ); - // check token_identifier is accepted (not really needed as this endpoint will be called by the minting contract) - require!( - self.lock_periods().contains(&lock_period), + self.lock_periods().contains(&lock_period_seconds), ERR_INVALID_LOCK_PERIOD ); require!( - !self.lock_period_bond_amount(lock_period).is_empty(), + !self.lock_period_bond_amount(lock_period_seconds).is_empty(), ERR_INVALID_LOCK_PERIOD - ); // check not really needed + ); - let bond_amount = self.lock_period_bond_amount(lock_period).get(); + let bond_amount = self.lock_period_bond_amount(lock_period_seconds).get(); require!(payment.amount == bond_amount, ERR_INVALID_AMOUNT); let current_timestamp = self.blockchain().get_block_timestamp(); - let unbound_timestamp = current_timestamp + lock_period; - - // let check_bond_id = self.bonds_ids().get_id((token_identifier.clone(), nonce)); - - // require!( - // !self.bonds_ids().contains_id(check_bond_id), - // ERR_BOND_ALREADY_CREATED - // ); + let unbound_timestamp = current_timestamp + lock_period_seconds; self.bond_address(bond_id).set(original_caller.clone()); self.bond_token_identifier(bond_id) .set(token_identifier.clone()); self.bond_nonce(bond_id).set(nonce); - self.bond_lock_period(bond_id).set(lock_period); + self.bond_lock_period(bond_id).set(lock_period_seconds); self.bond_timestamp(bond_id).set(current_timestamp); self.unbound_timestamp(bond_id).set(unbound_timestamp); self.bond_amount(bond_id).set(payment.amount.clone()); @@ -185,10 +176,8 @@ pub trait LifeBondingContract: 0u64, &bond_cache.remaining_amount, ); - // clear compensations as the entire bond is withdrawn - // compensation_cache.clear(); - // self.compensations_ids().remove_by_id(compensation_id); - self.compensations().swap_remove(&compensation_id); // remove from showing if it's withdrawn + + self.compensations().swap_remove(&compensation_id); } self.withdraw_event( @@ -198,11 +187,7 @@ pub trait LifeBondingContract: &penalty_amount, ); - // Do not clear bond totally as creator can come and bond/renew again - // bond_cache.clear(); - // self.bonds_ids().remove_by_id(bond_id); - // self.address_bonds(&caller).swap_remove(&bond_id); - self.bonds().swap_remove(&bond_id); // remove from showing if it's withdrawn + self.bonds().swap_remove(&bond_id); } #[endpoint(renew)] @@ -281,7 +266,7 @@ pub trait LifeBondingContract: let current_timestamp = self.blockchain().get_block_timestamp(); require!( - current_timestamp > compensation_cache.end_date + COMPENSATION_SAFE_PERIOD, // 86_400 seconds safe period for black list to be uploaded + current_timestamp > compensation_cache.end_date + COMPENSATION_SAFE_PERIOD, ERR_INVALID_TIMELINE_TO_REFUND ); @@ -297,7 +282,7 @@ pub trait LifeBondingContract: let address_refund = self.address_refund(&caller, compensation_id).get(); self.send() - .direct_non_zero_esdt_payment(&caller, &address_refund.proof_of_refund); // sending back the nfts + .direct_non_zero_esdt_payment(&caller, &address_refund.proof_of_refund); compensation_cache.proof_amount -= &address_refund.proof_of_refund.amount; self.compensation_blacklist(compensation_id) @@ -351,14 +336,10 @@ pub trait LifeBondingContract: self.address_refund(&caller, compensation_id).clear(); } - if compensation_cache.accumulated_amount == BigUint::zero() // remove compensation if there is no more accumulated amount + if compensation_cache.accumulated_amount == BigUint::zero() && compensation_cache.proof_amount == BigUint::zero() { - self.compensations().swap_remove(&compensation_id); // remove from showing if it's empty - - // Do not clear compensation totally as creator can come and bond/renew again - // compensation_cache.clear(); - // self.compensations_ids().remove_by_id(compensation_id); + self.compensations().swap_remove(&compensation_id); } } } diff --git a/src/storage.rs b/src/storage.rs index f0cd86f..69bc805 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -91,33 +91,12 @@ pub struct Refund { #[multiversx_sc::module] pub trait StorageModule { - #[view(getAcceptedCallers)] - #[storage_mapper("accepted_callers")] - fn accepted_callers(&self) -> UnorderedSetMapper; - - #[view(getBondPaymentToken)] - #[storage_mapper("bond_payment_token")] - fn bond_payment_token(&self) -> SingleValueMapper; // bonding token - - #[view(getLockPeriods)] - #[storage_mapper("lock_periods")] - fn lock_periods(&self) -> SetMapper; // list of lock periods in days // max_value = 65535 ~ 179 years - - #[view(getLockPeriodBondAmount)] - #[storage_mapper("lock_period_bond_amount")] - fn lock_period_bond_amount(&self, lock_period: u64) -> SingleValueMapper; // bonds based on lock_period if 0 then period not accepted - - #[view(getMinimumPenalty)] - #[storage_mapper("minimum_penalty")] - fn minimum_penalty(&self) -> SingleValueMapper; // percentage - - #[view(getMaximumPenalty)] - #[storage_mapper("maximum_penalty")] - fn maximum_penalty(&self) -> SingleValueMapper; // percentage 100% = 10_000 + // Compensation storage + #[storage_mapper("compensations_ids")] + fn compensations_ids(&self) -> ObjectToIdMapper; - #[view(getWithdrawPenalty)] - #[storage_mapper("withdraw_penalty")] - fn withdraw_penalty(&self) -> SingleValueMapper; // percentage + #[storage_mapper("compensations_ids")] + fn compensations(&self) -> UnorderedSetMapper; #[storage_mapper("compensation_token_identifer")] fn compensation_token_identifer( @@ -141,12 +120,12 @@ pub trait StorageModule { #[storage_mapper("refund_blacklist")] fn compensation_blacklist(&self, compensation_id: u64) -> UnorderedSetMapper; - // do not use view annotation - #[storage_mapper("compensations_ids")] - fn compensations_ids(&self) -> ObjectToIdMapper; + // Bond storage + #[storage_mapper("bonds_ids")] + fn bonds_ids(&self) -> ObjectToIdMapper; - #[storage_mapper("compensations_ids")] - fn compensations(&self) -> UnorderedSetMapper; + #[storage_mapper("bonds")] + fn bonds(&self) -> UnorderedSetMapper; #[storage_mapper("bond_address")] fn bond_address(&self, bond_id: u64) -> SingleValueMapper; @@ -172,16 +151,9 @@ pub trait StorageModule { #[storage_mapper("remaining_amount")] fn remaining_amount(&self, bond_id: u64) -> SingleValueMapper; - // do not use view annotation - #[storage_mapper("bonds_ids")] - fn bonds_ids(&self) -> ObjectToIdMapper; - #[storage_mapper("address_bonds")] fn address_bonds(&self, address: &ManagedAddress) -> UnorderedSetMapper; - #[storage_mapper("bonds")] - fn bonds(&self) -> UnorderedSetMapper; - #[storage_mapper("address_refund")] fn address_refund( &self, diff --git a/tests/bonding_state/bonding_state.rs b/tests/bonding_state/bonding_state.rs index 046f962..f8dc7b9 100644 --- a/tests/bonding_state/bonding_state.rs +++ b/tests/bonding_state/bonding_state.rs @@ -365,7 +365,7 @@ impl ContractState { self.world.sc_call( ScCallStep::new() .from(caller) - .call(self.contract.set_lock_periods_with_bonds(arg)) + .call(self.contract.add_lock_periods_with_bonds(arg)) .expect(tx_expect), ); self diff --git a/tests/endpoints/admin.rs b/tests/endpoints/admin.rs index 7e25955..d254178 100644 --- a/tests/endpoints/admin.rs +++ b/tests/endpoints/admin.rs @@ -70,7 +70,7 @@ fn accepted_callers_test() { state.set_accepted_caller( OWNER_BONDING_CONTRACT_ADDRESS_EXPR, minter_address.clone(), - None, + Some(TxExpect::user_error("str:Already in storage")), ); state.remove_accepted_caller( diff --git a/tests/endpoints/bond.rs b/tests/endpoints/bond.rs index 651f99d..6b86a18 100644 --- a/tests/endpoints/bond.rs +++ b/tests/endpoints/bond.rs @@ -107,6 +107,12 @@ fn bond() { ); state + .set_administrator( + OWNER_BONDING_CONTRACT_ADDRESS_EXPR, + minter_address.clone(), + None, + ) + .remove_lock_period_and_bond(OWNER_BONDING_CONTRACT_ADDRESS_EXPR, 10u64, None) .default_deploy_and_set(10u64, 100u64) .remove_accepted_caller(OWNER_BONDING_CONTRACT_ADDRESS_EXPR, admin.clone(), None) .set_accepted_caller( diff --git a/tests/endpoints/deploy_upgrade.rs b/tests/endpoints/deploy_upgrade.rs index f6912a7..980c5c3 100644 --- a/tests/endpoints/deploy_upgrade.rs +++ b/tests/endpoints/deploy_upgrade.rs @@ -17,8 +17,17 @@ pub fn deploy_and_pause() { admin, Some(TxExpect::ok()), ) - .pause_contract(OWNER_BONDING_CONTRACT_ADDRESS_EXPR, Some(TxExpect::ok())); + .pause_contract( + OWNER_BONDING_CONTRACT_ADDRESS_EXPR, + Some(TxExpect::user_error("str:Already inactive")), + ); state.check_contract_state(State::Inactive); + + state.unpause_contract(OWNER_BONDING_CONTRACT_ADDRESS_EXPR, None); + state.unpause_contract( + OWNER_BONDING_CONTRACT_ADDRESS_EXPR, + Some(TxExpect::user_error("str:Already active")), + ); } #[test] diff --git a/tests/unit_test.rs b/tests/unit_test.rs index 28107b7..ccd8559 100644 --- a/tests/unit_test.rs +++ b/tests/unit_test.rs @@ -3,7 +3,6 @@ mod endpoints; use core_mx_life_bonding_sc::{ config::{ConfigModule, State}, - storage::StorageModule, LifeBondingContract, }; use multiversx_sc_scenario::{ diff --git a/wasm/src/lib.rs b/wasm/src/lib.rs index 7ee2f3b..3e55911 100644 --- a/wasm/src/lib.rs +++ b/wasm/src/lib.rs @@ -5,9 +5,9 @@ //////////////////////////////////////////////////// // Init: 1 -// Endpoints: 46 +// Endpoints: 47 // Async Callback (empty): 1 -// Total number of exported functions: 48 +// Total number of exported functions: 49 #![no_std] #![allow(internal_features)] @@ -26,13 +26,6 @@ multiversx_sc_wasm_adapter::endpoints! { renew => renew proof => add_proof claimRefund => claim_refund - getAcceptedCallers => accepted_callers - getBondPaymentToken => bond_payment_token - getLockPeriods => lock_periods - getLockPeriodBondAmount => lock_period_bond_amount - getMinimumPenalty => minimum_penalty - getMaximumPenalty => maximum_penalty - getWithdrawPenalty => withdraw_penalty getCompensationBlacklist => compensation_blacklist getBond => get_bond getCompensation => get_compensation @@ -48,6 +41,7 @@ multiversx_sc_wasm_adapter::endpoints! { getCompensationsLen => get_compensations_len getLockPeriodsBonds => get_lock_periods_bonds getContractConfiguration => get_contract_configuration + initiateBond => initiate_bond_for_address setBlacklist => add_to_black_list removeBlacklist => remove_from_black_list initiateRefund => initiate_refund @@ -58,7 +52,7 @@ multiversx_sc_wasm_adapter::endpoints! { setAcceptedCallers => set_accepted_callers removeAcceptedCallers => remove_accepted_callers setBondToken => set_bond_token - setPeriodsBonds => set_lock_periods_with_bonds + addPeriodsBonds => add_lock_periods_with_bonds removePeriodsBonds => remove_lock_periods_with_bonds setMinimumPenalty => set_minimum_penalty setMaximumPenalty => set_maximum_penalty @@ -66,6 +60,13 @@ multiversx_sc_wasm_adapter::endpoints! { setAdministrator => set_administrator getContractState => contract_state getAdministrator => administrator + getAcceptedCallers => accepted_callers + getBondPaymentToken => bond_payment_token + getLockPeriods => lock_periods + getLockPeriodBondAmount => lock_period_bond_amount + getMinimumPenalty => minimum_penalty + getMaximumPenalty => maximum_penalty + getWithdrawPenalty => withdraw_penalty ) }