Skip to content

Commit

Permalink
Allow create/top up with cw20 tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanfrey committed Jun 26, 2020
1 parent 83a2f36 commit 72d117d
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 4 deletions.
3 changes: 2 additions & 1 deletion contracts/cw20-escrow/examples/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fs::create_dir_all;

use cosmwasm_schema::{export_schema, remove_schemas, schema_for};

use cw20_escrow::msg::{DetailsResponse, HandleMsg, InitMsg, ListResponse, QueryMsg};
use cw20_escrow::msg::{DetailsResponse, HandleMsg, InitMsg, ListResponse, QueryMsg, ReceiveMsg};

fn main() {
let mut out_dir = current_dir().unwrap();
Expand All @@ -14,6 +14,7 @@ fn main() {
export_schema(&schema_for!(InitMsg), &out_dir);
export_schema(&schema_for!(HandleMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
export_schema(&schema_for!(ReceiveMsg), &out_dir);
export_schema(&schema_for!(DetailsResponse), &out_dir);
export_schema(&schema_for!(ListResponse), &out_dir);
}
65 changes: 65 additions & 0 deletions contracts/cw20-escrow/schema/handle_msg.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@
}
}
},
{
"description": "Adds all sent native tokens to the contract",
"type": "object",
"required": [
"top_up"
],
"properties": {
"top_up": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string"
}
}
}
}
},
{
"description": "Approve sends all tokens to the recipient. Only the arbiter can do this",
"type": "object",
Expand Down Expand Up @@ -54,9 +74,25 @@
}
}
}
},
{
"description": "This accepts a properly-encoded ReceiveMsg from a cw20 contract",
"type": "object",
"required": [
"receive"
],
"properties": {
"receive": {
"$ref": "#/definitions/Cw20ReceiveMsg"
}
}
}
],
"definitions": {
"Binary": {
"description": "Binary is a wrapper around Vec<u8> to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec<u8>",
"type": "string"
},
"CreateMsg": {
"type": "object",
"required": [
Expand Down Expand Up @@ -105,8 +141,37 @@
}
}
},
"Cw20ReceiveMsg": {
"description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a HandleMsg",
"type": "object",
"required": [
"amount",
"sender"
],
"properties": {
"amount": {
"$ref": "#/definitions/Uint128"
},
"msg": {
"anyOf": [
{
"$ref": "#/definitions/Binary"
},
{
"type": "null"
}
]
},
"sender": {
"$ref": "#/definitions/HumanAddr"
}
}
},
"HumanAddr": {
"type": "string"
},
"Uint128": {
"type": "string"
}
}
}
90 changes: 90 additions & 0 deletions contracts/cw20-escrow/schema/receive_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ReceiveMsg",
"anyOf": [
{
"type": "object",
"required": [
"create"
],
"properties": {
"create": {
"$ref": "#/definitions/CreateMsg"
}
}
},
{
"description": "Adds all sent native tokens to the contract",
"type": "object",
"required": [
"top_up"
],
"properties": {
"top_up": {
"type": "object",
"required": [
"id"
],
"properties": {
"id": {
"type": "string"
}
}
}
}
}
],
"definitions": {
"CreateMsg": {
"type": "object",
"required": [
"arbiter",
"id",
"recipient"
],
"properties": {
"arbiter": {
"description": "arbiter can decide to approve or refund the escrow",
"allOf": [
{
"$ref": "#/definitions/HumanAddr"
}
]
},
"end_height": {
"description": "When end height set and block height exceeds this value, the escrow is expired. Once an escrow is expired, it can be returned to the original funder (via \"refund\").",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"end_time": {
"description": "When end time (in seconds since epoch 00:00:00 UTC on 1 January 1970) is set and block time exceeds this value, the escrow is expired. Once an escrow is expired, it can be returned to the original funder (via \"refund\").",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0.0
},
"id": {
"description": "id is a human-readable name for the escrow to use later 3-20 bytes of utf-8 text",
"type": "string"
},
"recipient": {
"description": "if approved, funds go to the recipient",
"allOf": [
{
"$ref": "#/definitions/HumanAddr"
}
]
}
}
},
"HumanAddr": {
"type": "string"
}
}
}
91 changes: 88 additions & 3 deletions contracts/cw20-escrow/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use cosmwasm_std::{
log, to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Env, Extern, HandleResponse, HumanAddr,
InitResponse, Querier, StdError, StdResult, Storage, WasmMsg,
from_binary, log, to_binary, Api, BankMsg, Binary, Coin, CosmosMsg, Env, Extern,
HandleResponse, HumanAddr, InitResponse, Querier, StdError, StdResult, Storage, WasmMsg,
};
use cw20::Cw20HandleMsg;
use cw20::{Cw20HandleMsg, Cw20ReceiveMsg};

