Solady’s SafeTransferLib Part 1 — Safe ETH transfers
its a work of art they say! looking at solady’s gas optimized solidity snippets
Let’s dive deep into Solady’s SafeTransferLib
which is modified from Solmate’s SafeTransferLib
to handle missing return values, Gracefully!
In this part, we will only look into ETH transfers.
Starting with custom errors
Customer errors are much cheaper than a string description because you can use the name of the error to describe it, which is encoded in only four bytes.
👍 for optimizing Solmate’s error handling
The comment above each is in NatSpec Format, check here to read more about it
safeTransferETH()
As per the function's name, it is used to transfer ETH from a contract to another account.
- At line number 7
ifcall(gas(), to, amount, 0, 0, 0, 0)
is a success then
returns0
else returns1
We are here to handle the missing return values, remember?
In this case, we will return an error with revert
opcode and revert the transaction to save gas.
Hence if call(gas(), to, amount, 0, 0, 0, 0)
== 0
theniszero(0)
is true
and the if
condition satisfies so we enter into the if loop.
- At line number 9,
ETHtransferFailed()
function selector0xb12d13eb
is stored at position 0 in the memory as 32 bytes (right padded with zeroes) - At line number 11
revert
opcode takes 2 args
— offset: memory slot position, position 28 in this case
— size: byte size to return, 4 bytes in this case
As shown in the image above, the function selector is 4 bytes and bytes<M>
is left-aligned (while unit256
is right-aligned) we can get the function signature by returning 4 bytes from position 0x1c (28) to 0x1f (31), as the first memory slot stores 32 bytes of function signature (remaining 28 bytes padded with zeroes) that is why we revert(0x1c, 0x04)
For the sake of comparison, I have added a sample visual for uint<M>
right-aligned
forceSafeTransferETH() with the three-argument version
let’s first see what Solady has to say about this function
Force sends
amount
(in wei) ETH toto
, with agasStipend
. ThegasStipend
can be set to a low enough value to prevent storage writes or gas griefing.
If sending via the normal procedure fails, force sends the ETH by creating a temporary contract which usesSELFDESTRUCT
to force send the ETH. Reverts if the current contract has insufficient balance.
- From line number 5 to 10, it is checking the balance of the caller account using
lt
opcode, if it's less than the amount sending then it will revert withETHTransferFailed()
. - From line number 12 to 20, if the
call()
fails and returns0
, it will create a temporary contract account withSELFDESTRUCT
to remove the contract bytecode from EVM. AsSELFDESTRUCT
opcode takes an address as a parameter to send the current (contract) balance to the given
address. mstore(0x00, to)
stores the 20 bytesto
address at position 0, since bytes are left-aligned remaining 12 bytes are padded with 0s and the address is at position/offset 12 to 32.mstore8(0x0b, 0x73)
at position 11 of the above 32 byte memory slot, we storePUSH20
opcode to push 20 bytesto
address to the stack.mstore8(0x20, 0xff)
storesSELFDESTRUCT
opcode at position 33 (next starting byte)
Hence from position/offset 11 to 33, is our contract initialization code making the size of the code 22 bytes 0x16
create(amount, 0x0b, 0x16)
In the end, we use pop
the opcode to remove the contract address from the EVM stack. pop(create(amount, 0x0b, 0x16))
forceSafeTransferETH()
The only difference with the above forceSafeTransferETH()
is that we are passing uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000
as gasStipend
.
trySafeTransferETH()
The above function does not revert upon failure, returns whether the transfer of ETH is successful instead.
In the next part, we will look into ERC20 transfers.