Skip to content

Commit

Permalink
More code comments
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeHartnell committed Jan 4, 2024
1 parent b84dd14 commit d682318
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 10 deletions.
50 changes: 50 additions & 0 deletions contracts/distribution/dao-rewards-distributor/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn instantiate(
) -> Result<Response<Empty>, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

// Intialize the contract owner
cw_ownable::initialize_owner(deps.storage, deps.api, msg.owner.as_deref())?;

let reward_token = match msg.reward_token {
Expand All @@ -52,6 +53,8 @@ pub fn instantiate(
&VotingQueryMsg::TotalPowerAtHeight { height: None },
)?;

// Optional hook caller is allowed to call voting power change hooks.
// If not provided, only the voting power contract is used.
let hook_caller: Option<Addr>;

Check failure on line 58 in contracts/distribution/dao-rewards-distributor/src/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

unneeded late initialization

Check failure on line 58 in contracts/distribution/dao-rewards-distributor/src/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

unneeded late initialization
match msg.hook_caller {
Some(addr) => {
Expand All @@ -61,17 +64,21 @@ pub fn instantiate(
hook_caller = None;
}
}

// Save the contract configuration
let config = Config {
vp_contract: deps.api.addr_validate(&msg.vp_contract)?,
hook_caller,
reward_token,
};
CONFIG.save(deps.storage, &config)?;

// Reward duration must be greater than 0
if msg.reward_duration == 0 {
return Err(ContractError::ZeroRewardDuration {});
}

// Initialize the reward config
let reward_config = RewardConfig {
period_finish: 0,
reward_rate: Uint128::zero(),
Expand Down Expand Up @@ -124,9 +131,12 @@ pub fn execute_receive(
let msg: ReceiveMsg = from_json(&wrapper.msg)?;
let config = CONFIG.load(deps.storage)?;
let sender = deps.api.addr_validate(&wrapper.sender)?;

// This method is only to be used by cw20 tokens
if config.reward_token != Denom::Cw20(info.sender) {
return Err(InvalidCw20 {});
};

match msg {
ReceiveMsg::Fund {} => execute_fund(deps, env, sender, wrapper.amount),
}
Expand All @@ -141,6 +151,7 @@ pub fn execute_fund_native(

match config.reward_token {
Denom::Native(denom) => {
// Check that the correct denom has been sent
let amount = cw_utils::must_pay(&info, &denom).map_err(|_| InvalidFunds {})?;
execute_fund(deps, env, info.sender, amount)
}
Expand All @@ -154,13 +165,18 @@ pub fn execute_fund(
sender: Addr,
amount: Uint128,
) -> Result<Response<Empty>, ContractError> {
// Ensure that the sender is the owner
cw_ownable::assert_owner(deps.storage, &sender)?;

update_rewards(&mut deps, &env, &sender)?;

let reward_config = REWARD_CONFIG.load(deps.storage)?;

// Ensure that the current reward period has ended
if reward_config.period_finish > env.block.height {
return Err(RewardPeriodNotFinished {});
}

let new_reward_config = RewardConfig {
period_finish: env.block.height + reward_config.reward_duration,
reward_rate: amount
Expand Down Expand Up @@ -256,15 +272,25 @@ pub fn execute_claim(
env: Env,
info: MessageInfo,
) -> Result<Response<Empty>, ContractError> {
// Update the rewards information for the sender.
update_rewards(&mut deps, &env, &info.sender)?;

// Get the pending rewards for the sender.
let rewards = PENDING_REWARDS
.load(deps.storage, info.sender.clone())
.map_err(|_| NoRewardsClaimable {})?;

// If there are no rewards to claim, return an error.
if rewards == Uint128::zero() {
return Err(ContractError::NoRewardsClaimable {});
}

// Save the pending rewards for the sender, it will now be zero.
PENDING_REWARDS.save(deps.storage, info.sender.clone(), &Uint128::zero())?;

let config = CONFIG.load(deps.storage)?;

// Transfer the rewards to the sender.
let transfer_msg = get_transfer_msg(info.sender, rewards, config.reward_token)?;
Ok(Response::new()
.add_message(transfer_msg)
Expand All @@ -278,6 +304,9 @@ pub fn execute_update_owner(
env: Env,
action: cw_ownable::Action,
) -> Result<Response, ContractError> {
// Update the current contract owner.
// Note, this is a two step process, the new owner must accept this ownership transfer.
// First the owner specifies the new owner, then the new owner must accept.
let ownership = cw_ownable::update_ownership(deps, &env.block, &info.sender, action)?;
Ok(Response::default().add_attributes(ownership.into_attributes()))
}
Expand Down Expand Up @@ -330,9 +359,11 @@ pub fn get_transfer_msg(recipient: Addr, amount: Uint128, denom: Denom) -> StdRe
pub fn update_rewards(deps: &mut DepsMut, env: &Env, addr: &Addr) -> StdResult<()> {
let config = CONFIG.load(deps.storage)?;

// Reward per token represents the amount of rewards per unit of voting power.
let reward_per_token = get_reward_per_token(deps.as_ref(), env, &config.vp_contract)?;
REWARD_PER_TOKEN.save(deps.storage, &reward_per_token)?;

// The amount of rewards earned up until this point.
let earned_rewards = get_rewards_earned(
deps.as_ref(),
env,
Expand All @@ -341,21 +372,31 @@ pub fn update_rewards(deps: &mut DepsMut, env: &Env, addr: &Addr) -> StdResult<(
&config.vp_contract,
)?;

// Update the users pending rewards
PENDING_REWARDS.update::<_, StdError>(deps.storage, addr.clone(), |r| {
Ok(r.unwrap_or_default() + earned_rewards)
})?;

// Update the users latest reward per token value.
USER_REWARD_PER_TOKEN.save(deps.storage, addr.clone(), &reward_per_token)?;

// Update the last time rewards were updated.
let last_time_reward_applicable = get_last_time_reward_applicable(deps.as_ref(), env)?;
LAST_UPDATE_BLOCK.save(deps.storage, &last_time_reward_applicable)?;
Ok(())
}

pub fn get_reward_per_token(deps: Deps, env: &Env, vp_contract: &Addr) -> StdResult<Uint256> {
let reward_config = REWARD_CONFIG.load(deps.storage)?;

// Get the total voting power at this block height.
let total_power = get_total_voting_power(deps, vp_contract)?;

// Get information on the last time rewards were updated.
let last_time_reward_applicable = get_last_time_reward_applicable(deps, env)?;
let last_update_block = LAST_UPDATE_BLOCK.load(deps.storage).unwrap_or_default();

// Get the amount of rewards per unit of voting power.
let prev_reward_per_token = REWARD_PER_TOKEN.load(deps.storage).unwrap_or_default();
let additional_reward_per_token = if total_power == Uint128::zero() {
Uint256::zero()
Expand All @@ -382,11 +423,17 @@ pub fn get_rewards_earned(
reward_per_token: Uint256,
vp_contract: &Addr,
) -> StdResult<Uint128> {
// Get the users voting power at the current height.
let voting_power = Uint256::from(get_voting_power(deps, vp_contract, addr)?);
// Load the users latest reward per token value.
let user_reward_per_token = USER_REWARD_PER_TOKEN
.load(deps.storage, addr.clone())
.unwrap_or_default();
// Calculate the difference between the current reward per token value and the users latest
let reward_factor = reward_per_token.checked_sub(user_reward_per_token)?;

// Calculate the amount of rewards earned.
// voting_power * reward_factor / scale_factor
Ok(voting_power
.checked_mul(reward_factor)?
.checked_div(scale_factor())?
Expand All @@ -395,6 +442,8 @@ pub fn get_rewards_earned(

fn get_last_time_reward_applicable(deps: Deps, env: &Env) -> StdResult<u64> {
let reward_config = REWARD_CONFIG.load(deps.storage)?;

// Take the minimum of the current block height and the period finish height.
Ok(min(env.block.height, reward_config.period_finish))
}

Expand All @@ -419,6 +468,7 @@ pub fn execute_update_reward_duration(
info: MessageInfo,
new_duration: u64,
) -> Result<Response<Empty>, ContractError> {
// Ensure the sender is the owner.
cw_ownable::assert_owner(deps.storage, &info.sender)?;

let mut reward_config = REWARD_CONFIG.load(deps.storage)?;
Expand Down
19 changes: 13 additions & 6 deletions contracts/distribution/dao-rewards-distributor/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,31 @@ use thiserror::Error;
pub enum ContractError {
#[error(transparent)]
Std(#[from] StdError),

#[error(transparent)]
Ownable(#[from] cw_ownable::OwnershipError),

#[error(transparent)]
Cw20Error(#[from] cw20_base::ContractError),

#[error("Invalid Cw20")]
InvalidCw20 {},

#[error("Invalid funds")]
InvalidFunds {},

#[error("Staking change hook sender is not staking contract")]
InvalidHookSender {},

#[error("No rewards claimable")]
NoRewardsClaimable {},

#[error("Reward period not finished")]
RewardPeriodNotFinished {},
#[error("Invalid funds")]
InvalidFunds {},
#[error("Invalid Cw20")]
InvalidCw20 {},

#[error("Reward rate less then one per block")]
RewardRateLessThenOnePerBlock {},

#[error("Reward duration can not be zero")]
ZeroRewardDuration {},
#[error("can not migrate. current version is up to date")]
AlreadyMigrated {},
}
12 changes: 8 additions & 4 deletions contracts/distribution/dao-rewards-distributor/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Uint128, Uint256};
use cw20::Denom;

use cw_storage_plus::{Item, Map};

#[cw_serde]
pub struct Config {
/// The address of a DAO DAO voting power module contract.
pub vp_contract: Addr,
/// An optional contract that is allowed to call the StakeChangedHook in
/// place of the voting power contract.
pub hook_caller: Option<Addr>,
/// The Denom in which rewards are paid out.
pub reward_token: Denom,
}

// `"config"` key stores v1 configuration.
pub const CONFIG: Item<Config> = Item::new("config_v2");
pub const CONFIG: Item<Config> = Item::new("config");

#[cw_serde]
pub struct RewardConfig {
Expand All @@ -26,6 +27,9 @@ pub const REWARD_PER_TOKEN: Item<Uint256> = Item::new("reward_per_token");

pub const LAST_UPDATE_BLOCK: Item<u64> = Item::new("last_update_block");

/// A map of user addresses to their pending rewards.
pub const PENDING_REWARDS: Map<Addr, Uint128> = Map::new("pending_rewards");

/// A map of user addresses to their rewards per token. In other words, it is the
/// reward per share of voting power that the user has.
pub const USER_REWARD_PER_TOKEN: Map<Addr, Uint256> = Map::new("user_reward_per_token");

0 comments on commit d682318

Please sign in to comment.