Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7408f469b6 | |||
| bf4e18be15 | |||
| 9e8455f49e | |||
| 4352dddde5 | |||
| a44d5f98b2 | |||
| 9f4dac592c | |||
| 61fcc9f1b9 | |||
| 9bb59fddf7 | |||
| 86bf8b86bc | |||
| d052fb80ca | |||
| abf97e7be8 | |||
| 3706c7b01c | |||
| 48b1e5a720 | |||
| 4d2788f055 | |||
| 209be7d433 | |||
| 7fb15725e8 | |||
| 5c693f0148 | |||
| c041f51ba8 | |||
| 0ea4560644 | |||
| 887331a1e0 | |||
| 7a5bee30ce | |||
| 842cba3d9a | |||
| 0ae4e7bdfb | |||
| 7301316f2c |
+2
-1
@@ -33,4 +33,5 @@ contracts/mixnet/code_id
|
||||
contracts/mixnet/Justfile
|
||||
contracts/mixnet/Makefile
|
||||
validator-config
|
||||
*.patch
|
||||
*.patch
|
||||
validator-api-config.toml
|
||||
@@ -11,7 +11,7 @@ use std::fmt::Display;
|
||||
pub struct UnpackedDelegation<T> {
|
||||
pub owner: Addr,
|
||||
pub node_identity: IdentityKey,
|
||||
pub delegation_data: T,
|
||||
pub delegation_data: T, // proxy address used to delegate the funds on behalf of anouther address
|
||||
}
|
||||
|
||||
impl<T> UnpackedDelegation<T> {
|
||||
@@ -28,13 +28,15 @@ impl<T> UnpackedDelegation<T> {
|
||||
pub struct RawDelegationData {
|
||||
pub amount: Uint128,
|
||||
pub block_height: u64,
|
||||
pub proxy: Option<Addr> // proxy address used to delegate the funds on behalf of anouther address
|
||||
}
|
||||
|
||||
impl RawDelegationData {
|
||||
pub fn new(amount: Uint128, block_height: u64) -> Self {
|
||||
pub fn new(amount: Uint128, block_height: u64, proxy: Option<Addr>) -> Self {
|
||||
RawDelegationData {
|
||||
amount,
|
||||
block_height,
|
||||
proxy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ pub struct MixNodeBond {
|
||||
pub block_height: u64,
|
||||
pub mix_node: MixNode,
|
||||
pub profit_margin_percent: Option<u8>,
|
||||
pub proxy: Option<Addr>
|
||||
}
|
||||
|
||||
impl MixNodeBond {
|
||||
@@ -158,6 +159,7 @@ impl MixNodeBond {
|
||||
block_height: u64,
|
||||
mix_node: MixNode,
|
||||
profit_margin_percent: Option<u8>,
|
||||
proxy: Option<Addr>,
|
||||
) -> Self {
|
||||
MixNodeBond {
|
||||
total_delegation: coin(0, &bond_amount.denom),
|
||||
@@ -167,6 +169,7 @@ impl MixNodeBond {
|
||||
block_height,
|
||||
mix_node,
|
||||
profit_margin_percent,
|
||||
proxy
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +443,7 @@ mod tests {
|
||||
block_height: 100,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None
|
||||
};
|
||||
|
||||
let mix2 = MixNodeBond {
|
||||
@@ -450,6 +454,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None
|
||||
};
|
||||
|
||||
let mix3 = MixNodeBond {
|
||||
@@ -460,6 +465,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None
|
||||
};
|
||||
|
||||
let mix4 = MixNodeBond {
|
||||
@@ -470,6 +476,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None
|
||||
};
|
||||
|
||||
let mix5 = MixNodeBond {
|
||||
@@ -480,6 +487,7 @@ mod tests {
|
||||
block_height: 120,
|
||||
mix_node: mixnode_fixture(),
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None
|
||||
};
|
||||
|
||||
// summary:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnode::NodeRewardParams;
|
||||
use crate::StateParams;
|
||||
use crate::{Gateway, IdentityKey, MixNode};
|
||||
@@ -59,6 +58,21 @@ pub enum ExecuteMsg {
|
||||
// nonce of the current rewarding interval
|
||||
rewarding_interval_nonce: u32,
|
||||
},
|
||||
DelegateToMixnodeOnBehalf {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
},
|
||||
UndelegateFromMixnodeOnBehalf {
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
},
|
||||
BondMixnodeOnBehalf {
|
||||
mix_node: MixNode,
|
||||
owner: Addr,
|
||||
},
|
||||
UnbondMixnodeOnBehalf {
|
||||
owner: Addr,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
Generated
+1006
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
[workspace]
|
||||
members = ["erc20-bridge", "mixnet", "vesting"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
@@ -5,22 +5,9 @@ edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace] # adding a blank workspace to keep it out of the global workspace.
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
@@ -12,22 +12,9 @@ exclude = [
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[workspace] # adding a blank workspace to keep it out of the global workspace.
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
rpath = false
|
||||
lto = true
|
||||
debug-assertions = false
|
||||
codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
@@ -35,6 +22,7 @@ backtraces = ["cosmwasm-std/backtraces"]
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config"}
|
||||
vesting-contract = { path = "../vesting" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", features = ["iterator"] }
|
||||
|
||||
@@ -36,6 +36,8 @@ pub const DEFAULT_SYBIL_RESISTANCE_PERCENT: u8 = 30;
|
||||
// We'll be assuming a few more things, profit margin and cost function. Since we don't have relialable package measurement, we'll be using uptime. We'll also set the value of 1 Nym to 1 $, to be able to translate epoch costs to Nyms. We'll also assume a cost of 40$ per epoch(month), converting that to Nym at our 1$ rate translates to 40_000_000 uNyms
|
||||
pub const DEFAULT_COST_PER_EPOCH: u32 = 40_000_000;
|
||||
|
||||
pub const VESTING_CONTRACT_ADDR: &str = "vesting-contract-address";
|
||||
|
||||
fn default_initial_state(owner: Addr, env: Env) -> State {
|
||||
let mixnode_bond_reward_rate = Decimal::percent(INITIAL_MIXNODE_BOND_REWARD_RATE);
|
||||
let mixnode_delegation_reward_rate = Decimal::percent(INITIAL_MIXNODE_DELEGATION_REWARD_RATE);
|
||||
@@ -141,6 +143,31 @@ pub fn execute(
|
||||
ExecuteMsg::FinishMixnodeRewarding {
|
||||
rewarding_interval_nonce,
|
||||
} => transactions::try_finish_mixnode_rewarding(deps, info, rewarding_interval_nonce),
|
||||
ExecuteMsg::DelegateToMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
delegate,
|
||||
} => transactions::try_delegate_to_mixnode_on_behalf(
|
||||
deps,
|
||||
env,
|
||||
info,
|
||||
mix_identity,
|
||||
delegate,
|
||||
),
|
||||
ExecuteMsg::UndelegateFromMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
delegate,
|
||||
} => transactions::try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
mix_identity,
|
||||
delegate,
|
||||
),
|
||||
ExecuteMsg::BondMixnodeOnBehalf { mix_node, owner } => {
|
||||
transactions::try_add_mixnode_on_behalf(deps, env, info, mix_node, owner)
|
||||
}
|
||||
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
|
||||
transactions::try_remove_mixnode_on_behalf(deps, info, owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -100,4 +100,7 @@ pub enum ContractError {
|
||||
|
||||
#[error("Mixnode {identity} has already been rewarded during the current rewarding interval")]
|
||||
MixnodeAlreadyRewarded { identity: IdentityKey },
|
||||
|
||||
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
|
||||
ProxyMismatch{existing: String, incoming: String}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ mod tests {
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42);
|
||||
let raw_delegation = RawDelegationData::new(1000u128.into(), 42, None);
|
||||
let mut start_after = None;
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
|
||||
@@ -888,7 +888,7 @@ pub(crate) mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegation_owner.as_bytes(),
|
||||
&RawDelegationData::new(42u128.into(), 12_345),
|
||||
&RawDelegationData::new(42u128.into(), 12_345, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@@ -42,6 +42,18 @@ const PREFIX_REWARDED_MIXNODES: &[u8] = b"rm";
|
||||
// Contract-level stuff
|
||||
|
||||
// TODO Unify bucket and mixnode storage functions
|
||||
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
|
||||
if let Some(proxy) = &proxy {
|
||||
address
|
||||
.as_bytes()
|
||||
.iter()
|
||||
.zip(proxy.as_bytes())
|
||||
.map(|(x, y)| x ^ y)
|
||||
.collect()
|
||||
} else {
|
||||
address.as_bytes().to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn config(storage: &mut dyn Storage) -> Singleton<State> {
|
||||
singleton(storage, CONFIG_KEY)
|
||||
@@ -460,6 +472,7 @@ mod tests {
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
mixnodes(&mut storage)
|
||||
@@ -513,8 +526,8 @@ mod tests {
|
||||
let delegation_owner1 = Addr::unchecked("bar1");
|
||||
let node_identity2: IdentityKey = "foo2".into();
|
||||
let delegation_owner2 = Addr::unchecked("bar2");
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000);
|
||||
let raw_delegation1 = RawDelegationData::new(1u128.into(), 1000, None);
|
||||
let raw_delegation2 = RawDelegationData::new(2u128.into(), 2000, None);
|
||||
|
||||
mix_delegations(&mut deps.storage, &node_identity1)
|
||||
.save(delegation_owner1.as_bytes(), &raw_delegation1)
|
||||
@@ -586,7 +599,7 @@ mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -602,7 +615,7 @@ mod tests {
|
||||
|
||||
// amount is incremented, block height remains the same
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), 42),
|
||||
RawDelegationData::new(1001u128.into(), 42, None),
|
||||
mix_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
@@ -622,7 +635,7 @@ mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -639,7 +652,7 @@ mod tests {
|
||||
|
||||
// amount is not incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
RawDelegationData::new(1000u128.into(), delegation_blockstamp, None),
|
||||
mix_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
@@ -658,7 +671,7 @@ mod tests {
|
||||
|
||||
// amount is incremented
|
||||
assert_eq!(
|
||||
RawDelegationData::new(1001u128.into(), delegation_blockstamp),
|
||||
RawDelegationData::new(1001u128.into(), delegation_blockstamp, None),
|
||||
mix_delegations_read(&mut deps.storage, &node_identity)
|
||||
.load(delegator_address.as_bytes())
|
||||
.unwrap()
|
||||
@@ -679,7 +692,7 @@ mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp, None),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -719,7 +732,7 @@ mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
delegator_address.as_bytes(),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp),
|
||||
&RawDelegationData::new(1000u128.into(), delegation_blockstamp, None),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -134,6 +134,7 @@ pub mod helpers {
|
||||
12_345,
|
||||
mix_node,
|
||||
None,
|
||||
None
|
||||
)
|
||||
}
|
||||
|
||||
@@ -164,7 +165,7 @@ pub mod helpers {
|
||||
}
|
||||
|
||||
pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData {
|
||||
RawDelegationData::new(Uint128(amount), 42)
|
||||
RawDelegationData::new(Uint128(amount), 42, None)
|
||||
}
|
||||
|
||||
pub fn query_contract_balance(
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use crate::contract::VESTING_CONTRACT_ADDR;
|
||||
use crate::error::ContractError;
|
||||
use crate::helpers::{calculate_epoch_reward_rate, scale_reward_by_uptime, Delegations};
|
||||
use crate::queries;
|
||||
use crate::storage::*;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response, StdResult, Uint128,
|
||||
attr, coins, wasm_execute, Addr, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Response,
|
||||
StdResult, Uint128,
|
||||
};
|
||||
use cosmwasm_storage::ReadonlyBucket;
|
||||
use mixnet_contract::mixnode::NodeRewardParams;
|
||||
use mixnet_contract::{
|
||||
Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams,
|
||||
};
|
||||
use vesting_contract::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
|
||||
pub(crate) const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500;
|
||||
|
||||
@@ -56,17 +59,44 @@ fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn try_add_mixnode(
|
||||
pub fn try_add_mixnode_on_behalf(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
owner: Addr,
|
||||
) -> Result<Response, ContractError> {
|
||||
let proxy = info.sender.to_owned();
|
||||
|
||||
if proxy != VESTING_CONTRACT_ADDR {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
_try_add_mixnode(deps, env, info, mix_node, owner, None)
|
||||
}
|
||||
|
||||
pub fn try_add_mixnode(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
) -> Result<Response, ContractError> {
|
||||
let sender_bytes = info.sender.as_bytes();
|
||||
let owner = info.sender.to_owned();
|
||||
|
||||
_try_add_mixnode(deps, env, info, mix_node, owner, None)
|
||||
}
|
||||
|
||||
fn _try_add_mixnode(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_node: MixNode,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
// if the client has an active bonded gateway, don't allow mixnode bonding
|
||||
if gateways_owners_read(deps.storage)
|
||||
.may_load(sender_bytes)?
|
||||
.may_load(&owner.as_bytes())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ContractError::AlreadyOwnsGateway);
|
||||
@@ -74,7 +104,7 @@ pub(crate) fn try_add_mixnode(
|
||||
|
||||
let mut was_present = false;
|
||||
// if the client has an active mixnode with a different identity, don't allow bonding
|
||||
if let Some(existing_node) = mixnodes_owners_read(deps.storage).may_load(sender_bytes)? {
|
||||
if let Some(existing_node) = mixnodes_owners_read(deps.storage).may_load(&owner.as_bytes())? {
|
||||
if existing_node != mix_node.identity_key {
|
||||
return Err(ContractError::AlreadyOwnsMixnode);
|
||||
}
|
||||
@@ -85,7 +115,7 @@ pub(crate) fn try_add_mixnode(
|
||||
if let Some(existing_bond) =
|
||||
mixnodes_read(deps.storage).may_load(mix_node.identity_key.as_bytes())?
|
||||
{
|
||||
if existing_bond.owner != info.sender {
|
||||
if existing_bond.owner != owner {
|
||||
return Err(ContractError::DuplicateMixnode {
|
||||
owner: existing_bond.owner,
|
||||
});
|
||||
@@ -100,11 +130,12 @@ pub(crate) fn try_add_mixnode(
|
||||
|
||||
let mut bond = MixNodeBond::new(
|
||||
info.funds[0].clone(),
|
||||
info.sender.clone(),
|
||||
owner.clone(),
|
||||
layer,
|
||||
env.block.height,
|
||||
mix_node,
|
||||
None,
|
||||
proxy,
|
||||
);
|
||||
|
||||
// this might potentially require more gas if a significant number of delegations was there
|
||||
@@ -115,7 +146,7 @@ pub(crate) fn try_add_mixnode(
|
||||
let identity = bond.identity();
|
||||
|
||||
mixnodes(deps.storage).save(identity.as_bytes(), &bond)?;
|
||||
mixnodes_owners(deps.storage).save(sender_bytes, identity)?;
|
||||
mixnodes_owners(deps.storage).save(&owner.as_bytes(), identity)?;
|
||||
increment_layer_count(deps.storage, bond.layer)?;
|
||||
|
||||
let attributes = vec![attr("overwritten", was_present)];
|
||||
@@ -127,44 +158,82 @@ pub(crate) fn try_add_mixnode(
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_mixnode(
|
||||
pub fn try_remove_mixnode_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner: Addr,
|
||||
) -> Result<Response, ContractError> {
|
||||
let sender_bytes = info.sender.as_bytes();
|
||||
let proxy = info.sender.to_owned();
|
||||
|
||||
if proxy != VESTING_CONTRACT_ADDR {
|
||||
Err(ContractError::Unauthorized)
|
||||
} else {
|
||||
_try_remove_mixnode(deps, owner, Some(proxy))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_remove_mixnode(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
|
||||
let owner = info.sender;
|
||||
|
||||
_try_remove_mixnode(deps, owner, None)
|
||||
}
|
||||
|
||||
pub(crate) fn _try_remove_mixnode(
|
||||
deps: DepsMut,
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
// try to find the identity of the sender's node
|
||||
let mix_identity = match mixnodes_owners_read(deps.storage).may_load(sender_bytes)? {
|
||||
|
||||
let mix_identity = match mixnodes_owners_read(deps.storage).may_load(owner.as_bytes())? {
|
||||
Some(identity) => identity,
|
||||
None => return Err(ContractError::NoAssociatedMixNodeBond { owner: info.sender }),
|
||||
None => return Err(ContractError::NoAssociatedMixNodeBond { owner: owner }),
|
||||
};
|
||||
|
||||
// get the bond, since we found associated identity, the node MUST exist
|
||||
let mixnode_bond = mixnodes_read(deps.storage).load(mix_identity.as_bytes())?;
|
||||
|
||||
// send bonded funds back to the bond owner
|
||||
let messages = vec![BankMsg::Send {
|
||||
to_address: info.sender.as_str().to_owned(),
|
||||
amount: vec![mixnode_bond.bond_amount()],
|
||||
if proxy == mixnode_bond.proxy {
|
||||
// send bonded funds back to the bond owner
|
||||
let mut messages = vec![BankMsg::Send {
|
||||
to_address: owner.as_str().to_owned(),
|
||||
amount: vec![mixnode_bond.bond_amount()],
|
||||
}
|
||||
.into()];
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUnbond {
|
||||
owner: owner.to_owned(),
|
||||
amount: coins(mixnode_bond.bond_amount.amount.u128(), DENOM)[0].clone(),
|
||||
};
|
||||
|
||||
messages.push(wasm_execute(proxy, &msg, coins(0, DENOM))?.into());
|
||||
}
|
||||
|
||||
// remove the bond from the list of bonded mixnodes
|
||||
mixnodes(deps.storage).remove(mix_identity.as_bytes());
|
||||
// remove the node ownership
|
||||
mixnodes_owners(deps.storage).remove(owner.as_bytes());
|
||||
// decrement layer count
|
||||
decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
|
||||
// log our actions
|
||||
let attributes = vec![attr("action", "unbond"), attr("mixnode_bond", mixnode_bond)];
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes,
|
||||
data: None,
|
||||
})
|
||||
} else {
|
||||
Err(ContractError::ProxyMismatch {
|
||||
existing: mixnode_bond
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
})
|
||||
}
|
||||
.into()];
|
||||
|
||||
// remove the bond from the list of bonded mixnodes
|
||||
mixnodes(deps.storage).remove(mix_identity.as_bytes());
|
||||
// remove the node ownership
|
||||
mixnodes_owners(deps.storage).remove(sender_bytes);
|
||||
// decrement layer count
|
||||
decrement_layer_count(deps.storage, mixnode_bond.layer)?;
|
||||
|
||||
// log our actions
|
||||
let attributes = vec![attr("action", "unbond"), attr("mixnode_bond", mixnode_bond)];
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes,
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_gateway_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> {
|
||||
@@ -656,6 +725,40 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
validate_delegation_stake(&info.funds)?;
|
||||
|
||||
let delegate_addr = info.sender.clone();
|
||||
let amount = info.funds[0].amount;
|
||||
|
||||
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate_addr, amount, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
) -> Result<Response, ContractError> {
|
||||
// check if the delegation contains any funds of the appropriate denomination
|
||||
validate_delegation_stake(&info.funds)?;
|
||||
let amount = info.funds[0].amount;
|
||||
|
||||
let proxy = info.sender;
|
||||
|
||||
if proxy != VESTING_CONTRACT_ADDR {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
_try_delegate_to_mixnode(deps, env, mix_identity, &delegate, amount, Some(proxy))
|
||||
}
|
||||
|
||||
fn _try_delegate_to_mixnode(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: &Addr,
|
||||
amount: Uint128,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
// check if the target node actually exists
|
||||
let mut current_bond = match mixnodes_read(deps.storage).load(mix_identity.as_bytes()) {
|
||||
Ok(bond) => bond,
|
||||
@@ -666,25 +769,23 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
}
|
||||
};
|
||||
|
||||
let amount = info.funds[0].amount;
|
||||
|
||||
// update total_delegation of this node
|
||||
current_bond.total_delegation.amount += info.funds[0].amount;
|
||||
current_bond.total_delegation.amount += amount;
|
||||
mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?;
|
||||
|
||||
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
|
||||
let sender_bytes = info.sender.as_bytes();
|
||||
|
||||
// We need to differentiate proxy delefations from direct delegations with the same owner
|
||||
let storage_key = generate_storage_key(delegate, proxy.as_ref());
|
||||
// write the delegation
|
||||
let new_amount = match delegation_bucket.may_load(sender_bytes)? {
|
||||
let new_amount = match delegation_bucket.may_load(&storage_key)? {
|
||||
Some(existing_delegation) => existing_delegation.amount + amount,
|
||||
None => amount,
|
||||
};
|
||||
// the block height is reset, if it existed
|
||||
let new_delegation = RawDelegationData::new(new_amount, env.block.height);
|
||||
delegation_bucket.save(sender_bytes, &new_delegation)?;
|
||||
let new_delegation = RawDelegationData::new(new_amount, env.block.height, proxy);
|
||||
delegation_bucket.save(&storage_key, &new_delegation)?;
|
||||
|
||||
reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?;
|
||||
reverse_mix_delegations(deps.storage, delegate).save(mix_identity.as_bytes(), &())?;
|
||||
|
||||
Ok(Response::default())
|
||||
}
|
||||
@@ -693,46 +794,98 @@ pub(crate) fn try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Response, ContractError> {
|
||||
let sender = info.sender.clone();
|
||||
_try_remove_delegation_from_mixnode(deps, info, mix_identity, &sender, None)
|
||||
}
|
||||
|
||||
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: Addr,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.sender != VESTING_CONTRACT_ADDR {
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
let proxy = info.sender.clone();
|
||||
|
||||
_try_remove_delegation_from_mixnode(deps, info, mix_identity, &delegate, Some(proxy))
|
||||
}
|
||||
|
||||
fn _try_remove_delegation_from_mixnode(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
mix_identity: IdentityKey,
|
||||
delegate: &Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity);
|
||||
let sender_bytes = info.sender.as_bytes();
|
||||
match delegation_bucket.may_load(sender_bytes)? {
|
||||
|
||||
let storage_key = generate_storage_key(delegate, proxy.as_ref());
|
||||
|
||||
match delegation_bucket.may_load(&storage_key)? {
|
||||
Some(delegation) => {
|
||||
// remove delegation from the buckets
|
||||
delegation_bucket.remove(sender_bytes);
|
||||
reverse_mix_delegations(deps.storage, &info.sender).remove(mix_identity.as_bytes());
|
||||
if proxy == delegation.proxy {
|
||||
delegation_bucket.remove(&storage_key);
|
||||
reverse_mix_delegations(deps.storage, &info.sender).remove(mix_identity.as_bytes());
|
||||
|
||||
// send delegated funds back to the delegation owner
|
||||
let messages = vec![BankMsg::Send {
|
||||
to_address: info.sender.to_string(),
|
||||
amount: coins(delegation.amount.u128(), DENOM),
|
||||
// Send delegated funds back to the delegation owner. In case of a direct delegation proxy should be None.
|
||||
// Both the function call and in the RawDelegationData, in a proxied undelegation proxy and sender must match.
|
||||
// Since we control that only vesting account address can create proxy delegations at time of delegation only vesting account
|
||||
// can undelegate proxy undelegations.
|
||||
let mut messages = vec![BankMsg::Send {
|
||||
to_address: info.sender.to_string(),
|
||||
amount: coins(delegation.amount.u128(), DENOM),
|
||||
}
|
||||
.into()];
|
||||
|
||||
if let Some(proxy) = &proxy {
|
||||
let msg = VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: delegate.to_owned(),
|
||||
mix_identity: mix_identity.clone(),
|
||||
amount: coins(delegation.amount.u128(), DENOM)[0].clone(),
|
||||
};
|
||||
|
||||
messages.push(wasm_execute(proxy, &msg, coins(0, DENOM))?.into());
|
||||
}
|
||||
|
||||
// update total_delegation of this node
|
||||
let mut mixnodes_bucket = mixnodes(deps.storage);
|
||||
// in some rare cases the mixnode bond might no longer exist as the node unbonded
|
||||
// before delegation was removed. that is fine
|
||||
if let Some(mut existing_bond) =
|
||||
mixnodes_bucket.may_load(mix_identity.as_bytes())?
|
||||
{
|
||||
// we should NEVER underflow here, if we do, it means we have some serious error in our logic
|
||||
existing_bond.total_delegation.amount = existing_bond
|
||||
.total_delegation
|
||||
.amount
|
||||
.checked_sub(delegation.amount)
|
||||
.unwrap();
|
||||
mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes: Vec::new(),
|
||||
data: None,
|
||||
})
|
||||
} else {
|
||||
Err(ContractError::ProxyMismatch {
|
||||
existing: delegation
|
||||
.proxy
|
||||
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
|
||||
})
|
||||
}
|
||||
.into()];
|
||||
|
||||
// update total_delegation of this node
|
||||
let mut mixnodes_bucket = mixnodes(deps.storage);
|
||||
// in some rare cases the mixnode bond might no longer exist as the node unbonded
|
||||
// before delegation was removed. that is fine
|
||||
if let Some(mut existing_bond) = mixnodes_bucket.may_load(mix_identity.as_bytes())? {
|
||||
// we should NEVER underflow here, if we do, it means we have some serious error in our logic
|
||||
existing_bond.total_delegation.amount = existing_bond
|
||||
.total_delegation
|
||||
.amount
|
||||
.checked_sub(delegation.amount)
|
||||
.unwrap();
|
||||
mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?;
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
submessages: Vec::new(),
|
||||
messages,
|
||||
attributes: Vec::new(),
|
||||
data: None,
|
||||
})
|
||||
}
|
||||
None => Err(ContractError::NoMixnodeDelegationFound {
|
||||
identity: mix_identity,
|
||||
address: info.sender,
|
||||
address: delegate.to_owned(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -2048,6 +2201,7 @@ pub mod tests {
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
mixnodes(deps.as_mut().storage)
|
||||
@@ -2057,7 +2211,7 @@ pub mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
b"delegator",
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height),
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2337,6 +2491,7 @@ pub mod tests {
|
||||
..mix_node_fixture()
|
||||
},
|
||||
profit_margin_percent: Some(10),
|
||||
proxy: None,
|
||||
};
|
||||
|
||||
mixnodes(deps.as_mut().storage)
|
||||
@@ -2349,7 +2504,7 @@ pub mod tests {
|
||||
mix_delegations(&mut deps.storage, &node_identity)
|
||||
.save(
|
||||
b"delegator",
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height),
|
||||
&RawDelegationData::new(initial_delegation.into(), env.block.height, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -2554,7 +2709,7 @@ pub mod tests {
|
||||
.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation.amount, mock_env().block.height),
|
||||
RawDelegationData::new(delegation.amount, mock_env().block.height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2618,7 +2773,7 @@ pub mod tests {
|
||||
.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation.amount, mock_env().block.height),
|
||||
RawDelegationData::new(delegation.amount, mock_env().block.height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2668,7 +2823,8 @@ pub mod tests {
|
||||
assert_eq!(
|
||||
RawDelegationData::new(
|
||||
delegation1.amount + delegation2.amount,
|
||||
mock_env().block.height
|
||||
mock_env().block.height,
|
||||
None
|
||||
),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
@@ -2715,7 +2871,7 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation.amount, initial_height),
|
||||
RawDelegationData::new(delegation.amount, initial_height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2730,7 +2886,7 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation.amount + delegation.amount, updated_height),
|
||||
RawDelegationData::new(delegation.amount + delegation.amount, updated_height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2763,7 +2919,7 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation1.amount, initial_height),
|
||||
RawDelegationData::new(delegation1.amount, initial_height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner1.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2778,13 +2934,13 @@ pub mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation1.amount, initial_height),
|
||||
RawDelegationData::new(delegation1.amount, initial_height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner1.as_bytes())
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
RawDelegationData::new(delegation2.amount, second_height),
|
||||
RawDelegationData::new(delegation2.amount, second_height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner2.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2848,7 +3004,7 @@ pub mod tests {
|
||||
.is_ok());
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(123u128.into(), mock_env().block.height),
|
||||
RawDelegationData::new(123u128.into(), mock_env().block.height, None),
|
||||
mix_delegations_read(&deps.storage, &identity1)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2860,7 +3016,7 @@ pub mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(42u128.into(), mock_env().block.height),
|
||||
RawDelegationData::new(42u128.into(), mock_env().block.height, None),
|
||||
mix_delegations_read(&deps.storage, &identity2)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -2927,7 +3083,7 @@ pub mod tests {
|
||||
try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
RawDelegationData::new(100u128.into(), mock_env().block.height),
|
||||
RawDelegationData::new(100u128.into(), mock_env().block.height, None),
|
||||
mix_delegations_read(&deps.storage, &identity)
|
||||
.load(delegation_owner.as_bytes())
|
||||
.unwrap()
|
||||
@@ -3136,19 +3292,19 @@ pub mod tests {
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(
|
||||
b"delegator1",
|
||||
&RawDelegationData::new(initial_delegation1.into(), env.block.height),
|
||||
&RawDelegationData::new(initial_delegation1.into(), env.block.height, None),
|
||||
)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(
|
||||
b"delegator2",
|
||||
&RawDelegationData::new(initial_delegation2.into(), env.block.height),
|
||||
&RawDelegationData::new(initial_delegation2.into(), env.block.height, None),
|
||||
)
|
||||
.unwrap();
|
||||
mix_delegations(&mut deps.storage, &identity)
|
||||
.save(
|
||||
b"delegator3",
|
||||
&RawDelegationData::new(initial_delegation3.into(), env.block.height),
|
||||
&RawDelegationData::new(initial_delegation3.into(), env.block.height, None),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
||||
Generated
+908
@@ -0,0 +1,908 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "az"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6dff4a1892b54d70af377bf7a17064192e822865791d812957f21e3108c325"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
dependencies = [
|
||||
"block-padding",
|
||||
"byte-tools",
|
||||
"byteorder",
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-padding"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
|
||||
dependencies = [
|
||||
"byte-tools",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"network-defaults",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"ed25519-zebra",
|
||||
"k256",
|
||||
"rand_core 0.5.1",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-derive"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
"cosmwasm-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde-json-wasm",
|
||||
"thiserror",
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "0.14.1"
|
||||
source = "git+https://github.com/jstuczyn/cosmwasm?branch=0.14.1-updatedk256#308781cd5f33b0e996176c6b500793d3648c0f7b"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-mac"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.5.1",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
|
||||
dependencies = [
|
||||
"const-oid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||
dependencies = [
|
||||
"generic-array 0.12.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array 0.14.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "ecdsa"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
|
||||
dependencies = [
|
||||
"der",
|
||||
"elliptic-curve",
|
||||
"hmac",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-zebra"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"rand_core 0.5.1",
|
||||
"serde",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "beca177dcb8eb540133e7680baff45e7cc4d93bf22002676cec549f82343721b"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"ff",
|
||||
"generic-array 0.14.4",
|
||||
"group",
|
||||
"pkcs8",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake-simd"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||
|
||||
[[package]]
|
||||
name = "ff"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0f40b2dcd8bc322217a5f6559ae5f9e9d1de202a2ecee2e9eafcbece7562a4f"
|
||||
dependencies = [
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixed"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d333a26ec13a023c6dff4b7584de4d323cfee2e508f5dd2bbee6669e4f7efdf"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bytemuck",
|
||||
"half",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.9.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "group"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c363a5301b8f153d80747126a04b3c82073b9fe3130571a9d170cacdeaf7912"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"rand_core 0.6.3",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "3.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"quick-error",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
|
||||
dependencies = [
|
||||
"crypto-mac",
|
||||
"digest 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "humantime-serde"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac34a56cfd4acddb469cc7fff187ed5ac36f498ba085caf8bbc725e3ff474058"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
|
||||
|
||||
[[package]]
|
||||
name = "k256"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "903ae2481bcdfdb7b68e0a9baa4b7c9aff600b9ae2e8e5bb5833b8c91ab851ea"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ecdsa",
|
||||
"elliptic-curve",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "maplit"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"az",
|
||||
"cosmwasm-std",
|
||||
"fixed",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "network-defaults"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hex-literal",
|
||||
"serde",
|
||||
"time",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
dependencies = [
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
|
||||
dependencies = [
|
||||
"maplit",
|
||||
"pest",
|
||||
"sha-1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
|
||||
dependencies = [
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom 0.1.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271ac0c667b8229adf70f0f957697c96fafd7486ab7481e15dc5e45e3e6a4368"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ebda811090b257411540779860bc09bf321bc587f58d2c5864309d1566214e7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde-json-wasm"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50eef3672ec8fa45f3457fd423ba131117786784a895548021976117c1ded449"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.130"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_repr"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha-1"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||
dependencies = [
|
||||
"block-buffer 0.7.3",
|
||||
"digest 0.8.1",
|
||||
"fake-simd",
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer 0.9.0",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest 0.9.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4"
|
||||
dependencies = [
|
||||
"digest 0.9.0",
|
||||
"rand_core 0.6.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
|
||||
dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41effe7cfa8af36f439fac33861b66b049edc6f9a32331e2312660529c1c24ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||
|
||||
[[package]]
|
||||
name = "uint"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crunchy",
|
||||
"hex",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contracts"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"mixnet-contract",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
|
||||
edition = "2018"
|
||||
|
||||
exclude = [
|
||||
# Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication.
|
||||
"contract.wasm",
|
||||
"hash.txt",
|
||||
]
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
# for more explicit tests, cargo test --features=backtraces
|
||||
backtraces = ["cosmwasm-std/backtraces"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract = { path = "../../common/mixnet-contract" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
# this branch is identical to 0.14.1 with addition of updated k256 dependency required to help poor cargo choose correct version
|
||||
cosmwasm-std = { version = "1.0.0-beta2", features = ["iterator"]}
|
||||
cosmwasm-storage = { version = "1.0.0-beta2", features = ["iterator"]}
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
@@ -0,0 +1,2 @@
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
@@ -0,0 +1,11 @@
|
||||
## Nym vesting contract
|
||||
|
||||
1. Initial vesting tokens are deposited and assigned to existing addresses via `CreatePeriodicVestingAccount`
|
||||
2. Admin account can then delegate vested and unvested tokens to mixnodes on behalf of vesting accounts
|
||||
3. Vesting accounts can withdraw vested and undelegated (spendable) coins to their addresses
|
||||
|
||||
### Vesting coin delegation flow
|
||||
|
||||

|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 699 KiB |
@@ -0,0 +1,437 @@
|
||||
use crate::errors::ContractError;
|
||||
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
|
||||
use crate::storage::{get_account, get_account_balance, set_account_balance};
|
||||
use crate::vesting::{
|
||||
populate_vesting_periods, BondingAccount, DelegationAccount, PeriodicVestingAccount,
|
||||
VestingAccount,
|
||||
};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{
|
||||
attr, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo,
|
||||
QueryResponse, Response, Timestamp, Uint128,
|
||||
};
|
||||
use mixnet_contract::{IdentityKey, MixNode};
|
||||
|
||||
pub const NUM_VESTING_PERIODS: usize = 8;
|
||||
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
|
||||
pub const ADMIN_ADDRESS: &str = "admin";
|
||||
|
||||
// TODO: Validate vesting/vested withdraw mathematics
|
||||
// TODO: Try and get to the bottom of multilevel bucket vs vector and performance of the whole storage thing
|
||||
|
||||
/// Instantiate the contract.
|
||||
///
|
||||
/// `deps` contains Storage, API and Querier
|
||||
/// `env` contains block, message and contract info
|
||||
/// `msg` is the contract initialization message, sort of like a constructor call.
|
||||
#[entry_point]
|
||||
pub fn instantiate(
|
||||
_deps: DepsMut,
|
||||
_env: Env,
|
||||
_info: MessageInfo,
|
||||
_msg: InitMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
/// Handle an incoming message
|
||||
#[entry_point]
|
||||
pub fn execute(
|
||||
deps: DepsMut,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::DelegateToMixnode { mix_identity } => {
|
||||
try_delegate_to_mixnode(mix_identity, info, env, deps)
|
||||
}
|
||||
ExecuteMsg::UndelegateFromMixnode { mix_identity } => {
|
||||
try_undelegate_from_mixnode(mix_identity, info, deps)
|
||||
}
|
||||
ExecuteMsg::CreatePeriodicVestingAccount {
|
||||
address,
|
||||
start_time,
|
||||
} => try_create_periodic_vesting_account(address, start_time, info, env, deps),
|
||||
ExecuteMsg::WithdrawVestedCoins { amount } => {
|
||||
try_withdraw_vested_coins(amount, env, info, deps)
|
||||
}
|
||||
ExecuteMsg::TrackUndelegation {
|
||||
owner,
|
||||
mix_identity,
|
||||
amount,
|
||||
} => try_track_undelegation(owner, mix_identity, amount, deps),
|
||||
ExecuteMsg::BondMixnode { mix_node } => try_bond_mixnode(mix_node, info, env, deps),
|
||||
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
||||
ExecuteMsg::TrackUnbond { owner, amount } => try_track_unbond(owner, amount, deps),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_bond_mixnode(
|
||||
mix_node: MixNode,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let bond = validate_funds(&info.funds)?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.try_bond_mixnode(mix_node, bond, &env, deps.storage)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.try_unbond_mixnode()
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_track_unbond(
|
||||
owner: Addr,
|
||||
amount: Coin,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(owner.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &owner) {
|
||||
account.track_unbond(amount, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
owner.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_withdraw_vested_coins(
|
||||
amount: Coin,
|
||||
env: Env,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let address = info.sender;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
||||
if amount.amount < spendable_coins.amount {
|
||||
if let Some(balance) = get_account_balance(deps.storage, &address) {
|
||||
let new_balance = balance.u128().saturating_sub(amount.amount.u128());
|
||||
set_account_balance(deps.storage, &address, Uint128::new(new_balance))?;
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
address.as_str().to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let send_tokens = BankMsg::Send {
|
||||
to_address: address.as_str().to_string(),
|
||||
amount: vec![amount],
|
||||
};
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "whitdraw")
|
||||
.add_message(send_tokens))
|
||||
} else {
|
||||
Err(ContractError::InsufficientSpendable(
|
||||
address.as_str().to_string(),
|
||||
spendable_coins.amount.u128(),
|
||||
))
|
||||
}
|
||||
} else {
|
||||
return Err(ContractError::NoAccountForAddress(
|
||||
address.as_str().to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn try_track_undelegation(
|
||||
address: Addr,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let adddress = deps.api.addr_validate(address.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &adddress) {
|
||||
account.track_undelegation(mix_identity, amount, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
address.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_delegate_to_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate_addr = info.sender;
|
||||
let amount = validate_funds(&info.funds)?;
|
||||
let address = deps.api.addr_validate(delegate_addr.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
address.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_undelegate_from_mixnode(
|
||||
mix_identity: IdentityKey,
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate_addr = info.sender;
|
||||
let address = deps.api.addr_validate(delegate_addr.as_str())?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
account.try_undelegate_from_mixnode(mix_identity)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(
|
||||
address.as_str().to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_create_periodic_vesting_account(
|
||||
address: String,
|
||||
start_time: Option<u64>,
|
||||
info: MessageInfo,
|
||||
env: Env,
|
||||
deps: DepsMut,
|
||||
) -> Result<Response, ContractError> {
|
||||
if info.sender != ADMIN_ADDRESS {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
let coin = validate_funds(&info.funds)?;
|
||||
let address = deps.api.addr_validate(&address)?;
|
||||
let start_time = start_time.unwrap_or_else(|| env.block.time.seconds());
|
||||
let periods = populate_vesting_periods(start_time, NUM_VESTING_PERIODS);
|
||||
PeriodicVestingAccount::new(
|
||||
address,
|
||||
coin,
|
||||
Timestamp::from_seconds(start_time),
|
||||
periods,
|
||||
deps.storage,
|
||||
)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
|
||||
let query_res = match msg {
|
||||
QueryMsg::LockedCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_locked_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::SpendableCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_spendable_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetVestedCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_vested_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetVestingCoins {
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
} => to_binary(&try_get_vesting_coins(
|
||||
vesting_account_address,
|
||||
block_time,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetStartTime {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_start_time(vesting_account_address, deps)?),
|
||||
QueryMsg::GetEndTime {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_end_time(vesting_account_address, deps)?),
|
||||
QueryMsg::GetOriginalVesting {
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_original_vesting(vesting_account_address, deps)?),
|
||||
QueryMsg::GetDelegatedFree {
|
||||
block_time,
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_delegated_free(
|
||||
block_time,
|
||||
vesting_account_address,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
QueryMsg::GetDelegatedVesting {
|
||||
block_time,
|
||||
vesting_account_address,
|
||||
} => to_binary(&try_get_delegated_vesting(
|
||||
block_time,
|
||||
vesting_account_address,
|
||||
env,
|
||||
deps,
|
||||
)?),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
pub fn try_get_locked_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.locked_coins(block_time, &env, deps.storage)?)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_spendable_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.spendable_coins(block_time, &env, deps.storage)?)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_vested_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_vested_coins(block_time, &env)?)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_vesting_coins(
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_vesting_coins(block_time, &env)?)
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_start_time(
|
||||
vesting_account_address: String,
|
||||
deps: Deps,
|
||||
) -> Result<Timestamp, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_start_time())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_end_time(
|
||||
vesting_account_address: String,
|
||||
deps: Deps,
|
||||
) -> Result<Timestamp, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_end_time())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_original_vesting(
|
||||
vesting_account_address: String,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_original_vesting())
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_delegated_free(
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: String,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_delegated_free(block_time, &env, deps.storage))
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_get_delegated_vesting(
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: String,
|
||||
env: Env,
|
||||
deps: Deps,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let address = deps.api.addr_validate(&vesting_account_address)?;
|
||||
if let Some(account) = get_account(deps.storage, &address) {
|
||||
Ok(account.get_delegated_vesting(block_time, &env, deps.storage))
|
||||
} else {
|
||||
Err(ContractError::NoAccountForAddress(vesting_account_address))
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_funds(funds: &[Coin]) -> Result<Coin, ContractError> {
|
||||
if funds.is_empty() || funds[0].amount.is_zero() {
|
||||
return Err(ContractError::EmptyFunds);
|
||||
}
|
||||
|
||||
if funds.len() > 1 {
|
||||
return Err(ContractError::MultipleDenoms);
|
||||
}
|
||||
|
||||
if funds[0].denom != DENOM {
|
||||
return Err(ContractError::WrongDenom(
|
||||
funds[0].denom.clone(),
|
||||
DENOM.to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(funds[0].clone())
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use cosmwasm_std::StdError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq)]
|
||||
pub enum ContractError {
|
||||
#[error("{0}")]
|
||||
Std(#[from] StdError),
|
||||
#[error("Account does not exist - {0}")]
|
||||
NoAccountForAddress(String),
|
||||
#[error("Only admin can perform this action, {0} is not admin")]
|
||||
NotAdmin(String),
|
||||
#[error("Balance not found for existing account ({0}), this is a bug")]
|
||||
NoBalanceForAddress(String),
|
||||
#[error("Insufficient balance for address {0} -> {1}")]
|
||||
InsufficientBalance(String, u128),
|
||||
#[error("Insufficient spendable balance for address {0} -> {1}")]
|
||||
InsufficientSpendable(String, u128),
|
||||
#[error(
|
||||
"Only delegation owner can perform delegation actions, {0} is not the delegation owner"
|
||||
)]
|
||||
NotDelegate(String),
|
||||
#[error("Total vesting amount is inprobably low -> {0}, this is likely an error")]
|
||||
ImprobableVestingAmount(u128),
|
||||
#[error("Address {0} has already bonded a node")]
|
||||
AlreadyBonded(String),
|
||||
#[error("Recieved empty funds vector")]
|
||||
EmptyFunds,
|
||||
#[error("Recieved wrong denom: {0}, expected {1}")]
|
||||
WrongDenom(String, String),
|
||||
#[error("Recieved multiple denoms, expected 1")]
|
||||
MultipleDenoms,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod contract;
|
||||
mod errors;
|
||||
pub mod messages;
|
||||
mod storage;
|
||||
mod support;
|
||||
mod vesting;
|
||||
@@ -0,0 +1,77 @@
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::MixNode;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct InitMsg {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ExecuteMsg {
|
||||
DelegateToMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
UndelegateFromMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
CreatePeriodicVestingAccount {
|
||||
address: String,
|
||||
start_time: Option<u64>,
|
||||
},
|
||||
WithdrawVestedCoins {
|
||||
amount: Coin,
|
||||
},
|
||||
TrackUndelegation {
|
||||
owner: Addr,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
},
|
||||
BondMixnode {
|
||||
mix_node: MixNode,
|
||||
},
|
||||
UnbondMixnode {},
|
||||
TrackUnbond {
|
||||
owner: Addr,
|
||||
amount: Coin,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
LockedCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
SpendableCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetVestedCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetVestingCoins {
|
||||
vesting_account_address: String,
|
||||
block_time: Option<Timestamp>,
|
||||
},
|
||||
GetStartTime {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetEndTime {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetOriginalVesting {
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetDelegatedFree {
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: String,
|
||||
},
|
||||
GetDelegatedVesting {
|
||||
block_time: Option<Timestamp>,
|
||||
vesting_account_address: String,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use cosmwasm_std::{Addr, StdResult, Storage, Uint128};
|
||||
use cosmwasm_storage::{bucket, bucket_read, Bucket, ReadonlyBucket};
|
||||
|
||||
use crate::{
|
||||
errors::ContractError,
|
||||
vesting::{BondData, DelegationData, PeriodicVestingAccount},
|
||||
};
|
||||
// storage prefixes
|
||||
// all of them must be unique and presumably not be a prefix of a different one
|
||||
// keeping them as short as possible is also desirable as they are part of each stored key
|
||||
// it's not as important for singletons, but is a nice optimisation for buckets
|
||||
|
||||
// buckets
|
||||
const PREFIX_ACCOUNTS: &[u8] = b"ac";
|
||||
const PREFIX_ACCOUNT_DELEGATIONS: &[u8] = b"ad";
|
||||
const PREFIX_ACCOUNT_BALANCE: &[u8] = b"ab";
|
||||
const PREFIX_ACCOUNT_MIXBOND: &[u8] = b"am";
|
||||
// Contract-level stuff
|
||||
|
||||
fn accounts_mut(storage: &mut dyn Storage) -> Bucket<PeriodicVestingAccount> {
|
||||
bucket(storage, PREFIX_ACCOUNTS)
|
||||
}
|
||||
|
||||
fn accounts(storage: &dyn Storage) -> ReadonlyBucket<PeriodicVestingAccount> {
|
||||
bucket_read(storage, PREFIX_ACCOUNTS)
|
||||
}
|
||||
|
||||
fn account_delegations_mut(storage: &mut dyn Storage) -> Bucket<Vec<DelegationData>> {
|
||||
bucket(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
fn account_delegations(storage: &dyn Storage) -> ReadonlyBucket<Vec<DelegationData>> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_DELEGATIONS)
|
||||
}
|
||||
|
||||
fn account_bond_mut(storage: &mut dyn Storage) -> Bucket<BondData> {
|
||||
bucket(storage, PREFIX_ACCOUNT_MIXBOND)
|
||||
}
|
||||
|
||||
fn account_bond(storage: &dyn Storage) -> ReadonlyBucket<BondData> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_MIXBOND)
|
||||
}
|
||||
|
||||
fn account_balance(storage: &dyn Storage) -> ReadonlyBucket<Uint128> {
|
||||
bucket_read(storage, PREFIX_ACCOUNT_BALANCE)
|
||||
}
|
||||
|
||||
fn account_balance_mut(storage: &mut dyn Storage) -> Bucket<Uint128> {
|
||||
bucket(storage, PREFIX_ACCOUNT_BALANCE)
|
||||
}
|
||||
|
||||
pub fn get_account(storage: &dyn Storage, address: &Addr) -> Option<PeriodicVestingAccount> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
accounts(storage).may_load(address.as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn set_account(
|
||||
storage: &mut dyn Storage,
|
||||
account: PeriodicVestingAccount,
|
||||
) -> Result<(), ContractError> {
|
||||
Ok(accounts_mut(storage).save(account.address().as_bytes(), &account)?)
|
||||
}
|
||||
|
||||
pub fn get_account_delegations(
|
||||
storage: &dyn Storage,
|
||||
address: &Addr,
|
||||
) -> Option<Vec<DelegationData>> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_delegations(storage)
|
||||
.may_load(address.as_bytes())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set_account_delegations(
|
||||
storage: &mut dyn Storage,
|
||||
address: &Addr,
|
||||
delegations: Vec<DelegationData>,
|
||||
) -> StdResult<()> {
|
||||
account_delegations_mut(storage).save(address.as_bytes(), &delegations)
|
||||
}
|
||||
|
||||
pub fn get_account_bond(storage: &dyn Storage, address: &Addr) -> Option<BondData> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_bond(storage).may_load(address.as_bytes()).unwrap()
|
||||
}
|
||||
|
||||
pub fn drop_account_bond(storage: &mut dyn Storage, address: &Addr) -> StdResult<()> {
|
||||
Ok(account_bond_mut(storage).remove(address.as_bytes()))
|
||||
}
|
||||
|
||||
pub fn set_account_bond(
|
||||
storage: &mut dyn Storage,
|
||||
address: &Addr,
|
||||
bond: BondData,
|
||||
) -> StdResult<()> {
|
||||
account_bond_mut(storage).save(address.as_bytes(), &bond)
|
||||
}
|
||||
|
||||
pub fn get_account_balance(storage: &dyn Storage, address: &Addr) -> Option<Uint128> {
|
||||
// Due to using may_load this should be safe to unwrap
|
||||
account_balance(storage)
|
||||
.may_load(address.as_bytes())
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set_account_balance(
|
||||
storage: &mut dyn Storage,
|
||||
address: &Addr,
|
||||
balance: Uint128,
|
||||
) -> StdResult<()> {
|
||||
account_balance_mut(storage).save(address.as_bytes(), &balance)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod tests;
|
||||
@@ -0,0 +1,47 @@
|
||||
#[cfg(test)]
|
||||
pub mod helpers {
|
||||
use crate::contract::{instantiate, NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::messages::InitMsg;
|
||||
use crate::storage;
|
||||
use crate::vesting::populate_vesting_periods;
|
||||
use crate::vesting::PeriodicVestingAccount;
|
||||
use crate::vesting::VestingPeriod;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::from_binary;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use cosmwasm_std::testing::mock_env;
|
||||
use cosmwasm_std::testing::mock_info;
|
||||
use cosmwasm_std::testing::MockApi;
|
||||
use cosmwasm_std::testing::MockQuerier;
|
||||
use cosmwasm_std::Addr;
|
||||
use cosmwasm_std::Coin;
|
||||
use cosmwasm_std::OwnedDeps;
|
||||
use cosmwasm_std::Uint128;
|
||||
use cosmwasm_std::{Empty, Env, MemoryStorage, Storage};
|
||||
|
||||
pub fn init_contract() -> OwnedDeps<MemoryStorage, MockApi, MockQuerier<Empty>> {
|
||||
let mut deps = mock_dependencies();
|
||||
let msg = InitMsg {};
|
||||
let env = mock_env();
|
||||
let info = mock_info("creator", &[]);
|
||||
instantiate(deps.as_mut(), env.clone(), info, msg).unwrap();
|
||||
return deps;
|
||||
}
|
||||
|
||||
pub fn vesting_account_fixture(storage: &mut dyn Storage, env: &Env) -> PeriodicVestingAccount {
|
||||
let start_time = env.block.time;
|
||||
let periods = populate_vesting_periods(start_time.seconds(), NUM_VESTING_PERIODS);
|
||||
|
||||
PeriodicVestingAccount::new(
|
||||
Addr::unchecked("fixture"),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
start_time,
|
||||
periods,
|
||||
storage,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::errors::ContractError;
|
||||
use crate::storage::{
|
||||
drop_account_bond, get_account_balance, get_account_bond, get_account_delegations, set_account,
|
||||
set_account_balance, set_account_bond, set_account_delegations,
|
||||
};
|
||||
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
||||
use cosmwasm_std::{attr, wasm_execute, Addr, Coin, Env, Response, Storage, Timestamp, Uint128};
|
||||
use mixnet_contract::IdentityKey;
|
||||
use mixnet_contract::{ExecuteMsg as MixnetExecuteMsg, MixNode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub trait VestingAccount {
|
||||
// locked_coins returns the set of coins that are not spendable (can still be delegated tough) (i.e. locked),
|
||||
// defined as the vesting coins that are not delegated.
|
||||
//
|
||||
// To get spendable coins of a vesting account, first the total balance must
|
||||
// be retrieved and the locked tokens can be subtracted from the total balance.
|
||||
// Note, the spendable balance can be negative.
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
// Calculates the total spendable balance that can be sent to other accounts.
|
||||
fn spendable_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
fn get_vested_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
) -> Result<Coin, ContractError>;
|
||||
fn get_vesting_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
) -> Result<Coin, ContractError>;
|
||||
|
||||
fn get_start_time(&self) -> Timestamp;
|
||||
fn get_end_time(&self) -> Timestamp;
|
||||
|
||||
fn get_original_vesting(&self) -> Coin;
|
||||
fn get_delegated_free(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Coin;
|
||||
fn get_delegated_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Coin;
|
||||
}
|
||||
|
||||
pub trait DelegationAccount {
|
||||
fn try_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_undelegate_from_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
// track_delegation performs internal vesting accounting necessary when
|
||||
// delegating from a vesting account. It accepts the current block time, the
|
||||
// delegation amount and balance of all coins whose denomination exists in
|
||||
// the account's original vesting balance.
|
||||
fn track_delegation(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
mix_identity: IdentityKey,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
// track_undelegation performs internal vesting accounting necessary when a
|
||||
// vesting account performs an undelegation.
|
||||
fn track_undelegation(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
pub trait BondingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
amount: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_unbond_mixnode(&self) -> Result<Response, ContractError>;
|
||||
|
||||
fn track_bond(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
bond: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError>;
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError>;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct VestingPeriod {
|
||||
pub start_time: u64,
|
||||
}
|
||||
|
||||
impl VestingPeriod {
|
||||
pub fn end_time(&self) -> Timestamp {
|
||||
Timestamp::from_seconds(self.start_time + VESTING_PERIOD)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct PeriodicVestingAccount {
|
||||
address: Addr,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
coin: Coin,
|
||||
}
|
||||
|
||||
impl PeriodicVestingAccount {
|
||||
pub fn new(
|
||||
address: Addr,
|
||||
coin: Coin,
|
||||
start_time: Timestamp,
|
||||
periods: Vec<VestingPeriod>,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Self, ContractError> {
|
||||
let amount = coin.amount;
|
||||
let account = PeriodicVestingAccount {
|
||||
address,
|
||||
start_time,
|
||||
periods,
|
||||
coin,
|
||||
};
|
||||
set_account(storage, account.clone())?;
|
||||
set_account_balance(storage, &account.address, amount)?;
|
||||
Ok(account)
|
||||
}
|
||||
|
||||
pub fn address(&self) -> Addr {
|
||||
self.address.clone()
|
||||
}
|
||||
|
||||
pub fn tokens_per_period(&self) -> Result<u128, ContractError> {
|
||||
let amount = self.coin.amount.u128();
|
||||
if amount < NUM_VESTING_PERIODS as u128 {
|
||||
Err(ContractError::ImprobableVestingAmount(amount))
|
||||
} else {
|
||||
// Remainder tokens will be lumped into the last period.
|
||||
Ok(amount / NUM_VESTING_PERIODS as u128)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_vesting_period(&self, block_time: Timestamp) -> usize {
|
||||
// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
|
||||
// In case vesting is over it will always return NUM_VESTING_PERIODS.
|
||||
let period = match self
|
||||
.periods
|
||||
.iter()
|
||||
.map(|period| period.start_time)
|
||||
.collect::<Vec<u64>>()
|
||||
.binary_search(&block_time.seconds())
|
||||
{
|
||||
Ok(u) => u,
|
||||
Err(u) => u,
|
||||
};
|
||||
|
||||
if period > 0 {
|
||||
period - 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
fn get_delegated_with_op(
|
||||
&self,
|
||||
op: &dyn Fn(u64, u64) -> bool,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Coin {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
if period == 0 {
|
||||
return Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
};
|
||||
}
|
||||
|
||||
let end_time = if period >= NUM_VESTING_PERIODS as usize {
|
||||
u64::MAX
|
||||
} else {
|
||||
self.periods[period].end_time().seconds()
|
||||
};
|
||||
|
||||
if let Some(delegations) = get_account_delegations(storage, &self.address) {
|
||||
delegations
|
||||
.iter()
|
||||
.filter(|d| op(d.block_time.seconds(), end_time))
|
||||
.fold(
|
||||
Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
|mut acc, d| {
|
||||
acc.amount += d.amount;
|
||||
acc
|
||||
},
|
||||
)
|
||||
} else {
|
||||
Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_balance(&self, storage: &dyn Storage) -> Uint128 {
|
||||
get_account_balance(storage, &self.address).unwrap_or(Uint128::new(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl DelegationAccount for PeriodicVestingAccount {
|
||||
fn try_delegate_to_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
coin: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
if coin.amount < self.get_balance(storage) {
|
||||
let msg = MixnetExecuteMsg::DelegateToMixnodeOnBehalf {
|
||||
mix_identity: mix_identity.clone(),
|
||||
delegate: self.address.clone(),
|
||||
};
|
||||
let delegate_to_mixnode =
|
||||
wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![coin.clone()])?.into();
|
||||
let attributes = vec![attr("action", "delegate to mixnode on behalf")];
|
||||
self.track_delegation(env.block.time, mix_identity, coin, storage)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "delegate to mixnode on behalf")
|
||||
.add_message(delegate_to_mixnode))
|
||||
} else {
|
||||
Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.get_balance(storage).u128(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn try_undelegate_from_mixnode(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf {
|
||||
mix_identity,
|
||||
delegate: self.address.clone(),
|
||||
};
|
||||
let undelegate_from_mixnode = wasm_execute(
|
||||
DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
&msg,
|
||||
vec![Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
}],
|
||||
)?;
|
||||
|
||||
let attributes = vec![attr("action", "undelegate to mixnode on behalf")];
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "undelegate to mixnode on behalf")
|
||||
.add_message(undelegate_from_mixnode))
|
||||
}
|
||||
|
||||
fn track_delegation(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
mix_identity: IdentityKey,
|
||||
delegation: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let mut delegations =
|
||||
if let Some(delegations) = get_account_delegations(storage, &self.address) {
|
||||
delegations
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
delegations.push(DelegationData {
|
||||
mix_identity,
|
||||
amount: delegation.amount,
|
||||
block_time,
|
||||
});
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
// We've checked that delegation < balance in the caller function
|
||||
Uint128::new(balance.u128() - delegation.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_delegations(storage, &self.address, delegations)?;
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn track_undelegation(
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
amount: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
// This has to exist in storage at this point.
|
||||
let delegations = get_account_delegations(storage, &self.address)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|d| d.mix_identity != mix_identity)
|
||||
.collect();
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
Uint128::new(balance.u128() + amount.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
|
||||
Ok(set_account_delegations(
|
||||
storage,
|
||||
&self.address,
|
||||
delegations,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl BondingAccount for PeriodicVestingAccount {
|
||||
fn try_bond_mixnode(
|
||||
&self,
|
||||
mix_node: MixNode,
|
||||
bond: Coin,
|
||||
env: &Env,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<Response, ContractError> {
|
||||
if bond.amount < self.get_balance(storage) {
|
||||
let msg = MixnetExecuteMsg::BondMixnodeOnBehalf {
|
||||
mix_node,
|
||||
owner: self.address.clone(),
|
||||
};
|
||||
let bond_mixnode =
|
||||
wasm_execute(DEFAULT_MIXNET_CONTRACT_ADDRESS, &msg, vec![bond.clone()])?;
|
||||
let attributes = vec![attr("action", "bond mixnode on behalf")];
|
||||
self.track_bond(env.block.time, bond, storage)?;
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "bond mixnode on behalf")
|
||||
.add_message(bond_mixnode))
|
||||
} else {
|
||||
Err(ContractError::InsufficientBalance(
|
||||
self.address.as_str().to_string(),
|
||||
self.get_balance(storage).u128(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn track_bond(
|
||||
&self,
|
||||
block_time: Timestamp,
|
||||
bond: Coin,
|
||||
storage: &mut dyn Storage,
|
||||
) -> Result<(), ContractError> {
|
||||
let bond = if let Some(_bond) = get_account_bond(storage, &self.address) {
|
||||
return Err(ContractError::AlreadyBonded(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
} else {
|
||||
BondData {
|
||||
block_time,
|
||||
amount: bond.amount,
|
||||
}
|
||||
};
|
||||
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
// We've checked that bond.amount < balance in the caller function
|
||||
Uint128::new(balance.u128() - bond.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
Ok(set_account_bond(storage, &self.address, bond)?)
|
||||
}
|
||||
|
||||
fn try_unbond_mixnode(&self) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::UnbondMixnodeOnBehalf {
|
||||
owner: self.address.clone(),
|
||||
};
|
||||
let unbond_mixnode = wasm_execute(
|
||||
DEFAULT_MIXNET_CONTRACT_ADDRESS,
|
||||
&msg,
|
||||
vec![Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
}],
|
||||
)?;
|
||||
|
||||
let attributes = vec![attr("action", "unbond mixnode on behalf")];
|
||||
|
||||
Ok(Response::new()
|
||||
.add_attribute("action", "unbond mixnode on behalf")
|
||||
.add_message(unbond_mixnode))
|
||||
}
|
||||
|
||||
fn track_unbond(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
|
||||
let new_balance = if let Some(balance) = get_account_balance(storage, &self.address) {
|
||||
Uint128::new(balance.u128() + amount.amount.u128())
|
||||
} else {
|
||||
return Err(ContractError::NoBalanceForAddress(
|
||||
self.address.as_str().to_string(),
|
||||
));
|
||||
};
|
||||
|
||||
set_account_balance(storage, &self.address, new_balance)?;
|
||||
drop_account_bond(storage, &self.address)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl VestingAccount for PeriodicVestingAccount {
|
||||
fn locked_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
// Returns 0 in case of underflow.
|
||||
Ok(Coin {
|
||||
amount: Uint128::new(
|
||||
self.get_vesting_coins(block_time, env)?
|
||||
.amount
|
||||
.u128()
|
||||
.saturating_sub(
|
||||
self.get_delegated_vesting(block_time, env, storage)
|
||||
.amount
|
||||
.u128(),
|
||||
),
|
||||
),
|
||||
denom: DENOM.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn spendable_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
Ok(Coin {
|
||||
amount: Uint128::new(
|
||||
self.get_balance(storage)
|
||||
.u128()
|
||||
.saturating_sub(self.locked_coins(block_time, env, storage)?.amount.u128()),
|
||||
),
|
||||
denom: DENOM.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_vested_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
) -> Result<Coin, ContractError> {
|
||||
let block_time = block_time.unwrap_or(env.block.time);
|
||||
let period = self.get_current_vesting_period(block_time);
|
||||
|
||||
let amount = match period {
|
||||
// We're in the first period, or the vesting has not started yet.
|
||||
0 => Coin {
|
||||
amount: Uint128::new(0),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
// We always have 8 vesting periods, so periods 1-7 are special
|
||||
1..=7 => Coin {
|
||||
amount: Uint128::new(self.tokens_per_period()? * period as u128),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
_ => Coin {
|
||||
amount: self.coin.amount,
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
};
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
fn get_vesting_coins(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
) -> Result<Coin, ContractError> {
|
||||
Ok(Coin {
|
||||
amount: Uint128::new(
|
||||
self.get_original_vesting().amount.u128()
|
||||
- self.get_vested_coins(block_time, env)?.amount.u128(),
|
||||
),
|
||||
denom: DENOM.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn get_start_time(&self) -> Timestamp {
|
||||
self.start_time
|
||||
}
|
||||
|
||||
fn get_end_time(&self) -> Timestamp {
|
||||
self.periods[(NUM_VESTING_PERIODS - 1) as usize].end_time()
|
||||
}
|
||||
|
||||
fn get_original_vesting(&self) -> Coin {
|
||||
self.coin.clone()
|
||||
}
|
||||
|
||||
fn get_delegated_free(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Coin {
|
||||
self.get_delegated_with_op(<, block_time, env, storage)
|
||||
}
|
||||
|
||||
fn get_delegated_vesting(
|
||||
&self,
|
||||
block_time: Option<Timestamp>,
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Coin {
|
||||
self.get_delegated_with_op(&ge, block_time, env, storage)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct DelegationData {
|
||||
mix_identity: IdentityKey,
|
||||
amount: Uint128,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
pub struct BondData {
|
||||
amount: Uint128,
|
||||
block_time: Timestamp,
|
||||
}
|
||||
|
||||
fn lt(x: u64, y: u64) -> bool {
|
||||
x < y
|
||||
}
|
||||
|
||||
fn ge(x: u64, y: u64) -> bool {
|
||||
x >= y
|
||||
}
|
||||
|
||||
pub fn populate_vesting_periods(start_time: u64, n: usize) -> Vec<VestingPeriod> {
|
||||
let mut periods = Vec::with_capacity(n as usize);
|
||||
for i in 0..n {
|
||||
let period = VestingPeriod {
|
||||
start_time: start_time + i as u64 * VESTING_PERIOD,
|
||||
};
|
||||
periods.push(period);
|
||||
}
|
||||
periods
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::contract::{NUM_VESTING_PERIODS, VESTING_PERIOD};
|
||||
use crate::storage::{get_account, get_account_balance, get_account_delegations};
|
||||
use crate::support::tests::helpers::{init_contract, vesting_account_fixture};
|
||||
use crate::vesting::{DelegationData, VestingAccount};
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info};
|
||||
use cosmwasm_std::{Addr, Coin, Timestamp, Uint128};
|
||||
|
||||
use super::DelegationAccount;
|
||||
|
||||
#[test]
|
||||
fn test_account_creation() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
let created_account = get_account(&deps.storage, &account.address);
|
||||
let created_account_test = get_account(&deps.storage, &Addr::unchecked("fixture"));
|
||||
assert_eq!(Some(&account), created_account.as_ref());
|
||||
assert_eq!(Some(&account), created_account_test.as_ref());
|
||||
assert_eq!(
|
||||
get_account_balance(&deps.storage, &account.address),
|
||||
Some(Uint128::new(1_000_000_000_000))
|
||||
);
|
||||
assert_eq!(
|
||||
account.get_balance(&deps.storage),
|
||||
Uint128::new(1_000_000_000_000)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_period_logic() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
|
||||
assert_eq!(account.periods.len(), NUM_VESTING_PERIODS as usize);
|
||||
assert_eq!(account.periods.len(), 8);
|
||||
|
||||
let current_period = account.get_current_vesting_period(Timestamp::from_seconds(0));
|
||||
assert_eq!(0, current_period);
|
||||
|
||||
let block_time = Timestamp::from_seconds(account.start_time.seconds() + VESTING_PERIOD + 1);
|
||||
let current_period = account.get_current_vesting_period(block_time);
|
||||
assert_eq!(current_period, 1);
|
||||
let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap();
|
||||
assert_eq!(
|
||||
vested_coins.amount,
|
||||
Uint128::new(account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128)
|
||||
);
|
||||
assert_eq!(
|
||||
vesting_coins.amount,
|
||||
Uint128::new(
|
||||
account.get_original_vesting().amount.u128()
|
||||
- account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128
|
||||
)
|
||||
);
|
||||
|
||||
let block_time =
|
||||
Timestamp::from_seconds(account.start_time.seconds() + 5 * VESTING_PERIOD + 1);
|
||||
let current_period = account.get_current_vesting_period(block_time);
|
||||
assert_eq!(current_period, 5);
|
||||
let vested_coins = account.get_vested_coins(Some(block_time), &env).unwrap();
|
||||
let vesting_coins = account.get_vesting_coins(Some(block_time), &env).unwrap();
|
||||
assert_eq!(
|
||||
vested_coins.amount,
|
||||
Uint128::new(5 * account.get_original_vesting().amount.u128() / NUM_VESTING_PERIODS as u128)
|
||||
);
|
||||
assert_eq!(
|
||||
vesting_coins.amount,
|
||||
Uint128::new(
|
||||
account.get_original_vesting().amount.u128()
|
||||
- 5 * account.get_original_vesting().amount.u128()
|
||||
/ NUM_VESTING_PERIODS as u128
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delegations() {
|
||||
let mut deps = init_contract();
|
||||
let env = mock_env();
|
||||
|
||||
let account = vesting_account_fixture(&mut deps.storage, &env);
|
||||
|
||||
// Try delegating too much
|
||||
let err = account.try_delegate_to_mixnode(
|
||||
"alice".to_string(),
|
||||
Coin {
|
||||
amount: Uint128::new(1_000_000_000_001),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(err.is_err());
|
||||
|
||||
let ok = account.try_delegate_to_mixnode(
|
||||
"alice".to_string(),
|
||||
Coin {
|
||||
amount: Uint128::new(100_000_000_000),
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
&env,
|
||||
&mut deps.storage,
|
||||
);
|
||||
assert!(ok.is_ok());
|
||||
|
||||
let delegations = get_account_delegations(&mut deps.storage, &account.address).unwrap();
|
||||
assert_eq!(
|
||||
DelegationData {
|
||||
mix_identity: "alice".to_string(),
|
||||
block_time: env.block.time,
|
||||
amount: Uint128::new(100_000_000_000)
|
||||
},
|
||||
delegations[0]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user