Token-0x

First Flight #54
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: low
Likelihood: high

The `ERC20Internals::_transfer` function is not gas-efficient which is against the purpose of this token.

Author Revealed upon completion

Root + Impact

Description

  • The main purpose of creating this token is to be more gas-efficient that its counterpart from Openzeppelin. For the most part, it is as gas-efficient as possible. But in the case of the _transfer function, it can save more gas by making some simple adjustments.

  • To be more precise, the section fetching the recipient's balance should be done after the sender's balance is checked for availability of funds.

  • If they do not have enough funds to send, then it reverts without ever fetching the recipient's balance (which is irrelevant now).

  • In addition, there are cases where we know the result of a calculation at the time of writing the code. For example, mstore(add(0x00, 4), from) can be simplified to mstore(0x04, from) which saves a few more gas.

function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
if iszero(from) {
mstore(0x00, shl(224, 0x96c6fd1e))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
if iszero(to) {
mstore(0x00, shl(224, 0xec442f05))
mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let baseSlot := _balances.slot
mstore(ptr, from)
mstore(add(ptr, 0x20), baseSlot)
let fromSlot := keccak256(ptr, 0x40)
let fromAmount := sload(fromSlot)
mstore(ptr, to)
@> mstore(add(ptr, 0x20), baseSlot)
@> let toSlot := keccak256(ptr, 0x40)
@> let toAmount := sload(toSlot)
@> if lt(fromAmount, value) {
mstore(0x00, shl(224, 0xe450d38c))
mstore(add(0x00, 4), from)
mstore(add(0x00, 0x24), fromAmount)
mstore(add(0x00, 0x44), value)
revert(0x00, 0x64)
}
sstore(fromSlot, sub(fromAmount, value))
sstore(toSlot, add(toAmount, value))
success := 1
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
}
}

Risk

Likelihood: Low

  • It can happen when the sender's balance is not enough to proceed with the transaction.

  • It does not occur when the sender has enough funds and the transaction goes through.

Impact:

  • It can save upto 2,190 gas which is about 3.31% improvement comparing with the original version of this function (of course when it reverts with the IERC20Errors::ERC20InsufficientBalance custom error).


Proof of Concept

To see it in action, you need to run the test test_transferRevert2() function, then apply the changes and run it again. Next, compare their used gas amounts.

Original:

Improved:

// Run the tests with and without the changes and compare the results

Recommended Mitigation

Please make the following changes to improve the gas usage of the function by 3.31%.

function _transfer(address from, address to, uint256 value) internal returns (bool success) {
assembly ("memory-safe") {
if iszero(from) {
mstore(0x00, shl(224, 0x96c6fd1e))
// The following line has no effect because the `from` is 0 and by shifting left in the previous command we have created 28 bits of 0 in the right part of the memory slot. By removing it we can save 7 gas.
- mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
if iszero(to) {
mstore(0x00, shl(224, 0xec442f05))
// The following line has no effect because the `to` is 0 and by shifting left in the previous command we have created 28 bits of 0 in the right part of the memory slot. By removing it we can save 7 gas.
- mstore(add(0x00, 4), 0x00)
revert(0x00, 0x24)
}
let ptr := mload(0x40)
let baseSlot := _balances.slot
mstore(ptr, from)
mstore(add(ptr, 0x20), baseSlot)
let fromSlot := keccak256(ptr, 0x40)
let fromAmount := sload(fromSlot)
+ if lt(fromAmount, value) {
+ mstore(0x00, shl(224, 0xe450d38c))
+ mstore(0x04, from)
+ mstore(0x24, fromAmount)
+ mstore(0x44, value)
+ revert(0x00, 0x64)
+ }
mstore(ptr, to)
mstore(add(ptr, 0x20), baseSlot)
let toSlot := keccak256(ptr, 0x40)
let toAmount := sload(toSlot)
- if lt(fromAmount, value) {
- mstore(0x00, shl(224, 0xe450d38c))
- mstore(add(0x00, 4), from)
- mstore(add(0x00, 0x24), fromAmount)
- mstore(add(0x00, 0x44), value)
- revert(0x00, 0x64)
- }
sstore(fromSlot, sub(fromAmount, value))
sstore(toSlot, add(toAmount, value))
success := 1
mstore(ptr, value)
log3(ptr, 0x20, 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, from, to)
}
}

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!