name: mvx_cache_patterns description: 基于 Drop 的回写缓存用于 gas 优化。适用于读取 3+ 存储值的端点。
MultiversX 缓存模式
为什么使用缓存?
- 在 MultiversX 合约中,存储操作是最昂贵的
- 使用缓存:入口读取 + 出口写入,中间读取免费(内存中)
模式 1:使用 Drop Trait 的回写缓存
在入口加载状态到结构体,在内存中修改,通过 Drop 在作用域退出时提交。
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);
}
}
模式 2:带计算方法的缓存
在缓存结构体上添加派生值方法——使用缓存字段和合约模块方法,无需额外存储读取:
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
}
}
选择性回写
仅回写可变字段——在 Drop 中跳过配置/只读字段以节省 gas。
何时使用 vs 直接存储
| 场景 | 方法 |
|---|---|
| 端点读取 3+ 存储值 | 使用缓存 |
| 单个存储读取/写入 | 直接访问即可 |
| 视图函数读取多个值 | 只读缓存(无 Drop) |
| 异步调用边界 | 在异步调用前手动删除缓存 |
反模式
1. 跨越异步边界的缓存
// 错误 - async_call_and_exit() 终止执行,drop() 永远不会运行!
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();
}
// 正确 - 在异步调用前手动删除缓存
fn good_async(&self) {
{
let mut cache = StorageCache::new(self);
cache.balance += &deposit;
} // cache.drop() 在此处触发
self.tx().to(&other).typed(Proxy).call()
.callback(self.callbacks().on_done())
.async_call_and_exit();
}