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

update treasury for single allowance and multi-any #27

Merged
merged 24 commits into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ rsa = { version = "0.9.2" }
getrandom = { version = "0.2.10", features = ["custom"] }
p256 = {version = "0.13.2", features = ["ecdsa-core", "arithmetic", "serde"]}
prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]}
cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "9108ae0517bd9fd543c0662e06598032a642e426", default-features = false, features = ["cosmwasm", "xion"]}
cosmos-sdk-proto = {git = "https://github.com/burnt-labs/cosmos-rust.git", rev = "b50389f3988ba932e07f4fdee48d5928a1ed675e", default-features = false, features = ["cosmwasm", "xion"]}
osmosis-std-derive = "0.13.2"
prost-types = "0.12.6"
pbjson-types = "0.6.0"
17 changes: 14 additions & 3 deletions contracts/treasury/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::error::ContractResult;
use crate::execute::{revoke_allowance, update_fee_config};
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};
use crate::{execute, query, CONTRACT_NAME, CONTRACT_VERSION};
use cosmwasm_std::{
Expand All @@ -13,7 +14,14 @@ pub fn instantiate(
msg: InstantiateMsg,
) -> ContractResult<Response> {
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
execute::init(deps, info, msg.admin, msg.type_urls, msg.grant_configs)
execute::init(
deps,
info,
msg.admin,
msg.type_urls,
msg.grant_configs,
msg.fee_config,
)
}

#[entry_point]
Expand All @@ -27,8 +35,7 @@ pub fn execute(
ExecuteMsg::DeployFeeGrant {
authz_granter,
authz_grantee,
msg_type_url,
} => execute::deploy_fee_grant(deps, env, authz_granter, authz_grantee, msg_type_url),
} => execute::deploy_fee_grant(deps, env, authz_granter, authz_grantee),
ExecuteMsg::UpdateAdmin { new_admin } => execute::update_admin(deps, info, new_admin),
ExecuteMsg::UpdateGrantConfig {
msg_type_url,
Expand All @@ -37,6 +44,8 @@ pub fn execute(
ExecuteMsg::RemoveGrantConfig { msg_type_url } => {
execute::remove_grant_config(deps, info, msg_type_url)
}
ExecuteMsg::UpdateFeeConfig { fee_config } => update_fee_config(deps, info, fee_config),
ExecuteMsg::RevokeAllowance { grantee } => revoke_allowance(deps, env, info, grantee),
}
}

Expand All @@ -49,5 +58,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
QueryMsg::GrantConfigTypeUrls {} => {
to_json_binary(&query::grant_config_type_urls(deps.storage)?)
}
QueryMsg::FeeConfig {} => to_json_binary(&query::fee_config(deps.storage)?),
QueryMsg::Admin {} => to_json_binary(&query::admin(deps.storage)?),
}
}
7 changes: 5 additions & 2 deletions contracts/treasury/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ pub enum ContractError {
#[error(transparent)]
Encode(#[from] cosmos_sdk_proto::prost::EncodeError),

#[error("authz grant not found")]
AuthzGrantNotFound,
#[error(transparent)]
Decode(#[from] cosmos_sdk_proto::prost::DecodeError),

#[error("authz grant not found, msg_type: {msg_type_url}")]
AuthzGrantNotFound { msg_type_url: String },

#[error("authz grant has no authorization")]
AuthzGrantNoAuthorization,
Expand Down
256 changes: 163 additions & 93 deletions contracts/treasury/src/execute.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
use cosmos_sdk_proto::cosmos::authz::v1beta1::{QueryGrantsRequest, QueryGrantsResponse};

Check warning on line 1 in contracts/treasury/src/execute.rs

View workflow job for this annotation

GitHub Actions / Test Suite

unused import: `QueryGrantsResponse`
use cosmos_sdk_proto::cosmos::feegrant::v1beta1::{QueryAllowanceRequest, QueryAllowanceResponse};
use cosmos_sdk_proto::traits::MessageExt;
use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Env, Event, MessageInfo, Order, Response};
use pbjson_types::Timestamp;
use serde_json::Value;

use crate::error::ContractError::{
self, AuthzGrantMismatch, AuthzGrantNoAuthorization, AuthzGrantNotFound, ConfigurationMismatch,
AuthzGrantMismatch, AuthzGrantNoAuthorization, AuthzGrantNotFound, ConfigurationMismatch,
Unauthorized,
};
use crate::error::ContractResult;
use crate::grant::allowance::format_allowance;
use crate::grant::GrantConfig;
use crate::state::{ADMIN, GRANT_CONFIGS};
use cosmos_sdk_proto::cosmos::authz::v1beta1::QueryGrantsRequest;
use cosmos_sdk_proto::tendermint::serializers::timestamp;
use cosmos_sdk_proto::traits::MessageExt;
use cosmwasm_std::{Addr, CosmosMsg, DepsMut, Env, Event, MessageInfo, Response};
use pbjson_types::Timestamp;
use serde::de::value::{Error, StringDeserializer};
use serde_json::Value;
use crate::grant::{FeeConfig, GrantConfig};
use crate::state::{ADMIN, FEE_CONFIG, GRANT_CONFIGS};

pub fn init(
deps: DepsMut,
info: MessageInfo,
admin: Option<Addr>,
type_urls: Vec<String>,
grant_configs: Vec<GrantConfig>,
fee_config: FeeConfig,
) -> ContractResult<Response> {
let treasury_admin = match admin {
None => info.sender,
Expand All @@ -34,6 +35,10 @@
for i in 0..type_urls.len() {
GRANT_CONFIGS.save(deps.storage, type_urls[i].clone(), &grant_configs[i])?;
}

FEE_CONFIG.save(deps.storage, &fee_config)?;

FEE_CONFIG.save(deps.storage, &fee_config)?;
ash-burnt marked this conversation as resolved.
Show resolved Hide resolved

Ok(Response::new().add_event(
Event::new("create_treasury_instance")
Expand Down Expand Up @@ -98,118 +103,183 @@
))
}

pub fn update_fee_config(
deps: DepsMut,
info: MessageInfo,
fee_config: FeeConfig,
) -> ContractResult<Response> {
let admin = ADMIN.load(deps.storage)?;
if admin != info.sender {
return Err(Unauthorized);
}

FEE_CONFIG.save(deps.storage, &fee_config)?;

Ok(Response::new().add_event(Event::new("updated_treasury_fee_config")))
}

pub fn deploy_fee_grant(
deps: DepsMut,
env: Env,
authz_granter: Addr,
authz_grantee: Addr,
msg_type_url: String,
) -> ContractResult<Response> {
// check if grant exists in patterns on contract
let grant_config = GRANT_CONFIGS.load(deps.storage, msg_type_url.clone())?;

// check if grant exists on chain
let query_msg = QueryGrantsRequest {
granter: authz_granter.to_string(),
grantee: authz_grantee.to_string(),
msg_type_url: msg_type_url.clone(),
pagination: None,
};
let query_msg_bytes = match query_msg.to_bytes() {
Ok(bz) => bz,
Err(_) => {
return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr {
source_type: String::from("QueryGrantsRequest"),
msg: "Unable to serialize QueryGrantsRequest".to_string(),
}))
}
};
let query_res = deps
.querier
.query::<Value>(&cosmwasm_std::QueryRequest::Stargate {
path: "/cosmos.authz.v1beta1.Query/Grants".to_string(),
data: query_msg_bytes.into(),
})?;

let grants = &query_res["grants"];
// grant queries with a granter, grantee and type_url should always result
// in only one result
if !grants.is_array() {
return Err(AuthzGrantNotFound);
}
let grant = grants[0].clone();
if grant.is_null() {
return Err(AuthzGrantNotFound);
}
// iterate through all grant configs to validate user has correct permissions
// we must iterate, because calling for the list of grants doesn't return msg_type_urls
for key in GRANT_CONFIGS.keys(deps.storage, None, None, Order::Ascending) {
let msg_type_url = key?;
let grant_config = GRANT_CONFIGS.load(deps.storage, msg_type_url.clone())?;

let auth = &grant["authorization"];
if auth.is_null() {
return Err(AuthzGrantNoAuthorization);
}
// check if grant exists on chain
let authz_query_msg_bytes = QueryGrantsRequest {
granter: authz_granter.to_string(),
grantee: authz_grantee.to_string(),
msg_type_url: msg_type_url.clone(),
pagination: None,
}
.to_bytes()?;
let authz_query_res =
deps.querier
.query::<Value>(&cosmwasm_std::QueryRequest::Stargate {
path: "/cosmos.authz.v1beta1.Query/Grants".to_string(),
data: authz_query_msg_bytes.into(),
})?;

if grant_config.authorization.ne(auth) {
return Err(AuthzGrantMismatch);
let grants = &authz_query_res["grants"];
// grant queries with a granter, grantee and type_url should always result
// in only one result, unless the grant is optional
if !grants.is_array() {
return Err(AuthzGrantNotFound { msg_type_url });
}
let grant = grants[0].clone();
if grant.is_null() {
return Err(AuthzGrantNotFound { msg_type_url });
}
let auth = &grant["authorization"];
if auth.is_null() {
return Err(AuthzGrantNoAuthorization);
}
if grant_config.authorization.ne(auth) {
return Err(AuthzGrantMismatch);
}
// if grants.clone().is_empty() && !grant_config.optional {
// return Err(AuthzGrantNotFound { msg_type_url });
// } else {
// match grants.first() {
// None => return Err(AuthzGrantNotFound { msg_type_url }),
// Some(grant) => {
// match grant.clone().authorization {
// None => return Err(AuthzGrantNotFound { msg_type_url }),
// Some(auth) => {
// // the authorization must match the one in the config
// if grant_config.authorization.ne(&auth.into()) {
// return Err(AuthzGrantMismatch);
// }
// }
// }
// }
// }
// }
}
// at this point, all the authz grants in the grant_config are verified

let fee_config = FEE_CONFIG.load(deps.storage)?;
// create feegrant, if needed
match grant_config.allowance {
match fee_config.allowance {
// this treasury doesn't deploy any fees, and can return
None => Ok(Response::new()),
// allowance should be stored as a prost proto from the feegrant definition
Some(allowance) => {
let grant_expiration =
grant["expiration"].as_str().map(|t| {
match timestamp::deserialize(StringDeserializer::<Error>::new(t.to_string())) {
Ok(tm) => Timestamp {
seconds: tm.seconds,
nanos: tm.nanos,
},
Err(_) => Timestamp::default(),
}
});

let max_expiration = match grant_config.max_duration {
// build the new allowance based on expiration
let expiration = match fee_config.expiration {
None => None,
Some(duration) => {
let max_timestamp = env.block.time.plus_seconds(duration as u64);
Some(seconds) => {
let expiration_time = env.block.time.plus_seconds(seconds as u64);
Some(Timestamp {
seconds: max_timestamp.seconds() as i64,
nanos: max_timestamp.nanos() as i32,
seconds: expiration_time.seconds() as i64,
nanos: expiration_time.subsec_nanos() as i32,
})
}
};

let expiration = match grant_expiration {
None => max_expiration,
Some(grant_expiration) => match max_expiration {
None => Some(grant_expiration),
Some(max_expiration) => {
if max_expiration.seconds < grant_expiration.seconds {
Some(max_expiration)
} else {
Some(grant_expiration)
}
}
},
};

let formatted_allowance = format_allowance(
allowance,
env.contract.address.clone(),
authz_grantee.clone(),
expiration,
)?;
let feegrant_msg = cosmos_sdk_proto::cosmos::feegrant::v1beta1::MsgGrantAllowance {
granter: env.contract.address.into_string(),
grantee: authz_grantee.into_string(),
allowance: Some(formatted_allowance.into()),
};
let feegrant_msg_bytes = feegrant_msg.to_bytes()?;
// todo: what if a feegrant already exists?
let cosmos_msg = CosmosMsg::Stargate {
let feegrant_msg_bytes =
cosmos_sdk_proto::cosmos::feegrant::v1beta1::MsgGrantAllowance {
granter: env.contract.address.clone().into_string(),
grantee: authz_grantee.clone().into_string(),
allowance: Some(formatted_allowance.into()),
}
.to_bytes()?;
let cosmos_feegrant_msg = CosmosMsg::Stargate {
type_url: "/cosmos.feegrant.v1beta1.MsgGrantAllowance".to_string(),
value: feegrant_msg_bytes.into(),
};
Ok(Response::new().add_message(cosmos_msg))

// check to see if the user already has an existing feegrant
let feegrant_query_msg_bytes = QueryAllowanceRequest {
granter: authz_granter.to_string(),
grantee: authz_grantee.to_string(),
}
.to_bytes()?;
let feegrant_query_res = deps
.querier
.query::<QueryAllowanceResponse>(&cosmwasm_std::QueryRequest::Stargate {
path: "/cosmos.feegrant.v1beta1.Query/Allowance".to_string(),
data: feegrant_query_msg_bytes.into(),
})
.unwrap_or_default();

let mut msgs: Vec<CosmosMsg> = Vec::new();
if feegrant_query_res.allowance.is_some() {
let feegrant_revoke_msg_bytes =
cosmos_sdk_proto::cosmos::feegrant::v1beta1::MsgRevokeAllowance {
granter: env.contract.address.clone().into_string(),
grantee: authz_grantee.clone().into_string(),
}
.to_bytes()?;
let cosmos_revoke_msg = CosmosMsg::Stargate {
type_url: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance".to_string(),
value: feegrant_revoke_msg_bytes.into(),
};
msgs.push(cosmos_revoke_msg);
}
msgs.push(cosmos_feegrant_msg);
Ok(Response::new().add_messages(msgs))
}
}
}

pub fn revoke_allowance(
deps: DepsMut,
env: Env,
info: MessageInfo,
grantee: Addr,
) -> ContractResult<Response> {
let admin = ADMIN.load(deps.storage)?;
if admin != info.sender {
return Err(Unauthorized);
}

let feegrant_revoke_msg_bytes =
cosmos_sdk_proto::cosmos::feegrant::v1beta1::MsgRevokeAllowance {
granter: env.contract.address.into_string(),
grantee: grantee.clone().into_string(),
}
.to_bytes()?;
let cosmos_feegrant_revoke_msg = CosmosMsg::Stargate {
type_url: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance".to_string(),
value: feegrant_revoke_msg_bytes.into(),
};

Ok(Response::new()
.add_message(cosmos_feegrant_revoke_msg)
.add_event(
Event::new("revoked_treasury_allowance")
.add_attributes(vec![("grantee", grantee.into_string())]),
))
}
Loading
Loading