Skip to content

Commit

Permalink
Merge pull request #364 from CosmWasm/cw20-merkle-airdrop
Browse files Browse the repository at this point in the history
Implement cw20-merkle-airdrop
  • Loading branch information
ethanfrey authored Aug 3, 2021
2 parents 9c17aa9 + 9c9dff9 commit d88742e
Show file tree
Hide file tree
Showing 36 changed files with 4,337 additions and 1 deletion.
37 changes: 37 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ workflows:
- contract_cw20_escrow
- contract_cw20_ics20
- contract_cw20_staking
- contract_cw20_merkle_airdrop
- contract_cw721_base
- contract_cw1155_base
- package_controllers
Expand Down Expand Up @@ -475,6 +476,42 @@ jobs:
- target
key: cargocache-cw20-staking-rust:1.51.0-{{ checksum "~/project/Cargo.lock" }}

contract_cw20_merkle_airdrop:
docker:
- image: rust:1.51.0
working_directory: ~/project/contracts/cw20-merkle-airdrop
steps:
- checkout:
path: ~/project
- run:
name: Version information
command: rustc --version; cargo --version; rustup --version
- restore_cache:
keys:
- cargocache-cw20-merkle-airdrop-rust:1.51.0-{{ checksum "~/project/Cargo.lock" }}
- run:
name: Unit Tests
environment:
RUST_BACKTRACE: 1
command: cargo unit-test --locked
- run:
name: Build and run schema generator
command: cargo schema --locked
- run:
name: Ensure checked-in schemas are up-to-date
command: |
CHANGES_IN_REPO=$(git status --porcelain)
if [[ -n "$CHANGES_IN_REPO" ]]; then
echo "Repository is dirty. Showing 'git status' and 'git --no-pager diff' for debugging now:"
git status && git --no-pager diff
exit 1
fi
- save_cache:
paths:
- /usr/local/cargo/registry
- target
key: cargocache-cw20-merkle-airdrop-rust:1.51.0-{{ checksum "~/project/Cargo.lock" }}

