Reverting Transactions: Learning to use require

Revert

  • We talk to a contract with message calls
  • A contract can REVERT a call, negating all state changes
  • Each calling contract can choose to handle that success, or REVERT as well

Message Call Revert

  • 🙅‍♀️ No state changes occur
  • 🙅‍♀️ No value is transfered
  • 🙅‍♀️ No logs are emitted
  • ⛽️ Gas is still spent

Require

Often you’ll see require used like this:

contract X {
  // shorthand!
  address owner = msg.sender;

  function ownerOnly() external {
    // REVERT if not the owner
    require(msg.sender == owner, "only owner!");
    // do something owner-y
  }
}

Revert

Revert can be used with a string revert("Unauthorized") or, better yet:

contract X {
  // @notice a non-privileged user attempted to access an admin-only method
  error Unauthorized();

  function adminOnly() external {
    if (!isAdmin(msg.sender)) {
      revert Unauthorized();
    }
  }
}

👆⛽️ Gas Efficient!

Assert

Use assert with things that should not happen:

contract X {
  function withdraw() external {
    uint balance = getBalance(msg.sender);
    sendBalance(msg.sender);

    // they should not still have a balance!
    assert(getBalance(msg.sender) == 0);
  }
}

Hands-on

learn-solidity-presentations\3-reverting-transactions\examples\0-revert-require\src\Example.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "forge-std/console.sol";

// REVERT
// no state changes should have occured
// no value should have been transferred
// gas will still be paid for
contract A {
    address b;
    uint256 public errorsCount = 0;

    constructor(address _b) {
        b = _b;
    }

    function callB() external payable {
        (bool success, bytes memory returnData) = b.call{value: 1 ether}("");
        if (!success) {
            console.logBytes(returnData);
            console.logBytes32(keccak256("DoNotPayMe(uint256)"));
            errorsCount++;
        }
    }
}

contract B {
    uint256 public x = 0;
    // @notice nobody should ever pay this contract

    // 4 byte
    error DoNotPayMe(uint256);

    receive() external payable {
        x = 15;
        revert DoNotPayMe(msg.value);
    }
}

learn-solidity-presentations\3-reverting-transactions\examples\0-revert-require\test\Example.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "../src/Example.sol";

contract ExampleTest is Test {
    A public a;
    B public b;

    function setUp() public {
        b = new B();
        a = new A(address(b));
    }

    function testExample() public {
        a.callB{value: 1.5 ether}();
        assertEq(address(a).balance, 1.5 ether);
        assertEq(a.errorsCount(), 1);
        assertEq(b.x(), 0);
    }
}

edo@DESKTOP-4POSOVK:/mnt/e/Programming/Solidity/learn-solidity-presentations/3-reverting-transactions/examples/0-revert-require/src$ forge test -vv
[⠑] Compiling...
[⠔] Compiling 20 files with 0.8.25
[⠃] Solc 0.8.25 finished in 1.73s
Compiler run successful!

Ran 1 test for test/Example.t.sol:ExampleTest
[PASS] testExample() (gas: 77397)
Logs:
  0x568804630000000000000000000000000000000000000000000000000de0b6b3a7640000
  0x568804639171345705b794b2fd1a938758dc6b678c203b630b224e0da48a84ad

Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.10ms (3.07ms CPU time)

Ran 1 test suite in 55.17ms (14.10ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Learn Solidity. Alchemy University.
Accessed January 1, 2025
https://university.alchemy.com/overview/solidity