Contract Audit
Contract Audit
合约审计定位于审计步骤,审计工具和审计中需要注意的地方.
审计步骤: 运行slither工具, 执行测试文件, 人工审计
Slither
Detector
inheritance-graph
call-graph
Reentrancy
A reentrancy attack is a type of vulnerability in smart contracts that allows attackers to execute a function multiple times before the previous call completes. This can lead to unexpected and harmful behavior, such as the theft of funds or unauthorized access to data.
use check-effect-interaction pattern
use mutex
same method for the same contract
pragma solidity 0.8.9; contract VulnerableContract { mapping (address => uint256) public balances; function withdraw(uint256 amount) public { require(balances[msg.sender] >= amount, "Insufficient balance"); (bool success, ) = msg.sender.call{value: amount}(""); if (success) { balances[msg.sender] -= amount; } } function deposit() public payable { balances[msg.sender] += msg.value; } }
different methods for the same contract
different methods for different contracts
read-only reentrancy
Access Control
Arithmetic issue
unchecked, 溢出
Bad randomness
Timestamp dependence
External data source dependence
Chainlink
Transaction order risk
ENS front attack
Dos Attack
- Dos with (unexpected) revert
- Dos with block gas limit
forcibly send ether to contract
Deprecated attack
call depth attack
// SPDX-License-Identifier: MIT pragma solidity ^0.8.5; // DO NOT USE!!! contract Auction { address highestBidder; uint256 highestBid; function bid() external payable { if (msg.value < highestBid) revert(); if (highestBidder != address(0)) { payable(highestBidder).send(highestBid); // refund previous bidder } highestBidder = msg.sender; highestBid = msg.value; } }
Prior to the implementation of EIP-150, the contract above could be susceptible to “Call Depth Attack.” This is because a malevolent bidder could initiate a recursive call to itself, causing the stack depth to increase to 1023 before calling the bid() function. As a result, the send(highestBid) call would fail silently, meaning that the previous bidder would not receive a refund, while the new bidder would still become the highest bidder.
EIP150: all gas would be consumed well before reaching the 1024 call depth limit
闪电贷
去中心化借贷平台攻击事件bZx
- 黑客通过闪电贷从去中心化数字资产衍生交易平台dYdX借出10000枚ETH;
- 使用其中的5000枚ETH抵押在去中心化借贷平台Compound借出112枚wBTC;
- 剩下的5000枚ETH到去中心化借贷平台bZx开空单;
- 用借出的112枚wBTC到去中心化交易所Uniswap砸盘,让wBTC价格下跌;
常见的攻击原理
AbiReencodingHeadOverflowWithStaticArrayCleanup: https://etherscan.io/solcbuginfo?a=AbiReencodingHeadOverflowWithStaticArrayCleanup
tx.origin 漏洞攻击
避免使用extcodesize用于检查外部拥有的账户
Testing And Verification
Gas Optimization
Packing variable into a single slot
Use Merkle tree proof
stateless contracts
calldata, memory, storage之间的复制
Stop using bools for True/False
EVM将bool存储为uint8类型,占用两个字节;因为EVM字长32字节,需要额外的逻辑来解析此值.
将false存储为uint256(0),true -> uint256(1), 这也会产生大量的gas, 因为将零值flip转为其它值需要支付固定费用.
最有效的方法是1表示false, 2表示true.(非零值会使合约部署成本高一些)