home / skills / multiversx / mx-ai-skills / mvx_cache_patterns
This skill helps optimize MultiversX contracts by using drop-based write-back caches to minimize gas through selective reads and cached state.
npx playbooks add skill multiversx/mx-ai-skills --skill mvx_cache_patternsReview the files below or copy the command above to add this skill to your agents.
---
name: mvx_cache_patterns
description: Drop-based write-back caches for gas optimization. Use for endpoints reading 3+ storage values.
---
# MultiversX Cache Patterns
## Why Cache?
- Storage operations are the most expensive in MultiversX contracts
- With cache: reads on entry + writes on exit, intermediate reads FREE (in-memory)
## Pattern 1: Write-Back Cache with Drop Trait
Load state into struct on entry, mutate in memory, commit on scope exit via `Drop`.
```rust
pub struct StorageCache<'a, C>
where
C: crate::storage::StorageModule,
{
sc_ref: &'a C,
pub field_a: BigUint<C::Api>,
pub field_b: BigUint<C::Api>,
pub field_c: BigUint<C::Api>,
}
impl<'a, C> StorageCache<'a, C>
where
C: crate::storage::StorageModule,
{
pub fn new(sc_ref: &'a C) -> Self {
StorageCache {
field_a: sc_ref.field_a().get(),
field_b: sc_ref.field_b().get(),
field_c: sc_ref.field_c().get(),
sc_ref,
}
}
}
impl<C> Drop for StorageCache<'_, C>
where
C: crate::storage::StorageModule,
{
fn drop(&mut self) {
self.sc_ref.field_a().set(&self.field_a);
self.sc_ref.field_b().set(&self.field_b);
self.sc_ref.field_c().set(&self.field_c);
}
}
```
## Pattern 2: Cache with Computed Methods
Add derived value methods on the cache struct — uses cached fields + contract module methods without extra storage reads:
```rust
impl<C> StateCache<'_, C>
where
C: crate::storage::StorageModule + crate::math::MathModule,
{
pub fn exchange_rate(&self) -> BigUint<C::Api> {
if self.total_shares == 0u64 { return BigUint::from(1u64); }
&self.total_deposited / &self.total_shares
}
}
```
## Selective Write-Back
Only write back mutable fields — skip config/read-only fields in Drop to save gas.
## When to Use vs Direct Storage
| Scenario | Approach |
|---|---|
| Endpoint reads 3+ storage values | Use cache |
| Single storage read/write | Direct access is fine |
| View function reading multiple values | Read-only cache (no Drop) |
| Async call boundary | Manually drop cache BEFORE async call |
## Anti-Patterns
### 1. Caching Across Async Boundaries
```rust
// WRONG - async_call_and_exit() terminates execution, drop() never runs!
fn bad_async(&self) {
let mut cache = StorageCache::new(self);
cache.balance += &deposit;
self.tx().to(&other).typed(Proxy).call()
.callback(self.callbacks().on_done())
.async_call_and_exit();
}
// CORRECT - manually drop cache before async call
fn good_async(&self) {
{
let mut cache = StorageCache::new(self);
cache.balance += &deposit;
} // cache.drop() fires here
self.tx().to(&other).typed(Proxy).call()
.callback(self.callbacks().on_done())
.async_call_and_exit();
}
```
### 2. Forgetting Fields in Drop
### 3. Writing Back Immutable Config
This skill documents drop-based write-back cache patterns for MultiversX smart contracts to reduce gas costs when endpoints read multiple storage values. It provides a minimal, safe pattern that loads storage into an in-memory struct, mutates it during execution, and commits only on scope exit. The patterns include computed helper methods and selective write-back to avoid unnecessary storage writes.
On entry, the cache struct reads multiple storage entries into memory. Mutations happen on the in-memory fields. A Drop implementation writes changed fields back to storage when the cache goes out of scope, making intermediate reads free and reducing round-trip storage operations. Read-only variants skip Drop, and manual scoping is used to avoid crossing async boundaries.
What happens if I forget to drop the cache before an async_call_and_exit?
Drop will not run when execution terminates via async_call_and_exit, so writes will never happen. Always scope the cache so it goes out of scope and drops before initiating async termination.
Can I cache immutable configuration fields?
You can read immutable config into the cache for convenience, but do not write them back in Drop. Excluding them from Drop saves gas and avoids accidental writes.