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

F/slashing #126

Merged
merged 50 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
94b97de
Fix stub impl messages in passing
maurolacy Sep 14, 2023
fef8045
Add cross slashing spec
maurolacy Sep 18, 2023
7b55125
Cross slashing event routing
maurolacy Sep 18, 2023
1a82b8c
Add IBC handler on the Provider
maurolacy Sep 18, 2023
55ab0a3
Add routing of users to the vault for collateral slashing
maurolacy Sep 18, 2023
f7e90a2
Add stakes reverse map
maurolacy Sep 19, 2023
8183839
Use reverse map to get list of users
maurolacy Sep 19, 2023
7997166
Tombstone validator check, tombstone validator
maurolacy Sep 19, 2023
2d40932
Initial slashing impl
maurolacy Sep 20, 2023
9ed6760
Propagate slash: adjust liens
maurolacy Sep 21, 2023
62fe8d2
Propagate slash: recompute max lien for the user
maurolacy Sep 21, 2023
25a01fc
Propagate slash: adjust user's total slashable amount
maurolacy Sep 21, 2023
d8da794
Fix: implement slashing vs native unbonding vs slashing propagation
maurolacy Sep 24, 2023
016e365
Add account details query
maurolacy Sep 25, 2023
dc1cc53
Add simple slashing test (scenario 1)
maurolacy Sep 25, 2023
f1faa98
Add handle slashing test method
maurolacy Sep 25, 2023
7b589fa
Fix: user vs validator
maurolacy Sep 25, 2023
b4a142d
Fix test: route vault process slashing msg
maurolacy Sep 25, 2023
0ecd3b4
Call slashing through external-taking test impl
maurolacy Sep 25, 2023
3ccd9b8
Rename to cross slash for simplicity
maurolacy Sep 25, 2023
791a365
Fix: reduce user's lien
maurolacy Sep 25, 2023
5fe6496
Add liens after slashing check
maurolacy Sep 25, 2023
94d1662
Add scenario doc reference
maurolacy Sep 25, 2023
28f24e1
cargo fmt
maurolacy Sep 25, 2023
f33627e
Add no free collateral slashing test (scenario 2)
maurolacy Sep 25, 2023
48c6d62
Fix: Save slashed lien before slashing propagation
maurolacy Sep 25, 2023
9fd7c9a
Adjust scenario 2 slashing test
maurolacy Sep 25, 2023
22a6f3c
Add some free collateral slashing test (scenario 3)
maurolacy Sep 26, 2023
21fe609
Refactor slashing tests to use helpers
maurolacy Sep 26, 2023
ea9e087
Add slashing propagation slashing test (scenario 4)
maurolacy Sep 26, 2023
0c48ccc
Make slashing percentage configurable
maurolacy Sep 26, 2023
de61454
Add (failing) (modified) scenario 5
maurolacy Sep 26, 2023
aa7e3e7
Fix: don't always slash native stake
maurolacy Sep 26, 2023
6785cf1
Fix: (failing) scenario 5 native stake after slashing
maurolacy Sep 26, 2023
4e5912b
Add slashing propagation for total slashable invariance breaking
maurolacy Sep 27, 2023
14f6ae8
Fix: Add rounding down
maurolacy Sep 27, 2023
c94b627
Add TODO / remove FIXME
maurolacy Sep 27, 2023
efed295
Add no native staking slashing test
maurolacy Sep 27, 2023
caf5b11
Add ensure_authorized helper
maurolacy Sep 28, 2023
c345ff5
Fix rebase error
maurolacy Sep 28, 2023
093be5d
Revert "Add cross slashing spec"
maurolacy Sep 29, 2023
fc5e812
Revert "Cross slashing event routing"
maurolacy Sep 29, 2023
dea63cc
Revert "Add IBC handler on the Provider"
maurolacy Sep 29, 2023
95b01c1
Fix typo
maurolacy Sep 29, 2023
81f6b58
Add TODO
maurolacy Sep 29, 2023
4e1dc25
Handle slashing as part of validator tombstoning
maurolacy Sep 29, 2023
d0a2b33
Adapt external-staking test methods
maurolacy Sep 29, 2023
15e8f44
Adapt vault slashing tests
maurolacy Sep 29, 2023
d468260
Fix: slash messages routing
maurolacy Sep 29, 2023
744a17d
Add remove validator at height check
maurolacy Oct 3, 2023
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
2 changes: 1 addition & 1 deletion contracts/consumer/converter/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ impl ConverterApi for ConverterContract<'_> {

