输入验证是智能合约安全的第一道防线。由于区块链上的所有交易都是公开的,攻击者可以发送任意数据来测试合约的边界。不当的输入验证可能导致各种安全问题,从资金损失到合约完全失控。
2017 年 7 月,Parity 多签钱包漏洞
// 简化的漏洞代码
function initWallet(address[] _owners, uint _required) public {
// ❌ 没有检查是否已经初始化!
owners = _owners;
required = _required;
}
问题:
initWallet结果:价值 3000 万美元的 ETH 被盗。
// ❌ 危险:相信用户输入
function withdraw(uint amount) public {
payable(msg.sender).transfer(amount); // 没有任何检查!
}
// ✅ 安全:验证输入
function withdraw(uint amount) public {
require(amount > 0, "Amount must be positive");
require(amount <= balances[msg.sender], "Insufficient balance");
require(amount <= address(this).balance, "Insufficient contract balance");
balances[msg.sender] -= amount;
payable(msg.sender).transfer(amount);
}
// ❌ 黑名单:容易遗漏
function setRole(uint roleId) public {
require(roleId != 0, "Cannot set admin role"); // 容易被绕过
roles[msg.sender] = roleId;
}
// ✅ 白名单:明确允许的值
function setRole(uint roleId) public {
require(
roleId == ROLE_USER || roleId == ROLE_MODERATOR,
"Invalid role"
);
roles[msg.sender] = roleId;
}
// ✅ 在函数开始就验证所有输入
function transfer(address to, uint amount) public {
// 所有验证放在最前面
require(to != address(0), "Invalid recipient");
require(amount > 0, "Amount must be positive");
require(balances[msg.sender] >= amount, "Insufficient balance");
// 执行业务逻辑
balances[msg.sender] -= amount;
balances[to] += amount;
}
contract AddressValidation {
// ✅ 检查零地址
function setRecipient(address _recipient) public {
require(_recipient != address(0), "Invalid address");
recipient = _recipient;
}
// ✅ 检查是否是合约地址
function onlyEOA(address addr) public view {
require(addr.code.length == 0, "Must be EOA");
}
// ✅ 检查是否是合约地址
function onlyContract(address addr) public view {
require(addr.code.length > 0, "Must be contract");
}
// ✅ 检查地址列表
function setAddresses(address[] memory addrs) public {
require(addrs.length > 0, "Empty array");
require(addrs.length <= 100, "Too many addresses");
for (uint i = 0; i < addrs.length; i++) {
require(addrs[i] != address(0), "Invalid address");
// 检查重复
for (uint j = i + 1; j < addrs.length; j++) {
require(addrs[i] != addrs[j], "Duplicate address");
}
}
}
}
contract NumericValidation {
uint public constant MIN_AMOUNT = 1000;
uint public constant MAX_AMOUNT = 1000000;
// ✅ 范围检查
function deposit(uint amount) public payable {
require(amount >= MIN_AMOUNT, "Amount too small");
require(amount <= MAX_AMOUNT, "Amount too large");
require(msg.value == amount, "Value mismatch");
balances[msg.sender] += amount;
}
// ✅ 百分比验证
function setFee(uint feePercent) public {
require(feePercent <= 100, "Fee cannot exceed 100%");
fee = feePercent;
}
// ✅ 除数检查
function divide(uint a, uint b) public pure returns (uint) {
require(b != 0, "Division by zero");
return a / b;
}
// ✅ 溢出前检查
function multiply(uint a, uint b) public pure returns (uint) {
if (a == 0) return 0;
uint c = a * b;
require(c / a == b, "Multiplication overflow");
return c;
}
}
contract StringValidation {
// ✅ 字符串长度检查
function setName(string memory name) public {
bytes memory nameBytes = bytes(name);
require(nameBytes.length > 0, "Name cannot be empty");
require(nameBytes.length <= 32, "Name too long");
userName[msg.sender] = name;
}
// ✅ 字节数组长度检查
function setData(bytes memory data) public {
require(data.length > 0, "Data cannot be empty");
require(data.length <= 1024, "Data too large");
userData[msg.sender] = data;
}
// ✅ 检查特殊字符(gas 消耗较高,谨慎使用)
function isAlphanumeric(string memory str) public pure returns (bool) {
bytes memory b = bytes(str);
for (uint i = 0; i < b.length; i++) {
bytes1 char = b[i];
if (!(
(char >= 0x30 && char <= 0x39) || // 0-9
(char >= 0x41 && char <= 0x5A) || // A-Z
(char >= 0x61 && char <= 0x7A) // a-z
)) {
return false;
}
}
return true;
}
}
contract ArrayValidation {
uint public constant MAX_ARRAY_LENGTH = 100;
// ✅ 数组长度限制
function processBatch(uint[] memory values) public {
require(values.length > 0, "Empty array");
require(values.length <= MAX_ARRAY_LENGTH, "Array too large");
for (uint i = 0; i < values.length; i++) {
require(values[i] > 0, "Invalid value");
// 处理数据
}
}
// ✅ 检查数组元素唯一性
function checkUnique(address[] memory addrs) public pure returns (bool) {
for (uint i = 0; i < addrs.length; i++) {
for (uint j = i + 1; j < addrs.length; j++) {
if (addrs[i] == addrs[j]) {
return false;
}
}
}
return true;
}
// ✅ 双数组长度匹配
function batchTransfer(
address[] memory recipients,
uint[] memory amounts
) public {
require(recipients.length == amounts.length, "Length mismatch");
require(recipients.length > 0, "Empty arrays");
require(recipients.length <= MAX_ARRAY_LENGTH, "Too many transfers");
for (uint i = 0; i < recipients.length; i++) {
require(recipients[i] != address(0), "Invalid recipient");
require(amounts[i] > 0, "Invalid amount");
// 执行转账
}
}
}
contract StateValidation {
enum Status { Pending, Active, Paused, Completed }
Status public status;
// ✅ 状态转换验证
function activate() public {
require(status == Status.Pending, "Can only activate from pending");
status = Status.Active;
}
// ✅ 多状态检查
function pause() public {
require(
status == Status.Pending || status == Status.Active,
"Invalid state for pause"
);
status = Status.Paused;
}
// ✅ 枚举范围检查(如果从外部输入)
function setStatus(uint _status) public {
require(_status <= uint(Status.Completed), "Invalid status");
status = Status(_status);
}
}
contract TimeValidation {
uint public startTime;
uint public endTime;
// ✅ 时间范围验证
function setTimeRange(uint _start, uint _end) public {
require(_start > block.timestamp, "Start time must be in future");
require(_end > _start, "End must be after start");
require(_end - _start >= 1 days, "Duration too short");
require(_end - _start <= 365 days, "Duration too long");
startTime = _start;
endTime = _end;
}
// ✅ 检查是否在有效期内
function isActive() public view returns (bool) {
return block.timestamp >= startTime && block.timestamp <= endTime;
}
modifier onlyDuringPeriod() {
require(isActive(), "Not in active period");
_;
}
// ✅ 区块号验证
function setDeadlineBlock(uint blockNumber) public {
require(blockNumber > block.number, "Block must be in future");
require(blockNumber <= block.number + 1000000, "Too far in future");
deadlineBlock = blockNumber;
}
}
contract ValidationModifiers {
address public owner;
mapping(address => uint) public balances;
modifier validAddress(address addr) {
require(addr != address(0), "Invalid address");
_;
}
modifier validAmount(uint amount) {
require(amount > 0, "Amount must be positive");
require(amount <= 1000 ether, "Amount too large");
_;
}
modifier sufficientBalance(uint amount) {
require(balances[msg.sender] >= amount, "Insufficient balance");
_;
}
// 使用多个修饰器
function transfer(address to, uint amount)
public
validAddress(to)
validAmount(amount)
sufficientBalance(amount)
{
balances[msg.sender] -= amount;
balances[to] += amount;
}
}
library InputValidator {
// 地址验证
function validateAddress(address addr) internal pure {
require(addr != address(0), "Invalid address");
}
// 范围验证
function validateRange(uint value, uint min, uint max) internal pure {
require(value >= min && value <= max, "Value out of range");
}
// 数组长度验证
function validateArrayLength(uint length, uint max) internal pure {
require(length > 0, "Empty array");
require(length <= max, "Array too large");
}
// 百分比验证
function validatePercentage(uint percent) internal pure {
require(percent <= 100, "Invalid percentage");
}
}
contract UsingValidator {
using InputValidator for *;
function setFee(uint feePercent) public {
feePercent.validatePercentage();
fee = feePercent;
}
function setRecipient(address recipient) public {
recipient.validateAddress();
defaultRecipient = recipient;
}
}
// ❌ 危险:使用 tx.origin
function withdraw() public {
require(tx.origin == owner, "Not owner"); // 可被钓鱼攻击
// ...
}
// ✅ 安全:使用 msg.sender
function withdraw() public {
require(msg.sender == owner, "Not owner");
// ...
}
// ❌ 危险:不检查 transfer 返回值
function unsafeTransfer(address token, address to, uint amount) public {
IERC20(token).transfer(to, amount); // 可能失败但不revert
}
// ✅ 安全:检查返回值
function safeTransfer(address token, address to, uint amount) public {
bool success = IERC20(token).transfer(to, amount);
require(success, "Transfer failed");
}
// ✅ 更好:使用 SafeERC20
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
function bestTransfer(address token, address to, uint amount) public {
SafeERC20.safeTransfer(IERC20(token), to, amount);
}
// ❌ 危险:假设调用成功
function dangerousCall(address target, bytes memory data) public {
target.call(data); // 忽略返回值
}
// ✅ 安全:检查调用结果
function safeCall(address target, bytes memory data) public {
(bool success, bytes memory returnData) = target.call(data);
require(success, "Call failed");
}
// ❌ 危险:精度损失
function calculateFee(uint amount) public pure returns (uint) {
return amount / 100 * 3; // 先除后乘,损失精度
}
// ✅ 安全:先乘后除
function calculateFee(uint amount) public pure returns (uint) {
return amount * 3 / 100; // 先乘后除,保留精度
}
核心原则: 永远不信任输入、系统化验证、详尽的测试边界。 记住:输入验证是防御的重要防线。花时间做好输入验证,能避免大部分的安全问题。