8000 Airdrop permit fixes by FloppyDisck · Pull Request #154 · securesecrets/shade · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Airdrop permit fixes #154

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

Merged
merged 2 commits into from
Jan 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion contracts/airdrop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ Get the account's information
##### Request
|Name |Type |Description | optional |
|--------|--------|-------------------------------------|----------|
|permit | [AddressProofPermit](#AddressProofPermit)|Address's permit | no |
|permit | [AccountProofPermit](#AccountProofMsg)|Address's permit | no |
|current_date | u64 | Current time in UNIT format | yes |
```json
{
Expand All @@ -218,6 +218,17 @@ NOTE: The parameters must be in order
| chain_id | String | Chain ID of the network this proof will be used in | no |
| signature | PermitSignature | Signature of the permit | no |

## AccountProofMsg
The information inside permits that validate account ownership

NOTE: The parameters must be in order
### Structure
| Name | Type | Description | optional |
|----------|---------|---------------------------------------------------------|----------|
| contract | String | Airdrop contract | no |
| key | String | Some permit key | no |


## AddressProofMsg
The information inside permits that validate the airdrop eligibility and validate the account holder's key.

Expand Down
4 changes: 2 additions & 2 deletions contracts/airdrop/src/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use cosmwasm_std::{to_binary, Api, Env, Extern, HandleResponse, Querier, StdErro
use rs_merkle::{Hasher, MerkleProof, algorithms::Sha256};
use crate::state::{
config_r, config_w, claim_status_w, claim_status_r, account_total_claimed_w,
total_claimed_w, total_claimed_r, account_r, address_in_account_w, account_w, validate_permit,
total_claimed_w, total_claimed_r, account_r, address_in_account_w, account_w, validate_address_permit,
revoke_permit
};
use shade_protocol::{airdrop::{HandleAnswer, Config, claim_info::{RequiredTask},
Expand Down Expand Up @@ -462,7 +462,7 @@ pub fn try_add_account_addresses<S: Storage>(
// Avoid verifying sender
if &permit.params.address != sender {
// Check permit legitimacy
address = validate_permit(storage, permit, config.contract.clone())?;
address = validate_address_permit(storage, permit, config.contract.clone())?;
if address != permit.params.address {
return Err(StdError::generic_err("Signer address is not the same as the permit address"))
}
Expand Down
8 changes: 4 additions & 4 deletions contracts/airdrop/src/query.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::{Api, Extern, Querier, StdResult, Storage, Uint128};
use shade_protocol::airdrop::{QueryAnswer, account::AddressProofPermit, claim_info::RequiredTask};
use shade_protocol::airdrop::{QueryAnswer, account::AccountPermit, claim_info::RequiredTask};
use crate::handle::decay_factor;
use crate::state::{config_r, claim_status_r, total_claimed_r, validate_permit, account_r, account_total_claimed_r};
use crate::state::{config_r, claim_status_r, total_claimed_r, validate_address_permit, account_r, account_total_claimed_r, validate_account_permit};

pub fn config<S: Storage, A: Api, Q: Querier>
(deps: &Extern<S, A, Q>) -> StdResult<QueryAnswer> {
Expand All @@ -26,12 +26,12 @@ pub fn dates<S: Storage, A: Api, Q: Querier>
}

pub fn account<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>, permit: AddressProofPermit, current_date: Option<u64>
deps: &Extern<S, A, Q>, permit: AccountPermit, current_date: Option<u64>
) -> StdResult<QueryAnswer> {

let config = config_r(&deps.storage).load()?;

let account_address = validate_permit(&deps.storage, &permit, config.contract)?;
let account_address = validate_account_permit(&deps, &permit, config.contract)?;

let account = account_r(&deps.storage).load(account_address.to_string().as_bytes())?;

Expand Down
32 changes: 27 additions & 5 deletions contracts/airdrop/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use cosmwasm_std::{Storage, Uint128, StdResult, HumanAddr, StdError};
use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton, bucket, Bucket, bucket_read, ReadonlyBucket};
use shade_protocol::airdrop::{Config, account::Account};
use shade_protocol::airdrop::account::AddressProofPermit;
use cosmwasm_std::{Storage, Uint128, StdResult, HumanAddr, StdError, Api, Querier, Extern};
use cosmwasm_storage::{singleton, singleton_read, ReadonlySingleton, Singleton,
bucket, Bucket, bucket_read, ReadonlyBucket};
use shade_protocol::airdrop::{Config, account::{Account, AccountPermit, AddressProofPermit}};

pub static CONFIG_KEY: &[u8] = b"config";
pub static CLAIM_STATUS_KEY: &[u8] = b"claim_status_";
Expand Down Expand Up @@ -92,7 +92,9 @@ pub fn is_permit_revoked<S: Storage>(storage: &S, account: String, permit_key: S
}
}

pub fn validate_permit<S: Storage>(storage: &S, permit: &AddressProofPermit, contract: HumanAddr) -> StdResult<HumanAddr> {
pub fn validate_address_permit<S: Storage>(
storage: &S, permit: &AddressProofPermit, contract: HumanAddr
) -> StdResult<HumanAddr> {
// Check that contract matches
if permit.params.contract != contract {
return Err(StdError::unauthorized())
Expand All @@ -106,4 +108,24 @@ pub fn validate_permit<S: Storage>(storage: &S, permit: &AddressProofPermit, con

// Authenticate permit
permit.authenticate()
}

pub fn validate_account_permit<S: Storage, A: Api, Q: Querier>(
deps: &Extern<S, A, Q>, permit: &AccountPermit, contract: HumanAddr
) -> StdResult<HumanAddr> {
// Check that contract matches
if permit.params.contract != contract {
return Err(StdError::unauthorized())
}

// Authenticate permit
let address = permit.authenticate(&deps.api)?;

// Check that permit is not revoked
if is_permit_revoked(&deps.storage, address.to_string(),
permit.params.key.clone())? {
return Err(StdError::generic_err("permit key revoked"))
}

Ok(address)
}
36 changes: 19 additions & 17 deletions packages/network_integration/tests/testnet_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use network_integration::{utils::{print_header, print_warning, generate_label, p
minter::{initialize_minter, setup_minters, get_balance},
stake::setup_staker}};
use rs_merkle::{Hasher, MerkleTree, algorithms::Sha256};
use shade_protocol::airdrop::account::AccountPermitMsg;

fn create_signed_permit<T: Clone + Serialize>(permit_msg: T, signer: &str) -> Permit<T> {
let chain_id = "testnet".to_string();
Expand Down Expand Up @@ -140,9 +141,9 @@ fn run_airdrop() -> Result<()> {
print_header("Initializing airdrop");

let now = chrono::offset::Utc::now().timestamp() as u64;
let duration = 180;
let duration = 200;
let decay_date = now + duration;
let end_date = now + duration + 80;
let end_date = decay_date + 60;

let airdrop_init_msg = airdrop::InitMsg {
admin: None,
Expand Down Expand Up @@ -206,6 +207,10 @@ fn run_airdrop() -> Result<()> {
let initial_proof = proof_from_tree(&vec![0,1], &merlke_tree.layers());

let a_permit = create_signed_permit(a_address_proof, ACCOUNT_KEY);
let account_permit = create_signed_permit(
AccountPermitMsg{
contract: HumanAddr(airdrop.address.clone()),
key: "key".to_string() }, ACCOUNT_KEY);

/// Create an account which will also claim whatever amount is available
print_warning("Creating an account");
Expand All @@ -220,7 +225,7 @@ fn run_airdrop() -> Result<()> {
print_warning("Getting initial account information");
{
let msg = airdrop::QueryMsg::GetAccount {
permit: a_permit.clone(),
permit: account_permit.clone(),
current_date: None
};

Expand All @@ -247,7 +252,7 @@ fn run_airdrop() -> Result<()> {

{
let msg = airdrop::QueryMsg::GetAccount {
permit: a_permit.clone(),
permit: account_permit.clone(),
current_date: None
};

Expand Down Expand Up @@ -285,7 +290,7 @@ fn run_airdrop() -> Result<()> {
/// Query that all of the airdrop is claimed
{
let msg = airdrop::QueryMsg::GetAccount {
permit: a_permit.clone(),
permit: account_permit.clone(),
current_date: None
};

Expand All @@ -307,7 +312,7 @@ fn run_airdrop() -> Result<()> {

{
let msg = airdrop::QueryMsg::GetAccount {
permit: a_permit.clone(),
permit: account_permit.clone(),
current_date: None
};

Expand All @@ -316,19 +321,15 @@ fn run_airdrop() -> Result<()> {
assert!(query.is_err());
}

let new_a_address_proof = AddressProofMsg {
address: HumanAddr(account_a.clone()),
amount: a_airdrop,
contract: HumanAddr(airdrop.address.clone()),
index: 0,
key: "new_key".to_string()
};

let new_a_permit = create_signed_permit(new_a_address_proof, ACCOUNT_KEY);
let new_account_permit = create_signed_permit(
AccountPermitMsg{
contract: HumanAddr(airdrop.address.clone()),
key: "new_key".to_string() }, ACCOUNT_KEY);

{
let msg = airdrop::QueryMsg::GetAccount {
permit: new_a_permit.clone(),
permit: new_account_permit.clone(),
current_date: None
};

Expand All @@ -347,7 +348,7 @@ fn run_airdrop() -> Result<()> {
{
let current = chrono::offset::Utc::now().timestamp() as u64;
// Wait until times is between decay start and end of airdrop
thread::sleep(time::Duration::from_secs(decay_date - current + 30));
thread::sleep(time::Duration::from_secs((decay_date - current) + 20));
}

{
Expand All @@ -370,7 +371,8 @@ fn run_airdrop() -> Result<()> {

let balance = get_balance(&snip, account_a.clone());

assert!(balance > total_airdrop && balance < total_airdrop + decay_amount);
assert!(balance > total_airdrop);
assert!(balance < total_airdrop + decay_amount);
}

/// Try to claim expired tokens
Expand Down
25 changes: 22 additions & 3 deletions packages/shade_protocol/src/airdrop/account.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{HumanAddr, Uint128, StdResult, StdError};
use cosmwasm_std::{HumanAddr, Uint128, StdResult, StdError, Api};
use crate::signature::{Permit, bech32_to_canonical};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand All @@ -10,13 +10,31 @@ pub struct Account {
pub total_claimable: Uint128,
}

// Used for querying account information
pub type AccountPermit = Permit<AccountPermitMsg>;

impl AccountPermit {
pub fn authenticate<A: Api>(&self, api: &A) -> StdResult<HumanAddr> {
self.validate()?.as_humanaddr(api)
}
}

#[remain::sorted]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct AccountPermitMsg {
pub contract: HumanAddr,
pub key: String,
}

// Used to prove ownership over IBC addresses
pub type AddressProofPermit = Permit<AddressProofMsg>;

impl AddressProofPermit {
/// Will check if signer is the same as the given address
pub fn authenticate(&self) -> StdResult<HumanAddr> {
let permit_address = self.params.address.clone();
let signer_address = self.validate()?;
let signer_address = self.validate()?.as_canonical();
if signer_address != bech32_to_canonical(permit_address.as_str()) {
return Err(StdError::generic_err(
format!("{:?} is not the message signer", permit_address.as_str())))
Expand All @@ -29,12 +47,13 @@ impl AddressProofPermit {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct AddressProofMsg {
// Address is necessary since we have other network permits present
pub address: HumanAddr,
// Reward amount
pub amount: Uint128,
// Used to prevent permits from being used elsewhere
pub contract: HumanAddr,
// Index of the address in the leafs array
// Index of the address in the leaves array
pub index: u32,
// Used to identify permits
pub key: String,
Expand Down
4 changes: 2 additions & 2 deletions packages/shade_protocol/src/airdrop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use secret_toolkit::utils::{InitCallback, HandleCallback, Query};
use cosmwasm_std::{Binary, HumanAddr, Uint128};
use crate::{asset::Contract, generic_response::ResponseStatus,
airdrop::{claim_info::{RequiredTask}, account::AddressProofPermit}};
airdrop::{claim_info::{RequiredTask}, account::{AccountPermit, AddressProofPermit}}};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
Expand Down Expand Up @@ -117,7 +117,7 @@ pub enum HandleAnswer {
pub enum QueryMsg {
GetConfig { },
GetDates { current_date: Option<u64> },
GetAccount { permit: AddressProofPermit, current_date: Option<u64> },
GetAccount { permit: AccountPermit, current_date: Option<u64> },
}

impl Query for QueryMsg {
Expand Down
16 changes: 4 additions & 12 deletions packages/shade_protocol/src/signature/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ pub mod transaction;
use cosmwasm_std::{Binary, CanonicalAddr, StdError, StdResult, to_binary};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ripemd160::{Digest, Ripemd160};
use secp256k1::Secp256k1;
use bech32::FromBase32;
use secret_toolkit::crypto::sha_256;
use crate::signature::transaction::{SignedTx, TxMsg, PermitSignature};
use crate::signature::transaction::{SignedTx, TxMsg, PermitSignature, PubKeyValue};

// NOTE: Struct order is very important for signatures

Expand All @@ -34,9 +33,8 @@ impl<T: Clone + Serialize> Permit<T> {
}

/// Returns the permit signer
pub fn validate(&self) -> StdResult<CanonicalAddr> {
pub fn validate(&self) -> StdResult<PubKeyValue> {
let pubkey = &self.signature.pub_key.value;
let account = pubkey_to_account(pubkey);

// Validate signature
let signed_bytes = to_binary(&self.create_signed_tx())?;
Expand Down Expand Up @@ -65,16 +63,10 @@ impl<T: Clone + Serialize> Permit<T> {
))
})?;

Ok(account)
Ok(PubKeyValue(pubkey.clone()))
}
}

pub fn pubkey_to_account(pubkey: &Binary) -> CanonicalAddr {
let mut hasher = Ripemd160::new();
hasher.update(sha_256(&pubkey.0));
CanonicalAddr(Binary(hasher.finalize().to_vec()))
}

#[cfg(test)]
mod signature_tests {
use super::*;
Expand Down Expand Up @@ -136,7 +128,7 @@ mod signature_tests {
};

let addr = permit.validate().unwrap();
assert_eq!(addr, bech32_to_canonical(ADDRESS));
assert_eq!(addr.as_canonical(), bech32_to_canonical(ADDRESS));
}

}
18 changes: 17 additions & 1 deletion packages/shade_protocol/src/signature/transaction.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use cosmwasm_std::{Binary, Uint128};
use cosmwasm_std::{Api, Binary, CanonicalAddr, HumanAddr, StdResult, Uint128};
use ripemd160::{Digest, Ripemd160};
use secret_toolkit::crypto::sha_256;
use crate::signature::Permit;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
Expand Down Expand Up @@ -28,6 +30,20 @@ impl PubKey {
}
}

pub struct PubKeyValue(pub Binary);

impl PubKeyValue {
pub fn as_canonical(&self) -> CanonicalAddr {
let mut hasher = Ripemd160::new();
hasher.update(sha_256(&self.0.0));
CanonicalAddr(Binary(hasher.finalize().to_vec()))
}

pub fn as_humanaddr<A: Api>(&self, api: &A) -> StdResult<HumanAddr> {
api.human_address(&self.as_canonical())
}
}

#[remain::sorted]
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
Expand Down
0