You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Solidity合约:事件触发时从管理员存款向用户正确发放ETH及bug排查

核心Bug定位
  • 收益转账目标完全写反:触发价格条件时,代码将2倍锁仓额的收益转给了管理员payable(manager).transfer(amountOfToken),而非取款用户,这是用户无法拿到额外收益的最直接原因
  • 价格取数逻辑不符合业务要求:全局变量ethPrice仅在用户存款时更新一次,取款时没有重新拉取Chainlink预言机的锁仓到期时刻最新价格,且全局单值会被多用户的存款操作覆盖,根本无法作为收益发放的判断依据
  • 用户锁仓数据存储结构错误:deadline(锁仓到期时间)等核心字段使用全局单变量存储,多用户存款时,后一个用户的数据会直接覆盖前一个用户的所有锁仓信息,仅最后一个存款的用户数据能被正常读取
  • 管理员保证金逻辑完全失效:管理员补充保证金的depositVault函数仅做了身份校验,没有将转入的msg.value计入保证金池余额,发放收益时也没有校验保证金是否充足、没有做对应余额扣减
  • 存在大量无效冗余代码:合约顶层定义的lockAmount、startTime全局变量属于无效代码,既不在函数内执行,也无实际业务作用;startTimes映射用时间戳作为key,完全不符合按用户维度存储数据的需求
  • 基础逻辑漏洞:原代码未校验用户是否存在有效锁仓、是否已经提取过资金,存在重复取款风险;使用transfer方法硬编码2300gas限制,在部分网络升级后容易出现转账失败问题;价格判断逻辑放在到期时间校验之前,执行顺序不合理。
正确实现代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract EthVault {
    // 管理员地址
    address public immutable manager;
    // 管理员保证金总余额
    uint256 public managerDepositBalance;
    // Chainlink ETH/USD预言机地址(Kovan测试网)
    AggregatorV3Interface internal immutable priceFeed;
    // 价格触发阈值:2000美元,预言机精度为8位,对应值为2000 * 1e8
    int256 public constant PRICE_THRESHOLD = 2000 * 100000000;

    // 用户锁仓信息结构
    struct LockInfo {
        uint256 amount; // 锁仓ETH金额
        uint256 deadline; // 锁仓到期时间戳
        bool isWithdrawn; // 是否已取款
    }
    // 按用户地址映射锁仓信息
    mapping(address => LockInfo) public userLockInfo;

    constructor() payable {
        manager = msg.sender;
        managerDepositBalance = msg.value;
        // 初始化Kovan网ETH/USD预言机
        priceFeed = AggregatorV3Interface(0x9326BFA02ADD2366b30bacB125260Af641031331);
    }

    // 管理员补充保证金
    function addManagerDeposit() external payable {
        require(msg.sender == manager, "Only manager");
        require(msg.value > 0, "Deposit amount must be greater than 0");
        managerDepositBalance += msg.value;
    }

    // 用户锁仓ETH
    function lockEth(uint256 lockSeconds) external payable {
        require(msg.value > 0, "Lock amount must be greater than 0");
        require(lockSeconds > 0, "Lock period must be greater than 0");
        LockInfo storage info = userLockInfo[msg.sender];
        require(info.amount == 0, "You already have an active lock");

        info.amount = msg.value;
        info.deadline = block.timestamp + lockSeconds;
        info.isWithdrawn = false;
    }

    // 用户取款
    function withdraw() external {
        LockInfo storage info = userLockInfo[msg.sender];
        require(info.amount > 0, "No active lock");
        require(!info.isWithdrawn, "Already withdrawn");
        require(block.timestamp >= info.deadline, "Lock not expired");

        // 拉取到期时刻最新ETH价格
        (,int256 latestPrice,,,) = priceFeed.latestRoundData();
        require(latestPrice > 0, "Invalid price feed data");

        uint256 totalPay = info.amount;
        // 价格低于2000美元时,加发2倍本金的收益
        if (latestPrice < PRICE_THRESHOLD) {
            uint256 reward = info.amount * 2;
            require(managerDepositBalance >= reward, "Insufficient reward balance");
            // 从保证金中扣除收益
            managerDepositBalance -= reward;
            totalPay += reward;
        }

        // 先标记为已取款,防重入
        info.isWithdrawn = true;
        info.amount = 0;

        // 执行转账,用call替代transfer避免gas限制问题
        (bool success,) = payable(msg.sender).call{value: totalPay}("");
        require(success, "Transfer failed");
    }

    // 接收ETH的兜底函数
    receive() external payable {}
}
优化建议
  • 加重入锁:建议引入重入保护修饰器,所有涉及外部转账的函数优先执行状态修改,再做外部调用,进一步降低重入攻击风险
  • 加预言机数据校验:读取预言机价格时,需要校验价格返回的时间戳,若最新价格更新时间超过预设阈值(比如1小时),则暂停取款逻辑,避免使用过期价格造成资金损失
  • 动态适配预言机精度:不要硬编码价格精度,可通过预言机的decimals()方法动态读取精度值计算价格阈值,方便合约部署到不同网络时适配不同预言机配置
  • 加紧急管控能力:可增加管理员紧急暂停、提取剩余保证金的功能,在出现预言机故障、合约漏洞时及时止损
  • 去掉不必要的payable修饰:withdraw等不需要用户转入资金的函数不要加payable,避免用户误转资金。

内容的提问来源于stack exchange,提问作者Ale Ale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 14:42:32