Frankencoin - pfapostol's results

A decentralized and fully collateralized stablecoin.

General Information

Platform: Code4rena

Start Date: 12/04/2023

Pot Size: $60,500 USDC

Total HM: 21

Participants: 199

Period: 7 days

Judge: hansfriese

Total Solo HM: 5

Id: 231

League: ETH

Frankencoin

Findings Distribution

Researcher Performance

Rank: 172/199

Findings: 1

Award: $21.03

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

21.0255 USDC - $21.03

Labels

bug
G (Gas Optimization)
grade-b
high quality report
G-25

External Links

Gas Optimizations
IssueInstancesEstimated gas(deployments)Estimated gas(avg method call)
1Optimize revert statements1460362
2Optimize boolean logic1300311
3Cache function call1-400117

Total: 3 instances over 3 issues


001. Optimize revert statements

PoC:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../../contracts/Frankencoin.sol";
import "forge-std/Test.sol";

contract _001Gas is Test {
    Frankencoin internal immutable zchf;
    address internal immutable TEST_MINTER =
        vm.addr(uint256(bytes32("TEST_MINTER")));
    uint256 internal constant TEST_MIN_APPLICATION_PERIOD = 864000;

    constructor() {
        zchf = new Frankencoin(TEST_MIN_APPLICATION_PERIOD);
    }

    function test_suggestMinter() public {
        zchf.suggestMinter(address(0), 0, 0, "");
        
        vm.prank(address(0));
        zchf.mint(TEST_MINTER, zchf.MIN_FEE());

        vm.startPrank(TEST_MINTER);
        zchf.suggestMinter(
            TEST_MINTER,
            zchf.MIN_APPLICATION_PERIOD(),
            zchf.MIN_FEE(),
            ""
        );
        vm.stopPrank();
    }
}
Fix:
    function suggestMinter(address _minter, uint256 _applicationPeriod, uint256 _applicationFee, string calldata _message) override external {
-      if (_applicationPeriod < MIN_APPLICATION_PERIOD && totalSupply() > 0) revert PeriodTooShort();
-      if (_applicationFee < MIN_FEE  && totalSupply() > 0) revert FeeTooLow(); // @audit-gas double check for totalSupply > 0
       if (minters[_minter] != 0) revert AlreadyRegistered();
+      if (totalSupply() > 0) {
+         if (_applicationPeriod < MIN_APPLICATION_PERIOD) revert PeriodTooShort();
+         if (_applicationFee < MIN_FEE) revert FeeTooLow(); // @audit-gas double check for totalSupply > 0
+      }
Before:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
296039615168
Function Nameminavgmedianmax# calls
...
suggestMinter348043678336783387622
After:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
295579315145
Function Nameminavgmedianmax# calls
...
suggestMinter346013672136721388422

Difference (Before-After):

Deployment Cost: 4603 min: 203 avg: 62 median: 62 max: -80


002. Optimize boolean logic

PoC:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "forge-std/console2.sol";

import "../../contracts/Frankencoin.sol";

contract _002Gas is Test {
    Frankencoin internal immutable zchf;

    address internal immutable TEST_MINTER =
        vm.addr(uint256(bytes32("TEST_MINTER")));

    address internal immutable USER_1 = vm.addr(uint256(bytes32("USER_1")));
    address internal immutable USER_2 = vm.addr(uint256(bytes32("USER_2")));

    uint256 internal constant TEST_MIN_APPLICATION_PERIOD = 864000;

    constructor() {
        zchf = new Frankencoin(TEST_MIN_APPLICATION_PERIOD);
    }

    function test_allowance() public {
        zchf.suggestMinter(TEST_MINTER, 0, 0, "");
        vm.startPrank(TEST_MINTER);
        zchf.mint(USER_1, 1e30);
        vm.stopPrank();
        vm.startPrank(USER_1);
        zchf.approve(USER_2, 1e30);
        vm.stopPrank();

        zchf.allowance(USER_1, TEST_MINTER);
        zchf.allowance(USER_1, USER_2);
        zchf.allowance(USER_2, USER_1);
    }
}
Fix:
    function allowanceInternal(address owner, address spender) internal view override returns (uint256) {
       uint256 explicit = super.allowanceInternal(owner, spender);
-      if (explicit > 0){
-         return explicit; // don't waste gas checking minter
-      } else if (isMinter(spender) || isMinter(isPosition(spender))){
-         return INFINITY;
-      } else {
-         return 0;
-      } // @audit-gas Can be optimized by reducing if else
+      if (explicit == 0) {
+         if (isMinter(spender) || isMinter(isPosition(spender))){
+            return INFINITY;
+         }
+      }
+      return explicit; // don't waste gas checking minter
    }
Before:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
296039615168
Function Nameminavgmedianmax# calls
allowance8924636338196353
...
After:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
295739315153
Function Nameminavgmedianmax# calls
allowance8814627338396173
...

Difference (Before-After):

Deployment Cost: 3,003 min: 11 avg: 11 median: -2 max: 18


003. Cache function call

PoC:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "forge-std/console2.sol";

import "../../contracts/Frankencoin.sol";

contract _003Gas is Test {
    Frankencoin internal immutable zchf;

    address internal immutable TEST_MINTER =
        vm.addr(uint256(bytes32("TEST_MINTER")));

    address internal immutable USER_1 = vm.addr(uint256(bytes32("USER_1")));

    uint256 internal constant TEST_MIN_APPLICATION_PERIOD = 864000;

    constructor() {
        zchf = new Frankencoin(TEST_MIN_APPLICATION_PERIOD);
    }

    function test_calculateAssignedReserve() public {
        zchf.suggestMinter(TEST_MINTER, 0, 0, "");
        vm.startPrank(TEST_MINTER);
        zchf.mint(USER_1, 1e30, 100_000, 100_000);
        zchf.calculateAssignedReserve(1e30, 100_000);
        zchf.balanceOf(address(zchf.reserve()));
        zchf.notifyLoss(2e29-1000);
        zchf.balanceOf(address(zchf.reserve()));
        zchf.calculateAssignedReserve(1e30, 100_000);
        zchf.notifyLoss(1000);
        zchf.calculateAssignedReserve(1e30, 100_000);
        vm.stopPrank();
    }
}
Fix:
    function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) public view returns (uint256) {
       uint256 theoreticalReserve = _reservePPM * mintedAmount / 1000000; // @audit-qa cast to 1e6
       uint256 currentReserve = balanceOf(address(reserve));
-      if (currentReserve < minterReserve()){ // @audit-gas cache function call
+      uint256 minterReserve = minterReserve();
+      if (currentReserve < minterReserve){ // @audit-gas cache function call
          // not enough reserves, owner has to take a loss
-         return theoreticalReserve * currentReserve / minterReserve();
+         return theoreticalReserve * currentReserve / minterReserve;
       } else {
          return theoreticalReserve;
       }
Before:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
296039615168
Function Nameminavgmedianmax# calls
...
calculateAssignedReserve11041339145714573
...
After:
contracts/Frankencoin.sol:Frankencoin contract
Deployment CostDeployment Size
296079615170
Function Nameminavgmedianmax# calls
...
calculateAssignedReserve11221222127212723
...

Difference (Before-After):

Deployment Cost: -400 min: -18 avg: 117 median: 185 max: 185

#0 - c4-pre-sort

2023-04-27T13:16:00Z

0xA5DF marked the issue as high quality report

#1 - 0xA5DF

2023-04-27T13:16:13Z

Short but correct and well formatted

#2 - c4-judge

2023-05-16T13:23:28Z

hansfriese marked the issue as grade-b

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