Skip to content

Commit 8099e49

Browse files
authored
Merge pull request #3 from Sui-Volo/audit/ottersec
Audit/ottersec
2 parents 5f8aef2 + 61340f1 commit 8099e49

6 files changed

Lines changed: 512 additions & 19 deletions

File tree

liquid_staking/sources/cert.move

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ module liquid_staking::cert {
2424

2525
// Track the current version of the module, iterate each upgrade
2626
const VERSION: u64 = 1;
27+
const DECIMALS: u8 = 9;
2728

2829
/* Constants */
2930

@@ -49,7 +50,7 @@ module liquid_staking::cert {
4950
fun init(witness: CERT, ctx: &mut TxContext) {
5051
// create coin with metadata
5152
let (treasury_cap, metadata) = coin::create_currency<CERT>(
52-
witness, 9, b"voloSUI", b"Volo Staked SUI",
53+
witness, DECIMALS, b"voloSUI", b"Volo Staked SUI",
5354
b"Volo's SUI staking solution provides the best user experience and highest level of decentralization, security, combined with an attractive reward mechanism and instant staking liquidity through a bond-like synthetic token called voloSUI.",
5455
option::some<Url>(url::new_unsafe_from_bytes(b"https://volo.fi/voloSUI.png")),
5556
ctx
@@ -159,4 +160,4 @@ module liquid_staking::cert {
159160
public fun test_init(ctx: &mut TxContext) {
160161
init(CERT {}, ctx)
161162
}
162-
}
163+
}

liquid_staking/sources/native_pool.move

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module liquid_staking::native_pool {
4646
const E_REWARD_NOT_IN_THRESHOLD: u64 = 107;
4747
const E_BAD_SHARES: u64 = 108;
4848
const E_TOO_BIG_PERCENT: u64 = 109;
49+
const E_NOT_ENOUGH_BALANCE: u64 = 110;
4950

5051
/* Events */
5152
struct StakedEvent has copy, drop {
@@ -188,16 +189,18 @@ module liquid_staking::native_pool {
188189
*table::borrow(&self.total_staked, self.staked_update_epoch) + pending
189190
}
190191

191-
// returns total staked for active epoch
192+
/// returns total staked for active epoch
193+
/// active stake can be unstaked
192194
public fun get_total_active_stake(self: &NativePool, ctx: &mut TxContext): u64 {
193195
let last_active_epoch = self.staked_update_epoch;
194196
let current_epoch = tx_context::epoch(ctx);
195197

196198
if (last_active_epoch > current_epoch) {
197199
last_active_epoch = current_epoch;
198200
};
199-
200-
*table::borrow(&self.total_staked, last_active_epoch)
201+
202+
let pending = get_pending(self);
203+
*table::borrow(&self.total_staked, last_active_epoch) + pending
201204
}
202205

203206
public fun get_total_rewards(self: &NativePool): u64 {
@@ -235,7 +238,7 @@ module liquid_staking::native_pool {
235238
// we can allow to stake less than 1 SUI
236239
public entry fun change_min_stake(self: &mut NativePool, _owner_cap: &OwnerCap, value: u64) {
237240
assert_version(self);
238-
assert!(value > 0, E_LIMIT_TOO_LOW);
241+
assert!(value > 1000, E_LIMIT_TOO_LOW);
239242

240243
event::emit(MinStakeChangedEvent {
241244
prev_value: self.min_stake,
@@ -344,7 +347,11 @@ module liquid_staking::native_pool {
344347
}
345348

346349
fun sub_rewards_unsafe(self: &mut NativePool, value: u64) {
347-
self.total_rewards = self.total_rewards - value;
350+
if (value > self.total_rewards) {
351+
self.total_rewards = 0;
352+
} else {
353+
self.total_rewards = self.total_rewards - value;
354+
};
348355
event::emit(RewardsUpdated {
349356
value: self.total_rewards,
350357
});
@@ -474,6 +481,11 @@ module liquid_staking::native_pool {
474481
fun stake_pool(self: &mut NativePool, wrapper: &mut SuiSystemState, ctx: &mut TxContext) {
475482
let pending_value = coin::value(&self.pending);
476483

484+
let tickets_supply = unstake_ticket::get_total_supply(&self.ticket_metadata);
485+
if (pending_value < tickets_supply) {
486+
return
487+
};
488+
pending_value = pending_value - tickets_supply;
477489
if (pending_value < ONE_SUI) {
478490
return
479491
};
@@ -581,10 +593,12 @@ module liquid_staking::native_pool {
581593
ctx: &mut TxContext
582594
): Coin<SUI> {
583595

596+
assert!(vector::length(&validators) > 0, E_NOTHING_TO_UNSTAKE);
584597
let i = vector::length(&validators) - 1;
585598

586-
let total_removed_balance = balance::zero<SUI>();
587-
let total_removed_value = 0;
599+
let total_removed_value = coin::value(&self.pending);
600+
let total_removed_balance = coin::into_balance(coin::split(&mut self.pending, total_removed_value, ctx));
601+
588602
let collectable_reward = 0;
589603

590604
while (total_removed_value < amount_to_unstake) {
@@ -624,6 +638,7 @@ module liquid_staking::native_pool {
624638
};
625639

626640
// extract our fees
641+
assert!(balance::value(&total_removed_balance) >= fee + collectable_reward, E_NOT_ENOUGH_BALANCE);
627642
let fee_balance = balance::split(&mut total_removed_balance, fee + collectable_reward);
628643
coin::join(&mut self.collectable_fee, coin::from_balance(fee_balance, ctx));
629644

@@ -651,6 +666,9 @@ module liquid_staking::native_pool {
651666

652667
// unstake validators with zero priority and stake to top validator
653668
public entry fun rebalance(self: &mut NativePool, wrapper: &mut SuiSystemState, ctx: &mut TxContext) {
669+
assert_version(self);
670+
when_not_paused(self);
671+
654672
// calculate total stake of validators
655673
let validators = validator_set::get_bad_validators(&self.validator_set);
656674
let unstaked_sui = unstake_amount_from_validators(self, wrapper, MAX_UINT_64, 0, validators, ctx);

liquid_staking/sources/validator_set.move

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ module liquid_staking::validator_set {
5454
vaults: Table<address, Vault>, // validator => Vault
5555
validators: VecMap<address, u64>,
5656
sorted_validators: vector<address>,
57+
is_sorted: bool,
5758
}
5859

5960
// called only once while native_pool init
@@ -63,6 +64,7 @@ module liquid_staking::validator_set {
6364
vaults: table::new<address, Vault>(ctx),
6465
validators: vec_map::empty<address, u64>(),
6566
sorted_validators: vector::empty<address>(),
67+
is_sorted: false,
6668
}
6769
}
6870

@@ -142,9 +144,11 @@ module liquid_staking::validator_set {
142144
event::emit(ValidatorsSorted{
143145
validators: sorted,
144146
});
147+
self.is_sorted = true;
145148
self.sorted_validators = sorted;
146149
}
147150

151+
// @dev add or update validator and priority
148152
public(friend) fun update_validators(self: &mut ValidatorSet, validators: vector<address>, priorities: vector<u64>) {
149153
let length = vector::length(&validators);
150154
assert!(length < MAX_VLDRS_UPDATE, E_TOO_MANY_VLDRS);
@@ -160,6 +164,10 @@ module liquid_staking::validator_set {
160164
i = i + 1;
161165
};
162166

167+
if (length > 0) {
168+
self.is_sorted = false;
169+
};
170+
163171
assert!(vec_map::size(&self.validators) < MAX_VLDRS_UPDATE, E_TOO_MANY_VLDRS);
164172
}
165173

@@ -227,7 +235,10 @@ module liquid_staking::validator_set {
227235

228236
let staked_sui_to_withdraw;
229237
let rest_requested_amount = requested_amount - balance::value(&total_withdrawn);
230-
if (rest_requested_amount >= MIST_PER_SUI && principal_value > rest_requested_amount && principal_value - rest_requested_amount >= MIST_PER_SUI) {
238+
if (rest_requested_amount < MIST_PER_SUI) {
239+
rest_requested_amount = MIST_PER_SUI
240+
};
241+
if (principal_value > rest_requested_amount && principal_value - rest_requested_amount >= MIST_PER_SUI) {
231242
// it's possible to split StakedSui
232243
staked_sui_to_withdraw = staking_pool::split(staked_sui_mut_ref, rest_requested_amount, ctx);
233244
principal_value = rest_requested_amount;

liquid_staking/tests/native_pool_tests.move

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ module liquid_staking::native_pool_tests {
174174
// Stake SUI, epoch = 1
175175
next_tx(&mut scenario, SENDER);
176176
{
177-
let sui = coin::mint_for_testing<SUI>(MIST_PER_SUI, ctx(&mut scenario)); // 1 SUI
177+
let sui = coin::mint_for_testing<SUI>(MIST_PER_SUI * 2, ctx(&mut scenario)); // 1 SUI
178178
let pool = test_scenario::take_shared<NativePool>(&scenario);
179179
let metadata = test_scenario::take_shared<Metadata<CERT>>(&scenario);
180180
let system_state = test_scenario::take_shared<SuiSystemState>(&scenario);
@@ -193,16 +193,16 @@ module liquid_staking::native_pool_tests {
193193
let pool = test_scenario::take_shared<NativePool>(&scenario);
194194

195195
let total_staked = native_pool::get_total_staked(&pool);
196-
test_utils::assert_eq(total_staked, MIST_PER_SUI * 101);
196+
test_utils::assert_eq(total_staked, MIST_PER_SUI * 102);
197197
let total_active_stake = native_pool::get_total_active_stake(&pool, ctx(&mut scenario));
198-
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 100);
198+
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 101);
199199

200200
advance_epoch(&mut scenario);
201201

202202
let total_staked = native_pool::get_total_staked(&pool);
203-
test_utils::assert_eq(total_staked, MIST_PER_SUI * 101);
203+
test_utils::assert_eq(total_staked, MIST_PER_SUI * 102);
204204
let total_active_stake = native_pool::get_total_active_stake(&pool, ctx(&mut scenario));
205-
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 101);
205+
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 102);
206206

207207
test_scenario::return_shared(pool);
208208
};
@@ -223,9 +223,9 @@ module liquid_staking::native_pool_tests {
223223
coin::burn_for_testing(sui);
224224

225225
let total_staked = native_pool::get_total_staked(&pool);
226-
test_utils::assert_eq(total_staked, MIST_PER_SUI * 100);
226+
test_utils::assert_eq(total_staked, MIST_PER_SUI * 101);
227227
let total_active_stake = native_pool::get_total_active_stake(&pool, ctx);
228-
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 100);
228+
test_utils::assert_eq(total_active_stake, MIST_PER_SUI * 101);
229229

230230
let ticket_supply = native_pool::get_ticket_supply(&pool);
231231
test_utils::assert_eq(ticket_supply, MIST_PER_SUI);
@@ -436,9 +436,9 @@ module liquid_staking::native_pool_tests {
436436
let owner_cap = test_scenario::take_from_sender<OwnerCap>(&scenario);
437437
let pool = test_scenario::take_shared<NativePool>(&scenario);
438438

439-
native_pool::change_min_stake(&mut pool, &owner_cap, 2);
439+
native_pool::change_min_stake(&mut pool, &owner_cap, 2002);
440440
let min_stake = native_pool::get_min_stake(&pool);
441-
test_utils::assert_eq(min_stake, 2);
441+
test_utils::assert_eq(min_stake, 2002);
442442

443443
test_scenario::return_to_address<OwnerCap>(SENDER, owner_cap);
444444
test_scenario::return_shared(pool);

0 commit comments

Comments
 (0)