MultiversX智能合约缓存优化Skill multiversx-cache-patterns

这个技能专注于在MultiversX区块链平台上开发智能合约时,通过实现基于Drop写回的缓存模式来优化性能。它能减少存储读取/写入操作,降低Gas消耗,提高合约效率,特别适用于DeFi协议、高频交易或任何对Gas敏感的场景。关键词:MultiversX, 智能合约, 缓存优化, Gas节约, DeFi, 区块链开发, 写回缓存, 存储操作

智能合约 0 次安装 0 次浏览 更新于 3/21/2026

name: multiversx-cache-patterns description: 用于MultiversX智能合约的基于Drop写回缓存的Gas优化缓存模式。适用于构建每个交易读取/写入多个存储值的合约、DeFi协议或任何对Gas敏感的合约。

MultiversX缓存模式

为什么需要缓存?

  • 存储读取/写入是MultiversX智能合约中最昂贵的操作
  • 一个读取5个存储值并写回3个的端点需要8次存储操作
  • 使用缓存:进入时5次读取 + 退出时3次写入 = 相同,但函数内部的中间读取是免费的(在内存中)

模式1:基于Drop特质的写回缓存

核心模式:在进入时将状态加载到结构体中,在内存中修改,通过Drop在作用域退出时提交。

multiversx_sc::imports!();
use multiversx_sc::derive_imports::*;

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);
    }
}

在端点中的使用

#[endpoint]
fn deposit(&self) {
    let payment = self.call_value().single();
    let mut cache = StorageCache::new(self);

    // 所有读取/写入都在内存中 - 初始加载后免费
    cache.field_a += payment.amount.as_big_uint();
    cache.field_b += &self.calculate_shares(&cache, payment.amount.as_big_uint());

    // cache.drop() 在此处自动调用 - 写入存储
}

模式2:只读缓存(无Drop)

用于视图函数或读取密集的操作,不需要写回:

pub struct ReadCache<'a, C: crate::storage::StorageModule> {
    sc_ref: &'a C,
    pub total_supply: BigUint<C::Api>,
    pub total_reserve: BigUint<C::Api>,
    pub config_params: YourConfigType<C::Api>,
}

impl<'a, C: crate::storage::StorageModule> ReadCache<'a, C> {
    pub fn new(sc_ref: &'a C) -> Self {
        ReadCache {
            total_supply: sc_ref.total_supply().get(),
            total_reserve: sc_ref.total_reserve().get(),
            config_params: sc_ref.config_params().get(),
            sc_ref,
        }
    }
    // 无Drop实现 - 不写回任何内容
}

模式3:带计算方法的缓存

当缓存需要从缓存字段计算衍生值时,直接在缓存结构体上添加方法:

pub struct StateCache<'a, C>
where
    C: crate::storage::StorageModule + crate::math::MathModule,
{
    sc_ref: &'a C,
    pub total_deposited: BigUint<C::Api>,
    pub total_shares: BigUint<C::Api>,
    pub fee_rate_bps: u64,
}

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
    }

    /// 使用缓存数据和合约的数学模块计算
    pub fn calculate_fee(&self, amount: &BigUint<C::Api>) -> BigUint<C::Api> {
        (amount * self.fee_rate_bps) / 10_000u64
    }
}

关键洞察:缓存可以持有对合约的引用(sc_ref)并调用其模块方法。这允许您使用缓存字段和共享数学特质计算衍生值 — 而无需额外存储读取。

选择性写回

当只有某些字段可变时,避免写回未更改的字段:

impl<C> Drop for StorageCache<'_, C>
where
    C: crate::storage::StorageModule,
{
    fn drop(&mut self) {
        // 只写回可能更改的字段
        self.sc_ref.balance().set(&self.balance);
        self.sc_ref.total_shares().set(&self.total_shares);
        // 不要写回:self.config_params(在此上下文中为只读,永不更改)
    }
}

何时使用 vs 直接存储访问

场景 方法
端点读取3+个存储值 使用缓存
单个存储读取/写入 直接访问即可
视图函数读取多个值 只读缓存(无Drop)
多个端点共享相同状态 创建共享缓存结构体
异步调用边界 在异步调用前手动删除缓存

反模式

1. 跨异步边界缓存

// 错误 - async_call_and_exit() 终止执行,drop() 从不运行!
// 缓存写入丢失,不是过时。
fn bad_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();
    // cache 从未删除 — 余额更改丢失!
}

// 正确 - 在异步调用前手动删除缓存
fn good_async(&self) {
    let deposit = self.call_value().egld_value().clone_value();

    {
        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();
}

坏 — 在异步边界持有缓存

// 不要:缓存从未删除 — 写入静默丢失
fn bad(&self) {
    let mut cache = StorageCache::new(self);
    cache.balance += &amount;
    self.tx().to(&other).typed(Proxy).call()
        .callback(self.callbacks().on_done())
        .async_call_and_exit(); // 执行停止 — drop() 从不运行!
}

好 — 克隆/作用域在异步前

// 做:作用域缓存以便drop()在异步调用前触发
fn good(&self) {
    {
        let mut cache = StorageCache::new(self);
        cache.balance += &amount;
    } // drop() 在此处触发 — 写入提交到存储

    self.tx().to(&other).typed(Proxy).call()
        .callback(self.callbacks().on_done())
        .async_call_and_exit();
}

2. 在Drop中忘记字段

// 错误 - 忘记写回field_c
impl<C> Drop for StorageCache<'_, C> {
    fn drop(&mut self) {
        self.sc_ref.field_a().set(&self.field_a);
        self.sc_ref.field_b().set(&self.field_b);
        // 错误:field_c更改丢失!
    }
}

3. 写回不可变配置

// 错误 - 配置很少更改,不要每次都写回
impl<C> Drop for Cache<'_, C> {
    fn drop(&mut self) {
        self.sc_ref.config_params().set(&self.config_params); // 不必要的写入!
    }
}

模板:入门缓存

multiversx_sc::imports!();
use multiversx_sc::derive_imports::*;

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>,
}

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(),
            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);
    }
}