Backd Tokenomics contest - 0x1f8b's results

Maximize the power of your assets and start earning yield

General Information

Platform: Code4rena

Start Date: 27/05/2022

Pot Size: $75,000 USDC

Total HM: 20

Participants: 58

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 15

Id: 131

League: ETH

Backd

Findings Distribution

Researcher Performance

Rank: 13/58

Findings: 4

Award: $2,143.98

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

113.8755 USDC - $113.88

Labels

bug
QA (Quality Assurance)
resolved
sponsor confirmed

External Links

Approve not compatible with Tether (USDT) implementation

Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT or CVX)'s approve() function will revert if the current approval is not zero, to protect against front-running changes of approvals.

function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {

     // To change the approve amount you first have to reduce the addresses`
     //  allowance to zero by calling `approve(_spender, 0)` if it is not
     //  already 0 to mitigate the race condition described here:
     //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));

     allowed[msg.sender][_spender] = _value;
     Approval(msg.sender, _spender, _value);
 }

The code as currently implemented does not handle these sorts of tokens properly, which would prevent USDT or CVX, from being used by this project.

If the method allPools return pools with duplicate underlyings, it will fault in the case of USDT or CVX, for example.

Affected source code:

Lack of checks

The following methods have a lack checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0).

Affected source code:

If you set any wrong address, without being a contract, an EOA for example, it cannot be changed again:

Lack of int range checks:

Not max defined:

Lack of ACK during owner change

It's possible to lose the ownership under specific circumstances.

Because an human error it's possible to set a new invalid owner. When you want to change the owner's address it's better to propose a new owner, and then accept this ownership with the new wallet.

Affected source code:

Avoid errors with transferFrom

The following methods take all the user's balance and send it through transferFrom, this call may fail, since transferFrom extracts the balance from the previously approved allowance, it is better to use the user's allowance in order to avoid the unnecessary failure when both amounts are not the same. It's better to use allowance instead of balanceOf.

Affected source code:

Wrong logic around grantRole and revokeRole.

The RoleManager contract contains a few special roles (Roles.ADDRESS_PROVIDER, Roles.POOL_FACTORY, Roles.CONTROLLER, Roles.POOL and Roles.VAULT) that are not controlled in the grantRole and revokeRole methods.

Affected source code:

Centralization risks

Multiple initialization

The initialize method of the BkdLocker contract allows it to be started multiple times as long as the value startBoost=0 is set. Abuse these settings to his advantage.

Affected source code:

Centralized minting

The minter address can mint arbitrary amount of tokens. If the private key of the owner or minter address is compromised, the attacker will be able to mint an unlimited amount of tokens, or burn from arbitrary addresses.

Affected source code:

Controlled swapRouter

The FeeBurner contract sets the swapperRouter in the _addressProvider, so the owner can set any type of swapper, paths or pools, even malicious ones. Since there is no slippage defined in the FeeBurner contract itself, it could be that a swapperRouter returns 0 WETH, and keeps the sent tokens.

Affected source code:

#0 - GalloDaSballo

2022-06-19T22:47:03Z

Approve not compatible with Tether (USDT) implementation

Invalid as the approval happen once, from zero, to the max

Lack of checks

Valid per industry standard

## Lack of ACK during owner change Personally disagree, but completely acceptable suggestion

Avoid errors with transferFrom

I believe this finding to be invalid in lack of acknowledging the broader system

Wrong logic around grantRole and revokeRole.

Idem

Multiple initialization

Dup of #136 Which I've yet to judge (may raise severity of this one)

Centralized minting

Observation is valid, but lacks nuance as the minter is a smart contract under scope

Controlled swapRouter

Agree with this finding and may raise severity as well as Duplicate of #113

#1 - GalloDaSballo

2022-06-19T22:47:39Z

Overall most findings are pretty common from CodeArena, however the format is short and to the point, which I appreciate especially for the more basic findings

#2 - GalloDaSballo

2022-06-22T14:45:38Z

Will raise Multiple initialization and Controlled swapRouter

To 2 separate medium findings

Awards

62.6774 USDC - $62.68

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

External Links

Outdated compiler

The pragma version used is pragma solidity 0.8.10; but recently solidity released a new version with important Bugfixes:

  • The first one is related to ABI-encoding nested arrays directly from calldata. You can find more information here.

  • The second bug is triggered in certain inheritance structures and can cause a memory pointer to be interpreted as a calldata pointer or vice-versa. We also have a dedicated blog post about this bug.

Apart from these, there are several minor bug fixes and improvements.

The minimum required version should be 0.8.14

Reduce the size of error messages (Long revert Strings)

Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next (require pragma upgrade).

Use Custom Errors instead of Revert Strings to save Gas

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source Custom Errors in Solidity:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

Affected source code:

Delete optimization

Use delete instead of set to default value (false or 0)

Affected source code:

unckecked keyword

It's possible to save gas using the unckecked keyword around the i variable. This will avoid the required checks to ensure that the variable won't overflow, and in the case of this for loop, is almost impossible:

Reference:

Affected source code:

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;
i++; // == 1 but i == 2

But ++i returns the actual incremented value:

uint i = 1;
++i; // == 2 and i == 2 too, so no need for a temporary variable

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2. I suggest using ++i instead of i++ to increment the value of an uint variable. Same thing for --i and i--. Also, in some of the points mentioned below, it would be necessary to add unckecked since said variable is impossible to overflow.

Affected source code:

Avoid unused returns

Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert in case of failure, it is not necessary to return a true, since it will never be false. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.

Affected source code:

Improve logic

It's possible to optimize the method getRoleMember of the contract RoleManager checking first if the index is 0.

Before:

if (role == Roles.ADDRESS_PROVIDER && index == 0) {
            return address(addressProvider);
        } else if (role == Roles.POOL_FACTORY && index == 0) {
            return addressProvider.getAddress(AddressProviderKeys._POOL_FACTORY_KEY);
        } else if (role == Roles.CONTROLLER && index == 0) {
            return addressProvider.getAddress(AddressProviderKeys._CONTROLLER_KEY);
        } else if (role == Roles.POOL) {
            return addressProvider.getPoolAtIndex(index);
        } else if (role == Roles.VAULT) {
            return addressProvider.getVaultAtIndex(index);
        }

After:

if (index == 0)
{
    if (role == Roles.ADDRESS_PROVIDER) return address(addressProvider);
    if (role == Roles.POOL_FACTORY) return addressProvider.getAddress(AddressProviderKeys._POOL_FACTORY_KEY);
    if (role == Roles.CONTROLLER) return addressProvider.getAddress(AddressProviderKeys._CONTROLLER_KEY);
}
if (role == Roles.POOL) return addressProvider.getPoolAtIndex(index);
if (role == Roles.VAULT) return addressProvider.getVaultAtIndex(index);

Note that also, this change will increase readability.

Affected source code:

Inline improve

El metodo uncheckedInc de la libreria UncheckedMath pretenden ahorrar gas, pero suponiendo que el compilador lo optimize al ser mayor a 0.8.2 y se compile como inline, aun así devolverá un valor a la pila que puede ser evitado. Y además es más optimo utiliza ++a en lugar de a+1;

The uncheckedInc method of the UncheckedMath library is intended to save gas, but assuming the compiler optimizes it because it's greater than 0.8.2, it will still return a value on the stack that can be avoided. And it is also more optimal to use ++a instead of a + 1;.

Affected source code:

#0 - chase-manning

2022-06-08T08:06:58Z

I consider this report to be of particularly high quality

#1 - GalloDaSballo

2022-06-15T23:24:54Z

Outdated compiler Not a gas saving

Reduce the size of error messages (Long revert Strings) Finding is valid, in lack of a list of the messages that are too long I'll give 1 for Error.sol and 1 for Minter.sol Per the discussion the warden specified saving an MSTORE would save 3 gas, computing an offset would most likely be another 3, so for the sake of this contest I'll give 6 gas saved per revert message.

12 gas

Delete optimization Delete internally sets the value to 0, so I do not believe this will save gas, to confirm. delete xyz is equivalent to xyz = 0

unchecked keyword Agree it would save around 20 gas

++i Personally I think inlining unchecked { ++i } would net the highest savings.

That said I believe that just doing ++i would save 3 gas and not 5 as the warden said. EDIT: I ran tests and it's 5 gas

19 * 5 = 95

Avoid unused return Agree that it would save gas, let's say 3 gas. It would also save the cost of verifying the return value which would most likely be around 6 gas (3 to check + 3 to check offset)

41 * 3 = 123

Improve logic This should save gas in some cases

Inline improve Agree that inlining would save more gas, let's say 3 as the intermediary result from the library should still be inlined per this discussion: https://github.com/ethereum/solidity/issues/3985#issuecomment-383848718

3 * 19 = 57

Total Gas Saved: 307

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter