问题描述
在 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 个目标代币)。
问题描述
在
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中的设置:param.tokenAmount = 9 * 10^6(9 个目标代币,以目标代币的最小单位计)tokenRatio设置:tokenRatio.daoTokenAmount = 9 * 10^18(9 个 DAO 代币,以 DAO 代币的最小单位计)tokenRatio.tokenAmount = 9 * 10^6(9 个目标代币,以目标代币的最小单位计)在
startInvestment中:param.tokenAmount错误设置为9 * 10^18(假设 DAO 代币精度),则尝试转账9 * 10^12个目标代币,可能因余额或授权不足而失败。param.tokenAmount是否符合目标代币的精度,依赖调用者提供正确单位。在
invest中:invest(investmentId, amount),设置amount = 1 * 10^18(1 个 DAO 代币)。tokenAmount = (1 * 10^18) * (9 * 10^6) / (9 * 10^18) = 1 * 10^6。tokenAmount = 1 * 10^6,正确表示 1 个目标代币(以目标代币的最小单位计,精度为 6)。deltaDecimals = 18 - 6 = 12。tokenAmount = 1 * 10^6 / 10^12 = 10^-6。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)计。移除
invest中的精度调整:tokenRatio.tokenAmount已使用目标代币的精度,初始计算的tokenAmount已是正确的,无需除以10^deltaDecimals。从invest中移除以下代码:tokenAmount保持在目标代币的精度(例如,示例中的1 * 10^6表示 1 个目标代币)。