Skip to content

Instantly share code, notes, and snippets.

@minhquanym
Created February 14, 2023 20:47
Show Gist options
  • Save minhquanym/3109c921bda7e00109721a31fa425fc1 to your computer and use it in GitHub Desktop.
Save minhquanym/3109c921bda7e00109721a31fa425fc1 to your computer and use it in GitHub Desktop.

Report

Gas Optimizations

Issue Instances
GAS-1 Using bools for storage incurs overhead 5
GAS-2 Cache array length outside of loop 2
GAS-3 Use Custom Errors 138
GAS-4 Don't initialize variables with default value 2
GAS-5 Long revert strings 10
GAS-6 Functions guaranteed to revert when called by normal users can be marked payable 10
GAS-7 Splitting require() statements that use && saves gas 7
GAS-8 Use != 0 instead of > 0 for unsigned integer comparison 20

[GAS-1] Using bools for storage incurs overhead

Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.

Instances (5):

File: contracts/Repository.sol

24:   mapping(bytes32 => bool) public validRoles;
File: contracts/StabilityPod/StabilizerNode.sol

69:   bool internal trackAfterStabilize = true;

70:   bool public onlyStabilizeToPeg = false;

71:   bool public usePrimedWindow;
File: contracts/Token/Malt.sol

21:   bool internal initialSetup;

[GAS-2] Cache array length outside of loop

If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).

Instances (2):

File: contracts/Token/Malt.sol

