a2324f98f8
* Base58 representation of ed25519 signature * Helper for verifying ed25519 signature on sender address * Signature verification for gateway bonding * Signature verification for mixnode bonding * Added owner signatures for bonding in vesting contract * Fixed choosing mixnode layer test * Added owner signature fields to nymd client for bonding * 'Updated' tauri wallet with new bond requirements * Mixnode sign command with extra address validation * Sign command for the gateway * Signing own gateway's address derived with known mnemonic in not(coconut) case * Fixed imports post-merge
404 lines
13 KiB
Rust
404 lines
13 KiB
Rust
use crate::errors::ContractError;
|
|
use crate::messages::{ExecuteMsg, InitMsg, QueryMsg};
|
|
use crate::storage::account_from_address;
|
|
use crate::traits::{
|
|
DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount,
|
|
};
|
|
use crate::vesting::{populate_vesting_periods, Account};
|
|
use config::defaults::{DEFAULT_MIXNET_CONTRACT_ADDRESS, DENOM};
|
|
use cosmwasm_std::{
|
|
entry_point, to_binary, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse,
|
|
Response, Timestamp, Uint128,
|
|
};
|
|
use mixnet_contract::{Gateway, IdentityKey, MixNode};
|
|
|
|
// We're using a 24 month vesting period with 3 months sub-periods.
|
|
// There are 8 three month periods in two years
|
|
// and duration of a single period is 30 days.
|
|
pub const NUM_VESTING_PERIODS: usize = 8;
|
|
pub const VESTING_PERIOD: u64 = 3 * 30 * 86400;
|
|
// Address of the account set to be contract admin
|
|
pub const ADMIN_ADDRESS: &str = "admin";
|
|
|
|
#[entry_point]
|
|
pub fn instantiate(
|
|
_deps: DepsMut,
|
|
_env: Env,
|
|
_info: MessageInfo,
|
|
_msg: InitMsg,
|
|
) -> Result<Response, ContractError> {
|
|
Ok(Response::default())
|
|
}
|
|
|
|
#[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::CreateAccount {
|
|
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, info, deps),
|
|
ExecuteMsg::BondMixnode {
|
|
mix_node,
|
|
owner_signature,
|
|
} => try_bond_mixnode(mix_node, owner_signature, info, env, deps),
|
|
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
|
|
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
|
|
try_track_unbond_mixnode(&owner, amount, info, deps)
|
|
}
|
|
ExecuteMsg::BondGateway {
|
|
gateway,
|
|
owner_signature,
|
|
} => try_bond_gateway(gateway, owner_signature, info, env, deps),
|
|
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
|
|
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
|
|
try_track_unbond_gateway(&owner, amount, info, deps)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn try_bond_gateway(
|
|
gateway: Gateway,
|
|
owner_signature: String,
|
|
info: MessageInfo,
|
|
env: Env,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
let pledge = validate_funds(&info.funds)?;
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_bond_gateway(gateway, owner_signature, pledge, &env, deps.storage)
|
|
}
|
|
|
|
pub fn try_unbond_gateway(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_unbond_gateway(deps.storage)
|
|
}
|
|
|
|
pub fn try_track_unbond_gateway(
|
|
owner: &str,
|
|
amount: Coin,
|
|
info: MessageInfo,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
|
|
return Err(ContractError::NotMixnetContract(info.sender));
|
|
}
|
|
let account = account_from_address(owner, deps.storage, deps.api)?;
|
|
account.try_track_unbond_gateway(amount, deps.storage)?;
|
|
Ok(Response::default())
|
|
}
|
|
|
|
pub fn try_bond_mixnode(
|
|
mix_node: MixNode,
|
|
owner_signature: String,
|
|
info: MessageInfo,
|
|
env: Env,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
let pledge = validate_funds(&info.funds)?;
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_bond_mixnode(mix_node, owner_signature, pledge, &env, deps.storage)
|
|
}
|
|
|
|
pub fn try_unbond_mixnode(info: MessageInfo, deps: DepsMut) -> Result<Response, ContractError> {
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_unbond_mixnode(deps.storage)
|
|
}
|
|
|
|
pub fn try_track_unbond_mixnode(
|
|
owner: &str,
|
|
amount: Coin,
|
|
info: MessageInfo,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
|
|
return Err(ContractError::NotMixnetContract(info.sender));
|
|
}
|
|
let account = account_from_address(owner, deps.storage, deps.api)?;
|
|
account.try_track_unbond_mixnode(amount, deps.storage)?;
|
|
Ok(Response::default())
|
|
}
|
|
|
|
pub fn try_withdraw_vested_coins(
|
|
amount: Coin,
|
|
env: Env,
|
|
info: MessageInfo,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
let address = info.sender.clone();
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
let spendable_coins = account.spendable_coins(None, &env, deps.storage)?;
|
|
if amount.amount <= spendable_coins.amount {
|
|
let new_balance = account
|
|
.load_balance(deps.storage)?
|
|
.u128()
|
|
.saturating_sub(amount.amount.u128());
|
|
account.save_balance(Uint128::new(new_balance), deps.storage)?;
|
|
|
|
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(),
|
|
))
|
|
}
|
|
}
|
|
|
|
fn try_track_undelegation(
|
|
address: &str,
|
|
mix_identity: IdentityKey,
|
|
amount: Coin,
|
|
info: MessageInfo,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
if info.sender != DEFAULT_MIXNET_CONTRACT_ADDRESS {
|
|
return Err(ContractError::NotMixnetContract(info.sender));
|
|
}
|
|
let account = account_from_address(address, deps.storage, deps.api)?;
|
|
account.track_undelegation(mix_identity, amount, deps.storage)?;
|
|
Ok(Response::default())
|
|
}
|
|
|
|
fn try_delegate_to_mixnode(
|
|
mix_identity: IdentityKey,
|
|
info: MessageInfo,
|
|
env: Env,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
let amount = validate_funds(&info.funds)?;
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_delegate_to_mixnode(mix_identity, amount, &env, deps.storage)
|
|
}
|
|
|
|
fn try_undelegate_from_mixnode(
|
|
mix_identity: IdentityKey,
|
|
info: MessageInfo,
|
|
deps: DepsMut,
|
|
) -> Result<Response, ContractError> {
|
|
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
|
|
account.try_undelegate_from_mixnode(mix_identity, deps.storage)
|
|
}
|
|
|
|
fn try_create_periodic_vesting_account(
|
|
address: &str,
|
|
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);
|
|
Account::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: &str,
|
|
block_time: Option<Timestamp>,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.locked_coins(block_time, &env, deps.storage)
|
|
}
|
|
|
|
pub fn try_get_spendable_coins(
|
|
vesting_account_address: &str,
|
|
block_time: Option<Timestamp>,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.spendable_coins(block_time, &env, deps.storage)
|
|
}
|
|
|
|
pub fn try_get_vested_coins(
|
|
vesting_account_address: &str,
|
|
block_time: Option<Timestamp>,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.get_vested_coins(block_time, &env)
|
|
}
|
|
|
|
pub fn try_get_vesting_coins(
|
|
vesting_account_address: &str,
|
|
block_time: Option<Timestamp>,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.get_vesting_coins(block_time, &env)
|
|
}
|
|
|
|
pub fn try_get_start_time(
|
|
vesting_account_address: &str,
|
|
deps: Deps,
|
|
) -> Result<Timestamp, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
Ok(account.get_start_time())
|
|
}
|
|
|
|
pub fn try_get_end_time(
|
|
vesting_account_address: &str,
|
|
deps: Deps,
|
|
) -> Result<Timestamp, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
Ok(account.get_end_time())
|
|
}
|
|
|
|
pub fn try_get_original_vesting(
|
|
vesting_account_address: &str,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
Ok(account.get_original_vesting())
|
|
}
|
|
|
|
pub fn try_get_delegated_free(
|
|
block_time: Option<Timestamp>,
|
|
vesting_account_address: &str,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.get_delegated_free(block_time, &env, deps.storage)
|
|
}
|
|
|
|
pub fn try_get_delegated_vesting(
|
|
block_time: Option<Timestamp>,
|
|
vesting_account_address: &str,
|
|
env: Env,
|
|
deps: Deps,
|
|
) -> Result<Coin, ContractError> {
|
|
let account = account_from_address(vesting_account_address, deps.storage, deps.api)?;
|
|
account.get_delegated_vesting(block_time, &env, deps.storage)
|
|
}
|
|
|
|
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())
|
|
}
|