Copy use cosmwasm_std::{
attr, entry_point, to_binary, Addr, Binary, BlockInfo, CosmosMsg, Decimal, Deps, DepsMut,
Env, MessageInfo, Response, StdError, StdResult, Uint128, Uint256, Uint512,
WasmMsg,
};
use cw2::set_contract_version;
use cw20::Denom::Cw20;
use cw20::{Cw20ExecuteMsg, Denom, Expiration};
use cw20_base::contract::query_balance;
use std::convert::TryInto;
use std::str::FromStr;
use crate::error::ContractError;
use crate::msg::{
ExecuteMsg, FeeResponse, InfoResponse, InstantiateMsg, QueryMsg, Token1ForToken2PriceResponse,
Token2ForToken1PriceResponse, TokenSelect,
};
use crate::state::{Fees, Token, FEES, FROZEN, OWNER, TOKEN1, TOKEN2};
// Version info for migration info
pub const CONTRACT_NAME: &str = "crates.io:product-amm";
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
const FEE_SCALE_FACTOR: Uint128 = Uint128::new(10_000);
const MAX_FEE_PERCENT: &str = "1";
const FEE_DECIMAL_PRECISION: Uint128 = Uint128::new(10u128.pow(20));
// Note, you can use StdResult in some functions where you do not
// make use of the custom errors
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let token1 = Token {
reserve: Uint128::zero(),
denom: msg.token1_denom.clone(),
};
TOKEN1.save(deps.storage, &token1)?;
let token2 = Token {
denom: msg.token2_denom.clone(),
reserve: Uint128::zero(),
};
TOKEN2.save(deps.storage, &token2)?;
let owner = msg.owner.map(|h| deps.api.addr_validate(&h)).transpose()?;
OWNER.save(deps.storage, &owner)?;
let protocol_fee_recipient = deps.api.addr_validate(&msg.protocol_fee_recipient)?;
let total_fee_percent = msg.lp_fee_percent + msg.protocol_fee_percent;
let max_fee_percent = Decimal::from_str(MAX_FEE_PERCENT)?;
if total_fee_percent > max_fee_percent {
return Err(ContractError::FeesTooHigh {
max_fee_percent,
total_fee_percent,
});
}
let fees = Fees {
lp_fee_percent: msg.lp_fee_percent,
protocol_fee_percent: msg.protocol_fee_percent,
protocol_fee_recipient,
};
FEES.save(deps.storage, &fees)?;
// Depositing is not frozen by default
FROZEN.save(deps.storage, &false)?;
Ok(Response::new().add_attribute("key", "instantiate"))
}
// And declare a custom Error variant for the ones where you will want to make use of it
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::AddLiquidity {
token1_amount,
min_liquidity,
expiration, token2_amount } => {
if FROZEN.load(deps.storage)? {
return Err(ContractError::FrozenPool {});
}
execute_add_liquidity(
deps,
&info,
env,
min_liquidity,
token1_amount,
token2_amount,
expiration,
)
}
ExecuteMsg::RemoveLiquidity {
amount,
min_token1,
min_token2,
expiration,
} => execute_remove_liquidity(deps, info, env, amount, min_token1, min_token2, expiration),
ExecuteMsg::Swap {
input_token,
input_amount,
min_output,
expiration,
..
} => {
if FROZEN.load(deps.storage)? {
return Err(ContractError::FrozenPool {});
}
execute_swap(
deps,
&info,
input_amount,
env,
input_token,
info.sender.to_string(),
min_output,
expiration,
)
}
ExecuteMsg::UpdateConfig {
owner,
protocol_fee_recipient,
lp_fee_percent,
protocol_fee_percent,
} => execute_update_config(
deps,
info,
owner,
lp_fee_percent,
protocol_fee_percent,
protocol_fee_recipient,
),
ExecuteMsg::FreezeDeposits { freeze } => execute_freeze_deposits(deps, info.sender, freeze),
}
}
fn execute_freeze_deposits(
deps: DepsMut,
sender: Addr,
freeze: bool,
) -> Result<Response, ContractError> {
if let Some(owner) = OWNER.load(deps.storage)? {
if sender != owner {
return Err(ContractError::UnauthorizedPoolFreeze {});
}
} else {
return Err(ContractError::UnauthorizedPoolFreeze {});
}
FROZEN.save(deps.storage, &freeze)?;
Ok(Response::new().add_attribute("action", "freezing-contracts"))
}
fn check_expiration(
expiration: &Option<Expiration>,
block: &BlockInfo,
) -> Result<(), ContractError> {
match expiration {
Some(e) => {
if e.is_expired(block) {
return Err(ContractError::MsgExpirationError {});
}
Ok(())
}
None => Ok(()),
}
}
pub fn execute_add_liquidity(
deps: DepsMut,
info: &MessageInfo,
env: Env,
min_liquidity: Uint128,
token1_amount: Uint128,
token2_amount: Uint128,
expiration: Option<Expiration>,
) -> Result<Response, ContractError> {
check_expiration(&expiration, &env.block)?;
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
let liquidity_amount = token1_amount+token2_amount;
if liquidity_amount < min_liquidity {
return Err(ContractError::MinLiquidityError {
min_liquidity,
liquidity_available: liquidity_amount,
});
}
// Generate cw20 transfer messages if necessary
let mut transfer_msgs: Vec<CosmosMsg> = vec![];
if let Cw20(addr) = token1.denom {
transfer_msgs.push(get_cw20_transfer_from_msg(
&info.sender,
&env.contract.address,
&addr,
token1_amount,
)?)
}
if let Cw20(addr) = token2.denom.clone() {
transfer_msgs.push(get_cw20_transfer_from_msg(
&info.sender,
&env.contract.address,
&addr,
token2_amount,
)?)
}
TOKEN1.update(deps.storage, |mut token1| -> Result<_, ContractError> {
token1.reserve += token1_amount;
Ok(token1)
})?;
TOKEN2.update(deps.storage, |mut token2| -> Result<_, ContractError> {
token2.reserve += token2_amount;
Ok(token2)
})?;
Ok(Response::new()
.add_messages(transfer_msgs)
.add_attributes(vec![
attr("token1_amount", token1_amount),
attr("token2_amount", token2_amount),
attr("liquidity_received", liquidity_amount),
]))
}
fn get_cw20_transfer_from_msg(
owner: &Addr,
recipient: &Addr,
token_addr: &Addr,
token_amount: Uint128,
) -> StdResult<CosmosMsg> {
// create transfer cw20 msg
let transfer_cw20_msg = Cw20ExecuteMsg::TransferFrom {
owner: owner.into(),
recipient: recipient.into(),
amount: token_amount,
};
let exec_cw20_transfer = WasmMsg::Execute {
contract_addr: token_addr.into(),
msg: to_binary(&transfer_cw20_msg)?,
funds: vec![],
};
let cw20_transfer_cosmos_msg: CosmosMsg = exec_cw20_transfer.into();
Ok(cw20_transfer_cosmos_msg)
}
pub fn execute_update_config(
deps: DepsMut,
info: MessageInfo,
new_owner: Option<String>,
lp_fee_percent: Decimal,
protocol_fee_percent: Decimal,
protocol_fee_recipient: String,
) -> Result<Response, ContractError> {
let owner = OWNER.load(deps.storage)?;
if Some(info.sender) != owner {
return Err(ContractError::Unauthorized {});
}
let new_owner_addr = new_owner
.as_ref()
.map(|h| deps.api.addr_validate(h))
.transpose()?;
OWNER.save(deps.storage, &new_owner_addr)?;
let total_fee_percent = lp_fee_percent + protocol_fee_percent;
let max_fee_percent = Decimal::from_str(MAX_FEE_PERCENT)?;
if total_fee_percent > max_fee_percent {
return Err(ContractError::FeesTooHigh {
max_fee_percent,
total_fee_percent,
});
}
let protocol_fee_recipient = deps.api.addr_validate(&protocol_fee_recipient)?;
let updated_fees = Fees {
protocol_fee_recipient: protocol_fee_recipient.clone(),
lp_fee_percent,
protocol_fee_percent,
};
FEES.save(deps.storage, &updated_fees)?;
let new_owner = new_owner.unwrap_or_default();
Ok(Response::new().add_attributes(vec![
attr("new_owner", new_owner),
attr("lp_fee_percent", lp_fee_percent.to_string()),
attr("protocol_fee_percent", protocol_fee_percent.to_string()),
attr("protocol_fee_recipient", protocol_fee_recipient.to_string()),
]))
}
pub fn execute_remove_liquidity(
deps: DepsMut,
info: MessageInfo,
env: Env,
amount: Uint128,
min_token1: Uint128,
min_token2: Uint128,
expiration: Option<Expiration>,
) -> Result<Response, ContractError> {
check_expiration(&expiration, &env.block)?;
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
let total_token_supply = token1.reserve+token2.reserve;
if amount > total_token_supply {
return Err(ContractError::InsufficientLiquidityError {
requested: amount,
available: total_token_supply,
});
}
let token1_amount = amount
.checked_mul(token1.reserve)
.map_err(StdError::overflow)?
.checked_div(total_token_supply)
.map_err(StdError::divide_by_zero)?;
if token1_amount < min_token1 {
return Err(ContractError::MinToken1Error {
requested: min_token1,
available: token1_amount,
});
}
let token2_amount = amount
.checked_mul(token2.reserve)
.map_err(StdError::overflow)?
.checked_div(total_token_supply)
.map_err(StdError::divide_by_zero)?;
if token2_amount < min_token2 {
return Err(ContractError::MinToken2Error {
requested: min_token2,
available: token2_amount,
});
}
TOKEN1.update(deps.storage, |mut token1| -> Result<_, ContractError> {
token1.reserve = token1
.reserve
.checked_sub(token1_amount)
.map_err(StdError::overflow)?;
Ok(token1)
})?;
TOKEN2.update(deps.storage, |mut token2| -> Result<_, ContractError> {
token2.reserve = token2
.reserve
.checked_sub(token2_amount)
.map_err(StdError::overflow)?;
Ok(token2)
})?;
let token1_transfer_msg = match token1.denom {
Denom::Cw20(addr) => get_cw20_transfer_to_msg(&info.sender, &addr, token1_amount)?,
Denom::Native(_denom) => {unimplemented!()},
};
let token2_transfer_msg = match token2.denom {
Denom::Cw20(addr) => get_cw20_transfer_to_msg(&info.sender, &addr, token2_amount)?,
Denom::Native(_denom) => {unimplemented!()},
};
Ok(Response::new()
.add_messages(vec![
token1_transfer_msg,
token2_transfer_msg,
])
.add_attributes(vec![
attr("liquidity_burned", amount),
attr("token1_returned", token1_amount),
attr("token2_returned", token2_amount),
]))
}
fn get_cw20_transfer_to_msg(
recipient: &Addr,
token_addr: &Addr,
token_amount: Uint128,
) -> StdResult<CosmosMsg> {
// create transfer cw20 msg
let transfer_cw20_msg = Cw20ExecuteMsg::Transfer {
recipient: recipient.into(),
amount: token_amount,
};
let exec_cw20_transfer = WasmMsg::Execute {
contract_addr: token_addr.into(),
msg: to_binary(&transfer_cw20_msg)?,
funds: vec![],
};
let cw20_transfer_cosmos_msg: CosmosMsg = exec_cw20_transfer.into();
Ok(cw20_transfer_cosmos_msg)
}
fn get_fee_transfer_msg(
sender: &Addr,
recipient: &Addr,
fee_denom: &Denom,
amount: Uint128,
) -> StdResult<CosmosMsg> {
match fee_denom {
Denom::Cw20(addr) => get_cw20_transfer_from_msg(sender, recipient, addr, amount),
Denom::Native(_denom) => {unimplemented!()},
}
}
fn fee_decimal_to_uint128(decimal: Decimal) -> StdResult<Uint128> {
let result: Uint128 = decimal
.atomics()
.checked_mul(FEE_SCALE_FACTOR)
.map_err(StdError::overflow)?;
Ok(result / FEE_DECIMAL_PRECISION)
}
fn get_input_price(
input_amount: Uint128,
input_reserve: Uint128,
output_reserve: Uint128,
fee_percent: Decimal,
) -> StdResult<Uint128> {
if input_reserve == Uint128::zero() || output_reserve == Uint128::zero() {
return Err(StdError::generic_err("No liquidity"));
};
let fee_percent = fee_decimal_to_uint128(fee_percent)?;
let fee_reduction_percent = FEE_SCALE_FACTOR - fee_percent;
let input_amount_with_fee = Uint512::from(input_amount.full_mul(fee_reduction_percent));
let numerator = input_amount_with_fee
.checked_mul(Uint512::from(output_reserve))
.map_err(StdError::overflow)?;
let denominator = Uint512::from(input_reserve)
.checked_mul(Uint512::from(FEE_SCALE_FACTOR))
.map_err(StdError::overflow)?
.checked_add(input_amount_with_fee)
.map_err(StdError::overflow)?;
Ok(numerator
.checked_div(denominator)
.map_err(StdError::divide_by_zero)?
.try_into()?)
}
fn get_protocol_fee_amount(input_amount: Uint128, fee_percent: Decimal) -> StdResult<Uint128> {
if fee_percent.is_zero() {
return Ok(Uint128::zero());
}
let fee_percent = fee_decimal_to_uint128(fee_percent)?;
Ok(input_amount
.full_mul(fee_percent)
.checked_div(Uint256::from(FEE_SCALE_FACTOR))
.map_err(StdError::divide_by_zero)?
.try_into()?)
}
#[allow(clippy::too_many_arguments)]
pub fn execute_swap(
deps: DepsMut,
info: &MessageInfo,
input_amount: Uint128,
_env: Env,
input_token_enum: TokenSelect,
recipient: String,
min_token: Uint128,
expiration: Option<Expiration>,
) -> Result<Response, ContractError> {
check_expiration(&expiration, &_env.block)?;
let input_token_item = match input_token_enum {
TokenSelect::Token1 => TOKEN1,
TokenSelect::Token2 => TOKEN2,
};
let input_token = input_token_item.load(deps.storage)?;
let output_token_item = match input_token_enum {
TokenSelect::Token1 => TOKEN2,
TokenSelect::Token2 => TOKEN1,
};
let output_token = output_token_item.load(deps.storage)?;
let fees = FEES.load(deps.storage)?;
let total_fee_percent = fees.lp_fee_percent + fees.protocol_fee_percent;
let token_bought = get_input_price(
input_amount,
input_token.reserve,
output_token.reserve,
total_fee_percent,
)?;
if min_token > token_bought {
return Err(ContractError::SwapMinError {
min: min_token,
available: token_bought,
});
}
// Calculate fees
let protocol_fee_amount = get_protocol_fee_amount(input_amount, fees.protocol_fee_percent)?;
let input_amount_minus_protocol_fee = input_amount - protocol_fee_amount;
let mut msgs = match input_token.denom.clone() {
Denom::Cw20(addr) => vec![get_cw20_transfer_from_msg(
&info.sender,
&_env.contract.address,
&addr,
input_amount_minus_protocol_fee,
)?],
Denom::Native(_) => vec![],
};
// Send protocol fee to protocol fee recipient
if !protocol_fee_amount.is_zero() {
msgs.push(get_fee_transfer_msg(
&info.sender,
&fees.protocol_fee_recipient,
&input_token.denom,
protocol_fee_amount,
)?)
}
let recipient = deps.api.addr_validate(&recipient)?;
// Create transfer to message
msgs.push(match output_token.denom {
Denom::Cw20(addr) => get_cw20_transfer_to_msg(&recipient, &addr, token_bought)?,
Denom::Native(_denom) => {unimplemented!()},
});
input_token_item.update(
deps.storage,
|mut input_token| -> Result<_, ContractError> {
input_token.reserve = input_token
.reserve
.checked_add(input_amount_minus_protocol_fee)
.map_err(StdError::overflow)?;
Ok(input_token)
},
)?;
output_token_item.update(
deps.storage,
|mut output_token| -> Result<_, ContractError> {
output_token.reserve = output_token
.reserve
.checked_sub(token_bought)
.map_err(StdError::overflow)?;
Ok(output_token)
},
)?;
Ok(Response::new().add_messages(msgs).add_attributes(vec![
attr("native_sold", input_amount),
attr("token_bought", token_bought),
]))
}
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::Balance { address } => to_binary(&query_balance(deps, address)?),
QueryMsg::Info {} => to_binary(&query_info(deps)?),
QueryMsg::Token1ForToken2Price { token1_amount } => {
to_binary(&query_token1_for_token2_price(deps, token1_amount)?)
}
QueryMsg::Token2ForToken1Price { token2_amount } => {
to_binary(&query_token2_for_token1_price(deps, token2_amount)?)
}
QueryMsg::Fee {} => to_binary(&query_fee(deps)?),
}
}
pub fn query_info(deps: Deps) -> StdResult<InfoResponse> {
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
// TODO get total supply
Ok(InfoResponse {
token1_reserve: token1.reserve,
token1_denom: token1.denom,
token2_reserve: token2.reserve,
token2_denom: token2.denom,
})
}
pub fn query_token1_for_token2_price(
deps: Deps,
token1_amount: Uint128,
) -> StdResult<Token1ForToken2PriceResponse> {
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
let fees = FEES.load(deps.storage)?;
let total_fee_percent = fees.lp_fee_percent + fees.protocol_fee_percent;
let token2_amount = get_input_price(
token1_amount,
token1.reserve,
token2.reserve,
total_fee_percent,
)?;
Ok(Token1ForToken2PriceResponse { token2_amount })
}
pub fn query_token2_for_token1_price(
deps: Deps,
token2_amount: Uint128,
) -> StdResult<Token2ForToken1PriceResponse> {
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
let fees = FEES.load(deps.storage)?;
let total_fee_percent = fees.lp_fee_percent + fees.protocol_fee_percent;
let token1_amount = get_input_price(
token2_amount,
token2.reserve,
token1.reserve,
total_fee_percent,
)?;
Ok(Token2ForToken1PriceResponse { token1_amount })
}
pub fn query_fee(deps: Deps) -> StdResult<FeeResponse> {
let fees = FEES.load(deps.storage)?;
let owner = OWNER.load(deps.storage)?.map(|o| o.into_string());
Ok(FeeResponse {
owner,
lp_fee_percent: fees.lp_fee_percent,
protocol_fee_percent: fees.protocol_fee_percent,
protocol_fee_recipient: fees.protocol_fee_recipient.into_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_input_price() {
let fee_percent = Decimal::from_str("0.03").unwrap();
// Base case
assert_eq!(
get_input_price(
Uint128::new(10),
Uint128::new(100),
Uint128::new(100),
fee_percent
)
.unwrap(),
Uint128::new(9)
);
// No input reserve error
let err = get_input_price(
Uint128::new(10),
Uint128::new(0),
Uint128::new(100),
fee_percent,
)
.unwrap_err();
assert_eq!(err, StdError::generic_err("No liquidity"));
// No output reserve error
let err = get_input_price(
Uint128::new(10),
Uint128::new(100),
Uint128::new(0),
fee_percent,
)
.unwrap_err();
assert_eq!(err, StdError::generic_err("No liquidity"));
// No reserve error
let err = get_input_price(
Uint128::new(10),
Uint128::new(0),
Uint128::new(0),
fee_percent,
)
.unwrap_err();
assert_eq!(err, StdError::generic_err("No liquidity"));
}
}