Skip to content

Commit 37559a0

Browse files
committed
feat(access-managed): init
1 parent d1bff50 commit 37559a0

File tree

40 files changed

+1967
-1349
lines changed

40 files changed

+1967
-1349
lines changed

Cargo.lock

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ members = [
253253
"cosmwasm/cw20-token-minter",
254254
"cosmwasm/cw-account",
255255
"cosmwasm/access-manager",
256+
"cosmwasm/access-managed",
256257
"cosmwasm/cw-escrow-vault",
257258
"cosmwasm/cw-unionversal-token",
258259
"cosmwasm/ucs03-zkgm-token-minter-api",
@@ -288,6 +289,7 @@ members = [
288289
"lib/cw20-ctx",
289290
"lib/ucs03-zkgmable",
290291
"lib/ucs03-solvable",
292+
"lib/access-manager-types",
291293
]
292294

293295
[workspace.package]
@@ -394,10 +396,11 @@ frissitheto = { path = "lib/frissitheto", default-features = false }
394396

395397
lst = { path = "cosmwasm/lst", default-features = false }
396398

399+
access-manager-types = { path = "lib/access-manager-types", default-features = false }
397400
access-manager = { path = "cosmwasm/access-manager", default-features = false }
398401

399-
cw-account = { path = "cosmwasm/cw-account", default-features = false }
400-
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
402+
cw-account = { path = "cosmwasm/cw-account", default-features = false }
403+
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
401404

402405
cw20-base = { path = "cosmwasm/cw20-base", default-features = false }
403406
cw20-ctx = { path = "lib/cw20-ctx", default-features = false }

cosmwasm/access-managed/Cargo.toml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
name = "access-managed"
3+
version = "0.0.0"
4+
5+
authors = { workspace = true }
6+
edition = { workspace = true }
7+
license-file = { workspace = true }
8+
publish = { workspace = true }
9+
repository = { workspace = true }
10+
11+
[lints]
12+
workspace = true
13+
14+
[lib]
15+
crate-type = ["cdylib", "rlib"]
16+
17+
[dependencies]
18+
access-manager-types = { workspace = true }
19+
bincode = { workspace = true, features = ["derive"] }
20+
cosmwasm-std = { workspace = true, features = ["cosmwasm_1_3", "staking"] }
21+
depolama = { workspace = true, features = ["iterator"] }
22+
embed-commit = { workspace = true }
23+
frissitheto = { workspace = true }
24+
serde = { workspace = true, features = ["derive"] }
25+
serde-json-wasm = "1.0.0"
26+
serde-utils = { workspace = true }
27+
serde_json = { workspace = true, features = ["raw_value"] }
28+
sha2 = { workspace = true }
29+
strum = { version = "0.27.2", features = ["derive"] }
30+
thiserror = { workspace = true }
31+
unionlabs-encoding = { workspace = true, features = ["bincode"] }
32+
unionlabs-primitives = { workspace = true, features = ["bincode", "serde", "generic-array-compat"] }
33+
34+
[features]
35+
default = []
36+
library = []
37+
38+
[dev-dependencies]
39+
hex-literal = { workspace = true }
40+
itertools = { workspace = true }
41+
42+
# TODO: Do something like this so the solidity codeblocks are highlighted correctly: https://users.rust-lang.org/t/how-did-slint-create-custom-syntax-highlight-link-and-preview-for-their-code-examples/85294/2
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
use access_manager_types::{
2+
managed::{error::AccessManagedError, event::AuthorityUpdated, msg::QueryMsg},
3+
Selector,
4+
};
5+
use cosmwasm_std::{Addr, Deps, DepsMut, Event, MessageInfo};
6+
use depolama::StorageExt;
7+
8+
use crate::{
9+
error::ContractError,
10+
state::{Authority, ConsumingSchedule},
11+
};
12+
13+
/// See [`QueryMsg::Authority`].
14+
pub(crate) fn authority(deps: Deps) -> Result<Addr, ContractError> {
15+
deps.storage.read_item::<Authority>().map_err(Into::into)
16+
}
17+
18+
/// See [`ExecuteMsg::SetAuthority`].
19+
pub(crate) fn set_authority(
20+
deps: DepsMut,
21+
info: MessageInfo,
22+
new_authority: Addr,
23+
) -> Result<Event, ContractError> {
24+
let caller = info.sender;
25+
26+
if caller != authority(deps.as_ref())? {
27+
return Err(AccessManagedError::AccessManagedUnauthorized { caller }.into());
28+
}
29+
30+
if deps
31+
.querier
32+
.query_wasm_contract_info(&new_authority)
33+
.is_err()
34+
{
35+
return Err(AccessManagedError::AccessManagedInvalidAuthority {
36+
authority: new_authority,
37+
}
38+
.into());
39+
}
40+
41+
Ok(_set_authority(deps, &new_authority))
42+
}
43+
44+
/// Transfers control to a new authority. Internal function with no access restriction. Allows
45+
/// bypassing the permissions set by the current authority.
46+
///
47+
/// ```solidity
48+
/// function _setAuthority(address newAuthority) internal virtual
49+
/// ```
50+
///
51+
/// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/access/manager/AccessManaged.sol#L86>
52+
#[allow(clippy::needless_pass_by_value)]
53+
pub(crate) fn _set_authority(deps: DepsMut, new_authority: &Addr) -> Event {
54+
deps.storage.write_item::<Authority>(new_authority);
55+
56+
AuthorityUpdated {
57+
authority: new_authority,
58+
}
59+
.into()
60+
}
61+
62+
/// See [`ExecuteMsg::IsConsumingScheduledOp`].
63+
pub(crate) fn is_consuming_scheduled_op(deps: Deps) -> Result<&'static Selector, ContractError> {
64+
if deps.storage.read_item::<ConsumingSchedule>()? {
65+
Ok(QueryMsg::IsConsumingScheduledOp {}.selector())
66+
} else {
67+
Ok(Selector::new(""))
68+
}
69+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
use access_manager_types::managed::error::AccessManagedError;
2+
use cosmwasm_std::StdError;
3+
use frissitheto::UpgradeError;
4+
5+
#[derive(Debug, PartialEq, thiserror::Error)]
6+
pub enum ContractError {
7+
#[error(transparent)]
8+
Std(#[from] StdError),
9+
10+
#[error(transparent)]
11+
Migrate(#[from] UpgradeError),
12+
13+
#[error(transparent)]
14+
AccessManaged(#[from] AccessManagedError),
15+
}

cosmwasm/access-managed/src/lib.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
//! CosmWasm implementation of [OpenZeppelin][oz]'s [`AccessManaged.sol`][am].
2+
//!
3+
//! This contract module makes available a [`Restricted<T>`] wrapper. This should wrap the
4+
//! `ExecuteMsg` for the contract. All entrypoints will be permissioned according to an "authority":
5+
//! a contract like `access_manager` that follows the [`ExecuteMsg`][me]/[`QueryMsg`][mq] interface,
6+
//! implementing a policy that allows certain callers to access certain functions.
7+
//!
8+
//! NOTE: The [`Restricted<T>`] wrapper will apply access control to *all* methods. Methods that
9+
//! should be public must be configured as such on the manager.
10+
//!
11+
//! [me]: access_manager_types::manager::msg::ExecuteMsg
12+
//! [mq]: access_manager_types::manager::msg::QueryMsg
13+
//! [oz]: https://www.openzeppelin.com
14+
//! [am]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/access/manager/AccessManaged.sol
15+
//! [et]: https://serde.rs/enum-representations.html#externally-tagged
16+
//! [selector]: https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector
17+
18+
#![warn(clippy::pedantic)]
19+
#![allow(
20+
clippy::used_underscore_items,
21+
clippy::missing_errors_doc,
22+
clippy::enum_glob_use,
23+
clippy::doc_markdown
24+
)]
25+
#![cfg_attr(not(test), warn(clippy::unwrap_used))]
26+
#![cfg_attr(test, allow(clippy::too_many_lines))]
27+
28+
use access_manager_types::managed::msg::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
29+
use cosmwasm_std::{
30+
to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError,
31+
};
32+
use depolama::StorageExt;
33+
use frissitheto::UpgradeMsg;
34+
35+
use crate::{
36+
contract::{authority, is_consuming_scheduled_op, set_authority},
37+
error::ContractError,
38+
state::{Authority, ConsumingSchedule},
39+
};
40+
41+
pub mod contract;
42+
pub mod error;
43+
pub mod managed;
44+
mod restricted;
45+
pub mod state;
46+
47+
pub use restricted::{
48+
EnsureCanCallResult, Restricted, ACCESS_MANAGED_CONSUME_SCHEDULED_OP_REPLY_ID,
49+
};
50+
51+
// #[cfg(test)]
52+
// mod tests;
53+
54+
pub const EXECUTE_REPLY_ID: u64 = 1;
55+
56+
/// Initializes the contract connected to an initial authority.
57+
///
58+
/// ```solidity
59+
/// constructor(address initialAdmin)
60+
/// ```
61+
///
62+
/// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/access/manager/AccessManaged.sol#L27>
63+
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
64+
pub fn init(deps: DepsMut, _: &Env, msg: InitMsg) -> Result<Response, ContractError> {
65+
let InitMsg { initial_authority } = msg;
66+
67+
deps.storage.write_item::<Authority>(&initial_authority);
68+
deps.storage.write_item::<ConsumingSchedule>(&false);
69+
70+
Ok(Response::new())
71+
}
72+
73+
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
74+
pub fn execute(
75+
deps: DepsMut,
76+
_: Env,
77+
info: MessageInfo,
78+
msg: ExecuteMsg,
79+
) -> Result<Response, ContractError> {
80+
match msg {
81+
ExecuteMsg::SetAuthority { new_authority } => {
82+
let event = set_authority(deps, info, new_authority)?;
83+
84+
Ok(Response::new().add_event(event))
85+
}
86+
}
87+
}
88+
89+
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
90+
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
91+
pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, ContractError> {
92+
match msg {
93+
QueryMsg::Authority {} => Ok(to_json_binary(&authority(deps)?)?),
94+
QueryMsg::IsConsumingScheduledOp {} => {
95+
Ok(to_json_binary(&is_consuming_scheduled_op(deps)?)?)
96+
}
97+
}
98+
}
99+
100+
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
101+
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
102+
pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> Result<Response, ContractError> {
103+
match reply {
104+
Reply {
105+
id: ACCESS_MANAGED_CONSUME_SCHEDULED_OP_REPLY_ID,
106+
result,
107+
..
108+
} => {
109+
result.unwrap();
110+
111+
deps.storage.write_item::<ConsumingSchedule>(&false);
112+
113+
Ok(Response::new())
114+
}
115+
_ => Err(StdError::generic_err("unknown reply: {reply:?}").into()),
116+
}
117+
}
118+
119+
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
120+
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
121+
pub fn migrate(
122+
deps: DepsMut,
123+
env: Env,
124+
msg: UpgradeMsg<InitMsg, MigrateMsg>,
125+
) -> Result<Response, ContractError> {
126+
msg.run(
127+
deps,
128+
|deps, msg| {
129+
let res = init(deps, &env, msg)?;
130+
Ok((res, None))
131+
},
132+
|_, _, _| Ok((Response::default(), None)),
133+
)
134+
}

0 commit comments

Comments
 (0)