-
Notifications
You must be signed in to change notification settings - Fork 355
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
Conversation
6d98e81
to
e4ae969
Compare
There was a problem hiding this 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.
|
||
// validate owner change | ||
let new_owner = owner | ||
.ok_or(ContractError::InvalidInput {}) |
There was a problem hiding this comment.
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)?;
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@hashedone here is an explanation on how merkle airdrop works for better context Medium Merkle Airdrop: the Basics |
@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. |
This reverts commit 6c8c4c7.
47fd8bc
to
eb753c4
Compare
There was a problem hiding this 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
|
||
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave it.
return Err(ContractError::Unauthorized {}); | ||
} | ||
|
||
// check merkle root length |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
Please remove node_modules (and include that in .gitignore) and use node_modules is HUUUGGGEEE and should never be committed anywhere (there is already discussion on yarn.lock) |
There was a problem hiding this 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()) |
There was a problem hiding this comment.
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
}; | ||
let _res = execute(deps.as_mut(), env, info, msg).unwrap(); | ||
|
||
let msg = ExecuteMsg::Claim { |
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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.
There was a problem hiding this 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" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice
There was a problem hiding this 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave it.
I've finished sha256 refactor was writing tests. I will make it another PR |
This contract implements merkle airdrops for efficient token distribution.