Setup cargo workspace to support multiple contracts
This commit is contained in:
parent
f63ae76832
commit
f9f7a81cb0
8 changed files with 2 additions and 424 deletions
|
@ -16,7 +16,7 @@ CosmWasm smart contracts used in the Bisenzone MVP.
|
|||
```bash
|
||||
# terminal-2
|
||||
./scripts/build-contract.sh
|
||||
./scripts/deploy-contract.sh artifacts/cw_mtcs.wasm
|
||||
./scripts/deploy-contract.sh artifacts/cofi_karma_game.wasm
|
||||
```
|
||||
* Set contract env var (using the output of the `deploy.sh` script) -
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ set -eo pipefail
|
|||
|
||||
usage() {
|
||||
echo "Usage: $0 WASM_BIN [COUNT]"
|
||||
echo "Example: $0 artifacts/cw_mtcs.wasm"
|
||||
echo "Example: $0 artifacts/cofi_karma_game.wasm"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
use cosmwasm_schema::write_api;
|
||||
|
||||
use cw_mtcs::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
|
||||
|
||||
fn main() {
|
||||
write_api! {
|
||||
instantiate: InstantiateMsg,
|
||||
execute: ExecuteMsg,
|
||||
query: QueryMsg,
|
||||
}
|
||||
}
|
|
@ -1,322 +0,0 @@
|
|||
use cosmwasm_std::{
|
||||
entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response,
|
||||
StdResult, Uint128,
|
||||
};
|
||||
use cw2::set_contract_version;
|
||||
use cw20_base::{
|
||||
contract::{execute_mint, query_balance},
|
||||
state::{MinterData, TokenInfo, TOKEN_INFO},
|
||||
};
|
||||
|
||||
use crate::error::ContractError;
|
||||
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
|
||||
use crate::state::{State, STATE, UTILIZATION};
|
||||
|
||||
// version info for migration info
|
||||
const CONTRACT_NAME: &str = "crates.io:cw-mtcs";
|
||||
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn instantiate(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
_msg: InstantiateMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
let state = State {
|
||||
owner: info.sender.to_string(),
|
||||
};
|
||||
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
STATE.save(deps.storage, &state)?;
|
||||
|
||||
// store token info using cw20-base format
|
||||
let data = TokenInfo {
|
||||
name: "liquidity savings".to_string(),
|
||||
symbol: "!$".to_string(),
|
||||
decimals: 0,
|
||||
total_supply: Uint128::zero(),
|
||||
// set self as minter, so we can properly execute mint and burn
|
||||
mint: Some(MinterData {
|
||||
minter: env.contract.address,
|
||||
cap: None,
|
||||
}),
|
||||
};
|
||||
TOKEN_INFO.save(deps.storage, &data)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("method", "instantiate")
|
||||
.add_attribute("owner", info.sender))
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn execute(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::UploadObligation {
|
||||
creditor,
|
||||
amount,
|
||||
memo,
|
||||
} => execute::upload_obligation(deps, info, creditor, amount, memo),
|
||||
ExecuteMsg::ApplyCycle { path, amount } => {
|
||||
execute::apply_cycle(deps, env, info, path, amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod execute {
|
||||
use cosmwasm_std::Uint128;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn upload_obligation(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
creditor: String,
|
||||
amount: Uint128,
|
||||
memo: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let creditor = deps.api.addr_validate(&creditor)?;
|
||||
|
||||
UTILIZATION.update(
|
||||
deps.storage,
|
||||
(&creditor, &info.sender),
|
||||
|utilization| -> Result<_, ContractError> {
|
||||
// Uncomment if we want to only allow ourselves to add obligations
|
||||
// if info.sender != state.owner {
|
||||
// return Err(ContractError::Unauthorized);
|
||||
// }
|
||||
|
||||
let utilization = utilization.unwrap_or_default() + amount;
|
||||
Ok(utilization)
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "upload_obligation")
|
||||
.add_attribute("memo", memo))
|
||||
}
|
||||
|
||||
pub fn apply_cycle(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
path: Vec<String>,
|
||||
amount: Uint128,
|
||||
) -> Result<Response, ContractError> {
|
||||
let mut volume_cleared = Uint128::zero();
|
||||
|
||||
let path = path
|
||||
.into_iter()
|
||||
.map(|addr| deps.api.addr_validate(&addr))
|
||||
.collect::<StdResult<Vec<Addr>>>()?;
|
||||
|
||||
validate_cycle(&path, amount, &deps)?;
|
||||
|
||||
for from_to in path.windows(2) {
|
||||
let (from, to) = (&from_to[0], &from_to[1]);
|
||||
|
||||
UTILIZATION.update(
|
||||
deps.storage,
|
||||
(to, from),
|
||||
|utilization| -> Result<_, ContractError> {
|
||||
let utilization = utilization.unwrap_or_default() - amount;
|
||||
volume_cleared += amount;
|
||||
|
||||
Ok(utilization)
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// call into cw20-base to mint the token, call as self as no one else is allowed
|
||||
let sub_info = MessageInfo {
|
||||
sender: env.contract.address.clone(),
|
||||
funds: vec![],
|
||||
};
|
||||
execute_mint(deps, env, sub_info, info.sender.to_string(), volume_cleared)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "apply_cycle")
|
||||
.add_attribute("volume_cleared", format!("{}", volume_cleared)))
|
||||
}
|
||||
|
||||
fn validate_cycle(path: &[Addr], amount: Uint128, deps: &DepsMut) -> Result<(), ContractError> {
|
||||
if path.first() != path.last() {
|
||||
return Err(ContractError::PathNotCycle);
|
||||
}
|
||||
|
||||
for from_to in path.windows(2) {
|
||||
let (from, to) = (&from_to[0], &from_to[1]);
|
||||
|
||||
if amount > UTILIZATION.load(deps.storage, (to, from))? {
|
||||
return Err(ContractError::ClearingTooMuch);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(not(feature = "library"), entry_point)]
|
||||
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
|
||||
match msg {
|
||||
QueryMsg::GetObligations { creditor } => {
|
||||
to_json_binary(&query::get_obligations(deps, creditor)?)
|
||||
}
|
||||
QueryMsg::Balance { address } => to_json_binary(&query_balance(deps, address)?),
|
||||
}
|
||||
}
|
||||
|
||||
pub mod query {
|
||||
use super::*;
|
||||
use cosmwasm_std::Order;
|
||||
|
||||
use crate::msg::GetObligationsResponse;
|
||||
use crate::state::UTILIZATION;
|
||||
|
||||
pub fn get_obligations(deps: Deps, creditor: String) -> StdResult<GetObligationsResponse> {
|
||||
let creditor = deps.api.addr_validate(&creditor)?;
|
||||
|
||||
let keys = UTILIZATION
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.collect::<StdResult<Vec<(Addr, Addr)>>>()?
|
||||
.into_iter()
|
||||
.filter(|(from, _)| from == creditor);
|
||||
Ok(GetObligationsResponse {
|
||||
obligations: keys
|
||||
.map(|(from, to)| {
|
||||
let utilization = UTILIZATION.load(deps.storage, (&from, &to)).unwrap();
|
||||
(to.to_string(), utilization)
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::{
|
||||
mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage,
|
||||
};
|
||||
use cosmwasm_std::{coins, from_json, OwnedDeps};
|
||||
use cw20::BalanceResponse;
|
||||
|
||||
use crate::msg::GetObligationsResponse;
|
||||
|
||||
const ALICE_ADDRESS: &str = "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv";
|
||||
const BOB_ADDRESS: &str = "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw";
|
||||
const CHARLIE_ADDRESS: &str = "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f";
|
||||
|
||||
#[test]
|
||||
fn test_initialization() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
||||
let msg = InstantiateMsg;
|
||||
let info = mock_info("creator", &coins(1000, "earth"));
|
||||
|
||||
// we can just call .unwrap() to assert this was a success
|
||||
let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
assert_eq!(0, res.messages.len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upload_obligation() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
||||
let msg = InstantiateMsg;
|
||||
let info = mock_info("creator", &coins(2, "token"));
|
||||
let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
|
||||
create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob");
|
||||
|
||||
let res = query(
|
||||
deps.as_ref(),
|
||||
mock_env(),
|
||||
QueryMsg::GetObligations {
|
||||
creditor: BOB_ADDRESS.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let value: GetObligationsResponse = from_json(&res).unwrap();
|
||||
assert_eq!(&100u32.into(), value.obligations[0].1);
|
||||
}
|
||||
|
||||
fn create_obligation(
|
||||
deps: &mut OwnedDeps<MockStorage, MockApi, MockQuerier>,
|
||||
debtor: &str,
|
||||
creditor: &str,
|
||||
amount: u32,
|
||||
memo: &str,
|
||||
) {
|
||||
let info = mock_info(debtor, &coins(2, "token"));
|
||||
let msg = ExecuteMsg::UploadObligation {
|
||||
creditor: creditor.to_string(),
|
||||
amount: amount.into(),
|
||||
memo: memo.to_string(),
|
||||
};
|
||||
let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_cycle() {
|
||||
let mut deps = mock_dependencies();
|
||||
|
||||
let msg = InstantiateMsg;
|
||||
let info = mock_info("creator", &coins(2, "token"));
|
||||
let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
|
||||
create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob");
|
||||
create_obligation(
|
||||
&mut deps,
|
||||
BOB_ADDRESS,
|
||||
CHARLIE_ADDRESS,
|
||||
80,
|
||||
"bob -> charlie",
|
||||
);
|
||||
create_obligation(
|
||||
&mut deps,
|
||||
CHARLIE_ADDRESS,
|
||||
ALICE_ADDRESS,
|
||||
70,
|
||||
"charlie -> alice",
|
||||
);
|
||||
|
||||
let info = mock_info(ALICE_ADDRESS, &coins(2, "token"));
|
||||
let msg = ExecuteMsg::ApplyCycle {
|
||||
path: [ALICE_ADDRESS, BOB_ADDRESS, CHARLIE_ADDRESS, ALICE_ADDRESS]
|
||||
.into_iter()
|
||||
.map(ToString::to_string)
|
||||
.collect(),
|
||||
amount: 70u32.into(),
|
||||
};
|
||||
let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
|
||||
// Cycle should be cleared and only `30` should remain in `alice -> bob`
|
||||
let res = query(
|
||||
deps.as_ref(),
|
||||
mock_env(),
|
||||
QueryMsg::GetObligations {
|
||||
creditor: BOB_ADDRESS.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let value: GetObligationsResponse = from_json(&res).unwrap();
|
||||
assert_eq!(&30u32.into(), value.obligations[0].1);
|
||||
|
||||
// Check that alice received her karma tokens
|
||||
let res = query(
|
||||
deps.as_ref(),
|
||||
mock_env(),
|
||||
QueryMsg::Balance {
|
||||
address: ALICE_ADDRESS.to_string(),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let value: BalanceResponse = from_json(&res).unwrap();
|
||||
assert_eq!(&210u32.into(), value.balance);
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
use cosmwasm_std::StdError;
|
||||
use cw20_base::ContractError as Cw20ContractError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
|
||||
#[error("Unauthorized")]
|
||||
Unauthorized,
|
||||
|
||||
#[error("Specified path does not form a cycle")]
|
||||
PathNotCycle,
|
||||
|
||||
#[error("Amount is greater than utilization")]
|
||||
ClearingTooMuch,
|
||||
|
||||
#[error("Cw20 error: {0}")]
|
||||
Cw20(Cw20ContractError),
|
||||
}
|
||||
|
||||
impl From<Cw20ContractError> for ContractError {
|
||||
fn from(e: Cw20ContractError) -> Self {
|
||||
Self::Cw20(e)
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
#![deny(
|
||||
warnings,
|
||||
trivial_casts,
|
||||
trivial_numeric_casts,
|
||||
unused_import_braces,
|
||||
unused_qualifications
|
||||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod contract;
|
||||
mod error;
|
||||
pub mod msg;
|
||||
pub mod state;
|
||||
|
||||
pub use crate::error::ContractError;
|
|
@ -1,36 +0,0 @@
|
|||
use cosmwasm_schema::{cw_serde, QueryResponses};
|
||||
use cosmwasm_std::Uint128;
|
||||
#[allow(unused)]
|
||||
use cw20::BalanceResponse;
|
||||
|
||||
#[cw_serde]
|
||||
pub struct InstantiateMsg;
|
||||
|
||||
#[cw_serde]
|
||||
pub enum ExecuteMsg {
|
||||
UploadObligation {
|
||||
creditor: String,
|
||||
amount: Uint128,
|
||||
memo: String,
|
||||
},
|
||||
ApplyCycle {
|
||||
path: Vec<String>,
|
||||
amount: Uint128,
|
||||
},
|
||||
}
|
||||
|
||||
#[cw_serde]
|
||||
#[derive(QueryResponses)]
|
||||
pub enum QueryMsg {
|
||||
// GetCount returns the current count as a json-encoded number
|
||||
#[returns(GetObligationsResponse)]
|
||||
GetObligations { creditor: String },
|
||||
#[returns(BalanceResponse)]
|
||||
Balance { address: String },
|
||||
}
|
||||
|
||||
// We define a custom struct for each query response
|
||||
#[cw_serde]
|
||||
pub struct GetObligationsResponse {
|
||||
pub obligations: Vec<(String, Uint128)>,
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
use cosmwasm_schema::cw_serde;
|
||||
use cosmwasm_std::{Addr, Uint128};
|
||||
use cw_storage_plus::{Item, Map};
|
||||
|
||||
#[cw_serde]
|
||||
pub struct State {
|
||||
pub owner: String,
|
||||
}
|
||||
|
||||
pub const STATE: Item<State> = Item::new("state");
|
||||
pub const UTILIZATION: Map<(&Addr, &Addr), Uint128> = Map::new("utilization");
|
Loading…
Reference in a new issue