From 2ed74252fb4c73e78a0e60cf69ff297f45ef707d Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 9 Jan 2024 14:04:52 -0500 Subject: [PATCH 1/5] Add pausable ism with tests --- contracts/isms/pausable/Cargo.toml | 43 +++++++ contracts/isms/pausable/src/lib.rs | 164 +++++++++++++++++++++++++ packages/interface/src/ism/mod.rs | 1 + packages/interface/src/ism/pausable.rs | 26 ++++ 4 files changed, 234 insertions(+) create mode 100644 contracts/isms/pausable/Cargo.toml create mode 100644 contracts/isms/pausable/src/lib.rs create mode 100644 packages/interface/src/ism/pausable.rs diff --git a/contracts/isms/pausable/Cargo.toml b/contracts/isms/pausable/Cargo.toml new file mode 100644 index 00000000..41cef4a1 --- /dev/null +++ b/contracts/isms/pausable/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "hpl-ism-pausable" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +keywords.workspace = true + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[dependencies] +cosmwasm-std.workspace = true +cosmwasm-storage.workspace = true +cosmwasm-schema.workspace = true + +cw-storage-plus.workspace = true +cw2.workspace = true +cw-utils.workspace = true + +schemars.workspace = true +serde-json-wasm.workspace = true + +thiserror.workspace = true + +hpl-ownable.workspace = true +hpl-pausable.workspace = true +hpl-interface.workspace = true + +[dev-dependencies] +rstest.workspace = true +ibcx-test-utils.workspace = true + +anyhow.workspace = true diff --git a/contracts/isms/pausable/src/lib.rs b/contracts/isms/pausable/src/lib.rs new file mode 100644 index 00000000..51c44146 --- /dev/null +++ b/contracts/isms/pausable/src/lib.rs @@ -0,0 +1,164 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + ensure, to_json_binary, Deps, DepsMut, Env, Event, MessageInfo, QueryResponse, Response, + StdError, +}; +use hpl_interface::ism::{ + pausable::{ExecuteMsg, InstantiateMsg, QueryMsg}, + IsmQueryMsg, IsmType, ModuleTypeResponse, VerifyResponse, +}; + +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("{0}")] + PaymentError(#[from] cw_utils::PaymentError), + + #[error("unauthorized")] + Unauthorized {}, + + #[error("hook paused")] + Paused {}, +} + +// version info for migration info +pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +fn new_event(name: &str) -> Event { + Event::new(format!("hpl_hook_pausable::{}", name)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + + let owner = deps.api.addr_validate(&msg.owner)?; + + hpl_ownable::initialize(deps.storage, &owner)?; + hpl_pausable::initialize(deps.storage, &msg.paused)?; + + Ok(Response::new().add_event( + new_event("initialize") + .add_attribute("sender", info.sender) + .add_attribute("owner", owner), + )) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Ownable(msg) => Ok(hpl_ownable::handle(deps, env, info, msg)?), + ExecuteMsg::Pausable(msg) => Ok(hpl_pausable::handle(deps, env, info, msg)?), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { + use IsmQueryMsg::*; + + match msg { + QueryMsg::Pausable(msg) => Ok(hpl_pausable::handle_query(deps, env, msg)?), + QueryMsg::Ownable(msg) => Ok(hpl_ownable::handle_query(deps, env, msg)?), + QueryMsg::Ism(msg) => match msg { + ModuleType {} => Ok(to_json_binary(&ModuleTypeResponse { typ: IsmType::Null })?), + Verify { + metadata: _, + message: _, + } => { + ensure!( + !hpl_pausable::get_pause_info(deps.storage)?, + ContractError::Paused {} + ); + Ok(to_json_binary(&VerifyResponse { verified: true })?) + } + _ => unimplemented!(), + }, + } +} + +#[cfg(test)] +mod test { + use cosmwasm_schema::serde::{de::DeserializeOwned, Serialize}; + use cosmwasm_std::{ + from_json, + testing::{mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage}, + to_json_binary, Addr, OwnedDeps, + }; + use hpl_ownable::get_owner; + use hpl_pausable::get_pause_info; + use ibcx_test_utils::{addr, hex}; + use rstest::{fixture, rstest}; + + use super::*; + + type TestDeps = OwnedDeps; + + fn query(deps: Deps, msg: S) -> T { + let req: QueryMsg = from_json(to_json_binary(&msg).unwrap()).unwrap(); + let res = crate::query(deps, mock_env(), req).unwrap(); + from_json(res).unwrap() + } + + #[fixture] + fn deps( + #[default(addr("deployer"))] sender: Addr, + #[default(addr("owner"))] owner: Addr, + #[default(false)] paused: bool, + ) -> TestDeps { + let mut deps = mock_dependencies(); + + instantiate( + deps.as_mut(), + mock_env(), + mock_info(sender.as_str(), &[]), + InstantiateMsg { + owner: owner.to_string(), + paused, + }, + ) + .unwrap(); + + deps + } + + #[rstest] + fn test_init(deps: TestDeps) { + assert!(!get_pause_info(deps.as_ref().storage).unwrap()); + assert_eq!("owner", get_owner(deps.as_ref().storage).unwrap().as_str()); + } + + #[rstest] + #[case(false)] + #[should_panic(expected = "hook paused")] + #[case(true)] + fn test_query(mut deps: TestDeps, #[case] paused: bool) { + if paused { + hpl_pausable::pause(deps.as_mut().storage, &addr("owner")).unwrap(); + } + + let raw_message = hex("0000000000000068220000000000000000000000000d1255b09d94659bb0888e0aa9fca60245ce402a0000682155208cd518cffaac1b5d8df216a9bd050c9a03f0d4f3ba88e5268ac4cd12ee2d68656c6c6f"); + let raw_metadata = raw_message.clone(); + + query::<_, VerifyResponse>( + deps.as_ref(), + QueryMsg::Ism(IsmQueryMsg::Verify { + metadata: raw_metadata, + message: raw_message, + }), + ); + } +} diff --git a/packages/interface/src/ism/mod.rs b/packages/interface/src/ism/mod.rs index 13cccbe4..f3947053 100644 --- a/packages/interface/src/ism/mod.rs +++ b/packages/interface/src/ism/mod.rs @@ -1,6 +1,7 @@ pub mod aggregate; pub mod multisig; pub mod routing; +pub mod pausable; use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::{Addr, CustomQuery, HexBinary, QuerierWrapper, StdResult}; diff --git a/packages/interface/src/ism/pausable.rs b/packages/interface/src/ism/pausable.rs new file mode 100644 index 00000000..1f48934f --- /dev/null +++ b/packages/interface/src/ism/pausable.rs @@ -0,0 +1,26 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +use crate::{ownable::{OwnableMsg, OwnableQueryMsg}, pausable::{PausableMsg, PausableQueryMsg}}; + +use super::IsmQueryMsg; + +#[cw_serde] +pub struct InstantiateMsg { + pub owner: String, + pub paused: bool +} + +#[cw_serde] +pub enum ExecuteMsg { + Ownable(OwnableMsg), + Pausable(PausableMsg) +} + +#[cw_serde] +#[derive(QueryResponses)] +#[query_responses(nested)] +pub enum QueryMsg { + Ownable(OwnableQueryMsg), + Ism(IsmQueryMsg), + Pausable(PausableQueryMsg) +} From a6c890aea1599ff102858ef75528df4cfc8df720 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 9 Jan 2024 14:08:49 -0500 Subject: [PATCH 2/5] Fix paused query error case --- contracts/isms/pausable/src/lib.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/isms/pausable/src/lib.rs b/contracts/isms/pausable/src/lib.rs index 51c44146..d89ebe22 100644 --- a/contracts/isms/pausable/src/lib.rs +++ b/contracts/isms/pausable/src/lib.rs @@ -107,10 +107,9 @@ mod test { type TestDeps = OwnedDeps; - fn query(deps: Deps, msg: S) -> T { + fn query(deps: Deps, msg: crate::QueryMsg) -> Result { let req: QueryMsg = from_json(to_json_binary(&msg).unwrap()).unwrap(); - let res = crate::query(deps, mock_env(), req).unwrap(); - from_json(res).unwrap() + crate::query(deps, mock_env(), req) } #[fixture] @@ -153,12 +152,14 @@ mod test { let raw_message = hex("0000000000000068220000000000000000000000000d1255b09d94659bb0888e0aa9fca60245ce402a0000682155208cd518cffaac1b5d8df216a9bd050c9a03f0d4f3ba88e5268ac4cd12ee2d68656c6c6f"); let raw_metadata = raw_message.clone(); - query::<_, VerifyResponse>( + query( deps.as_ref(), QueryMsg::Ism(IsmQueryMsg::Verify { metadata: raw_metadata, message: raw_message, }), - ); + ) + .map_err(|e| e.to_string()) + .unwrap(); } } From ee01584ee668ffc09f64a05ad9261bcc9a18f313 Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 9 Jan 2024 14:19:55 -0500 Subject: [PATCH 3/5] Run CI against all PRs --- .github/workflows/test.yaml | 2 -- contracts/isms/pausable/src/lib.rs | 1 - 2 files changed, 3 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7959e3cd..ae8bdc8f 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,8 +2,6 @@ name: test on: pull_request: - branches: - - "main" push: branches: - "main" diff --git a/contracts/isms/pausable/src/lib.rs b/contracts/isms/pausable/src/lib.rs index d89ebe22..16b82b85 100644 --- a/contracts/isms/pausable/src/lib.rs +++ b/contracts/isms/pausable/src/lib.rs @@ -92,7 +92,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Date: Tue, 9 Jan 2024 14:22:53 -0500 Subject: [PATCH 4/5] Add pausable ISM to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 79814e61..fe00de58 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,8 @@ grcov . -s . --binary-path ./target/debug/ -t lcov --branch --ignore-not-existin - [aggregate ism](./contracts/isms/aggregate) + - [pausable](./contracts/isms/pausable) + - For testing: [mock ism](./contracts/mocks/mock-ism) 5. Set deployed hooks and isms to Mailbox From ef9c6f38173ea763ab8eb0a093f20724051bcfde Mon Sep 17 00:00:00 2001 From: Yorke Rhodes Date: Tue, 9 Jan 2024 16:46:04 -0500 Subject: [PATCH 5/5] Fix scripts --- scripts/action/ism.ts | 14 +++++++------- scripts/action/mailbox.ts | 18 +++++++++--------- scripts/src/contracts/hpl_ism_pausable.ts | 7 +++++++ scripts/src/contracts/index.ts | 5 +++-- scripts/src/migrations/InitializeStandalone.ts | 9 +++++---- scripts/src/types.ts | 10 +++++----- 6 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 scripts/src/contracts/hpl_ism_pausable.ts diff --git a/scripts/action/ism.ts b/scripts/action/ism.ts index 897a8d3f..f842d776 100644 --- a/scripts/action/ism.ts +++ b/scripts/action/ism.ts @@ -1,17 +1,17 @@ -import { Command } from "commander"; import { ExecuteResult } from "@cosmjs/cosmwasm-stargate"; +import { Command } from "commander"; import { version } from "../package.json"; import { config, getSigningClient } from "../src/config"; -import { loadContext } from "../src/load_context"; -import { ContractFetcher } from "./fetch"; import { - HplMailbox, - HplIgp, - HplIgpGasOracle, HplHookMerkle, + HplIgp, + HplIgpOracle, HplIsmAggregate, + HplMailbox, } from "../src/contracts"; +import { loadContext } from "../src/load_context"; +import { ContractFetcher } from "./fetch"; const program = new Command(); @@ -51,7 +51,7 @@ function makeHandler( const fetcher = new ContractFetcher(ctx, client); const mailbox = fetcher.get(HplMailbox, "hpl_mailbox"); const igp = fetcher.get(HplIgp, "hpl_igp"); - const igp_oracle = fetcher.get(HplIgpGasOracle, "hpl_igp_oracle"); + const igp_oracle = fetcher.get(HplIgpOracle, "hpl_igp_oracle"); const hook_merkle = fetcher.get(HplHookMerkle, "hpl_hook_merkle"); const hook_aggregate = fetcher.get(HplIsmAggregate, "hpl_hook_aggregate"); diff --git a/scripts/action/mailbox.ts b/scripts/action/mailbox.ts index 2509708d..bd40ea1a 100644 --- a/scripts/action/mailbox.ts +++ b/scripts/action/mailbox.ts @@ -1,18 +1,18 @@ -import { Command } from "commander"; import { ExecuteResult } from "@cosmjs/cosmwasm-stargate"; +import { Command } from "commander"; import { version } from "../package.json"; import { config, getSigningClient } from "../src/config"; +import { + HplHookMerkle, + HplIgp, + HplIgpOracle, + HplIsmAggregate, + HplMailbox, +} from "../src/contracts"; import { addPad } from "../src/conv"; import { loadContext } from "../src/load_context"; import { ContractFetcher } from "./fetch"; -import { - HplMailbox, - HplIgp, - HplIgpGasOracle, - HplHookMerkle, - HplIsmAggregate, -} from "../src/contracts"; const program = new Command(); @@ -54,7 +54,7 @@ function makeHandler( const fetcher = new ContractFetcher(ctx, client); const mailbox = fetcher.get(HplMailbox, "hpl_mailbox"); const igp = fetcher.get(HplIgp, "hpl_igp"); - const igp_oracle = fetcher.get(HplIgpGasOracle, "hpl_igp_oracle"); + const igp_oracle = fetcher.get(HplIgpOracle, "hpl_igp_oracle"); const hook_merkle = fetcher.get(HplHookMerkle, "hpl_hook_merkle"); const hook_aggregate = fetcher.get(HplIsmAggregate, "hpl_hook_aggregate"); diff --git a/scripts/src/contracts/hpl_ism_pausable.ts b/scripts/src/contracts/hpl_ism_pausable.ts new file mode 100644 index 00000000..7fce739a --- /dev/null +++ b/scripts/src/contracts/hpl_ism_pausable.ts @@ -0,0 +1,7 @@ +import { injectable } from "inversify"; +import { BaseContract } from "../types"; + +@injectable() +export class HplIsmPausable extends BaseContract { + contractName: string = "hpl_ism_pausable"; +} diff --git a/scripts/src/contracts/index.ts b/scripts/src/contracts/index.ts index 8016e991..417f7be2 100644 --- a/scripts/src/contracts/index.ts +++ b/scripts/src/contracts/index.ts @@ -8,6 +8,7 @@ export * from "./hpl_igp"; export * from "./hpl_igp_oracle"; export * from "./hpl_ism_aggregate"; export * from "./hpl_ism_multisig"; +export * from "./hpl_ism_pausable"; export * from "./hpl_ism_routing"; export * from "./hpl_mailbox"; export * from "./hpl_test_mock_hook"; @@ -16,10 +17,10 @@ export * from "./hpl_validator_announce"; export * from "./hpl_warp_cw20"; export * from "./hpl_warp_native"; -import { readdirSync } from "fs"; -import { Context, Contract, ContractConstructor } from "../types"; import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate"; +import { readdirSync } from "fs"; import { Container } from "inversify"; +import { Context, Contract, ContractConstructor } from "../types"; const contractNames: string[] = readdirSync(__dirname) .filter((f) => f !== "index.ts") diff --git a/scripts/src/migrations/InitializeStandalone.ts b/scripts/src/migrations/InitializeStandalone.ts index 920cc5b4..1bede003 100644 --- a/scripts/src/migrations/InitializeStandalone.ts +++ b/scripts/src/migrations/InitializeStandalone.ts @@ -1,12 +1,13 @@ import { injectable } from "inversify"; -import { Context, Migration } from "../types"; import { - HplMailbox, HplHookMerkle, - HplIgpGasOracle, + HplIgp, + HplIgpOracle, HplIsmMultisig, + HplMailbox, HplTestMockHook, } from "../contracts"; +import { Context, Migration } from "../types"; @injectable() export default class InitializeStandalone implements Migration { @@ -18,7 +19,7 @@ export default class InitializeStandalone implements Migration { private mailbox: HplMailbox, private hook_merkle: HplHookMerkle, private igp: HplIgp, - private igp_oracle: HplIgpGasOracle, + private igp_oracle: HplIgpOracle, private ism_multisig: HplIsmMultisig, private test_mock_hook: HplTestMockHook ) {} diff --git a/scripts/src/types.ts b/scripts/src/types.ts index b28f9edc..95c63af6 100644 --- a/scripts/src/types.ts +++ b/scripts/src/types.ts @@ -1,10 +1,10 @@ import { - ExecuteResult, - SigningCosmWasmClient, + ExecuteResult, + SigningCosmWasmClient, } from "@cosmjs/cosmwasm-stargate"; -import { getWasmPath } from "./load_wasm"; -import fs from "fs"; import { fromBech32 } from "@cosmjs/encoding"; +import fs from "fs"; +import { getWasmPath } from "./load_wasm"; export interface ContractContext { codeId: number | undefined; @@ -165,7 +165,7 @@ export interface HplIgpCoreInstantiateMsg { beneficiary: string; } -export interface HplIgpGasOracleInstantiateMsg {} +export interface HplIgpOracleInstantiateMsg {} export interface HplIsmMultisigInstantiateMsg { owner: string;