1use std::sync::Arc;
4
5use aztec_core::error::Error;
6use aztec_core::types::{AztecAddress, Fr};
7
8use super::kv::KvStore;
9
10pub struct CapsuleStore {
15 kv: Arc<dyn KvStore>,
16}
17
18impl CapsuleStore {
19 pub fn new(kv: Arc<dyn KvStore>) -> Self {
20 Self { kv }
21 }
22
23 pub async fn add(&self, contract: &Fr, capsule: &[Vec<Fr>]) -> Result<(), Error> {
25 let key = capsule_key(contract);
26 let value = serde_json::to_vec(capsule)?;
27 self.kv.put(&key, &value).await
28 }
29
30 pub async fn pop(&self, contract: &Fr) -> Result<Option<Vec<Vec<Fr>>>, Error> {
32 let key = capsule_key(contract);
33 match self.kv.get(&key).await? {
34 Some(bytes) => {
35 self.kv.delete(&key).await?;
36 Ok(Some(serde_json::from_slice(&bytes)?))
37 }
38 None => Ok(None),
39 }
40 }
41
42 pub async fn store_capsule(
44 &self,
45 contract_address: &AztecAddress,
46 slot: &Fr,
47 capsule: &[Fr],
48 ) -> Result<(), Error> {
49 let key = db_slot_key(contract_address, slot);
50 let value = serde_json::to_vec(capsule)?;
51 self.kv.put(&key, &value).await
52 }
53
54 pub async fn load_capsule(
56 &self,
57 contract_address: &AztecAddress,
58 slot: &Fr,
59 ) -> Result<Option<Vec<Fr>>, Error> {
60 let key = db_slot_key(contract_address, slot);
61 match self.kv.get(&key).await? {
62 Some(bytes) => Ok(Some(serde_json::from_slice(&bytes)?)),
63 None => Ok(None),
64 }
65 }
66
67 pub async fn delete_capsule(
69 &self,
70 contract_address: &AztecAddress,
71 slot: &Fr,
72 ) -> Result<(), Error> {
73 let key = db_slot_key(contract_address, slot);
74 self.kv.delete(&key).await
75 }
76
77 pub async fn copy_capsule(
79 &self,
80 contract_address: &AztecAddress,
81 src_slot: &Fr,
82 dst_slot: &Fr,
83 num_entries: usize,
84 ) -> Result<(), Error> {
85 if num_entries == 0 {
86 return Ok(());
87 }
88
89 let mut copied = Vec::with_capacity(num_entries);
90 for i in 0..num_entries {
91 let current_src = Fr::from((src_slot.to_usize() + i) as u64);
92 let data = self
93 .load_capsule(contract_address, ¤t_src)
94 .await?
95 .ok_or_else(|| {
96 Error::InvalidData(format!(
97 "attempted to copy empty capsule slot {} for contract {}",
98 current_src, contract_address
99 ))
100 })?;
101 copied.push(data);
102 }
103
104 for (i, data) in copied.into_iter().enumerate() {
105 let current_dst = Fr::from((dst_slot.to_usize() + i) as u64);
106 self.store_capsule(contract_address, ¤t_dst, &data)
107 .await?;
108 }
109
110 Ok(())
111 }
112
113 pub async fn append_to_capsule_array(
115 &self,
116 contract_address: &AztecAddress,
117 base_slot: &Fr,
118 content: &[Vec<Fr>],
119 ) -> Result<(), Error> {
120 let current_length = self
121 .load_capsule(contract_address, base_slot)
122 .await?
123 .and_then(|capsule| capsule.first().copied())
124 .unwrap_or_else(Fr::zero)
125 .to_usize();
126
127 for (i, capsule) in content.iter().enumerate() {
128 let next_slot = array_slot(base_slot, current_length + i);
129 self.store_capsule(contract_address, &next_slot, capsule)
130 .await?;
131 }
132
133 self.store_capsule(
134 contract_address,
135 base_slot,
136 &[Fr::from((current_length + content.len()) as u64)],
137 )
138 .await
139 }
140
141 pub async fn read_capsule_array(
143 &self,
144 contract_address: &AztecAddress,
145 base_slot: &Fr,
146 ) -> Result<Vec<Vec<Fr>>, Error> {
147 let length = self
148 .load_capsule(contract_address, base_slot)
149 .await?
150 .and_then(|capsule| capsule.first().copied())
151 .unwrap_or_else(Fr::zero)
152 .to_usize();
153
154 let mut values = Vec::with_capacity(length);
155 for i in 0..length {
156 let slot = array_slot(base_slot, i);
157 let value = self
158 .load_capsule(contract_address, &slot)
159 .await?
160 .ok_or_else(|| {
161 Error::InvalidData(format!(
162 "expected non-empty capsule array value at slot {} for contract {}",
163 slot, contract_address
164 ))
165 })?;
166 values.push(value);
167 }
168
169 Ok(values)
170 }
171
172 pub async fn set_capsule_array(
174 &self,
175 contract_address: &AztecAddress,
176 base_slot: &Fr,
177 content: &[Vec<Fr>],
178 ) -> Result<(), Error> {
179 let original_length = self
180 .load_capsule(contract_address, base_slot)
181 .await?
182 .and_then(|capsule| capsule.first().copied())
183 .unwrap_or_else(Fr::zero)
184 .to_usize();
185
186 self.store_capsule(
187 contract_address,
188 base_slot,
189 &[Fr::from(content.len() as u64)],
190 )
191 .await?;
192
193 for (i, capsule) in content.iter().enumerate() {
194 let slot = array_slot(base_slot, i);
195 self.store_capsule(contract_address, &slot, capsule).await?;
196 }
197
198 for i in content.len()..original_length {
199 let slot = array_slot(base_slot, i);
200 self.delete_capsule(contract_address, &slot).await?;
201 }
202
203 Ok(())
204 }
205}
206
207fn capsule_key(contract: &Fr) -> Vec<u8> {
208 format!("capsule:{contract}").into_bytes()
209}
210
211fn db_slot_key(contract_address: &AztecAddress, slot: &Fr) -> Vec<u8> {
212 format!("capsule_db:{contract_address}:{slot}").into_bytes()
213}
214
215fn array_slot(base_slot: &Fr, index: usize) -> Fr {
218 Fr(base_slot.0 + Fr::from(1u64 + index as u64).0)
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use crate::stores::InMemoryKvStore;
226
227 #[tokio::test]
228 async fn capsule_is_consumed_on_pop() {
229 let kv = Arc::new(InMemoryKvStore::new());
230 let store = CapsuleStore::new(kv);
231 let contract = Fr::from(1u64);
232 let capsule = vec![vec![Fr::from(10u64), Fr::from(20u64)]];
233
234 store.add(&contract, &capsule).await.unwrap();
235
236 let first = store.pop(&contract).await.unwrap();
237 assert!(first.is_some());
238
239 let second = store.pop(&contract).await.unwrap();
240 assert!(second.is_none());
241 }
242
243 #[tokio::test]
244 async fn contract_scoped_capsule_array_roundtrip() {
245 let kv = Arc::new(InMemoryKvStore::new());
246 let store = CapsuleStore::new(kv);
247 let contract = AztecAddress::from(1u64);
248 let base_slot = Fr::from(10u64);
249
250 store
251 .append_to_capsule_array(
252 &contract,
253 &base_slot,
254 &[
255 vec![Fr::from(11u64)],
256 vec![Fr::from(12u64), Fr::from(13u64)],
257 ],
258 )
259 .await
260 .unwrap();
261
262 let values = store
263 .read_capsule_array(&contract, &base_slot)
264 .await
265 .unwrap();
266 assert_eq!(values.len(), 2);
267 assert_eq!(values[0], vec![Fr::from(11u64)]);
268 assert_eq!(values[1], vec![Fr::from(12u64), Fr::from(13u64)]);
269
270 store
271 .set_capsule_array(&contract, &base_slot, &[vec![Fr::from(99u64)]])
272 .await
273 .unwrap();
274
275 let values = store
276 .read_capsule_array(&contract, &base_slot)
277 .await
278 .unwrap();
279 assert_eq!(values, vec![vec![Fr::from(99u64)]]);
280 }
281}