diff --git a/Cargo.lock b/Cargo.lock index 145af77c8..4f20214c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -268,7 +268,7 @@ dependencies = [ "serde", "serde_json", "serde_with 3.8.1", - "service-registry", + "service-registry-api", "sha3", "stellar", "stellar-rs", @@ -4058,6 +4058,7 @@ dependencies = [ "serde", "serde_json", "service-registry", + "service-registry-api", "sha3", "tofn", "voting-verifier", @@ -5038,6 +5039,7 @@ dependencies = [ "router-api", "serde_json", "service-registry", + "service-registry-api", "sha3", "stellar", "stellar-xdr", @@ -7515,6 +7517,25 @@ dependencies = [ "router-api", "schemars", "serde", + "service-registry-api", + "thiserror", +] + +[[package]] +name = "service-registry-api" +version = "1.0.0" +dependencies = [ + "axelar-wasm-std", + "cosmwasm-schema", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "error-stack", + "goldie", + "msgs-derive", + "report", + "router-api", + "schemars", + "serde", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 85bb8bcf6..70a0af2dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } serde_json = "1.0.89" service-registry = { version = "^1.0.0", path = "contracts/service-registry" } +service-registry-api = { version = "^1.0.0", path = "packages/service-registry-api" } sha3 = { version = "0.10.8", default-features = false, features = [] } signature-verifier-api = { version = "^1.0.0", path = "packages/signature-verifier-api" } stellar = { version = "^1.0.0", path = "external-gateways/stellar" } diff --git a/ampd/Cargo.toml b/ampd/Cargo.toml index 738e89a87..7db154727 100644 --- a/ampd/Cargo.toml +++ b/ampd/Cargo.toml @@ -48,7 +48,7 @@ router-api = { workspace = true } serde = { version = "1.0.147", features = ["derive"] } serde_json = { workspace = true } serde_with = "3.2.0" -service-registry = { workspace = true } +service-registry-api = { workspace = true } sha3 = { workspace = true } stellar = { workspace = true } stellar-rs = "0.3.2" diff --git a/ampd/src/commands/bond_verifier.rs b/ampd/src/commands/bond_verifier.rs index 7703df219..df9b52c29 100644 --- a/ampd/src/commands/bond_verifier.rs +++ b/ampd/src/commands/bond_verifier.rs @@ -4,7 +4,7 @@ use cosmrs::tx::Msg; use cosmrs::Coin; use error_stack::Result; use report::ResultCompatExt; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use valuable::Valuable; use crate::commands::{broadcast_tx, verifier_pub_key}; diff --git a/ampd/src/commands/claim_stake.rs b/ampd/src/commands/claim_stake.rs index 918c1e90a..0a6c033fd 100644 --- a/ampd/src/commands/claim_stake.rs +++ b/ampd/src/commands/claim_stake.rs @@ -3,7 +3,7 @@ use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use error_stack::Result; use report::ResultCompatExt; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use valuable::Valuable; use crate::commands::{broadcast_tx, verifier_pub_key}; diff --git a/ampd/src/commands/deregister_chain_support.rs b/ampd/src/commands/deregister_chain_support.rs index c212125f3..9deffe15e 100644 --- a/ampd/src/commands/deregister_chain_support.rs +++ b/ampd/src/commands/deregister_chain_support.rs @@ -4,7 +4,7 @@ use cosmrs::tx::Msg; use error_stack::Result; use report::ResultCompatExt; use router_api::ChainName; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use valuable::Valuable; use crate::commands::{broadcast_tx, verifier_pub_key}; diff --git a/ampd/src/commands/register_chain_support.rs b/ampd/src/commands/register_chain_support.rs index 68c3d8070..9baeea8c3 100644 --- a/ampd/src/commands/register_chain_support.rs +++ b/ampd/src/commands/register_chain_support.rs @@ -4,7 +4,7 @@ use cosmrs::tx::Msg; use error_stack::Result; use report::ResultCompatExt; use router_api::ChainName; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use valuable::Valuable; use crate::commands::{broadcast_tx, verifier_pub_key}; diff --git a/ampd/src/commands/unbond_verifier.rs b/ampd/src/commands/unbond_verifier.rs index f17f4919f..098e973ed 100644 --- a/ampd/src/commands/unbond_verifier.rs +++ b/ampd/src/commands/unbond_verifier.rs @@ -3,7 +3,7 @@ use cosmrs::cosmwasm::MsgExecuteContract; use cosmrs::tx::Msg; use error_stack::Result; use report::ResultCompatExt; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use valuable::Valuable; use crate::commands::{broadcast_tx, verifier_pub_key}; diff --git a/contracts/multisig-prover/Cargo.toml b/contracts/multisig-prover/Cargo.toml index c96ab2b94..98752d716 100644 --- a/contracts/multisig-prover/Cargo.toml +++ b/contracts/multisig-prover/Cargo.toml @@ -56,6 +56,7 @@ report = { workspace = true } router-api = { workspace = true } serde_json = "1.0.89" service-registry = { workspace = true } +service-registry-api = { workspace = true } sha3 = { workspace = true } stellar = { workspace = true } stellar-xdr = { workspace = true } diff --git a/contracts/multisig-prover/src/contract/execute.rs b/contracts/multisig-prover/src/contract/execute.rs index a32a6ec04..bead6b5c2 100644 --- a/contracts/multisig-prover/src/contract/execute.rs +++ b/contracts/multisig-prover/src/contract/execute.rs @@ -11,7 +11,7 @@ use itertools::Itertools; use multisig::msg::Signer; use multisig::verifier_set::VerifierSet; use router_api::{ChainName, CrossChainId, Message}; -use service_registry::WeightedVerifier; +use service_registry_api::WeightedVerifier; use crate::contract::START_MULTISIG_REPLY_ID; use crate::error::ContractError; diff --git a/contracts/multisig-prover/src/error.rs b/contracts/multisig-prover/src/error.rs index bdc8476d9..f0f946a60 100644 --- a/contracts/multisig-prover/src/error.rs +++ b/contracts/multisig-prover/src/error.rs @@ -31,7 +31,7 @@ pub enum ContractError { PublicKeyNotFound { participant: String }, #[error(transparent)] - ServiceRegistryError(#[from] service_registry::ContractError), + ServiceRegistryError(#[from] service_registry_api::error::ContractError), #[error(transparent)] NonEmptyError(#[from] nonempty::Error), diff --git a/contracts/multisig-prover/src/test/test_utils.rs b/contracts/multisig-prover/src/test/test_utils.rs index a177365dc..00afbb574 100644 --- a/contracts/multisig-prover/src/test/test_utils.rs +++ b/contracts/multisig-prover/src/test/test_utils.rs @@ -4,9 +4,8 @@ use multisig::msg::Signer; use multisig::multisig::Multisig; use multisig::types::MultisigState; use multisig::verifier_set::VerifierSet; -use service_registry::{ - AuthorizationState, BondingState, Verifier, WeightedVerifier, VERIFIER_WEIGHT, -}; +use service_registry::VERIFIER_WEIGHT; +use service_registry_api::{AuthorizationState, BondingState, Verifier, WeightedVerifier}; use super::test_data::{self, TestOperator}; @@ -113,12 +112,12 @@ fn mock_multisig(operators: Vec) -> Multisig { } fn service_registry_mock_querier_handler( - msg: service_registry::msg::QueryMsg, + msg: service_registry_api::msg::QueryMsg, operators: Vec, ) -> QuerierResult { let result = match msg { - service_registry::msg::QueryMsg::Service { service_name } => { - to_json_binary(&service_registry::Service { + service_registry_api::msg::QueryMsg::Service { service_name } => { + to_json_binary(&service_registry_api::Service { name: service_name.to_string(), coordinator_contract: Addr::unchecked(COORDINATOR_ADDRESS), min_num_verifiers: 1, @@ -129,7 +128,7 @@ fn service_registry_mock_querier_handler( description: "verifiers".to_string(), }) } - service_registry::msg::QueryMsg::ActiveVerifiers { + service_registry_api::msg::QueryMsg::ActiveVerifiers { service_name: _, chain_name: _, } => to_json_binary( diff --git a/contracts/rewards/src/contract/migrations/v1_0_0.rs b/contracts/rewards/src/contract/migrations/v1_0_0.rs index 3c7f9416a..4075cc492 100644 --- a/contracts/rewards/src/contract/migrations/v1_0_0.rs +++ b/contracts/rewards/src/contract/migrations/v1_0_0.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::marker::PhantomData; -use std::u64; use axelar_wasm_std::error::ContractError; use cosmwasm_schema::cw_serde; diff --git a/contracts/service-registry/Cargo.toml b/contracts/service-registry/Cargo.toml index 7045cff22..125586fbf 100644 --- a/contracts/service-registry/Cargo.toml +++ b/contracts/service-registry/Cargo.toml @@ -46,6 +46,7 @@ report = { workspace = true } router-api = { workspace = true } schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } +service-registry-api = { workspace = true } thiserror = { workspace = true } [dev-dependencies] diff --git a/contracts/service-registry/src/bin/schema.rs b/contracts/service-registry/src/bin/schema.rs index 85da9dcac..54bb78415 100644 --- a/contracts/service-registry/src/bin/schema.rs +++ b/contracts/service-registry/src/bin/schema.rs @@ -1,5 +1,6 @@ use cosmwasm_schema::write_api; -use service_registry::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use service_registry::msg::InstantiateMsg; +use service_registry_api::msg::{ExecuteMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/service-registry/src/client.rs b/contracts/service-registry/src/client.rs index 56f14277d..1fd604ad3 100644 --- a/contracts/service-registry/src/client.rs +++ b/contracts/service-registry/src/client.rs @@ -1,7 +1,7 @@ use error_stack::ResultExt; use router_api::ChainName; +use service_registry_api::msg::{ExecuteMsg, QueryMsg}; -use crate::msg::{ExecuteMsg, QueryMsg}; use crate::{Service, Verifier, WeightedVerifier}; type Result = error_stack::Result; @@ -92,9 +92,9 @@ mod test { use cosmwasm_std::testing::MockQuerier; use cosmwasm_std::{from_json, to_json_binary, Addr, QuerierWrapper, SystemError, WasmQuery}; use router_api::ChainName; + use service_registry_api::msg::QueryMsg; use crate::client::Client; - use crate::msg::QueryMsg; use crate::{Service, Verifier, WeightedVerifier}; #[test] diff --git a/contracts/service-registry/src/contract.rs b/contracts/service-registry/src/contract.rs index 93b5d6c0b..c1b0cb1bd 100644 --- a/contracts/service-registry/src/contract.rs +++ b/contracts/service-registry/src/contract.rs @@ -6,10 +6,11 @@ use cosmwasm_std::{ Storage, }; use error_stack::{bail, Report, ResultExt}; +use service_registry_api::error::ContractError; +use service_registry_api::{AuthorizationState, BondingState, Service}; -use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{AuthorizationState, BondingState, Service, SERVICES, VERIFIERS}; +use crate::state::{SERVICES, VERIFIERS}; mod execute; mod migrations; @@ -201,10 +202,11 @@ mod test { coins, from_json, CosmosMsg, Empty, OwnedDeps, StdResult, Uint128, WasmQuery, }; use router_api::ChainName; + use service_registry_api::msg::{UpdatedServiceParams, VerifierDetails}; + use service_registry_api::{Verifier, WeightedVerifier}; use super::*; - use crate::msg::{UpdatedServiceParams, VerifierDetails}; - use crate::state::{Verifier, WeightedVerifier, VERIFIER_WEIGHT}; + use crate::state::VERIFIER_WEIGHT; const GOVERNANCE_ADDRESS: &str = "governance"; const UNAUTHORIZED_ADDRESS: &str = "unauthorized"; diff --git a/contracts/service-registry/src/contract/execute.rs b/contracts/service-registry/src/contract/execute.rs index 057f3e380..34a0e6344 100644 --- a/contracts/service-registry/src/contract/execute.rs +++ b/contracts/service-registry/src/contract/execute.rs @@ -1,10 +1,12 @@ use axelar_wasm_std::nonempty; use error_stack::Result; use router_api::ChainName; +use service_registry_api::{self, AuthorizationState, Verifier}; +use state::VERIFIERS; use super::*; use crate::msg::UpdatedServiceParams; -use crate::state::{self, AuthorizationState, Verifier, VERIFIERS}; +use crate::state::{self}; #[allow(clippy::too_many_arguments)] pub fn register_service( @@ -134,7 +136,7 @@ pub fn bond_verifier( (&service_name.clone(), &info.sender.clone()), |sw| -> std::result::Result { match sw { - Some(verifier) => Ok(verifier.bond(bond)?), + Some(verifier) => Ok(state::bond_verifier(verifier, bond)?), None => Ok(Verifier { address: info.sender, bonding_state: BondingState::Bonded { @@ -210,7 +212,7 @@ pub fn unbond_verifier( .ready_to_unbond(verifier.address.to_string()) .change_context(ContractError::FailedToUnbondVerifier)?; - let verifier = verifier.unbond(ready_to_unbond, env.block.time)?; + let verifier = state::unbond_verifier(verifier, ready_to_unbond, env.block.time)?; VERIFIERS .save(deps.storage, (&service_name, &info.sender), &verifier) @@ -235,8 +237,11 @@ pub fn claim_stake( .change_context(ContractError::StorageError)? .ok_or(ContractError::VerifierNotFound)?; - let (verifier, released_bond) = - verifier.claim_stake(env.block.time, service.unbonding_period_days as u64)?; + let (verifier, released_bond) = state::claim_verifier_stake( + verifier, + env.block.time, + service.unbonding_period_days as u64, + )?; VERIFIERS .save(deps.storage, (&service_name, &info.sender), &verifier) diff --git a/contracts/service-registry/src/contract/query.rs b/contracts/service-registry/src/contract/query.rs index 6f5127107..080e2b8e3 100644 --- a/contracts/service-registry/src/contract/query.rs +++ b/contracts/service-registry/src/contract/query.rs @@ -1,10 +1,12 @@ -use cosmwasm_std::Order; +use axelar_wasm_std::address; +use cosmwasm_std::{Deps, Order}; use itertools::Itertools; use router_api::ChainName; +use service_registry_api::error::ContractError; +use service_registry_api::*; -use super::*; use crate::msg::VerifierDetails; -use crate::state::{WeightedVerifier, VERIFIERS, VERIFIERS_PER_CHAIN, VERIFIER_WEIGHT}; +use crate::state::{SERVICES, VERIFIERS, VERIFIERS_PER_CHAIN, VERIFIER_WEIGHT}; pub fn active_verifiers( deps: Deps, diff --git a/contracts/service-registry/src/helpers.rs b/contracts/service-registry/src/helpers.rs index 8ba4bfa8f..9d94499e6 100644 --- a/contracts/service-registry/src/helpers.rs +++ b/contracts/service-registry/src/helpers.rs @@ -1,8 +1,7 @@ use cosmwasm_std::{to_json_binary, Addr, CosmosMsg, StdResult, WasmMsg}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; - -use crate::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; /// ServiceRegistry is a wrapper around Addr that provides a lot of helpers /// for working with this. diff --git a/contracts/service-registry/src/lib.rs b/contracts/service-registry/src/lib.rs index 3a3417461..03cdfe39e 100644 --- a/contracts/service-registry/src/lib.rs +++ b/contracts/service-registry/src/lib.rs @@ -1,13 +1,11 @@ mod client; pub use client::Client; pub mod contract; -pub mod error; pub mod helpers; pub mod msg; mod state; -pub use state::{ - AuthorizationState, BondingState, Service, Verifier, WeightedVerifier, VERIFIER_WEIGHT, +pub use service_registry_api::{ + AuthorizationState, BondingState, Service, Verifier, WeightedVerifier, }; - -pub use crate::error::ContractError; +pub use state::VERIFIER_WEIGHT; diff --git a/contracts/service-registry/src/msg.rs b/contracts/service-registry/src/msg.rs index 331e0717e..8728ffb36 100644 --- a/contracts/service-registry/src/msg.rs +++ b/contracts/service-registry/src/msg.rs @@ -1,119 +1,15 @@ -use axelar_wasm_std::nonempty; -use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_schema::cw_serde; use cosmwasm_std::Addr; -use msgs_derive::EnsurePermissions; -use router_api::ChainName; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::Verifier; #[cw_serde] pub struct InstantiateMsg { pub governance_account: String, } -#[cw_serde] -#[derive(EnsurePermissions)] -pub enum ExecuteMsg { - /// Can only be called by governance account - #[permission(Governance)] - RegisterService { - service_name: String, - coordinator_contract: String, - min_num_verifiers: u16, - max_num_verifiers: Option, - min_verifier_bond: nonempty::Uint128, - bond_denom: String, - unbonding_period_days: u16, // number of days to wait after starting unbonding before allowed to claim stake - description: String, - }, - /// Updates modifiable fields of the service. Note, not all fields are modifiable. - #[permission(Governance)] - UpdateService { - service_name: String, - updated_service_params: UpdatedServiceParams, - }, - /// Authorizes verifiers to join a service. Can only be called by governance account. Verifiers must still bond sufficient stake to participate. - #[permission(Governance)] - AuthorizeVerifiers { - verifiers: Vec, - service_name: String, - }, - /// Revoke authorization for specified verifiers. Can only be called by governance account. Verifiers bond remains unchanged - #[permission(Governance)] - UnauthorizeVerifiers { - verifiers: Vec, - service_name: String, - }, - /// Jail verifiers. Can only be called by governance account. Jailed verifiers are not allowed to unbond or claim stake. - #[permission(Governance)] - JailVerifiers { - verifiers: Vec, - service_name: String, - }, - - /// Register support for the specified chains. Called by the verifier. - #[permission(Specific(verifier))] - RegisterChainSupport { - service_name: String, - chains: Vec, - }, - /// Deregister support for the specified chains. Called by the verifier. - #[permission(Specific(verifier))] - DeregisterChainSupport { - service_name: String, - chains: Vec, - }, - - /// Locks up any funds sent with the message as stake. Marks the sender as a potential verifier that can be authorized. - #[permission(Any)] - BondVerifier { service_name: String }, - /// Initiates unbonding of staked funds for the sender. - #[permission(Any)] - UnbondVerifier { service_name: String }, - /// Claim previously staked funds that have finished unbonding for the sender. - #[permission(Any)] - ClaimStake { service_name: String }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Vec)] - ActiveVerifiers { - service_name: String, - chain_name: ChainName, - }, - - #[returns(crate::state::Service)] - Service { service_name: String }, - - #[returns(VerifierDetails)] - Verifier { - service_name: String, - verifier: String, - }, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct VerifierDetails { - pub verifier: Verifier, - pub weight: nonempty::Uint128, - pub supported_chains: Vec, -} - #[cw_serde] pub struct MigrateMsg { pub coordinator_contract: Addr, } -// Represents any modifiable fields of the Service struct -// Any non-None field overwrites the value currently stored in the Service object -#[cw_serde] -pub struct UpdatedServiceParams { - pub min_num_verifiers: Option, - pub max_num_verifiers: Option>, - pub min_verifier_bond: Option, - pub unbonding_period_days: Option, -} +// these messages and structs are extracted into a separate package to avoid circular dependencies +pub use service_registry_api::msg::{ExecuteMsg, QueryMsg, UpdatedServiceParams, VerifierDetails}; diff --git a/contracts/service-registry/src/state.rs b/contracts/service-registry/src/state.rs index d824dfb3b..d66cdaa13 100644 --- a/contracts/service-registry/src/state.rs +++ b/contracts/service-registry/src/state.rs @@ -1,147 +1,12 @@ use axelar_wasm_std::nonempty; -use axelar_wasm_std::snapshot::Participant; -use cosmwasm_schema::cw_serde; use cosmwasm_std::{Addr, Storage, Timestamp, Uint128}; use cw_storage_plus::{Index, IndexList, IndexedMap, KeyDeserialize, Map, MultiIndex}; use router_api::ChainName; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -use crate::ContractError; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Service { - pub name: String, - pub coordinator_contract: Addr, - pub min_num_verifiers: u16, - pub max_num_verifiers: Option, - pub min_verifier_bond: nonempty::Uint128, - pub bond_denom: String, - // should be set to a duration longer than the voting period for governance proposals, - // otherwise a verifier could bail before they get penalized - pub unbonding_period_days: u16, - pub description: String, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub struct Verifier { - pub address: Addr, - pub bonding_state: BondingState, - pub authorization_state: AuthorizationState, - pub service_name: String, -} - -impl Verifier { - pub fn bond(self, to_add: Option) -> Result { - let amount: nonempty::Uint128 = match self.bonding_state { - BondingState::Bonded { amount } - | BondingState::RequestedUnbonding { amount } - | BondingState::Unbonding { - amount, - unbonded_at: _, - } => amount - .into_inner() - .checked_add(to_add.map(Uint128::from).unwrap_or(Uint128::zero())) - .map_err(ContractError::Overflow)? - .try_into()?, - BondingState::Unbonded => to_add.ok_or(ContractError::NoFundsToBond)?, - }; - - Ok(Self { - bonding_state: BondingState::Bonded { amount }, - ..self - }) - } - - pub fn unbond(self, can_unbond: bool, time: Timestamp) -> Result { - if self.authorization_state == AuthorizationState::Jailed { - return Err(ContractError::VerifierJailed); - } - - let bonding_state = match self.bonding_state { - BondingState::Bonded { amount } | BondingState::RequestedUnbonding { amount } => { - if can_unbond { - BondingState::Unbonding { - unbonded_at: time, - amount, - } - } else { - BondingState::RequestedUnbonding { amount } - } - } - _ => return Err(ContractError::InvalidBondingState(self.bonding_state)), - }; - - Ok(Self { - bonding_state, - ..self - }) - } - - pub fn claim_stake( - self, - time: Timestamp, - unbonding_period_days: u64, - ) -> Result<(Self, nonempty::Uint128), ContractError> { - if self.authorization_state == AuthorizationState::Jailed { - return Err(ContractError::VerifierJailed); - } - - match self.bonding_state { - BondingState::Unbonding { - amount, - unbonded_at, - } if unbonded_at.plus_days(unbonding_period_days) <= time => Ok(( - Self { - bonding_state: BondingState::Unbonded, - ..self - }, - amount, - )), - _ => Err(ContractError::InvalidBondingState(self.bonding_state)), - } - } -} - -#[cw_serde] -pub struct WeightedVerifier { - pub verifier_info: Verifier, - pub weight: nonempty::Uint128, -} - -/// For now, all verifiers have equal weight, regardless of amount bonded -pub const VERIFIER_WEIGHT: nonempty::Uint128 = nonempty::Uint128::one(); - -impl From for Participant { - fn from(verifier: WeightedVerifier) -> Participant { - Self { - weight: verifier.weight, - address: verifier.verifier_info.address, - } - } -} +use service_registry_api::error::ContractError; +use service_registry_api::{AuthorizationState, BondingState, Service, Verifier}; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub enum BondingState { - Bonded { - amount: nonempty::Uint128, - }, - RequestedUnbonding { - amount: nonempty::Uint128, - }, - Unbonding { - amount: nonempty::Uint128, - unbonded_at: Timestamp, - }, - Unbonded, -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] -pub enum AuthorizationState { - NotAuthorized, - Authorized, - Jailed, -} +type ServiceName = String; +type VerifierAddress = Addr; pub struct VerifierPerChainIndexes<'a> { pub verifier_address: MultiIndex< @@ -179,12 +44,89 @@ pub const VERIFIERS_PER_CHAIN: IndexedMap< }, ); -type ServiceName = String; -type VerifierAddress = Addr; +/// For now, all verifiers have equal weight, regardless of amount bonded +pub const VERIFIER_WEIGHT: nonempty::Uint128 = nonempty::Uint128::one(); pub const SERVICES: Map<&ServiceName, Service> = Map::new("services"); pub const VERIFIERS: Map<(&ServiceName, &VerifierAddress), Verifier> = Map::new("verifiers"); +pub fn bond_verifier( + verifier: Verifier, + to_add: Option, +) -> Result { + let amount: nonempty::Uint128 = match verifier.bonding_state { + BondingState::Bonded { amount } + | BondingState::RequestedUnbonding { amount } + | BondingState::Unbonding { + amount, + unbonded_at: _, + } => amount + .into_inner() + .checked_add(to_add.map(Uint128::from).unwrap_or(Uint128::zero())) + .map_err(ContractError::Overflow)? + .try_into()?, + BondingState::Unbonded => to_add.ok_or(ContractError::NoFundsToBond)?, + }; + + Ok(Verifier { + bonding_state: BondingState::Bonded { amount }, + ..verifier + }) +} + +pub fn unbond_verifier( + verifier: Verifier, + can_unbond: bool, + time: Timestamp, +) -> Result { + if verifier.authorization_state == AuthorizationState::Jailed { + return Err(ContractError::VerifierJailed); + } + + let bonding_state = match verifier.bonding_state { + BondingState::Bonded { amount } | BondingState::RequestedUnbonding { amount } => { + if can_unbond { + BondingState::Unbonding { + unbonded_at: time, + amount, + } + } else { + BondingState::RequestedUnbonding { amount } + } + } + _ => return Err(ContractError::InvalidBondingState(verifier.bonding_state)), + }; + + Ok(Verifier { + bonding_state, + ..verifier + }) +} + +pub fn claim_verifier_stake( + verifier: Verifier, + time: Timestamp, + unbonding_period_days: u64, +) -> Result<(Verifier, nonempty::Uint128), ContractError> { + if verifier.authorization_state == AuthorizationState::Jailed { + return Err(ContractError::VerifierJailed); + } + + match verifier.bonding_state { + BondingState::Unbonding { + amount, + unbonded_at, + } if unbonded_at.plus_days(unbonding_period_days) <= time => Ok(( + Verifier { + bonding_state: BondingState::Unbonded, + ..verifier + }, + amount, + )), + _ => Err(ContractError::InvalidBondingState(verifier.bonding_state)), + } +} + pub fn register_chains_support( storage: &mut dyn Storage, service_name: String, @@ -218,7 +160,10 @@ mod tests { use std::str::FromStr; use std::vec; + use axelar_wasm_std::nonempty; use cosmwasm_std::testing::mock_dependencies; + use cosmwasm_std::{Timestamp, Uint128}; + use service_registry_api::{AuthorizationState, BondingState, Verifier}; use super::*; @@ -362,7 +307,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); + let res = bond_verifier(verifier, Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -383,7 +328,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); + let res = bond_verifier(verifier, Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -405,7 +350,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); + let res = bond_verifier(verifier, Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -424,7 +369,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(Some(Uint128::from(200u32).try_into().unwrap())); + let res = bond_verifier(verifier, Some(Uint128::from(200u32).try_into().unwrap())); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -445,7 +390,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(None); + let res = bond_verifier(verifier, None); assert!(res.is_err()); assert_eq!(res.unwrap_err(), ContractError::NoFundsToBond); } @@ -464,7 +409,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.bond(None); + let res = bond_verifier(verifier, None); assert!(res.is_ok()); assert_eq!(res.unwrap().bonding_state, BondingState::Bonded { amount }); } @@ -481,7 +426,7 @@ mod tests { }; let unbonded_at = Timestamp::from_nanos(0); - let res = verifier.unbond(true, unbonded_at); + let res = unbond_verifier(verifier, true, unbonded_at); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -504,7 +449,7 @@ mod tests { }; let unbonded_at = Timestamp::from_nanos(0); - let res = verifier.unbond(false, unbonded_at); + let res = unbond_verifier(verifier, false, unbonded_at); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -526,7 +471,7 @@ mod tests { }; let unbonded_at = Timestamp::from_nanos(0); - let res = verifier.unbond(true, unbonded_at); + let res = unbond_verifier(verifier, true, unbonded_at); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -549,7 +494,7 @@ mod tests { }; let unbonded_at = Timestamp::from_nanos(0); - let res = verifier.unbond(false, unbonded_at); + let res = unbond_verifier(verifier, false, unbonded_at); assert!(res.is_ok()); assert_eq!( res.unwrap().bonding_state, @@ -573,14 +518,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().unbond(true, Timestamp::from_nanos(2)); + let res = unbond_verifier(verifier.clone(), true, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.unbond(false, Timestamp::from_nanos(2)); + let res = unbond_verifier(verifier, false, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), @@ -599,14 +544,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().unbond(true, Timestamp::from_nanos(2)); + let res = unbond_verifier(verifier.clone(), true, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.unbond(false, Timestamp::from_nanos(2)); + let res = unbond_verifier(verifier, false, Timestamp::from_nanos(2)); assert!(res.is_err()); assert_eq!( res.unwrap_err(), @@ -626,14 +571,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().claim_stake(Timestamp::from_seconds(60), 1); + let res = claim_verifier_stake(verifier.clone(), Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + let res = claim_verifier_stake(verifier, Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), @@ -653,14 +598,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().claim_stake(Timestamp::from_seconds(60), 1); + let res = claim_verifier_stake(verifier.clone(), Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + let res = claim_verifier_stake(verifier, Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), @@ -681,14 +626,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().claim_stake(Timestamp::from_seconds(60), 1); + let res = claim_verifier_stake(verifier.clone(), Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + let res = claim_verifier_stake(verifier, Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_ok()); let (verifier, amount) = res.unwrap(); @@ -711,14 +656,14 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.clone().claim_stake(Timestamp::from_seconds(60), 1); + let res = claim_verifier_stake(verifier.clone(), Timestamp::from_seconds(60), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), ContractError::InvalidBondingState(bonding_state.clone()) ); - let res = verifier.claim_stake(Timestamp::from_seconds(60 * 60 * 24), 1); + let res = claim_verifier_stake(verifier, Timestamp::from_seconds(60 * 60 * 24), 1); assert!(res.is_err()); assert_eq!( res.unwrap_err(), @@ -737,7 +682,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.unbond(true, Timestamp::from_nanos(0)); + let res = unbond_verifier(verifier, true, Timestamp::from_nanos(0)); assert!(res.is_err()); assert_eq!(res.unwrap_err(), ContractError::VerifierJailed); } @@ -754,7 +699,7 @@ mod tests { service_name: "validators".to_string(), }; - let res = verifier.claim_stake(Timestamp::from_nanos(1), 0); + let res = claim_verifier_stake(verifier, Timestamp::from_nanos(1), 0); assert!(res.is_err()); assert_eq!(res.unwrap_err(), ContractError::VerifierJailed); } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 09a19d565..b97b1c623 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -46,6 +46,7 @@ router-api = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } service-registry = { workspace = true } +service-registry-api = { workspace = true } sha3 = { workspace = true } tofn = { workspace = true } voting-verifier = { workspace = true } diff --git a/integration-tests/src/service_registry_contract.rs b/integration-tests/src/service_registry_contract.rs index 59afd08da..6cffa8854 100644 --- a/integration-tests/src/service_registry_contract.rs +++ b/integration-tests/src/service_registry_contract.rs @@ -32,8 +32,8 @@ impl ServiceRegistryContract { } impl Contract for ServiceRegistryContract { - type QMsg = service_registry::msg::QueryMsg; - type ExMsg = service_registry::msg::ExecuteMsg; + type QMsg = service_registry_api::msg::QueryMsg; + type ExMsg = service_registry_api::msg::ExecuteMsg; fn contract_address(&self) -> Addr { self.contract_addr.clone() diff --git a/integration-tests/tests/bond_unbond.rs b/integration-tests/tests/bond_unbond.rs index 257b39e35..33a7096a1 100644 --- a/integration-tests/tests/bond_unbond.rs +++ b/integration-tests/tests/bond_unbond.rs @@ -1,6 +1,6 @@ use cosmwasm_std::BlockInfo; use integration_tests::contract::Contract; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; pub mod test_utils; diff --git a/integration-tests/tests/test_utils/mod.rs b/integration-tests/tests/test_utils/mod.rs index bc610edce..121ccf2ce 100644 --- a/integration-tests/tests/test_utils/mod.rs +++ b/integration-tests/tests/test_utils/mod.rs @@ -24,7 +24,7 @@ use multisig::verifier_set::VerifierSet; use multisig_prover::msg::VerifierSetResponse; use rewards::PoolId; use router_api::{Address, ChainName, CrossChainId, GatewayDirection, Message}; -use service_registry::msg::ExecuteMsg; +use service_registry_api::msg::ExecuteMsg; use sha3::{Digest, Keccak256}; use tofn::ecdsa::KeyPair; diff --git a/integration-tests/tests/update_worker_set.rs b/integration-tests/tests/update_worker_set.rs index 3c0323745..689dbb1a6 100644 --- a/integration-tests/tests/update_worker_set.rs +++ b/integration-tests/tests/update_worker_set.rs @@ -2,8 +2,8 @@ use cosmwasm_std::Addr; use cw_multi_test::Executor; use integration_tests::contract::Contract; use multisig_prover::msg::ExecuteMsg; -use service_registry::msg::QueryMsg as ServiceRegistryQueryMsg; use service_registry::WeightedVerifier; +use service_registry_api::msg::QueryMsg as ServiceRegistryQueryMsg; pub mod test_utils; diff --git a/packages/service-registry-api/Cargo.toml b/packages/service-registry-api/Cargo.toml new file mode 100644 index 000000000..50fe50a2a --- /dev/null +++ b/packages/service-registry-api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "service-registry-api" +version = "1.0.0" +rust-version = { workspace = true } +edition = { workspace = true } +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +axelar-wasm-std = { workspace = true, features = ["derive"] } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +error-stack = { workspace = true } +msgs-derive = { workspace = true } +report = { workspace = true } +router-api = { workspace = true } +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { workspace = true } + +[dev-dependencies] +goldie = { workspace = true } + +[lints] +workspace = true diff --git a/packages/service-registry-api/release.toml b/packages/service-registry-api/release.toml new file mode 100644 index 000000000..954564097 --- /dev/null +++ b/packages/service-registry-api/release.toml @@ -0,0 +1 @@ +release = false diff --git a/contracts/service-registry/src/error.rs b/packages/service-registry-api/src/error.rs similarity index 97% rename from contracts/service-registry/src/error.rs rename to packages/service-registry-api/src/error.rs index 324b84090..29a1e7fcc 100644 --- a/contracts/service-registry/src/error.rs +++ b/packages/service-registry-api/src/error.rs @@ -2,7 +2,7 @@ use axelar_wasm_std::{nonempty, IntoContractError}; use cosmwasm_std::{OverflowError, StdError}; use thiserror::Error; -use crate::state::BondingState; +use crate::primitives::BondingState; #[derive(Error, Debug, PartialEq, IntoContractError)] pub enum ContractError { diff --git a/packages/service-registry-api/src/lib.rs b/packages/service-registry-api/src/lib.rs new file mode 100644 index 000000000..8308809a2 --- /dev/null +++ b/packages/service-registry-api/src/lib.rs @@ -0,0 +1,6 @@ +pub mod error; +mod primitives; + +pub mod msg; + +pub use primitives::*; diff --git a/packages/service-registry-api/src/msg.rs b/packages/service-registry-api/src/msg.rs new file mode 100644 index 000000000..a59ad9e42 --- /dev/null +++ b/packages/service-registry-api/src/msg.rs @@ -0,0 +1,108 @@ +use axelar_wasm_std::nonempty; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use msgs_derive::EnsurePermissions; +use router_api::ChainName; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::primitives::*; + +#[cw_serde] +#[derive(EnsurePermissions)] +pub enum ExecuteMsg { + /// Can only be called by governance account + #[permission(Governance)] + RegisterService { + service_name: String, + coordinator_contract: String, + min_num_verifiers: u16, + max_num_verifiers: Option, + min_verifier_bond: nonempty::Uint128, + bond_denom: String, + unbonding_period_days: u16, // number of days to wait after starting unbonding before allowed to claim stake + description: String, + }, + /// Updates modifiable fields of the service. Note, not all fields are modifiable. + #[permission(Governance)] + UpdateService { + service_name: String, + updated_service_params: UpdatedServiceParams, + }, + /// Authorizes verifiers to join a service. Can only be called by governance account. Verifiers must still bond sufficient stake to participate. + #[permission(Governance)] + AuthorizeVerifiers { + verifiers: Vec, + service_name: String, + }, + /// Revoke authorization for specified verifiers. Can only be called by governance account. Verifiers bond remains unchanged + #[permission(Governance)] + UnauthorizeVerifiers { + verifiers: Vec, + service_name: String, + }, + /// Jail verifiers. Can only be called by governance account. Jailed verifiers are not allowed to unbond or claim stake. + #[permission(Governance)] + JailVerifiers { + verifiers: Vec, + service_name: String, + }, + + /// Register support for the specified chains. Called by the verifier. + #[permission(Specific(verifier))] + RegisterChainSupport { + service_name: String, + chains: Vec, + }, + /// Deregister support for the specified chains. Called by the verifier. + #[permission(Specific(verifier))] + DeregisterChainSupport { + service_name: String, + chains: Vec, + }, + + /// Locks up any funds sent with the message as stake. Marks the sender as a potential verifier that can be authorized. + #[permission(Any)] + BondVerifier { service_name: String }, + /// Initiates unbonding of staked funds for the sender. + #[permission(Any)] + UnbondVerifier { service_name: String }, + /// Claim previously staked funds that have finished unbonding for the sender. + #[permission(Any)] + ClaimStake { service_name: String }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(Vec)] + ActiveVerifiers { + service_name: String, + chain_name: ChainName, + }, + + #[returns(Service)] + Service { service_name: String }, + + #[returns(VerifierDetails)] + Verifier { + service_name: String, + verifier: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct VerifierDetails { + pub verifier: Verifier, + pub weight: nonempty::Uint128, + pub supported_chains: Vec, +} + +// Represents any modifiable fields of the Service struct +// Any non-None field overwrites the value currently stored in the Service object +#[cw_serde] +pub struct UpdatedServiceParams { + pub min_num_verifiers: Option, + pub max_num_verifiers: Option>, + pub min_verifier_bond: Option, + pub unbonding_period_days: Option, +} diff --git a/packages/service-registry-api/src/primitives.rs b/packages/service-registry-api/src/primitives.rs new file mode 100644 index 000000000..d05eb7ee2 --- /dev/null +++ b/packages/service-registry-api/src/primitives.rs @@ -0,0 +1,64 @@ +use axelar_wasm_std::{nonempty, Participant}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Timestamp}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Service { + pub name: String, + pub coordinator_contract: Addr, + pub min_num_verifiers: u16, + pub max_num_verifiers: Option, + pub min_verifier_bond: nonempty::Uint128, + pub bond_denom: String, + // should be set to a duration longer than the voting period for governance proposals, + // otherwise a verifier could bail before they get penalized + pub unbonding_period_days: u16, + pub description: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Verifier { + pub address: Addr, + pub bonding_state: BondingState, + pub authorization_state: AuthorizationState, + pub service_name: String, +} + +#[cw_serde] +pub struct WeightedVerifier { + pub verifier_info: Verifier, + pub weight: nonempty::Uint128, +} + +impl From for Participant { + fn from(verifier: WeightedVerifier) -> Participant { + Self { + weight: verifier.weight, + address: verifier.verifier_info.address, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum BondingState { + Bonded { + amount: nonempty::Uint128, + }, + RequestedUnbonding { + amount: nonempty::Uint128, + }, + Unbonding { + amount: nonempty::Uint128, + unbonded_at: Timestamp, + }, + Unbonded, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub enum AuthorizationState { + NotAuthorized, + Authorized, + Jailed, +}