Skip to content

Commit 3bff810

Browse files
Optimize enumerable collections gas usage.
Cache per-namespace element counts in EnumerableSet/EnumerableMap so length() avoids loading full vectors, reducing common-read Soroban gas while preserving existing behavior. Made-with: Cursor
1 parent a5dcc33 commit 3bff810

26 files changed

Lines changed: 490 additions & 8 deletions

soroban-contract/contracts/collections/src/enumerable_map.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use soroban_sdk::Env;
3939
pub enum MapKey {
4040
/// The ordered `Vec<K>` of map keys.
4141
Keys(Symbol),
42+
/// Cached number of entries in the map.
43+
Count(Symbol),
4244
/// 1-based position of a key. The `u64` is the key's raw `Val` payload.
4345
KeyIndex(Symbol, u64),
4446
/// The value stored for a key. The `u64` is the key's raw `Val` payload.
@@ -87,6 +89,7 @@ impl EnumerableMap {
8789
vec.push_back(key.clone());
8890
let one_based = vec.len();
8991
tier.set(env, &keys_key, &vec);
92+
tier.set(env, &MapKey::Count(ns.clone()), &one_based);
9093
tier.set(env, &index_key, &one_based);
9194
}
9295

@@ -126,6 +129,7 @@ impl EnumerableMap {
126129

127130
vec.pop_back();
128131
tier.set(env, &keys_key, &vec);
132+
tier.set(env, &MapKey::Count(ns.clone()), &vec.len());
129133

130134
// Delete the index and value for the removed key.
131135
tier.remove(env, &index_key);
@@ -160,10 +164,7 @@ impl EnumerableMap {
160164
where
161165
K: IntoVal<Env, Val> + TryFromVal<Env, Val>,
162166
{
163-
let vec: Vec<K> = tier
164-
.get(env, &MapKey::Keys(ns.clone()))
165-
.unwrap_or_else(|| Vec::new(env));
166-
vec.len()
167+
tier.get(env, &MapKey::Count(ns.clone())).unwrap_or(0)
167168
}
168169

169170
/// Returns all keys in insertion order.

soroban-contract/contracts/collections/src/enumerable_set.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ use soroban_sdk::Env;
3636
pub enum SetKey {
3737
/// The ordered `Vec<V>` of set members.
3838
Values(Symbol),
39+
/// Cached number of elements in the set.
40+
Count(Symbol),
3941
/// 1-based index of a member. The second field is the member's raw `Val`
4042
/// payload encoded as a `u64`, avoiding XDR limitations on `Val` fields.
4143
Index(Symbol, u64),
@@ -84,6 +86,7 @@ impl EnumerableSet {
8486
let one_based_index = vec.len(); // length after push == 1-based index of new element
8587

8688
tier.set(env, &values_key, &vec);
89+
tier.set(env, &SetKey::Count(ns.clone()), &one_based_index);
8790
tier.set(env, &index_key, &one_based_index);
8891

8992
true
@@ -124,6 +127,7 @@ impl EnumerableSet {
124127

125128
vec.pop_back(); // remove last (was either target or swapped copy)
126129
tier.set(env, &values_key, &vec);
130+
tier.set(env, &SetKey::Count(ns.clone()), &vec.len());
127131
tier.remove(env, &index_key);
128132

129133
true
@@ -145,10 +149,7 @@ impl EnumerableSet {
145149
where
146150
V: IntoVal<Env, Val> + TryFromVal<Env, Val>,
147151
{
148-
let vec: Vec<V> = tier
149-
.get(env, &SetKey::Values(ns.clone()))
150-
.unwrap_or_else(|| Vec::new(env));
151-
vec.len()
152+
tier.get(env, &SetKey::Count(ns.clone())).unwrap_or(0)
152153
}
153154

154155
/// Returns all members as an ordered `Vec<V>`.

soroban-contract/contracts/collections/test_snapshots/test/map_entries_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "entries"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 2
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_insert_and_get_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "balances"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 1
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_insert_and_remove_persistent.1.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,51 @@
1717
"min_temp_entry_ttl": 16,
1818
"max_entry_ttl": 6312000,
1919
"ledger_entries": [
20+
[
21+
{
22+
"contract_data": {
23+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
24+
"key": {
25+
"vec": [
26+
{
27+
"symbol": "Count"
28+
},
29+
{
30+
"symbol": "pmap"
31+
}
32+
]
33+
},
34+
"durability": "persistent"
35+
}
36+
},
37+
[
38+
{
39+
"last_modified_ledger_seq": 0,
40+
"data": {
41+
"contract_data": {
42+
"ext": "v0",
43+
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "pmap"
51+
}
52+
]
53+
},
54+
"durability": "persistent",
55+
"val": {
56+
"u32": 0
57+
}
58+
}
59+
},
60+
"ext": "v0"
61+
},
62+
4095
63+
]
64+
],
2065
[
2166
{
2267
"contract_data": {

soroban-contract/contracts/collections/test_snapshots/test/map_insert_update_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "balu"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 1
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_key_at_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "keyat"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 2
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_keys_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "mapkeys"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 2
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_length_instance.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "maplen"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 3
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

soroban-contract/contracts/collections/test_snapshots/test/map_namespaces_are_isolated.1.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,21 @@
4040
"wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
4141
},
4242
"storage": [
43+
{
44+
"key": {
45+
"vec": [
46+
{
47+
"symbol": "Count"
48+
},
49+
{
50+
"symbol": "mnsA"
51+
}
52+
]
53+
},
54+
"val": {
55+
"u32": 1
56+
}
57+
},
4358
{
4459
"key": {
4560
"vec": [

0 commit comments

Comments
 (0)