Cakepie合约安全审计报告

这是我们在2023年11月为Cakepie Contracts进行的安全审计报告。

Cakepie合约安全审计报告

报告清单

项目 描述
客户 Magpie XYZ
目标 CakePie 合约

版本历史

版本 日期 描述
1.0 2023年11月30日 首次发布

1. 引言

1.1 关于目标合约

信息 描述
类型 智能合约
语言 Solidity
方法 半自动化和手动验证

本次审计的目标是 Magpie XYZ 的 CakePie 合约^1 代码库。CakePie 合约运行一个 CakeRush 活动,用户可以在 CakePie 上将 CAKE 代币或锁定的 CAKE 头寸转换为 CakePie。请注意,本次审计范围仅包括 CakeRush.sol 和 PancakeStakingBNBChain.sol,其他文件均不在审计范围内。

审计过程是迭代的。具体来说,我们将审计修复已发现问题的提交。如果存在新问题,我们将继续此过程。审计期间的提交 SHA 值显示在下表中。我们的审计报告负责初始版本(版本 1)以及用于修复审计报告中问题的(后续版本中的)新代码。

1.2 安全模型

为了评估风险,我们遵循行业和学术界广泛采用的标准或建议,包括 OWASP 风险评级方法^2 和常见漏洞枚举^3。风险的整体严重性可能性影响决定。具体来说,可能性用于估计攻击者发现和利用特定漏洞的可能性,而影响则用于衡量成功利用的后果。

在本报告中,可能性和影响均分为两个等级,即,它们的组合显示在表 1.1 中。

因此,本报告中测量的严重性分为三类:。为了完整起见,还使用未定来涵盖风险无法很好确定的情况。

此外,已发现项目的状态将属于以下四类之一:

  • 未定 尚未收到回复。

  • 已确认 项目已由客户收到,但尚未确认。

  • 已确认 项目已由客户承认,但尚未修复。

  • 已修复 项目已由客户确认并修复。

2. 发现

总共,我们发现了两个潜在问题。此外,我们还有项建议和一项说明。

  • 高风险:1

  • 低风险:1

  • 建议:3

  • 说明:1

ID 严重性 描述 类别 状态
1 参数重置后可能的状态不一致 软件安全 已修复
2 重复申领 mCake 奖励 软件安全 已修复
3 - 检查初始化函数中的参数 建议 已确认
4 - 检查 CakeRush 合约中的参数 建议 已修复
5 - 修饰符中的额外条件 建议 已确认
6 - 潜在的中心化风险 说明 -

详细信息将在以下各节中提供。

2.1 软件安全

2.1.1 参数重置后可能的状态不一致

项目 描述
严重性
状态 已在版本 2 中修复
引入者 版本 1

描述 CakeRush 合约根据多个参数分配奖励。以下函数允许项目维护者重置某些参数:

function resetMultiplier() external onlyOwner {
        uint256 len = rewardMultiplier.length;
        for (uint8 i = 0; i < len; ++i) {
            rewardMultiplier.pop();
            rewardTier.pop();
        }

        tierLength = 0;
    }

    function resetTimeWeighting() external onlyOwner {
        uint256 len = weightedTime.length;
        for (uint8 i = 0; i < len; ++i) {
            weightedTime.pop();
            weighting.pop();
        }

        weightLength = 0;
    }

列表 2.1:CakeRush.sol

但是,这些函数仅重置参数,而不重置存储在 userInfos 状态变量中的用户信息。因此,CakeRush 合约中的计算可能会因状态不一致而失败。例如,如果参数被重置并设置为不正确的值,Line 155 上的减法可能会因整数下溢而失败。