110:     for (uint256 i = 0; i < minters.length; i = i + 1) {

114:     for (uint256 i = 0; i < burners.length; i = i + 1) {

[GAS-3] Use Custom Errors

Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.

Instances (138):

File: contracts/DataFeed/MaltDataLab.sol

78:     require(!contractActive, "MaltDataLab: Already setup");

79:     require(_malt != address(0), "MaltDataLab: Malt addr(0)");

80:     require(_collateralToken != address(0), "MaltDataLab: Col addr(0)");

81:     require(_stakeToken != address(0), "MaltDataLab: LP Token addr(0)");

82:     require(_poolMA != address(0), "MaltDataLab: PoolMA addr(0)");

91:     require(_globalIC != address(0), "MaltDataLab: GlobalIC addr(0)");

92:     require(_ratioMA != address(0), "MaltDataLab: RatioMA addr(0)");

523:     require(_price > 0, "Cannot have 0 price");

537:     require(_lookback > 0, "Cannot have 0 lookback");

545:     require(_lookback > 0, "Cannot have 0 lookback");

553:     require(_lookback > 0, "Cannot have 0 lookback");

561:     require(_lookback > 0, "Cannot have 0 lookback");

569:     require(_poolMA != address(0), "Cannot use 0 address");

577:     require(_ratioMA != address(0), "Cannot use 0 address");

585:     require(_z != 0, "Cannot have 0 for Z");

586:     require(_z <= 100, "Cannot be over 100");

594:     require(_swingTraderLowBps != 0, "Cannot have 0 Swing Trader Low BPS");

606:     require(_breakpointBps != 0, "Cannot have 0 breakpoint BPS");
File: contracts/GlobalImpliedCollateralService.sol

55:     require(_repository != address(0), "GlobImpCol: Timelock addr(0)");

56:     require(initialAdmin != address(0), "GlobImpCol: Admin addr(0)");

57:     require(_malt != address(0), "GlobImpCol: Malt addr(0)");

112:     require(_updater != address(0), "GlobImpCol: No addr(0)");

223:     require(msg.sender == deployer, "Only deployer");

224:     require(_updaterManager != address(0), "Cannot use addr(0)");

225:     require(updaterManager == address(0), "Initial updater already set");

237:     require(_updaterManager != address(0), "Cannot use addr(0)");

245:     require(msg.sender == proposedManager, "Must be proposedManager");
File: contracts/Repository.sol

54:     require(msg.sender == deployer, "Must be deployer");

91:     require(validRoles[role], "Unknown role");

129:     require(validRoles[role], "Unknown role");

134:       require(account != address(0), "0x0");

143:     require(destination != address(0), "Withdraw: addr(0)");

146:     require(success, "emergencyWithdrawGAS error");

153:     require(destination != address(0), "Withdraw: addr(0)");

163:     require(destination != address(0), "Withdraw: addr(0)");

165:     require(success, "partialWithdrawGAS error");

173:     require(destination != address(0), "Withdraw: addr(0)");

190:       require(account != address(0), "0x0");

213:     require(_contract != address(0), "0x0");

216:     require(currentContract.contractAddress == address(0), "Contract exists");

237:     require(_newContract != address(0), "0x0");
File: contracts/RewardSystem/LinearDistributor.sol

61:     require(!contractActive, "Distributor: Setup already done");

62:     require(_collateralToken != address(0), "Distributor: Col addr(0)");

63:     require(_rewardMine != address(0), "Distributor: RewardMine addr(0)");

64:     require(_rewardThrottle != address(0), "Distributor: Throttler addr(0)");

65:     require(_forfeitor != address(0), "Distributor: Forfeitor addr(0)");

175:     require(reward > 0, "Cannot declare 0 reward");

182:     require(declaredBalance <= totalReward, "Insufficient balance");

186:     require(forfeited <= declaredBalance, "Cannot forfeit more than declared");

196:     require(declaredBalance <= totalReward, "Insufficient balance");

209:     require(_rewardMine != address(0), "Cannot set 0 address as rewardMine");

218:     require(_forfeitor != address(0), "Cannot set 0 address as forfeitor");

226:     require(_vestingDistributor != address(0), "SetVestDist: No addr(0)");
File: contracts/RewardSystem/RewardThrottle.sol

67:     require(_timekeeper != address(0), "Throttle: Timekeeper addr(0)");

79:     require(!contractActive, "RewardThrottle: Already setup");

80:     require(_collateralToken != address(0), "RewardThrottle: Col addr(0)");

81:     require(_overflowPool != address(0), "RewardThrottle: Overflow addr(0)");

82:     require(_bonding != address(0), "RewardThrottle: Bonding addr(0)");

250:     require(startEpoch <= endEpoch, "Start cannot be before the end");

320:     require(startEpoch <= endEpoch, "Start cannot be before the end");

466:     require(epoch <= actualEpoch && epoch > activeEpoch, "Invalid epoch");

694:     require(_timekeeper != address(0), "Not address 0");

702:     require(_smoothingPeriod > 0, "No zero smoothing period");

710:     require(_runway > 604800, "Runway must be > 1 week");

718:     require(_aprCap != 0, "Cap cannot be 0");

726:     require(_aprFloor != 0, "Floor cannot be 0");

734:     require(_period >= timekeeper.epochLength(), "< 1 epoch");

742:     require(_cushionBps != 0, "Cannot be 0");

750:     require(_max != 0, "Cannot be 0");

758:     require(_gain != 0 && _gain < 10000, "Between 1-9999 inc");
File: contracts/StabilityPod/ImpliedCollateralService.sol

60:     require(!contractActive, "ImpCol: Already setup");

61:     require(_auction != address(0), "ImpCol: Auction addr(0)");

62:     require(_rewardOverflow != address(0), "ImpCol: Overflow addr(0)");

63:     require(_swingTraderManager != address(0), "ImpCol: Swing addr(0)");

64:     require(_liquidityExtension != address(0), "ImpCol: LE addr(0)");

65:     require(_maltDataLab != address(0), "ImpCol: DataLab addr(0)");

66:     require(_stabilizerNode != address(0), "ImpCol: StablizerNode addr(0)");

67:     require(_globalIC != address(0), "ImpCol: GlobalIC addr(0)");

68:     require(_collateralToken != address(0), "ImpCol: ColToken addr(0)");

69:     require(_malt != address(0), "ImpCol: Malt addr(0)");

70:     require(_stakeToken != address(0), "ImpCol: Stake Token addr(0)");
File: contracts/StabilityPod/StabilizerNode.sol

125:     require(!contractActive, "StabilizerNode: Already setup");

126:     require(_malt != address(0), "StabilizerNode: Malt addr(0)");

127:     require(_collateralToken != address(0), "StabilizerNode: Col addr(0)");

128:     require(_dexHandler != address(0), "StabilizerNode: DexHandler addr(0)");

129:     require(_maltDataLab != address(0), "StabilizerNode: DataLab addr(0)");

138:     require(_auction != address(0), "StabilizerNode: Auction addr(0)");

205:         require(livePrice < upperBand, "Stabilize: Beyond upper bound");

206:         require(livePrice > minThreshold, "Stabilize: Slippage threshold");

217:         require(livePrice > lowerBand, "Stabilize: Beyond lower bound");

249:     require(block.timestamp >= lastTracking + trackingBackoff, "Too early");

251:     require(success, "Too early");

458:     require(_period > 0, "Must be greater than 0");

467:     require(_incentive != 0 && _incentive <= 1000, "Incentive out of range");

479:     require(_incentive != 0 && _incentive <= 100000, "Incentive out of range");

493:     require(amount > 0, "No negative damping");

503:     require(_upper != 0 && _lower != 0, "Must be above 0");

504:     require(_lower < 10000, "Lower to large");

533:     require(_period > 0, "Cannot have 0 period");

554:     require(_period > 0, "Cannot have 0 period");

563:     require(_upper != 0 && _lower != 0, "Cannot have 0 band limit");

573:     require(_slippageBps <= 10000, "slippage: Must be <= 100%");

598:     require(_backoff != 0, "Cannot be 0");

623:     require(_callerCut <= 1000, "Must be less than 10%");

643:     require(_primedWindow != 0, "Cannot be 0");
File: contracts/StabilityPod/SwingTraderManager.sol

74:     require(!contractActive, "SwingTraderManager: Already setup");

79:     require(_malt != address(0), "SwingTraderManager: Malt addr(0)");

404:     require(traderId > 2 && trader.id == 0, "TraderId already used");

405:     require(_swingTrader != address(0), "addr(0)");

425:     require(trader.id == traderId, "Unknown trader");
File: contracts/Token/Malt.sol

46:     require(_repository != address(0), "Malt: Repo addr(0)");

47:     require(_transferService != address(0), "Malt: XferSvc addr(0)");

99:     require(msg.sender == deployer, "Only deployer");

100:     require(!initialSetup, "Malt: Already setup");

101:     require(_globalIC != address(0), "Malt: GlobalIC addr(0)");

102:     require(_manager != address(0), "Malt: Manager addr(0)");

111:       require(minters[i] != address(0), "Malt: Minter addr(0)");

115:       require(burners[i] != address(0), "Malt: Burner addr(0)");

148:     require(_burner != address(0), "No addr(0)");

157:     require(_minter != address(0), "No addr(0)");

198:     require(_manager != address(0), "Cannot use addr(0)");

206:     require(msg.sender == proposedManager, "Must be proposedManager");

217:     require(_service != address(0), "Cannot use address 0 as transfer service");

226:     require(_service != address(0), "Cannot use address 0 as global ic");
File: contracts/Token/TransferService.sol

37:     require(_repository != address(0), "XferSvc: Repo addr(0)");

38:     require(initialAdmin != address(0), "XferSvc: Admin addr(0)");

67:       require(success, "TransferService: External failure");

72:       require(success, "TransferService: External failure");

152:     require(_verifier != address(0), "Cannot use address 0");

153:     require(_address != address(0), "Cannot use address 0");

155:     require(verifiers[_address] == address(0), "Address already exists");

167:     require(_address != address(0), "Cannot use address 0");

168:     require(verifiers[_address] != address(0), "Address does not exists");

181:     require(msg.sender == deployer, "Only deployer");

182:     require(_verifierManager != address(0), "Cannot use addr(0)");

183:     require(verifierManager == address(0), "Initial verifier already set");

195:     require(_verifierManager != address(0), "Cannot use addr(0)");

203:     require(msg.sender == proposedManager, "Must be proposedManager");

[GAS-4] Don't initialize variables with default value

Instances (2):

File: contracts/Token/Malt.sol

110:     for (uint256 i = 0; i < minters.length; i = i + 1) {

114:     for (uint256 i = 0; i < burners.length; i = i + 1) {

[GAS-5] Long revert strings

Instances (10):

File: contracts/DataFeed/MaltDataLab.sol

594:     require(_swingTraderLowBps != 0, "Cannot have 0 Swing Trader Low BPS");
File: contracts/RewardSystem/LinearDistributor.sol

186:     require(forfeited <= declaredBalance, "Cannot forfeit more than declared");

209:     require(_rewardMine != address(0), "Cannot set 0 address as rewardMine");

218:     require(_forfeitor != address(0), "Cannot set 0 address as forfeitor");
File: contracts/StabilityPod/StabilizerNode.sol

128:     require(_dexHandler != address(0), "StabilizerNode: DexHandler addr(0)");
File: contracts/StabilityPod/SwingTraderManager.sol

74:     require(!contractActive, "SwingTraderManager: Already setup");
File: contracts/Token/Malt.sol

217:     require(_service != address(0), "Cannot use address 0 as transfer service");

226:     require(_service != address(0), "Cannot use address 0 as global ic");
File: contracts/Token/TransferService.sol

67:       require(success, "TransferService: External failure");

72:       require(success, "TransferService: External failure");

[GAS-6] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner 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 legitimate callers because the compiler will not include checks for whether a payment was provided.

Instances (10):

File: contracts/DataFeed/MaltDataLab.sol

297:   function trackPool() external onlyActive returns (bool) {
File: contracts/Repository.sol

95:   function addNewRole(bytes32 role) external onlyRole(TIMELOCK_ROLE) {

99:   function removeRole(bytes32 role) external onlyRole(getRoleAdmin(role)) {
File: contracts/RewardSystem/RewardThrottle.sol

94:   function handleReward() external onlyActive {

131:   function updateDesiredAPR() public onlyActive {

437:   function checkRewardUnderflow() public onlyActive {
File: contracts/StabilityPod/ImpliedCollateralService.sol

89:   function syncGlobalCollateral() public onlyActive {
File: contracts/StabilityPod/StabilizerNode.sol

161:   function stabilize() external nonReentrant onlyEOA onlyActive whenNotPaused {

239:   function endAuctionEarly() external onlyActive whenNotPaused {

248:   function trackPool() external onlyActive {

[GAS-7] Splitting require() statements that use && saves gas

Instances (7):

File: contracts/RewardSystem/RewardThrottle.sol

466:     require(epoch <= actualEpoch && epoch > activeEpoch, "Invalid epoch");

758:     require(_gain != 0 && _gain < 10000, "Between 1-9999 inc");
File: contracts/StabilityPod/StabilizerNode.sol

467:     require(_incentive != 0 && _incentive <= 1000, "Incentive out of range");

479:     require(_incentive != 0 && _incentive <= 100000, "Incentive out of range");

503:     require(_upper != 0 && _lower != 0, "Must be above 0");

563:     require(_upper != 0 && _lower != 0, "Cannot have 0 band limit");
File: contracts/StabilityPod/SwingTraderManager.sol

404:     require(traderId > 2 && trader.id == 0, "TraderId already used");

[GAS-8] Use != 0 instead of > 0 for unsigned integer comparison

Instances (20):

File: contracts/DataFeed/MaltDataLab.sol

333:       maltPriceCumulativeLast > 0 && priceCumulative == maltPriceCumulativeLast

523:     require(_price > 0, "Cannot have 0 price");

537:     require(_lookback > 0, "Cannot have 0 lookback");

545:     require(_lookback > 0, "Cannot have 0 lookback");

553:     require(_lookback > 0, "Cannot have 0 lookback");

561:     require(_lookback > 0, "Cannot have 0 lookback");
File: contracts/RewardSystem/LinearDistributor.sol

130:     if (distributed > 0) {

168:     if (amount > 0) {

175:     require(reward > 0, "Cannot declare 0 reward");
File: contracts/RewardSystem/RewardThrottle.sol

109:     if (aprTarget > 0 && _epochAprGivenReward(epoch, balance) > aprTarget) {

113:       if (remainder > 0) {

124:     if (balance > 0) {

449:         if (underflow > 0) {

629:     if (_activeEpoch > 0) {

702:     require(_smoothingPeriod > 0, "No zero smoothing period");
File: contracts/StabilityPod/StabilizerNode.sol

458:     require(_period > 0, "Must be greater than 0");

493:     require(amount > 0, "No negative damping");

533:     require(_period > 0, "Cannot have 0 period");

554:     require(_period > 0, "Cannot have 0 period");
File: contracts/StabilityPod/SwingTraderManager.sol

315:     if (netBalance > 0) {

Non Critical Issues

Issue Instances
NC-1 TODO Left in the code 3

[NC-1] TODO Left in the code

TODOs may signal that a feature is missing or not ready for audit, consider resolving the issue and removing the TODO comment

Instances (3):

File: contracts/DataFeed/MaltDataLab.sol

212:     // TODO MaltDataLab.sol will this work with other decimals? Sat 22 Oct 2022 18:44:07 BST
File: contracts/StabilityPod/StabilizerNode.sol

352:     // TODO StabilizerNode.sol these checks won't work when working with pools not pegged to 1 Wed 26 Oct 2022 16:40:25 BST

441:     // TODO StabilizerNode.sol invert priceTarget? Fri 21 Oct 2022 11:02:43 BST

Low Issues

Issue Instances
L-1 abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() 5
L-2 Do not use deprecated library functions 3
L-3 Empty Function Body - Consider commenting why 1

[L-1] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). "Unless there is a compelling reason, abi.encode should be preferred". If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead. If all arguments are strings and or bytes, bytes.concat() should be used instead

Instances (5):

File: contracts/Repository.sol

77:     bytes32 hashedRole = keccak256(abi.encodePacked(_role));

82:     bytes32 hashedContract = keccak256(abi.encodePacked(_contract));

214:     bytes32 hashedName = keccak256(abi.encodePacked(_name));

224:     bytes32 hashedName = keccak256(abi.encodePacked(_name));

238:     bytes32 hashedName = keccak256(abi.encodePacked(_name));

[L-2] Do not use deprecated library functions

Instances (3):

File: contracts/StabilityPod/SwingTraderManager.sol

99:     _setupRole(STABILIZER_NODE_ROLE, _stabilizerNode);
File: contracts/Token/Malt.sol

112:       _setupRole(MONETARY_MINTER_ROLE, minters[i]);

116:       _setupRole(MONETARY_BURNER_ROLE, burners[i]);

[L-3] Empty Function Body - Consider commenting why

Instances (1):

File: contracts/StabilityPod/ImpliedCollateralService.sol

46:   ) StabilizedPoolUnit(timelock, repository, poolFactory) {}

Medium Issues

Issue Instances
M-1 Centralization Risk for trusted owners 12

[M-1] Centralization Risk for trusted owners

Impact:

Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.

Instances (12):

File: contracts/Repository.sol

16: contract MaltRepository is AccessControl {

89:     onlyRole(getRoleAdmin(role))

95:   function addNewRole(bytes32 role) external onlyRole(TIMELOCK_ROLE) {

99:   function removeRole(bytes32 role) external onlyRole(getRoleAdmin(role)) {

106:     onlyRole(TIMELOCK_ROLE)

113:     onlyRole(TIMELOCK_ROLE)

120:     onlyRole(TIMELOCK_ROLE)

127:     onlyRole(getRoleAdmin(role))

141:     onlyRole(TIMELOCK_ROLE)

151:     onlyRole(TIMELOCK_ROLE)

161:     onlyRole(TIMELOCK_ROLE)

172:   ) external onlyRole(TIMELOCK_ROLE) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment