Skip to content

Commit e07593b

Browse files
committed
feat(access-manager): e2e test for scheduling
wip wip wip wip
1 parent 37559a0 commit e07593b

File tree

27 files changed

+1898
-246
lines changed

27 files changed

+1898
-246
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ members = [
290290
"lib/ucs03-zkgmable",
291291
"lib/ucs03-solvable",
292292
"lib/access-manager-types",
293+
"e2e/access-manager-tests",
294+
"e2e/access-managed-example",
293295
]
294296

295297
[workspace.package]
@@ -392,15 +394,16 @@ ibc-union = { path = "cosmwasm/ibc-union/core", default-features =
392394
ibc-union-light-client = { path = "cosmwasm/ibc-union/core/light-client-interface", default-features = false }
393395
ibc-union-msg = { path = "cosmwasm/ibc-union/core/msg", default-features = false }
394396

397+
access-managed = { path = "cosmwasm/access-managed", default-features = false }
398+
access-managed-example = { path = "e2e/access-managed-example", default-features = false }
399+
access-manager-types = { path = "lib/access-manager-types", default-features = false }
400+
395401
frissitheto = { path = "lib/frissitheto", default-features = false }
396402

397403
lst = { path = "cosmwasm/lst", default-features = false }
398404

399-
access-manager-types = { path = "lib/access-manager-types", default-features = false }
400-
access-manager = { path = "cosmwasm/access-manager", default-features = false }
401-
402-
cw-account = { path = "cosmwasm/cw-account", default-features = false }
403-
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
405+
cw-account = { path = "cosmwasm/cw-account", default-features = false }
406+
cw-escrow-vault = { path = "cosmwasm/cw-escrow-vault", default-features = false }
404407

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

cosmwasm/access-managed/src/lib.rs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ use crate::{
4040

4141
pub mod contract;
4242
pub mod error;
43-
pub mod managed;
4443
mod restricted;
4544
pub mod state;
4645

@@ -51,8 +50,6 @@ pub use restricted::{
5150
// #[cfg(test)]
5251
// mod tests;
5352

54-
pub const EXECUTE_REPLY_ID: u64 = 1;
55-
5653
/// Initializes the contract connected to an initial authority.
5754
///
5855
/// ```solidity
@@ -61,7 +58,7 @@ pub const EXECUTE_REPLY_ID: u64 = 1;
6158
///
6259
/// <https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.4.0/contracts/access/manager/AccessManaged.sol#L27>
6360
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
64-
pub fn init(deps: DepsMut, _: &Env, msg: InitMsg) -> Result<Response, ContractError> {
61+
pub fn init(deps: DepsMut, msg: InitMsg) -> Result<Response, ContractError> {
6562
let InitMsg { initial_authority } = msg;
6663

6764
deps.storage.write_item::<Authority>(&initial_authority);
@@ -98,8 +95,22 @@ pub fn query(deps: Deps, _: Env, msg: QueryMsg) -> Result<Binary, ContractError>
9895
}
9996

10097
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
101-
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
10298
pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> Result<Response, ContractError> {
99+
if let Some(reply) = handle_consume_scheduled_op_reply(deps, reply)? {
100+
Err(StdError::generic_err(format!("unknown reply: {reply:?}")).into())
101+
} else {
102+
Ok(Response::new())
103+
}
104+
}
105+
106+
#[expect(
107+
clippy::needless_pass_by_value,
108+
reason = "DepsMut should be passed by value"
109+
)]
110+
pub fn handle_consume_scheduled_op_reply(
111+
deps: DepsMut<'_>,
112+
reply: Reply,
113+
) -> Result<Option<Reply>, ContractError> {
103114
match reply {
104115
Reply {
105116
id: ACCESS_MANAGED_CONSUME_SCHEDULED_OP_REPLY_ID,
@@ -110,23 +121,22 @@ pub fn reply(deps: DepsMut, _: Env, reply: Reply) -> Result<Response, ContractEr
110121

111122
deps.storage.write_item::<ConsumingSchedule>(&false);
112123

113-
Ok(Response::new())
124+
Ok(None)
114125
}
115-
_ => Err(StdError::generic_err("unknown reply: {reply:?}").into()),
126+
_ => Ok(Some(reply)),
116127
}
117128
}
118129

119130
#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
120-
#[expect(clippy::needless_pass_by_value, reason = "required for entry_point")]
121131
pub fn migrate(
122132
deps: DepsMut,
123-
env: Env,
133+
_: Env,
124134
msg: UpgradeMsg<InitMsg, MigrateMsg>,
125135
) -> Result<Response, ContractError> {
126136
msg.run(
127137
deps,
128138
|deps, msg| {
129-
let res = init(deps, &env, msg)?;
139+
let res = init(deps, msg)?;
130140
Ok((res, None))
131141
},
132142
|_, _, _| Ok((Response::default(), None)),
Lines changed: 26 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,20 @@
1-
use std::marker::PhantomData;
2-
31
use access_manager_types::{managed::error::AccessManagedError, CanCall, Selector};
4-
use cosmwasm_std::{
5-
from_json, to_json_binary, Addr, DepsMut, Env, MessageInfo, StdError, SubMsg, WasmMsg,
6-
};
2+
use cosmwasm_std::{to_json_binary, Addr, DepsMut, Env, MessageInfo, SubMsg, WasmMsg};
73
use depolama::{StorageExt, Store};
8-
use serde::{
9-
de::{self, DeserializeOwned},
10-
Deserialize, Deserializer,
11-
};
12-
use serde_json::value::RawValue;
4+
use serde::{de::DeserializeOwned, Deserialize, Deserializer, Serialize};
135

146
use crate::{error::ContractError, state::ConsumingSchedule};
157

168
pub const ACCESS_MANAGED_CONSUME_SCHEDULED_OP_REPLY_ID: u64 = u64::MAX;
179

1810
#[derive(Debug)]
19-
pub struct Restricted<'a, T: DeserializeOwned> {
20-
selector: &'a Selector,
21-
raw: &'a RawValue,
22-
__marker: PhantomData<fn() -> T>,
11+
pub struct Restricted<T: DeserializeOwned + Serialize> {
12+
selector: &'static Selector,
13+
value: T,
2314
}
2415

25-
impl<T: DeserializeOwned> Restricted<'_, T> {
26-
#[allow(clippy::needless_pass_by_value)]
16+
impl<T: DeserializeOwned + Serialize> Restricted<T> {
17+
#[allow(clippy::needless_pass_by_value, clippy::missing_panics_doc)]
2718
pub fn ensure_can_call<S: Store<Key = (), Value = Addr>>(
2819
self,
2920
deps: DepsMut,
@@ -45,7 +36,7 @@ impl<T: DeserializeOwned> Restricted<'_, T> {
4536
)?;
4637

4738
if immediate {
48-
Ok(EnsureCanCallResult::Msg(self.deserialize_inner()?))
39+
Ok(EnsureCanCallResult::Msg(self.value))
4940
} else if delay > 0 {
5041
deps.storage.write_item::<ConsumingSchedule>(&true);
5142

@@ -56,15 +47,15 @@ impl<T: DeserializeOwned> Restricted<'_, T> {
5647
msg: to_json_binary(
5748
&access_manager_types::manager::msg::ExecuteMsg::ConsumeScheduledOp {
5849
caller: info.sender.clone(),
59-
data: self.raw.get().to_owned(),
50+
data: serde_json_wasm::to_string(&self.value).expect("infallible"),
6051
},
6152
)?,
6253
funds: vec![],
6354
}),
64-
SubMsg::reply_always(
55+
SubMsg::reply_on_success(
6556
WasmMsg::Execute {
6657
contract_addr: env.contract.address.to_string(),
67-
msg: self.raw.get().as_bytes().into(),
58+
msg: to_json_binary(&self.value).expect("infallible"),
6859
funds: vec![],
6960
},
7061
ACCESS_MANAGED_CONSUME_SCHEDULED_OP_REPLY_ID,
@@ -79,31 +70,23 @@ impl<T: DeserializeOwned> Restricted<'_, T> {
7970
.into())
8071
}
8172
}
82-
83-
fn deserialize_inner(self) -> Result<T, StdError> {
84-
from_json(self.raw.get())
85-
}
8673
}
8774

8875
pub enum EnsureCanCallResult<T> {
8976
Msg(T),
9077
Scheduled(Vec<SubMsg>),
9178
}
9279

93-
impl<'de, T: DeserializeOwned> Deserialize<'de> for Restricted<'de, T> {
80+
impl<'de, T: DeserializeOwned + Serialize> Deserialize<'de> for Restricted<T> {
9481
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
9582
where
9683
D: Deserializer<'de>,
9784
{
98-
let raw = <&RawValue as Deserialize>::deserialize(deserializer)?;
85+
let value = T::deserialize(deserializer)?;
9986

100-
let selector = Selector::extract(raw.get()).map_err(de::Error::custom)?;
87+
let selector = Selector::extract_from_serialize(&value);
10188

102-
Ok(Self {
103-
selector,
104-
raw,
105-
__marker: PhantomData,
106-
})
89+
Ok(Self { selector, value })
10790
}
10891
}
10992

@@ -123,7 +106,7 @@ mod tests {
123106
#[track_caller]
124107
fn deser_expect_error(json: &[u8], expect: &str) {
125108
assert_eq!(
126-
serde_json::from_slice::<Restricted<ExecuteMsg>>(json)
109+
serde_json_wasm::from_slice::<Restricted<ExecuteMsg>>(json)
127110
.unwrap_err()
128111
.to_string(),
129112
expect
@@ -133,59 +116,55 @@ mod tests {
133116
#[test]
134117
fn restricted_deser_ok() {
135118
let obj = br#"{"key":{}}"#;
136-
let restricted = serde_json::from_slice::<Restricted<ExecuteMsg>>(obj).unwrap();
119+
let restricted = serde_json_wasm::from_slice::<Restricted<ExecuteMsg>>(obj).unwrap();
137120

138121
assert_eq!(restricted.selector, Selector::new("key"));
139-
assert_eq!(restricted.raw.get().as_bytes(), obj);
140-
141-
assert_eq!(restricted.deserialize_inner().unwrap(), ExecuteMsg::Key {});
122+
assert_eq!(restricted.value, ExecuteMsg::Key {});
142123
}
143124

144125
#[test]
145126
fn restricted_deser_value_not_object_ok() {
146127
let obj = br#"{"key2":1}"#;
147-
let restricted = serde_json::from_slice::<Restricted<ExecuteMsg>>(obj).unwrap();
128+
let restricted = serde_json_wasm::from_slice::<Restricted<ExecuteMsg>>(obj).unwrap();
148129

149130
assert_eq!(restricted.selector, Selector::new("key2"));
150-
assert_eq!(restricted.raw.get().as_bytes(), obj);
151-
152-
assert_eq!(restricted.deserialize_inner().unwrap(), ExecuteMsg::Key2(1));
131+
assert_eq!(restricted.value, ExecuteMsg::Key2(1));
153132
}
154133

155134
#[test]
156-
fn restricted_deser_escaped_fails() {
135+
fn restricted_deser_unknown_variant_fails() {
157136
deser_expect_error(
158137
br#"{"key\n":{}}"#,
159-
r#"invalid type: string "key\n", expected a borrowed string at line 1 column 8"#,
138+
"unknown variant `key\n`, expected `key` or `key2`",
160139
);
161140
}
162141

163142
#[test]
164143
fn restricted_deser_multiple_keys_different_key_name_fails() {
165144
deser_expect_error(
166145
br#"{"key":{},"key2":{}}"#,
167-
"multiple keys found at line 1 column 16",
146+
"Expected this character to start a JSON value.",
168147
);
169148
}
170149

171150
#[test]
172151
fn restricted_deser_multiple_keys_same_key_name_fails() {
173152
deser_expect_error(
174153
br#"{"key":{},"key":{}}"#,
175-
"multiple keys found at line 1 column 15",
154+
"Expected this character to start a JSON value.",
176155
);
177156
}
178157

179158
#[test]
180159
fn restricted_deser_no_key() {
181-
deser_expect_error(br"{}", "no key found at line 1 column 2");
160+
deser_expect_error(br"{}", "Invalid type");
182161
}
183162

184163
#[test]
185164
fn restricted_deser_not_object() {
186165
deser_expect_error(
187166
b"null",
188-
"invalid type: null, expected json object with single top level key at line 1 column 4",
167+
"Expected to parse either a `true`, `false`, or a `null`.",
189168
);
190169
}
191170
}

0 commit comments

Comments
 (0)