Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement simple protocol fee hook #89

Merged
merged 29 commits into from
Jan 23, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2ed7425
Add pausable ism with tests
yorhodes Jan 9, 2024
a6c890a
Fix paused query error case
yorhodes Jan 9, 2024
ee01584
Run CI against all PRs
yorhodes Jan 9, 2024
476b777
Add pausable ISM to README
yorhodes Jan 9, 2024
c2c2804
Build wasm
nambrot Jan 9, 2024
ef9c6f3
Fix scripts
yorhodes Jan 9, 2024
8a9aa1b
Allow threshold == set size and add tests
nambrot Jan 9, 2024
63c62bb
Upload artifacts
nambrot Jan 9, 2024
ae2706d
Force
nambrot Jan 9, 2024
46d3e36
Move into makefile
nambrot Jan 10, 2024
f7af326
Install rename
nambrot Jan 11, 2024
280fb07
Rename properly
nambrot Jan 11, 2024
d5666cd
Update test.yaml
nambrot Jan 12, 2024
282430c
Implement simple fee hook_
yorhodes Jan 16, 2024
6bd5efa
Make merkle hook not ownable
yorhodes Jan 16, 2024
9e335f4
Address pr comments
yorhodes Jan 16, 2024
1b0e3c9
Fix unit tests
yorhodes Jan 16, 2024
cfe3377
Merge pull request #5 from hyperlane-xyz/merkle-not-ownable
yorhodes Jan 16, 2024
7973533
Merge pull request #1 from hyperlane-xyz/pausable-ism
yorhodes Jan 16, 2024
26f040f
Merge pull request #3 from hyperlane-xyz/nambrot/fix-multisig-ism
yorhodes Jan 16, 2024
04468ba
Fix renaming
yorhodes Jan 16, 2024
ddb174b
Fix makefile indentation
yorhodes Jan 16, 2024
189ecf3
Force cargo install
yorhodes Jan 16, 2024
2cd2bfc
Merge pull request #2 from hyperlane-xyz/nambrot/ci-wasm-build
yorhodes Jan 16, 2024
0b9b3a8
Merge branch 'hypmain' into simple-fee-hook
yorhodes Jan 17, 2024
d65a08a
Fix fee hook tests
yorhodes Jan 17, 2024
189ee15
Make set fee only owner
yorhodes Jan 17, 2024
317ef61
Implement remaining unit tests
yorhodes Jan 19, 2024
5b82038
Fix merkle integration test use
yorhodes Jan 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions contracts/hooks/fees/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "hpl-hook-fee"
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-interface.workspace = true

[dev-dependencies]
rstest.workspace = true
ibcx-test-utils.workspace = true

anyhow.workspace = true
233 changes: 233 additions & 0 deletions contracts/hooks/fees/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
ensure, ensure_eq, BankMsg, Coin, CosmosMsg, Deps, DepsMut, Env, Event,
MessageInfo, QueryResponse, Response, StdError,
};
use cw_storage_plus::Item;
use hpl_interface::{
hook::{
fee::{ExecuteMsg, FeeHookMsg, FeeHookQueryMsg, FeeResponse, InstantiateMsg, QueryMsg},
HookQueryMsg, MailboxResponse, QuoteDispatchResponse,
},
to_binary,
};

#[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");

pub const COIN_FEE_KEY: &str = "coin_fee";
pub const COIN_FEE: Item<Coin> = Item::new(COIN_FEE_KEY);

fn new_event(name: &str) -> Event {
Event::new(format!("hpl_hook_fee::{}", name))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

let owner = deps.api.addr_validate(&msg.owner)?;

hpl_ownable::initialize(deps.storage, &owner)?;
COIN_FEE.save(deps.storage, &msg.fee)?;

Ok(Response::new().add_event(
new_event("initialize")
.add_attribute("sender", info.sender)
.add_attribute("owner", owner)
.add_attribute("fee_denom", msg.fee.denom)
.add_attribute("fee_amount", msg.fee.amount),
))
}

fn get_fee(deps: Deps) -> Result<FeeResponse, ContractError> {
let fee = COIN_FEE.load(deps.storage)?;

Ok(FeeResponse { fee })
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Ownable(msg) => Ok(hpl_ownable::handle(deps, env, info, msg)?),
ExecuteMsg::FeeHook(msg) => match msg {
FeeHookMsg::SetFee { fee } => {
COIN_FEE.save(deps.storage, &fee)?;

Ok(Response::new().add_event(
new_event("set_fee")
.add_attribute("fee_denom", fee.denom)
.add_attribute("fee_amount", fee.amount),
))
}
FeeHookMsg::Claim { recipient } => {
let owner = hpl_ownable::get_owner(deps.storage)?;
ensure_eq!(owner, info.sender, StdError::generic_err("unauthorized"));

let recipient = recipient.unwrap_or(owner);
let balances = deps.querier.query_all_balances(&env.contract.address)?;

let claim_msg: CosmosMsg = BankMsg::Send {
to_address: recipient.into_string(),
amount: balances,
}
.into();

Ok(Response::new()
.add_message(claim_msg)
.add_event(new_event("claim")))
}
},
ExecuteMsg::PostDispatch(_) => {
let fee = COIN_FEE.load(deps.storage)?;
let supplied = cw_utils::must_pay(&info, &fee.denom)?;

ensure!(
supplied.u128() >= fee.amount.u128(),
StdError::generic_err("insufficient fee")
);

Ok(Response::new().add_event(
new_event("post_dispatch")
.add_attribute("paid_denom", fee.denom)
.add_attribute("paid_amount", supplied),
))
}
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
match msg {
QueryMsg::Ownable(msg) => Ok(hpl_ownable::handle_query(deps, env, msg)?),
QueryMsg::Hook(msg) => match msg {
HookQueryMsg::Mailbox {} => to_binary(get_mailbox(deps)),
HookQueryMsg::QuoteDispatch(_) => to_binary(quote_dispatch(deps)),
},
QueryMsg::FeeHook(FeeHookQueryMsg::Fee {}) => to_binary(get_fee(deps)),
}
}

