原子借贷-执行-验证模式Skill multiversx-flash-loan-patterns

用于在MultiversX区块链上实现原子借贷、闪电贷等操作的智能合约模式。此模式确保资产借出、执行回调、验证还款和手续费在单笔交易内完成,增强安全性和原子性,适用于DeFi应用如闪贷和原子交换。关键词:MultiversX, 原子借贷, 闪电贷, 智能合约, 安全模式, Rust, DeFi, 区块链开发。

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

名称:multiversx-flash-loan-patterns 描述:用于MultiversX智能合约的原子借贷-执行-验证模式。适用于构建闪电贷、原子交换、临时授权,或任何在单笔交易内借出资产、执行回调并验证还款的操作。

MultiversX原子借贷-执行-验证模式

一种用于临时借出资产、执行外部回调并验证还款的操作模式——所有这些都在单笔交易内原子性地完成。

解决什么问题?

您想将代币借给合约,让其执行任意逻辑,并在交易完成前保证还款(加手续费)。如果还款失败,整个交易将回滚。

何时使用

场景 是否使用此模式?
闪电贷 是——典型用例
带验证的原子交换 是——发送代币,验证对方返还
临时授权(执行后返回) 是——借出代币用于计算,验证返还
跨分片操作 否——原子性要求在同一分片
简单转账 否——杀鸡用牛刀

安全检查清单

  1. 重入防护——防止嵌套操作
  2. 分片验证——调用者必须在同一分片(原子性要求)
  3. 端点验证——回调不能是内置函数
  4. 还款验证——在回调后检查合约余额
  5. 防护清理——始终清除重入标志

核心流程:防护 → 发送 → 执行 → 验证 → 清除

#[endpoint(atomicOperation)]
fn atomic_operation(
    &self,
    asset: TokenId,
    amount: BigUint,
    target_contract: ManagedAddress,
    callback_endpoint: ManagedBuffer,
) {
    // 1. 重入防护
    self.require_not_ongoing();

    // 2. 分片验证(原子性要求同一分片)
    self.require_same_shard(&target_contract);

    // 3. 端点验证
    self.require_valid_endpoint(&callback_endpoint);

    // 4. 计算预期还款
    let fee = &amount * self.fee_bps().get() / 10_000u64;
    let balance_before = self.blockchain().get_sc_balance(&asset.clone().into(), 0);

    // 5. 设置防护
    self.operation_ongoing().set(true);

    // 6. 发送代币并调用目标
    self.tx()
        .to(&target_contract)
        .raw_call(callback_endpoint)
        .single_esdt(&asset, 0, &amount)
        .sync_call();

    // 7. 验证还款
    let balance_after = self.blockchain().get_sc_balance(&asset.into(), 0);
    require!(
        balance_after >= balance_before + &fee,
        "还款不足"
    );

    // 8. 清除防护
    self.operation_ongoing().set(false);
}

重入防护

#[storage_mapper("operationOngoing")]
fn operation_ongoing(&self) -> SingleValueMapper<bool>;

fn require_not_ongoing(&self) {
    require!(
        !self.operation_ongoing().get(),
        "操作已在进行中"
    );
}

为何需要:没有此防护,恶意回调可能重入操作端点,创建嵌套操作以绕过还款检查。

分片验证

fn require_same_shard(&self, target_address: &ManagedAddress) {
    let target_shard = self.blockchain().get_shard_of_address(target_address);
    let contract_shard = self.blockchain().get_shard_of_address(
        &self.blockchain().get_sc_address()
    );
    require!(
        target_shard == contract_shard,
        "目标必须在同一分片"
    );
}

为何需要:跨分片调用在不同区块/轮次中执行,破坏原子性。回调将在单独交易中运行,允许在发送和验证之间进行操纵。

端点验证

fn require_valid_endpoint(&self, endpoint: &ManagedBuffer<Self::Api>) {
    require!(
        !endpoint.is_empty() && !self.blockchain().is_builtin_function(endpoint),
        "无效的回调端点"
    );
}

为何需要:内置函数(代币转账、ESDT操作)可能在不执行预期回调的情况下重定向代币,绕过还款逻辑。

重入防护示例

不好

// 不要:无重入防护——恶意回调重入并再次借贷
#[endpoint(flashLoan)]
fn flash_loan(&self, asset: TokenId, amount: BigUint, target: ManagedAddress) {
    let balance_before = self.blockchain().get_sc_balance(&asset.clone().into(), 0);
    self.tx().to(&target).raw_call("execute").single_esdt(&asset, 0, &amount).sync_call();
    let balance_after = self.blockchain().get_sc_balance(&asset.into(), 0);
    require!(balance_after >= balance_before, "未还款"); // 被重入绕过!
}

// 做:在发送前设置重入防护,在验证后清除
#[endpoint(flashLoan)]
fn flash_loan(&self, asset: TokenId, amount: BigUint, target: ManagedAddress) {
    self.require_not_ongoing(); // 阻止嵌套调用
    self.operation_ongoing().set(true);

    let balance_before = self.blockchain().get_sc_balance(&asset.clone().into(), 0);
    self.tx().to(&target).raw_call("execute").single_esdt(&asset, 0, &amount).sync_call();
    let balance_after = self.blockchain().get_sc_balance(&asset.into(), 0);
    require!(balance_after >= balance_before, "未还款");

    self.operation_ongoing().set(false);
}

反模式

1. 忘记清除防护

// 错误——如果验证失败,防护将永远保持设置
self.operation_ongoing().set(true);
self.tx().to(&target).raw_call(endpoint).sync_call();
// 如果这个require失败,防护永远不会被清除!
require!(balance_after >= expected, "未还款");
self.operation_ongoing().set(false);

注意:在MultiversX中,如果require!失败,交易回滚,所以防护也会回滚。但在基于回调的流程中,请注意您所处的执行上下文。

2. 检查余额不正确

// 错误——检查特定存储值而非实际合约余额
let repaid = self.deposits(&asset).get();

// 正确——检查实际链上余额
let balance_after = self.blockchain().get_sc_balance(&asset.into(), 0);

3. 无分片验证

// 错误——跨分片调用会悄悄破坏原子性
fn flash_loan(&self, borrower: ManagedAddress, /* ... */) {
    // 如果借款人在不同分片,sync_call变成异步
    self.tx().to(&borrower).raw_call(endpoint).sync_call();
}

模板

#[multiversx_sc::module]
pub trait AtomicOperationModule {
    #[storage_mapper("operationOngoing")]
    fn operation_ongoing(&self) -> SingleValueMapper<bool>;

    #[storage_mapper("feeBps")]
    fn fee_bps(&self) -> SingleValueMapper<u64>;

    fn require_not_ongoing(&self) {
        require!(!self.operation_ongoing().get(), "操作已在进行中");
    }

    fn require_same_shard(&self, target: &ManagedAddress) {
        let target_shard = self.blockchain().get_shard_of_address(target);
        let self_shard = self.blockchain().get_shard_of_address(&self.blockchain().get_sc_address());
        require!(target_shard == self_shard, "必须在同一分片");
    }

    fn require_valid_endpoint(&self, endpoint: &ManagedBuffer<Self::Api>) {
        require!(
            !endpoint.is_empty() && !self.blockchain().is_builtin_function(endpoint),
            "无效的端点"
        );
    }
}