From 3f666ba030904c1fef5722fd04a74aebc58a616d Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Mon, 11 Dec 2023 23:08:59 +0200 Subject: [PATCH 01/13] validators set contract --- contracts/validators-set/Cargo.toml | 2 +- contracts/validators-set/src/contract.rs | 97 +++++++++++++++++++++--- contracts/validators-set/src/msg.rs | 19 ++++- contracts/validators-set/src/state.rs | 23 +++++- 4 files changed, 126 insertions(+), 15 deletions(-) diff --git a/contracts/validators-set/Cargo.toml b/contracts/validators-set/Cargo.toml index 820694e8..a9d83af8 100644 --- a/contracts/validators-set/Cargo.toml +++ b/contracts/validators-set/Cargo.toml @@ -1,5 +1,5 @@ [package] -authors = [] +authors = ["Albert Andrejev "] description = "Contract to provide validators set" edition = "2021" name = "lido-validators-set" diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index c5d6717a..081b312a 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -1,17 +1,17 @@ -use cosmwasm_std::{entry_point, to_json_binary, Deps}; +use cosmwasm_std::{entry_point, to_json_binary, Addr, Deps, Order}; use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; use neutron_sdk::NeutronResult; -use crate::state::{QueryMsg, CONFIG}; +use crate::state::{QueryMsg, ValidatorInfo, CONFIG, VALIDATORS_SET}; use crate::{ msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}, state::Config, }; -const CONTRACT_NAME: &str = concat!("crates.io:lido-validators_stats__", env!("CARGO_PKG_NAME")); +const CONTRACT_NAME: &str = concat!("crates.io:lido-validators_set__", env!("CARGO_PKG_NAME")); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] @@ -23,11 +23,12 @@ pub fn instantiate( ) -> NeutronResult { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - let owner = deps.api.addr_validate(&msg.owner)?; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(msg.owner.as_ref()))?; - cw_ownable::initialize_owner(deps.storage, deps.api, Some(&msg.owner))?; - - let config = &Config { owner }; + let config = &Config { + owner: msg.owner, + stats_contract: msg.stats_contract, + }; CONFIG.save(deps.storage, config)?; @@ -38,6 +39,8 @@ pub fn instantiate( pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Config {} => query_config(deps, env), + QueryMsg::Validator { valoper } => query_validator(deps, valoper), + QueryMsg::Validators {} => query_validators(deps), } } @@ -46,14 +49,88 @@ fn query_config(deps: Deps, _env: Env) -> StdResult { to_json_binary(&config) } +fn query_validator(deps: Deps, valoper: Addr) -> StdResult { + let validators = VALIDATORS_SET.may_load(deps.storage, valoper.to_string())?; + + to_json_binary(&validators) +} + +fn query_validators(deps: Deps) -> StdResult { + let validators: StdResult> = VALIDATORS_SET + .range_raw(deps.storage, None, None, Order::Ascending) + .map(|item| item.map(|(_key, value)| value)) + .collect(); + + to_json_binary(&validators?) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - _deps: DepsMut, + deps: DepsMut, _env: Env, - _info: MessageInfo, + info: MessageInfo, msg: ExecuteMsg, ) -> NeutronResult> { - match msg {} + match msg { + ExecuteMsg::UpdateConfig { + owner, + stats_contract, + } => execute_update_config(deps, info, owner, stats_contract), + ExecuteMsg::UpdateValidators { validators } => execute_update_validators(deps, validators), + ExecuteMsg::UpdateValidator { validator } => execute_update_validator(deps, validator), + } +} + +fn execute_update_config( + deps: DepsMut, + info: MessageInfo, + owner: Option, + stats_contract: Option, +) -> NeutronResult> { + cw_ownable::is_owner(deps.storage, &info.sender)?; + + let mut state = CONFIG.load(deps.storage)?; + + if owner.is_some() && owner != Some(state.clone().owner) { + state.owner = owner.unwrap_or(state.owner); + cw_ownable::initialize_owner(deps.storage, deps.api, Some(state.owner.as_ref()))?; + } + + if stats_contract.is_some() && stats_contract != Some(state.clone().stats_contract) { + state.stats_contract = stats_contract.unwrap_or(state.stats_contract); + } + + CONFIG.save(deps.storage, &state)?; + + Ok(Response::default()) +} + +fn execute_update_validator( + deps: DepsMut, + validator: ValidatorInfo, +) -> NeutronResult> { + // TODO: implement notification of the validator stats contract about new validator + let valoper_address = validator.valoper_address.clone(); + + VALIDATORS_SET.save(deps.storage, valoper_address, &validator)?; + + Ok(Response::default()) +} + +fn execute_update_validators( + deps: DepsMut, + validators: Vec, +) -> NeutronResult> { + // TODO: implement notification of the validator stats contract about new validators set + VALIDATORS_SET.clear(deps.storage); + + for validator in validators { + let valoper_address = validator.valoper_address.clone(); + + VALIDATORS_SET.save(deps.storage, valoper_address, &validator)?; + } + + Ok(Response::default()) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/validators-set/src/msg.rs b/contracts/validators-set/src/msg.rs index ccb4c1f4..b08b0997 100644 --- a/contracts/validators-set/src/msg.rs +++ b/contracts/validators-set/src/msg.rs @@ -1,12 +1,27 @@ use cosmwasm_schema::cw_serde; +use cosmwasm_std::Addr; + +use crate::state::ValidatorInfo; #[cw_serde] pub struct InstantiateMsg { - pub owner: String, + pub owner: Addr, + pub stats_contract: Addr, } #[cw_serde] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + UpdateConfig { + owner: Option, + stats_contract: Option, + }, + UpdateValidators { + validators: Vec, + }, + UpdateValidator { + validator: ValidatorInfo, + }, +} #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/validators-set/src/state.rs b/contracts/validators-set/src/state.rs index 0164d172..81cde369 100644 --- a/contracts/validators-set/src/state.rs +++ b/contracts/validators-set/src/state.rs @@ -1,11 +1,25 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -use cw_storage_plus::Item; +use cosmwasm_std::{Addr, Decimal}; +use cw_storage_plus::{Item, Map}; #[cw_serde] pub struct Config { pub owner: Addr, + pub stats_contract: Addr, +} + +#[cw_serde] +pub struct ValidatorInfo { + pub valoper_address: String, + pub weight: u64, + pub last_processed_remote_height: Option, + pub last_processed_local_height: Option, + pub last_validated_height: Option, + pub last_commission_in_range: Option, + pub uptime: Decimal, + pub tombstone: bool, + pub jailed_number: Option, } #[cw_serde] @@ -13,6 +27,11 @@ pub struct Config { pub enum QueryMsg { #[returns(Config)] Config {}, + #[returns(ValidatorInfo)] + Validator { valoper: Addr }, + #[returns(Vec)] + Validators {}, } pub const CONFIG: Item = Item::new("config"); +pub const VALIDATORS_SET: Map = Map::new("validators_set"); From a721c8becf21ce6bbadaaf1a7d4be92b8624d21e Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Tue, 12 Dec 2023 14:10:30 +0200 Subject: [PATCH 02/13] Add errors and validator info update endpoint --- Cargo.lock | 1 + contracts/validators-set/Cargo.toml | 1 + contracts/validators-set/src/contract.rs | 77 ++++++++++++++++++++---- contracts/validators-set/src/error.rs | 18 ++++++ contracts/validators-set/src/lib.rs | 1 + contracts/validators-set/src/msg.rs | 11 +++- 6 files changed, 97 insertions(+), 12 deletions(-) create mode 100644 contracts/validators-set/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index 187e2d9b..b2b199af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -738,6 +738,7 @@ dependencies = [ "serde", "serde-json-wasm 1.0.0", "tendermint-proto", + "thiserror", ] [[package]] diff --git a/contracts/validators-set/Cargo.toml b/contracts/validators-set/Cargo.toml index a9d83af8..3f8ea5de 100644 --- a/contracts/validators-set/Cargo.toml +++ b/contracts/validators-set/Cargo.toml @@ -28,6 +28,7 @@ prost = { workspace = true } prost-types = { workspace = true } protobuf = { workspace = true } tendermint-proto = { workspace = true } +thiserror = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index 081b312a..72e25684 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -5,6 +5,8 @@ use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; use neutron_sdk::NeutronResult; +use crate::error::ContractResult; +use crate::msg::ValidatorData; use crate::state::{QueryMsg, ValidatorInfo, CONFIG, VALIDATORS_SET}; use crate::{ msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}, @@ -70,14 +72,21 @@ pub fn execute( _env: Env, info: MessageInfo, msg: ExecuteMsg, -) -> NeutronResult> { +) -> ContractResult> { match msg { ExecuteMsg::UpdateConfig { owner, stats_contract, } => execute_update_config(deps, info, owner, stats_contract), - ExecuteMsg::UpdateValidators { validators } => execute_update_validators(deps, validators), - ExecuteMsg::UpdateValidator { validator } => execute_update_validator(deps, validator), + ExecuteMsg::UpdateValidators { validators } => { + execute_update_validators(deps, info, validators) + } + ExecuteMsg::UpdateValidator { validator } => { + execute_update_validator(deps, info, validator) + } + ExecuteMsg::UpdateValidatorInfo { validator } => { + execute_update_validator_info(deps, info, validator) + } } } @@ -86,8 +95,8 @@ fn execute_update_config( info: MessageInfo, owner: Option, stats_contract: Option, -) -> NeutronResult> { - cw_ownable::is_owner(deps.storage, &info.sender)?; +) -> ContractResult> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; let mut state = CONFIG.load(deps.storage)?; @@ -107,32 +116,78 @@ fn execute_update_config( fn execute_update_validator( deps: DepsMut, - validator: ValidatorInfo, -) -> NeutronResult> { + info: MessageInfo, + validator: ValidatorData, +) -> ContractResult> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; // TODO: implement notification of the validator stats contract about new validator let valoper_address = validator.valoper_address.clone(); - VALIDATORS_SET.save(deps.storage, valoper_address, &validator)?; + VALIDATORS_SET.save( + deps.storage, + valoper_address, + &ValidatorInfo { + valoper_address: validator.valoper_address, + weight: validator.weight, + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Default::default(), + tombstone: false, + jailed_number: None, + }, + )?; Ok(Response::default()) } fn execute_update_validators( deps: DepsMut, - validators: Vec, -) -> NeutronResult> { + info: MessageInfo, + validators: Vec, +) -> ContractResult> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; + // TODO: implement notification of the validator stats contract about new validators set VALIDATORS_SET.clear(deps.storage); for validator in validators { let valoper_address = validator.valoper_address.clone(); - VALIDATORS_SET.save(deps.storage, valoper_address, &validator)?; + VALIDATORS_SET.save( + deps.storage, + valoper_address, + &ValidatorInfo { + valoper_address: validator.valoper_address, + weight: validator.weight, + last_processed_remote_height: None, + last_processed_local_height: None, + last_validated_height: None, + last_commission_in_range: None, + uptime: Default::default(), + tombstone: false, + jailed_number: None, + }, + )?; } Ok(Response::default()) } +fn execute_update_validator_info( + deps: DepsMut, + info: MessageInfo, + validator: ValidatorInfo, +) -> ContractResult> { + cw_ownable::assert_owner(deps.storage, &info.sender)?; + + // TODO: Implement logic to modify validator set based in incoming validator info + VALIDATORS_SET.save(deps.storage, validator.valoper_address.clone(), &validator)?; + + Ok(Response::default()) +} + #[cfg_attr(not(feature = "library"), entry_point)] pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult { deps.api.debug("WASMDEBUG: migrate"); diff --git a/contracts/validators-set/src/error.rs b/contracts/validators-set/src/error.rs new file mode 100644 index 00000000..8c839afe --- /dev/null +++ b/contracts/validators-set/src/error.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::StdError; +use cw_ownable::OwnershipError; +use neutron_sdk::NeutronError; +use thiserror::Error; + +#[derive(Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + NeutronError(#[from] NeutronError), + + #[error("{0}")] + OwnershipError(#[from] OwnershipError), +} + +pub type ContractResult = Result; diff --git a/contracts/validators-set/src/lib.rs b/contracts/validators-set/src/lib.rs index 4934c19d..a5abdbb0 100644 --- a/contracts/validators-set/src/lib.rs +++ b/contracts/validators-set/src/lib.rs @@ -1,3 +1,4 @@ pub mod contract; +pub mod error; pub mod msg; pub mod state; diff --git a/contracts/validators-set/src/msg.rs b/contracts/validators-set/src/msg.rs index b08b0997..4d2ac922 100644 --- a/contracts/validators-set/src/msg.rs +++ b/contracts/validators-set/src/msg.rs @@ -9,6 +9,12 @@ pub struct InstantiateMsg { pub stats_contract: Addr, } +#[cw_serde] +pub struct ValidatorData { + pub valoper_address: String, + pub weight: u64, +} + #[cw_serde] pub enum ExecuteMsg { UpdateConfig { @@ -16,9 +22,12 @@ pub enum ExecuteMsg { stats_contract: Option, }, UpdateValidators { - validators: Vec, + validators: Vec, }, UpdateValidator { + validator: ValidatorData, + }, + UpdateValidatorInfo { validator: ValidatorInfo, }, } From bc2df1550b3b9c75cf9ba015144bbfa474daa71d Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Wed, 13 Dec 2023 10:31:47 +0200 Subject: [PATCH 03/13] simple tests --- Cargo.lock | 1 + integration_tests/package.json | 1 + .../src/generated/contractLib/index.ts | 25 ++-- .../contractLib/lidoValidatorsSet.ts | 90 ++++++++++++- integration_tests/src/testSuite.ts | 34 +++++ .../src/testcases/validator-set.test.ts | 119 ++++++++++++++++++ 6 files changed, 251 insertions(+), 19 deletions(-) create mode 100644 integration_tests/src/testcases/validator-set.test.ts diff --git a/Cargo.lock b/Cargo.lock index b2b199af..82ea8321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -860,6 +860,7 @@ dependencies = [ "serde", "serde-json-wasm 1.0.0", "tendermint-proto", + "thiserror", ] [[package]] diff --git a/integration_tests/package.json b/integration_tests/package.json index 595c4109..ec866c4b 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -10,6 +10,7 @@ "test:interceptor": "vitest --run interceptor.test --bail 1", "test:interceptor-authz": "vitest --run interceptor-authz.test --bail 1", "test:validators-stats": "vitest --run validators-stats.test --bail 1", + "test:validator-set": "vitest validator-set.test --bail 1", "watch": "vitest", "build-ts-client": "ts-node ./src/rebuild-client.ts", "build-lsm-image": "./dockerfiles/lsm/build.sh", diff --git a/integration_tests/src/generated/contractLib/index.ts b/integration_tests/src/generated/contractLib/index.ts index 2e9dd14d..98db4ec5 100644 --- a/integration_tests/src/generated/contractLib/index.ts +++ b/integration_tests/src/generated/contractLib/index.ts @@ -7,23 +7,14 @@ export const LidoDistribution = _1; import * as _2 from './lidoFactory'; export const LidoFactory = _2; -import * as _3 from './lidoInterchainInterceptorAuthz'; -export const LidoInterchainInterceptorAuthz = _3; +import * as _3 from './lidoStrategy'; +export const LidoStrategy = _3; -import * as _4 from './lidoInterchainInterceptor'; -export const LidoInterchainInterceptor = _4; +import * as _4 from './lidoToken'; +export const LidoToken = _4; -import * as _5 from './lidoStargatePoc'; -export const LidoStargatePoc = _5; +import * as _5 from './lidoValidatorsSet'; +export const LidoValidatorsSet = _5; -import * as _6 from './lidoStrategy'; -export const LidoStrategy = _6; - -import * as _7 from './lidoToken'; -export const LidoToken = _7; - -import * as _8 from './lidoValidatorsSet'; -export const LidoValidatorsSet = _8; - -import * as _9 from './lidoValidatorsStats'; -export const LidoValidatorsStats = _9; +import * as _6 from './lidoValidatorsStats'; +export const LidoValidatorsStats = _6; diff --git a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts index cfb47235..c81adad7 100644 --- a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts +++ b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts @@ -1,8 +1,20 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; import { StdFee } from "@cosmjs/amino"; import { Coin } from "@cosmjs/amino"; +/** + * A human readable address. + * + * In Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length. + * + * This type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances. + * + * This type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance. + */ +export type Addr = string; + export interface InstantiateMsg { - owner: string; + owner: Addr; + stats_contract: Addr; } /** * A human readable address. @@ -14,13 +26,65 @@ export interface InstantiateMsg { * This type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance. */ export type Addr = string; +/** + * A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0 + * + * The greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18) + */ +export type Decimal = string; +export type ArrayOfValidatorInfo = ValidatorInfo1[]; export interface LidoValidatorsSetSchema { - responses: Config; + responses: Config | ValidatorInfo | ArrayOfValidatorInfo; + query: ValidatorArgs; + execute: UpdateConfigArgs | UpdateValidatorsArgs | UpdateValidatorArgs | UpdateValidatorInfoArgs; [k: string]: unknown; } export interface Config { owner: Addr; + stats_contract: Addr; +} +export interface ValidatorInfo { + jailed_number?: number | null; + last_commission_in_range?: number | null; + last_processed_local_height?: number | null; + last_processed_remote_height?: number | null; + last_validated_height?: number | null; + tombstone: boolean; + uptime: Decimal; + valoper_address: string; + weight: number; +} +export interface ValidatorInfo1 { + jailed_number?: number | null; + last_commission_in_range?: number | null; + last_processed_local_height?: number | null; + last_processed_remote_height?: number | null; + last_validated_height?: number | null; + tombstone: boolean; + uptime: Decimal; + valoper_address: string; + weight: number; +} +export interface ValidatorArgs { + valoper: Addr; +} +export interface UpdateConfigArgs { + owner?: Addr | null; + stats_contract?: Addr | null; +} +export interface UpdateValidatorsArgs { + validators: ValidatorData[]; +} +export interface ValidatorData { + valoper_address: string; + weight: number; +} +export interface UpdateValidatorArgs { + validator: ValidatorData; +} +export interface UpdateValidatorInfoArgs { + validator: ValidatorInfo1; } @@ -57,4 +121,26 @@ export class Client { queryConfig = async(): Promise => { return this.client.queryContractSmart(this.contractAddress, { config: {} }); } + queryValidator = async(args: ValidatorArgs): Promise => { + return this.client.queryContractSmart(this.contractAddress, { validator: args }); + } + queryValidators = async(): Promise => { + return this.client.queryContractSmart(this.contractAddress, { validators: {} }); + } + updateConfig = async(sender:string, args: UpdateConfigArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_config: args }, fee || "auto", memo, funds); + } + updateValidators = async(sender:string, args: UpdateValidatorsArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_validators: args }, fee || "auto", memo, funds); + } + updateValidator = async(sender:string, args: UpdateValidatorArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_validator: args }, fee || "auto", memo, funds); + } + updateValidatorInfo = async(sender:string, args: UpdateValidatorInfoArgs, fee?: number | StdFee | "auto", memo?: string, funds?: Coin[]): Promise => { + if (!isSigningCosmWasmClient(this.client)) { throw this.mustBeSigningClient(); } + return this.client.execute(sender, this.contractAddress, { update_validator_info: args }, fee || "auto", memo, funds); + } } diff --git a/integration_tests/src/testSuite.ts b/integration_tests/src/testSuite.ts index dbca13db..edb7291a 100644 --- a/integration_tests/src/testSuite.ts +++ b/integration_tests/src/testSuite.ts @@ -273,3 +273,37 @@ export const setupPark = async ( } return instance; }; + +export const setupSingle = async ( + context = 'lido', + network: string = 'neutron', +): Promise => { + const wallets = await generateWallets(); + const config: CosmoparkConfig = { + context, + networks: {}, + master_mnemonic: wallets.master, + loglevel: 'info', + wallets: { + demowallet1: { + mnemonic: wallets.demowallet1, + balance: '1000000000', + }, + demo1: { mnemonic: wallets.demo1, balance: '1000000000' }, + demo2: { mnemonic: wallets.demo2, balance: '1000000000' }, + demo3: { mnemonic: wallets.demo3, balance: '1000000000' }, + }, + }; + config.networks[network] = networkConfigs[network]; + const instance = await cosmopark.create(config); + await Promise.all( + Object.entries(instance.ports).map(([network, ports]) => + awaitFirstBlock(`127.0.0.1:${ports.rpc}`).catch((e) => { + console.log(`Failed to await first block for ${network}: ${e}`); + throw e; + }), + ), + ); + console.log('Awaited first blocks'); + return instance; +}; diff --git a/integration_tests/src/testcases/validator-set.test.ts b/integration_tests/src/testcases/validator-set.test.ts new file mode 100644 index 00000000..6266b609 --- /dev/null +++ b/integration_tests/src/testcases/validator-set.test.ts @@ -0,0 +1,119 @@ +import { describe, expect, it, beforeAll, afterAll } from 'vitest'; +import { LidoValidatorsSet } from '../generated/contractLib'; + +import { join } from 'path'; + +import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; +import { Client as NeutronClient } from '@neutron-org/client-ts'; +import { AccountData, DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; +import { GasPrice } from '@cosmjs/stargate'; +import { setupSingle } from '../testSuite'; +import fs from 'fs'; +import Cosmopark from '@neutron-org/cosmopark'; + +const SetClass = LidoValidatorsSet.Client; + +describe('Validator set', () => { + const context: { + park?: Cosmopark; + contractAddress?: string; + wallet?: DirectSecp256k1HdWallet; + contractClient?: InstanceType; + account?: AccountData; + client?: SigningCosmWasmClient; + neutronClient?: InstanceType; + } = {}; + + beforeAll(async () => { + context.park = await setupSingle('validatorset', 'neutron'); + + context.wallet = await DirectSecp256k1HdWallet.fromMnemonic( + context.park.config.wallets.demowallet1.mnemonic, + { + prefix: 'neutron', + }, + ); + + context.account = (await context.wallet.getAccounts())[0]; + context.neutronClient = new NeutronClient({ + apiURL: `http://127.0.0.1:${context.park.ports.neutron.rest}`, + rpcURL: `127.0.0.1:${context.park.ports.neutron.rpc}`, + prefix: 'neutron', + }); + + context.client = await SigningCosmWasmClient.connectWithSigner( + `http://127.0.0.1:${context.park.ports.neutron.rpc}`, + context.wallet, + { + gasPrice: GasPrice.fromString('0.025untrn'), + }, + ); + }); + + afterAll(async () => { + await context.park.stop(); + }); + + it('instantiate', async () => { + const { client, account } = context; + const res = await client.upload( + account.address, + fs.readFileSync( + join(__dirname, '../../../artifacts/lido_validators_set.wasm'), + ), + 1.5, + ); + expect(res.codeId).toBeGreaterThan(0); + const instantiateRes = await LidoValidatorsSet.Client.instantiate( + client, + account.address, + res.codeId, + { + owner: account.address, + stats_contract: account.address, + }, + 'label', + [], + 'auto', + ); + expect(instantiateRes.contractAddress).toHaveLength(66); + context.contractAddress = instantiateRes.contractAddress; + context.contractClient = new LidoValidatorsSet.Client( + client, + context.contractAddress, + ); + }); + + it('Add single validator', async () => { + const { contractClient, account } = context; + const res = await contractClient.updateValidator( + account.address, + { + validator: { + valoper_address: 'valoper1', + weight: 1, + }, + }, + 1.5, + ); + expect(res.transactionHash).toBeTruthy(); + + const validators = await contractClient.queryValidators(); + + expect(validators).toEqual( + expect.arrayContaining([ + { + valoper_address: 'valoper1', + weight: 1, + last_processed_remote_height: null, + last_processed_local_height: null, + last_validated_height: null, + last_commission_in_range: null, + uptime: '0', + tombstone: false, + jailed_number: null, + }, + ]), + ); + }); +}); From 9295a78cecaa7e8faf17cd638672dd2ab0f34315 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Wed, 13 Dec 2023 11:15:18 +0200 Subject: [PATCH 04/13] add validaotr set update tests --- contracts/validators-set/src/contract.rs | 19 +-- contracts/validators-set/src/error.rs | 3 + contracts/validators-set/src/msg.rs | 2 +- integration_tests/package.json | 2 +- .../src/generated/contractLib/index.ts | 19 +-- .../contractLib/lidoValidatorsSet.ts | 2 +- .../src/testcases/validator-set.test.ts | 110 ++++++++++++++++++ 7 files changed, 139 insertions(+), 18 deletions(-) diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index 72e25684..c456abf0 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -5,7 +5,7 @@ use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; use neutron_sdk::NeutronResult; -use crate::error::ContractResult; +use crate::error::{ContractError, ContractResult}; use crate::msg::ValidatorData; use crate::state::{QueryMsg, ValidatorInfo, CONFIG, VALIDATORS_SET}; use crate::{ @@ -84,8 +84,8 @@ pub fn execute( ExecuteMsg::UpdateValidator { validator } => { execute_update_validator(deps, info, validator) } - ExecuteMsg::UpdateValidatorInfo { validator } => { - execute_update_validator_info(deps, info, validator) + ExecuteMsg::UpdateValidatorInfo { validators } => { + execute_update_validator_info(deps, info, validators) } } } @@ -178,12 +178,17 @@ fn execute_update_validators( fn execute_update_validator_info( deps: DepsMut, info: MessageInfo, - validator: ValidatorInfo, + validators: Vec, ) -> ContractResult> { - cw_ownable::assert_owner(deps.storage, &info.sender)?; + let config = CONFIG.load(deps.storage)?; + if config.stats_contract != info.sender { + return Err(ContractError::Unauthorized {}); + } - // TODO: Implement logic to modify validator set based in incoming validator info - VALIDATORS_SET.save(deps.storage, validator.valoper_address.clone(), &validator)?; + for validator in validators { + // TODO: Implement logic to modify validator set based in incoming validator info + VALIDATORS_SET.save(deps.storage, validator.valoper_address.clone(), &validator)?; + } Ok(Response::default()) } diff --git a/contracts/validators-set/src/error.rs b/contracts/validators-set/src/error.rs index 8c839afe..0bb9f32a 100644 --- a/contracts/validators-set/src/error.rs +++ b/contracts/validators-set/src/error.rs @@ -13,6 +13,9 @@ pub enum ContractError { #[error("{0}")] OwnershipError(#[from] OwnershipError), + + #[error("unauthorized")] + Unauthorized, } pub type ContractResult = Result; diff --git a/contracts/validators-set/src/msg.rs b/contracts/validators-set/src/msg.rs index 4d2ac922..8abb44bf 100644 --- a/contracts/validators-set/src/msg.rs +++ b/contracts/validators-set/src/msg.rs @@ -28,7 +28,7 @@ pub enum ExecuteMsg { validator: ValidatorData, }, UpdateValidatorInfo { - validator: ValidatorInfo, + validators: Vec, }, } diff --git a/integration_tests/package.json b/integration_tests/package.json index ec866c4b..4c4ede52 100644 --- a/integration_tests/package.json +++ b/integration_tests/package.json @@ -10,7 +10,7 @@ "test:interceptor": "vitest --run interceptor.test --bail 1", "test:interceptor-authz": "vitest --run interceptor-authz.test --bail 1", "test:validators-stats": "vitest --run validators-stats.test --bail 1", - "test:validator-set": "vitest validator-set.test --bail 1", + "test:validator-set": "vitest --run validator-set.test --bail 1", "watch": "vitest", "build-ts-client": "ts-node ./src/rebuild-client.ts", "build-lsm-image": "./dockerfiles/lsm/build.sh", diff --git a/integration_tests/src/generated/contractLib/index.ts b/integration_tests/src/generated/contractLib/index.ts index 98db4ec5..0808a0a4 100644 --- a/integration_tests/src/generated/contractLib/index.ts +++ b/integration_tests/src/generated/contractLib/index.ts @@ -7,14 +7,17 @@ export const LidoDistribution = _1; import * as _2 from './lidoFactory'; export const LidoFactory = _2; -import * as _3 from './lidoStrategy'; -export const LidoStrategy = _3; +import * as _3 from './lidoStargatePoc'; +export const LidoStargatePoc = _3; -import * as _4 from './lidoToken'; -export const LidoToken = _4; +import * as _4 from './lidoStrategy'; +export const LidoStrategy = _4; -import * as _5 from './lidoValidatorsSet'; -export const LidoValidatorsSet = _5; +import * as _5 from './lidoToken'; +export const LidoToken = _5; -import * as _6 from './lidoValidatorsStats'; -export const LidoValidatorsStats = _6; +import * as _6 from './lidoValidatorsSet'; +export const LidoValidatorsSet = _6; + +import * as _7 from './lidoValidatorsStats'; +export const LidoValidatorsStats = _7; diff --git a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts index c81adad7..b1ec658d 100644 --- a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts +++ b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts @@ -84,7 +84,7 @@ export interface UpdateValidatorArgs { validator: ValidatorData; } export interface UpdateValidatorInfoArgs { - validator: ValidatorInfo1; + validators: ValidatorInfo1[]; } diff --git a/integration_tests/src/testcases/validator-set.test.ts b/integration_tests/src/testcases/validator-set.test.ts index 6266b609..eea28fe7 100644 --- a/integration_tests/src/testcases/validator-set.test.ts +++ b/integration_tests/src/testcases/validator-set.test.ts @@ -116,4 +116,114 @@ describe('Validator set', () => { ]), ); }); + + it('Add bunch of validators', async () => { + const { contractClient, account } = context; + const res = await contractClient.updateValidators( + account.address, + { + validators: [ + { + valoper_address: 'valoper2', + weight: 2, + }, + { + valoper_address: 'valoper3', + weight: 3, + }, + ], + }, + 1.5, + ); + expect(res.transactionHash).toBeTruthy(); + + const validators = await contractClient.queryValidators(); + + expect(validators).toEqual( + expect.arrayContaining([ + { + valoper_address: 'valoper2', + weight: 2, + last_processed_remote_height: null, + last_processed_local_height: null, + last_validated_height: null, + last_commission_in_range: null, + uptime: '0', + tombstone: false, + jailed_number: null, + }, + { + valoper_address: 'valoper3', + weight: 3, + last_processed_remote_height: null, + last_processed_local_height: null, + last_validated_height: null, + last_commission_in_range: null, + uptime: '0', + tombstone: false, + jailed_number: null, + }, + ]), + ); + }); + + it('Update validator info', async () => { + const { contractClient, account } = context; + const res = await contractClient.updateValidatorInfo( + account.address, + { + validators: [ + { + valoper_address: 'valoper2', + weight: 2, + tombstone: true, + uptime: '0.5', + jailed_number: 1, + last_commission_in_range: 1234, + last_processed_local_height: 2345, + last_processed_remote_height: 3456, + last_validated_height: 4567, + }, + { + valoper_address: 'valoper1', + weight: 1, + tombstone: false, + uptime: '0.96', + jailed_number: 3, + }, + ], + }, + 1.5, + ); + expect(res.transactionHash).toBeTruthy(); + + const validators = await contractClient.queryValidators(); + + expect(validators).toEqual( + expect.arrayContaining([ + { + valoper_address: 'valoper2', + weight: 2, + last_processed_remote_height: 3456, + last_processed_local_height: 2345, + last_validated_height: 4567, + last_commission_in_range: 1234, + uptime: '0.5', + tombstone: true, + jailed_number: 1, + }, + { + valoper_address: 'valoper1', + weight: 1, + last_processed_remote_height: null, + last_processed_local_height: null, + last_validated_height: null, + last_commission_in_range: null, + uptime: '0.96', + tombstone: false, + jailed_number: 3, + }, + ]), + ); + }); }); From fe4b0ee3db85fbf4dce7b0b83b344f412193927a Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Thu, 14 Dec 2023 23:39:01 +0200 Subject: [PATCH 05/13] last tests --- .../src/bin/lido-validators-set-schema.rs | 5 +- contracts/validators-set/src/contract.rs | 86 +++- contracts/validators-set/src/lib.rs | 3 + contracts/validators-set/src/msg.rs | 37 +- contracts/validators-set/src/state.rs | 15 +- contracts/validators-set/src/tests.rs | 467 ++++++++++++++++++ .../contractLib/lidoValidatorsSet.ts | 31 +- .../src/testcases/validator-set.test.ts | 10 +- 8 files changed, 585 insertions(+), 69 deletions(-) create mode 100644 contracts/validators-set/src/tests.rs diff --git a/contracts/validators-set/src/bin/lido-validators-set-schema.rs b/contracts/validators-set/src/bin/lido-validators-set-schema.rs index f05f2a1c..16e7b9ce 100644 --- a/contracts/validators-set/src/bin/lido-validators-set-schema.rs +++ b/contracts/validators-set/src/bin/lido-validators-set-schema.rs @@ -1,8 +1,5 @@ use cosmwasm_schema::write_api; -use lido_validators_set::{ - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}, - state::QueryMsg, -}; +use lido_validators_set::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index c456abf0..b75e5053 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -1,40 +1,47 @@ -use cosmwasm_std::{entry_point, to_json_binary, Addr, Deps, Order}; +use cosmwasm_std::{ + attr, ensure_eq, entry_point, to_json_binary, Addr, Attribute, Deps, Event, Order, +}; use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; -use neutron_sdk::NeutronResult; use crate::error::{ContractError, ContractResult}; -use crate::msg::ValidatorData; -use crate::state::{QueryMsg, ValidatorInfo, CONFIG, VALIDATORS_SET}; +use crate::msg::{ValidatorData, ValidatorInfoUpdate}; +use crate::state::{ValidatorInfo, CONFIG, VALIDATORS_SET}; use crate::{ - msg::{ExecuteMsg, InstantiateMsg, MigrateMsg}, + msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}, state::Config, }; -const CONTRACT_NAME: &str = concat!("crates.io:lido-validators_set__", env!("CARGO_PKG_NAME")); +const CONTRACT_NAME: &str = concat!("crates.io:lido-neutron-contracts__", env!("CARGO_PKG_NAME")); const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> NeutronResult { +) -> ContractResult> { set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - cw_ownable::initialize_owner(deps.storage, deps.api, Some(msg.owner.as_ref()))?; + let core = deps.api.addr_validate(&msg.core)?; + let stats_contract = deps.api.addr_validate(&msg.stats_contract)?; + + cw_ownable::initialize_owner(deps.storage, deps.api, Some(msg.core.as_ref()))?; let config = &Config { - owner: msg.owner, - stats_contract: msg.stats_contract, + core: core.clone(), + stats_contract: stats_contract.clone(), }; CONFIG.save(deps.storage, config)?; - Ok(Response::default()) + Ok(response( + "instantiate", + [attr("core", core), attr("stats_contract", stats_contract)], + )) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -75,9 +82,9 @@ pub fn execute( ) -> ContractResult> { match msg { ExecuteMsg::UpdateConfig { - owner, + core, stats_contract, - } => execute_update_config(deps, info, owner, stats_contract), + } => execute_update_config(deps, info, core, stats_contract), ExecuteMsg::UpdateValidators { validators } => { execute_update_validators(deps, info, validators) } @@ -100,9 +107,9 @@ fn execute_update_config( let mut state = CONFIG.load(deps.storage)?; - if owner.is_some() && owner != Some(state.clone().owner) { - state.owner = owner.unwrap_or(state.owner); - cw_ownable::initialize_owner(deps.storage, deps.api, Some(state.owner.as_ref()))?; + if owner.is_some() && owner != Some(state.clone().core) { + state.core = owner.unwrap_or(state.core); + cw_ownable::initialize_owner(deps.storage, deps.api, Some(state.core.as_ref()))?; } if stats_contract.is_some() && stats_contract != Some(state.clone().stats_contract) { @@ -178,15 +185,43 @@ fn execute_update_validators( fn execute_update_validator_info( deps: DepsMut, info: MessageInfo, - validators: Vec, + validators_update: Vec, ) -> ContractResult> { let config = CONFIG.load(deps.storage)?; - if config.stats_contract != info.sender { - return Err(ContractError::Unauthorized {}); - } + ensure_eq!( + config.stats_contract, + info.sender, + ContractError::Unauthorized {} + ); - for validator in validators { + for update in validators_update { // TODO: Implement logic to modify validator set based in incoming validator info + let validator = + VALIDATORS_SET.may_load(deps.storage, update.valoper_address.to_string())?; + if validator.is_none() { + continue; + } + let mut validator = validator.unwrap(); + + if update.last_commission_in_range.is_some() { + validator.last_commission_in_range = update.last_commission_in_range; + } + if update.last_processed_local_height.is_some() { + validator.last_processed_local_height = update.last_processed_local_height; + } + if update.last_processed_remote_height.is_some() { + validator.last_processed_remote_height = update.last_processed_remote_height; + } + if update.last_validated_height.is_some() { + validator.last_validated_height = update.last_validated_height; + } + if update.jailed_number.is_some() { + validator.jailed_number = update.jailed_number; + } + + validator.uptime = update.uptime; + validator.tombstone = update.tombstone; + VALIDATORS_SET.save(deps.storage, validator.valoper_address.clone(), &validator)?; } @@ -199,4 +234,9 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult>( + ty: &str, + attrs: impl IntoIterator, +) -> Response { + Response::new().add_event(Event::new(format!("{}-{}", CONTRACT_NAME, ty)).add_attributes(attrs)) +} diff --git a/contracts/validators-set/src/lib.rs b/contracts/validators-set/src/lib.rs index a5abdbb0..f0e2fd34 100644 --- a/contracts/validators-set/src/lib.rs +++ b/contracts/validators-set/src/lib.rs @@ -2,3 +2,6 @@ pub mod contract; pub mod error; pub mod msg; pub mod state; + +#[cfg(test)] +mod tests; diff --git a/contracts/validators-set/src/msg.rs b/contracts/validators-set/src/msg.rs index 8abb44bf..c5239e29 100644 --- a/contracts/validators-set/src/msg.rs +++ b/contracts/validators-set/src/msg.rs @@ -1,12 +1,12 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Decimal}; -use crate::state::ValidatorInfo; +use crate::state::{Config, ValidatorInfo}; #[cw_serde] pub struct InstantiateMsg { - pub owner: Addr, - pub stats_contract: Addr, + pub core: String, + pub stats_contract: String, } #[cw_serde] @@ -15,10 +15,22 @@ pub struct ValidatorData { pub weight: u64, } +#[cw_serde] +pub struct ValidatorInfoUpdate { + pub valoper_address: String, + pub last_processed_remote_height: Option, + pub last_processed_local_height: Option, + pub last_validated_height: Option, + pub last_commission_in_range: Option, + pub uptime: Decimal, + pub tombstone: bool, + pub jailed_number: Option, +} + #[cw_serde] pub enum ExecuteMsg { UpdateConfig { - owner: Option, + core: Option, stats_contract: Option, }, UpdateValidators { @@ -28,9 +40,20 @@ pub enum ExecuteMsg { validator: ValidatorData, }, UpdateValidatorInfo { - validators: Vec, + validators: Vec, }, } +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Config)] + Config {}, + #[returns(ValidatorInfo)] + Validator { valoper: Addr }, + #[returns(Vec)] + Validators {}, +} + #[cw_serde] pub struct MigrateMsg {} diff --git a/contracts/validators-set/src/state.rs b/contracts/validators-set/src/state.rs index 81cde369..a526f53e 100644 --- a/contracts/validators-set/src/state.rs +++ b/contracts/validators-set/src/state.rs @@ -1,11 +1,11 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Decimal}; use cw_storage_plus::{Item, Map}; #[cw_serde] pub struct Config { - pub owner: Addr, + pub core: Addr, pub stats_contract: Addr, } @@ -22,16 +22,5 @@ pub struct ValidatorInfo { pub jailed_number: Option, } -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Config)] - Config {}, - #[returns(ValidatorInfo)] - Validator { valoper: Addr }, - #[returns(Vec)] - Validators {}, -} - pub const CONFIG: Item = Item::new("config"); pub const VALIDATORS_SET: Map = Map::new("validators_set"); diff --git a/contracts/validators-set/src/tests.rs b/contracts/validators-set/src/tests.rs new file mode 100644 index 00000000..ca02f4c4 --- /dev/null +++ b/contracts/validators-set/src/tests.rs @@ -0,0 +1,467 @@ +use cosmwasm_std::{ + attr, + testing::{mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + to_json_binary, Addr, Decimal, Event, OwnedDeps, Querier, +}; +use neutron_sdk::bindings::query::NeutronQuery; +use std::marker::PhantomData; + +fn mock_dependencies() -> OwnedDeps { + OwnedDeps { + storage: MockStorage::default(), + api: MockApi::default(), + querier: Q::default(), + custom_query_type: PhantomData, + } +} + +#[test] +fn instantiate() { + let mut deps = mock_dependencies::(); + let response = crate::contract::instantiate( + deps.as_mut(), + mock_env(), + mock_info("admin", &[]), + crate::msg::InstantiateMsg { + core: "core".to_string(), + stats_contract: "stats_contract".to_string(), + }, + ) + .unwrap(); + + let config = crate::state::CONFIG.load(deps.as_ref().storage).unwrap(); + assert_eq!( + config, + crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + } + ); + + assert_eq!(response.messages.len(), 0); + assert_eq!( + response.events, + vec![ + Event::new("crates.io:lido-neutron-contracts__lido-validators-set-instantiate") + .add_attributes([ + attr("core", "core"), + attr("stats_contract", "stats_contract") + ]) + ] + ); + assert!(response.attributes.is_empty()); +} + +#[test] +fn query_config() { + let mut deps = mock_dependencies::(); + crate::state::CONFIG + .save( + deps.as_mut().storage, + &crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + }, + ) + .unwrap(); + + let response = + crate::contract::query(deps.as_ref(), mock_env(), crate::msg::QueryMsg::Config {}).unwrap(); + assert_eq!( + response, + to_json_binary(&crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract") + }) + .unwrap() + ); +} + +#[test] +fn update_config_wrong_owner() { + let mut deps = mock_dependencies::(); + + crate::state::CONFIG + .save( + deps.as_mut().storage, + &crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + }, + ) + .unwrap(); + + let error = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core1", &[]), + crate::msg::ExecuteMsg::UpdateConfig { + core: Some(Addr::unchecked("owner1")), + stats_contract: Some(Addr::unchecked("stats_contract1")), + }, + ) + .unwrap_err(); + assert_eq!( + error, + crate::error::ContractError::OwnershipError(cw_ownable::OwnershipError::Std( + cosmwasm_std::StdError::NotFound { + kind: "type: cw_ownable::Ownership; key: [6F, 77, 6E, 65, 72, 73, 68, 69, 70]".to_string() + } + )) + ); +} + +#[test] +fn update_config_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()), + ); + + crate::state::CONFIG + .save( + deps.as_mut().storage, + &crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + }, + ) + .unwrap(); + + let response = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core", &[]), + crate::msg::ExecuteMsg::UpdateConfig { + core: Some(Addr::unchecked("owner1")), + stats_contract: Some(Addr::unchecked("stats_contract1")), + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let config = + crate::contract::query(deps.as_ref(), mock_env(), crate::msg::QueryMsg::Config {}).unwrap(); + assert_eq!( + config, + to_json_binary(&crate::state::Config { + core: Addr::unchecked("owner1"), + stats_contract: Addr::unchecked("stats_contract1") + }) + .unwrap() + ); +} + +#[test] +fn update_validator_wrong_owner() { + let mut deps = mock_dependencies::(); + + let error = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core1", &[]), + crate::msg::ExecuteMsg::UpdateValidator { + validator: crate::msg::ValidatorData { + valoper_address: "valoper_address".to_string(), + weight: 1, + }, + }, + ) + .unwrap_err(); + assert_eq!( + error, + crate::error::ContractError::OwnershipError(cw_ownable::OwnershipError::Std( + cosmwasm_std::StdError::NotFound { + kind: "type: cw_ownable::Ownership; key: [6F, 77, 6E, 65, 72, 73, 68, 69, 70]".to_string() + } + )) + ); +} + +#[test] +fn update_validator_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", &[]), + crate::msg::ExecuteMsg::UpdateValidator { + validator: crate::msg::ValidatorData { + valoper_address: "valoper_address".to_string(), + weight: 1, + }, + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let validator = crate::contract::query( + deps.as_ref(), + mock_env(), + crate::msg::QueryMsg::Validator { + valoper: Addr::unchecked("valoper_address"), + }, + ) + .unwrap(); + assert_eq!( + validator, + to_json_binary(&crate::state::ValidatorInfo { + valoper_address: "valoper_address".to_string(), + weight: 1, + 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, + }) + .unwrap() + ); +} + +#[test] +fn update_validators_wrong_owner() { + let mut deps = mock_dependencies::(); + + let error = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core1", &[]), + crate::msg::ExecuteMsg::UpdateValidators { + validators: vec![crate::msg::ValidatorData { + valoper_address: "valoper_address".to_string(), + weight: 1, + }], + }, + ) + .unwrap_err(); + assert_eq!( + error, + crate::error::ContractError::OwnershipError(cw_ownable::OwnershipError::Std( + cosmwasm_std::StdError::NotFound { + kind: "type: cw_ownable::Ownership; key: [6F, 77, 6E, 65, 72, 73, 68, 69, 70]".to_string() + } + )) + ); +} + +#[test] +fn update_validators_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", &[]), + crate::msg::ExecuteMsg::UpdateValidators { + validators: vec![ + crate::msg::ValidatorData { + valoper_address: "valoper_address1".to_string(), + weight: 1, + }, + crate::msg::ValidatorData { + valoper_address: "valoper_address2".to_string(), + weight: 1, + }, + ], + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let validator = crate::contract::query( + deps.as_ref(), + mock_env(), + crate::msg::QueryMsg::Validators {}, + ) + .unwrap(); + assert_eq!( + validator, + to_json_binary(&vec![ + crate::state::ValidatorInfo { + valoper_address: "valoper_address1".to_string(), + weight: 1, + 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, + }, + crate::state::ValidatorInfo { + valoper_address: "valoper_address2".to_string(), + weight: 1, + 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, + } + ]) + .unwrap() + ); +} + +#[test] +fn update_validator_info_wrong_sender() { + 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()), + ); + + crate::state::CONFIG + .save( + deps_mut.storage, + &crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + }, + ) + .unwrap(); + + let _response = crate::contract::execute( + deps_mut, + mock_env(), + mock_info("core", &[]), + crate::msg::ExecuteMsg::UpdateValidator { + validator: crate::msg::ValidatorData { + valoper_address: "valoper_address".to_string(), + weight: 1, + }, + }, + ) + .unwrap(); + + let error = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("stats_contract1", &[]), + crate::msg::ExecuteMsg::UpdateValidatorInfo { + validators: vec![crate::msg::ValidatorInfoUpdate { + valoper_address: "valoper_address".to_string(), + 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, + }], + }, + ) + .unwrap_err(); + assert_eq!(error, crate::error::ContractError::Unauthorized); +} + +#[test] +fn update_validator_info_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()), + ); + + crate::state::CONFIG + .save( + deps_mut.storage, + &crate::state::Config { + core: Addr::unchecked("core"), + stats_contract: Addr::unchecked("stats_contract"), + }, + ) + .unwrap(); + + let response = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("core", &[]), + crate::msg::ExecuteMsg::UpdateValidator { + validator: crate::msg::ValidatorData { + valoper_address: "valoper_address".to_string(), + weight: 1, + }, + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let response = crate::contract::execute( + deps.as_mut(), + mock_env(), + mock_info("stats_contract", &[]), + crate::msg::ExecuteMsg::UpdateValidatorInfo { + validators: vec![crate::msg::ValidatorInfoUpdate { + valoper_address: "valoper_address".to_string(), + last_processed_remote_height: Some(1234), + last_processed_local_height: Some(2345), + last_validated_height: Some(3456), + last_commission_in_range: Some(4567), + uptime: Decimal::one(), + tombstone: true, + jailed_number: Some(5678), + }], + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 0); + + let validator = crate::contract::query( + deps.as_ref(), + mock_env(), + crate::msg::QueryMsg::Validator { + valoper: Addr::unchecked("valoper_address"), + }, + ) + .unwrap(); + + assert_eq!( + validator, + to_json_binary(&crate::state::ValidatorInfo { + valoper_address: "valoper_address".to_string(), + weight: 1, + last_processed_remote_height: Some(1234), + last_processed_local_height: Some(2345), + last_validated_height: Some(3456), + last_commission_in_range: Some(4567), + uptime: Decimal::one(), + tombstone: true, + jailed_number: Some(5678), + }) + .unwrap() + ); +} diff --git a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts index b1ec658d..4a9d5afb 100644 --- a/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts +++ b/integration_tests/src/generated/contractLib/lidoValidatorsSet.ts @@ -1,20 +1,9 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult } from "@cosmjs/cosmwasm-stargate"; import { StdFee } from "@cosmjs/amino"; import { Coin } from "@cosmjs/amino"; -/** - * A human readable address. - * - * In Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length. - * - * This type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances. - * - * This type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance. - */ -export type Addr = string; - export interface InstantiateMsg { - owner: Addr; - stats_contract: Addr; + core: string; + stats_contract: string; } /** * A human readable address. @@ -41,7 +30,7 @@ export interface LidoValidatorsSetSchema { [k: string]: unknown; } export interface Config { - owner: Addr; + core: Addr; stats_contract: Addr; } export interface ValidatorInfo { @@ -70,7 +59,7 @@ export interface ValidatorArgs { valoper: Addr; } export interface UpdateConfigArgs { - owner?: Addr | null; + core?: Addr | null; stats_contract?: Addr | null; } export interface UpdateValidatorsArgs { @@ -84,7 +73,17 @@ export interface UpdateValidatorArgs { validator: ValidatorData; } export interface UpdateValidatorInfoArgs { - validators: ValidatorInfo1[]; + validators: ValidatorInfoUpdate[]; +} +export interface ValidatorInfoUpdate { + jailed_number?: number | null; + last_commission_in_range?: number | null; + last_processed_local_height?: number | null; + last_processed_remote_height?: number | null; + last_validated_height?: number | null; + tombstone: boolean; + uptime: Decimal; + valoper_address: string; } diff --git a/integration_tests/src/testcases/validator-set.test.ts b/integration_tests/src/testcases/validator-set.test.ts index eea28fe7..d0714d7e 100644 --- a/integration_tests/src/testcases/validator-set.test.ts +++ b/integration_tests/src/testcases/validator-set.test.ts @@ -69,7 +69,7 @@ describe('Validator set', () => { account.address, res.codeId, { - owner: account.address, + core: account.address, stats_contract: account.address, }, 'label', @@ -175,7 +175,6 @@ describe('Validator set', () => { validators: [ { valoper_address: 'valoper2', - weight: 2, tombstone: true, uptime: '0.5', jailed_number: 1, @@ -185,8 +184,7 @@ describe('Validator set', () => { last_validated_height: 4567, }, { - valoper_address: 'valoper1', - weight: 1, + valoper_address: 'valoper3', tombstone: false, uptime: '0.96', jailed_number: 3, @@ -213,8 +211,8 @@ describe('Validator set', () => { jailed_number: 1, }, { - valoper_address: 'valoper1', - weight: 1, + valoper_address: 'valoper3', + weight: 3, last_processed_remote_height: null, last_processed_local_height: null, last_validated_height: null, From 43deb125e977fc6f2cddfb7d1ab47aacc32fda42 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Fri, 15 Dec 2023 13:04:34 +0200 Subject: [PATCH 06/13] Update after rebase --- Cargo.lock | 2 +- contracts/validators-set/Cargo.toml | 1 + contracts/validators-set/src/contract.rs | 49 +++++++++++++------ .../src/generated/contractLib/index.ts | 12 ++--- .../src/generated/contractLib/lidoToken.ts | 4 +- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82ea8321..c156eed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -738,7 +738,6 @@ dependencies = [ "serde", "serde-json-wasm 1.0.0", "tendermint-proto", - "thiserror", ] [[package]] @@ -852,6 +851,7 @@ dependencies = [ "cw-storage-plus", "cw2", "cw20", + "lido-staking-base", "neutron-sdk", "prost", "prost-types", diff --git a/contracts/validators-set/Cargo.toml b/contracts/validators-set/Cargo.toml index 3f8ea5de..28bdaea2 100644 --- a/contracts/validators-set/Cargo.toml +++ b/contracts/validators-set/Cargo.toml @@ -41,6 +41,7 @@ serde = { workspace = true } serde-json-wasm = { workspace = true } neutron-sdk = { workspace = true } +lido-staking-base = { workspace = true } [dev-dependencies] cosmwasm-storage = { workspace = true } diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index b75e5053..e89b821a 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -3,6 +3,7 @@ use cosmwasm_std::{ }; use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; +use lido_staking_base::helpers::answer::response; use neutron_sdk::bindings::msg::NeutronMsg; use neutron_sdk::bindings::query::NeutronQuery; @@ -40,6 +41,7 @@ pub fn instantiate( Ok(response( "instantiate", + "lido-validators-set", [attr("core", core), attr("stats_contract", stats_contract)], )) } @@ -92,7 +94,7 @@ pub fn execute( execute_update_validator(deps, info, validator) } ExecuteMsg::UpdateValidatorInfo { validators } => { - execute_update_validator_info(deps, info, validators) + execute_update_validators_info(deps, info, validators) } } } @@ -118,7 +120,14 @@ fn execute_update_config( CONFIG.save(deps.storage, &state)?; - Ok(Response::default()) + Ok(response( + "update_config", + "lido-validators-set", + [ + attr("core", state.core), + attr("stats_contract", state.stats_contract), + ], + )) } fn execute_update_validator( @@ -132,7 +141,7 @@ fn execute_update_validator( VALIDATORS_SET.save( deps.storage, - valoper_address, + valoper_address.clone(), &ValidatorInfo { valoper_address: validator.valoper_address, weight: validator.weight, @@ -146,7 +155,14 @@ fn execute_update_validator( }, )?; - Ok(Response::default()) + Ok(response( + "update_validator", + "lido-validators-set", + [ + attr("address", valoper_address), + attr("weight", validator.weight.to_string()), + ], + )) } fn execute_update_validators( @@ -159,7 +175,7 @@ fn execute_update_validators( // TODO: implement notification of the validator stats contract about new validators set VALIDATORS_SET.clear(deps.storage); - for validator in validators { + for validator in validators.clone() { let valoper_address = validator.valoper_address.clone(); VALIDATORS_SET.save( @@ -179,10 +195,14 @@ fn execute_update_validators( )?; } - Ok(Response::default()) + Ok(response( + "update_validators", + "lido-validators-set", + [attr("total_count", validators.len().to_string())], + )) } -fn execute_update_validator_info( +fn execute_update_validators_info( deps: DepsMut, info: MessageInfo, validators_update: Vec, @@ -194,7 +214,7 @@ fn execute_update_validator_info( ContractError::Unauthorized {} ); - for update in validators_update { + for update in validators_update.clone() { // TODO: Implement logic to modify validator set based in incoming validator info let validator = VALIDATORS_SET.may_load(deps.storage, update.valoper_address.to_string())?; @@ -225,7 +245,11 @@ fn execute_update_validator_info( VALIDATORS_SET.save(deps.storage, validator.valoper_address.clone(), &validator)?; } - Ok(Response::default()) + Ok(response( + "update_validators_info", + "lido-validators-set", + [attr("total_count", validators_update.len().to_string())], + )) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -233,10 +257,3 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> StdResult>( - ty: &str, - attrs: impl IntoIterator, -) -> Response { - Response::new().add_event(Event::new(format!("{}-{}", CONTRACT_NAME, ty)).add_attributes(attrs)) -} diff --git a/integration_tests/src/generated/contractLib/index.ts b/integration_tests/src/generated/contractLib/index.ts index 0808a0a4..8072cc2d 100644 --- a/integration_tests/src/generated/contractLib/index.ts +++ b/integration_tests/src/generated/contractLib/index.ts @@ -1,11 +1,11 @@ -import * as _0 from './lidoCore'; -export const LidoCore = _0; +import * as _0 from './lidoDistribution'; +export const LidoDistribution = _0; -import * as _1 from './lidoDistribution'; -export const LidoDistribution = _1; +import * as _1 from './lidoInterchainInterceptorAuthz'; +export const LidoInterchainInterceptorAuthz = _1; -import * as _2 from './lidoFactory'; -export const LidoFactory = _2; +import * as _2 from './lidoInterchainInterceptor'; +export const LidoInterchainInterceptor = _2; import * as _3 from './lidoStargatePoc'; export const LidoStargatePoc = _3; diff --git a/integration_tests/src/generated/contractLib/lidoToken.ts b/integration_tests/src/generated/contractLib/lidoToken.ts index fe58f8a3..0f79eea3 100644 --- a/integration_tests/src/generated/contractLib/lidoToken.ts +++ b/integration_tests/src/generated/contractLib/lidoToken.ts @@ -2,7 +2,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult import { StdFee } from "@cosmjs/amino"; import { Coin } from "@cosmjs/amino"; export interface InstantiateMsg { - core_address: string; + core: string; subdenom: string; } /** @@ -26,7 +26,7 @@ export interface LidoTokenSchema { [k: string]: unknown; } export interface ConfigResponse { - core_address: string; + core: string; denom: string; } export interface MintArgs { From f7c846ca2dfd95b43917c614f1d2b056710114a6 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Fri, 15 Dec 2023 13:28:36 +0200 Subject: [PATCH 07/13] fix tests --- contracts/validators-set/src/contract.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index e89b821a..b264110e 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -1,6 +1,4 @@ -use cosmwasm_std::{ - attr, ensure_eq, entry_point, to_json_binary, Addr, Attribute, Deps, Event, Order, -}; +use cosmwasm_std::{attr, ensure_eq, entry_point, to_json_binary, Addr, Deps, Order}; use cosmwasm_std::{Binary, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; use lido_staking_base::helpers::answer::response; @@ -41,7 +39,7 @@ pub fn instantiate( Ok(response( "instantiate", - "lido-validators-set", + CONTRACT_NAME, [attr("core", core), attr("stats_contract", stats_contract)], )) } @@ -122,7 +120,7 @@ fn execute_update_config( Ok(response( "update_config", - "lido-validators-set", + CONTRACT_NAME, [ attr("core", state.core), attr("stats_contract", state.stats_contract), @@ -157,7 +155,7 @@ fn execute_update_validator( Ok(response( "update_validator", - "lido-validators-set", + CONTRACT_NAME, [ attr("address", valoper_address), attr("weight", validator.weight.to_string()), @@ -197,7 +195,7 @@ fn execute_update_validators( Ok(response( "update_validators", - "lido-validators-set", + CONTRACT_NAME, [attr("total_count", validators.len().to_string())], )) } @@ -247,7 +245,7 @@ fn execute_update_validators_info( Ok(response( "update_validators_info", - "lido-validators-set", + CONTRACT_NAME, [attr("total_count", validators_update.len().to_string())], )) } From 0d56e97e3c072976e191a6c330ae3cc9036ef744 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Fri, 15 Dec 2023 14:25:14 +0200 Subject: [PATCH 08/13] update ts client --- .../src/generated/contractLib/index.ts | 38 +++++++++++-------- .../src/generated/contractLib/lidoToken.ts | 4 +- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/integration_tests/src/generated/contractLib/index.ts b/integration_tests/src/generated/contractLib/index.ts index 8072cc2d..2e9dd14d 100644 --- a/integration_tests/src/generated/contractLib/index.ts +++ b/integration_tests/src/generated/contractLib/index.ts @@ -1,23 +1,29 @@ -import * as _0 from './lidoDistribution'; -export const LidoDistribution = _0; +import * as _0 from './lidoCore'; +export const LidoCore = _0; -import * as _1 from './lidoInterchainInterceptorAuthz'; -export const LidoInterchainInterceptorAuthz = _1; +import * as _1 from './lidoDistribution'; +export const LidoDistribution = _1; -import * as _2 from './lidoInterchainInterceptor'; -export const LidoInterchainInterceptor = _2; +import * as _2 from './lidoFactory'; +export const LidoFactory = _2; -import * as _3 from './lidoStargatePoc'; -export const LidoStargatePoc = _3; +import * as _3 from './lidoInterchainInterceptorAuthz'; +export const LidoInterchainInterceptorAuthz = _3; -import * as _4 from './lidoStrategy'; -export const LidoStrategy = _4; +import * as _4 from './lidoInterchainInterceptor'; +export const LidoInterchainInterceptor = _4; -import * as _5 from './lidoToken'; -export const LidoToken = _5; +import * as _5 from './lidoStargatePoc'; +export const LidoStargatePoc = _5; -import * as _6 from './lidoValidatorsSet'; -export const LidoValidatorsSet = _6; +import * as _6 from './lidoStrategy'; +export const LidoStrategy = _6; -import * as _7 from './lidoValidatorsStats'; -export const LidoValidatorsStats = _7; +import * as _7 from './lidoToken'; +export const LidoToken = _7; + +import * as _8 from './lidoValidatorsSet'; +export const LidoValidatorsSet = _8; + +import * as _9 from './lidoValidatorsStats'; +export const LidoValidatorsStats = _9; diff --git a/integration_tests/src/generated/contractLib/lidoToken.ts b/integration_tests/src/generated/contractLib/lidoToken.ts index 0f79eea3..fe58f8a3 100644 --- a/integration_tests/src/generated/contractLib/lidoToken.ts +++ b/integration_tests/src/generated/contractLib/lidoToken.ts @@ -2,7 +2,7 @@ import { CosmWasmClient, SigningCosmWasmClient, ExecuteResult, InstantiateResult import { StdFee } from "@cosmjs/amino"; import { Coin } from "@cosmjs/amino"; export interface InstantiateMsg { - core: string; + core_address: string; subdenom: string; } /** @@ -26,7 +26,7 @@ export interface LidoTokenSchema { [k: string]: unknown; } export interface ConfigResponse { - core: string; + core_address: string; denom: string; } export interface MintArgs { From 6967bb712d9eefb12f57872e075c5546289d319c Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Mon, 18 Dec 2023 13:05:43 +0200 Subject: [PATCH 09/13] Initiate pipeline restart --- contracts/validators-set/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/validators-set/README.md b/contracts/validators-set/README.md index ab38c1ea..655a7026 100644 --- a/contracts/validators-set/README.md +++ b/contracts/validators-set/README.md @@ -1 +1 @@ -# LIDO Validators set \ No newline at end of file +# LIDO Validators set contract \ No newline at end of file From 50e9bab77ba1a8ba35b5074855c1644a52e14740 Mon Sep 17 00:00:00 2001 From: Murad Karammaev Date: Wed, 20 Dec 2023 21:43:31 +0200 Subject: [PATCH 10/13] chore: simplify update config --- contracts/validators-set/src/contract.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index b264110e..71a18157 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -107,13 +107,17 @@ fn execute_update_config( let mut state = CONFIG.load(deps.storage)?; - if owner.is_some() && owner != Some(state.clone().core) { - state.core = owner.unwrap_or(state.core); - cw_ownable::initialize_owner(deps.storage, deps.api, Some(state.core.as_ref()))?; + if let Some(owner) = owner { + if owner != state.core { + state.core = owner; + cw_ownable::initialize_owner(deps.storage, deps.api, Some(state.core.as_ref()))?; + } } - if stats_contract.is_some() && stats_contract != Some(state.clone().stats_contract) { - state.stats_contract = stats_contract.unwrap_or(state.stats_contract); + if let Some(stats_contract) = stats_contract { + if stats_contract != state.stats_contract { + state.stats_contract = stats_contract; + } } CONFIG.save(deps.storage, &state)?; From ef1afbf08bfeaa62db0e45283f5ee28a862ed1c4 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Wed, 20 Dec 2023 22:44:57 +0200 Subject: [PATCH 11/13] review fixes --- contracts/validators-set/src/contract.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/validators-set/src/contract.rs b/contracts/validators-set/src/contract.rs index b264110e..2975c51c 100644 --- a/contracts/validators-set/src/contract.rs +++ b/contracts/validators-set/src/contract.rs @@ -170,10 +170,12 @@ fn execute_update_validators( ) -> ContractResult> { cw_ownable::assert_owner(deps.storage, &info.sender)?; + let total_count = validators.len(); + // TODO: implement notification of the validator stats contract about new validators set VALIDATORS_SET.clear(deps.storage); - for validator in validators.clone() { + for validator in validators { let valoper_address = validator.valoper_address.clone(); VALIDATORS_SET.save( @@ -196,7 +198,7 @@ fn execute_update_validators( Ok(response( "update_validators", CONTRACT_NAME, - [attr("total_count", validators.len().to_string())], + [attr("total_count", total_count.to_string())], )) } @@ -212,7 +214,9 @@ fn execute_update_validators_info( ContractError::Unauthorized {} ); - for update in validators_update.clone() { + let total_count = validators_update.len(); + + for update in validators_update { // TODO: Implement logic to modify validator set based in incoming validator info let validator = VALIDATORS_SET.may_load(deps.storage, update.valoper_address.to_string())?; @@ -246,7 +250,7 @@ fn execute_update_validators_info( Ok(response( "update_validators_info", CONTRACT_NAME, - [attr("total_count", validators_update.len().to_string())], + [attr("total_count", total_count.to_string())], )) } From 4de8cc9f4c7a93e148deaaa614cac45221edd776 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Fri, 22 Dec 2023 00:34:07 +0200 Subject: [PATCH 12/13] remove setupSingle --- integration_tests/src/testSuite.ts | 34 ------------------- .../src/testcases/validator-set.test.ts | 5 ++- 2 files changed, 2 insertions(+), 37 deletions(-) diff --git a/integration_tests/src/testSuite.ts b/integration_tests/src/testSuite.ts index edb7291a..dbca13db 100644 --- a/integration_tests/src/testSuite.ts +++ b/integration_tests/src/testSuite.ts @@ -273,37 +273,3 @@ export const setupPark = async ( } return instance; }; - -export const setupSingle = async ( - context = 'lido', - network: string = 'neutron', -): Promise => { - const wallets = await generateWallets(); - const config: CosmoparkConfig = { - context, - networks: {}, - master_mnemonic: wallets.master, - loglevel: 'info', - wallets: { - demowallet1: { - mnemonic: wallets.demowallet1, - balance: '1000000000', - }, - demo1: { mnemonic: wallets.demo1, balance: '1000000000' }, - demo2: { mnemonic: wallets.demo2, balance: '1000000000' }, - demo3: { mnemonic: wallets.demo3, balance: '1000000000' }, - }, - }; - config.networks[network] = networkConfigs[network]; - const instance = await cosmopark.create(config); - await Promise.all( - Object.entries(instance.ports).map(([network, ports]) => - awaitFirstBlock(`127.0.0.1:${ports.rpc}`).catch((e) => { - console.log(`Failed to await first block for ${network}: ${e}`); - throw e; - }), - ), - ); - console.log('Awaited first blocks'); - return instance; -}; diff --git a/integration_tests/src/testcases/validator-set.test.ts b/integration_tests/src/testcases/validator-set.test.ts index d0714d7e..bf5f45af 100644 --- a/integration_tests/src/testcases/validator-set.test.ts +++ b/integration_tests/src/testcases/validator-set.test.ts @@ -7,7 +7,7 @@ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { Client as NeutronClient } from '@neutron-org/client-ts'; import { AccountData, DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; import { GasPrice } from '@cosmjs/stargate'; -import { setupSingle } from '../testSuite'; +import { setupPark, setupSingle } from '../testSuite'; import fs from 'fs'; import Cosmopark from '@neutron-org/cosmopark'; @@ -25,8 +25,7 @@ describe('Validator set', () => { } = {}; beforeAll(async () => { - context.park = await setupSingle('validatorset', 'neutron'); - + context.park = await setupPark('validatorset', ['neutron'], false); context.wallet = await DirectSecp256k1HdWallet.fromMnemonic( context.park.config.wallets.demowallet1.mnemonic, { From 54f922d6cd33234e32895c77f7824c1c4460fc71 Mon Sep 17 00:00:00 2001 From: Albert Andrejev Date: Fri, 22 Dec 2023 10:43:12 +0200 Subject: [PATCH 13/13] remove not used import --- integration_tests/src/testcases/validator-set.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_tests/src/testcases/validator-set.test.ts b/integration_tests/src/testcases/validator-set.test.ts index bf5f45af..9c50c458 100644 --- a/integration_tests/src/testcases/validator-set.test.ts +++ b/integration_tests/src/testcases/validator-set.test.ts @@ -7,7 +7,7 @@ import { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { Client as NeutronClient } from '@neutron-org/client-ts'; import { AccountData, DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; import { GasPrice } from '@cosmjs/stargate'; -import { setupPark, setupSingle } from '../testSuite'; +import { setupPark } from '../testSuite'; import fs from 'fs'; import Cosmopark from '@neutron-org/cosmopark';