Skip to content

Commit

Permalink
feat: fee distribution (#61)
Browse files Browse the repository at this point in the history
* wip: swap and burn.
* wip: swap and burn.
* fix: swap msg and integration test denoms.
* feature: distribution configuration.
  • Loading branch information
desamtralized authored Jul 28, 2022
1 parent 47579da commit 56c8ed7
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 25 deletions.
6 changes: 2 additions & 4 deletions contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
![Build & Tests](https://github.com/Local-Terra/localterra-contracts/actions/workflows/rust.yml/badge.svg)
# Local Money Smart Contracts

# LocalTerra Smart Contracts

CosmWasm Smart Contracts for LocalTerra marketplace.
CosmWasm Smart Contracts for Local Money P2P Marketplace.
118 changes: 111 additions & 7 deletions contracts/contracts/trade/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use cosmwasm_std::{
coin, entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, Env,
MessageInfo, QueryRequest, Response, StdResult, SubMsg, Uint128, WasmMsg, WasmQuery,
coin, entry_point, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Decimal, Deps, DepsMut,
Env, MessageInfo, QueryRequest, Reply, ReplyOn, Response, StdError, StdResult, SubMsg, Uint128,
WasmMsg, WasmQuery,
};
use std::ops::Mul;
use std::str::FromStr;

use localterra_protocol::constants::{FUNDING_TIMEOUT, REQUEST_TIMEOUT};
use localterra_protocol::constants::{FUNDING_TIMEOUT, LOCAL_FEE, REQUEST_TIMEOUT};
use localterra_protocol::denom_utils::denom_to_string;
use localterra_protocol::guards::{
assert_caller_is_buyer_or_seller, assert_ownership, assert_trade_state_and_type,
Expand All @@ -16,13 +19,16 @@ use localterra_protocol::offer::{
load_offer, Arbitrator, Offer, OfferType, QueryMsg as OfferQueryMsg, TradeInfo,
};
use localterra_protocol::trade::{
ExecuteMsg, InstantiateMsg, NewTrade, QueryMsg, Trade, TradeModel, TradeState, TraderRole,
Asset, AssetInfo, ExecuteMsg, InstantiateMsg, NewTrade, QueryMsg, Swap, SwapMsg, Trade,
TradeModel, TradeState, TraderRole,
};
use localterra_protocol::trading_incentives::ExecuteMsg as TradingIncentivesMsg;

use crate::errors::TradeError;
use crate::errors::TradeError::HubAlreadyRegistered;

pub const SWAP_REPLY_ID: u64 = 1u64;

#[entry_point]
pub fn instantiate(
_deps: DepsMut,
Expand Down Expand Up @@ -409,7 +415,7 @@ fn release_escrow(
trade_id: String,
) -> Result<Response, TradeError> {
let mut trade = TradeModel::from_store(deps.storage, &trade_id);
let denom = denom_to_string(&trade.denom);
let trade_denom = denom_to_string(&trade.denom);

let arbitrator = trade.arbitrator.clone().unwrap_or(Addr::unchecked(""));
if trade.seller.eq(&info.sender) {
Expand Down Expand Up @@ -442,7 +448,13 @@ fn release_escrow(

//Calculate fees and final release amount
let mut send_msgs: Vec<SubMsg> = Vec::new();
let release_amount = trade.amount.clone();
let mut release_amount = trade.amount.clone();
let one = Uint128::new(1u128);
let fee = one.mul(Decimal::from_ratio(release_amount, LOCAL_FEE));
release_amount = release_amount.checked_sub(fee.clone()).unwrap();
let burn_amount = fee.mul(Decimal::from_ratio(hub_cfg.burn_fee_pct, 100u128));
let chain_amount = fee.mul(Decimal::from_ratio(hub_cfg.chain_fee_pct, 100u128));
let warchest_amount = fee.mul(Decimal::from_ratio(hub_cfg.warchest_fee_pct, 100u128));

//Create Trade Registration message to be sent to the Trading Incentives contract.
let register_trade_msg = SubMsg::new(CosmosMsg::Wasm(WasmMsg::Execute {
Expand All @@ -466,7 +478,50 @@ fn release_escrow(
// Send tokens to buyer
send_msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
to_address: trade.buyer.into_string(),
amount: vec![Coin::new(release_amount.u128(), denom.clone())],
amount: vec![Coin::new(release_amount.u128(), trade_denom.clone())],
})));

// Fee Distribution
let local_denom = denom_to_string(&hub_cfg.local_denom);
//If coin being traded is not $LOCAL, swap it and burn it on swap reply.
if trade_denom.ne(&local_denom) {
send_msgs.push(SubMsg {
id: SWAP_REPLY_ID,
msg: CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: hub_cfg.local_market_addr.to_string(),
msg: to_binary(&SwapMsg {
swap: Swap {
offer_asset: Asset {
info: AssetInfo::NativeToken {
denom: trade_denom.to_string(),
},
amount: burn_amount.clone(),
},
},
})
.unwrap(),
funds: vec![coin(burn_amount.u128(), trade_denom.clone())],
}),
gas_limit: None,
reply_on: ReplyOn::Success,
});
} else {
//If coin being traded is $LOCAL, add message burning the local_burn amount
send_msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Burn {
amount: vec![coin(burn_amount.u128(), local_denom.clone())],
})));
}

// Warchest
send_msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
to_address: hub_cfg.warchest_addr.to_string(),
amount: vec![coin(warchest_amount.u128(), trade_denom.clone())],
})));

