Skip to content

Commit

Permalink
support multiple target denoms
Browse files Browse the repository at this point in the history
  • Loading branch information
codehans committed Apr 25, 2024
1 parent 06045bc commit 0574814
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 58 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,9 @@ At the end of each execution, `revenue_token` balance is read and is deposited t

- code-id `3147`
- address `kujira158ydy6qlfq7khtnj5lj9a5dy25ep8hece4d0lqngzxqrwuz6dctsdl5eqx`

### Mainnet

- code-id `282`
- `kujira1xajlwpfpjnvwehurrj2w7d8ru6sm4579vzfp44c6fd5sj86u6tvqdk6mjn`: USK target
- `kujira1x97ay4eq7uv7hh59ytdxm3lsz567yz9wrwn74gq7dsspuapqvjtq03tajj`: KUJI target
2 changes: 1 addition & 1 deletion artifacts/checksums.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3e32932f38991ab7fb572aaa0aea4792b6d72ff034b7a9927dfdcd790e8b940b kujira_revenue_converter.wasm
30e7100ba9fe0599593ca45bf9b42efbd0b317ee11e1fa702dfda797d1a2e01a kujira_revenue_converter.wasm
Binary file modified artifacts/kujira_revenue_converter.wasm
Binary file not shown.
200 changes: 149 additions & 51 deletions src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use cosmwasm_std::entry_point;
use cosmwasm_std::{
to_json_binary, Addr, Binary, CosmosMsg, Decimal, Deps, DepsMut, Env, Event, MessageInfo,
Reply, Response, StdResult, SubMsg,
QuerierWrapper, Reply, Response, StdResult, Storage, SubMsg,
};
use kujira::Denom;

Expand All @@ -18,7 +18,10 @@ const CONTRACT_NAME: &str = "crates.io:kujira-revenue-converter";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: ()) -> Result<Response, ContractError> {
pub fn migrate(deps: DepsMut, _env: Env, msg: InstantiateMsg) -> Result<Response, ContractError> {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let config = Config::from(msg);
config.save(deps.storage)?;
Ok(Response::default())
}

Expand Down Expand Up @@ -82,25 +85,38 @@ pub fn execute(
if info.sender != config.executor {
return Err(ContractError::Unauthorized {});
}

if let Some((action, msg)) = get_action_msg(deps, &env.contract.address)? {
let event =
Event::new("revenue/run").add_attribute("denom", action.denom.to_string());
return Ok(Response::default()
.add_event(event)
.add_submessage(SubMsg::reply_always(msg, 0)));
let action_msg = get_action_msg(deps.storage, deps.querier, &env.contract.address)?;

match action_msg {
Some((action, msg)) => {
let event =
Event::new("revenue/run").add_attribute("denom", action.denom.to_string());
Ok(Response::default()
.add_event(event)
.add_submessage(SubMsg::reply_always(msg, 0)))
}
// If there's no compatible action, skip to the reply
None => {
let mut sends: Vec<CosmosMsg> = vec![];
for target in config.target_denoms.clone() {
distribute_denom(deps.as_ref(), &env, &config, &mut sends, target)?;
}

Ok(Response::default().add_messages(sends))
}
}
Ok(Response::default())
}
}
}

