Skip to content

Acquired合约的startInvestment 和 invest 函数中目标代币金额的精度处理问题 #12

@lurenpluto

Description

@lurenpluto

问题描述

Acquired.sol 合约中,startInvestmentinvest 函数在处理代币金额时,假设 param.tokenAmount(在 startInvestment 中)和 tokenRatio.tokenAmount(在 invest 中)是以 DAO 代币的精度(通常为 18)定义的。这导致 invest 函数中对 tokenAmount 的精度调整(除以 10 ** deltaDecimals,其中 deltaDecimals = daoTokenDecimals - tokenDecimals)不必要且错误。当 tokenRatio.tokenAmount 已正确使用目标代币的精度时,此调整会导致 tokenAmount 变为不正确的值(通常为 0),从而导致交易失败。

此外,startInvestment 函数未明确要求 param.tokenAmount 必须以目标代币的精度(10^tokenDecimals)定义。如果调用者提供了错误的单位(例如,假设 DAO 代币的 18 位精度),可能导致错误的代币转账。

核心问题是,param.tokenAmounttokenRatio.tokenAmount 应始终使用目标代币的精度(10^tokenDecimals),而不是假设 DAO 代币的精度(10^18)。这将简化逻辑,避免不必要的调整,并防止错误。

示例与计算过程

以下是通过一个具体示例来说明问题:

  • startInvestment 中的设置

    • DAO 代币精度:18
    • 目标代币精度:6(例如 USDC)
    • 投资金额:param.tokenAmount = 9 * 10^6(9 个目标代币,以目标代币的最小单位计)
    • 兑换比例:1 DAO 代币 = 1 目标代币
    • tokenRatio 设置:
      • tokenRatio.daoTokenAmount = 9 * 10^18(9 个 DAO 代币,以 DAO 代币的最小单位计)
      • tokenRatio.tokenAmount = 9 * 10^6(9 个目标代币,以目标代币的最小单位计)
  • startInvestment

    • 合约调用:
      IERC20(param.tokenAddress).transferFrom(msg.sender, address(this), param.tokenAmount);
    • 如果 param.tokenAmount 错误设置为 9 * 10^18(假设 DAO 代币精度),则尝试转账 9 * 10^12 个目标代币,可能因余额或授权不足而失败。
    • 合约未验证 param.tokenAmount 是否符合目标代币的精度,依赖调用者提供正确单位。
  • invest

    • 用户调用 invest(investmentId, amount),设置 amount = 1 * 10^18(1 个 DAO 代币)。
    • 计算:
      uint256 tokenAmount = amount * tokenRatio.tokenAmount / tokenRatio.daoTokenAmount;
      • 代入:tokenAmount = (1 * 10^18) * (9 * 10^6) / (9 * 10^18) = 1 * 10^6
      • 结果:tokenAmount = 1 * 10^6,正确表示 1 个目标代币(以目标代币的最小单位计,精度为 6)。
    • 精度调整:
      if (investment.tokenAddress != address(0)) {
          uint8 tokenDecimals = IERC20Metadata(investment.tokenAddress).decimals();
          uint8 daoTokenDecimals = daoToken.decimals();
          uint8 deltaDecimals = daoTokenDecimals - tokenDecimals;
          tokenAmount /= 10 ** deltaDecimals;
      }
      • deltaDecimals = 18 - 6 = 12
      • tokenAmount = 1 * 10^6 / 10^12 = 10^-6
      • 在 Solidity 中,10^-6 被向下取整为 0,导致 require(tokenAmount > 0, "invalid amount") 失败,交易回滚。
  • 问题

    • 初始计算的 tokenAmount = 1 * 10^6 是正确的,表示 1 个目标代币,符合 1:1 的兑换比例。
    • 额外除以 10^deltaDecimals(即 10^12)是不必要且错误的,因为 tokenRatio.tokenAmount 已正确使用目标代币的精度(10^6)。
    • 此调整假设 tokenRatio.tokenAmount 是以 DAO 代币精度(10^18)定义的,而在示例中并非如此。

影响

  • invest 中错误的 tokenAmount:精度调整导致 tokenAmount 变为 0,触发交易失败,阻止用户完成投资。
  • param.tokenAmount 中的单位不明确startInvestment 缺乏对 param.tokenAmount 单位的明确验证,可能导致错误的代币转账,造成交易失败或金额不符合预期。
  • 灵活性受限:假设 tokenRatio.tokenAmount 使用 DAO 代币精度限制了合约处理以目标代币单位定义的比例的能力。

改进建议

为解决这些问题,建议以下改进,确保 param.tokenAmounttokenRatio.tokenAmount 始终使用目标代币的精度(10^tokenDecimals),从而消除 invest 中不必要的精度调整。

  1. 明确单位要求

    • 在文档和代码注释中明确:
      • param.tokenAmount(在 startInvestment 中)必须以目标代币的最小单位(10^tokenDecimals)计。
      • tokenRatio.tokenAmount 必须以目标代币的最小单位(10^tokenDecimals)计。
      • tokenRatio.daoTokenAmount 必须以 DAO 代币的最小单位(10^daoTokenDecimals)计。
    • 示例文档:
      - `param.tokenAmount`:投资的目标代币金额,以目标代币的最小单位计(例如,USDC 精度为 6,`9 * 10^6` 表示 9 USDC)。
      - `tokenRatio.tokenAmount`:兑换比例的分子,以目标代币的最小单位计(例如,`10^6` 表示 1 USDC)。
      - `tokenRatio.daoTokenAmount`:兑换比例的分母,以 DAO 代币的最小单位计(例如,`10^18` 表示 1 DAO 代币)。
  2. 移除 invest 中的精度调整

    • 由于 tokenRatio.tokenAmount 已使用目标代币的精度,初始计算的 tokenAmount 已是正确的,无需除以 10^deltaDecimals。从 invest 中移除以下代码:
      if (investment.tokenAddress != address(0)) {
          uint8 tokenDecimals = IERC20Metadata(investment.tokenAddress).decimals();
          uint8 daoTokenDecimals = daoToken.decimals();
          uint8 deltaDecimals = daoTokenDecimals - tokenDecimals;
          tokenAmount /= 10 ** deltaDecimals;
      }
    • 这确保 tokenAmount 保持在目标代币的精度(例如,示例中的 1 * 10^6 表示 1 个目标代币)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions