diff --git a/Cargo.toml b/Cargo.toml index 08b8ae2..076572e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "account" + "contracts/*", ] [profile.release] @@ -9,18 +9,16 @@ overflow-checks = true # Disable integer overflow checks. [workspace.dependencies] -cosmos-sdk-proto = { version = "0.19", default-features = false } cosmwasm-schema = "=1.4.1" cosmwasm-std = { version = "=1.4.1", features = ["stargate"] } cw2 = "1.1.1" cw-storage-plus = "1.1.0" cw-utils = "1.0.2" hex = "0.4" -prost = "0.11" sha2 = { version = "0.10.8", features = ["oid"]} thiserror = "1" tiny-keccak = { version = "2", features = ["keccak"] } -serde = { version = "1.0.145", default-features = false, features = ["derive"] } +serde = { version = "1.0.203", default-features = false, features = ["derive"] } serde_json = "1.0.87" schemars = "0.8.10" ripemd = "0.1.3" @@ -30,3 +28,8 @@ phf = { version = "0.11.2", features = ["macros"] } 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"]} +osmosis-std-derive = "0.13.2" +prost-types = "0.12.6" +pbjson-types = "0.6.0" \ No newline at end of file diff --git a/README.md b/README.md index 8ec074e..c4e1df3 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ```bash -docker run --rm -v "$(pwd)":/code \ + docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/workspace-optimizer:0.14.0 + cosmwasm/optimizer:0.15.1 ``` \ No newline at end of file diff --git a/account/Cargo.toml b/contracts/account/Cargo.toml similarity index 84% rename from account/Cargo.toml rename to contracts/account/Cargo.toml index 1612b58..4f934f2 100644 --- a/account/Cargo.toml +++ b/contracts/account/Cargo.toml @@ -27,9 +27,5 @@ phf = { workspace = true } rsa = { workspace = true } getrandom = { workspace = true } p256 = { workspace = true } -url = "2.4.1" -coset = "0.3.5" -futures = "0.3.29" -async-trait = "0.1.74" -prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} -osmosis-std-derive = "0.13.2" +prost = { workspace = true } +osmosis-std-derive = { workspace = true } diff --git a/account/src/auth.rs b/contracts/account/src/auth.rs similarity index 100% rename from account/src/auth.rs rename to contracts/account/src/auth.rs diff --git a/account/src/auth/eth_crypto.rs b/contracts/account/src/auth/eth_crypto.rs similarity index 100% rename from account/src/auth/eth_crypto.rs rename to contracts/account/src/auth/eth_crypto.rs diff --git a/account/src/auth/jwt.rs b/contracts/account/src/auth/jwt.rs similarity index 97% rename from account/src/auth/jwt.rs rename to contracts/account/src/auth/jwt.rs index fde85c9..8500f9b 100644 --- a/account/src/auth/jwt.rs +++ b/contracts/account/src/auth/jwt.rs @@ -42,7 +42,7 @@ pub fn verify( let query = proto::QueryValidateJWTRequest { aud: aud.to_string(), sub: sub.to_string(), - sig_bytes: String::from_utf8(sig_bytes.into()).unwrap(), + sig_bytes: String::from_utf8(sig_bytes.into())?, // tx_hash: challenge, }; diff --git a/account/src/auth/passkey.rs b/contracts/account/src/auth/passkey.rs similarity index 100% rename from account/src/auth/passkey.rs rename to contracts/account/src/auth/passkey.rs diff --git a/account/src/auth/secp256r1.rs b/contracts/account/src/auth/secp256r1.rs similarity index 100% rename from account/src/auth/secp256r1.rs rename to contracts/account/src/auth/secp256r1.rs diff --git a/account/src/auth/sign_arb.rs b/contracts/account/src/auth/sign_arb.rs similarity index 100% rename from account/src/auth/sign_arb.rs rename to contracts/account/src/auth/sign_arb.rs diff --git a/account/src/auth/util.rs b/contracts/account/src/auth/util.rs similarity index 100% rename from account/src/auth/util.rs rename to contracts/account/src/auth/util.rs diff --git a/account/src/contract.rs b/contracts/account/src/contract.rs similarity index 100% rename from account/src/contract.rs rename to contracts/account/src/contract.rs diff --git a/account/src/error.rs b/contracts/account/src/error.rs similarity index 93% rename from account/src/error.rs rename to contracts/account/src/error.rs index cbfc309..e92cfb5 100644 --- a/account/src/error.rs +++ b/contracts/account/src/error.rs @@ -71,6 +71,12 @@ pub enum ContractError { #[error("cannot override existing authenticator at index {index}")] OverridingIndex { index: u8 }, + + #[error(transparent)] + SerdeJSON(#[from] serde_json::Error), + + #[error(transparent)] + FromUTF8(#[from] std::string::FromUtf8Error), } pub type ContractResult = Result; diff --git a/account/src/execute.rs b/contracts/account/src/execute.rs similarity index 97% rename from account/src/execute.rs rename to contracts/account/src/execute.rs index 9a5a7d0..2e9ba7f 100644 --- a/account/src/execute.rs +++ b/contracts/account/src/execute.rs @@ -19,10 +19,7 @@ pub fn init( Ok( Response::new().add_event(Event::new("create_abstract_account").add_attributes(vec![ ("contract_address", env.contract.address.to_string()), - ( - "authenticator", - serde_json::to_string(&add_authenticator).unwrap(), - ), + ("authenticator", serde_json::to_string(&add_authenticator)?), ("authenticator_id", add_authenticator.get_id().to_string()), ])), ) @@ -227,10 +224,7 @@ pub fn add_auth_method( Ok( Response::new().add_event(Event::new("add_auth_method").add_attributes(vec![ ("contract_address", env.contract.address.clone().to_string()), - ( - "authenticator", - serde_json::to_string(&add_authenticator).unwrap(), - ), + ("authenticator", serde_json::to_string(&add_authenticator)?), ])), ) } diff --git a/account/src/lib.rs b/contracts/account/src/lib.rs similarity index 100% rename from account/src/lib.rs rename to contracts/account/src/lib.rs diff --git a/account/src/msg.rs b/contracts/account/src/msg.rs similarity index 100% rename from account/src/msg.rs rename to contracts/account/src/msg.rs diff --git a/account/src/proto.rs b/contracts/account/src/proto.rs similarity index 100% rename from account/src/proto.rs rename to contracts/account/src/proto.rs diff --git a/account/src/query.rs b/contracts/account/src/query.rs similarity index 100% rename from account/src/query.rs rename to contracts/account/src/query.rs diff --git a/account/src/state.rs b/contracts/account/src/state.rs similarity index 100% rename from account/src/state.rs rename to contracts/account/src/state.rs diff --git a/contracts/treasury/Cargo.toml b/contracts/treasury/Cargo.toml new file mode 100644 index 0000000..774274a --- /dev/null +++ b/contracts/treasury/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "treasury" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw2 = { workspace = true } +cw-storage-plus = { workspace = true } +thiserror = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +schemars = { workspace = true } +cosmos-sdk-proto = { workspace = true } +prost = { workspace = true } +osmosis-std-derive = { workspace = true } +prost-types = { workspace = true } +pbjson-types = { workspace = true } \ No newline at end of file diff --git a/contracts/treasury/src/contract.rs b/contracts/treasury/src/contract.rs new file mode 100644 index 0000000..10b7090 --- /dev/null +++ b/contracts/treasury/src/contract.rs @@ -0,0 +1,53 @@ +use crate::error::ContractResult; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::{execute, query, CONTRACT_NAME, CONTRACT_VERSION}; +use cosmwasm_std::{ + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; + +#[entry_point] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> ContractResult { + cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + execute::init(deps, info, msg.admin, msg.type_urls, msg.grant_configs) +} + +#[entry_point] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> ContractResult { + match msg { + ExecuteMsg::DeployFeeGrant { + authz_granter, + authz_grantee, + msg_type_url, + } => execute::deploy_fee_grant(deps, env, authz_granter, authz_grantee, msg_type_url), + ExecuteMsg::UpdateAdmin { new_admin } => execute::update_admin(deps, info, new_admin), + ExecuteMsg::UpdateGrantConfig { + msg_type_url, + grant_config, + } => execute::update_grant_config(deps, info, msg_type_url, grant_config), + ExecuteMsg::RemoveGrantConfig { msg_type_url } => { + execute::remove_grant_config(deps, info, msg_type_url) + } + } +} + +#[entry_point] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GrantConfigByTypeUrl { msg_type_url } => to_binary( + &query::grant_config_by_type_url(deps.storage, msg_type_url)?, + ), + QueryMsg::GrantConfigTypeUrls {} => { + to_binary(&query::grant_config_type_urls(deps.storage)?) + } + } +} diff --git a/contracts/treasury/src/error.rs b/contracts/treasury/src/error.rs new file mode 100644 index 0000000..8aedc8e --- /dev/null +++ b/contracts/treasury/src/error.rs @@ -0,0 +1,31 @@ +#[derive(Debug, thiserror::Error)] +pub enum ContractError { + #[error(transparent)] + Std(#[from] cosmwasm_std::StdError), + + #[error(transparent)] + Encode(#[from] cosmos_sdk_proto::prost::EncodeError), + + #[error("authz grant not found")] + AuthzGrantNotFound, + + #[error("authz grant has no authorization")] + AuthzGrantNoAuthorization, + + #[error("authz grant did not match config")] + AuthzGrantMismatch, + + #[error("invalid allowance type: {msg_type_url}")] + InvalidAllowanceType { msg_type_url: String }, + + #[error("allowance unset")] + AllowanceUnset, + + #[error("config mismatch")] + ConfigurationMismatch, + + #[error("unauthorized")] + Unauthorized, +} + +pub type ContractResult = Result; diff --git a/contracts/treasury/src/execute.rs b/contracts/treasury/src/execute.rs new file mode 100644 index 0000000..1af5918 --- /dev/null +++ b/contracts/treasury/src/execute.rs @@ -0,0 +1,215 @@ +use crate::error::ContractError::{ + self, 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; + +pub fn init( + deps: DepsMut, + info: MessageInfo, + admin: Option, + type_urls: Vec, + grant_configs: Vec, +) -> ContractResult { + let treasury_admin = match admin { + None => info.sender, + Some(adm) => adm, + }; + ADMIN.save(deps.storage, &treasury_admin)?; + + if type_urls.len().ne(&grant_configs.len()) { + return Err(ConfigurationMismatch); + } + + for i in 0..type_urls.len() { + GRANT_CONFIGS.save(deps.storage, type_urls[i].clone(), &grant_configs[i])?; + } + + Ok(Response::new().add_event( + Event::new("create_treasury_instance") + .add_attributes(vec![("admin", treasury_admin.into_string())]), + )) +} + +pub fn update_admin(deps: DepsMut, info: MessageInfo, new_admin: Addr) -> ContractResult { + let admin = ADMIN.load(deps.storage)?; + if admin != info.sender { + return Err(Unauthorized); + } + + ADMIN.save(deps.storage, &new_admin)?; + + Ok( + Response::new().add_event(Event::new("updated_treasury_admin").add_attributes(vec![ + ("old admin", admin.into_string()), + ("new admin", new_admin.into_string()), + ])), + ) +} + +pub fn update_grant_config( + deps: DepsMut, + info: MessageInfo, + msg_type_url: String, + grant_config: GrantConfig, +) -> ContractResult { + let admin = ADMIN.load(deps.storage)?; + if admin != info.sender { + return Err(Unauthorized); + } + + let existed = GRANT_CONFIGS.has(deps.storage, msg_type_url.clone()); + + GRANT_CONFIGS.save(deps.storage, msg_type_url.clone(), &grant_config)?; + + Ok(Response::new().add_event( + Event::new("updated_treasury_grant_config").add_attributes(vec![ + ("msg type url", msg_type_url), + ("overwritten", existed.to_string()), + ]), + )) +} + +pub fn remove_grant_config( + deps: DepsMut, + info: MessageInfo, + msg_type_url: String, +) -> ContractResult { + let admin = ADMIN.load(deps.storage)?; + if admin != info.sender { + return Err(Unauthorized); + } + + GRANT_CONFIGS.remove(deps.storage, msg_type_url.clone()); + + Ok(Response::new().add_event( + Event::new("removed_treasury_grant_config") + .add_attributes(vec![("msg type url", msg_type_url)]), + )) +} + +pub fn deploy_fee_grant( + deps: DepsMut, + env: Env, + authz_granter: Addr, + authz_grantee: Addr, + msg_type_url: String, +) -> ContractResult { + // 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::(&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); + } + + let auth = &grant["authorization"]; + if auth.is_null() { + return Err(AuthzGrantNoAuthorization); + } + + if grant_config.authorization.ne(auth) { + return Err(AuthzGrantMismatch); + } + + // create feegrant, if needed + match grant_config.allowance { + 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::::new(t.to_string())) { + Ok(tm) => Timestamp { + seconds: tm.seconds, + nanos: tm.nanos, + }, + Err(_) => Timestamp::default(), + } + }); + + let max_expiration = match grant_config.max_duration { + None => None, + Some(duration) => { + let max_timestamp = env.block.time.plus_seconds(duration as u64); + Some(Timestamp { + seconds: max_timestamp.seconds() as i64, + nanos: max_timestamp.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 { + type_url: "/cosmos.feegrant.v1beta1.MsgGrantAllowance".to_string(), + value: feegrant_msg_bytes.into(), + }; + Ok(Response::new().add_message(cosmos_msg)) + } + } +} diff --git a/contracts/treasury/src/grant.rs b/contracts/treasury/src/grant.rs new file mode 100644 index 0000000..119d529 --- /dev/null +++ b/contracts/treasury/src/grant.rs @@ -0,0 +1,38 @@ +pub mod allowance; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; +use prost::bytes::Bytes; +use serde_json::Value; + +#[cw_serde] +pub struct GrantConfig { + description: String, + pub authorization: Value, + pub allowance: Option, + pub max_duration: Option, +} + +#[cw_serde] +pub struct Any { + pub type_url: String, + pub value: Binary, +} + +impl From for Any { + fn from(value: pbjson_types::Any) -> Self { + Any { + type_url: value.type_url, + value: Binary::from(value.value.to_vec()), + } + } +} + +impl From for pbjson_types::Any { + fn from(value: Any) -> Self { + pbjson_types::Any { + type_url: value.type_url, + value: Bytes::copy_from_slice(value.value.as_slice()), + } + } +} diff --git a/contracts/treasury/src/grant/allowance.rs b/contracts/treasury/src/grant/allowance.rs new file mode 100644 index 0000000..3c3f779 --- /dev/null +++ b/contracts/treasury/src/grant/allowance.rs @@ -0,0 +1,178 @@ +use crate::error::ContractError::{self, AllowanceUnset, InvalidAllowanceType}; +use crate::error::ContractResult; +use crate::grant::Any; +use cosmos_sdk_proto::cosmos::feegrant::v1beta1::{ + AllowedMsgAllowance, BasicAllowance, PeriodicAllowance, +}; +use cosmos_sdk_proto::prost::Message; +use cosmos_sdk_proto::traits::MessageExt; +use cosmos_sdk_proto::xion::v1::{AuthzAllowance, ContractsAllowance}; +use cosmwasm_std::Addr; +use pbjson_types::Timestamp; + +pub fn format_allowance( + allowance_any: Any, + _granter: Addr, + grantee: Addr, + expiration: Option, +) -> ContractResult { + let formatted_allowance: Any = match allowance_any.type_url.as_str() { + "/cosmos.feegrant.v1beta1.BasicAllowance" => match expiration.clone() { + None => allowance_any, + Some(_) => { + let mut allowance = BasicAllowance::decode::<&[u8]>(allowance_any.value.as_slice()) + .map_err(|err| { + ContractError::Std(cosmwasm_std::StdError::ParseErr { + target_type: "Basic Allowance".to_string(), + msg: err.to_string(), + }) + })?; + allowance.expiration = expiration; + let allowance_bz = match allowance.to_bytes() { + Ok(bz) => bz, + Err(_) => { + return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr { + source_type: String::from("BasicAllowance"), + msg: "unable to serialize basic allowance".to_string(), + })) + } + }; + Any { + type_url: allowance_any.type_url, + value: allowance_bz.into(), + } + } + }, + + "/cosmos.feegrant.v1beta1.PeriodicAllowance" => match expiration.clone() { + None => allowance_any, + Some(_) => { + let mut allowance = PeriodicAllowance::decode::<&[u8]>( + allowance_any.value.as_slice(), + ) + .map_err(|err| { + ContractError::Std(cosmwasm_std::StdError::ParseErr { + target_type: "Periodic Allowance".to_string(), + msg: err.to_string(), + }) + })?; + let mut inner_basic = allowance.basic.clone().ok_or(AllowanceUnset)?; + inner_basic.expiration = expiration; + allowance.basic = Some(inner_basic); + let allowance_bz = match allowance.to_bytes() { + Ok(bz) => bz, + Err(_) => { + return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr { + source_type: String::from("PeriodicAllowance"), + msg: "unable to serialize periodic allowance".to_string(), + })) + } + }; + Any { + type_url: allowance_any.type_url, + value: allowance_bz.into(), + } + } + }, + + "/cosmos.feegrant.v1beta1.AllowedMsgAllowance" => { + let mut allowance = AllowedMsgAllowance::decode::<&[u8]>( + allowance_any.value.as_slice(), + ) + .map_err(|err| { + ContractError::Std(cosmwasm_std::StdError::ParseErr { + target_type: "Allowed Msg Allowance".to_string(), + msg: err.to_string(), + }) + })?; + let inner_allowance = format_allowance( + allowance.allowance.ok_or(AllowanceUnset)?.into(), + _granter, + grantee, + expiration, + )?; + allowance.allowance = Some(inner_allowance.into()); + let allowance_bz = match allowance.to_bytes() { + Ok(bz) => bz, + Err(_) => { + return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr { + source_type: String::from("AllowedMsgAllowance"), + msg: "unable to serialize allowed msg allowance".to_string(), + })) + } + }; + Any { + type_url: allowance_any.type_url, + value: allowance_bz.into(), + } + } + + "/xion.v1.AuthzAllowance" => { + let mut allowance = AuthzAllowance::decode::<&[u8]>(allowance_any.value.as_slice()) + .map_err(|err| { + ContractError::Std(cosmwasm_std::StdError::ParseErr { + target_type: "Authz Allowance".to_string(), + msg: err.to_string(), + }) + })?; + let inner_allowance = format_allowance( + allowance.allowance.ok_or(AllowanceUnset)?.into(), + _granter, + grantee.clone(), + expiration, + )?; + allowance.allowance = Some(inner_allowance.into()); + allowance.authz_grantee = grantee.into_string(); + let allowance_bz = match allowance.to_bytes() { + Ok(bz) => bz, + Err(_) => { + return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr { + source_type: String::from("AuthzAllowance"), + msg: "unable to serialize authz allowance".to_string(), + })) + } + }; + Any { + type_url: allowance_any.type_url, + value: allowance_bz.into(), + } + } + + "/xion.v1.ContractsAllowance" => { + let mut allowance = ContractsAllowance::decode::<&[u8]>(allowance_any.value.as_slice()) + .map_err(|err| { + ContractError::Std(cosmwasm_std::StdError::ParseErr { + target_type: "Contract Allowance".to_string(), + msg: err.to_string(), + }) + })?; + let inner_allowance = format_allowance( + allowance.allowance.ok_or(AllowanceUnset)?.into(), + _granter, + grantee.clone(), + expiration, + )?; + allowance.allowance = Some(inner_allowance.into()); + let allowance_bz = match allowance.to_bytes() { + Ok(bz) => bz, + Err(_) => { + return Err(ContractError::Std(cosmwasm_std::StdError::SerializeErr { + source_type: String::from("ContractAllowance"), + msg: "unable to serialize contract allowance".to_string(), + })) + } + }; + Any { + type_url: allowance_any.type_url, + value: allowance_bz.into(), + } + } + _ => { + return Err(InvalidAllowanceType { + msg_type_url: allowance_any.type_url, + }) + } + }; + + Ok(formatted_allowance) +} diff --git a/contracts/treasury/src/lib.rs b/contracts/treasury/src/lib.rs new file mode 100644 index 0000000..b7f80b0 --- /dev/null +++ b/contracts/treasury/src/lib.rs @@ -0,0 +1,14 @@ +extern crate core; + +#[cfg(not(feature = "library"))] +pub mod contract; +mod error; +mod execute; +mod msg; +mod state; + +mod grant; +mod query; + +pub const CONTRACT_NAME: &str = "treasury"; +pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/contracts/treasury/src/msg.rs b/contracts/treasury/src/msg.rs new file mode 100644 index 0000000..9b2b120 --- /dev/null +++ b/contracts/treasury/src/msg.rs @@ -0,0 +1,40 @@ +use crate::grant::GrantConfig; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary}; + +#[cw_serde] +pub struct InstantiateMsg { + pub admin: Option, + pub type_urls: Vec, + pub grant_configs: Vec, +} + +#[cw_serde] +pub enum ExecuteMsg { + UpdateAdmin { + new_admin: Addr, + }, + UpdateGrantConfig { + msg_type_url: String, + grant_config: GrantConfig, + }, + RemoveGrantConfig { + msg_type_url: String, + }, + DeployFeeGrant { + authz_granter: Addr, + authz_grantee: Addr, + msg_type_url: String, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + /// Query the grant config by type url + #[returns(Binary)] + GrantConfigByTypeUrl { msg_type_url: String }, + + #[returns(Binary)] + GrantConfigTypeUrls {}, +} diff --git a/contracts/treasury/src/query.rs b/contracts/treasury/src/query.rs new file mode 100644 index 0000000..0a4390c --- /dev/null +++ b/contracts/treasury/src/query.rs @@ -0,0 +1,17 @@ +use crate::grant::GrantConfig; +use crate::state::GRANT_CONFIGS; +use cosmwasm_std::{Order, StdResult, Storage}; + +pub fn grant_config_type_urls(store: &dyn Storage) -> StdResult> { + Ok(GRANT_CONFIGS + .keys(store, None, None, Order::Ascending) + .map(|k| k.unwrap()) + .collect()) +} + +pub fn grant_config_by_type_url( + store: &dyn Storage, + msg_type_url: String, +) -> StdResult { + GRANT_CONFIGS.load(store, msg_type_url) +} diff --git a/contracts/treasury/src/state.rs b/contracts/treasury/src/state.rs new file mode 100644 index 0000000..a4dc05b --- /dev/null +++ b/contracts/treasury/src/state.rs @@ -0,0 +1,8 @@ +use crate::grant::GrantConfig; +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +// msg_type_url to grant config +pub const GRANT_CONFIGS: Map = Map::new("grant_configs"); + +pub const ADMIN: Item = Item::new("admin");