fn get_action_msg(deps: DepsMut, contract: &Addr) -> StdResult<Option<(Action, CosmosMsg)>> {
fn get_action_msg(
storage: &mut dyn Storage,
querier: QuerierWrapper,
contract: &Addr,
) -> StdResult<Option<(Action, CosmosMsg)>> {
// Fetch the next action in the iterator
if let Some(action) = Action::next(deps.storage)? {
let balance = deps
.querier
.query_balance(contract, action.denom.to_string())?;
if let Some(action) = Action::next(storage)? {
let balance = querier.query_balance(contract, action.denom.to_string())?;
return match action.execute(balance)? {
None => Ok(None),
Some(msg) => Ok(Some((action, msg))),
Expand All @@ -111,38 +127,17 @@ fn get_action_msg(deps: DepsMut, contract: &Addr) -> StdResult<Option<(Action, C

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, env: Env, _msg: Reply) -> Result<Response, ContractError> {
let config = Config::load(deps.storage)?;
let balance = deps
.querier
.query_balance(env.contract.address, config.target_denom.to_string())?;
let send = if balance.amount.is_zero() {
vec![]
} else {
let total_weight = config.target_addresses.iter().fold(0, |a, e| e.1 + a);
let mut sends = vec![];
let mut remaining = balance.amount;
let mut targets = config.target_addresses.iter().peekable();
execute_reply(deps.as_ref(), env)
}

while let Some((addr, weight)) = targets.next() {
let amount = if targets.peek().is_none() {
remaining
} else {
balance
.amount
.mul_floor(Decimal::from_ratio(*weight, total_weight))
};
if amount.is_zero() {
continue;
}
remaining -= amount;
sends.push(config.target_denom.send(&addr, &balance.amount));
}
pub fn execute_reply(deps: Deps, env: Env) -> Result<Response, ContractError> {
let config = Config::load(deps.storage)?;
let mut sends: Vec<CosmosMsg> = vec![];
for target in config.target_denoms.clone() {
distribute_denom(deps, &env, &config, &mut sends, target)?;
}

sends
};
Ok(Response::default()
.add_messages(send)
.add_event(Event::new("revenue/reply").add_attribute("send", balance.to_string())))
Ok(Response::default().add_messages(sends))
}

#[cfg_attr(not(feature = "library"), entry_point)]
Expand All @@ -161,14 +156,48 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
}
}

fn distribute_denom(
deps: Deps,
env: &Env,
config: &Config,
sends: &mut Vec<CosmosMsg>,
denom: Denom,
) -> StdResult<()> {
let balance = deps
.querier
.query_balance(env.contract.address.clone(), denom.to_string())?;

let total_weight = config.target_addresses.iter().fold(0, |a, e| e.1 + a);
if !balance.amount.is_zero() {
let mut remaining = balance.amount;
let mut targets = config.target_addresses.iter().peekable();

while let Some((addr, weight)) = targets.next() {
let amount = if targets.peek().is_none() {
remaining
} else {
let ratio = Decimal::from_ratio(*weight, total_weight);
balance.amount.mul_floor(ratio)
};

if amount.is_zero() {
continue;
}
remaining -= amount;
sends.push(denom.send(&addr, &amount))

Check failure on line 187 in src/contract.rs

View workflow job for this annotation

GitHub Actions / Lints

this expression creates a reference which is immediately dereferenced by the compiler
}
};
Ok(())
}

#[cfg(test)]
mod tests {

use super::*;
use cosmwasm_std::{
coin, from_json,
coin, coins, from_json,
testing::{mock_dependencies, mock_dependencies_with_balances, mock_env, mock_info},
Uint128,
BankMsg, ReplyOn, Uint128,
};
use kujira::fee_address;

Expand All @@ -178,15 +207,18 @@ mod tests {
let info = mock_info("owner", &vec![]);
let msg = InstantiateMsg {
owner: Addr::unchecked("owner"),
target_denom: Denom::from("ukuji"),
target_denoms: vec![Denom::from("ukuji"), Denom::from("another")],
target_addresses: vec![(fee_address(), 1)],
executor: Addr::unchecked("executor"),
};
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
let config: ConfigResponse =
from_json(query(deps.as_ref(), mock_env(), QueryMsg::Config {}).unwrap()).unwrap();
assert_eq!(config.owner, Addr::unchecked("owner"));
assert_eq!(config.target_denom, Denom::from("ukuji"));
assert_eq!(
config.target_denoms,
vec![Denom::from("ukuji"), Denom::from("another")],
);
let status: StatusResponse =
from_json(query(deps.as_ref(), mock_env(), QueryMsg::Status {}).unwrap()).unwrap();
assert_eq!(status.last, None);
Expand All @@ -200,7 +232,7 @@ mod tests {
let info = mock_info("owner", &vec![]);
let msg = InstantiateMsg {
owner: Addr::unchecked("owner"),
target_denom: Denom::from("ukuji"),
target_denoms: vec![Denom::from("ukuji"), Denom::from("another")],
target_addresses: vec![(fee_address(), 1)],
executor: Addr::unchecked("executor"),
};
Expand Down Expand Up @@ -308,7 +340,7 @@ mod tests {
let info = mock_info("contract-0", &vec![]);
let msg = InstantiateMsg {
owner: Addr::unchecked("owner"),
target_denom: Denom::from("ukuji"),
target_denoms: vec![Denom::from("ukuji"), Denom::from("another")],
target_addresses: vec![(fee_address(), 1)],
executor: Addr::unchecked("executor"),
};
Expand Down Expand Up @@ -422,4 +454,70 @@ mod tests {
)
.unwrap();
}

#[test]
fn distribution() {
let mut deps = mock_dependencies_with_balances(&[(
"cosmos2contract",
&[coin(1000u128, "ukuji"), coin(2000u128, "another")],
)]);
let info = mock_info("contract-0", &vec![]);
let msg = InstantiateMsg {
owner: Addr::unchecked("owner"),
target_denoms: vec![Denom::from("ukuji"), Denom::from("another")],
target_addresses: vec![(fee_address(), 1), (Addr::unchecked("another"), 3)],
executor: Addr::unchecked("executor"),
};
instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();
// Dummy action to make sure it cranks the reply
set_action(deps.as_mut(), "token-a", "contract-a", Uint128::MAX);

// Make sure that execution ends when there are no actions
let res = execute(
deps.as_mut(),
mock_env(),
mock_info("executor", &vec![]),
ExecuteMsg::Run {},
)
.unwrap();

assert!(res.messages.contains(&SubMsg {
id: 0,
msg: CosmosMsg::Bank(BankMsg::Send {
to_address: "kujira17xpfvakm2amg962yls6f84z3kell8c5lp3pcxh".to_string(),
amount: coins(250, "ukuji"),
},),
gas_limit: None,
reply_on: ReplyOn::Never,
}));
assert!(res.messages.contains(&SubMsg {
id: 0,
msg: CosmosMsg::Bank(BankMsg::Send {
to_address: "another".to_string(),
amount: coins(750, "ukuji"),
},),
gas_limit: None,
reply_on: ReplyOn::Never,
}));

assert!(res.messages.contains(&SubMsg {
id: 0,
msg: CosmosMsg::Bank(BankMsg::Send {
to_address: "kujira17xpfvakm2amg962yls6f84z3kell8c5lp3pcxh".to_string(),
amount: coins(500, "another"),
},),
gas_limit: None,
reply_on: ReplyOn::Never,
}));

assert!(res.messages.contains(&SubMsg {
id: 0,
msg: CosmosMsg::Bank(BankMsg::Send {
to_address: "another".to_string(),
amount: coins(1500, "another"),
},),
gas_limit: None,
reply_on: ReplyOn::Never,
}));
}
}
4 changes: 2 additions & 2 deletions src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::state::Action;
pub struct InstantiateMsg {
pub owner: Addr,
pub executor: Addr,
pub target_denom: Denom,
pub target_denoms: Vec<Denom>,
pub target_addresses: Vec<(Addr, u8)>,
}

Expand Down Expand Up @@ -36,7 +36,7 @@ pub enum QueryMsg {
pub struct ConfigResponse {
pub owner: Addr,
pub executor: Addr,
pub target_denom: Denom,
pub target_denoms: Vec<Denom>,
pub target_addresses: Vec<(Addr, u8)>,
}

Expand Down
8 changes: 4 additions & 4 deletions src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ pub struct Config {
/// The address permitted to execute the crank
pub executor: Addr,

/// The denom that is transferred to the fee_collector at the end of every execution
pub target_denom: Denom,
/// The denoms that are transferred to the fee_collector at the end of every execution
pub target_denoms: Vec<Denom>,

/// The final destinations that `target_denom` is sent to (address, weight)
pub target_addresses: Vec<(Addr, u8)>,
Expand All @@ -42,7 +42,7 @@ impl From<InstantiateMsg> for Config {
Self {
owner: value.owner,
executor: value.executor,
target_denom: value.target_denom,
target_denoms: value.target_denoms,
target_addresses: value.target_addresses,
}
}
Expand All @@ -53,7 +53,7 @@ impl From<Config> for ConfigResponse {
Self {
owner: value.owner,
executor: value.executor,
target_denom: value.target_denom,
target_denoms: value.target_denoms,
target_addresses: value.target_addresses,
}
}
Expand Down

0 comments on commit 0574814

Please sign in to comment.