-
Notifications
You must be signed in to change notification settings - Fork 2
Acquired合约的startInvestment 和 invest 函数中目标代币金额的精度处理问题 #12
Description
问题描述
在 Acquired.sol 合约中,startInvestment 和 invest 函数在处理代币金额时,假设 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.tokenAmount 和 tokenRatio.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.tokenAmount 和 tokenRatio.tokenAmount 始终使用目标代币的精度(10^tokenDecimals),从而消除 invest 中不必要的精度调整。
-
明确单位要求:
- 在文档和代码注释中明确:
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 代币)。
- 在文档和代码注释中明确:
-
移除
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 个目标代币)。
- 由于