multiversx_sc_modules/
staking.rs1multiversx_sc::imports!();
2multiversx_sc::derive_imports!();
3
4#[derive(TopEncode, TopDecode)]
5pub struct TokenAmountPair<M: ManagedTypeApi> {
6 pub token_id: TokenIdentifier<M>,
7 pub amount: BigUint<M>,
8}
9
10static NOT_ENOUGH_STAKE_ERR_MSG: &[u8] = b"Not enough stake";
11static MINIMUM_BOARD_MEMBERS: usize = 2;
12static MAXIMUM_BOARD_MEMBERS: usize = 100;
13
14#[multiversx_sc::module]
15pub trait StakingModule {
16 fn init_staking_module(
17 &self,
18 staking_token: &EgldOrEsdtTokenIdentifier,
19 staking_amount: &BigUint,
20 slash_amount: &BigUint,
21 slash_quorum: usize,
22 user_whitelist: &ManagedVec<ManagedAddress>,
23 ) {
24 for user in user_whitelist {
25 let _ = self.user_whitelist().insert(user.clone());
26 }
27
28 let nr_board_members = self.user_whitelist().len();
29 require!(nr_board_members > 0, "No board members");
30 require!(
31 nr_board_members <= MAXIMUM_BOARD_MEMBERS,
32 "Too many board members"
33 );
34 require!(
35 slash_quorum < nr_board_members,
36 "Quorum higher than total possible board members"
37 );
38 require!(
39 slash_quorum >= MINIMUM_BOARD_MEMBERS,
40 "Quorum minimum board members requirement not met"
41 );
42 require!(
43 staking_amount > &0 && slash_amount > &0,
44 "Staking and slash amount cannot be 0"
45 );
46 require!(
47 slash_amount <= staking_amount,
48 "Slash amount cannot be higher than required stake"
49 );
50
51 self.staking_token().set(staking_token);
52 self.required_stake_amount().set(staking_amount);
53 self.slash_amount().set(slash_amount);
54 self.slash_quorum().set(slash_quorum);
55 }
56
57 #[payable("*")]
58 #[endpoint]
59 fn stake(&self) {
60 let (payment_token, payment_amount) = self.call_value().egld_or_single_fungible_esdt();
61 let staking_token = self.staking_token().get();
62 require!(payment_token == staking_token, "Invalid payment token");
63
64 let caller = self.blockchain().get_caller();
65 require!(
66 self.user_whitelist().contains(&caller),
67 "Only whitelisted members can stake"
68 );
69
70 self.staked_amount(&caller)
71 .update(|amt| *amt += payment_amount);
72 }
73
74 #[endpoint]
75 fn unstake(&self, unstake_amount: BigUint) {
76 let caller = self.blockchain().get_caller();
77 let staked_amount_mapper = self.staked_amount(&caller);
78 let staked_amount = staked_amount_mapper.get();
79 require!(unstake_amount <= staked_amount, NOT_ENOUGH_STAKE_ERR_MSG);
80
81 let leftover_amount = &staked_amount - &unstake_amount;
82 let required_stake_amount = self.required_stake_amount().get();
83 if self.user_whitelist().contains(&caller) {
84 require!(
85 leftover_amount >= required_stake_amount,
86 NOT_ENOUGH_STAKE_ERR_MSG
87 );
88 }
89
90 staked_amount_mapper.set(&leftover_amount);
91
92 let staking_token = self.staking_token().get();
93 self.tx()
94 .to(caller)
95 .egld_or_single_esdt(&staking_token, 0, &unstake_amount)
96 .transfer();
97 }
98
99 #[endpoint(voteSlashMember)]
100 fn vote_slash_member(&self, member_to_slash: ManagedAddress) {
101 require!(
102 self.is_staked_board_member(&member_to_slash),
103 "Voted user is not a staked board member"
104 );
105
106 let caller = self.blockchain().get_caller();
107 require!(
108 self.is_staked_board_member(&caller),
109 NOT_ENOUGH_STAKE_ERR_MSG
110 );
111
112 let _ = self
113 .slashing_proposal_voters(&member_to_slash)
114 .insert(caller);
115 }
116
117 #[endpoint(cancelVoteSlashMember)]
118 fn cancel_vote_slash_member(&self, member_to_slash: ManagedAddress) {
119 let caller = self.blockchain().get_caller();
120
121 let _ = self
122 .slashing_proposal_voters(&member_to_slash)
123 .swap_remove(&caller);
124 }
125
126 #[endpoint(slashMember)]
127 fn slash_member(&self, member_to_slash: ManagedAddress) {
128 let quorum = self.slash_quorum().get();
129 let mut slashing_voters_mapper = self.slashing_proposal_voters(&member_to_slash);
130 require!(slashing_voters_mapper.len() >= quorum, "Quorum not reached");
131
132 let slash_amount = self.slash_amount().get();
133 self.staked_amount(&member_to_slash)
134 .update(|amt| *amt -= &slash_amount);
135 self.total_slashed_amount()
136 .update(|total| *total += slash_amount);
137
138 slashing_voters_mapper.clear();
139 }
140
141 fn is_staked_board_member(&self, user: &ManagedAddress) -> bool {
142 let required_stake = self.required_stake_amount().get();
143 let user_stake = self.staked_amount(user).get();
144
145 self.user_whitelist().contains(user) && user_stake >= required_stake
146 }
147
148 #[inline]
149 fn add_board_member(&self, user: ManagedAddress) {
150 let _ = self.user_whitelist().insert(user);
151 let nr_board_members = self.user_whitelist().len();
152 require!(
153 nr_board_members <= MAXIMUM_BOARD_MEMBERS,
154 "Too many board members"
155 );
156 }
157
158 fn remove_board_member(&self, user: &ManagedAddress) {
159 let mut whitelist_mapper = self.user_whitelist();
160
161 let slash_quorum = self.slash_quorum().get();
162
163 let was_whitelisted = whitelist_mapper.swap_remove(user);
164 let nr_board_members = whitelist_mapper.len();
165 require!(
166 nr_board_members > slash_quorum,
167 "remaining number of board members must be greater than the slash quorum"
168 );
169 if !was_whitelisted {
170 return;
171 }
172
173 for board_member in whitelist_mapper.iter() {
175 let _ = self
176 .slashing_proposal_voters(&board_member)
177 .swap_remove(user);
178 }
179 self.slashing_proposal_voters(user).clear();
180 }
181
182 #[storage_mapper("staking_module:stakingToken")]
183 fn staking_token(&self) -> SingleValueMapper<EgldOrEsdtTokenIdentifier>;
184
185 #[storage_mapper("staking_module:requiredStakeAmount")]
186 fn required_stake_amount(&self) -> SingleValueMapper<BigUint>;
187
188 #[storage_mapper("staking_module:userWhitelist")]
189 fn user_whitelist(&self) -> UnorderedSetMapper<ManagedAddress>;
190
191 #[storage_mapper("staking_module:stakedAmount")]
192 fn staked_amount(&self, user: &ManagedAddress) -> SingleValueMapper<BigUint>;
193
194 #[storage_mapper("staking_module:slashingProposalVoters")]
195 fn slashing_proposal_voters(
196 &self,
197 slash_address: &ManagedAddress,
198 ) -> UnorderedSetMapper<ManagedAddress>;
199
200 #[storage_mapper("staking_module:slashQuorum")]
201 fn slash_quorum(&self) -> SingleValueMapper<usize>;
202
203 #[storage_mapper("staking_module:slashAmount")]
204 fn slash_amount(&self) -> SingleValueMapper<BigUint>;
205
206 #[storage_mapper("staking_module:totalSlashedAmount")]
207 fn total_slashed_amount(&self) -> SingleValueMapper<BigUint>;
208}