Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement cw20-merkle-airdrop #364

Merged
merged 51 commits into from
Aug 3, 2021
Merged

Implement cw20-merkle-airdrop #364

merged 51 commits into from
Aug 3, 2021

Conversation

orkunkl
Copy link
Contributor

@orkunkl orkunkl commented Jul 29, 2021

This contract implements merkle airdrops for efficient token distribution.

@orkunkl orkunkl force-pushed the cw20-merkle-airdrop branch from 6d98e81 to e4ae969 Compare July 29, 2021 11:49
Copy link
Contributor

@hashedone hashedone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just left some Rust-idioms related comments, coupled with very general stuff (like files which I think shouldn't be there). I didn't review helper code at all as I have literally no knowledge of Node ecosystem (neither JS nor TS), so someone should probably take a look at this. Later I would also do a second round with attempt to understand the contract logic and verify if I can spot any bugs.

contracts/cw20-merkle-airdrop/.editorconfig Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/helpers/.editorconfig Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/rustfmt.toml Outdated Show resolved Hide resolved

// validate owner change
let new_owner = owner
.ok_or(ContractError::InvalidInput {})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand how owner can be Optional in this message, if setting it to None always result in an error? Wouldn't it be cleaner to just make owner a String?

Also this can be done easier to read like:

let new_owner = owner.ok_or(ContractError::InvalidInput {})?;
let new_owner = deps.api.addr_validate(&new_owner)?;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you changed it to what I proposed, but I still don't get - why owner is optional in the message in the fist place? For me it looks like if it is not set it is an error, so why not to make it required?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you changed it to what I proposed, but I still don't get - why owner is optional in the message in the fist place? For me it looks like if it is not set it is an error, so why not to make it required?

I agree, the Msg types should capture requirements, and will enforce them in parsing without you writing any code. if we require a number to be present AND between 1 and 100, well, I would just use serde to enforce it is a value u64, and then add business logic for 1-100. But the types should be captured in the Msg classes and the rustdoc there

contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
@orkunkl orkunkl requested a review from hashedone July 29, 2021 13:50
@orkunkl
Copy link
Contributor Author

orkunkl commented Jul 29, 2021

@hashedone here is an explanation on how merkle airdrop works for better context Medium Merkle Airdrop: the Basics

@hashedone
Copy link
Contributor

@ethanfrey please take additional look at contract details itself as we spoke yesterday. I do my best here, but I am not yet confident enough about smart contract logic to approve/merge it even if it is ok for me.

@orkunkl orkunkl force-pushed the cw20-merkle-airdrop branch from 47fd8bc to eb753c4 Compare July 30, 2021 13:43
Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was doing a review untl a big diff came in. Will continue later

contracts/cw20-merkle-airdrop/NOTICE Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/README.md Show resolved Hide resolved

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this choice?
To be compatible with Ethereum obviously, but do we need to?
Are there frontend tools we can reuse by doing so?

I would use sha2-256 or sha3-256 (non-keccak) or maybe blake2. Keccak is only used in Ethereum, the rest are more standard libs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pulled this code base from anchor protocol(Apache 2.0) licence.
I believe developers come from solidity background.
I thought of changing it but left it for your decision. I will apply it!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it.

contracts/cw20-merkle-airdrop/README.md Show resolved Hide resolved
contracts/cw20-merkle-airdrop/README.md Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/msg.rs Show resolved Hide resolved
return Err(ContractError::Unauthorized {});
}

// check merkle root length
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a proper decode, but does it assert the length?
I would just manually assert the input is 64 chars and assert it explicitly

Copy link
Contributor Author

@orkunkl orkunkl Jul 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let mut root_buf: [u8; 32] = [0; 32];

I thought 32 runs assertion

contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
@ethanfrey
Copy link
Member

Please remove node_modules (and include that in .gitignore) and use git rebase -i to splice out that commit from the git history (splitting it into 2 parts first if you want to save something).

node_modules is HUUUGGGEEE and should never be committed anywhere (there is already discussion on yarn.lock)

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finished looking through it.
The actual hashing and verify logic should be in a separate function and heavily tested for compatibility with TS. Otherwise, looks good.

let merkle_root = MERKLE_ROOT.load(deps.storage, stage.into())?;

let user_input = format!("{}{}", info.sender, amount);
let hash = sha3::Keccak256::digest(user_input.as_bytes())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These lines (142/143-157) should be pulled out into a separate function to heavily unit test it and compare with TS test vectors. They are simple function on basic data structures and easier to heavily test than the execute methods

contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Show resolved Hide resolved
contracts/cw20-merkle-airdrop/src/contract.rs Outdated Show resolved Hide resolved
};
let _res = execute(deps.as_mut(), env, info, msg).unwrap();

let msg = ExecuteMsg::Claim {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to document where these come from. Or even better, have a script that generates many of these examples, so we can use testvectors.

You can store as json files and include in the tests with include_bytes!()

pub const CONFIG_KEY: &str = "config";
pub const CONFIG: Item<Config> = Item::new(CONFIG_KEY);

pub const STAGE_KEY: &str = "stage";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I avoid supporting raw queries, except in a few special cases.
cw4 interface is one and has a very well define raw query support for ONE use case - is_member.

But if you prefer them, your style is not wrong, just a bit more verbose. And yes, does allow raw queries.

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good steps

cw20 = { path = "../../packages/cw20", version = "0.7.0" }
cosmwasm-std = { version = "0.15.0", features = ["iterator"] }
cw-storage-plus = { path = "../../packages/storage-plus", version = "0.7.0", features = ["iterator"] }
cw0 = { path = "../../packages/cw0", version = "0.8.0-rc1" }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Member

@ethanfrey ethanfrey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will merge this in, so it doesn't rot as we prepare a release.

The Rust contract code is fine. However, I really would like to see some unit tests in TS land showing compatibility with the Rust hashing algorithm. That can be a follow up PR (and maybe by a JS dev if you can convince one to do so)


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's leave it.

@ethanfrey ethanfrey merged commit d88742e into main Aug 3, 2021
@ethanfrey ethanfrey deleted the cw20-merkle-airdrop branch August 3, 2021 20:40
@orkunkl
Copy link
Contributor Author

orkunkl commented Aug 4, 2021

I've finished sha256 refactor was writing tests. I will make it another PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants