Files
nym/contracts/nym-pool/src/contract.rs
T
Jędrzej Stuczyński 6de0c4ce92 feat: initial performance contract (#5833)
* initialised basic structure for the performance contract

* shared code for contract testing

* unified common testing methods between performance and nym pool contracts

* impl of ExecuteMsg for the contract

* impl of QueryMsg for the contract

* setting initial authorised NMs during instantiation

* additional tests and fixes

* ibid

* scaffolding for client traits

* completed client traits

* clippy

* naive add performance contract to testnet manager

* placeholder values for the performance contract address

* introduced admin messages to purge old measurements from the storage

* introduced check ensuring performance data is only added to bonded nodes
2025-06-20 09:06:56 +01:00

328 lines
12 KiB
Rust

// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::queries::{
query_admin, query_available_tokens, query_grant, query_granter, query_granters_paged,
query_grants_paged, query_locked_tokens, query_locked_tokens_paged, query_total_locked_tokens,
};
use crate::storage::NYM_POOL_STORAGE;
use crate::transactions::{
try_add_new_granter, try_grant_allowance, try_lock_allowance, try_remove_expired,
try_revoke_grant, try_revoke_granter, try_unlock_allowance, try_update_contract_admin,
try_use_allowance, try_use_locked_allowance, try_withdraw_allowance,
try_withdraw_locked_allowance,
};
use cosmwasm_std::{
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response,
};
use nym_contracts_common::set_build_information;
use nym_pool_contract_common::{
ExecuteMsg, InstantiateMsg, MigrateMsg, NymPoolContractError, QueryMsg,
};
const CONTRACT_NAME: &str = "crate:nym-pool-contract";
const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
#[entry_point]
pub fn instantiate(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, NymPoolContractError> {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
set_build_information!(deps.storage)?;
NYM_POOL_STORAGE.initialise(deps, env, info.sender, &msg.pool_denomination, msg.grants)?;
Ok(Response::default())
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, NymPoolContractError> {
match msg {
ExecuteMsg::UpdateAdmin {
admin,
update_granter_set,
} => try_update_contract_admin(deps, env, info, admin, update_granter_set),
ExecuteMsg::GrantAllowance { grantee, allowance } => {
try_grant_allowance(deps, env, info, grantee, *allowance)
}
ExecuteMsg::RevokeAllowance { grantee } => try_revoke_grant(deps, env, info, grantee),
ExecuteMsg::UseAllowance { recipients } => try_use_allowance(deps, env, info, recipients),
ExecuteMsg::WithdrawAllowance { amount } => try_withdraw_allowance(deps, env, info, amount),
ExecuteMsg::LockAllowance { amount } => try_lock_allowance(deps, env, info, amount),
ExecuteMsg::UnlockAllowance { amount } => try_unlock_allowance(deps, env, info, amount),
ExecuteMsg::UseLockedAllowance { recipients } => {
try_use_locked_allowance(deps, env, info, recipients)
}
ExecuteMsg::WithdrawLockedAllowance { amount } => {
try_withdraw_locked_allowance(deps, env, info, amount)
}
ExecuteMsg::AddNewGranter { granter } => try_add_new_granter(deps, env, info, granter),
ExecuteMsg::RevokeGranter { granter } => try_revoke_granter(deps, env, info, granter),
ExecuteMsg::RemoveExpiredGrant { grantee } => try_remove_expired(deps, env, info, grantee),
}
}
#[entry_point]
pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<Binary, NymPoolContractError> {
match msg {
QueryMsg::Admin {} => Ok(to_json_binary(&query_admin(deps)?)?),
QueryMsg::GetAvailableTokens {} => Ok(to_json_binary(&query_available_tokens(deps, env)?)?),
QueryMsg::GetTotalLockedTokens {} => Ok(to_json_binary(&query_total_locked_tokens(deps)?)?),
QueryMsg::GetLockedTokens { grantee } => {
Ok(to_json_binary(&query_locked_tokens(deps, grantee)?)?)
}
QueryMsg::GetLockedTokensPaged { limit, start_after } => Ok(to_json_binary(
&query_locked_tokens_paged(deps, limit, start_after)?,
)?),
QueryMsg::GetGrant { grantee } => Ok(to_json_binary(&query_grant(deps, env, grantee)?)?),
QueryMsg::GetGranter { granter } => Ok(to_json_binary(&query_granter(deps, granter)?)?),
QueryMsg::GetGrantersPaged { limit, start_after } => Ok(to_json_binary(
&query_granters_paged(deps, limit, start_after)?,
)?),
QueryMsg::GetGrantsPaged { limit, start_after } => Ok(to_json_binary(
&query_grants_paged(deps, env, limit, start_after)?,
)?),
}
}
#[entry_point]
pub fn migrate(
deps: DepsMut,
_env: Env,
_msg: MigrateMsg,
) -> Result<Response, NymPoolContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
Ok(Default::default())
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
mod contract_instantiaton {
use super::*;
use crate::storage::NYM_POOL_STORAGE;
use crate::testing::TEST_DENOM;
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
#[test]
fn sets_contract_admin_to_the_message_sender() -> anyhow::Result<()> {
let mut deps = mock_dependencies();
let env = mock_env();
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants: Default::default(),
};
let some_sender = deps.api.addr_make("some_sender");
instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
)?;
NYM_POOL_STORAGE
.contract_admin
.assert_admin(deps.as_ref(), &some_sender)?;
Ok(())
}
#[test]
fn sets_the_pool_denomination() -> anyhow::Result<()> {
let mut deps = mock_dependencies();
let env = mock_env();
let init_msg = InstantiateMsg {
pool_denomination: "some_denom".to_string(),
grants: Default::default(),
};
let some_sender = deps.api.addr_make("some_sender");
instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
)?;
assert_eq!(
NYM_POOL_STORAGE
.pool_denomination
.load(deps.as_ref().storage)?,
"some_denom"
);
Ok(())
}
#[test]
fn adds_sender_to_set_of_initial_granters() -> anyhow::Result<()> {
let mut deps = mock_dependencies();
let env = mock_env();
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants: Default::default(),
};
let some_sender = deps.api.addr_make("some_sender");
instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
)?;
let granter = query_granter(deps.as_ref(), some_sender.to_string())?;
assert!(granter.information.is_some());
Ok(())
}
#[cfg(test)]
mod setting_initial_grants {
use super::*;
use cosmwasm_std::{coin, Order, Storage};
use nym_contracts_common_testing::deps_with_balance;
use nym_pool_contract_common::{Allowance, BasicAllowance, Grant, GranteeAddress};
use std::collections::HashMap;
fn all_grants(storage: &dyn Storage) -> HashMap<GranteeAddress, Grant> {
NYM_POOL_STORAGE
.grants
.range(storage, None, None, Order::Ascending)
.collect::<Result<HashMap<_, _>, _>>()
.unwrap()
}
#[test]
fn with_empty_map() -> anyhow::Result<()> {
let mut deps = mock_dependencies();
let env = mock_env();
let grants = HashMap::new();
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants,
};
let some_sender = deps.api.addr_make("some_sender");
instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
)?;
assert!(all_grants(&deps.storage).is_empty());
Ok(())
}
#[test]
fn with_insufficient_tokens() -> anyhow::Result<()> {
// limited grant
let mut deps = mock_dependencies();
let env = mock_env();
let mut grants = HashMap::new();
grants.insert(
deps.api.addr_make("grantee1").to_string(),
Allowance::Basic(BasicAllowance {
spend_limit: Some(coin(100, TEST_DENOM)),
expiration_unix_timestamp: None,
}),
);
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants,
};
let some_sender = deps.api.addr_make("some_sender");
let res = instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
);
assert!(res.is_err());
// unlimited grant
let mut deps = mock_dependencies();
let env = mock_env();
let mut grants = HashMap::new();
grants.insert(
deps.api.addr_make("grantee1").to_string(),
Allowance::Basic(BasicAllowance::unlimited()),
);
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants,
};
let some_sender = deps.api.addr_make("some_sender");
let res = instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[]),
init_msg,
);
assert!(res.is_err());
Ok(())
}
#[test]
fn with_valid_request() -> anyhow::Result<()> {
let env = mock_env();
let mut deps = deps_with_balance(&env);
let mut grants = HashMap::new();
grants.insert(
deps.api.addr_make("grantee1").to_string(),
Allowance::Basic(BasicAllowance {
spend_limit: Some(coin(100, TEST_DENOM)),
expiration_unix_timestamp: None,
}),
);
grants.insert(
deps.api.addr_make("grantee2").to_string(),
Allowance::Basic(BasicAllowance {
spend_limit: Some(coin(200, TEST_DENOM)),
expiration_unix_timestamp: None,
}),
);
grants.insert(
deps.api.addr_make("grantee3").to_string(),
Allowance::Basic(BasicAllowance {
spend_limit: Some(coin(300, TEST_DENOM)),
expiration_unix_timestamp: None,
}),
);
let init_msg = InstantiateMsg {
pool_denomination: TEST_DENOM.to_string(),
grants,
};
let some_sender = deps.api.addr_make("some_sender");
instantiate(
deps.as_mut(),
env,
message_info(&some_sender, &[coin(600, TEST_DENOM)]),
init_msg,
)?;
assert_eq!(all_grants(&deps.storage).len(), 3);
Ok(())
}
}
}
}