Platform: Code4rena
Start Date: 07/10/2022
Pot Size: $50,000 USDC
Total HM: 4
Participants: 62
Period: 5 days
Judge: 0xean
Total Solo HM: 2
Id: 169
League: ETH
Rank: 37/62
Findings: 1
Award: $20.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0xNazgul, 0xSmartContract, 0xdeadbeef, B2, Bnke0x0, Deivitto, ElKu, Jujic, KoKo, Pheonix, RaymondFam, RedOneN, RockingMiles, Rolezn, Saintcode_, Shinchan, TomJ, Tomio, __141345__, ajtra, aysha, c3phas, carlitox477, catchup, delfin454000, emrekocak, erictee, fatherOfBlocks, gerdusx, gianganhnguyen, gogo, martin, mcwildy, medikko, oyc_109, pedr02b2, rbserver, ret2basic, rotcivegaf, saian, sakman, zishansami
20.7905 USDC - $20.79
Gas report (12 findings with 27 instances )
Contracts allow use of solc version 0.7.6, which is fairly dated. Upgrading the solc compiler to 0.8 or higher will give the latest compiler benefits including bug fixes, security enhancements and overall optimizations especially the in-built overflow/underflow checks which may give gas savings by avoiding expensive SafeMath.
pragma solidity ^0.7.6;
In all scope files.
In the contracts, change abi.encode to abi.encodePacked can save gas. There are 4 instances of this issue:
return abi.encode(seqNum);
abi.encode(emptyBytes, _data)
return abi.encode(id);
abi.encode(0, _data)
See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper There are 3 instances of this issue:
require(_escrow != address(0) && Address.isContract(_escrow), "INVALID_ESCROW");
require( pendingGovernor != address(0) && msg.sender == pendingGovernor, "Caller must be pending governor" );
require( _pendingImplementation != address(0) && msg.sender == _pendingImplementation, "Caller must be the pending implementation" );
Each extra memory word of bytes past the original 32 [incurs an MSTORE] (https://gist.github.com/hrkrshnn/ee8fabd532058307229d65dcd5836ddc#consider-having-short-revert-strings) which costs 3 gas There are 4 instances of this issue:
require(msg.sender == controller.getGovernor(), "Caller must be Controller governor");
require(_newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
require( _pendingImplementation != address(0) && msg.sender == _pendingImplementation, "Caller must be the pending implementation" );
require(msg.sender == _implementation(), "Caller must be the implementation");
Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deploy cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.
Where possible, use equivalent function parameters or local variables in event emits instead of state variables to prevent expensive SLOADs. Post-Berlin, SLOADs on state variables accessed first-time in a transaction increased from 800 gas to 2100, which is a 2.5x increase.
function setGateway(address _gw) external onlyGovernor { require(_gw != address(0), "INVALID_GATEWAY"); gateway = _gw; emit GatewaySet(gateway); }
There are 4 instances of this issue:
require(_amount > 0, "INVALID_ZERO_AMOUNT");
require(maxSubmissionCost > 0, "NO_SUBMISSION_COST");
require(_amount > 0, "INVALID_ZERO_AMOUNT");
if (_data.length > 0) {
The more widespread pattern is using the ERC-165 interface, which not only checks a single functions, but a complete interface. The staticcall approach has the possibility of wasting gas, should the recipient perform a lot of steps before hitting an exception.
function getProxyImplementation(IGraphProxy _proxy) public view returns (address) { // We need to manually run the static call since the getter cannot be flagged as view // bytes4(keccak256("implementation()")) == 0x5c60da1b (bool success, bytes memory returndata) = address(_proxy).staticcall(hex"5c60da1b"); require(success); return abi.decode(returndata, (address)); }
modifier onlyL2Counterpart() { require(inbox != address(0), "INBOX_NOT_SET"); // a message coming from the counterpart gateway was executed by the bridge IBridge bridge = IInbox(inbox).bridge(); require(msg.sender == address(bridge), "NOT_FROM_BRIDGE"); // and the outbox reports that the L2 address of the sender is the counterpart gateway address l2ToL1Sender = IOutbox(bridge.activeOutbox()).l2ToL1Sender(); require(l2ToL1Sender == l2Counterpart, "ONLY_COUNTERPART_GATEWAY"); _; }
This results in the keccak operation being performed whenever the variable is used, increasing gas costs relative to just storing the output hash. There are 4 instances of this issue:
bytes32 private constant DOMAIN_TYPE_HASH = keccak256( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract,bytes32 salt)" ); bytes32 private constant DOMAIN_NAME_HASH = keccak256("Graph Token"); bytes32 private constant DOMAIN_VERSION_HASH = keccak256("0"); ... bytes32 private constant PERMIT_TYPEHASH = keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" );
public/external function names and public member variable names can be optimized to save gas. See [this] (https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92) link for an example of how it works. Below is the interface that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup.
interface IgraphToken
// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past. There are 3 instances of this issue:
bool internal _partialPaused; bool internal _paused;
mapping(address => bool) private _minters;