diff --git a/contracts/factory/src/tests.rs b/contracts/factory/src/tests.rs index 780bafe5..4fe38dc9 100644 --- a/contracts/factory/src/tests.rs +++ b/contracts/factory/src/tests.rs @@ -713,12 +713,12 @@ fn test_proxy_validators_set_update_validators_unauthorized() { drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address1".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address2".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, ], }, @@ -750,12 +750,12 @@ fn test_proxy_validators_set_update_validators() { drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address1".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address2".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, ], }, @@ -774,12 +774,12 @@ fn test_proxy_validators_set_update_validators() { drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address1".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address2".to_string(), weight: 10u64, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, ], }) diff --git a/contracts/val-ref/src/contract.rs b/contracts/val-ref/src/contract.rs index c520080f..35d29a17 100644 --- a/contracts/val-ref/src/contract.rs +++ b/contracts/val-ref/src/contract.rs @@ -1,7 +1,7 @@ use crate::error::{ContractError, ContractResult}; use cosmwasm_std::{ attr, entry_point, to_json_binary, Binary, Decimal, Deps, DepsMut, Env, MessageInfo, Order, - Response, StdResult, SubMsg, WasmMsg, + Reply, Response, StdResult, SubMsg, WasmMsg, }; use drop_helpers::answer::response; use drop_staking_base::{ @@ -17,6 +17,8 @@ use neutron_sdk::bindings::{msg::NeutronMsg, query::NeutronQuery}; pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const EDIT_ON_TOP_REPLY_ID: u64 = 1; + #[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] pub fn instantiate( deps: DepsMut, @@ -89,16 +91,19 @@ fn execute_bond_hook( &drop_staking_base::msg::core::QueryMsg::ExchangeRate {}, )?; let on_top_increase = bond_hook.dasset_minted * exchange_rate; - messages.push(SubMsg::new(WasmMsg::Execute { - contract_addr: VALIDATORS_SET_ADDRESS.load(deps.storage)?.into_string(), - funds: vec![], - msg: to_json_binary(&ValidatorSetExecuteMsg::EditOnTop { - operations: vec![OnTopEditOperation::Add { - validator_address, - amount: on_top_increase, - }], - })?, - })); + messages.push(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: VALIDATORS_SET_ADDRESS.load(deps.storage)?.into_string(), + funds: vec![], + msg: to_json_binary(&ValidatorSetExecuteMsg::EditOnTop { + operations: vec![OnTopEditOperation::Add { + validator_address, + amount: on_top_increase, + }], + })?, + }, + EDIT_ON_TOP_REPLY_ID, + )); attrs.push(attr("on_top_increase", on_top_increase)); } else { attrs.push(attr("validator", "None")); @@ -180,6 +185,18 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> ContractResu } } +#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)] +pub fn reply(_deps: DepsMut, _env: Env, msg: Reply) -> ContractResult { + match msg.id { + EDIT_ON_TOP_REPLY_ID => Ok(response( + "reply", + CONTRACT_NAME, + [attr("edit_on_top_error", true.to_string())], + )), + id => Err(ContractError::UnknownReplyId { id }), + } +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> ContractResult> { let version: semver::Version = CONTRACT_VERSION.parse()?; diff --git a/contracts/val-ref/src/error.rs b/contracts/val-ref/src/error.rs index 8d2705b5..a3744c80 100644 --- a/contracts/val-ref/src/error.rs +++ b/contracts/val-ref/src/error.rs @@ -11,6 +11,9 @@ pub enum ContractError { #[error("Semver parsing error: {0}")] SemVer(String), + + #[error("unknown reply id: {id}")] + UnknownReplyId { id: u64 }, } impl From for ContractError { diff --git a/contracts/val-ref/src/tests.rs b/contracts/val-ref/src/tests.rs index 206809ca..4dc53cd5 100644 --- a/contracts/val-ref/src/tests.rs +++ b/contracts/val-ref/src/tests.rs @@ -1,8 +1,11 @@ -use crate::{contract, error::ContractError}; +use crate::{ + contract::{self, EDIT_ON_TOP_REPLY_ID}, + error::ContractError, +}; use cosmwasm_std::{ from_json, testing::{mock_env, mock_info}, - to_json_binary, Addr, Decimal, Event, Order, Response, StdResult, Uint128, WasmMsg, + to_json_binary, Addr, Decimal, Event, Order, Response, StdResult, SubMsg, Uint128, WasmMsg, }; use drop_helpers::testing::mock_dependencies; use drop_staking_base::{ @@ -262,17 +265,20 @@ fn execute_bond_hook_known_validator() { assert_eq!( response, Response::new() - .add_message(WasmMsg::Execute { - contract_addr: String::from("validators_set"), - msg: to_json_binary(&ValidatorSetExecuteMsg::EditOnTop { - operations: vec![OnTopEditOperation::Add { - validator_address: String::from("valoperX"), - amount: Uint128::new(150), - }] - }) - .unwrap(), - funds: vec![] - }) + .add_submessage(SubMsg::reply_on_error( + WasmMsg::Execute { + contract_addr: String::from("validators_set"), + msg: to_json_binary(&ValidatorSetExecuteMsg::EditOnTop { + operations: vec![OnTopEditOperation::Add { + validator_address: String::from("valoperX"), + amount: Uint128::new(150), + }] + }) + .unwrap(), + funds: vec![] + }, + EDIT_ON_TOP_REPLY_ID + )) .add_event( Event::new("drop-val-ref-execute-bond-hook").add_attributes([ ("ref", "X"), diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index 4f54bbec..24ce4e73 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -15,6 +15,8 @@ use drop_staking_base::state::validatorset::{ use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; +use std::collections::HashMap; + const CONTRACT_NAME: &str = concat!("crates.io:drop-staking__", env!("CARGO_PKG_NAME")); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -138,14 +140,23 @@ fn execute_update_validators( let total_count = validators.len(); - // TODO: implement notification of the validator stats contract about new validators set + let old_validator_set: HashMap = VALIDATORS_SET + .range_raw(deps.storage, None, None, Order::Ascending) + .map(|item| item.map(|(_key, value)| (value.valoper_address.to_string(), value))) + .collect::>()?; + VALIDATORS_SET.clear(deps.storage); for validator in validators { + let on_top_value = old_validator_set + .get(&validator.valoper_address) + .map(|validator_info| validator_info.on_top) + .unwrap_or_default(); + let validator_info = ValidatorInfo { valoper_address: validator.valoper_address, weight: validator.weight, - on_top: validator.on_top, + on_top: validator.on_top.unwrap_or(on_top_value), last_processed_remote_height: None, last_processed_local_height: None, last_validated_height: None, @@ -330,12 +341,12 @@ fn execute_edit_on_top( validator_info.on_top = validator_info.on_top.checked_add(amount)?; validator_info } - OnTopEditOperation::Subtract { + OnTopEditOperation::Set { validator_address, amount, } => { let mut validator_info = VALIDATORS_SET.load(deps.storage, &validator_address)?; - validator_info.on_top = validator_info.on_top.checked_sub(amount)?; + validator_info.on_top = amount; validator_info } }; diff --git a/contracts/validators-set/src/tests.rs b/contracts/validators-set/src/tests.rs index fc34bb33..596ff3ac 100644 --- a/contracts/validators-set/src/tests.rs +++ b/contracts/validators-set/src/tests.rs @@ -177,7 +177,7 @@ fn update_validators_wrong_owner() { validators: vec![drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address".to_string(), weight: 1, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }], }, ) @@ -211,12 +211,12 @@ fn update_validators_ok() { drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address1".to_string(), weight: 1, - on_top: Uint128::new(10), + on_top: Some(Uint128::new(10)), }, drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address2".to_string(), weight: 1, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }, ], }, @@ -268,6 +268,162 @@ fn update_validators_ok() { ); } +#[test] +fn update_validators_without_ontop_ok() { + let mut deps = mock_dependencies(&[]); + + let deps_mut = deps.as_mut(); + + let _result = cw_ownable::initialize_owner( + deps_mut.storage, + deps_mut.api, + Some(Addr::unchecked("core").as_ref()), + ); + + let response = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core", &[]), + drop_staking_base::msg::validatorset::ExecuteMsg::UpdateValidators { + validators: vec![ + drop_staking_base::msg::validatorset::ValidatorData { + valoper_address: "valoper_address1".to_string(), + weight: 1, + on_top: Some(Uint128::new(20)), + }, + drop_staking_base::msg::validatorset::ValidatorData { + valoper_address: "valoper_address2".to_string(), + weight: 1, + on_top: None, + }, + ], + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let validator = crate::contract::query( + deps.as_ref(), + mock_env(), + drop_staking_base::msg::validatorset::QueryMsg::Validators {}, + ) + .unwrap(); + assert_eq!( + validator, + to_json_binary(&vec![ + drop_staking_base::state::validatorset::ValidatorInfo { + valoper_address: "valoper_address1".to_string(), + weight: 1, + on_top: Uint128::new(20), + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Decimal::zero(), + tombstone: false, + jailed_number: None, + init_proposal: None, + total_passed_proposals: 0, + total_voted_proposals: 0, + }, + drop_staking_base::state::validatorset::ValidatorInfo { + valoper_address: "valoper_address2".to_string(), + weight: 1, + on_top: Uint128::zero(), + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Decimal::zero(), + tombstone: false, + jailed_number: None, + init_proposal: None, + total_passed_proposals: 0, + total_voted_proposals: 0, + } + ]) + .unwrap() + ); +} + +#[test] +fn update_validators_use_last_ontop() { + let mut deps = mock_dependencies(&[]); + + let deps_mut = deps.as_mut(); + + let _result = cw_ownable::initialize_owner( + deps_mut.storage, + deps_mut.api, + Some(Addr::unchecked("core").as_ref()), + ); + + drop_staking_base::state::validatorset::VALIDATORS_SET + .save( + deps.as_mut().storage, + "voter", + &drop_staking_base::state::validatorset::ValidatorInfo { + valoper_address: "valoper_address1".to_string(), + weight: 0u64, + on_top: Uint128::new(30), + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Decimal::zero(), + tombstone: false, + jailed_number: None, + init_proposal: None, + total_passed_proposals: 0u64, + total_voted_proposals: 0u64, + }, + ) + .unwrap(); + + let response = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core", &[]), + drop_staking_base::msg::validatorset::ExecuteMsg::UpdateValidators { + validators: vec![drop_staking_base::msg::validatorset::ValidatorData { + valoper_address: "valoper_address1".to_string(), + weight: 1, + on_top: None, + }], + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let validator = crate::contract::query( + deps.as_ref(), + mock_env(), + drop_staking_base::msg::validatorset::QueryMsg::Validators {}, + ) + .unwrap(); + assert_eq!( + validator, + to_json_binary(&vec![ + drop_staking_base::state::validatorset::ValidatorInfo { + valoper_address: "valoper_address1".to_string(), + weight: 1, + on_top: Uint128::new(30), + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Decimal::zero(), + tombstone: false, + jailed_number: None, + init_proposal: None, + total_passed_proposals: 0, + total_voted_proposals: 0, + }, + ]) + .unwrap() + ); +} + #[test] fn update_validators_info_wrong_sender() { let mut deps = mock_dependencies(&[]); @@ -299,7 +455,7 @@ fn update_validators_info_wrong_sender() { validators: vec![drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address".to_string(), weight: 1, - on_top: Uint128::zero(), + on_top: Some(Uint128::zero()), }], }, ) @@ -360,7 +516,7 @@ fn update_validators_info_ok() { validators: vec![drop_staking_base::msg::validatorset::ValidatorData { valoper_address: "valoper_address".to_string(), weight: 1, - on_top: Uint128::new(2), + on_top: Some(Uint128::new(2)), }], }, ) @@ -914,7 +1070,7 @@ fn execute_edit_on_top_subtract() { mock_info("val_ref_contract", &[]), drop_staking_base::msg::validatorset::ExecuteMsg::EditOnTop { operations: vec![ - drop_staking_base::msg::validatorset::OnTopEditOperation::Subtract { + drop_staking_base::msg::validatorset::OnTopEditOperation::Set { validator_address: String::from("valoperX"), amount: Uint128::new(100), }, @@ -1017,7 +1173,7 @@ fn execute_edit_on_top_mixed() { mock_info("val_ref_contract", &[]), drop_staking_base::msg::validatorset::ExecuteMsg::EditOnTop { operations: vec![ - drop_staking_base::msg::validatorset::OnTopEditOperation::Subtract { + drop_staking_base::msg::validatorset::OnTopEditOperation::Set { validator_address: String::from("valoperX"), amount: Uint128::new(100), }, diff --git a/packages/base/src/msg/validatorset.rs b/packages/base/src/msg/validatorset.rs index 284a9c0f..3aa99386 100644 --- a/packages/base/src/msg/validatorset.rs +++ b/packages/base/src/msg/validatorset.rs @@ -16,7 +16,7 @@ pub struct InstantiateMsg { pub struct ValidatorData { pub valoper_address: String, pub weight: u64, - pub on_top: Uint128, + pub on_top: Option, } #[cw_serde] @@ -57,7 +57,7 @@ pub enum OnTopEditOperation { validator_address: String, amount: Uint128, }, - Subtract { + Set { validator_address: String, amount: Uint128, },