/// Valset updates.
///
/// Sent validator set additions (entering the active validator set) to the external staking
/// Send validator set additions (entering the active validator set) to the external staking
/// contract on the Consumer via IBC.
#[msg(exec)]
fn valset_update(
Expand Down
13 changes: 11 additions & 2 deletions contracts/consumer/converter/src/ibc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use cw_storage_plus::Item;

use mesh_apis::ibc::{
ack_success, validate_channel_order, AckWrapper, AddValidator, ConsumerPacket, ProtocolVersion,
ProviderPacket, StakeAck, TransferRewardsAck, UnstakeAck, PROTOCOL_NAME,
ProviderPacket, RemoveValidator, StakeAck, TransferRewardsAck, UnstakeAck, PROTOCOL_NAME,
};
use sylvia::types::ExecCtx;

Expand Down Expand Up @@ -151,7 +151,16 @@ pub(crate) fn tombstone_validators_msg(
channel: &IbcChannel,
validators: &[String],
) -> Result<IbcMsg, ContractError> {
let packet = ConsumerPacket::RemoveValidators(Vec::from(validators));
let packet = ConsumerPacket::RemoveValidators(
validators
.iter()
.map(|v| RemoveValidator {
valoper: v.to_string(),
end_height: env.block.height,
end_time: env.block.time.seconds(),
})
.collect(),
);
let msg = IbcMsg::SendPacket {
channel_id: channel.endpoint.channel_id.clone(),
data: to_binary(&packet)?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ pub enum ContractError {
WrongDenom(String),
}

/// This is a stub implementation of the local staking proxy contract, for test purposes only.
/// When proper local staking proxy contract is available, this should be replaced in multitests
/// This is a stub implementation of the virtual staking contract, for test purposes only.
/// When proper virtual staking contract is available, this should be replaced in multitests
pub struct VirtualStakingMock<'a> {
config: Item<'a, Config>,
stake: Map<'a, &'a str, Uint128>,
Expand Down
1 change: 1 addition & 0 deletions contracts/consumer/virtual-staking/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ impl VirtualStakingContract<'_> {
let _ = (removals, updated, jailed, unjailed);

// Send additions and tombstones to the Converter. Removals are non-permanent and ignored
// TODO: Send jailed even when they are non-permanent, for slashing
let cfg = self.config.load(deps.storage)?;
let msg = converter_api::ExecMsg::ValsetUpdate {
additions: additions.to_vec(),
Expand Down
96 changes: 84 additions & 12 deletions contracts/provider/external-staking/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use cosmwasm_std::{
coin, ensure, ensure_eq, to_binary, Addr, Coin, Decimal, DepsMut, Env, Event, IbcMsg, Order,
coin, ensure, ensure_eq, to_binary, Coin, Decimal, DepsMut, Env, Event, IbcMsg, Order,
Response, StdResult, Storage, Uint128, Uint256, WasmMsg,
};
use cw2::set_contract_version;
Expand All @@ -12,7 +12,7 @@ use sylvia::types::{ExecCtx, InstantiateCtx, QueryCtx};

use mesh_apis::cross_staking_api::{self};
use mesh_apis::ibc::ProviderPacket;
use mesh_apis::vault_api::VaultApiHelper;
use mesh_apis::vault_api::{SlashInfo, VaultApiHelper};
use mesh_sync::Tx;

use crate::crdt::CrdtState;
Expand All @@ -23,6 +23,7 @@ use crate::msg::{
IbcChannelResponse, ListRemoteValidatorsResponse, PendingRewards, StakeInfo, StakesResponse,
TxResponse, ValidatorPendingRewards,
};
use crate::stakes::Stakes;
use crate::state::{Config, Distribution, Stake};

pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
Expand All @@ -41,7 +42,7 @@ fn clamp_page_limit(limit: Option<u32>) -> usize {
pub struct ExternalStakingContract<'a> {
pub config: Item<'a, Config>,
/// Stakes indexed by `(owner, validator)` pair
pub stakes: Map<'a, (&'a Addr, &'a str), Stake>,
pub stakes: Stakes<'a>,
/// Per-validator distribution information
pub distribution: Map<'a, &'a str, Distribution>,
/// Pending txs information
Expand All @@ -51,16 +52,22 @@ pub struct ExternalStakingContract<'a> {
pub val_set: CrdtState<'a>,
}

impl Default for ExternalStakingContract<'_> {
fn default() -> Self {
Self::new()
}
}

#[cfg_attr(not(feature = "library"), sylvia::entry_points)]
#[contract]
#[error(ContractError)]
#[messages(cross_staking_api as CrossStakingApi)]
#[messages(crate::test_methods as TestMethods)]
impl ExternalStakingContract<'_> {
pub const fn new() -> Self {
pub fn new() -> Self {
Self {
config: Item::new("config"),
stakes: Map::new("stakes"),
stakes: Stakes::new("stakes", "vals"),
distribution: Map::new("distribution"),
pending_txs: Map::new("pending_txs"),
tx_count: Item::new("tx_count"),
Expand Down Expand Up @@ -147,7 +154,10 @@ impl ExternalStakingContract<'_> {
};

// Load stake
let mut stake = self.stakes.load(deps.storage, (&tx_user, &tx_validator))?;
let mut stake = self
.stakes
.stake
.load(deps.storage, (&tx_user, &tx_validator))?;

// Load distribution
let mut distribution = self
Expand All @@ -166,6 +176,7 @@ impl ExternalStakingContract<'_> {

// Save stake
self.stakes
.stake
.save(deps.storage, (&tx_user, &tx_validator), &stake)?;

// Save distribution
Expand Down Expand Up @@ -208,13 +219,17 @@ impl ExternalStakingContract<'_> {
};

// Load stake
let mut stake = self.stakes.load(deps.storage, (&tx_user, &tx_validator))?;
let mut stake = self
.stakes
.stake
.load(deps.storage, (&tx_user, &tx_validator))?;

// Rollback add amount
stake.stake.rollback_add(tx_amount);

// Save stake
self.stakes
.stake
.save(deps.storage, (&tx_user, &tx_validator), &stake)?;

// Remove tx
Expand Down Expand Up @@ -248,6 +263,7 @@ impl ExternalStakingContract<'_> {

let mut stake = self
.stakes
.stake
.may_load(deps.storage, (&info.sender, &validator))?
.unwrap_or_default();

Expand All @@ -259,6 +275,7 @@ impl ExternalStakingContract<'_> {
stake.stake.prepare_sub(amount.amount, Uint128::zero())?;

self.stakes
.stake
.save(deps.storage, (&info.sender, &validator), &stake)?;

// Create new tx
Expand Down Expand Up @@ -336,7 +353,10 @@ impl ExternalStakingContract<'_> {
let config = self.config.load(deps.storage)?;

// Load stake
let mut stake = self.stakes.load(deps.storage, (&tx_user, &tx_validator))?;
let mut stake = self
.stakes
.stake
.load(deps.storage, (&tx_user, &tx_validator))?;

// Load distribution
let mut distribution = self
Expand Down Expand Up @@ -364,6 +384,7 @@ impl ExternalStakingContract<'_> {

// Save stake
self.stakes
.stake
.save(deps.storage, (&tx_user, &tx_validator), &stake)?;

// Save distribution
Expand Down Expand Up @@ -397,13 +418,17 @@ impl ExternalStakingContract<'_> {
};

// Load stake
let mut stake = self.stakes.load(deps.storage, (&tx_user, &tx_validator))?;
let mut stake = self
.stakes
.stake
.load(deps.storage, (&tx_user, &tx_validator))?;

// Rollback sub amount
stake.stake.rollback_sub(tx_amount);

// Save stake
self.stakes
.stake
.save(deps.storage, (&tx_user, &tx_validator), &stake)?;

// Remove tx
Expand All @@ -423,6 +448,7 @@ impl ExternalStakingContract<'_> {

let stakes: Vec<_> = self
.stakes
.stake
.prefix(&ctx.info.sender)
.range(ctx.deps.storage, None, None, Order::Ascending)
.collect::<Result<_, _>>()?;
Expand All @@ -433,8 +459,11 @@ impl ExternalStakingContract<'_> {
let released = stake.release_pending(&ctx.env.block);

if !released.is_zero() {
self.stakes
.save(ctx.deps.storage, (&ctx.info.sender, &validator), &stake)?
self.stakes.stake.save(
ctx.deps.storage,
(&ctx.info.sender, &validator),
&stake,
)?
}

Ok(released)
Expand Down Expand Up @@ -551,6 +580,7 @@ impl ExternalStakingContract<'_> {

let stake = self
.stakes
.stake
.may_load(ctx.deps.storage, (&ctx.info.sender, &validator))?
.unwrap_or_default();

Expand Down Expand Up @@ -658,15 +688,51 @@ impl ExternalStakingContract<'_> {
};

// Update withdrawn_funds to hold this transfer
let mut stake = self.stakes.load(deps.storage, (&staker, &validator))?;
let mut stake = self
.stakes
.stake
.load(deps.storage, (&staker, &validator))?;
stake.withdrawn_funds += amount;

self.stakes
.stake
.save(deps.storage, (&staker, &validator), &stake)?;

Ok(())
}

/// Slashes a validator.
///
/// In test code, this is called from `test_handle_slashing`.
/// In non-test code, this is being called from `ibc_packet_receive` (in the `ConsumerPacket::RemoveValidators`
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm probably missing something, but should user slashing not get triggered when a validator itself is slashed?

Copy link
Collaborator Author

@maurolacy maurolacy Oct 5, 2023

Choose a reason for hiding this comment

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

There's no specific slashing event. As mentioned, slashing is part of validator tombstone, because of double signing; or of validator jailing, because of being offline.

/// handler)
pub(crate) fn handle_slashing(
&self,
storage: &mut dyn Storage,
validator: &str,
) -> Result<WasmMsg, ContractError> {
// Route associated users to vault for slashing of their collateral
let config = self.config.load(storage)?;
let users = self
.stakes
.stake
.idx
.rev
.sub_prefix(validator.to_string())
.range(storage, None, None, Order::Ascending)
.map(|item| {
let ((user, _), stake) = item?;
Ok::<_, ContractError>(SlashInfo {
user,
stake: stake.stake.high(),
})
})
.collect::<Result<Vec<_>, _>>()?;

let msg = config.vault.process_cross_slashing(users)?;
Ok(msg)
}

/// Queries for contract configuration
#[msg(query)]
pub fn config(&self, ctx: QueryCtx) -> Result<ConfigResponse, ContractError> {
Expand Down Expand Up @@ -719,6 +785,7 @@ impl ExternalStakingContract<'_> {
let user = ctx.deps.api.addr_validate(&user)?;
let stake = self
.stakes
.stake
.may_load(ctx.deps.storage, (&user, &validator))?
.unwrap_or_default();

Expand All @@ -743,6 +810,7 @@ impl ExternalStakingContract<'_> {

let stakes = self
.stakes
.stake
.prefix(&user)
.range(ctx.deps.storage, bound, None, Order::Ascending)
.map(|item| {
Expand Down Expand Up @@ -810,6 +878,7 @@ impl ExternalStakingContract<'_> {

let stake = self
.stakes
.stake
.may_load(ctx.deps.storage, (&user, &validator))?
.unwrap_or_default();

Expand Down Expand Up @@ -845,6 +914,7 @@ impl ExternalStakingContract<'_> {

let rewards: Vec<_> = self
.stakes
.stake
.prefix(&user)
.range(ctx.deps.storage, bound, None, Order::Ascending)
.take(limit)
Expand Down Expand Up @@ -931,6 +1001,7 @@ pub mod cross_staking {
}
let mut stake = self
.stakes
.stake
.may_load(ctx.deps.storage, (&owner, &msg.validator))?
.unwrap_or_default();

Expand All @@ -939,6 +1010,7 @@ pub mod cross_staking {
// performed the proper check.
stake.stake.prepare_add(amount.amount, None)?;
self.stakes
.stake
.save(ctx.deps.storage, (&owner, &msg.validator), &stake)?;

// Save tx
Expand Down
12 changes: 12 additions & 0 deletions contracts/provider/external-staking/src/crdt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,18 @@ impl<'a> CrdtState<'a> {
Ok(active)
}

pub fn is_active_validator_at_height(
&self,
storage: &dyn Storage,
valoper: &str,
height: u64,
) -> StdResult<bool> {
let active = self
.active_validator_at_height(storage, valoper, height)?
.is_some();
Ok(active)
}

/// This returns the valoper address of all active validators
pub fn list_active_validators(
&self,
Expand Down
3 changes: 3 additions & 0 deletions contracts/provider/external-staking/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub enum ContractError {
#[error("No staking rewards to be withdrawn")]
NoRewards,

#[error("Validator '{0}' already tombstoned / not found at height {1}")]
AlreadyTombstoned(String, u64),

#[error("{0}")]
Range(#[from] RangeError),
}
Loading
Loading