contract_cw721_base:
docker:
- image: rust:1.51.0
Expand Down
45 changes: 44 additions & 1 deletion Cargo.lock

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

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
| cw20-escrow | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw20_escrow.wasm) | [![Docs](https://docs.rs/cw20-escrow/badge.svg)](https://docs.rs/cw20-escrow) |
| cw20-ics20 | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw20_ics20.wasm) | [![Docs](https://docs.rs/cw20-ics20/badge.svg)](https://docs.rs/cw20-ics20) |
| cw20-staking | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw20_staking.wasm) | [![Docs](https://docs.rs/cw20-staking/badge.svg)](https://docs.rs/cw20-staking) |
| cw20-merkle-airdrop | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw20_merkle_airdrop.wasm) | [![Docs](https://docs.rs/cw20-merkle-airdrop/badge.svg)](https://docs.rs/cw20-merkle-airdrop) |
| cw721-base | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw721_base.wasm) | [![Docs](https://docs.rs/cw721-base/badge.svg)](https://docs.rs/cw721-base) |
| cw1155-base | [Release v0.7.0](https://github.com/CosmWasm/cosmwasm-plus/releases/download/v0.7.0/cw1155_base.wasm) | [![Docs](https://docs.rs/cw1155-base/badge.svg)](https://docs.rs/cw1155-base) |

Expand Down Expand Up @@ -141,6 +142,9 @@ for prices.
and cw20 tokens. This is a good example to show how to interact with
cw20 tokens.

* [`cw20-merkle-airdrop`](./contracts/cw20-merkle-airdrop) is a contract
for efficient cw20 token airdrop distribution.

CW721 Non-fungible Tokens:

* [`cw721-base`](./contracts/cw721-base) a base implementation of a cw721 NFT contract.
Expand Down
6 changes: 6 additions & 0 deletions contracts/cw20-merkle-airdrop/.cargo/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[alias]
wasm = "build --release --target wasm32-unknown-unknown"
wasm-debug = "build --target wasm32-unknown-unknown"
unit-test = "test --lib"
integration-test = "test --test integration"
schema = "run --example schema"
35 changes: 35 additions & 0 deletions contracts/cw20-merkle-airdrop/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
[package]
name = "cw20-merkle-airdrop"
version = "0.7.0"
authors = ["Orkun Kulce <[email protected]>", "Terraform Labs, PTE."]
edition = "2018"
description = "An Airdrop contract for allowing users to claim rewards with Merkle Tree based proof"
license = "Apache-2.0"

exclude = [
"contract.wasm",
"hash.txt",
]

[lib]
crate-type = ["cdylib", "rlib"]

[features]
backtraces = ["cosmwasm-std/backtraces"]
library = []

[dependencies]
cw0 = { path = "../../packages/cw0", version = "0.8.0-rc1" }
cw2 = { path = "../../packages/cw2", version = "0.8.0-rc1" }
cw20 = { path = "../../packages/cw20", version = "0.8.0-rc1" }
cosmwasm-std = { version = "0.16.0-rc5", features = ["iterator"] }
cw-storage-plus = { path = "../../packages/storage-plus", version = "0.8.0-rc1", features = ["iterator"] }
schemars = "0.8.1"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
hex = "0.4"
sha3 = { version = "0.9.1", default-features = false }

[dev-dependencies]
cosmwasm-schema = "0.16.0-rc5"
serde_json = "1.0"
16 changes: 16 additions & 0 deletions contracts/cw20-merkle-airdrop/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
CW20-Merkle-Airdrop: A reference implementation for merkle airdrop on CosmWasm

Copyright (C) 2021 Terraform Labs, PTE.
Copyright (C) 2021 Confio OÜ

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
98 changes: 98 additions & 0 deletions contracts/cw20-merkle-airdrop/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# CW20 Merkle Airdrop

This is a merkle airdrop smart contract that works with cw20 token specification Mass airdrop distributions made cheap
and efficient.

Explanation of merkle airdrop: [Medium Merkle Airdrop: the Basics](https://medium.com/smartz-blog/merkle-airdrop-the-basics-9a0857fcc930)

Traditional and non-efficient airdrops:

- Distributor creates a list of airdrop
- Sends bank send messages to send tokens to recipients
**Or**
- Stores list of recipients on smart contract data
- Recipient claims the airdrop

These two solutions are very ineffective when recipient list is big. First, costly because bank send cost for the
distributor will be costly. Second, whole airdrop list stored in the state, again costly.

Merkle Airdrop is very efficient even when recipient number is massive.

This contract works with multiple airdrop rounds, meaning you can execute several airdrops using same instance.

Uses **SHA3 Keccak 256** for merkle root tree construction.

## Procedure

- Distributor of contract prepares a list of addresses with many entries and publishes this list in public static .js
file in JSON format
- Distributor reads this list, builds the merkle tree structure and writes down the Merkle root of it.
- Distributor creates contract and places calculated Merkle root into it.
- Distributor says to users, that they can claim their tokens, if they owe any of addresses, presented in list,
published on distributor's site.
- User wants to claim his N tokens, he also builds Merkle tree from public list and prepares Merkle proof, consisting
from log2N hashes, describing the way to reach Merkle root
- User sends transaction with Merkle proof to contract
- Contract checks Merkle proof, and, if proof is correct, then sender's address is in list of allowed addresses, and
contract does some action for this use.
- Distributor sends token to the contract, and registers new merkle root for the next distribution round.

## Spec

### Messages

#### InstantiateMsg

`InstantiateMsg` instantiates contract with owner and cw20 token address. Airdrop `stage` is set to 0.

```rust
pub struct InstantiateMsg {
pub owner: String,
pub cw20_token_address: String,
}
```

#### ExecuteMsg

```rust
pub enum ExecuteMsg {
UpdateConfig {
owner: Option<String>,
},
RegisterMerkleRoot {
merkle_root: String,
},
Claim {
stage: u8,
amount: Uint128,
proof: Vec<String>,
},
}
```

- `UpdateConfig{owner}` updates configuration.
- `RegisterMerkleRoot {merkle_root}` registers merkle tree root for further claim verification. Airdrop `Stage`
increased by 1.
- `Claim{stage, amount, proof}` recipient executes for claiming airdrop with `stage`, `amount` and `proof` data built
using full list.

#### QueryMsg

``` rust
pub enum QueryMsg {
Config {},
MerkleRoot { stage: u8 },
LatestStage {},
IsClaimed { stage: u8, address: String },
}
```

- `{ config: {} }` returns configuration, `{"cw20_token_address": ..., "owner": ...}`.
- `{ merkle_root: { stage: "1" }` returns merkle root of given stage, `{"merkle_root": ... , "stage": ...}`
- `{ latest_stage: {}}` returns current airdrop stage, `{"latest_stage": ...}`
- `{ is_claimed: {stage: "stage", address: "wasm1..."}` returns if address claimed airdrop, `{"is_claimed": "true"}`

## Airdrop helper CLI

[Airdrop helper CLI](helpers) contains js helpers for generating root, generating and verifying proofs for given airdrop
file.
23 changes: 23 additions & 0 deletions contracts/cw20-merkle-airdrop/examples/schema.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use std::env::current_dir;
use std::fs::create_dir_all;

use cosmwasm_schema::{export_schema, remove_schemas, schema_for};
use cw20_merkle_airdrop::msg::{
ConfigResponse, ExecuteMsg, InstantiateMsg, IsClaimedResponse, LatestStageResponse,
MerkleRootResponse, QueryMsg,
};

fn main() {
let mut out_dir = current_dir().unwrap();
out_dir.push("schema");
create_dir_all(&out_dir).unwrap();
remove_schemas(&out_dir).unwrap();

export_schema(&schema_for!(InstantiateMsg), &out_dir);
export_schema(&schema_for!(ExecuteMsg), &out_dir);
export_schema(&schema_for!(QueryMsg), &out_dir);
export_schema(&schema_for!(LatestStageResponse), &out_dir);
export_schema(&schema_for!(MerkleRootResponse), &out_dir);
export_schema(&schema_for!(IsClaimedResponse), &out_dir);
export_schema(&schema_for!(ConfigResponse), &out_dir);
}
1 change: 1 addition & 0 deletions contracts/cw20-merkle-airdrop/helpers/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/lib
6 changes: 6 additions & 0 deletions contracts/cw20-merkle-airdrop/helpers/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": [
"oclif",
"oclif-typescript"
]
}
8 changes: 8 additions & 0 deletions contracts/cw20-merkle-airdrop/helpers/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*-debug.log
*-error.log
/.nyc_output
/dist
/lib
/package-lock.json
/tmp
node_modules
Loading

0 comments on commit d88742e

Please sign in to comment.