Assessment reports>DojoSwap>Threat Model>Crate: dojoswap_staking

Crate: dojoswap_staking

The dojoswap_staking contract is responsible for handling requests to bond and unbond stake and for computing and distributing staking rewards.

Its state includes

  • A governance address, which is capable of updating the reward-distribution schedule, updating the governance address, and migrating the staking contract to a new address/version

  • The CW20 token that rewards are denominated in, dojo_token

  • The CW20 token that can be staked, staking_token

  • The distribution schedule, a vector of (u64, u64, Uint128)s (interpreted as start time, end time, and tokens per second)

  • The most recent block time that rewards were computed for, last_distributed

  • The current total amount of bonded stake, total_bond_amount

  • The cumulative sum of the ratio of rewarded tokens per bonded stake, global_reward_index, used for computing individual stakers' rewards on demand

as well as the following, per staking address:

  • A snapshot of global_reward_index as of the last time rewards were computed for this particular staker, reward_index

  • The amount of bonded staking_tokens for this staker, bond_amount

  • The amount of dojo_tokens that have been accumulated for this staker, but not yet withdrawn, pending_reward

Function: instantiate

The instantiate function configures the initial state of the contract. The sender of the instantiate message is stored as the governance address. The dojo_token, staking_token, and initial distribution_schedule are set from the contents of the instantiate message. Additionally, last_distributed is set to the current block time, and total_bond_amount and global_reward_index are initialized to zero.

Function: receive_cw20

The receive_cw20 function is called in response to being sent CW20 tokens. It rejects tokens of any type other than staking_token, and it accepts staking_tokens, passing the sender and amount to bond.

Function: bond

The bond function increases a staker's bonded tokens, initializing the tracking thereof if the staker previously had no stake.

Prior to increasing the staker's bond amount, it updates the global state with compute_reward and the per-staker state with compute_staker_reward so that the rewards up to the current block use the previous bonding amount. It increases the staker's bond amount with increase_bond_amount, then stores the updated per-staker and global state in storage.

Function: compute_reward

The compute_reward function updates last_distributed to the current block time.

If there is a nonzero amount of total bonded stake, it additionally iterates through the schedule entries to calculate a cumulative sum of the rewards accumulated since the last block in which compute_reward was called and adds it (divided by the total amount of stake) to global_reward_index.

Function: compute_staker_reward

The compute_staker_reward function updates the reward allocated to an individual staker. The difference between the current global_reward_index and the staker's reward_index, each multiplied by the staker's amount of bonded stake, is added to the staker's pending_reward, and its reward_index is updated to the current global_reward_index. This calculation can be made more overflow resistant; see Finding ref.

Function: unbond

The unbond function decreases a staker's bonded tokens, sending them to the staker.

It enforces that a staker cannot withdraw more than the amount of tokens they have staked.

Prior to decreasing the staker's bond amount, it updates the global and per-staker state via compute_reward and compute_staker_reward so that the rewards up to the current block use the previous bond amount; it then decreases the staker's bond amount with decrease_bond_amount.

If the staker has no pending reward to withdraw and decreased their bond amount all the way to zero, it removes the staking entry for the staker entirely (which saves storage space); otherwise, it stores the modified state for the staker.

It then updates the global state and sends the unbonded staking tokens to the staker.

Function: withdraw

The withdraw function allows a staker to withdraw their pending rewards.

It updates the global and per-staker state via compute_reward and compute_staker_reward, reads the updated pending_reward, then zeroes it in the to-be-updated state.

If the staker has zero bond amount (i.e., they unbonded previously, potentially in the same block), it removes their staking entry from the state; otherwise, it stores their modified entry in the state.

It then updates the global state and sends the withdrawn Dojo tokens to the staker.

Function: update_config

The update_config function allows the governance address to append entries to the distribution schedule.

It validates that the message sender is the governance address. It validates the schedule by calling the assert_new_schedules function; it then copies the validated schedule and the previous configuration's dojo_token and staking_token to a configuration object that it writes to the state.

Function: assert_new_schedules

The assert_new_schedules function validates that a proposed distribution schedule does not remove any schedule entries that have started nor adds schedule entries that would have retroactively started.

Function: update_gov

The update_gov function allows the governance address to set a replacement governance address.

It validates that the message sender is the current governance address. It does not check that the replacement governance address is a valid address, with similar impact to Finding ref.

Function: migrate_staking

The migrate_staking function allows the governance address to migrate the staking contract to a new, potentially updated instance. It is intended to send the reward tokens that have not yet been distributed to the new contract and leave the staking tokens and pending rewards with the current contract to be withdrawn by stakers.

It validates that the message sender is the governance address. It updates the global reward state via compute_reward (but does not update the per-staker rewards; those will be updated on subsequent calls to unbond/withdraw).

It clears all future (not yet started) schedule entries from the state, then iterates all past and current schedule entries to account for how many rewards have been distributed, but this overestimates schedule entries during which there was zero total bonded stake; see Finding ref.

It writes the updated schedule and global rewards to the state, then sends the reward tokens that have not yet been distributed to the new contract.

Zellic © 2024Back to top ↑