Creador de mercado autom谩tico de productos constantes (AMM)
Explicaci贸n
Este contrato es un creador de mercado automatizado (AMM) de productos constantes para CosmWasm. Este contrato le permite intercambiar tokens. Los proveedores de liquidez pueden agregar liquidez al mercado y recibir una tarifa determinada por cada transacci贸n que se establece en la instanciaci贸n. El c贸digo tambi茅n incluye varias comprobaciones de validaci贸n y manejo de errores para garantizar la correcci贸n y seguridad de las operaciones. Este tipo de AMM se basa en la funci贸n x*y=k, que establece un rango de precios para dos tokens seg煤n la liquidez disponible de cada token. Cuando la oferta de token X aumenta, la oferta de token de Y debe disminuir, y viceversa, para mantener el producto K constante. Cuando se representa gr谩ficamente, el resultado es una hip茅rbola donde la liquidez siempre est谩 disponible pero a precios cada vez m谩s altos, que se acercan al infinito. en ambos extremos.
Creaci贸n de instancias
El contrato se puede instanciar con los siguientes mensajes
{
"token1_denom": {"cw20": "<CONTRACT_ADDRESS>"},
"token2_denom": {"cw20": "<CONTRACT_ADDRESS>"},
}
El nombre del token puede ser cw20 para tokens cw20. Los tokens cw20 tienen una direcci贸n de contrato. CW20_CODE_ID es la identificaci贸n del c贸digo para un binario cw20 b谩sico.
Agregar liquidez
Permite a un usuario agregar liquidez al grupo.
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 mut token_supply = TOTAL_STORED.load(deps.storage)?;
let liquidity_amount = token1_amount+token2_amount;
token_supply+=liquidity_amount;
TOTAL_STORED.save(deps.storage, &token_supply)?;
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),
]))
}
Los usuarios pueden agregar liquidez al AMM llamando a la funci贸n ejecutar_add_liquidity. Esta funci贸n toma las cantidades deseadas de dos tokens (token1_amount y token2_amount) y acu帽a la cantidad correspondiente de tokens de liquidez. Los tokens de liquidez representan la participaci贸n del usuario en el fondo de liquidez de AMM. La funci贸n tambi茅n transfiere los tokens de entrada del usuario al contrato.
Eliminar liquidez
Permite a un usuario eliminar liquidez del grupo.
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 total_token_supply = TOTAL_STORED.load(deps.storage)?;
let token1 = TOKEN1.load(deps.storage)?;
let token2 = TOKEN2.load(deps.storage)?;
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),
]))
}
Los proveedores de liquidez pueden eliminar su liquidez llamando a la funci贸n ejecutar_remove_liquidity. Especifican la cantidad de tokens de liquidez (monto) que quieren quemar y la funci贸n calcula las cantidades proporcionales de los tokens subyacentes (token1_amount y token2_amount). La funci贸n transfiere los tokens correspondientes al usuario y reduce las reservas de tokens en consecuencia.
Swap
Cambiar un activo por otro
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),
]))
}
Los usuarios pueden intercambiar tokens utilizando AMM llamando a la funci贸n ejecutar_swap. Especifican el token de entrada (input_token), la cantidad a intercambiar (input_amount) y la cantidad m铆nima de salida (min_output). La funci贸n calcula la cantidad de salida calculada en la f贸rmula del producto constante y verifica si cumple con el requisito m铆nimo. Si el intercambio es v谩lido, transfiere el token de entrada del usuario al contrato y transfiere el token de salida nuevamente al usuario.
Actualizaci贸n de configuraci贸n
Para actualizar la configuraci贸n de AMM
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()),
]))
}
El propietario puede actualizar la configuraci贸n del AMM mediante la funci贸n ejecutar_update_config. El propietario puede cambiar el porcentaje de la tarifa LP (proveedor de liquidez), el porcentaje de la tarifa del protocolo y la direcci贸n del destinatario de la tarifa del protocolo.
Congelamiento de dep贸sitos
Para congelar el dep贸sito a AMM
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(()),
}
}
El propietario puede congelar los dep贸sitos en AMM llamando a la funci贸n ejecutar_freeze_deposits. Esto evita que los usuarios agreguen liquidez o intercambien tokens. S贸lo el propietario puede congelar o descongelar los dep贸sitos.
Ejemplos
Para crear un AMM con CosmWasm, puede crear los siguientes archivos: lib.rs Integration_tests.rs contract.rs msg.rs error.rs state.rs
lib.rs
pub mod contract;
pub mod error;
mod integration_test;
pub mod msg;
pub mod state;
integration_tests.rs
#![cfg(test)]
use std::borrow::BorrowMut;
use crate::error::ContractError;
use cosmwasm_std::{coins, Addr, Coin, Decimal, Empty, Uint128};
use cw20::{Cw20Coin, Cw20Contract, Cw20ExecuteMsg, Denom};
use cw_multi_test::{App, Contract, ContractWrapper, Executor};
use std::str::FromStr;
use crate::msg::{ExecuteMsg, FeeResponse, InfoResponse, InstantiateMsg, QueryMsg, TokenSelect};
fn mock_app() -> App {
App::default()
}
pub fn contract_amm() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
);
Box::new(contract)
}
pub fn contract_cw20() -> Box<dyn Contract<Empty>> {
let contract = ContractWrapper::new(
cw20_base::contract::execute,
cw20_base::contract::instantiate,
cw20_base::contract::query,
);
Box::new(contract)
}
fn get_info(router: &App, contract_addr: &Addr) -> InfoResponse {
router
.wrap()
.query_wasm_smart(contract_addr, &QueryMsg::Info {})
.unwrap()
}
fn get_fee(router: &App, contract_addr: &Addr) -> FeeResponse {
router
.wrap()
.query_wasm_smart(contract_addr, &QueryMsg::Fee {})
.unwrap()
}
fn create_amm(
router: &mut App,
owner: &Addr,
token1_denom: Denom,
token2_denom: Denom,
lp_fee_percent: Decimal,
protocol_fee_percent: Decimal,
protocol_fee_recipient: String,
) -> Addr {
// set up amm contract
let amm_id = router.store_code(contract_amm());
let msg = InstantiateMsg {
token1_denom,
token2_denom,
owner: Some(owner.to_string()),
lp_fee_percent,
protocol_fee_percent,
protocol_fee_recipient,
};
router
.instantiate_contract(amm_id, owner.clone(), &msg, &[], "amm", None)
.unwrap()
}
// CreateCW20 create new cw20 with given initial balance belonging to owner
fn create_cw20(
router: &mut App,
owner: &Addr,
name: String,
symbol: String,
balance: Uint128,
) -> Cw20Contract {
// set up cw20 contract with some tokens
let cw20_id = router.store_code(contract_cw20());
let msg = cw20_base::msg::InstantiateMsg {
name,
symbol,
decimals: 6,
initial_balances: vec![Cw20Coin {
address: owner.to_string(),
amount: balance,
}],
mint: None,
marketing: None,
};
let addr = router
.instantiate_contract(cw20_id, owner.clone(), &msg, &[], "CASH", None)
.unwrap();
Cw20Contract(addr)
}
fn bank_balance(router: &mut App, addr: &Addr, denom: String) -> Coin {
router
.wrap()
.query_balance(addr.to_string(), denom)
.unwrap()
}
#[test]
// receive cw20 tokens and release upon approval
fn test_instantiate() {
let mut router = mock_app();
const NATIVE_TOKEN_DENOM: &str = "juno";
let owner = Addr::unchecked("owner");
let funds = coins(2000, NATIVE_TOKEN_DENOM);
router.borrow_mut().init_modules(|router, _, storage| {
router.bank.init_balance(storage, &owner, funds).unwrap()
});
let cw20_token = create_cw20(
&mut router,
&owner,
"token".to_string(),
"CWTOKEN".to_string(),
Uint128::new(5000),
);
let lp_fee_percent = Decimal::from_str("0.3").unwrap();
let protocol_fee_percent = Decimal::zero();
let amm_addr = create_amm(
&mut router,
&owner,
Denom::Native(NATIVE_TOKEN_DENOM.into()),
Denom::Cw20(cw20_token.addr()),
lp_fee_percent,
protocol_fee_percent,
owner.to_string(),
);
assert_ne!(cw20_token.addr(), amm_addr);
let _info = get_info(&router, &amm_addr);
let fee = get_fee(&router, &amm_addr);
assert_eq!(fee.lp_fee_percent, lp_fee_percent);
assert_eq!(fee.protocol_fee_percent, protocol_fee_percent);
assert_eq!(fee.protocol_fee_recipient, owner.to_string());
assert_eq!(fee.owner.unwrap(), owner.to_string());
// Test instantiation with invalid fee amount
let lp_fee_percent = Decimal::from_str("1.01").unwrap();
let protocol_fee_percent = Decimal::zero();
let amm_id = router.store_code(contract_amm());
let msg = InstantiateMsg {
token1_denom: Denom::Native(NATIVE_TOKEN_DENOM.into()),
token2_denom: Denom::Cw20(cw20_token.addr()),
owner: Some(owner.to_string()),
lp_fee_percent,
protocol_fee_percent,
protocol_fee_recipient: owner.to_string(),
};
let err = router
.instantiate_contract(amm_id, owner.clone(), &msg, &[], "amm", None)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
ContractError::FeesTooHigh {
max_fee_percent: Decimal::from_str("1").unwrap(),
total_fee_percent: Decimal::from_str("1.01").unwrap()
},
err
);
}
#[test]
fn update_config() {
let mut router = mock_app();
const NATIVE_TOKEN_DENOM: &str = "juno";
let owner = Addr::unchecked("owner");
let funds = coins(2000, NATIVE_TOKEN_DENOM);
router.borrow_mut().init_modules(|router, _, storage| {
router.bank.init_balance(storage, &owner, funds).unwrap()
});
let cw20_token = create_cw20(
&mut router,
&owner,
"token".to_string(),
"CWTOKEN".to_string(),
Uint128::new(5000),
);
let lp_fee_percent = Decimal::from_str("0.3").unwrap();
let protocol_fee_percent = Decimal::zero();
let amm_addr = create_amm(
&mut router,
&owner,
Denom::Native(NATIVE_TOKEN_DENOM.to_string()),
Denom::Cw20(cw20_token.addr()),
lp_fee_percent,
protocol_fee_percent,
owner.to_string(),
);
let lp_fee_percent = Decimal::from_str("0.15").unwrap();
let protocol_fee_percent = Decimal::from_str("0.15").unwrap();
let msg = ExecuteMsg::UpdateConfig {
owner: Some(owner.to_string()),
protocol_fee_recipient: "new_fee_recpient".to_string(),
lp_fee_percent,
protocol_fee_percent,
};
let _res = router
.execute_contract(owner.clone(), amm_addr.clone(), &msg, &[])
.unwrap();
let fee = get_fee(&router, &amm_addr);
assert_eq!(fee.protocol_fee_recipient, "new_fee_recpient".to_string());
assert_eq!(fee.protocol_fee_percent, protocol_fee_percent);
assert_eq!(fee.lp_fee_percent, lp_fee_percent);
assert_eq!(fee.owner.unwrap(), owner.to_string());
// Try updating config with fee values that are too high
let lp_fee_percent = Decimal::from_str("1.01").unwrap();
let protocol_fee_percent = Decimal::zero();
let msg = ExecuteMsg::UpdateConfig {
owner: Some(owner.to_string()),
protocol_fee_recipient: "new_fee_recpient".to_string(),
lp_fee_percent,
protocol_fee_percent,
};
let err = router
.execute_contract(owner.clone(), amm_addr.clone(), &msg, &[])
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(
ContractError::FeesTooHigh {
max_fee_percent: Decimal::from_str("1").unwrap(),
total_fee_percent: Decimal::from_str("1.01").unwrap()
},
err
);
// Try updating config with invalid owner, show throw unauthoritzed error
let lp_fee_percent = Decimal::from_str("0.21").unwrap();
let protocol_fee_percent = Decimal::from_str("0.09").unwrap();
let msg = ExecuteMsg::UpdateConfig {
owner: Some(owner.to_string()),
protocol_fee_recipient: owner.to_string(),
lp_fee_percent,
protocol_fee_percent,
};
let err = router
.execute_contract(
Addr::unchecked("invalid_owner"),
amm_addr.clone(),
&msg,
&[],
)
.unwrap_err()
.downcast()
.unwrap();
assert_eq!(ContractError::Unauthorized {}, err);
// Try updating owner and fee params
let msg = ExecuteMsg::UpdateConfig {
owner: Some("new_owner".to_string()),
protocol_fee_recipient: owner.to_string(),
lp_fee_percent,
protocol_fee_percent,
};
let _res = router
.execute_contract(owner.clone(), amm_addr.clone(), &msg, &[])
.unwrap();
let fee = get_fee(&router, &amm_addr);
assert_eq!(fee.protocol_fee_recipient, owner.to_string());
assert_eq!(fee.protocol_fee_percent, protocol_fee_percent);
assert_eq!(fee.lp_fee_percent, lp_fee_percent);
assert_eq!(fee.owner.unwrap(), "new_owner".to_string());
}
#[test]
fn test_pass_through_swap() {
let mut router = mock_app();
const NATIVE_TOKEN_DENOM: &str = "juno";
let owner = Addr::unchecked("owner");
let funds = coins(2000, NATIVE_TOKEN_DENOM);
router.borrow_mut().init_modules(|router, _, storage| {
router.bank.init_balance(storage, &owner, funds).unwrap()
});
let token1 = create_cw20(
&mut router,
&owner,
"token1".to_string(),
"TOKENONE".to_string(),
Uint128::new(5000),
);
let token2 = create_cw20(
&mut router,
&owner,
"token2".to_string(),
"TOKENTWO".to_string(),
Uint128::new(5000),
);
let lp_fee_percent = Decimal::from_str("0.03").unwrap();
let protocol_fee_percent = Decimal::zero();
let amm = create_amm(
&mut router,
&owner,
Denom::Cw20(token1.addr()),
Denom::Cw20(token2.addr()),
lp_fee_percent,
protocol_fee_percent,
owner.to_string(),
);
// Add initial liquidity to both pools
let allowance_msg = Cw20ExecuteMsg::IncreaseAllowance {
spender: amm.to_string(),
amount: Uint128::new(100),
expires: None,
};
let _res = router
.execute_contract(owner.clone(), token1.addr(), &allowance_msg, &[])
.unwrap();
let _res = router
.execute_contract(owner.clone(), token2.addr(), &allowance_msg, &[])
.unwrap();
let add_liquidity_msg = ExecuteMsg::AddLiquidity {
token1_amount: Uint128::new(100),
min_liquidity: Uint128::new(100),
token2_amount: Uint128::new(100),
expiration: None,
};
router
.execute_contract(
owner.clone(),
amm.clone(),
&add_liquidity_msg,
&[Coin {
denom: NATIVE_TOKEN_DENOM.into(),
amount: Uint128::zero(),
}],
)
.unwrap();
// Swap token1 for token2
let allowance_msg = Cw20ExecuteMsg::IncreaseAllowance {
spender: amm.to_string(),
amount: Uint128::new(10),
expires: None,
};
let _res = router
.execute_contract(owner.clone(), token1.addr(), &allowance_msg, &[])
.unwrap();
let swap_msg = ExecuteMsg::Swap {
input_token: TokenSelect::Token1,
input_amount: Uint128::new(10),
min_output: Uint128::new(8),
expiration: None,
};
let _res = router
.execute_contract(owner.clone(), amm.clone(), &swap_msg, &[])
.unwrap();
// ensure balances updated
let token1_balance = token1.balance(&router, owner.clone()).unwrap();
assert_eq!(token1_balance, Uint128::new(4890));
let token2_balance = token2.balance(&router, owner.clone()).unwrap();
assert_eq!(token2_balance, Uint128::new(4909));
let amm_native_balance = bank_balance(&mut router, &amm, NATIVE_TOKEN_DENOM.to_string());
assert_eq!(amm_native_balance.amount, Uint128::zero());
// assert internal state is consistent
let info_amm: InfoResponse = get_info(&router, &amm);
println!("{:?}", info_amm);
let token1_balance = token1.balance(&router, amm.clone()).unwrap();
let token2_balance = token2.balance(&router, amm.clone()).unwrap();
println!("{} {}", token1_balance, token2_balance);
assert_eq!(info_amm.token2_reserve, token2_balance);
assert_eq!(info_amm.token1_reserve, token1_balance);
}
contract.rs
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"));
}
}
msg.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{Decimal, Uint128};
use cw20::{Denom, Expiration};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub token1_denom: Denom,
pub token2_denom: Denom,
pub owner: Option<String>,
pub protocol_fee_recipient: String,
// NOTE: Fees percents are out of 100 e.g., 1 = 1%
pub protocol_fee_percent: Decimal,
pub lp_fee_percent: Decimal,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum TokenSelect {
Token1,
Token2,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
AddLiquidity {
token1_amount: Uint128,
token2_amount: Uint128,
min_liquidity: Uint128,
expiration: Option<Expiration>,
},
RemoveLiquidity {
amount: Uint128,
min_token1: Uint128,
min_token2: Uint128,
expiration: Option<Expiration>,
},
Swap {
input_token: TokenSelect,
input_amount: Uint128,
min_output: Uint128,
expiration: Option<Expiration>,
},
UpdateConfig {
owner: Option<String>,
lp_fee_percent: Decimal,
protocol_fee_percent: Decimal,
protocol_fee_recipient: String,
},
// Freeze adding new deposits
FreezeDeposits {
freeze: bool,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
/// Implements CW20. Returns the current balance of the given address, 0 if unset.
Balance {
address: String,
},
Info {},
Token1ForToken2Price {
token1_amount: Uint128,
},
Token2ForToken1Price {
token2_amount: Uint128,
},
Fee {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MigrateMsg {
pub owner: Option<String>,
pub protocol_fee_recipient: String,
pub protocol_fee_percent: Decimal,
pub lp_fee_percent: Decimal,
pub freeze_pool: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InfoResponse {
pub token1_reserve: Uint128,
pub token1_denom: Denom,
pub token2_reserve: Uint128,
pub token2_denom: Denom,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, JsonSchema)]
pub struct FeeResponse {
pub owner: Option<String>,
pub lp_fee_percent: Decimal,
pub protocol_fee_percent: Decimal,
pub protocol_fee_recipient: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Token1ForToken2PriceResponse {
pub token2_amount: Uint128,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Token2ForToken1PriceResponse {
pub token1_amount: Uint128,
}
error.rs
use cosmwasm_std::{Decimal, StdError, Uint128};
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
#[error("{0}")]
Cw20Error(#[from] cw20_base::ContractError),
#[error("None Error")]
NoneError {},
#[error("Unauthorized")]
Unauthorized {},
// Add any other custom errors you like here.
// Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details.
#[error("Min liquidity error: requested: {min_liquidity}, available: {liquidity_available}")]
MinLiquidityError {
min_liquidity: Uint128,
liquidity_available: Uint128,
},
#[error("Max token error: max_token: {max_token}, tokens_required: {tokens_required}")]
MaxTokenError {
max_token: Uint128,
tokens_required: Uint128,
},
#[error("Insufficient liquidity error: requested: {requested}, available: {available}")]
InsufficientLiquidityError {
requested: Uint128,
available: Uint128,
},
#[error("Min token1 error: requested: {requested}, available: {available}")]
MinToken1Error {
requested: Uint128,
available: Uint128,
},
#[error("Min token2 error: requested: {requested}, available: {available}")]
MinToken2Error {
requested: Uint128,
available: Uint128,
},
#[error("Incorrect native denom: provided: {provided}, required: {required}")]
IncorrectNativeDenom { provided: String, required: String },
#[error("Swap min error: min: {min}, available: {available}")]
SwapMinError { min: Uint128, available: Uint128 },
#[error("MsgExpirationError")]
MsgExpirationError {},
#[error("Total fee ({total_fee_percent}) percent is higher than max ({max_fee_percent})")]
FeesTooHigh {
max_fee_percent: Decimal,
total_fee_percent: Decimal,
},
#[error("InsufficientFunds")]
InsufficientFunds {},
#[error("Uknown reply id: {id}")]
UnknownReplyId { id: u64 },
#[error("Failed to instantiate lp token")]
InstantiateLpTokenError {},
#[error("The output amm provided is invalid")]
InvalidOutputPool {},
#[error("Unauthorized pool freeze - sender is not an owner or owner has not been set")]
UnauthorizedPoolFreeze {},
#[error("This pools is frozen - you can not deposit or swap tokens")]
FrozenPool {},
}
state.rs
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{Addr, Decimal, Uint128};
use cw20::Denom;
use cw_storage_plus::Item;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Token {
pub reserve: Uint128,
pub denom: Denom,
}
pub const TOKEN1: Item<Token> = Item::new("token1");
pub const TOKEN2: Item<Token> = Item::new("token2");
pub const OWNER: Item<Option<Addr>> = Item::new("owner");
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct Fees {
pub protocol_fee_recipient: Addr,
pub protocol_fee_percent: Decimal,
pub lp_fee_percent: Decimal,
}
pub const FEES: Item<Fees> = Item::new("fees");
pub const FROZEN: Item<bool> = Item::new("frozen");
Last updated