Skip to content

Commit

Permalink
Merge branch 'main' into 5965-remove-html-with-certificate-go-straigh…
Browse files Browse the repository at this point in the history
…t-to-pdf-generation
  • Loading branch information
jschill authored Jan 17, 2024
2 parents 65701e6 + d77758f commit ad48447
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 37 deletions.
30 changes: 13 additions & 17 deletions cosmwasm/contracts/plastic-credit-marketplace/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use cosmos_sdk_proto::cosmos::authz::v1beta1::MsgExec;
use cosmos_sdk_proto::traits::{Message, TypeUrl};
use cosmos_sdk_proto::traits::MessageExt;
use cosmwasm_std::{entry_point, Binary, DepsMut, Env, MessageInfo, Response, Uint64, Coin, CosmosMsg, BankMsg, Addr, Decimal, Timestamp, StdError, Uint128};
use fee_splitter::{edit_fee_split_config, get_fee_split, Share};
use fee_splitter::{edit_fee_split_config, Share};
use crate::{msg::ExecuteMsg, error::ContractError, state::{LISTINGS, Listing}};
use crate::error::ContractError::FeeSplitError;
use crate::query::get_price_and_fee;
use crate::state::{ADMIN, Freeze, freezes};

const MAX_TIMEOUT_SECONDS : u64 = 2419200; // 4 weeks
Expand Down Expand Up @@ -93,11 +94,11 @@ pub fn execute_buy_credits(
return Err(ContractError::NotEnoughCredits {});
}

