Skip to content

Commit 31a00ea

Browse files
fix: cap resolver committee size and fix NextId consistency (#9)
* fix: cap resolver committee size and fix NextId consistency * docs: document resolver cap and nextid fix
1 parent 419ab89 commit 31a00ea

7 files changed

Lines changed: 695 additions & 32 deletions

File tree

contracts/demo-consumer/test_snapshots/test/test_demo_consumer_can_assert_and_read_status_through_tholos.1.json

Lines changed: 24 additions & 24 deletions
Large diffs are not rendered by default.

contracts/tholos/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ pub enum Error {
9999
Paused = 11,
100100
InvalidBondAmount = 12,
101101
InvalidChallengeWindow = 13,
102+
TooManyResolvers = 14,
102103
}
103104

104105
const DAY_IN_LEDGERS: u32 = 17280;
@@ -114,6 +115,11 @@ const ASSERTION_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS;
114115
const ASSERTION_LIFETIME_THRESHOLD: u32 = ASSERTION_BUMP_AMOUNT - DAY_IN_LEDGERS;
115116
const MAX_CHALLENGE_WINDOW_SECS: u64 = 7 * 24 * 60 * 60;
116117

118+
/// A resolver committee larger than this gets copied in full onto every
119+
/// disputed assertion (see `Assertion.resolvers`), so an unbounded size
120+
/// would grow the storage and iteration cost of every future dispute.
121+
const MAX_RESOLVERS: u32 = 21;
122+
117123
#[contract]
118124
pub struct Tholos;
119125

@@ -135,6 +141,9 @@ impl Tholos {
135141
if resolvers.is_empty() || resolvers.len().is_multiple_of(2) {
136142
return Err(Error::InvalidResolverCount);
137143
}
144+
if resolvers.len() > MAX_RESOLVERS {
145+
return Err(Error::TooManyResolvers);
146+
}
138147
if bond_amount <= 0 {
139148
return Err(Error::InvalidBondAmount);
140149
}
@@ -179,6 +188,9 @@ impl Tholos {
179188
if new_resolvers.is_empty() || new_resolvers.len().is_multiple_of(2) {
180189
return Err(Error::InvalidResolverCount);
181190
}
191+
if new_resolvers.len() > MAX_RESOLVERS {
192+
return Err(Error::TooManyResolvers);
193+
}
182194

183195
env.storage()
184196
.instance()
@@ -231,7 +243,7 @@ impl Tholos {
231243
// The new id is reserved and the assertion written before the
232244
// external token transfer below, so a reentrant call during the
233245
// transfer can't be allocated the same not-yet-incremented id.
234-
let id: u64 = env.storage().instance().get(&DataKey::NextId).unwrap_or(0);
246+
let id: u64 = Self::get(&env, &DataKey::NextId)?;
235247
env.storage().instance().set(&DataKey::NextId, &(id + 1));
236248
let assertion = Assertion {
237249
asserter: asserter.clone(),

contracts/tholos/src/test.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,28 @@ fn test_cannot_initialize_with_even_resolver_count() {
172172
assert!(result.is_err());
173173
}
174174

175+
#[test]
176+
fn test_cannot_initialize_with_too_many_resolvers() {
177+
let env = Env::default();
178+
env.mock_all_auths();
179+
180+
let (token_id, _resolvers) = setup(&env);
181+
let contract_id = env.register(Tholos, ());
182+
let client = TholosClient::new(&env, &contract_id);
183+
184+
let admin = Address::generate(&env);
185+
// +2, not +1: must stay odd (MAX_RESOLVERS is odd) so this isolates the
186+
// TooManyResolvers check rather than tripping InvalidResolverCount first.
187+
let mut too_many = Vec::new(&env);
188+
for _ in 0..(MAX_RESOLVERS + 2) {
189+
too_many.push_back(Address::generate(&env));
190+
}
191+
192+
let result =
193+
client.try_initialize(&admin, &token_id, &DEFAULT_BOND, &DEFAULT_WINDOW, &too_many);
194+
assert_eq!(result, Err(Ok(Error::TooManyResolvers)));
195+
}
196+
175197
#[test]
176198
fn test_cannot_initialize_with_zero_bond_amount() {
177199
let env = Env::default();
@@ -454,6 +476,19 @@ fn test_cannot_update_resolvers_to_even_count() {
454476
assert_eq!(result, Err(Ok(Error::InvalidResolverCount)));
455477
}
456478

479+
#[test]
480+
fn test_cannot_update_resolvers_to_too_many() {
481+
let f = Fixture::new();
482+
483+
let mut too_many = Vec::new(&f.env);
484+
for _ in 0..(MAX_RESOLVERS + 2) {
485+
too_many.push_back(f.generate());
486+
}
487+
488+
let result = f.client.try_update_resolvers(&too_many);
489+
assert_eq!(result, Err(Ok(Error::TooManyResolvers)));
490+
}
491+
457492
#[test]
458493
fn test_cannot_update_resolvers_before_initialization() {
459494
let env = Env::default();
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
{
2+
"generators": {
3+
"address": 30,
4+
"nonce": 0,
5+
"mux_id": 0
6+
},
7+
"auth": [
8+
[],
9+
[
10+
[
11+
"GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
12+
{
13+
"function": {
14+
"contract_fn": {
15+
"contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
16+
"function_name": "set_admin",
17+
"args": [
18+
{
19+
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
20+
}
21+
]
22+
}
23+
},
24+
"sub_invocations": []
25+
}
26+
]
27+
],
28+
[],
29+
[]
30+
],
31+
"ledger": {
32+
"protocol_version": 26,
33+
"sequence_number": 0,
34+
"timestamp": 0,
35+
"network_id": "0000000000000000000000000000000000000000000000000000000000000000",
36+
"base_reserve": 0,
37+
"min_persistent_entry_ttl": 4096,
38+
"min_temp_entry_ttl": 16,
39+
"max_entry_ttl": 6312000,
40+
"ledger_entries": [
41+
{
42+
"entry": {
43+
"last_modified_ledger_seq": 0,
44+
"data": {
45+
"account": {
46+
"account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
47+
"balance": "0",
48+
"seq_num": "0",
49+
"num_sub_entries": 0,
50+
"inflation_dest": null,
51+
"flags": 0,
52+
"home_domain": "",
53+
"thresholds": "01010101",
54+
"signers": [],
55+
"ext": "v0"
56+
}
57+
},
58+
"ext": "v0"
59+
},
60+
"live_until": null
61+
},
62+
{
63+
"entry": {
64+
"last_modified_ledger_seq": 0,
65+
"data": {
66+
"contract_data": {
67+
"ext": "v0",
68+
"contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
69+
"key": {
70+
"ledger_key_nonce": {
71+
"nonce": "801925984706572462"
72+
}
73+
},
74+
"durability": "temporary",
75+
"val": "void"
76+
}
77+
},
78+
"ext": "v0"
79+
},
80+
"live_until": 6311999
81+
},
82+
{
83+
"entry": {
84+
"last_modified_ledger_seq": 0,
85+
"data": {
86+
"contract_data": {
87+
"ext": "v0",
88+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDR4",
89+
"key": "ledger_key_contract_instance",
90+
"durability": "persistent",
91+
"val": {
92+
"contract_instance": {
93+
"executable": {
94+
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
95+
},
96+
"storage": null
97+
}
98+
}
99+
}
100+
},
101+
"ext": "v0"
102+
},
103+
"live_until": 4095
104+
},
105+
{
106+
"entry": {
107+
"last_modified_ledger_seq": 0,
108+
"data": {
109+
"contract_data": {
110+
"ext": "v0",
111+
"contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
112+
"key": "ledger_key_contract_instance",
113+
"durability": "persistent",
114+
"val": {
115+
"contract_instance": {
116+
"executable": "stellar_asset",
117+
"storage": [
118+
{
119+
"key": {
120+
"symbol": "METADATA"
121+
},
122+
"val": {
123+
"map": [
124+
{
125+
"key": {
126+
"symbol": "decimal"
127+
},
128+
"val": {
129+
"u32": 7
130+
}
131+
},
132+
{
133+
"key": {
134+
"symbol": "name"
135+
},
136+
"val": {
137+
"string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF"
138+
}
139+
},
140+
{
141+
"key": {
142+
"symbol": "symbol"
143+
},
144+
"val": {
145+
"string": "aaa"
146+
}
147+
}
148+
]
149+
}
150+
},
151+
{
152+
"key": {
153+
"vec": [
154+
{
155+
"symbol": "Admin"
156+
}
157+
]
158+
},
159+
"val": {
160+
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
161+
}
162+
},
163+
{
164+
"key": {
165+
"vec": [
166+
{
167+
"symbol": "AssetInfo"
168+
}
169+
]
170+
},
171+
"val": {
172+
"vec": [
173+
{
174+
"symbol": "AlphaNum4"
175+
},
176+
{
177+
"map": [
178+
{
179+
"key": {
180+
"symbol": "asset_code"
181+
},
182+
"val": {
183+
"string": "aaa\\0"
184+
}
185+
},
186+
{
187+
"key": {
188+
"symbol": "issuer"
189+
},
190+
"val": {
191+
"bytes": "0000000000000000000000000000000000000000000000000000000000000002"
192+
}
193+
}
194+
]
195+
}
196+
]
197+
}
198+
}
199+
]
200+
}
201+
}
202+
}
203+
},
204+
"ext": "v0"
205+
},
206+
"live_until": 120960
207+
},
208+
{
209+
"entry": {
210+
"last_modified_ledger_seq": 0,
211+
"data": {
212+
"contract_code": {
213+
"ext": "v0",
214+
"hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
215+
"code": ""
216+
}
217+
},
218+
"ext": "v0"
219+
},
220+
"live_until": 4095
221+
}
222+
]
223+
},
224+
"events": []
225+
}

0 commit comments

Comments
 (0)