use crate::msg::{
CreateMsg, Cw20CoinHuman, DetailsResponse, HandleMsg, InitMsg, ListResponse, QueryMsg,
ReceiveMsg,
};
use crate::state::{all_escrow_ids, escrows, escrows_read, Cw20Coin, Escrow, PREFIX_ESCROW};
use cosmwasm_storage::prefixed;
Expand All @@ -29,6 +30,27 @@ pub fn handle<S: Storage, A: Api, Q: Querier>(
HandleMsg::Approve { id } => try_approve(deps, env, id),
HandleMsg::TopUp { id } => try_top_up(deps, env, id),
HandleMsg::Refund { id } => try_refund(deps, env, id),
HandleMsg::Receive(msg) => try_receive(deps, env, msg),
}
}

pub fn try_receive<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
wrapper: Cw20ReceiveMsg,
) -> StdResult<HandleResponse> {
let msg: ReceiveMsg = match wrapper.msg {
Some(bin) => from_binary(&bin),
None => Err(StdError::parse_err("ReceiveMsg", "no data")),
}?;
// TODO: assert the sending token address is valid (contract not external account)
let token = Cw20Coin {
address: env.message.sender,
amount: wrapper.amount,
};
match msg {
ReceiveMsg::Create(create) => try_cw20_create(deps, wrapper.sender, token, create),
ReceiveMsg::TopUp { id } => try_cw20_top_up(deps, wrapper.sender, token, id),
}
}

Expand Down Expand Up @@ -65,6 +87,34 @@ pub fn try_create<S: Storage, A: Api, Q: Querier>(
Ok(res)
}

pub fn try_cw20_create<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
sender: HumanAddr,
token: Cw20Coin,
msg: CreateMsg,
) -> StdResult<HandleResponse> {
let escrow = Escrow {
arbiter: deps.api.canonical_address(&msg.arbiter)?,
recipient: deps.api.canonical_address(&msg.recipient)?,
source: deps.api.canonical_address(&sender)?,
end_height: msg.end_height,
end_time: msg.end_time,
// there are native coins sent with the message
native_balance: vec![],
cw20_balance: vec![token],
};

// try to store it, fail if the id was already in use
escrows(&mut deps.storage).update(msg.id.as_bytes(), |existing| match existing {
None => Ok(escrow),
Some(_) => Err(StdError::generic_err("escrow id already in use")),
})?;

let mut res = HandleResponse::default();
res.log = vec![log("action", "create"), log("id", msg.id)];
Ok(res)
}

pub fn try_top_up<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
Expand All @@ -75,6 +125,27 @@ pub fn try_top_up<S: Storage, A: Api, Q: Querier>(

// combine these two
add_tokens(&mut escrow.native_balance, env.message.sent_funds);
// and save
escrows(&mut deps.storage).save(id.as_bytes(), &escrow)?;

let mut res = HandleResponse::default();
res.log = vec![log("action", "top_up"), log("id", id)];
Ok(res)
}

pub fn try_cw20_top_up<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
_sender: HumanAddr,
token: Cw20Coin,
id: String,
) -> StdResult<HandleResponse> {
// this fails is no escrow there
let mut escrow = escrows_read(&deps.storage).load(id.as_bytes())?;

// combine these two
add_cw20_token(&mut escrow.cw20_balance, token);
// and save
escrows(&mut deps.storage).save(id.as_bytes(), &escrow)?;

let mut res = HandleResponse::default();
res.log = vec![log("action", "top_up"), log("id", id)];
Expand All @@ -97,6 +168,20 @@ fn add_tokens(store: &mut Vec<Coin>, add: Vec<Coin>) {
}
}

fn add_cw20_token(store: &mut Vec<Cw20Coin>, token: Cw20Coin) {
let index = store.iter().enumerate().find_map(|(i, exist)| {
if exist.address == token.address {
Some(i)
} else {
None
}
});
match index {
Some(idx) => store[idx].amount += token.amount,
None => store.push(token),
}
}

pub fn try_approve<S: Storage, A: Api, Q: Querier>(
deps: &mut Extern<S, A, Q>,
env: Env,
Expand Down
13 changes: 13 additions & 0 deletions contracts/cw20-escrow/src/msg.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use cosmwasm_std::{Coin, HumanAddr, Uint128};
use cw20::Cw20ReceiveMsg;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

Expand All @@ -25,6 +26,18 @@ pub enum HandleMsg {
/// id is a human-readable name for the escrow from create
id: String,
},
/// This accepts a properly-encoded ReceiveMsg from a cw20 contract
Receive(Cw20ReceiveMsg),
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ReceiveMsg {
Create(CreateMsg),
/// Adds all sent native tokens to the contract
TopUp {
id: String,
},
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand Down

0 comments on commit 72d117d

Please sign in to comment.