let total_price = listing.price_per_credit.amount.checked_mul(number_of_credits_to_buy.into()).unwrap();
if info.funds.len() != 1 || info.funds[0].denom != listing.price_per_credit.denom || info.funds[0].amount < total_price {
let (total_price, fee, fee_split_msgs) = get_price_and_fee(deps.as_ref(), listing.clone(), number_of_credits_to_buy);
if info.funds.len() != 1 || info.funds[0].denom != listing.price_per_credit.denom || info.funds[0].amount < total_price.amount {
return Err(ContractError::NotEnoughFunds {});
}
if info.funds[0].amount > total_price { // We can skip the denom check here because it is triggered in the previous if statement
if info.funds[0].amount > total_price.amount { // We can skip the denom check here because it is triggered in the previous if statement
return Err(ContractError::TooMuchFunds {});
}

Expand All @@ -119,11 +120,11 @@ pub fn execute_buy_credits(
retiring_entity_additional_data,
);

let funds_before_fee_split = Coin {
let remaining_amount = total_price.amount.checked_sub(fee.amount).unwrap();
let funds_after_split = Coin {
denom: listing.price_per_credit.denom.clone(),
amount: total_price,
amount: remaining_amount,
};
let (fee_split_msgs, funds_after_split, _) = get_fee_split(deps.storage, funds_before_fee_split).unwrap();
let transfer_funds_to_seller_msg = CosmosMsg::Bank(BankMsg::Send {
to_address: listing.owner.to_string(),
amount: vec![funds_after_split],
Expand Down Expand Up @@ -401,20 +402,15 @@ fn execute_release_frozen_credits(
freezes().save(deps.storage, (Addr::unchecked(owner.clone()), denom.clone(), buyer.clone()), &freeze)?;
}

// We need the original_price to calculate the fee split
let original_price = listing.price_per_credit.amount.checked_mul(number_of_credits_to_release.into()).unwrap();
let original_price_as_coin = Coin {
denom: listing.price_per_credit.denom.clone(),
amount: original_price,
};
let (fee_split_msgs, _, fee_amount) = get_fee_split(deps.storage, original_price_as_coin).unwrap();
if fee_amount.amount > Uint128::from(0u128) {
if info.funds.len() != 1 || info.funds[0].denom != listing.price_per_credit.denom || info.funds[0].amount < fee_amount.amount {
let (_, fee, fee_split_msgs) = get_price_and_fee(deps.as_ref(), listing.clone(), number_of_credits_to_release);

if fee.amount > Uint128::from(0u128) {
if info.funds.len() != 1 || info.funds[0].denom != listing.price_per_credit.denom || info.funds[0].amount < fee.amount {
return Err(ContractError::NotEnoughFunds {});
}
}

if info.funds.len() > 0 && info.funds[0].amount > fee_amount.amount { // We can skip the denom check here because it is triggered in the previous if statement
if info.funds.len() > 0 && info.funds[0].amount > fee.amount { // We can skip the denom check here because it is triggered in the previous if statement
return Err(ContractError::TooMuchFunds {});
}

Expand Down
12 changes: 12 additions & 0 deletions cosmwasm/contracts/plastic-credit-marketplace/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ pub enum QueryMsg {
},
#[returns(fee_splitter::Config)]
FeeSplitConfig {},
#[returns(PriceResponse)]
Price {
owner: Addr,
denom: String,
number_of_credits_to_buy: u64,
},
}

#[cw_serde]
Expand All @@ -94,4 +100,10 @@ pub struct ListingsResponse {
#[cw_serde]
pub struct ListingResponse {
pub listing: Listing,
}

#[cw_serde]
pub struct PriceResponse {
pub total_price: Coin,
pub fee: Coin,
}
134 changes: 131 additions & 3 deletions cosmwasm/contracts/plastic-credit-marketplace/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use cosmwasm_std::{entry_point, Deps, Env, StdResult, Binary, to_binary, Addr};
use cosmwasm_std::{entry_point, Deps, Env, StdResult, Binary, to_binary, Addr, Coin, CosmosMsg};
use cw_storage_plus::Bound;
use fee_splitter::get_fee_split;

use crate::{msg::{QueryMsg, ListingResponse, ListingsResponse}, state::{LISTINGS, Listing}};
use crate::msg::PriceResponse;

pub const DEFAULT_LIMIT: u64 = 30;

Expand All @@ -11,6 +13,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
QueryMsg::Listing {owner, denom} => to_binary(&listing(deps, owner, denom)?),
QueryMsg::Listings { limit, start_after } => to_binary(&listings(deps, start_after, limit)?),
QueryMsg::FeeSplitConfig {} => to_binary(&fee_splitter::get_config(deps.storage)?),
QueryMsg::Price { owner, denom, number_of_credits_to_buy } => to_binary(&price(deps, owner, denom, number_of_credits_to_buy)?),
}
}

Expand Down Expand Up @@ -42,14 +45,38 @@ pub fn listing(
Ok(ListingResponse { listing })
}

pub fn price(
deps: Deps,
owner: Addr,
denom: String,
number_of_credits_to_buy: u64,
) -> StdResult<PriceResponse> {
let listing = LISTINGS.load(deps.storage, (owner, denom.clone()))?;

let (total_price, fee, _) = get_price_and_fee(deps, listing, number_of_credits_to_buy);

Ok(PriceResponse { total_price, fee })
}

pub fn get_price_and_fee(deps: Deps, listing: Listing, number_of_credits_to_buy: u64) -> (Coin, Coin, Vec<CosmosMsg>) {
let total_price_amount = listing.price_per_credit.amount.checked_mul(number_of_credits_to_buy.into()).unwrap();
let total_price = Coin {
denom: listing.price_per_credit.denom.clone(),
amount: total_price_amount,
};
let (fee_split_msgs, fee) = get_fee_split(deps.storage, total_price.clone()).unwrap();

(total_price, fee, fee_split_msgs)
}

#[cfg(test)]
mod tests {
mod query_listings_tests {
use cosmwasm_std::{Coin, coins, from_binary, Uint128, Uint64, StdError, Decimal, Addr};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use crate::execute::execute;
use crate::{instantiate, query};
use crate::msg::{ExecuteMsg, ListingsResponse, InstantiateMsg};
use crate::msg::{ExecuteMsg, ListingsResponse, InstantiateMsg, ListingResponse};
use crate::query::query;

#[test]
Expand All @@ -73,7 +100,7 @@ mod tests {
owner: info.sender.clone(),
denom: "ptest".to_string(),
}).unwrap();
let res: crate::msg::ListingResponse = from_binary(&res).unwrap();
let res: ListingResponse = from_binary(&res).unwrap();
assert_eq!(res.listing.denom, "ptest");
assert_eq!(res.listing.number_of_credits, Uint64::from(42u64));
assert_eq!(res.listing.price_per_credit, Coin {
Expand Down Expand Up @@ -226,4 +253,105 @@ mod tests {
assert_eq!(res.shares[0].percentage, Decimal::percent(100));
}
}

mod query_price {
use cosmwasm_std::{Coin, coins, Decimal, from_binary, StdError, Uint128, Uint64};
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
use fee_splitter::Share;
use crate::execute::execute;
use crate::{instantiate, query};
use crate::msg::{ExecuteMsg, InstantiateMsg, PriceResponse};
use crate::query::query;

#[test]
fn test_query_price() {
let mut deps = mock_dependencies();
let info = mock_info("creator", &coins(2, "token"));
instantiate(deps.as_mut(), mock_env(), info.clone(), InstantiateMsg{ admin: info.sender.to_string(), fee_percentage: Decimal::percent(0), shares: vec![] }).unwrap();

let msg = ExecuteMsg::CreateListing {
denom: "ptest".to_string(),
number_of_credits: Uint64::from(42u64),
price_per_credit: Coin {
denom: "token".to_string(),
amount: Uint128::from(1337u128),
},
operator: None,
};
execute(deps.as_mut(), mock_env(), info.clone(), msg.clone()).unwrap();

let res = query(deps.as_ref(), mock_env(), query::QueryMsg::Price {
owner: info.sender.clone(),
denom: "ptest".to_string(),
number_of_credits_to_buy: 11,
}).unwrap();
let res: PriceResponse = from_binary(&res).unwrap();
assert_eq!(res.total_price, Coin {
denom: "token".to_string(),
amount: Uint128::from(14707u128),
});
assert_eq!(res.fee, Coin {
denom: "token".to_string(),
amount: Uint128::from(0u128)
});
}

#[test]
fn test_query_price_with_fee() {
let mut deps = mock_dependencies();
let creator_info = mock_info("creator", &coins(2, "token"));
let dev_share = Share {
address: "dev".to_string(),
percentage: Decimal::percent(90),
};
let user_share = Share {
address: "user".to_string(),
percentage: Decimal::percent(10),
};
instantiate(deps.as_mut(), mock_env(), creator_info.clone(), InstantiateMsg { admin: creator_info.sender.to_string(), fee_percentage: Decimal::permille(5), shares: vec![dev_share, user_share] }).unwrap();

let msg = ExecuteMsg::CreateListing {
denom: "ptest".to_string(),
number_of_credits: Uint64::from(42u64),
price_per_credit: Coin {
denom: "token".to_string(),
amount: Uint128::from(1337u128),
},
operator: None,
};
execute(deps.as_mut(), mock_env(), creator_info.clone(), msg.clone()).unwrap();

let res = query(deps.as_ref(), mock_env(), query::QueryMsg::Price {
owner: creator_info.sender.clone(),
denom: "ptest".to_string(),
number_of_credits_to_buy: 11,
}).unwrap();
let res: PriceResponse = from_binary(&res).unwrap();
assert_eq!(res.total_price, Coin {
denom: "token".to_string(),
amount: Uint128::from(14707u128),
});
assert_eq!(res.fee, Coin {
denom: "token".to_string(),
amount: Uint128::from(73u128)
});
}

#[test]
fn test_query_price_listing_not_found() {
let mut deps = mock_dependencies();
let creator_info = mock_info("creator", &coins(2, "token"));
instantiate(deps.as_mut(), mock_env(), creator_info.clone(), InstantiateMsg{ admin: creator_info.sender.to_string(), fee_percentage: Decimal::percent(0), shares: vec![] }).unwrap();

let res = query(deps.as_ref(), mock_env(), query::QueryMsg::Price {
owner: creator_info.sender.clone(),
denom: "ptest".to_string(),
number_of_credits_to_buy: 11,
});
match res {
Ok(_) => panic!("Expected error"),
Err(e) => assert_eq!(e, StdError::NotFound { kind: "plastic_credit_marketplace::state::Listing".to_string() }),
}
}
}
}
23 changes: 6 additions & 17 deletions cosmwasm/packages/fee-splitter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ pub fn edit_fee_split_config(storage: &mut dyn Storage, fee_percentage: Decimal,
}

// Returns a tuple of (msgs, remaining_amount_as_coin, fee_amount_as_coin)
pub fn get_fee_split(storage: &dyn Storage, full_price: Coin) -> Result<(Vec<CosmosMsg>, Coin, Coin), FeeSplitterError> {
pub fn get_fee_split(storage: &dyn Storage, full_price: Coin) -> Result<(Vec<CosmosMsg>, Coin), FeeSplitterError> {
let config = CONFIG.load(storage).unwrap();
let denom = full_price.denom.clone();
if Decimal::is_zero(&config.fee_percentage) || config.shares.is_empty() {
return Ok((vec![], full_price, Coin {
return Ok((vec![], Coin {
denom: denom.clone(),
amount: 0u128.into(),
}));
Expand All @@ -89,16 +89,11 @@ pub fn get_fee_split(storage: &dyn Storage, full_price: Coin) -> Result<(Vec<Cos
}

let fee_as_u128 = fee.numerator().checked_div(fee.denominator()).unwrap();
let remaining_amount = full_price.amount.checked_sub(fee_as_u128).unwrap();
let remaining_as_coin = Coin {
denom: denom.clone(),
amount: remaining_amount,
};
let fee_amount_as_coin = Coin {
denom: denom.clone(),
amount: fee_as_u128,
};
Ok((msgs, remaining_as_coin, fee_amount_as_coin))
Ok((msgs, fee_amount_as_coin))
}

pub fn get_config(storage: &dyn Storage) -> Result<Config, StdError> {
Expand Down Expand Up @@ -370,13 +365,11 @@ mod tests {
instantiate_fee_splitter(deps.as_mut().storage, Decimal::permille(5), vec![dev_share.clone(), stakers_share.clone()]).unwrap();

// 1000 umpwr fee
let (fee_split_msgs, remaining_amount, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
let (fee_split_msgs, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
denom: "umpwr".to_string(),
amount: Uint128::new(1000),
}).unwrap();
assert_eq!(fee_split_msgs.len(), 2);
assert_eq!(remaining_amount.denom, "umpwr");
assert_eq!(remaining_amount.amount, Uint128::new(995));
assert_eq!(fee_amount.denom, "umpwr");
assert_eq!(fee_amount.amount, Uint128::new(5));

Expand Down Expand Up @@ -414,13 +407,11 @@ mod tests {

// 500 umpwr fee, with 0.5% fee and 80/20 split means that dev gets 2 umpwr and stakers get 0 (rounded down)
// and the remaining 498 umpwr is returned
let (fee_split_msgs, remaining_amount, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
let (fee_split_msgs, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
denom: "umpwr".to_string(),
amount: Uint128::new(500),
}).unwrap();
assert_eq!(fee_split_msgs.len(), 2);
assert_eq!(remaining_amount.denom, "umpwr");
assert_eq!(remaining_amount.amount, Uint128::new(498));
assert_eq!(fee_amount.denom, "umpwr");
assert_eq!(fee_amount.amount, Uint128::new(2));

Expand Down Expand Up @@ -449,13 +440,11 @@ mod tests {
let mut deps = mock_dependencies();
instantiate_fee_splitter(deps.as_mut().storage, Decimal::zero(), vec![]).unwrap();

let (fee_split_msgs, remaining_amount, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
let (fee_split_msgs, fee_amount) = get_fee_split(deps.as_mut().storage, Coin {
denom: "umpwr".to_string(),
amount: Uint128::new(100),
}).unwrap();
assert_eq!(fee_split_msgs.len(), 0);
assert_eq!(remaining_amount.denom, "umpwr");
assert_eq!(remaining_amount.amount, Uint128::new(100));
assert_eq!(fee_amount.denom, "umpwr");
assert_eq!(fee_amount.amount, Uint128::new(0));
}
Expand Down

0 comments on commit ad48447

Please sign in to comment.