// Chain Fee Sharing
send_msgs.push(SubMsg::new(CosmosMsg::Bank(BankMsg::Send {
to_address: hub_cfg.chain_fee_collector_addr.to_string(),
amount: vec![coin(chain_amount.u128(), trade_denom.clone())],
})));

let res = Response::new()
Expand All @@ -477,6 +532,55 @@ fn release_escrow(
Ok(res)
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
SWAP_REPLY_ID => handle_swap_reply(deps, msg),
id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))),
}
}

fn handle_swap_reply(_deps: DepsMut, msg: Reply) -> StdResult<Response> {
let attributes = msg
.result
.unwrap()
.events
.iter()
.find(|evt| evt.ty.eq("wasm"))
.unwrap()
.attributes
.clone();
let return_amount = attributes
.iter()
.find(|attr| attr.key.eq("return_amount"))
.unwrap()
.value
.clone();
let ask_asset = attributes
.iter()
.find(|attr| attr.key.eq("ask_asset"))
.unwrap()
.value
.clone();

//Burn $LOCAL
let burn_msg = CosmosMsg::Bank(BankMsg::Burn {
amount: vec![coin(
u128::from_str(return_amount.as_str()).unwrap(),
ask_asset.clone(),
)],
});

let res = Response::new()
.add_attributes(vec![
("event", "swap_reply"),
("burn_amount", return_amount.as_str()),
("demon", ask_asset.as_str()),
])
.add_submessage(SubMsg::new(burn_msg));
Ok(res)
}