fn get_mailbox(_deps: Deps) -> Result<MailboxResponse, ContractError> {
Ok(MailboxResponse {
mailbox: "unrestricted".to_string(),
})
}

fn quote_dispatch(deps: Deps) -> Result<QuoteDispatchResponse, ContractError> {
let fee = COIN_FEE.load(deps.storage)?;
Ok(QuoteDispatchResponse { fees: vec![fee] })
}

#[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, HexBinary, OwnedDeps, coin,
};
use hpl_interface::hook::{PostDispatchMsg, QuoteDispatchMsg};
use hpl_ownable::get_owner;
use ibcx_test_utils::{addr, gen_bz};
use rstest::{fixture, rstest};

use super::*;

type TestDeps = OwnedDeps<MockStorage, MockApi, MockQuerier>;

fn query<S: Serialize, T: DeserializeOwned>(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(coin(100, "uusd"))] fee: Coin,
) -> TestDeps {
let mut deps = mock_dependencies();

instantiate(
deps.as_mut(),
mock_env(),
mock_info(sender.as_str(), &[]),
InstantiateMsg {
owner: owner.to_string(),
fee
},
)
.unwrap();

deps
}

#[rstest]
fn test_init(deps: TestDeps) {
assert_eq!(
"uusd",
query::<_, MailboxResponse>(deps.as_ref(), QueryMsg::Hook(HookQueryMsg::Mailbox {}))
.mailbox
);
assert_eq!("owner", get_owner(deps.as_ref().storage).unwrap().as_str());
}

#[rstest]
#[case("owner")]
#[should_panic(expected = "hook paused")]
#[case("owner")]
fn test_post_dispatch(mut deps: TestDeps, #[case] sender: &str) {
execute(
deps.as_mut(),
mock_env(),
mock_info(sender, &[]),
ExecuteMsg::PostDispatch(PostDispatchMsg {
metadata: HexBinary::default(),
message: gen_bz(100),
}),
)
.map_err(|e| e.to_string())
.unwrap();
}

#[rstest]
fn test_query(deps: TestDeps) {
let res: MailboxResponse = query(deps.as_ref(), QueryMsg::Hook(HookQueryMsg::Mailbox {}));
assert_eq!("unrestricted", res.mailbox.as_str());

let res: QuoteDispatchResponse = query(
deps.as_ref(),
QueryMsg::Hook(HookQueryMsg::QuoteDispatch(QuoteDispatchMsg::default())),
);
assert_eq!(res.fees, vec![coin(100, "uusd")]);
}
}
16 changes: 3 additions & 13 deletions contracts/hooks/merkle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,31 +59,26 @@ pub fn instantiate(
) -> Result<Response, ContractError> {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;

let owner = deps.api.addr_validate(&msg.owner)?;
let mailbox = deps.api.addr_validate(&msg.mailbox)?;

hpl_ownable::initialize(deps.storage, &owner)?;

MAILBOX.save(deps.storage, &mailbox)?;
MESSAGE_TREE.save(deps.storage, &MerkleTree::default())?;

Ok(Response::new().add_event(
new_event("initialize")
.add_attribute("sender", info.sender)
.add_attribute("owner", owner)
.add_attribute("mailbox", mailbox),
))
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
_env: Env,
_info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Ownable(msg) => Ok(hpl_ownable::handle(deps, env, info, msg)?),
ExecuteMsg::PostDispatch(PostDispatchMsg { message, .. }) => {
let mailbox = MAILBOX.load(deps.storage)?;

Expand Down Expand Up @@ -123,11 +118,10 @@ pub fn execute(
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
use MerkleHookQueryMsg::*;

match msg {
QueryMsg::Ownable(msg) => Ok(hpl_ownable::handle_query(deps, env, msg)?),
QueryMsg::Hook(msg) => match msg {
HookQueryMsg::Mailbox {} => to_binary(get_mailbox(deps)),
HookQueryMsg::QuoteDispatch(_) => to_binary(quote_dispatch()),
Expand Down Expand Up @@ -214,7 +208,6 @@ mod test {
use hpl_interface::{
build_test_executor, build_test_querier, core::mailbox, hook::QuoteDispatchMsg,
};
use hpl_ownable::get_owner;
use ibcx_test_utils::hex;
use rstest::{fixture, rstest};

Expand All @@ -228,7 +221,6 @@ mod test {
#[fixture]
fn deps(
#[default(Addr::unchecked("deployer"))] sender: Addr,
#[default(Addr::unchecked("owner"))] owner: Addr,
#[default(Addr::unchecked("mailbox"))] mailbox: Addr,
) -> TestDeps {
let mut deps = mock_dependencies();
Expand All @@ -238,7 +230,6 @@ mod test {
mock_env(),
mock_info(sender.as_str(), &[]),
InstantiateMsg {
owner: owner.to_string(),
mailbox: mailbox.to_string(),
},
)
Expand All @@ -249,7 +240,6 @@ mod test {

#[rstest]
fn test_init(deps: TestDeps) {
assert_eq!("owner", get_owner(deps.as_ref().storage).unwrap().as_str());
assert_eq!(
"mailbox",
MAILBOX.load(deps.as_ref().storage).unwrap().as_str()
Expand Down
Loading
Loading