Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Ensure validators are registered before staking #74

Merged
merged 2 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions contracts/provider/external-staking/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ use cw2::set_contract_version;
use cw_storage_plus::{Bounder, Item, Map};
use cw_utils::must_pay;
use mesh_apis::cross_staking_api::{self, CrossStakingApi};
use mesh_apis::ibc::AddValidator;
use mesh_apis::local_staking_api::MaxSlashResponse;
use mesh_apis::vault_api::VaultApiHelper;
use mesh_sync::Lockable;

use crate::crdt::CrdtState;
// IBC sending is disabled in tests...
#[cfg(not(test))]
use crate::ibc::{packet_timeout, IBC_CHANNEL};
Expand All @@ -22,7 +24,6 @@ use sylvia::contract;
use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};

use crate::error::ContractError;
use crate::ibc::VAL_CRDT;
use crate::msg::{
AllTxsResponse, AuthorizedEndpointResponse, ConfigResponse, IbcChannelResponse,
ListRemoteValidatorsResponse, PendingRewards, ReceiveVirtualStake, StakeInfo, StakesResponse,
Expand Down Expand Up @@ -53,6 +54,8 @@ pub struct ExternalStakingContract<'a> {
/// Pending txs information
pub tx_count: Item<'a, u64>,
pub pending_txs: Map<'a, u64, Tx>,
/// Valset CRDT
pub val_set: CrdtState<'a>,
}

#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
Expand All @@ -67,6 +70,7 @@ impl ExternalStakingContract<'_> {
distribution: Map::new("distribution"),
pending_txs: Map::new("pending_txs"),
tx_count: Item::new("tx_count"),
val_set: CrdtState::new(),
}
}

Expand Down Expand Up @@ -242,6 +246,38 @@ impl ExternalStakingContract<'_> {
}
}

/// Rollbacks a pending stake.
/// Method used for tests only.
#[msg(exec)]
fn test_set_active_validator(
&self,
ctx: ExecCtx,
validator: AddValidator,
) -> Result<Response, ContractError> {
#[cfg(test)]
{
let AddValidator {
valoper,
pub_key,
start_height,
start_time,
} = validator;
let update = crate::crdt::ValUpdate {
pub_key,
start_height,
start_time,
};
self.val_set
.add_validator(ctx.deps.storage, &valoper, update)?;
Ok(Response::new())
}
#[cfg(not(test))]
{
let _ = (ctx, validator);
Err(ContractError::Unauthorized {})
}
}

/// Schedules tokens for release, adding them to the pending unbonds. After `unbonding_period`
/// passes, funds are ready to be released with `withdraw_unbonded` call by the user
#[msg(exec)]
Expand Down Expand Up @@ -629,7 +665,8 @@ impl ExternalStakingContract<'_> {
) -> Result<ListRemoteValidatorsResponse, ContractError> {
let limit = limit.unwrap_or(100) as usize;
let validators =
VAL_CRDT.list_active_validators(ctx.deps.storage, start_after.as_deref(), limit)?;
self.val_set
.list_active_validators(ctx.deps.storage, start_after.as_deref(), limit)?;
Ok(ListRemoteValidatorsResponse { validators })
}

