Skip to content

Commit

Permalink
Merge pull request #107 from FigureTechnologies/brashidnia/allow-zero…
Browse files Browse the repository at this point in the history
…-fees

Allow zero fees to be in request
  • Loading branch information
brashidnia-figure authored Apr 6, 2023
2 parents 3dc9e56 + 0c401bd commit 13baf36
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 47 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ats-smart-contract"
version = "0.18.0"
version = "0.18.1"
authors = ["Ken Talley <[email protected]>"]
edition = "2018"

Expand Down
68 changes: 31 additions & 37 deletions src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,48 +503,42 @@ fn create_bid(
return Err(ContractError::SentFundsOrderMismatch);
}

// if bid fee exists, calculate and compare to sent fee_size
match contract_info.bid_fee_info {
// Get the bid fee rate (0 if not set)
let bid_fee_rate = match contract_info.bid_fee_info {
Some(bid_fee_info) => {
let bid_fee_rate = Decimal::from_str(&bid_fee_info.rate).map_err(|_| {
ContractError::InvalidFields {
fields: vec![String::from("ContractInfo.bid_fee_info.rate")],
}
})?;

// if the bid fee rate is 0, there should not be any sent fees
if bid_fee_rate.eq(&Decimal::zero()) && bid_order.fee.is_some() {
return Err(ContractError::SentFees);
}
Decimal::from_str(&bid_fee_info.rate).map_err(|_| ContractError::InvalidFields {
fields: vec![String::from("ContractInfo.bid_fee_info.rate")],
})?
}
None => Decimal::from(0),
};

let calculated_fee_size = bid_fee_rate
.checked_mul(total)
.ok_or(ContractError::TotalOverflow)?
.round_dp_with_strategy(0, RoundingStrategy::MidpointAwayFromZero)
.to_u128()
.ok_or(ContractError::TotalOverflow)?;
// Calculate the expected fees (bid_fee_rate * total)
let calculated_fee_size = bid_fee_rate
.checked_mul(total)
.ok_or(ContractError::TotalOverflow)?
.round_dp_with_strategy(0, RoundingStrategy::MidpointAwayFromZero)
.to_u128()
.ok_or(ContractError::TotalOverflow)?;

match &mut bid_order.fee {
Some(fee) => {
if fee.amount.ne(&Uint128::new(calculated_fee_size)) {
return Err(ContractError::InvalidFeeSize {
fee_rate: bid_fee_info.rate,
});
}
if fee.denom.ne(&bid_order.quote.denom) {
return Err(ContractError::SentFundsOrderMismatch);
}
}
_ => {
return Err(ContractError::InvalidFeeSize {
fee_rate: bid_fee_info.rate,
})
}
match &mut bid_order.fee {
Some(fee) => {
// If the user sent fees, then make sure the amount + denom match
if fee.amount.ne(&Uint128::new(calculated_fee_size)) {
return Err(ContractError::InvalidFeeSize {
fee_rate: bid_fee_rate.to_string(),
});
}
if fee.denom.ne(&bid_order.quote.denom) {
return Err(ContractError::SentFundsOrderMismatch);
}
}
None => {
if bid_order.fee.is_some() {
return Err(ContractError::SentFees);
// If the user did not send fees, make sure the calculated fees was 0
if calculated_fee_size.ne(&0) {
return Err(ContractError::InvalidFeeSize {
fee_rate: bid_fee_rate.to_string(),
});
}
}
}
Expand Down Expand Up @@ -1652,7 +1646,7 @@ pub fn query(deps: Deps<ProvenanceQuery>, _env: Env, msg: QueryMsg) -> StdResult
// unit tests
#[cfg(test)]
mod tests {
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::{Addr, Storage, Uint128};

use super::*;
Expand Down
7 changes: 1 addition & 6 deletions src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl Validate for ExecuteMsg {
ExecuteMsg::CreateBid {
id,
base,
fee,
fee: _,
price,
quote,
quote_size,
Expand All @@ -214,11 +214,6 @@ impl Validate for ExecuteMsg {
if base.is_empty() {
invalid_fields.push("base");
}
if let Some(fee) = fee {
if fee.amount.lt(&Uint128::new(1)) {
invalid_fields.push("fee");
}
}
if price.is_empty() {
invalid_fields.push("price");
}
Expand Down
1 change: 1 addition & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ pub mod execute;
pub mod instantiate;
pub mod query;
pub mod test_constants;
#[cfg(test)]
pub mod test_setup_utils;
pub mod test_utils;
138 changes: 136 additions & 2 deletions src/tests/execute/create_bid_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ mod create_bid_tests {
use crate::contract_info::ContractInfoV3;
use crate::error::ContractError;
use crate::msg::ExecuteMsg;
use crate::tests::test_constants::UNHYPHENATED_BID_ID;
use crate::tests::test_setup_utils::{setup_test_base, setup_test_base_contract_v3};
use crate::tests::test_constants::{
BASE_DENOM, HYPHENATED_BID_ID, QUOTE_DENOM_1, UNHYPHENATED_BID_ID,
};
use crate::tests::test_setup_utils::{
set_default_required_attributes, setup_test_base, setup_test_base_contract_v3,
};
use crate::tests::test_utils::validate_execute_invalid_id_field;
use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR};
use cosmwasm_std::{attr, coin, coins, from_binary, Addr, Binary, Coin, Uint128};
Expand Down Expand Up @@ -44,6 +48,136 @@ mod create_bid_tests {
\"supply_fixed\": false
}";

#[test]
fn create_bid_valid_with_fee_less_than_one_but_wrong_denom_return_err() {
let mut deps = mock_dependencies(&[]);
setup_test_base_contract_v3(&mut deps.storage);

// create bid data
let create_bid_msg = ExecuteMsg::CreateBid {
id: HYPHENATED_BID_ID.to_string(),
base: BASE_DENOM.into(),
fee: Some(coin(0, "BAD_DENOM")), // Incorrect fee denom
price: "2.5".into(),
quote: QUOTE_DENOM_1.into(),
quote_size: Uint128::new(250),
size: Uint128::new(100),
};
// Add bid required attributes
set_default_required_attributes(&mut deps.querier, "bidder", false, true);

let bidder_info = mock_info("bidder", &coins(250, QUOTE_DENOM_1));

// execute create bid
let create_bid_response = execute(
deps.as_mut(),
mock_env(),
bidder_info.clone(),
create_bid_msg.clone(),
);

// verify execute create bid response
match create_bid_response {
Ok(_) => panic!("expected error, but ok"),
Err(ContractError::SentFundsOrderMismatch) => {}
Err(error) => panic!("unexpected error: {:?}", error),
}
}

#[test]
fn create_bid_valid_with_fee_less_than_one_is_accepted() {
let mut deps = mock_dependencies(&[]);
setup_test_base_contract_v3(&mut deps.storage);

// create bid data
let create_bid_msg = ExecuteMsg::CreateBid {
id: HYPHENATED_BID_ID.to_string(),
base: BASE_DENOM.into(),
fee: Some(coin(0, QUOTE_DENOM_1)),
price: "2.5".into(),
quote: QUOTE_DENOM_1.into(),
quote_size: Uint128::new(250),
size: Uint128::new(100),
};
// Add bid required attributes
set_default_required_attributes(&mut deps.querier, "bidder", false, true);

let bidder_info = mock_info("bidder", &coins(250, QUOTE_DENOM_1));

// execute create bid
let create_bid_response = execute(
deps.as_mut(),
mock_env(),
bidder_info.clone(),
create_bid_msg.clone(),
);

// verify execute create bid response
match create_bid_response {
Ok(response) => {
assert_eq!(response.attributes.len(), 8);
assert_eq!(response.attributes[0], attr("action", "create_bid"));
assert_eq!(response.attributes[1], attr("base", BASE_DENOM));
assert_eq!(
response.attributes[2],
attr("id", "c13f8888-ca43-4a64-ab1b-1ca8d60aa49b")
);
assert_eq!(
response.attributes[3],
attr("fee", format!("{:?}", coin(0, QUOTE_DENOM_1)))
);
assert_eq!(response.attributes[4], attr("price", "2.5"));
assert_eq!(response.attributes[5], attr("quote", QUOTE_DENOM_1));
assert_eq!(response.attributes[6], attr("quote_size", "250"));
assert_eq!(response.attributes[7], attr("size", "100"));
}
Err(error) => {
panic!("failed to create bid: {:?}", error)
}
}

// verify bid order stored
let bid_storage = get_bid_storage_read(&deps.storage);
if let ExecuteMsg::CreateBid {
id,
base,
fee,
quote,
quote_size,
price,
size,
} = create_bid_msg
{
match bid_storage.load("c13f8888-ca43-4a64-ab1b-1ca8d60aa49b".as_bytes()) {
Ok(stored_order) => {
assert_eq!(
stored_order,
BidOrderV2 {
base: Coin {
amount: size,
denom: base,
},
events: vec![],
fee,
id,
owner: bidder_info.sender,
price,
quote: Coin {
amount: quote_size,
denom: quote,
},
}
)
}
_ => {
panic!("bid order was not found in storage")
}
}
} else {
panic!("bid_message is not a CreateBid type. this is bad.")
}
}

#[test]
fn create_bid_valid_data_unhyphenated() {
let mut deps = mock_dependencies(&[]);
Expand Down
2 changes: 2 additions & 0 deletions src/tests/test_constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@ pub const UNHYPHENATED_ASK_ID: &str = "ab5f5a62f6fc46d1aa8451ccc51ec367";
pub const HYPHENATED_BID_ID: &str = "c13f8888-ca43-4a64-ab1b-1ca8d60aa49b";
pub const UNHYPHENATED_BID_ID: &str = "c13f8888ca434a64ab1b1ca8d60aa49b";
pub const BASE_DENOM: &str = "base_denom";
pub const QUOTE_DENOM_1: &str = "quote_1";
pub const QUOTE_DENOM_2: &str = "quote_2";
pub const APPROVER_1: &str = "approver_1";
pub const APPROVER_2: &str = "approver_2";
23 changes: 23 additions & 0 deletions src/tests/test_setup_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::bid_order::{get_bid_storage, BidOrderV2};
use crate::contract_info::{set_contract_info, ContractInfoV3};
use crate::tests::test_constants::{APPROVER_1, APPROVER_2, BASE_DENOM};
use cosmwasm_std::{Addr, Storage, Uint128};
use provwasm_mocks::ProvenanceMockQuerier;

pub fn setup_test_base(storage: &mut dyn Storage, contract_info: &ContractInfoV3) {
if let Err(error) = set_contract_info(storage, contract_info) {
Expand Down Expand Up @@ -44,3 +45,25 @@ pub fn store_test_bid(storage: &mut dyn Storage, bid_order: &BidOrderV2) {
panic!("unexpected error: {:?}", error);
};
}

pub fn set_default_required_attributes(
querier: &mut ProvenanceMockQuerier,
address: &str,
ask_attributes: bool,
bid_attributes: bool,
) {
let mut attributes: Vec<(&str, &str, &str)> = Vec::new();
if ask_attributes {
attributes.append(&mut vec![
("ask_tag_1", "ask_tag_1_value", "String"),
("ask_tag_2", "ask_tag_2_value", "String"),
])
}
if bid_attributes {
attributes.append(&mut vec![
("bid_tag_1", "bid_tag_1_value", "String"),
("bid_tag_2", "bid_tag_2_value", "String"),
])
}
querier.with_attributes(address, &attributes);
}

0 comments on commit 13baf36

Please sign in to comment.