在以太坊智能合约开发中,数学运算的安全性至关重要,因为错误的运算可能导致溢出、截断或其他漏洞,从而危及合约的安全性和可靠性。Solidity是一种静态类型语言,早期版本(0.8.0之前)对整数溢出没有内置保护,因此开发者需要特别注意。数学运算中的安全问题常见风险Solidity中的数学运
在以太坊智能合约开发中,数学运算的安全性至关重要,因为错误的运算可能导致溢出、截断或其他漏洞,从而危及合约的安全性和可靠性。Solidity 是一种静态类型语言,早期版本(0.8.0 之前)对整数溢出没有内置保护,因此开发者需要特别注意。
Solidity 中的数学运算可能面临以下风险:
uint256
类型加法可能从最大值溢出到 0,减法可能从 0 下溢到最大值。uint256
到 uint128
)转换时,数据可能丢失。从 Solidity 0.8.0 开始,默认启用溢出检查:
+
, -
, *
, /
, %
等运算符会检查溢出/下溢,溢出时抛出 Panic(0x11)
错误。尽管如此,开发者仍需掌握安全数学运算的最佳实践,以应对复杂场景和兼容旧版本。
在 0.8.0 及以上版本中,Solidity 自动为 uint
和 int
类型添加溢出检查。
示例:简单的加法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SafeMathBasic {
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // 自动检查溢出
}
function subtract(uint256 a, uint256 b) public pure returns (uint256) {
return a - b; // 自动检查下溢
}
}
说明:
a + b
如果溢出,会抛出 Panic(0x11)
。a - b
如果下溢,同样抛出错误。测试用例:
const { expect } = require("chai");
describe("SafeMathBasic", function () {
let SafeMathBasic, contract;
beforeEach(async function () {
SafeMathBasic = await ethers.getContractFactory("SafeMathBasic");
contract = await SafeMathBasic.deploy();
await contract.deployed();
});
it("should add correctly", async function () {
expect(await contract.add(100, 200)).to.equal(300);
});
it("should revert on overflow", async function () {
await expect(contract.add(ethers.MaxUint256, 1)).to.be.revertedWithPanic("0x11");
});
it("should subtract correctly", async function () {
expect(await contract.subtract(200, 100)).to.equal(100);
});
it("should revert on underflow", async function () {
await expect(contract.subtract(100, 200)).to.be.revertedWithPanic("0x11");
});
});
运行测试:
npx hardhat test
注意:
unchecked
块禁用检查(见下文)。unchecked
块(谨慎使用)在 Solidity 0.8.0+ 中,unchecked
块可禁用溢出检查,用于优化 Gas 或特定场景(如模运算)。
示例:使用 unchecked
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract UncheckedMath {
function addUnchecked(uint256 a, uint256 b) public pure returns (uint256) {
unchecked {
return a + b; // 溢出时环绕
}
}
}
说明:
unchecked
允许溢出环绕(如 uint256.max + 1 = 0
)。测试用例:
const { expect } = require("chai");
describe("UncheckedMath", function () {
let UncheckedMath, contract;
beforeEach(async function () {
UncheckedMath = await ethers.getContractFactory("UncheckedMath");
contract = await UncheckedMath.deploy();
await contract.deployed();
});
it("should wrap around on overflow", async function () {
expect(await contract.addUnchecked(ethers.MaxUint256, 1)).to.equal(0);
});
});
注意:
unchecked
。对于 Solidity < 0.8.0,推荐使用 OpenZeppelin 的 SafeMath
库,显式检查溢出。
示例:使用 SafeMath
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.6;
import "@openzeppelin/contracts/math/SafeMath.sol";
contract SafeMathLegacy {
using SafeMath for uint256;
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a.add(b); // 检查溢出
}
function subtract(uint256 a, uint256 b) public pure returns (uint256) {
return a.sub(b); // 检查下溢
}
function multiply(uint256 a, uint256 b) public pure returns (uint256) {
return a.mul(b); // 检查溢出
}
function divide(uint256 a, uint256 b) public pure returns (uint256) {
return a.div(b); // 检查除零
}
}
说明:
SafeMath
提供 add
, sub
, mul
, div
等函数,自动检查溢出和除零。using SafeMath for uint256
简化调用。测试用例:
const { expect } = require("chai");
describe("SafeMathLegacy", function () {
let SafeMathLegacy, contract;
beforeEach(async function () {
SafeMathLegacy = await ethers.getContractFactory("SafeMathLegacy");
contract = await SafeMathLegacy.deploy();
await contract.deployed();
});
it("should add correctly", async function () {
expect(await contract.add(100, 200)).to.equal(300);
});
it("should revert on overflow", async function () {
await expect(contract.add(ethers.constants.MaxUint256, 1)).to.be.revertedWith("SafeMath: addition overflow");
});
it("should divide correctly", async function () {
expect(await contract.divide(100, 4)).to.equal(25);
});
it("should revert on division by zero", async function () {
await expect(contract.divide(100, 0)).to.be.revertedWith("SafeMath: division by zero");
});
});
安装 OpenZeppelin:
npm install @openzeppelin/contracts@3.4.2
注意:
SafeMath
,因为内置溢出检查已足够。SafeMath
是行业标准。除零检查: 即使在 0.8.0+,除法仍需显式检查除数不为 0。
示例:
function divide(uint256 a, uint256 b) public pure returns (uint256) {
require(b != 0, "Division by zero");
return a / b;
}
精度问题: Solidity 不支持浮点数,需使用整数模拟小数运算(如 DeFi 的利率计算)。
示例:处理小数
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SafeDecimalMath {
uint256 public constant DECIMAL_PRECISION = 1e18; // 18 位小数
function multiplyDecimal(uint256 a, uint256 b) public pure returns (uint256) {
return (a * b) / DECIMAL_PRECISION;
}
function divideDecimal(uint256 a, uint256 b) public pure returns (uint256) {
require(b != 0, "Division by zero");
return (a * DECIMAL_PRECISION) / b;
}
}
说明:
DECIMAL_PRECISION
(如 1e18)模拟小数运算。(a * DECIMAL_PRECISION) / b
)避免精度丢失。测试用例:
const { expect } = require("chai");
describe("SafeDecimalMath", function () {
let SafeDecimalMath, contract;
beforeEach(async function () {
SafeDecimalMath = await ethers.getContractFactory("SafeDecimalMath");
contract = await SafeDecimalMath.deploy();
await contract.deployed();
});
it("should multiply decimals correctly", async function () {
const a = ethers.parseUnits("2", 18); // 2.0
const b = ethers.parseUnits("3", 18); // 3.0
expect(await contract.multiplyDecimal(a, b)).to.equal(ethers.parseUnits("6", 18));
});
it("should divide decimals correctly", async function () {
const a = ethers.parseUnits("6", 18); // 6.0
const b = ethers.parseUnits("2", 18); // 2.0
expect(await contract.divideDecimal(a, b)).to.equal(ethers.parseUnits("3", 18));
});
it("should revert on division by zero", async function () {
await expect(contract.divideDecimal(100, 0)).to.be.revertedWith("Division by zero");
});
});
对于特殊需求,可实现自定义安全数学函数。
示例:自定义加法和乘法
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract CustomSafeMath {
error Overflow();
error Underflow();
error DivisionByZero();
function safeAdd(uint256 a, uint256 b) public pure returns (uint256) {
uint256 c = a + b;
if (c < a) revert Overflow();
return c;
}
function safeMul(uint256 a, uint256 b) public pure returns (uint256) {
if (a == 0 || b == 0) return 0;
uint256 c = a * b;
if (c / a != b) revert Overflow();
return c;
}
function safeDiv(uint256 a, uint256 b) public pure returns (uint256) {
if (b == 0) revert DivisionByZero();
return a / b;
}
}
说明:
Overflow
, Underflow
, DivisionByZero
)节省 Gas。safeMul
检查 c / a == b
确保无溢出。测试用例:
const { expect } = require("chai");
describe("CustomSafeMath", function () {
let CustomSafeMath, contract;
beforeEach(async function () {
CustomSafeMath = await ethers.getContractFactory("CustomSafeMath");
contract = await CustomSafeMath.deploy();
await contract.deployed();
});
it("should add correctly", async function () {
expect(await contract.safeAdd(100, 200)).to.equal(300);
});
it("should revert on overflow", async function () {
await expect(contract.safeAdd(ethers.MaxUint256, 1)).to.be.revertedWithCustomError(contract, "Overflow");
});
it("should multiply correctly", async function () {
expect(await contract.safeMul(10, 20)).to.equal(200);
});
it("should revert on division by zero", async function () {
await expect(contract.safeDiv(100, 0)).to.be.revertedWithCustomError(contract, "DivisionByZero");
});
});
以下是一个安全的 DeFi 存款合约,结合内置溢出检查和自定义小数运算。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SafeDeposit is ReentrancyGuard {
uint256 public constant DECIMAL_PRECISION = 1e18;
mapping(address => uint256) public balances;
uint256 public totalDeposits;
event Deposited(address indexed user, uint256 amount, uint256 scaledAmount);
event Withdrawn(address indexed user, uint256 amount, uint256 scaledAmount);
error InsufficientBalance();
error InvalidAmount();
function deposit(uint256 amount, uint256 interestRate) public payable nonReentrant {
if (amount == 0 || msg.value != amount) revert InvalidAmount();
// 计算利息(示例:固定利率)
uint256 scaledAmount = (amount * interestRate) / DECIMAL_PRECISION;
balances[msg.sender] += scaledAmount;
totalDeposits += scaledAmount; // 自动检查溢出
emit Deposited(msg.sender, amount, scaledAmount);
}
function withdraw(uint256 amount) public nonReentrant {
if (amount > balances[msg.sender]) revert InsufficientBalance();
balances[msg.sender] -= amount;
totalDeposits -= amount; // 自动检查下溢
payable(msg.sender).transfer(amount);
emit Withdrawn(msg.sender, amount, amount);
}
}
说明:
DECIMAL_PRECISION
处理小数运算。ReentrancyGuard
防止重入攻击。测试用例:
const { expect } = require("chai");
describe("SafeDeposit", function () {
let SafeDeposit, contract, owner, user;
beforeEach(async function () {
SafeDeposit = await ethers.getContractFactory("SafeDeposit");
[owner, user] = await ethers.getSigners();
contract = await SafeDeposit.deploy();
await contract.deployed();
});
it("should deposit with interest", async function () {
const amount = ethers.parseEther("1");
const interestRate = ethers.parseUnits("1.1", 18); // 1.1x
await expect(contract.connect(user).deposit(amount, interestRate, { value: amount }))
.to.emit(contract, "Deposited")
.withArgs(user.address, amount, ethers.parseUnits("1.1", 18));
expect(await contract.balances(user.address)).to.equal(ethers.parseUnits("1.1", 18));
expect(await contract.totalDeposits()).to.equal(ethers.parseUnits("1.1", 18));
});
it("should revert on overflow", async function () {
await contract.connect(user).deposit(ethers.parseEther("1"), ethers.parseUnits("1", 18), { value: ethers.parseEther("1") });
await expect(contract.connect(user).deposit(ethers.MaxUint256, ethers.parseUnits("2", 18), { value: ethers.MaxUint256 }))
.to.be.revertedWithPanic("0x11");
});
it("should withdraw correctly", async function () {
const amount = ethers.parseEther("1");
await contract.connect(user).deposit(amount, ethers.parseUnits("1", 18), { value: amount });
await expect(contract.connect(user).withdraw(amount))
.to.emit(contract, "Withdrawn")
.withArgs(user.address, amount, amount);
expect(await contract.balances(user.address)).to.equal(0);
});
it("should revert on insufficient balance", async function () {
await expect(contract.connect(user).withdraw(ethers.parseEther("1")))
.to.be.revertedWithCustomError(contract, "InsufficientBalance");
});
});
运行测试:
npx hardhat test
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!