[G-1]- USE CALLDATA INSTEAD OF MEMORY FOR FUNCTION PARAMETERS TYPE :
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
File: contracts/Witch.sol
line 223 : function _calcAuction(DataTypes.Vault memory vault, DataTypes.Series memory series, address to, DataTypes.Balances memory balances, DataTypes.Debt memory debt) internal view returns (DataTypes.Auction memory)
line 386 : function payInk(DataTypes.Auction memory auction, address to, uint256 liquidatorCut, uint256 auctioneerCut) internal
line 562 : function calcPayout(DataTypes.Auction memory auction, address to, uint256 artIn) internal view returns (uint256 liquidatorCut, uint256 auctioneerCut)
[G-2]- FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE :
If a function modifier such as onlyOwner or in this case auth is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for the owner because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are :
Which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
File: contracts/Witch.sol
line 83 : function point(bytes32 param, address value) external auth
line 95 : function setLine(bytes6 ilkId, bytes6 baseId, uint32 duration, uint64 proportion, uint64 initialOffer) external auth
line 126 : function setLimit(bytes6 ilkId, bytes6 baseId, uint128 max) external auth
line 141 : function setAnotherWitch(address value, bool isWitch) external auth
line 150 : function setIgnoredPair(bytes6 ilkId, bytes6 baseId, bool ignore) external auth
line 161 : function setAuctioneerReward(uint128 auctioneerReward_) external auth
[G-3] USING > 0 COSTS MORE GAS THAN != 0 WHEN USED ON A UINT IN A REQUIRE() STATEMENT :
UINT are always positive ( greater or equal than 0 ), so use != 0 instead of > 0 this change saves 6 gas per instance
File: contracts/Witch.sol
line 255 & line 300 & line 358 & line 416 : require(auction_.start > 0, "Vault not under auction");
[G-4] USAGE OF UINTS/INTS SMALLER THAN 32 BYTES (256 BITS) INCURS OVERHEAD :
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size as you can check here.
So use uint256/int256 for state variables and then downcast to lower sizes where needed
File: contracts/Witch.sol
line 63 : uint128 public auctioneerReward = 0.01e18;
[G-5] USE CUSTOM ERRORS RATHER THAN REQUIRE() STRINGS TO SAVE DEPLOYMENT 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) while providing the same amount of information.
There are many lines with this issue throughout the contract.
File: contracts/Witch.sol
line 84 : require(param == "ladle", "Unrecognized");
line 102 : require(initialOffer <= 1e18, "InitialOffer above 100%");
line 103 : require(proportion <= 1e18, "Proportion above 100%");
line 108 : require(proportion >= 0.01e18, "Proportion below 1%");
line 189 : require(cauldron.level(vaultId) < 0, "Not undercollateralized");
line 200 : require(limits.sum <= limits.max, "Collateral limit reached");
line 255 : require(auction_.start > 0, "Vault not under auction");
line 256 : require(cauldron.level(vaultId) >= 0, "Undercollateralized");
line 313 : require(liquidatorCut >= minInkOut, "Not enough bought");
line 328 : require(baseJoin != IJoin(address(0)), "Join not found");
[G-1]- USE CALLDATA INSTEAD OF MEMORY FOR FUNCTION PARAMETERS TYPE :
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
File: contracts/Witch.sol
line 223 : function _calcAuction(DataTypes.Vault memory vault, DataTypes.Series memory series, address to, DataTypes.Balances memory balances, DataTypes.Debt memory debt) internal view returns (DataTypes.Auction memory) line 386 : function payInk(DataTypes.Auction memory auction, address to, uint256 liquidatorCut, uint256 auctioneerCut) internal line 562 : function calcPayout(DataTypes.Auction memory auction, address to, uint256 artIn) internal view returns (uint256 liquidatorCut, uint256 auctioneerCut)
[G-2]- FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE :
If a function modifier such as onlyOwner or in this case auth is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for the owner because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are :
CALLVALUE(gas=2), DUP1(gas=3), ISZERO(gas=3), PUSH2(gas=3), JUMPI(gas=10), PUSH1(gas=3), DUP1(gas=3), REVERT(gas=0), JUMPDEST(gas=1), POP(gas=2).
Which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost
File: contracts/Witch.sol
line 83 : function point(bytes32 param, address value) external auth line 95 : function setLine(bytes6 ilkId, bytes6 baseId, uint32 duration, uint64 proportion, uint64 initialOffer) external auth line 126 : function setLimit(bytes6 ilkId, bytes6 baseId, uint128 max) external auth line 141 : function setAnotherWitch(address value, bool isWitch) external auth line 150 : function setIgnoredPair(bytes6 ilkId, bytes6 baseId, bool ignore) external auth line 161 : function setAuctioneerReward(uint128 auctioneerReward_) external auth
[G-3] USING > 0 COSTS MORE GAS THAN != 0 WHEN USED ON A UINT IN A REQUIRE() STATEMENT :
UINT are always positive ( greater or equal than 0 ), so use != 0 instead of > 0 this change saves 6 gas per instance
File: contracts/Witch.sol
line 255 & line 300 & line 358 & line 416 : require(auction_.start > 0, "Vault not under auction");
[G-4] USAGE OF UINTS/INTS SMALLER THAN 32 BYTES (256 BITS) INCURS OVERHEAD :
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size as you can check here.
So use uint256/int256 for state variables and then downcast to lower sizes where needed
File: contracts/Witch.sol
line 63 : uint128 public auctioneerReward = 0.01e18;
[G-5] USE CUSTOM ERRORS RATHER THAN REQUIRE() STRINGS TO SAVE DEPLOYMENT 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) while providing the same amount of information.
There are many lines with this issue throughout the contract.
File: contracts/Witch.sol
line 84 : require(param == "ladle", "Unrecognized"); line 102 : require(initialOffer <= 1e18, "InitialOffer above 100%"); line 103 : require(proportion <= 1e18, "Proportion above 100%"); line 108 : require(proportion >= 0.01e18, "Proportion below 1%"); line 189 : require(cauldron.level(vaultId) < 0, "Not undercollateralized"); line 200 : require(limits.sum <= limits.max, "Collateral limit reached"); line 255 : require(auction_.start > 0, "Vault not under auction"); line 256 : require(cauldron.level(vaultId) >= 0, "Undercollateralized"); line 313 : require(liquidatorCut >= minInkOut, "Not enough bought"); line 328 : require(baseJoin != IJoin(address(0)), "Join not found");