Skip to content

Griefing Attacks

Griefing attacks represent an exploit aimed at vulnerabilities in smart contracts, usually related to business logic. These attacks cause a negative impact on the operation of the smart contract system, despite providing no direct profit for the attackers. In some cases, griefing attacks may be deployed to disrupt overall system operations or to create disruption during specific critical moments.

Consider the following Solidity code, which illustrates a typical scenario for a griefing attack:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
pragma solidity ^0.8.17;

contract DelayedWithdrawal {
    address beneficiary;
    uint256 delay;
    uint256 lastDeposit;

    constructor(uint256 _delay) {
        beneficiary = msg.sender;
        lastDeposit = block.timestamp;
        delay = _delay;
    }

    modifier checkDelay() {
        require(block.timestamp >= lastDeposit + delay, "Keep waiting");
        _;
    }

    function deposit() public payable {
        require(msg.value != 0);
        lastDeposit = block.timestamp;
    }

    function withdraw() public checkDelay {
        (bool success, ) = beneficiary.call{value: address(this).balance}("");
        require(success, "Transfer failed");
    }
}

This example demonstrates a DelayedWithdrawal contract. In the constructor, the beneficiary is set to the deployer's address along with a custom delay, for instance, 24 hours. Anyone can deposit funds, which become available for withdrawal and transfer to the beneficiary after the configured delay. However, each deposit must transfer a non-zero amount of ETH to the smart contract.

The potential for a griefing attack arises because anyone can call the deposit function and reset the lastDeposit timestamp. This allows a griefing attacker to transfer a minimal amount (e.g., 1 wei) to the smart contract, triggering a reset of the lastDeposit timestamp. Consequently, this new lastDeposit timestamp prevents the beneficiary from withdrawing their funds.

An attacker could deploy this strategy by submitting a transaction just before the end of the delay period to reset the timestamp. Alternatively, the attacker could engage in frontrunning, anticipating, and pre-empting the beneficiary's calls to the withdraw function, thereby creating a more cost-effective denial-of-service attack.

Gas Griefing Attacks

Insufficient gas griefing attacks represent a subset of griefing attacks that primarily affect smart contracts performing external calls without checking the success return value. In such an attack, an adversary may supply just enough gas to ensure the top-level function's success while ensuring the external call's failure due to gas exhaustion. Owing to the 63/64 rule, the top-level contract can complete its function call, resulting in an incomplete state change. Such issues are particularly prevalent in smart contracts executing generic calls, including relayers and multisig wallets.

The following example, featuring a simplified relayer contract, illustrates the potential for an insufficient gas griefing attack:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
pragma solidity ^0.8.17;

contract Relayer {
    mapping (bytes => bool) executed;
    address target;

    function forward(bytes memory _data) public {
        require(!executed[_data], "Replay protection");
        // more code for signature validation in between
        executed[_data] = true;
        target.call(abi.encodeWithSignature("execute(bytes)", _data));
    }
}

Note

Relayers are much more complex smart contract systems. The section on signature-related attacks contains more inspiration on how to break them!

Upon an external call failure within the forward function, the contract has the choice of either reverting the entire transaction or continuing execution. The example contract provided does not check the success of the external call and simply continues execution. As a result, once the execution of the forward function is complete, the submitted data is marked as executed in the executed mapping, thereby preventing anyone from submitting the same data again.

Any third-party forwarder can invoke the forward function to execute a user's transaction on their behalf. Suppose a forwarder calls forward with minimal gas, sufficient only to allow the Relayer contract to succeed but causing the external call to revert due to an out-of-gas error. In that case, the user's transaction is not executed, and their signature is invalidated. Consequently, the anticipated state change on the target contract will not occur.

Malicious forwarders can exploit this technique to permanently censor user transactions within the Relayer contract. Even though the adversary does not profit directly from this griefing attack, they can still disrupt the operations of the smart contract and inconvenience the victim.