This kind of code:
require(_amountToRaise > 0, "Amount to raise smaller than 0");
Produces revert with bytes equivalent to calldata of the following method:
function Error(string memory reason) external;
ABI-encoded calldata structure is the following:
4 bytes selector
32 bytes offset (value 0x20)
32 bytes string length
N bytes string, optionally padded to 32 bytes
So correct assembly generating this calldata (reason up to 32 bytes):
let ptr := mload(0x40) // Get free memory pointer
mstore(ptr, 0x08c379a000000000000000000000000000000000000000000000000000000000000000) // Selector for method Error(string)
mstore(add(ptr, 0x04), 0x20) // String offset
mstore(add(ptr, 0x24), 30) // Revert reason length
mstore(add(ptr, 0x44), "Amount to raise smaller than 0")
revert(ptr, 0x64) // Revert data length is 4 bytes for selector and 3 slots of 0x20 bytes
For the case of custom error types:
contract Foo {
error MyError();
error MyErrorWithValue(uint256);
error MyErrorWithTwoValues(uint256, uint256);
function f() external {
revert MyError();
}
function g() external {
revert MyErrorWithValue(555);
}
function h() external {
revert MyErrorWithTwoValues(555, 666);
}
}
Assembly equivalent:
function f() external {
// revert MyError();
bytes4 errorSelector = this.MyError.selector;
assembly {
mstore(0, errorSelector)
revert(0x1c, 0x04)
}
}
function f() external {
// revert MyErrorWithValue(555);
bytes4 errorSelector = this.MyErrorWithValue.selector;
assembly {
mstore(0, errorSelector)
mstore(4, 555)
revert(0x1c, 0x24)
}
}
function h() external {
// revert MyErrorWithValues(555, 666);
bytes4 errorSelector = this.MyErrorWithTwoValues.selector;
assembly {
let ptr := mload(0x40)
mstore(ptr, errorSelector)
mstore(add(ptr, 0x04), 555)
mstore(add(ptr, 0x24), 666)
revert(add(ptr, 0x1c), 0x44)
}
}
In the first two methods revert data fits in scratch space (first 0x40 bytes), but third method doesn't fit and should use free memory pointer (stored in memory at 0x40). As some may think due to revert
operation they could use beginning of the memory for storing more than 64 bytes - Solidity documentation states that this can't be considered as memory-safe assembly if it's important in your case.
Using assembly with custom errors could produce issues with compiler generated ABI if this happens in libraries
. Compiler 0.8.17 could not include custom error into ABI due non-detection of actual usage this error type. You can track this issue here: https://github.com/ethereum/solidity/issues/13149