Expand Down Expand Up @@ -796,6 +833,7 @@ pub mod cross_staking {
let config = self.config.load(ctx.deps.storage)?;
ensure_eq!(ctx.info.sender, config.vault.0, ContractError::Unauthorized);

// sending proper denom
ensure_eq!(
amount.denom,
config.denom,
Expand All @@ -804,7 +842,14 @@ pub mod cross_staking {

let owner = ctx.deps.api.addr_validate(&owner)?;

// parse and validate message
let msg: ReceiveVirtualStake = from_binary(&msg)?;
if !self
.val_set
.is_active_validator(ctx.deps.storage, &msg.validator)?
{
return Err(ContractError::ValidatorNotActive(msg.validator));
}
let mut stake_lock = self
.stakes
.may_load(ctx.deps.storage, (&owner, &msg.validator))?
Expand Down
5 changes: 4 additions & 1 deletion contracts/provider/external-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ pub enum ContractError {
#[error("Not enough tokens released, up to {0} can be claimed")]
NotEnoughRelease(Uint128),

#[error("Validator for user missmatch, {0} expected")]
#[error("Validator for user mismatch, {0} expected")]
InvalidValidator(String),

#[error("Cannot stake to {0}, not listed as an active validator on consumer")]
ValidatorNotActive(String),

#[error("Contract already has an open IBC channel")]
IbcChannelAlreadyOpen,

Expand Down
21 changes: 9 additions & 12 deletions contracts/provider/external-staking/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ use mesh_apis::ibc::{
ConsumerPacket, ProtocolVersion, ProviderPacket, RemoveValidatorsAck,
};

use crate::{
contract::ExternalStakingContract,
crdt::{CrdtState, ValUpdate},
error::ContractError,
msg::AuthorizedEndpoint,
};
use crate::contract::ExternalStakingContract;
use crate::crdt::ValUpdate;
use crate::error::ContractError;
use crate::msg::AuthorizedEndpoint;

/// This is the maximum version of the Mesh Security protocol that we support
const SUPPORTED_IBC_PROTOCOL_VERSION: &str = "1.0.0";
Expand All @@ -26,12 +24,8 @@ const MIN_IBC_PROTOCOL_VERSION: &str = "1.0.0";

// IBC specific state
pub const AUTH_ENDPOINT: Item<AuthorizedEndpoint> = Item::new("auth_endpoint");

// TODO: expected endpoint
pub const IBC_CHANNEL: Item<IbcChannel> = Item::new("ibc_channel");

pub const VAL_CRDT: CrdtState = CrdtState::new();

// If we don't hear anything within 10 minutes, let's abort, for better UX
// This is long enough to allow some clock drift between chains
const DEFAULT_TIMEOUT: u64 = 10 * 60;
Expand Down Expand Up @@ -127,6 +121,7 @@ pub fn ibc_packet_receive(
) -> Result<IbcReceiveResponse, ContractError> {
// There is only one channel, so we don't need to switch.
// We also don't care about packet sequence as this is fully commutative.
let contract = ExternalStakingContract::new();
let packet: ConsumerPacket = from_slice(&msg.packet.data)?;
let ack = match packet {
ConsumerPacket::AddValidators(to_add) => {
Expand All @@ -142,13 +137,15 @@ pub fn ibc_packet_receive(
start_height,
start_time,
};
VAL_CRDT.add_validator(deps.storage, &valoper, update)?;
contract
.val_set
.add_validator(deps.storage, &valoper, update)?;
}
ack_success(&AddValidatorsAck {})?
}
ConsumerPacket::RemoveValidators(to_remove) => {
for valoper in to_remove {
VAL_CRDT.remove_validator(deps.storage, &valoper)?;
contract.val_set.remove_validator(deps.storage, &valoper)?;
}
ack_success(&RemoveValidatorsAck {})?
}
Expand Down
45 changes: 45 additions & 0 deletions contracts/provider/external-staking/src/multitest.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::Result as AnyResult;

use cosmwasm_std::{coin, coins, to_binary, Addr, Decimal};
use mesh_apis::ibc::AddValidator;
use mesh_native_staking::contract::multitest_utils::CodeId as NativeStakingCodeId;
use mesh_native_staking::contract::InstantiateMsg as NativeStakingInstantiateMsg;
use mesh_native_staking_proxy::contract::multitest_utils::CodeId as NativeStakingProxyCodeId;
Expand Down Expand Up @@ -102,6 +103,15 @@ fn staking() {

let (vault, contract) = setup(&app, owner, 100).unwrap();

// set these validators to be active
for val in validators {
let activate = AddValidator::mock(val);
contract
.test_set_active_validator(activate)
.call("test")
.unwrap();
}

// Bond tokens
vault
.bond()
Expand All @@ -115,6 +125,23 @@ fn staking() {
.call(users[1])
.unwrap();

/*
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@hashedone @jawoznia

Another non-blocking issue in Sylvia.

User calls contract A. contract A returns message to execute contract B. contract B calls fail (no reply registered). A and B both have different ContractError registered.

The caller doesn't ever get the result, but it fails inside ExecProxy when trying to downcast the result to vault::ContractError (contract A). Not sure if the error is external_staking::ContractError (contract B), or StdError, but I want to assert this fails without panic-ing the test

// Fail to stake on non-registered validator
let msg = to_binary(&ReceiveVirtualStake {
validator: "unknown".to_string(),
})
.unwrap();
println!("START");
// FIXME: Sylvia panics here, with this line in ExecProxy::call
// .map_err(|err| err.downcast().unwrap())
// Note that the error didn't happen in vault, but in a SubMsg, so this should be some StdError not ContractError...
let res = vault
.stake_remote(contract.contract_addr.to_string(), coin(100, OSMO), msg)
.call(users[0]);
println!("GOT: {:?}", res);
assert!(res.is_err());
*/

// Perform couple stakes
// users[0] stakes 200 on validators[0] in 2 batches
// users[0] stakes 100 on validators[1]
Expand Down Expand Up @@ -311,6 +338,15 @@ fn unstaking() {

let (vault, contract) = setup(&app, owner, 100).unwrap();

// set these validators to be active
for val in validators {
let activate = AddValidator::mock(val);
contract
.test_set_active_validator(activate)
.call("test")
.unwrap();
}

// Bond and stake tokens
//
// users[0] stakes 200 on validators[0]
Expand Down Expand Up @@ -626,6 +662,15 @@ fn distribution() {

let (vault, contract) = setup(&app, owner, 100).unwrap();

// set these validators to be active
for val in validators {
let activate = AddValidator::mock(val);
contract
.test_set_active_validator(activate)
.call("test")
.unwrap();
}

// Bond and stake tokens
//
// users[0] stakes 200 on validators[0]
Expand Down
11 changes: 11 additions & 0 deletions packages/apis/src/ibc/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ pub struct AddValidator {
pub start_time: u64,
}

impl AddValidator {
pub fn mock(valoper: &str) -> Self {
Self {
valoper: valoper.to_string(),
pub_key: "mock-pubkey".to_string(),
start_height: 12345,
start_time: 1687357499,
}
}
}

/// Ack sent for ConsumerPacket::AddValidators
#[cw_serde]
pub struct AddValidatorsAck {}
Expand Down