Pool
Stores all relevant contract parameters.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
pub this: CanonicalAddr,
pub owner: CanonicalAddr,
pub beneficiary: CanonicalAddr,
pub moneymarket: CanonicalAddr,
pub atoken: CanonicalAddr,
pub stable_denom: String,
pub dp_token: CanonicalAddr,
}
this
: the address of this pool contract.owner
: the owner address of this pool contract.beneficiary
: the address of which all interest collected from principal deposits are sent to. could be a single address, or a contract address that oversees additional distribution logic.moneymarket
: the address of the Anchor money market contract to accept deposits.atoken
: the address of the aUST token contract.stable_denom
: base Terra stablecoin denomination of this pool to be deposited to Anchor, i.e. uusd
.dp_token
: the address of the DP token contract minted by this pool.
Defines logic entry points for
InitMsg
, HandleMsg
and QueryMsg
calls.pub fn init<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: InitMsg,
) -> StdResult<InitResponse> {
let sender = env.message.sender;
let raw_sender = deps.api.canonical_address(&sender)?;
let mut config = config::Config {
this: deps.api.canonical_address(&env.contract.address)?,
owner: raw_sender,
beneficiary: deps.api.canonical_address(&msg.beneficiary)?,
fee_collector: deps.api.canonical_address(&msg.fee_collector)?,
exchange_rate_feeder: deps.api.canonical_address(&msg.exchange_rate_feeder)?,
moneymarket: deps.api.canonical_address(&msg.moneymarket)?,
stable_denom: String::default(),
atoken: CanonicalAddr::default(),
dp_token: CanonicalAddr::default(),
};
let market_config = querier::anchor::config(deps, &config.moneymarket)?;
config.stable_denom = market_config.stable_denom.clone();
config.atoken = deps.api.canonical_address(&market_config.aterra_contract)?;
config::store(&mut deps.storage, &config)?;
Ok(InitResponse {
messages: vec![CosmosMsg::Wasm(WasmMsg::Instantiate {
code_id: msg.dp_code_id,
send: vec![],
label: None,
msg: to_binary(&Cw20InitMsg {
name: format!("Deposit Token - {}", msg.pool_name),
symbol: "PylonDP".to_string(),
decimals: 6u8,
initial_balances: vec![],
mint: Some(MinterResponse {
minter: env.contract.address.clone(),
cap: None,
}),
init_hook: Some(Cw20InitHook {
contract_addr: env.contract.address,
msg: to_binary(&HandleMsg::RegisterDPToken {})?,
}),
})?,
})],
log: vec![],
})
}
Initializes a new pool contract. Updates data storage as defined with
config.rs
, and initializes a new DP token contract linked to this pool contract.pub fn handle<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
msg: HandleMsg,
) -> StdResult<HandleResponse> {
match msg {
HandleMsg::Receive(msg) => CoreHandler::receive(deps, env, msg),
HandleMsg::Deposit {} => CoreHandler::deposit(deps, env),
HandleMsg::ClaimReward {} => CoreHandler::claim_reward(deps, env),
HandleMsg::RegisterDPToken {} => CoreHandler::register_dp_token(deps, env),
}
}
Defines
HandleMsg
endpoints. Calls the following functions per HandleMsg
:Receive(msg)
: callsreceive(deps, env, msg)
defined underhandler_exec.rs
.Deposit {}
: callsdeposit(deps, env)
defined underhandler_exec.rs
.ClaimReward {}
: callsclaim_reward(deps, env)
defined underhandler_exec.rs
.- [INTERNAL]
RegisterDPToken {}
: callsregister_dp_token(deps, env)
defined underhandler_exec.rs
.
pub fn query<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
msg: QueryMsg,
) -> StdResult<Binary> {
match msg {
QueryMsg::DepositAmountOf { owner } => QueryHandler::deposit_amount(deps, owner), // dp_token.balanceOf(msg.sender)
QueryMsg::TotalDepositAmount {} => QueryHandler::total_deposit_amount(deps), // dp_token.totalSupply()
QueryMsg::Config {} => QueryHandler::config(deps), // config
QueryMsg::ClaimableReward {} => QueryHandler::claimable_reward(deps), // config.strategy.reward()
}
}
Defines
QueryMsg
endpoints. Calls the following functions per QueryMsg
:DepositAmountOf { owner }
: callsdeposit_amount(deps, owner)
defined underhandler_query.rs
; returns the current DP token balance of the sender of this message.TotalDepositAmount {}
: callstotal_deposit_amount(deps)
defined underhandler_query.rs
; returns the total DP token balanced linked to this pool contract.Config {}
: callsconfig(deps)
defined underhandler/query.rs
; returns the following:beneficiary
: returns the current beneficiary address set for this pool contract.moneymarket
: returns the Anchor money market contract set for this pool contract to call fordeposit_stable
andredeem_stable
messages. For further information regarding logic of the Anchor money market, please refer to Anchor Protocol's official documentation.stable_denom
: returns the current Terra stablecoin denomination that the underlying Anchor money market contract accepts.anchor_token
: returns the aToken contract address that the underlying Anchor money market contract returns on deposits.dp_token
: returns the DP token contract address minted by this pool contract on new deposits.
GetClaimableReward {}
: callsclaimable_reward(deps)
defined underhandler_query.rs
; returns all claimable UST rewards for all UST deposits held by this pool.
Defines core contract execution logic. Functions are as follows:
receive
: defines a CW-20 token contract receive hook, which is executed when a Pylon DP token is sent to this Pylon pool.deposit
: deposits UST to this Pylon pool, deposits them to Anchor, and mints Pylon DP tokens back to the depositor.redeem
: consecutively called on a CW-20receive
call. Burns received DP tokens, retrieves equivalent amounts of UST from Anchor, and returns principal UST deposits excluding any interest generated.claim_reward
: claims any remaining interest remaining in the pool, and sends them over to the beneficiary address. Interest is calculated as(total_aust_amount * exchange_rate) - (total_dp_balance)
register_dp_token
: registers a DP token contract controlled by this Pylon deposit pool for new DP token mints/burns on deposits/redemptions.
pub fn receive<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
env: Env,
cw20_msg: Cw20ReceiveMsg,
) -> StdResult<HandleResponse> {
let sender = env.message.sender.clone();
if let Some(msg) = cw20_msg.msg {
match from_binary(&msg)? {
Cw20HookMsg::Redeem {} => {
// only asset contract can execute this message
let config: config::Config = config::read(&deps.storage)?;
if deps.api.canonical_address(&sender)? != config.dp_token {
return Err(StdError::unauthorized());
}
redeem(deps, env, cw20_msg.sender, cw20_msg.amount)
}
}
} else {
Err(StdError::generic_err(
"Invalid request: \"redeem\" message not included in request",
))
}
}
If a CW-20 transfer to this contract includes a
Redeem {}
Cw20HookMsg
:- checks if the sender of this
HookMsg
is the registered DP token address (i.e. not any other token contract address being sent to this pool) - returnunauthorized()
error if not, else continue - if passed, call
redeem(deps, _env, cw20_msg.sender, cw20_msg.amount)
If a
Redeem {}
message is not included with this function call, return an invalid_request
error.pub fn deposit<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
env: Env,
) -> StdResult<HandleResponse> {
let config = config::read(&deps.storage)?;
// check deposit
let received: Uint256 = env
.message
.sent_funds
.iter()
.find(|c| c.denom == config.stable_denom)
.map(|c| Uint256::from(c.amount))
.unwrap_or_else(Uint256::zero);
if received.is_zero() {
return Err(StdError::generic_err(format!(
"Pool: insufficient token amount {}",
config.stable_denom,
)));
}
let deposit_amount = deduct_tax(
deps,
Coin {
denom: config.stable_denom.clone(),
amount: received.into(),
},
)?
.amount;
Ok(HandleResponse {
messages: [
querier::feeder::update_msg(deps, &config.exchange_rate_feeder, &config.dp_token)?,
querier::anchor::deposit_stable_msg(
deps,
&config.moneymarket,
&config.stable_denom,
deposit_amount,
)?,
vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.human_address(&config.dp_token)?,
msg: to_binary(&Cw20HandleMsg::Mint {
recipient: env.message.sender.clone(),
amount: deposit_amount,
})?,
send: vec![],
})],
]
.concat(),
log: vec![
log("action", "deposit"),
log("sender", env.message.sender),
log("amount", deposit_amount.to_string()),
],
data: None,
})
}
Sending native Terra stablecoins (UST) with this
MsgExecuteContract
call:- Check how much deposits have been made to this pool contract for
stable_denom
.- If there are no deposits, return a
generic_err
.
- Issue a
HandleResponse
issuing the following messages:- call
deposit_stable
from the Anchor money market contract fordeposit_amount
units ofstable_denom
(e.g. 100 UST). aUST will be returned from the Anchor money market contract to this pool contract. - issue another
MsgExecuteContract
call that mintsdeposit_amount
units of Pylon DP tokens for this particular pool to the caller.
pub fn redeem<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
env: Env,
sender: HumanAddr,
amount: Uint128,
) -> StdResult<HandleResponse> {
let config = config::read(&deps.storage)?;
let (market_redeem_amount, pool_redeem_amount, _) =
querier::pool::calculate_return_amount(deps, &config, Uint256::from(amount))?;
Ok(HandleResponse {
messages: [
querier::feeder::update_msg(deps, &config.exchange_rate_feeder, &config.dp_token)?,
vec![CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: deps.api.human_address(&config.dp_token)?,
msg: to_binary(&Cw20HandleMsg::Burn { amount })?,
send: vec![],
})],
querier::anchor::redeem_stable_msg(
deps,
&config.moneymarket,
&config.atoken,
market_redeem_amount.into(),
)?,
vec![CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address,
to_address: sender,
amount: vec![pool_redeem_amount.clone()],
})],
]
.concat(),
log: vec![
log("action", "redeem"),
log("sender", env.message.sender),
log("amount", pool_redeem_amount.amount),
],
data: None,
})
}
When a
Redeem {}
Cw20HookMsg
is called:- Query the current
epoch_state
from the Anchor money market. - Calculate how much aUST needs to be redeemed back to UST to maintain principle:
dp_token_balance / epoch_state.exchange_rate
- Calculate how much Terra tax is charged during redemptions. As tax is charged for every Terra stablecoin
MsgSend
s on the Terra blockchain,deduct_tax
should be called twice, as there are two cases when Terra tax is charged for UST transfers during redemptions:- when UST is transferred from the Anchor money market to this Pylon pool contract
- when UST is transferred from this Pylon pool contract to the user's wallet
- Issue a
HandleResponse
containing the following messages:- burn
amount
units of DP tokens for this Pylon pool. - redeem
market_redeem_amount
aUST (calculated asdp_token_balance / epoch_state.exchange_rate
) forpool_redeem_amount
UST. MsgSend
spool_redeem_amount
back to the caller of this contract
pub fn claim_reward<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>,
env: Env,
) -> StdResult<HandleResponse> {
// calculate (total_aust_amount * exchange_rate) - (total_dp_balance)
let config = config::read(&deps.storage)?;
if config.beneficiary != deps.api.canonical_address(&env.message.sender)? {
return Err(StdError::unauthorized());
}
let (reward_amount, fee_amount) =
querier::pool::calculate_reward_amount(deps, &config, Option::from(env.block.time))?;
let (market_redeem_amount, _, _) =
querier::pool::calculate_return_amount(deps, &config, reward_amount.add(fee_amount))?;
let (_, beneficiary_redeem_amount, _) =
querier::pool::calculate_return_amount(deps, &config, reward_amount)?;
let (_, collector_redeem_amount, _) =
querier::pool::calculate_return_amount(deps, &config, fee_amount)?;
Ok(HandleResponse {
messages: [
querier::feeder::update_msg(deps, &config.exchange_rate_feeder, &config.dp_token)?,
querier::anchor::redeem_stable_msg(
deps,
&config.moneymarket,
&config.atoken,
market_redeem_amount.into(),
)?,
vec![
CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address.clone(),
to_address: deps.api.human_address(&config.beneficiary)?,
amount: vec![beneficiary_redeem_amount.clone()],
}),
CosmosMsg::Bank(BankMsg::Send {
from_address: env.contract.address.clone(),
to_address: deps.api.human_address(&config.fee_collector)?,
amount: vec![collector_redeem_amount.clone()],
}),
],
]
.concat(),
log: vec![
log("action", "claim_reward"),
log("sender", env.message.sender),
log("amount", beneficiary_redeem_amount.amount),
log("fee", collector_redeem_amount.amount),
],
data: None,
})
}
On a
ClaimReward {}
HandleMsg
call:- calculate how much aUST should be redeemed:
(total_aust_amount * exchange_rate) - (total_dp_balance)
- apply a virtual exchange rate queryable from the Pylon Exchange Rate contract. This effectively re-calculates how much UST should be claimed excluding MINE token buybacks, which is sent to the Collector contract.
- calculates how much UST should be redeemed for:
market_redeem_amount
: amount of aUST for redemptions before Terra blockchain tax for bothbeneficiary
andcollector
. returnsUint256
.beneficiary_redeem_amount
: amount of aUST for redemptions after Terra blockchain tax forbeneficiary
. returns aCoin
object.collector_redeem_amount
: amount of aUST for redemptions after Terra blockchain tax forcollector
, sent to the Collector contract for buybacks. returns aCoin
object.
- Issue a
HandleResponse
that includes the following messages:- update virtual exchange rate
- redeem
market_redeem_amount
aUST from Anchor - send
beneficiary_redeem_amount
to beneficiary account - send
collector_redeem_amount
to Collector contract
Last modified 2yr ago