fn refund_escrow(deps: DepsMut, env: Env, trade_id: String) -> Result<Response, TradeError> {
// Refund can only happen if trade state is TradeState::EscrowFunded and FundingTimeout is expired
let trade = TradeModel::from_store(deps.storage, &trade_id);
Expand Down
4 changes: 1 addition & 3 deletions contracts/packages/protocol/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
pub const OFFERS_KEY: &str = "offers";
pub const REQUEST_TIMEOUT: u64 = 20 * 60; // 20 mins
pub const FUNDING_TIMEOUT: u64 = 140 * 60; // 2hrs 20 mins
pub const LOCAL_TERRA_FEE: u128 = 100;
pub const WARCHEST_FEE: u128 = 0;
pub const ARBITRATOR_FEE: u128 = 10;
pub const LOCAL_FEE: u128 = 100;
5 changes: 5 additions & 0 deletions contracts/packages/protocol/src/hub.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,9 @@ pub struct HubConfig {
pub trading_incentives_addr: Addr,
pub local_market_addr: Addr,
pub local_denom: Denom,
pub chain_fee_collector_addr: Addr,
pub warchest_addr: Addr,
pub burn_fee_pct: u128,
pub chain_fee_pct: u128,
pub warchest_fee_pct: u128,
}
31 changes: 29 additions & 2 deletions contracts/packages/protocol/src/trade.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use crate::currencies::FiatCurrency;
use std::fmt::{self};

use cosmwasm_std::{Addr, Order, StdResult, Storage, Uint128};
use cw20::Denom;
use cw_storage_plus::{Bound, Index, IndexList, IndexedMap, MultiIndex};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::{self};

use crate::currencies::FiatCurrency;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {}
Expand Down Expand Up @@ -53,6 +55,31 @@ pub struct NewTrade {
pub taker: Addr,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct SwapMsg {
pub swap: Swap,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct Swap {
pub offer_asset: Asset,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Asset {
pub info: AssetInfo,
pub amount: Uint128,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AssetInfo {
Token { contract_addr: String },
NativeToken { denom: String },
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TradeState {
Expand Down
23 changes: 14 additions & 9 deletions integration-tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ const makerWallet = await DirectSecp256k1HdWallet.fromMnemonic(maker_seed, { pre
const makerAccounts = await makerWallet.getAccounts();
const makerAddr = makerAccounts[0].address;
console.log('makerAddr', makerAddr);
//const local_denom = { "native": `factory/${makerAddr}/local` }
const local_denom = { "native": process.env.DENOM }
const local_denom = { "native": process.env.LOCAL_DENOM }
const offer_denom = { "native": process.env.OFFER_DENOM };

const makerClient = await SigningCosmWasmClient.connectWithSigner(rpcEndpoint, makerWallet, {
broadcastTimeoutMs: 30 * 1000,
Expand Down Expand Up @@ -67,8 +67,13 @@ async function setupProtocol(codeIds) {
offer_addr: offerInstantiateResult.contractAddress,
trade_addr: tradeInstantiateResult.contractAddress,
trading_incentives_addr: tradeIncentivesInstantiateResult.contractAddress,
local_market_addr: makerAddr, //TODO: use actual address
local_denom
local_market_addr: process.env.LOCAL_MARKET,
local_denom,
chain_fee_collector_addr: process.env.CHAIN_FEE_COLLECTOR,
warchest_addr: process.env.WARCHEST_ADDR,
warchest_fee_pct: "50",
chain_fee_pct: "10",
burn_fee_pct: "40",
}
}
console.log('Hub Update Config - Msg = ', JSON.stringify(updatedConfigMsg));
Expand All @@ -88,7 +93,7 @@ async function create_offers(offer_addr) {
min_amount,
max_amount,
rate: "1",
denom: local_denom,
denom: offer_denom,
},
},
},
Expand Down Expand Up @@ -183,7 +188,7 @@ async function test(codeIds) {
// TODO replace tradeAddr to hubConfig.trade_addr
tradeAddr = hubCfg.trade_addr
console.log("**Trade created with Id:", tradeId);
return makerClient.getBalance(tradeAddr, local_denom.native);
return makerClient.getBalance(tradeAddr, offer_denom.native);
}).then((balance) => {
console.log("Escrow initial balance: ", JSON.stringify(balance))
console.log("*Accepting Trade Request");
Expand All @@ -193,7 +198,7 @@ async function test(codeIds) {
console.log("Accept Trade Request Result:", r);
console.log("*Funding Escrow*");
console.log('makerAddr:',makerAddr);
const funds = coins(min_amount, process.env.DENOM);
const funds = coins(min_amount, offer_denom.native);
return makerClient.execute(makerAddr, hubCfg.trade_addr, {"fund_escrow":{"trade_id": tradeId}}, "auto", "fund_escrow", funds);
}).then((r) => {
//Query State
Expand All @@ -203,7 +208,7 @@ async function test(codeIds) {
} else {
console.log("%Error%");
}
return makerClient.getBalance(tradeAddr, local_denom.native);
return makerClient.getBalance(tradeAddr, offer_denom.native);
}).then((balance) => {
console.log("Escrow balance: ", JSON.stringify(balance))
return makerClient.queryContractSmart(hubCfg.trade_addr, {"trade":{"id": tradeId}});
Expand All @@ -224,7 +229,7 @@ async function test(codeIds) {
}).then((r) => {
//Query State
console.log("Trade Release Result:", r);
return makerClient.getBalance(tradeAddr, local_denom.native);
return makerClient.getBalance(tradeAddr, offer_denom.native);
}).then((balance) => {
console.log("Escrow final balance: ", JSON.stringify(balance))
return makerClient.queryContractSmart(hubCfg.trade_addr, {"trade":{"id": tradeId}});
Expand Down

0 comments on commit 56c8ed7

Please sign in to comment.