function quoteConvert(
        uint256 _amountToConvert,
        address _account
    )
        external
        view
        returns (
            uint256 newUserFactor,
            uint256 newTotalFactor,
            uint256 newUserWeightedFactor,
            uint256 newWeightedTotalFactor
        )
    {
        if (_amountToConvert == 0 || rewardMultiplier.length == 0 || weighting.length == 0)
            return (0, 0, 0, 0);

        UserInfo storage userInfo = userInfos[_account];
        uint256 accumulated = _amountToConvert + userInfo.converted;

        uint256 factorAccuNoWeighting = 0;
        uint256 i = 1;
        while (i < rewardTier.length && accumulated > rewardTier[i]) {
            factorAccuNoWeighting += (rewardTier[i] - rewardTier[i - 1]) * rewardMultiplier[i - 1];
            i++;
        }
        factorAccuNoWeighting += (accumulated - rewardTier[i - 1]) * rewardMultiplier[i - 1];

        uint256 factorToEarnNoWeighting = (factorAccuNoWeighting / DENOMINATOR) - userInfo.factor;

列表 2.2:CakeRush.sol

更糟糕的是,如果用户在重置参数后(例如通过回滚之前)立即调用 convertconvertWithCakePool,由于 Line 141-142 上的逻辑,合同中记录的总因子和加权因子可能会被重置。

影响 重置参数可能导致状态不一致和不正确。

建议 在清除旧参数后设置新参数。

项目反馈 一旦 Cake Rush 活动开始,乘数将不会被重置。

2.1.2 重复申领 mCake 奖励

项目 描述
严重性
状态 已在版本 3 中修复
引入者 版本 2

描述 在合同中锁定 CAKE 代币后,用户可以通过该函数申领 mCake 代币作为奖励。但是,该函数包含一个允许用户多次申领奖励的错误。在下面的代码片段中,如果金额大于用户的,将向用户转账或存入总金额。正确的实现应该只返回,因此当前的实现有效地允许用户反复申领 mCake 奖励。

function claim(bool _isStake) external nonReentrant {
        UserInfo storage userInfo = userInfos[msg.sender];
        if (claimedMCake[msg.sender] >= userInfo.converted) revert AlreadyClaimed();
        if (_isStake && userInfo.converted > 0) {
            if (masterCakepie == address(0)) revert MasterCakepieNotSet();
            IERC20(mCakeOFT).safeApprove(address(masterCakepie), userInfo.converted);
            IMasterCakepie(masterCakepie).depositFor(
                address(mCakeOFT),
                address(msg.sender),
                userInfo.converted
            );
        } else if (userInfo.converted > 0) {
            IERC20(mCakeOFT).transfer(msg.sender, userInfo.converted);
            emit Claim(msg.sender, userInfo.converted);
        }

        claimedMCake[msg.sender] = userInfo.converted;
    }

列表 2.3:CakeRush.sol

影响 用户可以反复申领 mCake 奖励。

建议 修改奖励申领逻辑。

2.2 附加建议

2.2.1 检查初始化函数中的参数

项目 描述
状态 已确认
引入者 版本 1

描述 在 CakeRush 和 PancakeStakingBNBChain 合约的初始化函数中,存在一些初始化后无法更改的参数。建议在初始化函数中检查这些参数。

function __CakeRush_init(
        address _cake,
        address _mCakeOFT,
        address _masterCakepie
    ) public initializer {
        __Ownable_init();
        __ReentrancyGuard_init();
        __Pausable_init();
        cake = _cake;
        mCakeOFT = _mCakeOFT;
        masterCakepie = _masterCakepie;
    }

列表 2.4:CakeRush.sol

影响 不适用

建议 检查初始化函数中的参数。

2.2.2 检查 CakeRush 合约中的参数

项目 描述
状态 已在版本 2 中修复
引入者 版本 1

描述 在 CakeRush 合约中,可以添加几个关于奖励分配的参数。但是,没有检查这些参数是否根据合约中的假设正确设置。具体来说,在 setMultiplersetTimeWeighting 函数中,必须检查额外的条件(即 rewardTierweightedTime 数组的单调递增属性)。

function setMultiplier(
        uint256[] calldata _multiplier,
        uint256[] calldata _tier
    ) external onlyOwner {
        if (_multiplier.length == 0 || (_multiplier.length != _tier.length)) revert LengthInvalid();

        for (uint8 i = 0; i < _multiplier.length; ++i) {
            if (_multiplier[i] == 0) revert InvalidAmount();
            rewardMultiplier.push(_multiplier[i]);
            rewardTier.push(_tier[i]);
            tierLength += 1;
        }
    }

列表 2.5:CakeRush.sol

影响 不适用

建议 检查设置参数的函数中的参数。

2.2.3 修饰符中的额外条件

项目 描述
状态 已确认
引入者 版本 1

描述 在 CakeRush 合约中,_onlyPancakeStaking 修饰符包含一个不必要的条件。根据此修饰符的语义,检查 msg.sender != pancakeStaking 就足够了。

modifier _onlyPancakeStaking() {
        if (pancakeStaking == address(0) || msg.sender != pancakeStaking)
            revert OnlyPancakeStaking();
        _;
    }

列表 2.6:CakeRush.sol

影响 不适用

建议 删除修饰符中的不必要条件。

2.3 说明

2.3.1 潜在的中心化风险

描述 CakeRush 的所有者拥有修改关键配置的重大特权。这造成了单点故障。如果攻击者损害了所有者,他们可能会使整个系统瘫痪。

此外,合同中的 CAKE 代币并未显式处理以将其锁定到 VECake 合约中。相反,CakeRush 允许所有者提取所有这些 CAKE,这意味着所有者必须在提取后锁定 CAKE 代币。但是,逻辑在代码层面并未得到保证,这也带来了中心化方面的担忧。

项目反馈 团队将所有者设置为多重签名以缓解风险。

3. 通知和备注

3.1 免责声明

本审计报告不构成投资建议或个人推荐。它不考虑,也不应被解释为考虑或对代币、代币销售或任何其他产品、服务或资产的潜在经济产生任何影响。任何实体均不应以任何方式依赖本报告,包括用于做出购买或出售任何代币、产品、服务或资产的决定。

本审计报告并非对任何特定项目或团队的认可,报告不保证任何特定项目的安全性。本次审计不保证发现智能合约的所有安全问题,即评估结果不保证不存在任何进一步发现的安全问题。由于一次审计不能被认为是全面的,我们始终建议进行独立的审计和公开的赏金计划,以确保智能合约的安全性。

本次审计的范围仅限于 1.1 节中提到的代码。除非明确说明,否则语言本身(例如 Solidity 语言)、底层编译工具链和计算基础设施的安全性均不在审计范围内。

3.2 审计流程

我们按照以下流程进行审计。

  • 漏洞检测 我们首先使用自动代码分析器扫描智能合约,然后手动验证(拒绝或确认)它们报告的问题。

  • 语义分析 我们研究智能合约的业务逻辑,并使用自动模糊测试工具(由我们的研究团队开发)对可能的漏洞进行进一步调查。我们还请独立审计师手动分析可能的攻击场景,以交叉检查结果。

  • 建议 我们从良好的编程实践的角度为开发人员提供一些有用的建议,包括 Gas 优化、代码风格等。

我们在以下展示主要具体的检查点。

3.2.1 软件安全

  • 重入攻击

  • 拒绝服务 (DoS)

  • 访问控制

  • 数据处理和数据流

  • 异常处理

  • 不可信的外部调用和控制流

  • 初始化一致性

  • 事件操作

  • 易出错的随机性

  • 代理系统使用不当

3.2.2 DeFi 安全

  • 语义一致性

  • 功能一致性

  • 权限管理

  • 业务逻辑

  • 代币操作

  • 紧急机制

  • 预言机安全

  • 白名单和黑名单

  • 经济影响

  • 批量转账

3.2.3 NFT 安全

  • 重复项目

  • 代币接收者验证

  • 链下元数据安全

3.2.4 附加建议

  • Gas 优化

  • 代码质量和风格

注意 以上检查点是主要的。我们可能会根据项目的具体功能在审计过程中使用更多的检查点。

Sign up for the latest updates