Skip to content

Commit a4a6fbf

Browse files
committed
Split payment and cleanup implementations
1 parent 0321e5c commit a4a6fbf

File tree

5 files changed

+177
-44
lines changed

5 files changed

+177
-44
lines changed

contracts/burner/schema/burner.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
],
2121
"properties": {
2222
"payout": {
23+
"description": "The address we send all remaining balance to",
2324
"type": "string"
2425
}
2526
},

contracts/burner/schema/raw/migrate.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
],
88
"properties": {
99
"payout": {
10+
"description": "The address we send all remaining balance to",
1011
"type": "string"
1112
}
1213
},

contracts/burner/src/contract.rs

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use cosmwasm_std::{
22
entry_point, BankMsg, DepsMut, Env, MessageInfo, Order, Response, StdError, StdResult,
33
};
44

5-
use crate::msg::{InstantiateMsg, MigrateMsg};
5+
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg};
66

77
#[entry_point]
88
pub fn instantiate(
@@ -18,31 +18,58 @@ pub fn instantiate(
1818

1919
#[entry_point]
2020
pub fn migrate(deps: DepsMut, env: Env, msg: MigrateMsg) -> StdResult<Response> {
21-
// delete all state
22-
let keys: Vec<_> = deps
23-
.storage
24-
.range(None, None, Order::Ascending)
25-
.map(|(k, _)| k)
26-
.collect();
27-
let count = keys.len();
28-
for k in keys {
29-
deps.storage.remove(&k);
30-
}
31-
3221
// get balance and send all to recipient
3322
let balance = deps.querier.query_all_balances(env.contract.address)?;
3423
let send = BankMsg::Send {
3524
to_address: msg.payout.clone(),
3625
amount: balance,
3726
};
27+
Ok(Response::new()
28+
.add_message(send)
29+
.add_attribute("action", "burn")
30+
.add_attribute("payout", msg.payout))
31+
}
32+
33+
#[entry_point]
34+
pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> StdResult<Response> {
35+
match msg {
36+
ExecuteMsg::Cleanup { limit } => execute_cleanup(deps, env, info, limit),
37+
}
38+
}
39+
40+
pub fn execute_cleanup(
41+
deps: DepsMut,
42+
_env: Env,
43+
_info: MessageInfo,
44+
limit: Option<u32>,
45+
) -> StdResult<Response> {
46+
// the number of elements we can still take (decreasing over time)
47+
let mut limit = limit.unwrap_or(u32::MAX) as usize;
3848

39-
let data_msg = format!("burnt {} keys", count).into_bytes();
49+
let mut deleted = 0;
50+
const PER_SCAN: usize = 20;
51+
loop {
52+
let take_this_scan = std::cmp::min(PER_SCAN, limit);
53+
let keys: Vec<_> = deps
54+
.storage
55+
.range(None, None, Order::Ascending)
56+
.take(take_this_scan)
57+
.map(|(k, _)| k)
58+
.collect();
59+
let deleted_this_scan = keys.len();
60+
for k in keys {
61+
deps.storage.remove(&k);
62+
}
63+
deleted += deleted_this_scan;
64+
limit -= deleted_this_scan;
65+
if limit == 0 || deleted_this_scan < take_this_scan {
66+
break;
67+
}
68+
}
4069

4170
Ok(Response::new()
42-
.add_message(send)
4371
.add_attribute("action", "burn")
44-
.add_attribute("payout", msg.payout)
45-
.set_data(data_msg))
72+
.add_attribute("deleted_entries", deleted.to_string()))
4673
}
4774

4875
#[cfg(test)]
@@ -51,7 +78,18 @@ mod tests {
5178
use cosmwasm_std::testing::{
5279
mock_dependencies, mock_dependencies_with_balance, mock_env, mock_info,
5380
};
54-
use cosmwasm_std::{coins, StdError, Storage, SubMsg};
81+
use cosmwasm_std::{coins, Attribute, StdError, Storage, SubMsg};
82+
83+
/// Gets the value of the first attribute with the given key
84+
fn first_attr(data: impl AsRef<[Attribute]>, search_key: &str) -> Option<String> {
85+
data.as_ref().iter().find_map(|a| {
86+
if a.key == search_key {
87+
Some(a.value.clone())
88+
} else {
89+
None
90+
}
91+
})
92+
}
5593

5694
#[test]
5795
fn instantiate_fails() {
@@ -70,16 +108,9 @@ mod tests {
70108
}
71109

72110
#[test]
73-
fn migrate_cleans_up_data() {
111+
fn migrate_sends_funds() {
74112
let mut deps = mock_dependencies_with_balance(&coins(123456, "gold"));
75113

76-
// store some sample data
77-
deps.storage.set(b"foo", b"bar");
78-
deps.storage.set(b"key2", b"data2");
79-
deps.storage.set(b"key3", b"cool stuff");
80-
let cnt = deps.storage.range(None, None, Order::Ascending).count();
81-
assert_eq!(3, cnt);
82-
83114
// change the verifier via migrate
84115
let payout = String::from("someone else");
85116
let msg = MigrateMsg {
@@ -96,9 +127,48 @@ mod tests {
96127
amount: coins(123456, "gold"),
97128
})
98129
);
130+
}
131+
132+
#[test]
133+
fn execute_cleans_up_data() {
134+
let mut deps = mock_dependencies_with_balance(&coins(123456, "gold"));
135+
136+
// store some sample data
137+
deps.storage.set(b"foo", b"bar");
138+
deps.storage.set(b"key2", b"data2");
139+
deps.storage.set(b"key3", b"cool stuff");
140+
let cnt = deps.storage.range(None, None, Order::Ascending).count();
141+
assert_eq!(cnt, 3);
142+
143+
// change the verifier via migrate
144+
let payout = String::from("someone else");
145+
let msg = MigrateMsg { payout };
146+
let _res = migrate(deps.as_mut(), mock_env(), msg).unwrap();
147+
148+
let res = execute(
149+
deps.as_mut(),
150+
mock_env(),
151+
mock_info("anon", &[]),
152+
ExecuteMsg::Cleanup { limit: Some(2) },
153+
)
154+
.unwrap();
155+
assert_eq!(first_attr(res.attributes, "deleted_entries").unwrap(), "2");
156+
157+
// One item should be left
158+
let cnt = deps.storage.range(None, None, Order::Ascending).count();
159+
assert_eq!(cnt, 1);
160+
161+
let res = execute(
162+
deps.as_mut(),
163+
mock_env(),
164+
mock_info("anon", &[]),
165+
ExecuteMsg::Cleanup { limit: Some(2) },
166+
)
167+
.unwrap();
168+
assert_eq!(first_attr(res.attributes, "deleted_entries").unwrap(), "1");
99169

100-
// check there is no data in storage
170+
// Now all are gone
101171
let cnt = deps.storage.range(None, None, Order::Ascending).count();
102-
assert_eq!(0, cnt);
172+
assert_eq!(cnt, 0);
103173
}
104174
}

contracts/burner/src/msg.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,22 @@ use cosmwasm_schema::cw_serde;
22

33
#[cw_serde]
44
pub struct MigrateMsg {
5+
/// The address we send all remaining balance to
56
pub payout: String,
67
}
78

89
/// A placeholder where we don't take any input
910
#[cw_serde]
1011
pub struct InstantiateMsg {}
12+
13+
#[cw_serde]
14+
pub enum ExecuteMsg {
15+
/// Cleans up the given number of state elements.
16+
/// Call this multiple times to increamentally clean up state.
17+
Cleanup {
18+
/// The number of state elements to delete.
19+
///
20+
/// Set this to None for unlimited cleanup (if your state is small or you are feeling YOLO)
21+
limit: Option<u32>,
22+
},
23+
}

contracts/burner/tests/integration.rs

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,28 @@
1717
//! });
1818
//! 4. Anywhere you see query(&deps, ...) you must replace it with query(&mut deps, ...)
1919
20-
use cosmwasm_std::{coins, BankMsg, ContractResult, Order, Response, SubMsg};
21-
use cosmwasm_vm::testing::{instantiate, migrate, mock_env, mock_info, mock_instance};
20+
use cosmwasm_std::{coins, Attribute, BankMsg, ContractResult, Order, Response, SubMsg};
21+
use cosmwasm_vm::testing::{execute, instantiate, migrate, mock_env, mock_info, mock_instance};
2222

23-
use burner::msg::{InstantiateMsg, MigrateMsg};
23+
use burner::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg};
2424
use cosmwasm_vm::Storage;
2525

2626
// This line will test the output of cargo wasm
2727
static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/burner.wasm");
2828
// You can uncomment this line instead to test productionified build from rust-optimizer
2929
// static WASM: &[u8] = include_bytes!("../contract.wasm");
3030

31+
/// Gets the value of the first attribute with the given key
32+
fn first_attr(data: impl AsRef<[Attribute]>, search_key: &str) -> Option<String> {
33+
data.as_ref().iter().find_map(|a| {
34+
if a.key == search_key {
35+
Some(a.value.clone())
36+
} else {
37+
None
38+
}
39+
})
40+
}
41+
3142
#[test]
3243
fn instantiate_fails() {
3344
let mut deps = mock_instance(WASM, &[]);
@@ -44,21 +55,9 @@ fn instantiate_fails() {
4455
}
4556

4657
#[test]
47-
fn migrate_cleans_up_data() {
58+
fn migrate_sends_funds() {
4859
let mut deps = mock_instance(WASM, &coins(123456, "gold"));
4960

50-
// store some sample data
51-
deps.with_storage(|storage| {
52-
storage.set(b"foo", b"bar").0.unwrap();
53-
storage.set(b"key2", b"data2").0.unwrap();
54-
storage.set(b"key3", b"cool stuff").0.unwrap();
55-
let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap();
56-
let cnt = storage.all(iter_id).0.unwrap().len();
57-
assert_eq!(3, cnt);
58-
Ok(())
59-
})
60-
.unwrap();
61-
6261
// change the verifier via migrate
6362
let payout = String::from("someone else");
6463
let msg = MigrateMsg {
@@ -75,12 +74,61 @@ fn migrate_cleans_up_data() {
7574
amount: coins(123456, "gold"),
7675
}),
7776
);
77+
}
78+
79+
#[test]
80+
fn execute_cleans_up_data() {
81+
let mut deps = mock_instance(WASM, &coins(123456, "gold"));
82+
83+
// store some sample data
84+
deps.with_storage(|storage| {
85+
storage.set(b"foo", b"bar").0.unwrap();
86+
storage.set(b"key2", b"data2").0.unwrap();
87+
storage.set(b"key3", b"cool stuff").0.unwrap();
88+
let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap();
89+
let cnt = storage.all(iter_id).0.unwrap().len();
90+
assert_eq!(cnt, 3);
91+
Ok(())
92+
})
93+
.unwrap();
94+
95+
// change the verifier via migrate
96+
let payout = String::from("someone else");
97+
let msg = MigrateMsg { payout };
98+
let _res: Response = migrate(&mut deps, mock_env(), msg).unwrap();
99+
100+
let res: Response = execute(
101+
&mut deps,
102+
mock_env(),
103+
mock_info("anon", &[]),
104+
ExecuteMsg::Cleanup { limit: Some(2) },
105+
)
106+
.unwrap();
107+
assert_eq!(first_attr(res.attributes, "deleted_entries").unwrap(), "2");
108+
109+
// One item should be left
110+
deps.with_storage(|storage| {
111+
let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap();
112+
let cnt = storage.all(iter_id).0.unwrap().len();
113+
assert_eq!(cnt, 1);
114+
Ok(())
115+
})
116+
.unwrap();
117+
118+
let res: Response = execute(
119+
&mut deps,
120+
mock_env(),
121+
mock_info("anon", &[]),
122+
ExecuteMsg::Cleanup { limit: Some(2) },
123+
)
124+
.unwrap();
125+
assert_eq!(first_attr(res.attributes, "deleted_entries").unwrap(), "1");
78126

79127
// check there is no data in storage
80128
deps.with_storage(|storage| {
81129
let iter_id = storage.scan(None, None, Order::Ascending).0.unwrap();
82130
let cnt = storage.all(iter_id).0.unwrap().len();
83-
assert_eq!(0, cnt);
131+
assert_eq!(cnt, 0);
84132
Ok(())
85133
})
86134
.unwrap();

0 commit comments

Comments
 (0)