Olas - IllIllI's results

Olas is a unified network for off-chain services like automation, oracles, co-owned AI. It offers a stack for building services and a protocol for incentivizing their creation and their operation in a co-owned and decentralized way.

General Information

Platform: Code4rena

Start Date: 21/12/2023

Pot Size: $90,500 USDC

Total HM: 10

Participants: 39

Period: 18 days

Judge: LSDan

Total Solo HM: 5

Id: 315

League: ETH

Olas

Findings Distribution

Researcher Performance

Rank: 8/39

Findings: 2

Award: $793.49

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 1

šŸš€ Solo Findings: 0

Awards

352.4857 USDC - $352.49

Labels

bug
grade-a
QA (Quality Assurance)
selected for report
sponsor disputed
sufficient quality report
Q-21

External Links

Note: I've removed the instances reported by the winning bot and 4naly3er

Summary

Non-critical Issues

IssueInstances
[N‑01]else-block not required1
[N‑02]public functions not called by the contract should be declared external instead2
[N‑03]Common functions should be refactored to a common base contract8
[N‑04]Consider making contracts Upgradeable22
[N‑05]Consider moving duplicated strings to constants4
[N‑06]Consider using delete rather than assigning zero/false to clear values3
[N‑07]Consider using descriptive constants when passing zero as a function argument22
[N‑08]Consider using named function arguments19
[N‑09]Consider using named returns63
[N‑10]Constants in comparisons should appear on the left side10
[N‑11]Contract timekeeping will break earlier than the Ethereum network itself will stop working1
[N‑12]Contracts/libraries/interfaces should each be defined in separate files12
[N‑13]Function state mutability can be restricted to view6
[N‑14]Inconsistent spacing in comments2
[N‑15]Interfaces should be defined in separate files from their usage1
[N‑16]Named imports of parent contracts are missing17
[N‑17]NatSpec: Event @param tag is missing149
[N‑18]NatSpec: Function @param tag is missing1
[N‑19]NatSpec: Function @return tag is missing8
[N‑20]Not using the named return variables anywhere in the function is confusing4
[N‑21]Style guide: Local and state variables should be named using lowerCamelCase1
[N‑22]Style guide: Non-external/public variable names should begin with an underscore3
[N‑23]Style guide: Top-level declarations should be separated by at least two lines2
[N‑24]Unnecessary cast3
[N‑25]Unused error definition9
[N‑26]Unused public contract variable9
[N‑27]Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning1
[N‑28]Use bit shifts to represent powers of two7
[N‑29]Use of override is unnecessary14

Total: 404 instances over 29 issues

Non-critical Issues

[N‑01] else-block not required

One level of nesting can be removed by not having an else block when the if-block returns, and if (foo) { return 1; } else { return 2; } becomes if (foo) { return 1; } return 2;. A following else if can become if

There is one instance of this issue:

File: contracts/veOLAS.sol

265                  if (tStep == block.timestamp) {
266                      lastPoint.blockNumber = uint64(block.number);
267                      lastPoint.balance = curSupply;
268:                     break;

GitHub: 265

[N‑02] public functions not called by the contract should be declared external instead

Contracts are allowed to override their parents' functions and change the visibility from external to public.

There are 2 instances of this issue:

File: contracts/veOLAS.sol

761:      function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {

GitHub: 761

File: contracts/GenericRegistry.sol

135:      function tokenURI(uint256 unitId) public view virtual override returns (string memory) {

GitHub: 135

[N‑03] Common functions should be refactored to a common base contract

The functions below have the same implementation as is seen in other files. The functions should be refactored into functions of a common base contract

There are 8 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/GenericManager.sol

20       function changeOwner(address newOwner) external virtual {
21           // Check for the ownership
22           if (msg.sender != owner) {
23               revert OwnerOnly(msg.sender, owner);
24           }
25   
26           // Check for the zero address
27           if (newOwner == address(0)) {
28               revert ZeroAddress();
29           }
30   
31           owner = newOwner;
32           emit OwnerUpdated(newOwner);
33:      }

GitHub: 20

File: contracts/GenericRegistry.sol

37       function changeOwner(address newOwner) external virtual {
38           // Check for the ownership
39           if (msg.sender != owner) {
40               revert OwnerOnly(msg.sender, owner);
41           }
42   
43           // Check for the zero address
44           if (newOwner == address(0)) {
45               revert ZeroAddress();
46           }
47   
48           owner = newOwner;
49           emit OwnerUpdated(newOwner);
50:      }

GitHub: 37

File: contracts/Depository.sol

123      function changeOwner(address newOwner) external {
124          // Check for the contract ownership
125          if (msg.sender != owner) {
126              revert OwnerOnly(msg.sender, owner);
127          }
128  
129          // Check for the zero address
130          if (newOwner == address(0)) {
131              revert ZeroAddress();
132          }
133  
134          owner = newOwner;
135          emit OwnerUpdated(newOwner);
136:     }

143      function changeManagers(address _tokenomics, address _treasury) external {
144          // Check for the contract ownership
145          if (msg.sender != owner) {
146              revert OwnerOnly(msg.sender, owner);
147          }
148  
149          // Change Tokenomics contract address
150          if (_tokenomics != address(0)) {
151              tokenomics = _tokenomics;
152              emit TokenomicsUpdated(_tokenomics);
153          }
154          // Change Treasury contract address
155          if (_treasury != address(0)) {
156              treasury = _treasury;
157              emit TreasuryUpdated(_treasury);
158          }
159:     }

GitHub: 123, 143

File: contracts/Dispenser.sol

46       function changeOwner(address newOwner) external {
47           // Check for the contract ownership
48           if (msg.sender != owner) {
49               revert OwnerOnly(msg.sender, owner);
50           }
51   
52           // Check for the zero address
53           if (newOwner == address(0)) {
54               revert ZeroAddress();
55           }
56   
57           owner = newOwner;
58           emit OwnerUpdated(newOwner);
59:      }

GitHub: 46

File: contracts/DonatorBlacklist.sol

36       function changeOwner(address newOwner) external {
37           // Check for the contract ownership
38           if (msg.sender != owner) {
39               revert OwnerOnly(msg.sender, owner);
40           }
41   
42           // Check for the zero address
43           if (newOwner == address(0)) {
44               revert ZeroAddress();
45           }
46   
47           owner = newOwner;
48           emit OwnerUpdated(newOwner);
49:      }

GitHub: 36

File: contracts/Tokenomics.sol

404      function changeOwner(address newOwner) external {
405          // Check for the contract ownership
406          if (msg.sender != owner) {
407              revert OwnerOnly(msg.sender, owner);
408          }
409  
410          // Check for the zero address
411          if (newOwner == address(0)) {
412              revert ZeroAddress();
413          }
414  
415          owner = newOwner;
416          emit OwnerUpdated(newOwner);
417:     }

GitHub: 404

File: contracts/Treasury.sol

137      function changeOwner(address newOwner) external {
138          // Check for the contract ownership
139          if (msg.sender != owner) {
140              revert OwnerOnly(msg.sender, owner);
141          }
142  
143          // Check for the zero address
144          if (newOwner == address(0)) {
145              revert ZeroAddress();
146          }
147  
148          owner = newOwner;
149          emit OwnerUpdated(newOwner);
150:     }

GitHub: 137

</details>

[N‑04] Consider making contracts Upgradeable

This allows for bugs to be fixed in production, at the expense of significantly increasing centralization.

There are 22 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

17:  contract OLAS is ERC20 {

GitHub: 17

File: contracts/Timelock.sol

9:   contract Timelock is TimelockController {

GitHub: 9

File: contracts/bridges/BridgedERC20.sol

18:  contract BridgedERC20 is ERC20 {

GitHub: 18

File: contracts/bridges/FxERC20ChildTunnel.sol

24:  contract FxERC20ChildTunnel is FxBaseChildTunnel {

GitHub: 24

File: contracts/bridges/FxERC20RootTunnel.sol

24:  contract FxERC20RootTunnel is FxBaseRootTunnel {

GitHub: 24

File: contracts/bridges/FxGovernorTunnel.sol

46:  contract FxGovernorTunnel is IFxMessageProcessor {

GitHub: 46

File: contracts/bridges/HomeMediator.sol

46:  contract HomeMediator {

GitHub: 46

File: contracts/multisigs/GuardCM.sol

88:  contract GuardCM {

GitHub: 88

File: contracts/veOLAS.sol

86:  contract veOLAS is IErrors, IVotes, IERC20, IERC165 {

GitHub: 86

File: contracts/wveOLAS.sol

130  contract wveOLAS {
131:     // veOLAS address

GitHub: 130

File: contracts/AgentRegistry.sol

9    contract AgentRegistry is UnitRegistry {
10:      // Component registry

GitHub: 9

File: contracts/ComponentRegistry.sol

8    contract ComponentRegistry is UnitRegistry {
9:       // Component registry version number

GitHub: 8

File: contracts/RegistriesManager.sol

9    contract RegistriesManager is GenericManager {
10:      // Component registry address

GitHub: 9

File: contracts/multisigs/GnosisSafeMultisig.sol

24   contract GnosisSafeMultisig {
25:      // Selector of the Gnosis Safe setup function

GitHub: 24

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

50   contract GnosisSafeSameAddressMultisig {
51       // Default data size to be parsed as an address of a Gnosis Safe multisig proxy address
52:      // This exact size suggests that all the changes to the multisig have been performed and only validation is needed

GitHub: 50

File: contracts/Depository.sol

62:  contract Depository is IErrorsTokenomics {

GitHub: 62

File: contracts/Dispenser.sol

11:  contract Dispenser is IErrorsTokenomics {

GitHub: 11

File: contracts/DonatorBlacklist.sol

20:  contract DonatorBlacklist {

GitHub: 20

File: contracts/GenericBondCalculator.sol

20   contract GenericBondCalculator {
21:      // OLAS contract address

GitHub: 20

File: contracts/Tokenomics.sol

118: contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {

GitHub: 118

File: contracts/TokenomicsProxy.sol

26   contract TokenomicsProxy {
27:      // Code position in storage is keccak256("PROXY_TOKENOMICS") = "0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f"

GitHub: 26

File: contracts/Treasury.sol

39:  contract Treasury is IErrorsTokenomics {

GitHub: 39

</details>

[N‑05] Consider moving duplicated strings to constants

There are 4 instances of this issue:

File: contracts/AgentRegistry.sol

13:      string public constant VERSION = "1.0.0";

GitHub: 13

File: contracts/ComponentRegistry.sol

10:      string public constant VERSION = "1.0.0";

GitHub: 10

File: contracts/Depository.sol

77:      string public constant VERSION = "1.0.1";

GitHub: 77

File: contracts/TokenomicsConstants.sol

11:      string public constant VERSION = "1.0.1";

GitHub: 11

[N‑06] Consider using delete rather than assigning zero/false to clear values

The delete keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic

There are 3 instances of this issue:

File: contracts/GenericManager.sol

53:          paused = false;

GitHub: 53

File: contracts/Treasury.sol

455:                 success = false;

518:             mapEnabledTokens[token] = false;

GitHub: 455, 518

[N‑07] Consider using descriptive constants when passing zero as a function argument

Passing zero as a function argument can sometimes result in a security issue (e.g. passing zero as the slippage parameter). Consider using a constant variable with a descriptive name, so it's clear that the argument is intentionally being used, and for the right reasons.

There are 22 instances of this issue:

File: contracts/veOLAS.sol

139:         mapSupplyPoints[0] = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

139:         mapSupplyPoints[0] = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

139:         mapSupplyPoints[0] = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

215:             lastPoint = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

215:             lastPoint = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

215:             lastPoint = PointVoting(0, 0, uint64(block.timestamp), uint64(block.number), 0);

321:         _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply));

321:         _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply));

321:         _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply));

321:         _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply));

321:         _checkpoint(address(0), LockedBalance(0, 0), LockedBalance(0, 0), uint128(supply));

396:         _depositFor(account, amount, 0, lockedBalance, DepositType.DEPOSIT_FOR_TYPE);

478:         _depositFor(msg.sender, amount, 0, lockedBalance, DepositType.INCREASE_LOCK_AMOUNT);

506:         _depositFor(msg.sender, 0, unlockTime, lockedBalance, DepositType.INCREASE_UNLOCK_TIME);

517:         mapLockedBalances[msg.sender] = LockedBalance(0, 0);

517:         mapLockedBalances[msg.sender] = LockedBalance(0, 0);

528:         _checkpoint(msg.sender, lockedBalance, LockedBalance(0, 0), uint128(supplyAfter));

528:         _checkpoint(msg.sender, lockedBalance, LockedBalance(0, 0), uint128(supplyAfter));

650:         (point, minPointNumber) = _findPointByBlock(blockNumber, address(0));

728:         (PointVoting memory sPoint, ) = _findPointByBlock(blockNumber, address(0));

GitHub: 139, 139, 139, 215, 215, 215, 321, 321, 321, 321, 321, 396, 478, 506, 517, 517, 528, 528, 650, 728

File: contracts/Tokenomics.sol

329:         uint256 _inflationPerSecond = getInflationForYear(0) / zeroYearSecondsLeft;

GitHub: 329

File: contracts/Treasury.sol

408:                 revert TransferFailed(address(0), address(this), account, accountRewards);

GitHub: 408

[N‑08] Consider using named function arguments

When calling functions in external contracts with multiple arguments, consider using named function parameters, rather than positional ones.

There are 19 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/bridges/FxERC20RootTunnel.sol

77:          IERC20(rootToken).mint(to, amount);

GitHub: 77

File: contracts/wveOLAS.sol

197:             uPoint = IVEOLAS(ve).getUserPoint(account, idx);

216:             balance = IVEOLAS(ve).getPastVotes(account, blockNumber);

236:             balance = IVEOLAS(ve).balanceOfAt(account, blockNumber);

GitHub: 197, 216, 236

File: contracts/RegistriesManager.sol

39:              unitId = IRegistry(componentRegistry).create(unitOwner, unitHash, dependencies);

41:              unitId = IRegistry(agentRegistry).create(unitOwner, unitHash, dependencies);

52:              success = IRegistry(componentRegistry).updateHash(msg.sender, unitId, unitHash);

54:              success = IRegistry(agentRegistry).updateHash(msg.sender, unitId, unitHash);

GitHub: 39, 41, 52, 54

File: contracts/multisigs/GnosisSafeMultisig.sol

106:         multisig = IGnosisSafeProxyFactory(gnosisSafeProxyFactory).createProxyWithNonce(gnosisSafe, safeParams, nonce);

GitHub: 106

File: contracts/Depository.sol

320:         payout = IGenericBondCalculator(bondCalculator).calculatePayoutOLAS(tokenAmount, product.priceLP);

337:         ITreasury(treasury).depositTokenForOLAS(msg.sender, tokenAmount, token, payout);

GitHub: 320, 337

File: contracts/Dispenser.sol

99:          (reward, topUp) = ITokenomics(tokenomics).accountOwnerIncentives(msg.sender, unitTypes, unitIds);

104:             success = ITreasury(treasury).withdrawToAccount(msg.sender, reward, topUp);

GitHub: 99, 104

File: contracts/Tokenomics.sol

716                  (uint256 numServiceUnits, uint32[] memory serviceUnitIds) = IServiceRegistry(serviceRegistry).
717:                     getUnitIdsOfService(IServiceRegistry.UnitType(unitType), serviceIds[i]);

GitHub: 716

File: contracts/Treasury.sol

228:         if (IToken(token).allowance(account, address(this)) < tokenAmount) {

229:             revert InsufficientAllowance(IToken(token).allowance((account), address(this)), tokenAmount);

242:         IOLAS(olas).mint(msg.sender, olasMintAmount);

298:         ITokenomics(tokenomics).trackServiceDonations(msg.sender, serviceIds, amounts, msg.value);

416:             IOLAS(olas).mint(account, accountTopUps);

GitHub: 228, 229, 242, 298, 416

</details>

[N‑09] Consider using named returns

Using named returns makes the code more self-documenting, makes it easier to fill out NatSpec, and in some cases can save gas. The cases below are where there currently is at most one return statement, which is ideal for named returns.

There are 63 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/GovernorOLAS.sol

33       function state(uint256 proposalId) public view override(IGovernor, Governor, GovernorTimelockControl)
34           returns (ProposalState)
35:      {

45       function propose(
46           address[] memory targets,
47           uint256[] memory values,
48           bytes[] memory calldatas,
49           string memory description
50       ) public override(IGovernor, Governor, GovernorCompatibilityBravo) returns (uint256)
51:      {

57       function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256)
58:      {

85       function _cancel(
86           address[] memory targets,
87           uint256[] memory values,
88           bytes[] memory calldatas,
89           bytes32 descriptionHash
90       ) internal override(Governor, GovernorTimelockControl) returns (uint256)
91:      {

97       function _executor() internal view override(Governor, GovernorTimelockControl) returns (address)
98:      {

105      function supportsInterface(bytes4 interfaceId) public view override(IERC165, Governor, GovernorTimelockControl)
106          returns (bool)
107:     {

GitHub: 33, 45, 57, 85, 97, 105

File: contracts/OLAS.sol

91:      function inflationControl(uint256 amount) public view returns (bool) {

128:     function decreaseAllowance(address spender, uint256 amount) external returns (bool) {

145:     function increaseAllowance(address spender, uint256 amount) external returns (bool) {

GitHub: 91, 128, 145

File: contracts/bridges/HomeMediator.sol

6:       function messageSender() external view returns (address);

GitHub: 6

File: contracts/interfaces/IERC20.sol

9:       function balanceOf(address account) external view returns (uint256);

13:      function totalSupply() external view returns (uint256);

19:      function allowance(address owner, address spender) external view returns (uint256);

25:      function approve(address spender, uint256 amount) external returns (bool);

31:      function transfer(address to, uint256 amount) external returns (bool);

38:      function transferFrom(address from, address to, uint256 amount) external returns (bool);

GitHub: 9, 13, 19, 25, 31, 38

File: contracts/multisigs/GuardCM.sol

7:       function state(uint256 proposalId) external returns (ProposalState);

GitHub: 7

File: contracts/veOLAS.sol

164:     function getUserPoint(address account, uint256 idx) external view returns (PointVoting memory) {

633:     function getVotes(address account) public view override returns (uint256) {

719:     function totalSupply() public view override returns (uint256) {

738:     function totalSupplyLockedAtT(uint256 ts) public view returns (uint256) {

745:     function totalSupplyLocked() public view returns (uint256) {

752:     function getPastTotalSupply(uint256 blockNumber) public view override returns (uint256) {

761:     function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {

767:     function transfer(address to, uint256 amount) external virtual override returns (bool) {

772:     function approve(address spender, uint256 amount) external virtual override returns (bool) {

777:     function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) {

782      function allowance(address owner, address spender) external view virtual override returns (uint256)
783:     {

788      function delegates(address account) external view virtual override returns (address)
789:     {

GitHub: 164, 633, 719, 738, 745, 752, 761, 767, 772, 777, 782, 788

File: contracts/wveOLAS.sol

98:      function supportsInterface(bytes4 interfaceId) external view returns (bool);

101:     function allowance(address owner, address spender) external view returns (uint256);

104:     function delegates(address account) external view returns (address);

292:     function supportsInterface(bytes4 interfaceId) external view returns (bool) {

297:     function transfer(address, uint256) external returns (bool) {

302:     function approve(address, uint256) external returns (bool) {

307:     function transferFrom(address, address, uint256) external returns (bool) {

312:     function allowance(address, address) external view returns (uint256) {

317:     function delegates(address) external view returns (address) {

GitHub: 98, 101, 104, 292, 297, 302, 307, 312, 317

File: contracts/GenericRegistry.sol

72:      function exists(uint256 unitId) external view virtual returns (bool) {

129:     function _getUnitHash(uint256 unitId) internal view virtual returns (bytes32);

135:     function tokenURI(uint256 unitId) public view virtual override returns (string memory) {

GitHub: 72, 129, 135

File: contracts/UnitRegistry.sol

270:     function _getUnitHash(uint256 unitId) internal view override returns (bytes32) {

GitHub: 270

File: contracts/interfaces/IRegistry.sol

16       function create(
17           address unitOwner,
18           bytes32 unitHash,
19           uint32[] memory dependencies
20:      ) external returns (uint256);

48:      function totalSupply() external view returns (uint256);

GitHub: 16, 48

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

8:       function getOwners() external view returns (address[] memory);

12:      function getThreshold() external view returns (uint256);

GitHub: 8, 12

File: contracts/interfaces/IOLAS.sol

12:      function timeLaunch() external view returns (uint256);

GitHub: 12

File: contracts/interfaces/IServiceRegistry.sol

14:      function exists(uint256 serviceId) external view returns (bool);

GitHub: 14

File: contracts/interfaces/IToken.sol

9:       function balanceOf(address account) external view returns (uint256);

14:      function ownerOf(uint256 tokenId) external view returns (address);

18:      function totalSupply() external view returns (uint256);

24:      function transfer(address to, uint256 amount) external returns (bool);

30:      function allowance(address owner, address spender) external view returns (uint256);

36:      function approve(address spender, uint256 amount) external returns (bool);

43:      function transferFrom(address from, address to, uint256 amount) external returns (bool);

GitHub: 9, 14, 18, 24, 30, 36, 43

File: contracts/interfaces/ITokenomics.sol

8:       function effectiveBond() external pure returns (uint256);

11:      function checkpoint() external returns (bool);

30:      function reserveAmountForBondProgram(uint256 amount) external returns(bool);

51:      function serviceRegistry() external view returns (address);

GitHub: 8, 11, 30, 51

File: contracts/interfaces/IUniswapV2Pair.sol

6:       function totalSupply() external view returns (uint);

7:       function token0() external view returns (address);

8:       function token1() external view returns (address);

GitHub: 6, 7, 8

File: contracts/interfaces/IVotingEscrow.sol

8:       function getVotes(address account) external view returns (uint256);

GitHub: 8

</details>

[N‑10] Constants in comparisons should appear on the left side

Doing so will prevent typo bugs where the first '!', '>', '<', or '=' at the beginning of the operator is missing.

There are 10 instances of this issue:

File: contracts/GenericBondCalculator.sol

82:              if (token0 == olas || token1 == olas) {

82:              if (token0 == olas || token1 == olas) {

84:                  if (token0 == olas) {

GitHub: 82, 82, 84

File: contracts/Tokenomics.sol

296:         if (uint32(_epochLen) < MIN_EPOCH_LENGTH) {

301:         if (uint32(_epochLen) > ONE_YEAR) {

511:         if (uint72(_devsPerCapital) > MIN_PARAM_VALUE) {

519:         if (uint72(_codePerDev) > MIN_PARAM_VALUE) {

536:         if (uint32(_epochLen) >= MIN_EPOCH_LENGTH && uint32(_epochLen) <= ONE_YEAR) {

536:         if (uint32(_epochLen) >= MIN_EPOCH_LENGTH && uint32(_epochLen) <= ONE_YEAR) {

902:         if (diffNumSeconds < curEpochLen || diffNumSeconds > ONE_YEAR) {

GitHub: 296, 301, 511, 519, 536, 536, 902

[N‑11] Contract timekeeping will break earlier than the Ethereum network itself will stop working

When a timestamp is downcast from uint256 to uint32, the value will wrap in the year 2106, and the contracts will break. Other downcasts will have different endpoints. Consider whether your contract is intended to live past the size of the type being used.

There is one instance of this issue:

File: contracts/Tokenomics.sol

325:         uint256 zeroYearSecondsLeft = uint32(_timeLaunch + ONE_YEAR - block.timestamp);

GitHub: 325

[N‑12] Contracts/libraries/interfaces should each be defined in separate files

This helps to make tracking changes across commits easier, among other reasons. They can be combined into a single file later by importing multiple contracts, and using that file as the common import.

There are 12 instances of this issue:

File: contracts/bridges/FxGovernorTunnel.sol

/// @audit FxGovernorTunnel,IFxMessageProcessor
5:   interface IFxMessageProcessor {

/// @audit FxGovernorTunnel,IFxMessageProcessor
46:  contract FxGovernorTunnel is IFxMessageProcessor {

GitHub: 5, 46

File: contracts/bridges/HomeMediator.sol

/// @audit IAMB,HomeMediator
5:   interface IAMB {

/// @audit IAMB,HomeMediator
46:  contract HomeMediator {

GitHub: 5, 46

File: contracts/multisigs/GuardCM.sol

/// @audit GuardCM,IGovernor
6:   interface IGovernor {

/// @audit GuardCM,IGovernor
88:  contract GuardCM {

GitHub: 6, 88

File: contracts/wveOLAS.sol

/// @audit IVEOLAS,wveOLAS
13   interface IVEOLAS {
14       /// @dev Gets the total number of supply points.
15:      /// @return numPoints Number of supply points.

/// @audit IVEOLAS,wveOLAS
130  contract wveOLAS {
131:     // veOLAS address

GitHub: 13, 130

File: contracts/multisigs/GnosisSafeMultisig.sol

/// @audit IGnosisSafeProxyFactory,GnosisSafeMultisig
5    interface IGnosisSafeProxyFactory {
6        /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
7        /// @param _singleton Address of singleton contract.
8        /// @param initializer Payload for message call sent to new proxy contract.
9:       /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.

/// @audit IGnosisSafeProxyFactory,GnosisSafeMultisig
24   contract GnosisSafeMultisig {
25:      // Selector of the Gnosis Safe setup function

GitHub: 5, 24

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

/// @audit GnosisSafeSameAddressMultisig,IGnosisSafe
5    interface IGnosisSafe {
6        /// @dev Gets set of owners.
7:       /// @return Set of Safe owners.

/// @audit GnosisSafeSameAddressMultisig,IGnosisSafe
50   contract GnosisSafeSameAddressMultisig {
51       // Default data size to be parsed as an address of a Gnosis Safe multisig proxy address
52:      // This exact size suggests that all the changes to the multisig have been performed and only validation is needed

GitHub: 5, 50

[N‑13] Function state mutability can be restricted to view

Note that when overriding, mutability is allowed to be changed to be more strict than the parent function's mutability

There are 6 instances of this issue:

File: contracts/multisigs/GuardCM.sol

189:     function _verifyData(address target, bytes memory data, uint256 chainId) internal {

GitHub: 189

File: contracts/wveOLAS.sol

297:     function transfer(address, uint256) external returns (bool) {

302:     function approve(address, uint256) external returns (bool) {

307:     function transferFrom(address, address, uint256) external returns (bool) {

322      function delegate(address) external
323:     {

328      function delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32) external
329:     {

GitHub: 297, 302, 307, 322, 328

[N‑14] Inconsistent spacing in comments

Some lines use // x and some use //x. The instances below point out the usages that don't follow the majority, within each file

There are 2 instances of this issue:

File: contracts/Tokenomics.sol

873:      ///$result == true && (block.timestamp - timeLaunch) / ONE_YEAR == old(currentYear)

GitHub: 873

File: contracts/Treasury.sol

464:      ///if_succeeds {:msg "slashed funds check"} IServiceRegistry(ITokenomics(tokenomics).serviceRegistry()).slashedFunds() >= minAcceptedETH

GitHub: 464

[N‑15] Interfaces should be defined in separate files from their usage

The interfaces below should be defined in separate files, so that it's easier for future projects to import them, and to avoid duplication later on if they need to be used elsewhere in the project

There is one instance of this issue:

File: contracts/bridges/FxGovernorTunnel.sol

5:   interface IFxMessageProcessor {

GitHub: 5

[N‑16] Named imports of parent contracts are missing

There are 17 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

/// @audit ERC20
17:  contract OLAS is ERC20 {

GitHub: 17

File: contracts/Timelock.sol

/// @audit TimelockController
9:   contract Timelock is TimelockController {

GitHub: 9

File: contracts/veOLAS.sol

/// @audit IErrors
/// @audit IVotes
/// @audit IERC20
/// @audit IERC165
86:  contract veOLAS is IErrors, IVotes, IERC20, IERC165 {

GitHub: 86, 86, 86, 86

File: contracts/AgentRegistry.sol

/// @audit UnitRegistry
9:   contract AgentRegistry is UnitRegistry {

GitHub: 9

File: contracts/ComponentRegistry.sol

/// @audit UnitRegistry
8:   contract ComponentRegistry is UnitRegistry {

GitHub: 8

File: contracts/GenericManager.sol

/// @audit IErrorsRegistries
8:   abstract contract GenericManager is IErrorsRegistries {

GitHub: 8

File: contracts/GenericRegistry.sol

/// @audit IErrorsRegistries
/// @audit ERC721
9:   abstract contract GenericRegistry is IErrorsRegistries, ERC721 {

GitHub: 9, 9

File: contracts/RegistriesManager.sol

/// @audit GenericManager
9:   contract RegistriesManager is GenericManager {

GitHub: 9

File: contracts/UnitRegistry.sol

/// @audit GenericRegistry
8:   abstract contract UnitRegistry is GenericRegistry {

GitHub: 8

File: contracts/Dispenser.sol

/// @audit IErrorsTokenomics
11:  contract Dispenser is IErrorsTokenomics {

GitHub: 11

File: contracts/Tokenomics.sol

/// @audit TokenomicsConstants
/// @audit IErrorsTokenomics
118: contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {

GitHub: 118, 118

File: contracts/Treasury.sol

/// @audit IErrorsTokenomics
39:  contract Treasury is IErrorsTokenomics {

GitHub: 39

</details>

[N‑17] NatSpec: Event @param tag is missing

There are 149 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

/// @audit Missing '@param minter'
13   
14   /// @title OLAS - Smart contract for the OLAS token.
15   /// @author AL
16   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
17   contract OLAS is ERC20 {
18:      event MinterUpdated(address indexed minter);

/// @audit Missing '@param owner'
14   /// @title OLAS - Smart contract for the OLAS token.
15   /// @author AL
16   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
17   contract OLAS is ERC20 {
18       event MinterUpdated(address indexed minter);
19:      event OwnerUpdated(address indexed owner);

GitHub: 13, 14

File: contracts/bridges/BridgedERC20.sol

/// @audit Missing '@param owner'
14   
15   /// @title BridgedERC20 - Smart contract for bridged ERC20 token
16   /// @dev Bridged token contract is owned by the bridge mediator contract, and thus the token representation from
17   ///      another chain must be minted and burned solely by the bridge mediator contract.
18   contract BridgedERC20 is ERC20 {
19:      event OwnerUpdated(address indexed owner);

GitHub: 14

File: contracts/bridges/FxERC20ChildTunnel.sol

/// @audit Missing '@param childToken'
/// @audit Missing '@param rootToken'
/// @audit Missing '@param from'
/// @audit Missing '@param to'
/// @audit Missing '@param amount'
20   /// @title FxERC20ChildTunnel - Smart contract for the L2 token management part
21   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
22   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
23   /// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
24   contract FxERC20ChildTunnel is FxBaseChildTunnel {
25:      event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);

/// @audit Missing '@param rootToken'
/// @audit Missing '@param childToken'
/// @audit Missing '@param from'
/// @audit Missing '@param to'
/// @audit Missing '@param amount'
21   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
22   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
23   /// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
24   contract FxERC20ChildTunnel is FxBaseChildTunnel {
25       event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);
26:      event FxWithdrawERC20(address indexed rootToken, address indexed childToken, address from, address indexed to, uint256 amount);

GitHub: 20, 20, 20, 20, 20, 21, 21, 21, 21, 21

File: contracts/bridges/FxERC20RootTunnel.sol

/// @audit Missing '@param childToken'
/// @audit Missing '@param rootToken'
/// @audit Missing '@param from'
/// @audit Missing '@param to'
/// @audit Missing '@param amount'
20   /// @title FxERC20RootTunnel - Smart contract for the L1 token management part
21   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
22   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
23   /// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
24   contract FxERC20RootTunnel is FxBaseRootTunnel {
25:      event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);

/// @audit Missing '@param rootToken'
/// @audit Missing '@param childToken'
/// @audit Missing '@param from'
/// @audit Missing '@param to'
/// @audit Missing '@param amount'
21   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
22   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
23   /// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
24   contract FxERC20RootTunnel is FxBaseRootTunnel {
25       event FxDepositERC20(address indexed childToken, address indexed rootToken, address from, address indexed to, uint256 amount);
26:      event FxWithdrawERC20(address indexed rootToken, address indexed childToken, address from, address indexed to, uint256 amount);

GitHub: 20, 20, 20, 20, 20, 21, 21, 21, 21, 21

File: contracts/bridges/FxGovernorTunnel.sol

/// @audit Missing '@param sender'
/// @audit Missing '@param value'
42   
43   /// @title FxGovernorTunnel - Smart contract for the governor child tunnel bridge implementation
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract FxGovernorTunnel is IFxMessageProcessor {
47:      event FundsReceived(address indexed sender, uint256 value);

/// @audit Missing '@param rootMessageSender'
43   /// @title FxGovernorTunnel - Smart contract for the governor child tunnel bridge implementation
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract FxGovernorTunnel is IFxMessageProcessor {
47       event FundsReceived(address indexed sender, uint256 value);
48:      event RootGovernorUpdated(address indexed rootMessageSender);

/// @audit Missing '@param stateId'
/// @audit Missing '@param rootMessageSender'
/// @audit Missing '@param data'
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract FxGovernorTunnel is IFxMessageProcessor {
47       event FundsReceived(address indexed sender, uint256 value);
48       event RootGovernorUpdated(address indexed rootMessageSender);
49:      event MessageReceived(uint256 indexed stateId, address indexed rootMessageSender, bytes data);

GitHub: 42, 42, 43, 44, 44, 44

File: contracts/bridges/HomeMediator.sol

/// @audit Missing '@param sender'
/// @audit Missing '@param value'
42   
43   /// @title HomeMediator - Smart contract for the governor home (gnosis chain) bridge implementation
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract HomeMediator {
47:      event FundsReceived(address indexed sender, uint256 value);

/// @audit Missing '@param foreignMessageSender'
43   /// @title HomeMediator - Smart contract for the governor home (gnosis chain) bridge implementation
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract HomeMediator {
47       event FundsReceived(address indexed sender, uint256 value);
48:      event ForeignGovernorUpdated(address indexed foreignMessageSender);

/// @audit Missing '@param foreignMessageSender'
/// @audit Missing '@param data'
44   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
45   /// @author AL
46   contract HomeMediator {
47       event FundsReceived(address indexed sender, uint256 value);
48       event ForeignGovernorUpdated(address indexed foreignMessageSender);
49:      event MessageReceived(address indexed foreignMessageSender, bytes data);

GitHub: 42, 42, 43, 44, 44

File: contracts/multisigs/GuardCM.sol

/// @audit Missing '@param governor'
84   
85   /// @title GuardCM - Smart contract for Gnosis Safe community multisig (CM) guard
86   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
87   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
88   contract GuardCM {
89:      event GovernorUpdated(address indexed governor);

/// @audit Missing '@param targets'
/// @audit Missing '@param selectors'
/// @audit Missing '@param chainIds'
/// @audit Missing '@param statuses'
85   /// @title GuardCM - Smart contract for Gnosis Safe community multisig (CM) guard
86   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
87   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
88   contract GuardCM {
89       event GovernorUpdated(address indexed governor);
90:      event SetTargetSelectors(address[] indexed targets, bytes4[] indexed selectors, uint256[] chainIds, bool[] statuses);

/// @audit Missing '@param bridgeMediatorL1s'
/// @audit Missing '@param bridgeMediatorL2s'
/// @audit Missing '@param chainIds'
86   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
87   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
88   contract GuardCM {
89       event GovernorUpdated(address indexed governor);
90       event SetTargetSelectors(address[] indexed targets, bytes4[] indexed selectors, uint256[] chainIds, bool[] statuses);
91:      event SetBridgeMediators(address[] indexed bridgeMediatorL1s, address[] indexed bridgeMediatorL2s, uint256[] chainIds);

/// @audit Missing '@param proposalId'
87   /// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
88   contract GuardCM {
89       event GovernorUpdated(address indexed governor);
90       event SetTargetSelectors(address[] indexed targets, bytes4[] indexed selectors, uint256[] chainIds, bool[] statuses);
91       event SetBridgeMediators(address[] indexed bridgeMediatorL1s, address[] indexed bridgeMediatorL2s, uint256[] chainIds);
92:      event GovernorCheckProposalIdChanged(uint256 indexed proposalId);

/// @audit Missing '@param account'
88   contract GuardCM {
89       event GovernorUpdated(address indexed governor);
90       event SetTargetSelectors(address[] indexed targets, bytes4[] indexed selectors, uint256[] chainIds, bool[] statuses);
91       event SetBridgeMediators(address[] indexed bridgeMediatorL1s, address[] indexed bridgeMediatorL2s, uint256[] chainIds);
92       event GovernorCheckProposalIdChanged(uint256 indexed proposalId);
93:      event GuardPaused(address indexed account);

GitHub: 84, 85, 85, 85, 85, 86, 86, 86, 87, 88

File: contracts/veOLAS.sol

/// @audit Missing '@param account'
/// @audit Missing '@param amount'
/// @audit Missing '@param locktime'
/// @audit Missing '@param depositType'
/// @audit Missing '@param ts'
89           CREATE_LOCK_TYPE,
90           INCREASE_LOCK_AMOUNT,
91           INCREASE_UNLOCK_TIME
92       }
93   
94:      event Deposit(address indexed account, uint256 amount, uint256 locktime, DepositType depositType, uint256 ts);

/// @audit Missing '@param account'
/// @audit Missing '@param amount'
/// @audit Missing '@param ts'
90           INCREASE_LOCK_AMOUNT,
91           INCREASE_UNLOCK_TIME
92       }
93   
94       event Deposit(address indexed account, uint256 amount, uint256 locktime, DepositType depositType, uint256 ts);
95:      event Withdraw(address indexed account, uint256 amount, uint256 ts);

/// @audit Missing '@param previousSupply'
/// @audit Missing '@param currentSupply'
91           INCREASE_UNLOCK_TIME
92       }
93   
94       event Deposit(address indexed account, uint256 amount, uint256 locktime, DepositType depositType, uint256 ts);
95       event Withdraw(address indexed account, uint256 amount, uint256 ts);
96:      event Supply(uint256 previousSupply, uint256 currentSupply);

GitHub: 89, 89, 89, 89, 89, 90, 90, 90, 91, 91

File: contracts/GenericManager.sol

/// @audit Missing '@param owner'
4    import "./interfaces/IErrorsRegistries.sol";
5    
6    /// @title Generic Manager - Smart contract for generic registry manager template
7    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
8    abstract contract GenericManager is IErrorsRegistries {
9:       event OwnerUpdated(address indexed owner);

/// @audit Missing '@param owner'
5    
6    /// @title Generic Manager - Smart contract for generic registry manager template
7    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
8    abstract contract GenericManager is IErrorsRegistries {
9        event OwnerUpdated(address indexed owner);
10:      event Pause(address indexed owner);

/// @audit Missing '@param owner'
6    /// @title Generic Manager - Smart contract for generic registry manager template
7    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
8    abstract contract GenericManager is IErrorsRegistries {
9        event OwnerUpdated(address indexed owner);
10       event Pause(address indexed owner);
11:      event Unpause(address indexed owner);

GitHub: 4, 5, 6

File: contracts/GenericRegistry.sol

/// @audit Missing '@param owner'
5    import "./interfaces/IErrorsRegistries.sol";
6    
7    /// @title Generic Registry - Smart contract for generic registry template
8    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
9    abstract contract GenericRegistry is IErrorsRegistries, ERC721 {
10:      event OwnerUpdated(address indexed owner);

/// @audit Missing '@param manager'
6    
7    /// @title Generic Registry - Smart contract for generic registry template
8    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
9    abstract contract GenericRegistry is IErrorsRegistries, ERC721 {
10       event OwnerUpdated(address indexed owner);
11:      event ManagerUpdated(address indexed manager);

/// @audit Missing '@param baseURI'
7    /// @title Generic Registry - Smart contract for generic registry template
8    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
9    abstract contract GenericRegistry is IErrorsRegistries, ERC721 {
10       event OwnerUpdated(address indexed owner);
11       event ManagerUpdated(address indexed manager);
12:      event BaseURIChanged(string baseURI);

GitHub: 5, 6, 7

File: contracts/UnitRegistry.sol

/// @audit Missing '@param unitId'
/// @audit Missing '@param uType'
/// @audit Missing '@param unitHash'
4    import "./GenericRegistry.sol";
5    
6    /// @title Unit Registry - Smart contract for registering generalized units / units
7    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
8    abstract contract UnitRegistry is GenericRegistry {
9:       event CreateUnit(uint256 unitId, UnitType uType, bytes32 unitHash);

/// @audit Missing '@param unitId'
/// @audit Missing '@param uType'
/// @audit Missing '@param unitHash'
5    
6    /// @title Unit Registry - Smart contract for registering generalized units / units
7    /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
8    abstract contract UnitRegistry is GenericRegistry {
9        event CreateUnit(uint256 unitId, UnitType uType, bytes32 unitHash);
10:      event UpdateUnitHash(uint256 unitId, UnitType uType, bytes32 unitHash);

GitHub: 4, 4, 4, 5, 5, 5

File: contracts/Depository.sol

/// @audit Missing '@param owner'
58   
59   /// @title Bond Depository - Smart contract for OLAS Bond Depository
60   /// @author AL
61   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
62   contract Depository is IErrorsTokenomics {
63:      event OwnerUpdated(address indexed owner);

/// @audit Missing '@param tokenomics'
59   /// @title Bond Depository - Smart contract for OLAS Bond Depository
60   /// @author AL
61   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
62   contract Depository is IErrorsTokenomics {
63       event OwnerUpdated(address indexed owner);
64:      event TokenomicsUpdated(address indexed tokenomics);

/// @audit Missing '@param treasury'
60   /// @author AL
61   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
62   contract Depository is IErrorsTokenomics {
63       event OwnerUpdated(address indexed owner);
64       event TokenomicsUpdated(address indexed tokenomics);
65:      event TreasuryUpdated(address indexed treasury);

/// @audit Missing '@param bondCalculator'
61   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
62   contract Depository is IErrorsTokenomics {
63       event OwnerUpdated(address indexed owner);
64       event TokenomicsUpdated(address indexed tokenomics);
65       event TreasuryUpdated(address indexed treasury);
66:      event BondCalculatorUpdated(address indexed bondCalculator);

/// @audit Missing '@param token'
/// @audit Missing '@param productId'
/// @audit Missing '@param owner'
/// @audit Missing '@param bondId'
62   contract Depository is IErrorsTokenomics {
63       event OwnerUpdated(address indexed owner);
64       event TokenomicsUpdated(address indexed tokenomics);
65       event TreasuryUpdated(address indexed treasury);
66       event BondCalculatorUpdated(address indexed bondCalculator);
67:      event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,

/// @audit Missing '@param amountOLAS'
/// @audit Missing '@param tokenAmount'
/// @audit Missing '@param maturity'
63       event OwnerUpdated(address indexed owner);
64       event TokenomicsUpdated(address indexed tokenomics);
65       event TreasuryUpdated(address indexed treasury);
66       event BondCalculatorUpdated(address indexed bondCalculator);
67       event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,
68:          uint256 amountOLAS, uint256 tokenAmount, uint256 maturity);

/// @audit Missing '@param productId'
/// @audit Missing '@param owner'
/// @audit Missing '@param bondId'
64       event TokenomicsUpdated(address indexed tokenomics);
65       event TreasuryUpdated(address indexed treasury);
66       event BondCalculatorUpdated(address indexed bondCalculator);
67       event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,
68           uint256 amountOLAS, uint256 tokenAmount, uint256 maturity);
69:      event RedeemBond(uint256 indexed productId, address indexed owner, uint256 bondId);

/// @audit Missing '@param token'
/// @audit Missing '@param productId'
/// @audit Missing '@param supply'
/// @audit Missing '@param priceLP'
65       event TreasuryUpdated(address indexed treasury);
66       event BondCalculatorUpdated(address indexed bondCalculator);
67       event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,
68           uint256 amountOLAS, uint256 tokenAmount, uint256 maturity);
69       event RedeemBond(uint256 indexed productId, address indexed owner, uint256 bondId);
70:      event CreateProduct(address indexed token, uint256 indexed productId, uint256 supply, uint256 priceLP,

/// @audit Missing '@param vesting'
66       event BondCalculatorUpdated(address indexed bondCalculator);
67       event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,
68           uint256 amountOLAS, uint256 tokenAmount, uint256 maturity);
69       event RedeemBond(uint256 indexed productId, address indexed owner, uint256 bondId);
70       event CreateProduct(address indexed token, uint256 indexed productId, uint256 supply, uint256 priceLP,
71:          uint256 vesting);

/// @audit Missing '@param token'
/// @audit Missing '@param productId'
/// @audit Missing '@param supply'
67       event CreateBond(address indexed token, uint256 indexed productId, address indexed owner, uint256 bondId,
68           uint256 amountOLAS, uint256 tokenAmount, uint256 maturity);
69       event RedeemBond(uint256 indexed productId, address indexed owner, uint256 bondId);
70       event CreateProduct(address indexed token, uint256 indexed productId, uint256 supply, uint256 priceLP,
71           uint256 vesting);
72:      event CloseProduct(address indexed token, uint256 indexed productId, uint256 supply);

GitHub: 58, 59, 60, 61, 62, 62, 62, 62, 63, 63, 63, 64, 64, 64, 65, 65, 65, 65, 66, 67, 67, 67

File: contracts/Dispenser.sol

/// @audit Missing '@param owner'
7    
8    /// @title Dispenser - Smart contract for distributing incentives
9    /// @author AL
10   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
11   contract Dispenser is IErrorsTokenomics {
12:      event OwnerUpdated(address indexed owner);

/// @audit Missing '@param tokenomics'
8    /// @title Dispenser - Smart contract for distributing incentives
9    /// @author AL
10   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
11   contract Dispenser is IErrorsTokenomics {
12       event OwnerUpdated(address indexed owner);
13:      event TokenomicsUpdated(address indexed tokenomics);

/// @audit Missing '@param treasury'
9    /// @author AL
10   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
11   contract Dispenser is IErrorsTokenomics {
12       event OwnerUpdated(address indexed owner);
13       event TokenomicsUpdated(address indexed tokenomics);
14:      event TreasuryUpdated(address indexed treasury);

/// @audit Missing '@param owner'
/// @audit Missing '@param reward'
/// @audit Missing '@param topUp'
10   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
11   contract Dispenser is IErrorsTokenomics {
12       event OwnerUpdated(address indexed owner);
13       event TokenomicsUpdated(address indexed tokenomics);
14       event TreasuryUpdated(address indexed treasury);
15:      event IncentivesClaimed(address indexed owner, uint256 reward, uint256 topUp);

GitHub: 7, 8, 9, 10, 10, 10

File: contracts/DonatorBlacklist.sol

/// @audit Missing '@param owner'
16   
17   /// @title DonatorBlacklist - Smart contract for donator address blacklisting
18   /// @author AL
19   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
20   contract DonatorBlacklist {
21:      event OwnerUpdated(address indexed owner);

/// @audit Missing '@param account'
/// @audit Missing '@param status'
17   /// @title DonatorBlacklist - Smart contract for donator address blacklisting
18   /// @author AL
19   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
20   contract DonatorBlacklist {
21       event OwnerUpdated(address indexed owner);
22:      event DonatorBlacklistStatus(address indexed account, bool status);

GitHub: 16, 17, 17

File: contracts/Tokenomics.sol

/// @audit Missing '@param owner'
114  
115  /// @title Tokenomics - Smart contract for tokenomics logic with incentives for unit owners and discount factor regulations for bonds.
116  /// @author AL
117  /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
118  contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {
119:     event OwnerUpdated(address indexed owner);

/// @audit Missing '@param treasury'
115  /// @title Tokenomics - Smart contract for tokenomics logic with incentives for unit owners and discount factor regulations for bonds.
116  /// @author AL
117  /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
118  contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {
119      event OwnerUpdated(address indexed owner);
120:     event TreasuryUpdated(address indexed treasury);

/// @audit Missing '@param depository'
116  /// @author AL
117  /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
118  contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {
119      event OwnerUpdated(address indexed owner);
120      event TreasuryUpdated(address indexed treasury);
121:     event DepositoryUpdated(address indexed depository);

/// @audit Missing '@param dispenser'
117  /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
118  contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {
119      event OwnerUpdated(address indexed owner);
120      event TreasuryUpdated(address indexed treasury);
121      event DepositoryUpdated(address indexed depository);
122:     event DispenserUpdated(address indexed dispenser);

/// @audit Missing '@param epochLen'
118  contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {
119      event OwnerUpdated(address indexed owner);
120      event TreasuryUpdated(address indexed treasury);
121      event DepositoryUpdated(address indexed depository);
122      event DispenserUpdated(address indexed dispenser);
123:     event EpochLengthUpdated(uint256 epochLen);

/// @audit Missing '@param effectiveBond'
119      event OwnerUpdated(address indexed owner);
120      event TreasuryUpdated(address indexed treasury);
121      event DepositoryUpdated(address indexed depository);
122      event DispenserUpdated(address indexed dispenser);
123      event EpochLengthUpdated(uint256 epochLen);
124:     event EffectiveBondUpdated(uint256 effectiveBond);

/// @audit Missing '@param idf'
120      event TreasuryUpdated(address indexed treasury);
121      event DepositoryUpdated(address indexed depository);
122      event DispenserUpdated(address indexed dispenser);
123      event EpochLengthUpdated(uint256 epochLen);
124      event EffectiveBondUpdated(uint256 effectiveBond);
125:     event IDFUpdated(uint256 idf);

/// @audit Missing '@param epochNumber'
/// @audit Missing '@param devsPerCapital'
/// @audit Missing '@param codePerDev'
121      event DepositoryUpdated(address indexed depository);
122      event DispenserUpdated(address indexed dispenser);
123      event EpochLengthUpdated(uint256 epochLen);
124      event EffectiveBondUpdated(uint256 effectiveBond);
125      event IDFUpdated(uint256 idf);
126:     event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,

/// @audit Missing '@param epsilonRate'
/// @audit Missing '@param epochLen'
/// @audit Missing '@param veOLASThreshold'
122      event DispenserUpdated(address indexed dispenser);
123      event EpochLengthUpdated(uint256 epochLen);
124      event EffectiveBondUpdated(uint256 effectiveBond);
125      event IDFUpdated(uint256 idf);
126      event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
127:         uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);

/// @audit Missing '@param epochNumber'
123      event EpochLengthUpdated(uint256 epochLen);
124      event EffectiveBondUpdated(uint256 effectiveBond);
125      event IDFUpdated(uint256 idf);
126      event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
127          uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
128:     event TokenomicsParametersUpdated(uint256 indexed epochNumber);

/// @audit Missing '@param epochNumber'
/// @audit Missing '@param rewardComponentFraction'
124      event EffectiveBondUpdated(uint256 effectiveBond);
125      event IDFUpdated(uint256 idf);
126      event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
127          uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
128      event TokenomicsParametersUpdated(uint256 indexed epochNumber);
129:     event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,

/// @audit Missing '@param rewardAgentFraction'
/// @audit Missing '@param maxBondFraction'
/// @audit Missing '@param topUpComponentFraction'
/// @audit Missing '@param topUpAgentFraction'
125      event IDFUpdated(uint256 idf);
126      event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
127          uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
128      event TokenomicsParametersUpdated(uint256 indexed epochNumber);
129      event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
130:         uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);

/// @audit Missing '@param epochNumber'
126      event TokenomicsParametersUpdateRequested(uint256 indexed epochNumber, uint256 devsPerCapital, uint256 codePerDev,
127          uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
128      event TokenomicsParametersUpdated(uint256 indexed epochNumber);
129      event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
130          uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);
131:     event IncentiveFractionsUpdated(uint256 indexed epochNumber);

/// @audit Missing '@param componentRegistry'
127          uint256 epsilonRate, uint256 epochLen, uint256 veOLASThreshold);
128      event TokenomicsParametersUpdated(uint256 indexed epochNumber);
129      event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
130          uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);
131      event IncentiveFractionsUpdated(uint256 indexed epochNumber);
132:     event ComponentRegistryUpdated(address indexed componentRegistry);

/// @audit Missing '@param agentRegistry'
128      event TokenomicsParametersUpdated(uint256 indexed epochNumber);
129      event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
130          uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);
131      event IncentiveFractionsUpdated(uint256 indexed epochNumber);
132      event ComponentRegistryUpdated(address indexed componentRegistry);
133:     event AgentRegistryUpdated(address indexed agentRegistry);

/// @audit Missing '@param serviceRegistry'
129      event IncentiveFractionsUpdateRequested(uint256 indexed epochNumber, uint256 rewardComponentFraction,
130          uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);
131      event IncentiveFractionsUpdated(uint256 indexed epochNumber);
132      event ComponentRegistryUpdated(address indexed componentRegistry);
133      event AgentRegistryUpdated(address indexed agentRegistry);
134:     event ServiceRegistryUpdated(address indexed serviceRegistry);

/// @audit Missing '@param blacklist'
130          uint256 rewardAgentFraction, uint256 maxBondFraction, uint256 topUpComponentFraction, uint256 topUpAgentFraction);
131      event IncentiveFractionsUpdated(uint256 indexed epochNumber);
132      event ComponentRegistryUpdated(address indexed componentRegistry);
133      event AgentRegistryUpdated(address indexed agentRegistry);
134      event ServiceRegistryUpdated(address indexed serviceRegistry);
135:     event DonatorBlacklistUpdated(address indexed blacklist);

/// @audit Missing '@param epochCounter'
/// @audit Missing '@param treasuryRewards'
/// @audit Missing '@param accountRewards'
/// @audit Missing '@param accountTopUps'
131      event IncentiveFractionsUpdated(uint256 indexed epochNumber);
132      event ComponentRegistryUpdated(address indexed componentRegistry);
133      event AgentRegistryUpdated(address indexed agentRegistry);
134      event ServiceRegistryUpdated(address indexed serviceRegistry);
135      event DonatorBlacklistUpdated(address indexed blacklist);
136:     event EpochSettled(uint256 indexed epochCounter, uint256 treasuryRewards, uint256 accountRewards, uint256 accountTopUps);

/// @audit Missing '@param implementation'
132      event ComponentRegistryUpdated(address indexed componentRegistry);
133      event AgentRegistryUpdated(address indexed agentRegistry);
134      event ServiceRegistryUpdated(address indexed serviceRegistry);
135      event DonatorBlacklistUpdated(address indexed blacklist);
136      event EpochSettled(uint256 indexed epochCounter, uint256 treasuryRewards, uint256 accountRewards, uint256 accountTopUps);
137:     event TokenomicsImplementationUpdated(address indexed implementation);

GitHub: 114, 115, 116, 117, 118, 119, 120, 121, 121, 121, 122, 122, 122, 123, 124, 124, 125, 125, 125, 125, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132

File: contracts/Treasury.sol

/// @audit Missing '@param owner'
35   /// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
36   /// Invariant does not support a failing call() function while transferring ETH when using the CEI pattern:
37   /// revert TransferFailed(address(0), address(this), to, tokenAmount);
38   /// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
39   contract Treasury is IErrorsTokenomics {
40:      event OwnerUpdated(address indexed owner);

/// @audit Missing '@param tokenomics'
36   /// Invariant does not support a failing call() function while transferring ETH when using the CEI pattern:
37   /// revert TransferFailed(address(0), address(this), to, tokenAmount);
38   /// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
39   contract Treasury is IErrorsTokenomics {
40       event OwnerUpdated(address indexed owner);
41:      event TokenomicsUpdated(address indexed tokenomics);

/// @audit Missing '@param depository'
37   /// revert TransferFailed(address(0), address(this), to, tokenAmount);
38   /// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
39   contract Treasury is IErrorsTokenomics {
40       event OwnerUpdated(address indexed owner);
41       event TokenomicsUpdated(address indexed tokenomics);
42:      event DepositoryUpdated(address indexed depository);

/// @audit Missing '@param dispenser'
38   /// invariant {:msg "broken conservation law"} address(this).balance == ETHFromServices + ETHOwned;
39   contract Treasury is IErrorsTokenomics {
40       event OwnerUpdated(address indexed owner);
41       event TokenomicsUpdated(address indexed tokenomics);
42       event DepositoryUpdated(address indexed depository);
43:      event DispenserUpdated(address indexed dispenser);

/// @audit Missing '@param account'
/// @audit Missing '@param token'
/// @audit Missing '@param tokenAmount'
/// @audit Missing '@param olasAmount'
39   contract Treasury is IErrorsTokenomics {
40       event OwnerUpdated(address indexed owner);
41       event TokenomicsUpdated(address indexed tokenomics);
42       event DepositoryUpdated(address indexed depository);
43       event DispenserUpdated(address indexed dispenser);
44:      event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);

/// @audit Missing '@param sender'
/// @audit Missing '@param serviceIds'
/// @audit Missing '@param amounts'
/// @audit Missing '@param donation'
40       event OwnerUpdated(address indexed owner);
41       event TokenomicsUpdated(address indexed tokenomics);
42       event DepositoryUpdated(address indexed depository);
43       event DispenserUpdated(address indexed dispenser);
44       event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
45:      event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);

/// @audit Missing '@param token'
/// @audit Missing '@param to'
/// @audit Missing '@param tokenAmount'
41       event TokenomicsUpdated(address indexed tokenomics);
42       event DepositoryUpdated(address indexed depository);
43       event DispenserUpdated(address indexed dispenser);
44       event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
45       event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
46:      event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);

/// @audit Missing '@param token'
42       event DepositoryUpdated(address indexed depository);
43       event DispenserUpdated(address indexed dispenser);
44       event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
45       event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
46       event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
47:      event EnableToken(address indexed token);

/// @audit Missing '@param token'
43       event DispenserUpdated(address indexed dispenser);
44       event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
45       event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
46       event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
47       event EnableToken(address indexed token);
48:      event DisableToken(address indexed token);

/// @audit Missing '@param sender'
/// @audit Missing '@param amount'
44       event DepositTokenFromAccount(address indexed account, address indexed token, uint256 tokenAmount, uint256 olasAmount);
45       event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
46       event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
47       event EnableToken(address indexed token);
48       event DisableToken(address indexed token);
49:      event ReceiveETH(address indexed sender, uint256 amount);

/// @audit Missing '@param ETHOwned'
/// @audit Missing '@param ETHFromServices'
45       event DonateToServicesETH(address indexed sender, uint256[] serviceIds, uint256[] amounts, uint256 donation);
46       event Withdraw(address indexed token, address indexed to, uint256 tokenAmount);
47       event EnableToken(address indexed token);
48       event DisableToken(address indexed token);
49       event ReceiveETH(address indexed sender, uint256 amount);
50:      event UpdateTreasuryBalances(uint256 ETHOwned, uint256 ETHFromServices);

/// @audit Missing '@param amount'
48       event DisableToken(address indexed token);
49       event ReceiveETH(address indexed sender, uint256 amount);
50       event UpdateTreasuryBalances(uint256 ETHOwned, uint256 ETHFromServices);
51       event PauseTreasury();
52       event UnpauseTreasury();
53:      event MinAcceptedETHUpdated(uint256 amount);

GitHub: 35, 36, 37, 38, 39, 39, 39, 39, 40, 40, 40, 40, 41, 41, 41, 42, 43, 44, 44, 45, 45, 48

</details>

[N‑18] NatSpec: Function @param tag is missing

There is one instance of this issue:

File: contracts/GovernorOLAS.sol

/// @audit Missing '@param quorumFraction'
17           IVotes governanceToken,
18           TimelockController timelock,
19           uint256 initialVotingDelay,
20           uint256 initialVotingPeriod,
21           uint256 initialProposalThreshold,
22:          uint256 quorumFraction

GitHub: 17

[N‑19] NatSpec: Function @return tag is missing

There are 8 instances of this issue:

File: contracts/bridges/HomeMediator.sol

/// @audit Missing '@return  '
6:       function messageSender() external view returns (address);

GitHub: 6

File: contracts/multisigs/GuardCM.sol

/// @audit Missing '@return  '
7:       function state(uint256 proposalId) external returns (ProposalState);

GitHub: 7

File: contracts/interfaces/IUniswapV2Pair.sol

/// @audit Missing '@return  '
6:       function totalSupply() external view returns (uint);

/// @audit Missing '@return  '
7:       function token0() external view returns (address);

/// @audit Missing '@return  '
8:       function token1() external view returns (address);

/// @audit Missing '@return reserve0'
/// @audit Missing '@return reserve1'
/// @audit Missing '@return blockTimestampLast'
9:       function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);

GitHub: 6, 7, 8, 9, 9, 9

[N‑20] Not using the named return variables anywhere in the function is confusing

Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.

There are 4 instances of this issue:

File: contracts/UnitRegistry.sol

/// @audit numDependencies
/// @audit dependencies
160      function getDependencies(uint256 unitId) external view virtual
161          returns (uint256 numDependencies, uint32[] memory dependencies)
162:     {

/// @audit numHashes
171      function getUpdatedHashes(uint256 unitId) external view virtual
172          returns (uint256 numHashes, bytes32[] memory unitHashes)
173:     {

GitHub: 160, 160, 171

File: contracts/Depository.sol

/// @audit priceLP
491:     function getCurrentPriceLP(address token) external view returns (uint256 priceLP) {

GitHub: 491

[N‑21] Style guide: Local and state variables should be named using lowerCamelCase

The Solidity style guide says to use mixedCase for function argument names

There is one instance of this issue:

File: contracts/bridges/HomeMediator.sol

62:      constructor(address _AMBContractProxyHome, address _foreignGovernor) {

GitHub: 62

[N‑22] Style guide: Non-external/public variable names should begin with an underscore

According to the Solidity Style Guide, non-external/public variable names should begin with an underscore

There are 3 instances of this issue:

File: contracts/veOLAS.sol

99:      uint64 internal constant WEEK = 1 weeks;

101:     uint256 internal constant MAXTIME = 4 * 365 * 86400;

103:     int128 internal constant IMAXTIME = 4 * 365 * 86400;

GitHub: 99, 101, 103

[N‑23] Style guide: Top-level declarations should be separated by at least two lines

And functions within contracts should be separate by a single line

There are 2 instances of this issue:

File: contracts/multisigs/GuardCM.sol

4     import {Enum} from "@gnosis.pm/safe-contracts/contracts/common/Enum.sol";
5     
6:    interface IGovernor {

GitHub: 4

File: contracts/wveOLAS.sol

11    }
12    
13:   interface IVEOLAS {

GitHub: 11

[N‑24] Unnecessary cast

The variable is being cast to its own type

There are 3 instances of this issue:

File: contracts/veOLAS.sol

/// @audit uint256
/// @audit uint256
225:             block_slope = (1e18 * uint256(block.number - lastPoint.blockNumber)) / uint256(block.timestamp - lastPoint.ts);

GitHub: 225, 225

File: contracts/UnitRegistry.sol

/// @audit uint256
185:         subComponentIds = mapSubComponents[uint256(unitId)];

GitHub: 185

[N‑25] Unused error definition

Note that there may be cases where an error superficially appears to be used, but this is only because there are multiple definitions of the error in different files. In such cases, the error definition should be moved into a separate file. The instances below are the unused definitions.

There are 9 instances of this issue:

File: contracts/interfaces/IErrors.sol

9:       error OwnerOnly(address sender, address owner);

18:      error NonZeroValue();

23:      error WrongArrayLength(uint256 numValues1, uint256 numValues2);

41:      error InsufficientAllowance(uint256 provided, uint256 expected);

GitHub: 9, 18, 23, 41

File: contracts/interfaces/IErrorsRegistries.sol

29:      error WrongArrayLength(uint256 numValues1, uint256 numValues2);

43:      error WrongThreshold(uint256 currentThreshold, uint256 minThreshold, uint256 maxThreshold);

95:      error UnauthorizedMultisig(address multisig);

114:     error TransferFailed(address token, address from, address to, uint256 value);

GitHub: 29, 43, 95, 114

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

19:  error ZeroAddress();

GitHub: 19

[N‑26] Unused public contract variable

Note that there may be cases where a variable superficially appears to be used, but this is only because there are multiple definitions of the variable in different files. In such cases, the variable definition should be moved into a separate file. The instances below are the unused variables.

There are 9 instances of this issue:

File: contracts/AgentRegistry.sol

13:      string public constant VERSION = "1.0.0";

GitHub: 13

File: contracts/ComponentRegistry.sol

10:      string public constant VERSION = "1.0.0";

GitHub: 10

File: contracts/Depository.sol

77:      string public constant VERSION = "1.0.1";

GitHub: 77

File: contracts/Tokenomics.sol

217:     mapping(uint256 => uint256) public mapServiceAmounts;

219:     mapping(address => uint256) public mapOwnerRewards;

221:     mapping(address => uint256) public mapOwnerTopUps;

GitHub: 217, 219, 221

File: contracts/TokenomicsConstants.sol

11:      string public constant VERSION = "1.0.1";

14:      bytes32 public constant PROXY_TOKENOMICS = 0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f;

GitHub: 11, 14

File: contracts/TokenomicsProxy.sol

28:      bytes32 public constant PROXY_TOKENOMICS = 0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f;

GitHub: 28

[N‑27] Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning

Starting with version 0.8.4, Solidity has the bytes.concat() function, which allows one to concatenate a list of bytes/strings, without extra padding. Using this function rather than abi.encodePacked() makes the intended operation more clear, leading to less reviewer confusion.

There is one instance of this issue:

File: contracts/GenericRegistry.sol

139          return string(abi.encodePacked(baseURI, CID_PREFIX, _toHex16(bytes16(unitHash)),
140:             _toHex16(bytes16(unitHash << 128))));

GitHub: 139

[N‑28] Use bit shifts to represent powers of two

Use bit shifts in an immutable variable rather than long hex strings for powers of two, for readability (e.g. 0x1000000000000000000000000 -> 1 << 96)

There are 7 instances of this issue:

File: contracts/Tokenomics.sol

550:         tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x01;

599:         tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x02;

942:             tokenomicsParametersUpdated = tokenomicsParametersUpdated | 0x04;

973:         if (tokenomicsParametersUpdated & 0x02 == 0x02) {

973:         if (tokenomicsParametersUpdated & 0x02 == 0x02) {

987:         if (tokenomicsParametersUpdated & 0x01 == 0x01) {

987:         if (tokenomicsParametersUpdated & 0x01 == 0x01) {

GitHub: 550, 599, 942, 973, 973, 987, 987

[N‑29] Use of override is unnecessary

Starting with Solidity version 0.8.8, using the override keyword when the function solely overrides an interface function, and the function doesn't exist in multiple base contracts, is unnecessary.

There are 14 instances of this issue:

File: contracts/GovernorOLAS.sol

33       function state(uint256 proposalId) public view override(IGovernor, Governor, GovernorTimelockControl)
34           returns (ProposalState)
35:      {

45       function propose(
46           address[] memory targets,
47           uint256[] memory values,
48           bytes[] memory calldatas,
49           string memory description
50       ) public override(IGovernor, Governor, GovernorCompatibilityBravo) returns (uint256)
51:      {

57       function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256)
58:      {

68       function _execute(
69           uint256 proposalId,
70           address[] memory targets,
71           uint256[] memory values,
72           bytes[] memory calldatas,
73           bytes32 descriptionHash
74       ) internal override(Governor, GovernorTimelockControl)
75:      {

85       function _cancel(
86           address[] memory targets,
87           uint256[] memory values,
88           bytes[] memory calldatas,
89           bytes32 descriptionHash
90       ) internal override(Governor, GovernorTimelockControl) returns (uint256)
91:      {

97       function _executor() internal view override(Governor, GovernorTimelockControl) returns (address)
98:      {

105      function supportsInterface(bytes4 interfaceId) public view override(IERC165, Governor, GovernorTimelockControl)
106          returns (bool)
107:     {

GitHub: 33, 45, 57, 68, 85, 97, 105

File: contracts/bridges/FxGovernorTunnel.sol

107:     function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes memory data) external override {

GitHub: 107

File: contracts/veOLAS.sol

607:     function balanceOf(address account) public view override returns (uint256 balance) {

719:     function totalSupply() public view override returns (uint256) {

767:     function transfer(address to, uint256 amount) external virtual override returns (bool) {

772:     function approve(address spender, uint256 amount) external virtual override returns (bool) {

777:     function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool) {

782      function allowance(address owner, address spender) external view virtual override returns (uint256)
783:     {

GitHub: 607, 719, 767, 772, 777, 782

#0 - thebrittfactor

2024-01-02T16:44:42Z

Content submitted prior to audit close.

#1 - c4-pre-sort

2024-01-10T14:44:41Z

alex-ppg marked the issue as sufficient quality report

#2 - c4-judge

2024-01-18T21:54:24Z

dmvt marked the issue as grade-a

#3 - c4-judge

2024-01-20T18:33:46Z

dmvt marked the issue as selected for report

#4 - c4-sponsor

2024-01-22T18:13:39Z

kupermind (sponsor) disputed

#5 - kupermind

2024-01-22T18:14:37Z

@c4-judge This report contains no issue and no valuable insights. Therefore we suggest not having this into the final report.

Findings Information

🌟 Selected for report: 0xAnah

Also found by: 0x11singh99, 0xA5DF, IllIllI, JCK, Raihan, Sathish9098, alphacipher, c3phas, hunter_w3b, naman1778

Labels

bug
G (Gas Optimization)
grade-a
sufficient quality report
G-10

Awards

440.995 USDC - $441.00

External Links

Note: I've removed the instances reported by the winning bot and 4naly3er

Summary

Gas Optimizations

IssueInstancesTotal Gas Saved
[G‑01]Argument validation should be at the top of the function/block41-
[G‑02]Mappings are cheaper to use than storage arrays36300
[G‑03]State variables only set in the constructor should be declared immutable12097
[G‑04]Emitting indexed constants wastes gas103750
[G‑05]Assembly: Use scratch space for building calldata398580
[G‑06]Use += for arrays4796
[G‑07]Assembly: Avoid fetching a low-level call's return data by using assembly2318
[G‑08]Avoid transferring amounts of zero in order to save gas2200
[G‑09]Combine mappings referenced in the same function by the same key7294
[G‑10]Functions guaranteed to revert when called by normal users can be marked payable561176
[G‑11]internal functions only called once can be inlined to save gas240
[G‑12]Assembly: Use scratch space when building emitted events with two data arguments10150
[G‑13]>= costs less gas than >51153
[G‑14]Consider using ERC721A over ERC7213-
[G‑15]Consider using solady's token contracts to save gas3-
[G‑16]Consider using solady's FixedPointMathLib23-
[G‑17]Initializers can be marked payable1-
[G‑18]Memory-safe annotation missing6-
[G‑19]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct4-
[G‑20]Multiple accesses of a memory/calldata array should use a local variable cache84-
[G‑21]Multiple accesses of a storage array should use a local variable cache284
[G‑22]Reduce deployment costs by tweaking contracts' metadata23-
[G‑23]Stack variable is only used once3296
[G‑24]Split revert checks to save gas1938
[G‑25]Update OpenZeppelin dependency to save gas8-
[G‑26]Use short-circuit evaluation to avoid external calls1-

Total: 437 instances over 26 issues with 24072 gas saved

Gas totals are estimates based on data from the Ethereum Yellowpaper. The estimates use the lower bounds of ranges and count two iterations of each for-loop. All values above are runtime, not deployment, values; deployment values are listed in the individual issue descriptions. The table above as well as its gas numbers do not include any of the excluded findings.

Gas Optimizations

[G‑01] Argument validation should be at the top of the function/block

Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.

There are 41 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

48           if (newOwner == address(0)) {
49               revert ZeroAddress();
50:          }

63           if (newMinter == address(0)) {
64               revert ZeroAddress();
65:          }

GitHub: 48, 63

File: contracts/bridges/BridgedERC20.sol

37           if (newOwner == address(0)) {
38               revert ZeroAddress();
39:          }

GitHub: 37

File: contracts/veOLAS.sol

379          if (amount == 0) {
380              revert ZeroValue();
381:         }

392          if (amount > type(uint96).max) {
393              revert Overflow(amount, type(uint96).max);
394:         }

449          if (amount > type(uint96).max) {
450              revert Overflow(amount, type(uint96).max);
451:         }

461          if (amount == 0) {
462              revert ZeroValue();
463:         }

474          if (amount > type(uint96).max) {
475              revert Overflow(amount, type(uint96).max);
476:         }

GitHub: 379, 392, 449, 461, 474

File: contracts/GenericManager.sol

27           if (newOwner == address(0)) {
28               revert ZeroAddress();
29:          }

GitHub: 27

File: contracts/GenericRegistry.sol

44           if (newOwner == address(0)) {
45               revert ZeroAddress();
46:          }

60           if (newManager == address(0)) {
61               revert ZeroAddress();
62:          }

85           if (bytes(bURI).length == 0) {
86               revert ZeroValue();
87:          }

GitHub: 44, 60, 85

File: contracts/UnitRegistry.sol

64           if(unitOwner == address(0)) {
65               revert ZeroAddress();
66:          }

69           if (unitHash == 0) {
70               revert ZeroValue();
71:          }

139          if (unitHash == 0) {
140              revert ZeroValue();
141:         }

GitHub: 64, 69, 139

File: contracts/Depository.sol

111          if (_olas == address(0) || _tokenomics == address(0) || _treasury == address(0) || _bondCalculator == address(0)) {
112              revert ZeroAddress();
113:         }

130          if (newOwner == address(0)) {
131              revert ZeroAddress();
132:         }

190          if (priceLP == 0) {
191              revert ZeroValue();
192:         }

195          if (priceLP > type(uint160).max) {
196              revert Overflow(priceLP, type(uint160).max);
197:         }

200          if (supply == 0) {
201              revert ZeroValue();
202:         }

205          if (supply > type(uint96).max) {
206              revert Overflow(supply, type(uint96).max);
207:         }

210          if (vesting < MIN_VESTING) {
211              revert LowerThan(vesting, MIN_VESTING);
212:         }

GitHub: 111, 130, 190, 195, 200, 205, 210

File: contracts/Dispenser.sol

36           if (_tokenomics == address(0) || _treasury == address(0)) {
37               revert ZeroAddress();
38:          }

53           if (newOwner == address(0)) {
54               revert ZeroAddress();
55:          }

GitHub: 36, 53

File: contracts/DonatorBlacklist.sol

43           if (newOwner == address(0)) {
44               revert ZeroAddress();
45:          }

63           if (accounts.length != statuses.length) {
64               revert WrongArrayLength(accounts.length, statuses.length);
65:          }

GitHub: 43, 63

File: contracts/Tokenomics.sol

283          if (_olas == address(0) || _treasury == address(0) || _depository == address(0) || _dispenser == address(0) ||
284              _ve == address(0) || _componentRegistry == address(0) || _agentRegistry == address(0) ||
285              _serviceRegistry == address(0)) {
286              revert ZeroAddress();
287:         }

296          if (uint32(_epochLen) < MIN_EPOCH_LENGTH) {
297              revert LowerThan(_epochLen, MIN_EPOCH_LENGTH);
298:         }

301          if (uint32(_epochLen) > ONE_YEAR) {
302              revert Overflow(_epochLen, ONE_YEAR);
303:         }

391          if (implementation == address(0)) {
392              revert ZeroAddress();
393:         }

411          if (newOwner == address(0)) {
412              revert ZeroAddress();
413:         }

576          if (_rewardComponentFraction + _rewardAgentFraction > 100) {
577              revert WrongAmount(_rewardComponentFraction + _rewardAgentFraction, 100);
578:         }

581          if (_maxBondFraction + _topUpComponentFraction + _topUpAgentFraction > 100) {
582              revert WrongAmount(_maxBondFraction + _topUpComponentFraction + _topUpAgentFraction, 100);
583:         }

1094         if (unitTypes.length != unitIds.length) {
1095             revert WrongArrayLength(unitTypes.length, unitIds.length);
1096:        }

GitHub: 283, 296, 301, 391, 411, 576, 581, 1094

File: contracts/Treasury.sol

100          if (_olas == address(0) || _tokenomics == address(0) || _depository == address(0) || _dispenser == address(0)) {
101              revert ZeroAddress();
102:         }

144          if (newOwner == address(0)) {
145              revert ZeroAddress();
146:         }

189          if (_minAcceptedETH == 0) {
190              revert ZeroValue();
191:         }

194          if (_minAcceptedETH > type(uint96).max) {
195              revert Overflow(_minAcceptedETH, type(uint96).max);
196:         }

320          if (to == address(this)) {
321              revert TransferFailed(token, address(this), to, tokenAmount);
322:         }

325          if (tokenAmount == 0) {
326              revert ZeroValue();
327:         }

494          if (token == address(0)) {
495              revert ZeroAddress();
496:         }

GitHub: 100, 144, 189, 194, 320, 325, 494

</details>

[G‑02] Mappings are cheaper to use than storage arrays

When using storage arrays, solidity adds an internal lookup of the array's length (a Gcoldsload 2100 gas) to ensure you don't read past the array's end. You can avoid this lookup by using a mapping and storing the number of entries in a separate storage variable. In cases where you have sentinel values (e.g. 'zero' means invalid), you can avoid length checks

There are 3 instances of this issue:

File: contracts/veOLAS.sol

119:     mapping(address => PointVoting[]) public mapUserPoints;

GitHub: 119

File: contracts/UnitRegistry.sol

29:      mapping(uint256 => bytes32[]) public mapUnitIdHashes;

31:      mapping(uint256 => uint32[]) public mapSubComponents;

GitHub: 29, 31

[G‑03] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.

There is one instance of this issue:

File: contracts/Treasury.sol

65:      address public olas;

GitHub: 65

[G‑04] Emitting indexed constants wastes gas

Every indexed event parameter adds an extra Glogtopic (375 gas). You can avoid this extra cost, in cases where you're emitting a constant, by creating a second version of the event, which doesn't have the indexed parameter (and have users look to the contract's variables for its value instead), and using the new event in the cases shown below.

There are 10 instances of this issue:

File: contracts/bridges/FxERC20ChildTunnel.sol

/// @audit arg0 is `indexed rootToken`
/// @audit arg1 is `indexed childToken`
84:          emit FxWithdrawERC20(rootToken, childToken, from, to, amount);

/// @audit arg0 is `indexed childToken`
/// @audit arg1 is `indexed rootToken`
107:         emit FxDepositERC20(childToken, rootToken, msg.sender, to, amount);

GitHub: 84, 84, 107, 107

File: contracts/bridges/FxERC20RootTunnel.sol

/// @audit arg0 is `indexed childToken`
/// @audit arg1 is `indexed rootToken`
79:          emit FxDepositERC20(childToken, rootToken, from, to, amount);

/// @audit arg0 is `indexed rootToken`
/// @audit arg1 is `indexed childToken`
106:         emit FxWithdrawERC20(rootToken, childToken, msg.sender, to, amount);

GitHub: 79, 79, 106, 106

File: contracts/Treasury.sol

/// @audit arg0 is `indexed token`
337:                 emit Withdraw(ETH_TOKEN_ADDRESS, to, tokenAmount);

/// @audit arg0 is `indexed token`
405:             emit Withdraw(ETH_TOKEN_ADDRESS, account, accountRewards);

GitHub: 337, 405

[G‑05] Assembly: Use scratch space for building calldata

If an external call's calldata can fit into two or fewer words, use the scratch space to build the calldata, rather than allowing Solidity to do a memory expansion.

There are 39 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/bridges/FxERC20ChildTunnel.sol

79:          bool success = IERC20(childToken).transfer(to, amount);

GitHub: 79

File: contracts/bridges/FxERC20RootTunnel.sol

77:          IERC20(rootToken).mint(to, amount);

104:         IERC20(rootToken).burn(amount);

GitHub: 77, 104

File: contracts/multisigs/GuardCM.sol

545:             ProposalState state = IGovernor(governor).state(governorCheckProposalId);

GitHub: 545

File: contracts/veOLAS.sol

534:         IERC20(token).transfer(msg.sender, amount);

GitHub: 534

File: contracts/wveOLAS.sol

164:         sPoint = IVEOLAS(ve).mapSupplyPoints(idx);

171:         slopeChange = IVEOLAS(ve).mapSlopeChanges(ts);

178:         pv = IVEOLAS(ve).getLastUserPoint(account);

185:         userNumPoints = IVEOLAS(ve).getNumUserPoints(account);

195:         uint256 userNumPoints = IVEOLAS(ve).getNumUserPoints(account);

197:             uPoint = IVEOLAS(ve).getUserPoint(account, idx);

204:         balance = IVEOLAS(ve).getVotes(account);

216:             balance = IVEOLAS(ve).getPastVotes(account, blockNumber);

224:         balance = IVEOLAS(ve).balanceOf(account);

236:             balance = IVEOLAS(ve).balanceOfAt(account, blockNumber);

244:         unlockTime = IVEOLAS(ve).lockedEnd(account);

257:         supplyAt = IVEOLAS(ve).totalSupplyAt(blockNumber);

266:         PointVoting memory sPoint = IVEOLAS(ve).mapSupplyPoints(numPoints);

269:             vPower = IVEOLAS(ve).totalSupplyLockedAtT(ts);

286:         vPower = IVEOLAS(ve).getPastTotalSupply(blockNumber);

293:         return IVEOLAS(ve).supportsInterface(interfaceId);

GitHub: 164, 171, 178, 185, 195, 197, 204, 216, 224, 236, 244, 257, 266, 269, 286, 293

File: contracts/AgentRegistry.sol

60:              (subComponentIds, ) = IRegistry(componentRegistry).getLocalSubComponents(uint256(unitId));

GitHub: 60

File: contracts/Depository.sol

221:         if (!ITreasury(treasury).isEnabled(token)) {

226:         if (!ITokenomics(tokenomics).reserveAmountForBondProgram(supply)) {

262:                 ITokenomics(tokenomics).refundFromBondProgram(supply);

320:         payout = IGenericBondCalculator(bondCalculator).calculatePayoutOLAS(tokenAmount, product.priceLP);

390:         IToken(olas).transfer(msg.sender, payout);

492:         return IGenericBondCalculator(bondCalculator).getCurrentPriceLP(token);

GitHub: 221, 226, 262, 320, 390, 492

File: contracts/Tokenomics.sol

708:                 address serviceOwner = IToken(serviceRegistry).ownerOf(serviceIds[i]);

709:                 topUpEligible = (IVotingEscrow(ve).getVotes(serviceOwner) >= veOLASThreshold  ||

710:                     IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;

801:         if (bList != address(0) && IDonatorBlacklist(bList).isDonatorBlacklisted(donator)) {

810:             if (!IServiceRegistry(serviceRegistry).exists(serviceIds[i])) {

1063:        if (incentives[1] == 0 || ITreasury(treasury).rebalanceTreasury(incentives[1])) {

GitHub: 708, 709, 710, 801, 810, 1063

File: contracts/Treasury.sol

228:         if (IToken(token).allowance(account, address(this)) < tokenAmount) {

229:             revert InsufficientAllowance(IToken(token).allowance((account), address(this)), tokenAmount);

242:         IOLAS(olas).mint(msg.sender, olasMintAmount);

362:                 success = IToken(token).transfer(to, tokenAmount);

416:             IOLAS(olas).mint(account, accountTopUps);

GitHub: 228, 229, 242, 362, 416

</details>

[G‑06] Use += for arrays

Using += for arrays saves 199 gas due to not having to re-fetch the array's length

There are 4 instances of this issue:

File: contracts/Tokenomics.sol

915:         incentives[1] = (incentives[0] * tp.epochPoint.rewardTreasuryFraction) / 100;

917:         incentives[2] = (incentives[0] * tp.unitPoints[0].rewardUnitFraction) / 100;

918:         incentives[3] = (incentives[0] * tp.unitPoints[1].rewardUnitFraction) / 100;

965:             incentives[4] = effectiveBond + incentives[4] - curMaxBond;

GitHub: 915, 917, 918, 965

[G‑07] Assembly: Avoid fetching a low-level call's return data by using assembly

Even if you don't assign the call's second return value, it still gets copied to memory. Use assembly instead to prevent this and save 159 gas:

(bool success,) = payable(receiver).call{gas: gas, value: value}(""); -> bool success; assembly { success := call(gas, receiver, value, 0, 0, 0, 0) }

There are 2 instances of this issue:

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

118:             (bool success, ) = multisig.call(payload);

GitHub: 118

File: contracts/TokenomicsProxy.sol

48:          (bool success, ) = tokenomics.delegatecall(tokenomicsData);

GitHub: 48

[G‑08] Avoid transferring amounts of zero in order to save gas

Skipping the external call when nothing will be transferred, will save at least 100 gas

There are 2 instances of this issue:

File: contracts/veOLAS.sol

534:         IERC20(token).transfer(msg.sender, amount);

GitHub: 534

File: contracts/Treasury.sol

235:         bool success = IToken(token).transferFrom(account, address(this), tokenAmount);

GitHub: 235

[G‑09] Combine mappings referenced in the same function by the same key

Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot (i.e. runtime gas savings). Even if the values can't be packed, if both fields are accessed in the same function (as is the case for these instances), combining them can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 7 instances of this issue:

File: contracts/UnitRegistry.sol

/// @audit combine into a `struct`: mapSubComponents,mapUnits
49       function create(address unitOwner, bytes32 unitHash, uint32[] memory dependencies)
50           external virtual returns (uint256 unitId)
51       {
52           // Reentrancy guard
53           if (_locked > 1) {
54               revert ReentrancyGuard();
55           }
56           _locked = 2;
57   
58           // Check for the manager privilege for a unit creation
59           if (manager != msg.sender) {
60               revert ManagerOnly(msg.sender, manager);
61           }
62   
63           // Checks for a non-zero owner address
64           if(unitOwner == address(0)) {
65               revert ZeroAddress();
66           }
67   
68           // Check for the non-zero hash value
69           if (unitHash == 0) {
70               revert ZeroValue();
71           }
72           
73           // Check for dependencies validity: must be already allocated, must not repeat
74           unitId = totalSupply;
75           _checkDependencies(dependencies, uint32(unitId));
76   
77           // Unit with Id = 0 is left empty not to do additional checks for the index zero
78           unitId++;
79   
80           // Initialize the unit and mint its token
81           Unit storage unit = mapUnits[unitId];
82           unit.unitHash = unitHash;
83           unit.dependencies = dependencies;
84   
85           // Update the map of subcomponents with calculated subcomponents for the new unit Id
86           // In order to get the correct set of subcomponents, we need to differentiate between the callers of this function
87           // Self contract (unit registry) can only call subcomponents calculation from the component level
88           uint32[] memory subComponentIds = _calculateSubComponents(UnitType.Component, dependencies);
89           // We need to add a current component Id to the set of subcomponents if the unit is a component
90           // For example, if component 3 (c3) has dependencies of [c1, c2], then the subcomponents will return [c1, c2].
91           // The resulting set will be [c1, c2, c3]. So we write into the map of component subcomponents: c3=>[c1, c2, c3].
92           // This is done such that the subcomponents start getting explored, and when the agent calls its subcomponents,
93           // it would have [c1, c2, c3] right away instead of adding c3 manually and then (for services) checking
94           // if another agent also has c3 as a component dependency. The latter will consume additional computation.
95           if (unitType == UnitType.Component) {
96               uint256 numSubComponents = subComponentIds.length;
97               uint32[] memory addSubComponentIds = new uint32[](numSubComponents + 1);
98               for (uint256 i = 0; i < numSubComponents; ++i) {
99                   addSubComponentIds[i] = subComponentIds[i];
100              }
101              // Adding self component Id
102              addSubComponentIds[numSubComponents] = uint32(unitId);
103              subComponentIds = addSubComponentIds;
104          }
105          mapSubComponents[unitId] = subComponentIds;
106  
107          // Set total supply to the unit Id number
108          totalSupply = unitId;
109          // Safe mint is needed since contracts can create units as well
110          _safeMint(unitOwner, unitId);
111  
112          emit CreateUnit(unitId, unitType, unitHash);
113          _locked = 1;
114:     }

GitHub: 49

File: contracts/Tokenomics.sol

/// @audit combine into a `struct`: mapEpochTokenomics,unitPoints
264      function initializeTokenomics(
265          address _olas,
266          address _treasury,
267          address _depository,
268          address _dispenser,
269          address _ve,
270          uint256 _epochLen,
271          address _componentRegistry,
272          address _agentRegistry,
273          address _serviceRegistry,
274          address _donatorBlacklist
275      ) external
276      {
277          // Check if the contract is already initialized
278          if (owner != address(0)) {
279              revert AlreadyInitialized();
280          }
281  
282          // Check for at least one zero contract address
283          if (_olas == address(0) || _treasury == address(0) || _depository == address(0) || _dispenser == address(0) ||
284              _ve == address(0) || _componentRegistry == address(0) || _agentRegistry == address(0) ||
285              _serviceRegistry == address(0)) {
286              revert ZeroAddress();
287          }
288  
289          // Initialize storage variables
290          owner = msg.sender;
291          _locked = 1;
292          epsilonRate = 1e17;
293          veOLASThreshold = 10_000e18;
294  
295          // Check that the epoch length has at least a practical minimal value
296          if (uint32(_epochLen) < MIN_EPOCH_LENGTH) {
297              revert LowerThan(_epochLen, MIN_EPOCH_LENGTH);
298          }
299  
300          // Check that the epoch length is not bigger than one year
301          if (uint32(_epochLen) > ONE_YEAR) {
302              revert Overflow(_epochLen, ONE_YEAR);
303          }
304  
305          // Assign other input variables
306          olas = _olas;
307          treasury = _treasury;
308          depository = _depository;
309          dispenser = _dispenser;
310          ve = _ve;
311          epochLen = uint32(_epochLen);
312          componentRegistry = _componentRegistry;
313          agentRegistry = _agentRegistry;
314          serviceRegistry = _serviceRegistry;
315          donatorBlacklist = _donatorBlacklist;
316  
317          // Time launch of the OLAS contract
318          uint256 _timeLaunch = IOLAS(_olas).timeLaunch();
319          // Check that the tokenomics contract is initialized no later than one year after the OLAS token is deployed
320          if (block.timestamp >= (_timeLaunch + ONE_YEAR)) {
321              revert Overflow(_timeLaunch + ONE_YEAR, block.timestamp);
322          }
323          // Seconds left in the deployment year for the zero year inflation schedule
324          // This value is necessary since it is different from a precise one year time, as the OLAS contract started earlier
325          uint256 zeroYearSecondsLeft = uint32(_timeLaunch + ONE_YEAR - block.timestamp);
326          // Calculating initial inflation per second: (mintable OLAS from getInflationForYear(0)) / (seconds left in a year)
327          // Note that we lose precision here dividing by the number of seconds right away, but to avoid complex calculations
328          // later we consider it less error-prone and sacrifice at most 6 insignificant digits (or 1e-12) of OLAS per year
329          uint256 _inflationPerSecond = getInflationForYear(0) / zeroYearSecondsLeft;
330          inflationPerSecond = uint96(_inflationPerSecond);
331          timeLaunch = uint32(_timeLaunch);
332  
333          // The initial epoch start time is the end time of the zero epoch
334          mapEpochTokenomics[0].epochPoint.endTime = uint32(block.timestamp);
335  
336          // The epoch counter starts from 1
337          epochCounter = 1;
338          TokenomicsPoint storage tp = mapEpochTokenomics[1];
339  
340          // Setting initial parameters and fractions
341          devsPerCapital = 1e18;
342          tp.epochPoint.idf = 1e18;
343  
344          // Reward fractions
345          // 0 stands for components and 1 for agents
346          // The initial target is to distribute around 2/3 of incentives reserved to fund owners of the code
347          // for components royalties and 1/3 for agents royalties
348          tp.unitPoints[0].rewardUnitFraction = 83;
349          tp.unitPoints[1].rewardUnitFraction = 17;
350          // tp.epochPoint.rewardTreasuryFraction is essentially equal to zero
351  
352          // We consider a unit of code as n agents or m components.
353          // Initially we consider 1 unit of code as either 2 agents or 1 component.
354          // E.g. if we have 2 profitable components and 2 profitable agents, this means there are (2 x 2.0 + 2 x 1.0) / 3 = 2
355          // units of code.
356          // We assume that during one epoch the developer can contribute with one piece of code (1 component or 2 agents)
357          codePerDev = 1e18;
358  
359          // Top-up fractions
360          uint256 _maxBondFraction = 50;
361          tp.epochPoint.maxBondFraction = uint8(_maxBondFraction);
362          tp.unitPoints[0].topUpUnitFraction = 41;
363          tp.unitPoints[1].topUpUnitFraction = 9;
364  
365          // Calculate initial effectiveBond based on the maxBond during the first epoch
366          // maxBond = inflationPerSecond * epochLen * maxBondFraction / 100
367          uint256 _maxBond = (_inflationPerSecond * _epochLen * _maxBondFraction) / 100;
368          maxBond = uint96(_maxBond);
369          effectiveBond = uint96(_maxBond);
370:     }

/// @audit combine into a `struct`: mapUnitIncentives,unitPoints
650      function _finalizeIncentivesForUnitId(uint256 epochNum, uint256 unitType, uint256 unitId) internal {
651          // Gets the overall amount of unit rewards for the unit's last epoch
652          // The pendingRelativeReward can be zero if the rewardUnitFraction was zero in the first place
653          // Note that if the rewardUnitFraction is set to zero at the end of epoch, the whole pending reward will be zero
654          // reward = (pendingRelativeReward * rewardUnitFraction) / 100
655          uint256 totalIncentives = mapUnitIncentives[unitType][unitId].pendingRelativeReward;
656          if (totalIncentives > 0) {
657              totalIncentives *= mapEpochTokenomics[epochNum].unitPoints[unitType].rewardUnitFraction;
658              // Add to the final reward for the last epoch
659              totalIncentives = mapUnitIncentives[unitType][unitId].reward + totalIncentives / 100;
660              mapUnitIncentives[unitType][unitId].reward = uint96(totalIncentives);
661              // Setting pending reward to zero
662              mapUnitIncentives[unitType][unitId].pendingRelativeReward = 0;
663          }
664  
665          // Add to the final top-up for the last epoch
666          totalIncentives = mapUnitIncentives[unitType][unitId].pendingRelativeTopUp;
667          // The pendingRelativeTopUp can be zero if the service owner did not stake enough veOLAS
668          // The topUpUnitFraction was checked before and if it were zero, pendingRelativeTopUp would be zero as well
669          if (totalIncentives > 0) {
670              // Summation of all the unit top-ups and total amount of top-ups per epoch
671              // topUp = (pendingRelativeTopUp * totalTopUpsOLAS * topUpUnitFraction) / (100 * sumUnitTopUpsOLAS)
672              totalIncentives *= mapEpochTokenomics[epochNum].epochPoint.totalTopUpsOLAS;
673              totalIncentives *= mapEpochTokenomics[epochNum].unitPoints[unitType].topUpUnitFraction;
674              uint256 sumUnitIncentives = uint256(mapEpochTokenomics[epochNum].unitPoints[unitType].sumUnitTopUpsOLAS) * 100;
675              totalIncentives = mapUnitIncentives[unitType][unitId].topUp + totalIncentives / sumUnitIncentives;
676              mapUnitIncentives[unitType][unitId].topUp = uint96(totalIncentives);
677              // Setting pending top-up to zero
678              mapUnitIncentives[unitType][unitId].pendingRelativeTopUp = 0;
679          }
680:     }

/// @audit combine into a `struct`: mapNewUnits,mapUnitIncentives,unitPoints
687      function _trackServiceDonations(address donator, uint256[] memory serviceIds, uint256[] memory amounts, uint256 curEpoch) internal {
688          // Component / agent registry addresses
689          address[] memory registries = new address[](2);
690          (registries[0], registries[1]) = (componentRegistry, agentRegistry);
691  
692          // Check all the unit fractions and identify those that need accounting of incentives
693          bool[] memory incentiveFlags = new bool[](4);
694          incentiveFlags[0] = (mapEpochTokenomics[curEpoch].unitPoints[0].rewardUnitFraction > 0);
695          incentiveFlags[1] = (mapEpochTokenomics[curEpoch].unitPoints[1].rewardUnitFraction > 0);
696          incentiveFlags[2] = (mapEpochTokenomics[curEpoch].unitPoints[0].topUpUnitFraction > 0);
697          incentiveFlags[3] = (mapEpochTokenomics[curEpoch].unitPoints[1].topUpUnitFraction > 0);
698  
699          // Get the number of services
700          uint256 numServices = serviceIds.length;
701          // Loop over service Ids to calculate their partial contributions
702          for (uint256 i = 0; i < numServices; ++i) {
703              // Check if the service owner or donator stakes enough OLAS for its components / agents to get a top-up
704              // If both component and agent owner top-up fractions are zero, there is no need to call external contract
705              // functions to check each service owner veOLAS balance
706              bool topUpEligible;
707              if (incentiveFlags[2] || incentiveFlags[3]) {
708                  address serviceOwner = IToken(serviceRegistry).ownerOf(serviceIds[i]);
709                  topUpEligible = (IVotingEscrow(ve).getVotes(serviceOwner) >= veOLASThreshold  ||
710                      IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;
711              }
712  
713              // Loop over component and agent Ids
714              for (uint256 unitType = 0; unitType < 2; ++unitType) {
715                  // Get the number and set of units in the service
716                  (uint256 numServiceUnits, uint32[] memory serviceUnitIds) = IServiceRegistry(serviceRegistry).
717                      getUnitIdsOfService(IServiceRegistry.UnitType(unitType), serviceIds[i]);
718                  // Service has to be deployed at least once to be able to receive donations,
719                  // otherwise its components and agents are undefined
720                  if (numServiceUnits == 0) {
721                      revert ServiceNeverDeployed(serviceIds[i]);
722                  }
723                  // Record amounts data only if at least one incentive unit fraction is not zero
724                  if (incentiveFlags[unitType] || incentiveFlags[unitType + 2]) {
725                      // The amount has to be adjusted for the number of units in the service
726                      uint96 amount = uint96(amounts[i] / numServiceUnits);
727                      // Accumulate amounts for each unit Id
728                      for (uint256 j = 0; j < numServiceUnits; ++j) {
729                          // Get the last epoch number the incentives were accumulated for
730                          uint256 lastEpoch = mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch;
731                          // Check if there were no donations in previous epochs and set the current epoch
732                          if (lastEpoch == 0) {
733                              mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);
734                          } else if (lastEpoch < curEpoch) {
735                              // Finalize unit rewards and top-ups if there were pending ones from the previous epoch
736                              // Pending incentives are getting finalized during the next epoch the component / agent
737                              // receives donations. If this is not the case before claiming incentives, the finalization
738                              // happens in the accountOwnerIncentives() where the incentives are issued
739                              _finalizeIncentivesForUnitId(lastEpoch, unitType, serviceUnitIds[j]);
740                              // Change the last epoch number
741                              mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);
742                          }
743                          // Sum the relative amounts for the corresponding components / agents
744                          if (incentiveFlags[unitType]) {
745                              mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeReward += amount;
746                          }
747                          // If eligible, add relative top-up weights in the form of donation amounts.
748                          // These weights will represent the fraction of top-ups for each component / agent relative
749                          // to the overall amount of top-ups that must be allocated
750                          if (topUpEligible && incentiveFlags[unitType + 2]) {
751                              mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeTopUp += amount;
752                              mapEpochTokenomics[curEpoch].unitPoints[unitType].sumUnitTopUpsOLAS += amount;
753                          }
754                      }
755                  }
756  
757                  // Record new units and new unit owners
758                  for (uint256 j = 0; j < numServiceUnits; ++j) {
759                      // Check if the component / agent is used for the first time
760                      if (!mapNewUnits[unitType][serviceUnitIds[j]]) {
761                          mapNewUnits[unitType][serviceUnitIds[j]] = true;
762                          mapEpochTokenomics[curEpoch].unitPoints[unitType].numNewUnits++;
763                          // Check if the owner has introduced component / agent for the first time
764                          // This is done together with the new unit check, otherwise it could be just a new unit owner
765                          address unitOwner = IToken(registries[unitType]).ownerOf(serviceUnitIds[j]);
766                          if (!mapNewOwners[unitOwner]) {
767                              mapNewOwners[unitOwner] = true;
768                              mapEpochTokenomics[curEpoch].epochPoint.numNewOwners++;
769                          }
770                      }
771                  }
772              }
773          }
774:     }

GitHub: 264, 650, 687

File: contracts/Treasury.sol

/// @audit combine into a `struct`: mapEnabledTokens,mapTokenReserves
212      function depositTokenForOLAS(address account, uint256 tokenAmount, address token, uint256 olasMintAmount) external {
213          // Check for the depository access
214          if (depository != msg.sender) {
215              revert ManagerOnly(msg.sender, depository);
216          }
217  
218          // Check if the token is authorized by the registry
219          if (!mapEnabledTokens[token]) {
220              revert UnauthorizedToken(token);
221          }
222  
223          // Increase the amount of LP token reserves
224          uint256 reserves = mapTokenReserves[token] + tokenAmount;
225          mapTokenReserves[token] = reserves;
226  
227          // Uniswap allowance implementation does not revert with the accurate message, need to check before the transfer is engaged
228          if (IToken(token).allowance(account, address(this)) < tokenAmount) {
229              revert InsufficientAllowance(IToken(token).allowance((account), address(this)), tokenAmount);
230          }
231  
232          // Transfer tokens from account to treasury and add to the token treasury reserves
233          // We assume that authorized LP tokens in the protocol are safe as they are enabled via the governance
234          // UniswapV2ERC20 realization has a standard transferFrom() function that returns a boolean value
235          bool success = IToken(token).transferFrom(account, address(this), tokenAmount);
236          if (!success) {
237              revert TransferFailed(token, account, address(this), tokenAmount);
238          }
239  
240          // Mint specified number of OLAS tokens corresponding to tokens bonding deposit
241          // The olasMintAmount is guaranteed by the product supply limit, which is limited by the effectiveBond
242          IOLAS(olas).mint(msg.sender, olasMintAmount);
243  
244          emit DepositTokenFromAccount(account, token, tokenAmount, olasMintAmount);
245:     }

/// @audit combine into a `struct`: mapEnabledTokens,mapTokenReserves
313      function withdraw(address to, uint256 tokenAmount, address token) external returns (bool success) {
314          // Check for the contract ownership
315          if (msg.sender != owner) {
316              revert OwnerOnly(msg.sender, owner);
317          }
318  
319          // Check that the withdraw address is not treasury itself
320          if (to == address(this)) {
321              revert TransferFailed(token, address(this), to, tokenAmount);
322          }
323  
324          // Check for the zero withdraw amount
325          if (tokenAmount == 0) {
326              revert ZeroValue();
327          }
328  
329          // ETH address is taken separately, and all the LP tokens must be validated with corresponding token reserves
330          if (token == ETH_TOKEN_ADDRESS) {
331              uint256 amountOwned = ETHOwned;
332              // Check if treasury has enough amount of owned ETH
333              if (amountOwned >= tokenAmount) {
334                  // This branch is used to transfer ETH to a specified address
335                  amountOwned -= tokenAmount;
336                  ETHOwned = uint96(amountOwned);
337                  emit Withdraw(ETH_TOKEN_ADDRESS, to, tokenAmount);
338                  // Send ETH to the specified address
339                  (success, ) = to.call{value: tokenAmount}("");
340                  if (!success) {
341                      revert TransferFailed(ETH_TOKEN_ADDRESS, address(this), to, tokenAmount);
342                  }
343              } else {
344                  // Insufficient amount of treasury owned ETH
345                  revert LowerThan(tokenAmount, amountOwned);
346              }
347          } else {
348              // Only approved token reserves can be used for redemptions
349              if (!mapEnabledTokens[token]) {
350                  revert UnauthorizedToken(token);
351              }
352              // Decrease the global LP token reserves record
353              uint256 reserves = mapTokenReserves[token];
354              if (reserves >= tokenAmount) {
355                  reserves -= tokenAmount;
356                  mapTokenReserves[token] = reserves;
357  
358                  emit Withdraw(token, to, tokenAmount);
359                  // Transfer LP tokens
360                  // We assume that LP tokens enabled in the protocol are safe by default
361                  // UniswapV2ERC20 realization has a standard transfer() function
362                  success = IToken(token).transfer(to, tokenAmount);
363                  if (!success) {
364                      revert TransferFailed(token, address(this), to, tokenAmount);
365                  }
366              }  else {
367                  // Insufficient amount of LP tokens
368                  revert LowerThan(tokenAmount, reserves);
369              }
370          }
371:     }

/// @audit combine into a `struct`: mapEnabledTokens,mapTokenReserves
507      function disableToken(address token) external {
508          // Check for the contract ownership
509          if (msg.sender != owner) {
510              revert OwnerOnly(msg.sender, owner);
511          }
512  
513          if (mapEnabledTokens[token]) {
514              // The reserves of a token must be zero in order to disable it
515              if (mapTokenReserves[token] > 0) {
516                  revert NonZeroValue();
517              }
518              mapEnabledTokens[token] = false;
519              emit DisableToken(token);
520          }
521:     }

GitHub: 212, 313, 507

[G‑10] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, or a function checks msg.sender some other way, 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. The extra opcodes avoided are CALLVALUE(2),DUP1(3),ISZERO(3),PUSH2(3),JUMPI(10),PUSH1(3),DUP1(3),REVERT(0),JUMPDEST(1),POP(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost

There are 56 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

43:      function changeOwner(address newOwner) external {

58:      function changeMinter(address newMinter) external {

75:      function mint(address account, uint256 amount) external {

GitHub: 43, 58, 75

File: contracts/bridges/BridgedERC20.sol

30:      function changeOwner(address newOwner) external {

48:      function mint(address account, uint256 amount) external {

59:      function burn(uint256 amount) external {

GitHub: 30, 48, 59

File: contracts/bridges/FxGovernorTunnel.sol

81:      function changeRootGovernor(address newRootGovernor) external {

107:     function processMessageFromRoot(uint256 stateId, address rootMessageSender, bytes memory data) external override {

GitHub: 81, 107

File: contracts/bridges/HomeMediator.sol

81:      function changeForeignGovernor(address newForeignGovernor) external {

105:     function processMessageFromForeign(bytes memory data) external {

GitHub: 81, 105

File: contracts/multisigs/GuardCM.sol

154:     function changeGovernor(address newGovernor) external {

170:     function changeGovernorCheckProposalId(uint256 proposalId) external {

441      function setTargetSelectorChainIds(
442          address[] memory targets,
443          bytes4[] memory selectors,
444          uint256[] memory chainIds,
445          bool[] memory statuses
446:     ) external {

495      function setBridgeMediatorChainIds(
496          address[] memory bridgeMediatorL1s,
497          address[] memory bridgeMediatorL2s,
498          uint256[] memory chainIds
499:     ) external {

560:     function unpause() external {

GitHub: 154, 170, 441, 495, 560

File: contracts/GenericManager.sol

20:      function changeOwner(address newOwner) external virtual {

36:      function pause() external virtual {

47:      function unpause() external virtual {

GitHub: 20, 36, 47

File: contracts/GenericRegistry.sol

37:      function changeOwner(address newOwner) external virtual {

54:      function changeManager(address newManager) external virtual {

78:      function setBaseURI(string memory bURI) external virtual {

GitHub: 37, 54, 78

File: contracts/UnitRegistry.sol

49       function create(address unitOwner, bytes32 unitHash, uint32[] memory dependencies)
50           external virtual returns (uint256 unitId)
51:      {

121      function updateHash(address unitOwner, uint256 unitId, bytes32 unitHash) external virtual
122          returns (bool success)
123:     {

GitHub: 49, 121

File: contracts/Depository.sol

123:     function changeOwner(address newOwner) external {

143:     function changeManagers(address _tokenomics, address _treasury) external {

163:     function changeBondCalculator(address _bondCalculator) external {

183:     function create(address token, uint256 priceLP, uint256 supply, uint256 vesting) external returns (uint256 productId) {

244:     function close(uint256[] memory productIds) external returns (uint256[] memory closedProductIds) {

356:     function redeem(uint256[] memory bondIds) external returns (uint256 payout) {

GitHub: 123, 143, 163, 183, 244, 356

File: contracts/Dispenser.sol

46:      function changeOwner(address newOwner) external {

64:      function changeManagers(address _tokenomics, address _treasury) external {

GitHub: 46, 64

File: contracts/DonatorBlacklist.sol

36:      function changeOwner(address newOwner) external {

56:      function setDonatorsStatuses(address[] memory accounts, bool[] memory statuses) external returns (bool success) {

GitHub: 36, 56

File: contracts/Tokenomics.sol

384:     function changeTokenomicsImplementation(address implementation) external {

404:     function changeOwner(address newOwner) external {

423:     function changeManagers(address _treasury, address _depository, address _dispenser) external {

450:     function changeRegistries(address _componentRegistry, address _agentRegistry, address _serviceRegistry) external {

474:     function changeDonatorBlacklist(address _donatorBlacklist) external {

497      function changeTokenomicsParameters(
498          uint256 _devsPerCapital,
499          uint256 _codePerDev,
500          uint256 _epsilonRate,
501          uint256 _epochLen,
502          uint256 _veOLASThreshold
503      ) external
504:     {

562      function changeIncentiveFractions(
563          uint256 _rewardComponentFraction,
564          uint256 _rewardAgentFraction,
565          uint256 _maxBondFraction,
566          uint256 _topUpComponentFraction,
567          uint256 _topUpAgentFraction
568      ) external
569:     {

609:     function reserveAmountForBondProgram(uint256 amount) external returns (bool success) {

630:     function refundFromBondProgram(uint256 amount) external {

788      function trackServiceDonations(
789          address donator,
790          uint256[] memory serviceIds,
791          uint256[] memory amounts,
792          uint256 donationETH
793:     ) external {

1085     function accountOwnerIncentives(address account, uint256[] memory unitTypes, uint256[] memory unitIds) external
1086         returns (uint256 reward, uint256 topUp)
1087:    {

GitHub: 384, 404, 423, 450, 474, 497, 562, 609, 630, 788, 1085

File: contracts/Treasury.sol

137:     function changeOwner(address newOwner) external {

156:     function changeManagers(address _tokenomics, address _depository, address _dispenser) external {

182:     function changeMinAcceptedETH(uint256 _minAcceptedETH) external {

212:     function depositTokenForOLAS(address account, uint256 tokenAmount, address token, uint256 olasMintAmount) external {

313:     function withdraw(address to, uint256 tokenAmount, address token) external returns (bool success) {

387      function withdrawToAccount(address account, uint256 accountRewards, uint256 accountTopUps) external
388          returns (bool success)
389:     {

428:     function rebalanceTreasury(uint256 treasuryRewards) external returns (bool success) {

466:     function drainServiceSlashedFunds() external returns (uint256 amount) {

487:     function enableToken(address token) external {

507:     function disableToken(address token) external {

531:     function pause() external {

542:     function unpause() external {

GitHub: 137, 156, 182, 212, 313, 387, 428, 466, 487, 507, 531, 542

</details>

[G‑11] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls. The inliner can do it only for 'simple' cases:

Now to get back to the point why we require the routine to be simple: As soon as you do more complicated things like for example branching, calling external contracts, the Common Subexpression Eliminator cannot re-construct the code anymore or does not do full symbolic evaluation of the expressions.

https://soliditylang.org/blog/2021/03/02/saving-gas-with-simple-inliner/

Therefore, the instances below contain branching or use op-codes with side-effects

There are 2 instances of this issue:

File: contracts/bridges/FxERC20ChildTunnel.sol

70       function _processMessageFromRoot(
71           uint256 /* stateId */,
72           address sender,
73           bytes memory message
74:      ) internal override validateSender(sender) {

GitHub: 70

File: contracts/bridges/FxERC20RootTunnel.sol

72:      function _processMessageFromChild(bytes memory message) internal override {

GitHub: 72

[G‑12] Assembly: Use scratch space when building emitted events with two data arguments

Using the scratch space for more than one, but at most two words worth of data (non-indexed arguments) will save gas over needing Solidity's abi memory expansion used for emitting normally.

There are 10 instances of this issue:

File: contracts/bridges/FxERC20ChildTunnel.sol

84:          emit FxWithdrawERC20(rootToken, childToken, from, to, amount);

107:         emit FxDepositERC20(childToken, rootToken, msg.sender, to, amount);

GitHub: 84, 107

File: contracts/bridges/FxERC20RootTunnel.sol

79:          emit FxDepositERC20(childToken, rootToken, from, to, amount);

106:         emit FxWithdrawERC20(rootToken, childToken, msg.sender, to, amount);

GitHub: 79, 106

File: contracts/veOLAS.sol

368:         emit Supply(supplyBefore, supplyAfter);

530:         emit Withdraw(msg.sender, amount, block.timestamp);

531:         emit Supply(supplyBefore, supplyAfter);

GitHub: 368, 530, 531

File: contracts/Dispenser.sol

112:         emit IncentivesClaimed(msg.sender, reward, topUp);

GitHub: 112

File: contracts/Treasury.sol

244:         emit DepositTokenFromAccount(account, token, tokenAmount, olasMintAmount);

452:                 emit UpdateTreasuryBalances(amountETHOwned, amountETHFromServices);

GitHub: 244, 452

[G‑13] >= costs less gas than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas. If < is being used, the condition can be inverted. In cases where a for-loop is being used, one can count down rather than up, in order to use the optimal operator

There are 51 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

108:             for (uint256 i = 0; i < numYears; ++i) {

GitHub: 108

File: contracts/bridges/FxGovernorTunnel.sol

125:         for (uint256 i = 0; i < dataLength;) {

153:             for (uint256 j = 0; j < payloadLength; ++j) {

GitHub: 125, 153

File: contracts/bridges/HomeMediator.sol

125:         for (uint256 i = 0; i < dataLength;) {

153:             for (uint256 j = 0; j < payloadLength; ++j) {

GitHub: 125, 153

File: contracts/multisigs/GuardCM.sol

212:         for (uint256 i = 0; i < data.length;) {

237:             for (uint256 j = 0; j < payloadLength; ++j) {

273:             for (uint256 i = 0; i < payload.length; ++i) {

292:             for (uint256 i = 0; i < bridgePayload.length; ++i) {

318:             for (uint256 i = 0; i < payload.length; ++i) {

340:         for (uint256 i = 0; i < payload.length; ++i) {

360:         for (uint i = 0; i < targets.length; ++i) {

458:         for (uint256 i = 0; i < targets.length; ++i) {

511:         for (uint256 i = 0; i < chainIds.length; ++i) {

GitHub: 212, 237, 273, 292, 318, 340, 360, 458, 511

File: contracts/veOLAS.sol

232:             for (uint256 i = 0; i < 255; ++i) {

561:         for (uint256 i = 0; i < 128; ++i) {

693:         for (uint256 i = 0; i < 255; ++i) {

GitHub: 232, 561, 693

File: contracts/AgentRegistry.sol

40:          for (uint256 iDep = 0; iDep < dependencies.length; ++iDep) {

GitHub: 40

File: contracts/ComponentRegistry.sol

29:          for (uint256 iDep = 0; iDep < dependencies.length; ++iDep) {

GitHub: 29

File: contracts/UnitRegistry.sol

98:              for (uint256 i = 0; i < numSubComponents; ++i) {

211:         for (uint32 i = 0; i < numUnits; ++i) {

227:         for (counter = 0; counter < maxNumComponents; ++counter) {

234:             for (uint32 i = 0; i < numUnits; ++i) {

236:                 for (; processedComponents[i] < numComponents[i]; ++processedComponents[i]) {

262:         for (uint32 i = 0; i < counter; ++i) {

GitHub: 98, 211, 227, 234, 236, 262

File: contracts/multisigs/GnosisSafeMultisig.sol

79:                  for (uint256 i = 0; i < payloadLength; ++i) {

GitHub: 79

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

113:             for (uint256 i = 0; i < payloadLength; ++i) {

139:         for (uint256 i = 0; i < numOwners; ++i) {

GitHub: 113, 139

File: contracts/Depository.sol

255:         for (uint256 i = 0; i < numProducts; ++i) {

274:         for (uint256 i = 0; i < numClosedProducts; ++i) {

357:         for (uint256 i = 0; i < bondIds.length; ++i) {

402:         for (uint256 i = 0; i < numProducts; ++i) {

413:         for (uint256 i = 0; i < numProducts; ++i) {

448:         for (uint256 i = 0; i < numBonds; ++i) {

468:         for (uint256 i = 0; i < numBonds; ++i) {

GitHub: 255, 274, 357, 402, 413, 448, 468

File: contracts/DonatorBlacklist.sol

67:          for (uint256 i = 0; i < accounts.length; ++i) {

GitHub: 67

File: contracts/Tokenomics.sol

702:         for (uint256 i = 0; i < numServices; ++i) {

714:             for (uint256 unitType = 0; unitType < 2; ++unitType) {

728:                     for (uint256 j = 0; j < numServiceUnits; ++j) {

758:                 for (uint256 j = 0; j < numServiceUnits; ++j) {

808:         for (uint256 i = 0; i < numServices; ++i) {

978:             for (uint256 i = 0; i < 2; ++i) {

1104:        for (uint256 i = 0; i < 2; ++i) {

1110:        for (uint256 i = 0; i < unitIds.length; ++i) {

1132:        for (uint256 i = 0; i < unitIds.length; ++i) {

1174:        for (uint256 i = 0; i < 2; ++i) {

1180:        for (uint256 i = 0; i < unitIds.length; ++i) {

1202:        for (uint256 i = 0; i < unitIds.length; ++i) {

GitHub: 702, 714, 728, 758, 808, 978, 1104, 1110, 1132, 1174, 1180, 1202

File: contracts/TokenomicsConstants.sol

55:              for (uint256 i = 0; i < numYears; ++i) {

92:              for (uint256 i = 1; i < numYears; ++i) {

GitHub: 55, 92

File: contracts/Treasury.sol

276:         for (uint256 i = 0; i < numServices; ++i) {

GitHub: 276

</details>

[G‑14] Consider using ERC721A over ERC721

ERC721A is much more gas-efficient for minting multiple NFTs in the same transaction

There are 3 instances of this issue:

File: contracts/AgentRegistry.sol

9    contract AgentRegistry is UnitRegistry {
10:      // Component registry

GitHub: 9

File: contracts/ComponentRegistry.sol

8    contract ComponentRegistry is UnitRegistry {
9:       // Component registry version number

GitHub: 8

File: contracts/UnitRegistry.sol

8:   abstract contract UnitRegistry is GenericRegistry {

GitHub: 8

[G‑15] Consider using solady's token contracts to save gas

They're written using heavily-optimized assembly, and will your users a lot of gas

There are 3 instances of this issue:

File: contracts/AgentRegistry.sol

/// @audit ERC721
9    contract AgentRegistry is UnitRegistry {
10:      // Component registry

GitHub: 9

File: contracts/ComponentRegistry.sol

/// @audit ERC721
8    contract ComponentRegistry is UnitRegistry {
9:       // Component registry version number

GitHub: 8

File: contracts/UnitRegistry.sol

/// @audit ERC721
8:   abstract contract UnitRegistry is GenericRegistry {

GitHub: 8

[G‑16] Consider using solady's FixedPointMathLib

Saves gas, and works to avoid unnecessary overflows.

There are 23 instances of this issue:

File: contracts/OLAS.sol

101:         uint256 numYears = (block.timestamp - timeLaunch) / oneYear;

109:                 supplyCap += (supplyCap * maxMintCapFraction) / 100;

GitHub: 101, 109

File: contracts/veOLAS.sol

225:             block_slope = (1e18 * uint256(block.number - lastPoint.blockNumber)) / uint256(block.timestamp - lastPoint.ts);

258:                 lastPoint.blockNumber = initialPoint.blockNumber + uint64((block_slope * uint256(tStep - initialPoint.ts)) / 1e18);

433:             unlockTime = ((block.timestamp + unlockTime) / WEEK) * WEEK;

487:             unlockTime = ((block.timestamp + unlockTime) / WEEK) * WEEK;

565:             uint256 mid = (minPointNumber + maxPointNumber + 1) / 2;

664:             blockTime += (dt * (blockNumber - point.blockNumber)) / dBlock;

GitHub: 225, 258, 433, 487, 565, 664

File: contracts/GenericBondCalculator.sol

89:                  priceLP = (reserve1 * 1e18) / totalSupply;

GitHub: 89

File: contracts/Tokenomics.sol

367:         uint256 _maxBond = (_inflationPerSecond * _epochLen * _maxBondFraction) / 100;

915:         incentives[1] = (incentives[0] * tp.epochPoint.rewardTreasuryFraction) / 100;

917:         incentives[2] = (incentives[0] * tp.unitPoints[0].rewardUnitFraction) / 100;

918:         incentives[3] = (incentives[0] * tp.unitPoints[1].rewardUnitFraction) / 100;

925:         uint256 numYears = (block.timestamp - timeLaunch) / ONE_YEAR;

951:         incentives[4] = (inflationPerEpoch * tp.epochPoint.maxBondFraction) / 100;

1010:        numYears = (block.timestamp + curEpochLen - timeLaunch) / ONE_YEAR;

1023:            curMaxBond = (inflationPerEpoch * nextEpochPoint.epochPoint.maxBondFraction) / 100;

1030:            curMaxBond = (curEpochLen * curInflationPerSecond * nextEpochPoint.epochPoint.maxBondFraction) / 100;

1054:        incentives[5] = (inflationPerEpoch * tp.unitPoints[0].topUpUnitFraction) / 100;

1056:        incentives[6] = (inflationPerEpoch * tp.unitPoints[1].topUpUnitFraction) / 100;

GitHub: 367, 915, 917, 918, 925, 951, 1010, 1023, 1030, 1054, 1056

File: contracts/TokenomicsConstants.sol

56:                  supplyCap += (supplyCap * maxMintCapFraction) / 100;

93:                  supplyCap += (supplyCap * maxMintCapFraction) / 100;

97:              inflationAmount = (supplyCap * maxMintCapFraction) / 100;

GitHub: 56, 93, 97

[G‑17] Initializers can be marked payable

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. An initializer can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

There is one instance of this issue:

File: contracts/Tokenomics.sol

264      function initializeTokenomics(
265          address _olas,
266          address _treasury,
267          address _depository,
268          address _dispenser,
269          address _ve,
270          uint256 _epochLen,
271          address _componentRegistry,
272          address _agentRegistry,
273          address _serviceRegistry,
274          address _donatorBlacklist
275      ) external
276:     {

GitHub: 264

[G‑18] Memory-safe annotation missing

Use assembly ("memory-safe") { ... } for the assembly blocks below since they dont't read or modify memory, and therefore are memory-safe. This will help the optimizer to create more optimal gas-efficient code. Use the comment variant if prior to Solidity version 0.8.13

There are 6 instances of this issue:

File: contracts/bridges/FxGovernorTunnel.sol

129              // solhint-disable-next-line no-inline-assembly
130              assembly {
131                  // First 20 bytes is the address (160 bits)
132                  i := add(i, 20)
133                  target := mload(add(data, i))
134                  // Offset the data by 12 bytes of value (96 bits)
135                  i := add(i, 12)
136                  value := mload(add(data, i))
137                  // Offset the data by 4 bytes of payload length (32 bits)
138                  i := add(i, 4)
139                  payloadLength := mload(add(data, i))
140:             }

GitHub: 129

File: contracts/bridges/HomeMediator.sol

129              // solhint-disable-next-line no-inline-assembly
130              assembly {
131                  // First 20 bytes is the address (160 bits)
132                  i := add(i, 20)
133                  target := mload(add(data, i))
134                  // Offset the data by 12 bytes of value (96 bits)
135                  i := add(i, 12)
136                  value := mload(add(data, i))
137                  // Offset the data by 4 bytes of payload length (32 bits)
138                  i := add(i, 4)
139                  payloadLength := mload(add(data, i))
140:             }

GitHub: 129

File: contracts/multisigs/GuardCM.sol

215              // solhint-disable-next-line no-inline-assembly
216              assembly {
217                  // First 20 bytes is the address (160 bits)
218                  i := add(i, 20)
219                  target := mload(add(data, i))
220                  // Offset the data by 12 bytes of value (96 bits) and by 4 bytes of payload length (32 bits)
221                  i := add(i, 16)
222                  payloadLength := mload(add(data, i))
223:             }

GitHub: 215

File: contracts/multisigs/GnosisSafeMultisig.sol

56               // Read the first 144 bytes of data
57               assembly {
58                   // Read all the addresses first (80 bytes)
59                   let offset := 20
60                   to := mload(add(data, offset))
61                   offset := add(offset, 20)
62                   fallbackHandler := mload(add(data, offset))
63                   offset := add(offset, 20)
64                   paymentToken := mload(add(data, offset))
65                   offset := add(offset, 20)
66                   paymentReceiver := mload(add(data, offset))
67   
68                   // Read all the uints (64 more bytes, a total of 144 bytes)
69                   offset := add(offset, 32)
70                   payment := mload(add(data, offset))
71                   offset := add(offset, 32)
72                   nonce := mload(add(data, offset))
73:              }

GitHub: 56

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

97           // Read the proxy multisig address (20 bytes) and the multisig-related data
98           assembly {
99               multisig := mload(add(data, DEFAULT_DATA_LENGTH))
100:         }

GitHub: 97

File: contracts/TokenomicsProxy.sol

55       fallback() external {
56           assembly {
57               let tokenomics := sload(PROXY_TOKENOMICS)
58               // Otherwise continue with the delegatecall to the tokenomics implementation
59               calldatacopy(0, 0, calldatasize())
60               let success := delegatecall(gas(), tokenomics, 0, calldatasize(), 0, 0)
61               returndatacopy(0, 0, returndatasize())
62               if eq(success, 0) {
63                   revert(0, returndatasize())
64               }
65               return(0, returndatasize())
66:          }

GitHub: 55

[G‑19] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct

Saves a storage slot for each of the removed mappings (i.e. this finding is not about runtime savings). The instances below refer to both mappings using the same key in the same function, so the mappings are related.

There are 4 instances of this issue:

File: contracts/UnitRegistry.sol

/// @audit combine into a `struct`: mapSubComponents,mapUnits
8:   abstract contract UnitRegistry is GenericRegistry {

GitHub: 8

File: contracts/Tokenomics.sol

/// @audit combine into a `struct`: mapUnitIncentives,unitPoints
/// @audit combine into a `struct`: mapEpochTokenomics,unitPoints
/// @audit combine into a `struct`: mapNewUnits,mapUnitIncentives,unitPoints
118: contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {

GitHub: 118, 118, 118

[G‑20] Multiple accesses of a memory/calldata array should use a local variable cache

The instances below point to the second+ access of a value inside a memory/calldata array, within a function. Caching avoids recalculating the array offsets into memory/calldata

There are 84 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/multisigs/GuardCM.sol

/// @audit targets...[]
376:                 _verifyData(targets[i], callDatas[i], block.chainid);

/// @audit targets...[]
476:             uint256 targetSelectorChainId = uint256(uint160(targets[i]));

/// @audit selectors...[]
478:             targetSelectorChainId |= uint256(uint32(selectors[i])) << 160;

/// @audit chainIds...[]
480:             targetSelectorChainId |= chainIds[i] << 192;

/// @audit bridgeMediatorL2s...[]
525:             uint256 bridgeMediatorL2ChainId = uint256(uint160(bridgeMediatorL2s[i]));

/// @audit bridgeMediatorL1s...[]
528:             mapBridgeMediatorL1L2ChainIds[bridgeMediatorL1s[i]] = bridgeMediatorL2ChainId;

GitHub: 376, 476, 478, 480, 525, 528

File: contracts/AgentRegistry.sol

/// @audit dependencies...[]
44:              lastId = dependencies[iDep];

GitHub: 44

File: contracts/ComponentRegistry.sol

/// @audit dependencies...[]
33:              lastId = dependencies[iDep];

GitHub: 33

File: contracts/UnitRegistry.sol

/// @audit components...[]
214:             numComponents[i] = uint32(components[i].length);

/// @audit numComponents...[]
215:             maxNumComponents += numComponents[i];

GitHub: 214, 215

File: contracts/Depository.sol

/// @audit productIds...[]
266:                 ids[numClosedProducts] = productIds[i];

/// @audit bondIds...[]
360:             bool matured = block.timestamp >= mapUserBonds[bondIds[i]].maturity;

/// @audit bondIds...[]
364:                 revert BondNotRedeemable(bondIds[i]);

/// @audit bondIds...[]
368:             if (mapUserBonds[bondIds[i]].account != msg.sender) {

/// @audit bondIds...[]
369:                 revert OwnerOnly(msg.sender, mapUserBonds[bondIds[i]].account);

/// @audit bondIds...[]
376:             uint256 productId = mapUserBonds[bondIds[i]].productId;

/// @audit bondIds...[]
379:             delete mapUserBonds[bondIds[i]];

/// @audit bondIds...[]
380:             emit RedeemBond(productId, msg.sender, bondIds[i]);

GitHub: 266, 360, 364, 368, 369, 376, 379, 380

File: contracts/DonatorBlacklist.sol

/// @audit accounts...[]
73:              mapBlacklistedDonators[accounts[i]] = statuses[i];

/// @audit accounts...[]
/// @audit statuses...[]
74:              emit DonatorBlacklistStatus(accounts[i], statuses[i]);

GitHub: 73, 74, 74

File: contracts/Tokenomics.sol

/// @audit incentiveFlags...[]
/// @audit incentiveFlags...[]
707:             if (incentiveFlags[2] || incentiveFlags[3]) {

/// @audit serviceIds...[]
721:                     revert ServiceNeverDeployed(serviceIds[i]);

/// @audit serviceUnitIds...[]
733:                             mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);

/// @audit serviceUnitIds...[]
739:                             _finalizeIncentivesForUnitId(lastEpoch, unitType, serviceUnitIds[j]);

/// @audit serviceUnitIds...[]
741:                             mapUnitIncentives[unitType][serviceUnitIds[j]].lastEpoch = uint32(curEpoch);

/// @audit serviceUnitIds...[]
745:                             mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeReward += amount;

/// @audit serviceUnitIds...[]
751:                             mapUnitIncentives[unitType][serviceUnitIds[j]].pendingRelativeTopUp += amount;

/// @audit serviceUnitIds...[]
765:                         address unitOwner = IToken(registries[unitType]).ownerOf(serviceUnitIds[j]);

/// @audit incentives...[]
915:         incentives[1] = (incentives[0] * tp.epochPoint.rewardTreasuryFraction) / 100;

/// @audit incentives...[]
917:         incentives[2] = (incentives[0] * tp.unitPoints[0].rewardUnitFraction) / 100;

/// @audit incentives...[]
918:         incentives[3] = (incentives[0] * tp.unitPoints[1].rewardUnitFraction) / 100;

/// @audit incentives...[]
963:         if (incentives[4] > curMaxBond) {

/// @audit incentives...[]
/// @audit incentives...[]
965:             incentives[4] = effectiveBond + incentives[4] - curMaxBond;

/// @audit incentives...[]
966:             effectiveBond = uint96(incentives[4]);

/// @audit incentives...[]
1041:        if (incentives[0] > 0) {

/// @audit incentives...[]
1043:            uint256 idf = _calculateIDF(incentives[1], tp.epochPoint.numNewOwners);

/// @audit incentives...[]
/// @audit incentives...[]
1052:        uint256 accountRewards = incentives[2] + incentives[3];

/// @audit incentives...[]
/// @audit incentives...[]
1060:        uint256 accountTopUps = incentives[5] + incentives[6];

/// @audit incentives...[]
/// @audit incentives...[]
1063:        if (incentives[1] == 0 || ITreasury(treasury).rebalanceTreasury(incentives[1])) {

/// @audit incentives...[]
1065:            emit EpochSettled(eCounter, incentives[1], accountRewards, accountTopUps);

/// @audit unitTypes...[]
/// @audit unitTypes...[]
1117:            if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {

/// @audit unitTypes...[]
1118:                revert WrongUnitId(unitIds[i], unitTypes[i]);

/// @audit unitTypes...[]
/// @audit unitIds...[]
1120:            lastIds[unitTypes[i]] = unitIds[i];

/// @audit unitTypes...[]
/// @audit unitIds...[]
1123:            address unitOwner = IToken(registries[unitTypes[i]]).ownerOf(unitIds[i]);

/// @audit unitTypes...[]
/// @audit unitIds...[]
1139:                _finalizeIncentivesForUnitId(lastEpoch, unitTypes[i], unitIds[i]);

/// @audit unitTypes...[]
/// @audit unitIds...[]
1141:                mapUnitIncentives[unitTypes[i]][unitIds[i]].lastEpoch = 0;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1145:            reward += mapUnitIncentives[unitTypes[i]][unitIds[i]].reward;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1146:            mapUnitIncentives[unitTypes[i]][unitIds[i]].reward = 0;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1148:            topUp += mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1149:            mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp = 0;

/// @audit unitTypes...[]
/// @audit unitTypes...[]
1187:            if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {

/// @audit unitTypes...[]
1188:                revert WrongUnitId(unitIds[i], unitTypes[i]);

/// @audit unitTypes...[]
/// @audit unitIds...[]
1190:            lastIds[unitTypes[i]] = unitIds[i];

/// @audit unitTypes...[]
/// @audit unitIds...[]
1193:            address unitOwner = IToken(registries[unitTypes[i]]).ownerOf(unitIds[i]);

/// @audit unitTypes...[]
/// @audit unitIds...[]
1209:                uint256 totalIncentives = mapUnitIncentives[unitTypes[i]][unitIds[i]].pendingRelativeReward;

/// @audit unitTypes...[]
1211:                    totalIncentives *= mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].rewardUnitFraction;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1216:                totalIncentives = mapUnitIncentives[unitTypes[i]][unitIds[i]].pendingRelativeTopUp;

/// @audit unitTypes...[]
1221:                    totalIncentives *= mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].topUpUnitFraction;

/// @audit unitTypes...[]
1222:                    uint256 sumUnitIncentives = uint256(mapEpochTokenomics[lastEpoch].unitPoints[unitTypes[i]].sumUnitTopUpsOLAS) * 100;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1229:            reward += mapUnitIncentives[unitTypes[i]][unitIds[i]].reward;

/// @audit unitTypes...[]
/// @audit unitIds...[]
1231:            topUp += mapUnitIncentives[unitTypes[i]][unitIds[i]].topUp;

GitHub: 707, 707, 721, 733, 739, 741, 745, 751, 765, 915, 917, 918, 963, 965, 965, 966, 1041, 1043, 1052, 1052, 1060, 1060, 1063, 1063, 1065, 1117, 1117, 1118, 1120, 1120, 1123, 1123, 1139, 1139, 1141, 1141, 1145, 1145, 1146, 1146, 1148, 1148, 1149, 1149, 1187, 1187, 1188, 1190, 1190, 1193, 1193, 1209, 1209, 1211, 1216, 1216, 1221, 1222, 1229, 1229, 1231, 1231

File: contracts/Treasury.sol

/// @audit amounts...[]
280:             totalAmount += amounts[i];

GitHub: 280

</details>

[G‑21] Multiple accesses of a storage array should use a local variable cache

The instances below point to the second+ access of a value inside a storage array, within a function. Caching an array index value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the array's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

There are 2 instances of this issue:

File: contracts/veOLAS.sol

/// @audit mapUserPoints...[]
148:             pv = mapUserPoints[account][lastPointNumber - 1];

/// @audit mapUserPoints...[]
596:             PointVoting memory uPoint = mapUserPoints[account][pointNumber - 1];

GitHub: 148, 596

[G‑22] Reduce deployment costs by tweaking contracts' metadata

See this link, at its bottom, for full details

There are 23 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/GovernorOLAS.sol

15:  contract GovernorOLAS is Governor, GovernorSettings, GovernorCompatibilityBravo, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {

GitHub: 15

File: contracts/OLAS.sol

17:  contract OLAS is ERC20 {

GitHub: 17

File: contracts/Timelock.sol

9:   contract Timelock is TimelockController {

GitHub: 9

File: contracts/bridges/BridgedERC20.sol

18:  contract BridgedERC20 is ERC20 {

GitHub: 18

File: contracts/bridges/FxERC20ChildTunnel.sol

24:  contract FxERC20ChildTunnel is FxBaseChildTunnel {

GitHub: 24

File: contracts/bridges/FxERC20RootTunnel.sol

24:  contract FxERC20RootTunnel is FxBaseRootTunnel {

GitHub: 24

File: contracts/bridges/FxGovernorTunnel.sol

46:  contract FxGovernorTunnel is IFxMessageProcessor {

GitHub: 46

File: contracts/bridges/HomeMediator.sol

46:  contract HomeMediator {

GitHub: 46

File: contracts/multisigs/GuardCM.sol

88:  contract GuardCM {

GitHub: 88

File: contracts/veOLAS.sol

86:  contract veOLAS is IErrors, IVotes, IERC20, IERC165 {

GitHub: 86

File: contracts/wveOLAS.sol

130  contract wveOLAS {
131:     // veOLAS address

GitHub: 130

File: contracts/AgentRegistry.sol

9    contract AgentRegistry is UnitRegistry {
10:      // Component registry

GitHub: 9

File: contracts/ComponentRegistry.sol

8    contract ComponentRegistry is UnitRegistry {
9:       // Component registry version number

GitHub: 8

File: contracts/RegistriesManager.sol

9    contract RegistriesManager is GenericManager {
10:      // Component registry address

GitHub: 9

File: contracts/multisigs/GnosisSafeMultisig.sol

24   contract GnosisSafeMultisig {
25:      // Selector of the Gnosis Safe setup function

GitHub: 24

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

50   contract GnosisSafeSameAddressMultisig {
51       // Default data size to be parsed as an address of a Gnosis Safe multisig proxy address
52:      // This exact size suggests that all the changes to the multisig have been performed and only validation is needed

GitHub: 50

File: contracts/Depository.sol

62:  contract Depository is IErrorsTokenomics {

GitHub: 62

File: contracts/Dispenser.sol

11:  contract Dispenser is IErrorsTokenomics {

GitHub: 11

File: contracts/DonatorBlacklist.sol

20:  contract DonatorBlacklist {

GitHub: 20

File: contracts/GenericBondCalculator.sol

20   contract GenericBondCalculator {
21:      // OLAS contract address

GitHub: 20

File: contracts/Tokenomics.sol

118: contract Tokenomics is TokenomicsConstants, IErrorsTokenomics {

GitHub: 118

File: contracts/TokenomicsProxy.sol

26   contract TokenomicsProxy {
27:      // Code position in storage is keccak256("PROXY_TOKENOMICS") = "0xbd5523e7c3b6a94aa0e3b24d1120addc2f95c7029e097b466b2bedc8d4b4362f"

GitHub: 26

File: contracts/Treasury.sol

39:  contract Treasury is IErrorsTokenomics {

GitHub: 39

</details>

[G‑23] Stack variable is only used once

If the variable is only accessed once, it's cheaper to use the assigned value directly that one time, and save the 3 gas the extra stack assignment would spend

There are 32 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/OLAS.sol

92:          uint256 remainder = inflationRemainder();

99:          uint256 _totalSupply = totalSupply;

GitHub: 92, 99

File: contracts/bridges/FxERC20ChildTunnel.sol

76:          (address from, address to, uint256 amount) = abi.decode(message, (address, address, uint256));

79:          bool success = IERC20(childToken).transfer(to, amount);

97:          bytes memory message = abi.encode(msg.sender, to, amount);

102:         bool success = IERC20(childToken).transferFrom(msg.sender, address(this), amount);

GitHub: 76, 79, 97, 102

File: contracts/bridges/FxERC20RootTunnel.sol

74:          (address from, address to, uint256 amount) = abi.decode(message, (address, address, uint256));

93:          bytes memory message = abi.encode(msg.sender, to, amount);

98:          bool success = IERC20(rootToken).transferFrom(msg.sender, address(this), amount);

GitHub: 74, 93, 98

File: contracts/multisigs/GuardCM.sol

297:             (bytes memory l2Message) = abi.decode(bridgePayload, (bytes));

323:             (address fxGovernorTunnel, bytes memory l2Message) = abi.decode(payload, (address, bytes));

GitHub: 297, 323

File: contracts/veOLAS.sol

677:         (, uint256 blockTime) = _getBlockTime(blockNumber);

753:         (PointVoting memory sPoint, uint256 blockTime) = _getBlockTime(blockNumber);

GitHub: 677, 753

File: contracts/wveOLAS.sol

195:         uint256 userNumPoints = IVEOLAS(ve).getNumUserPoints(account);

265:         uint256 numPoints = IVEOLAS(ve).totalNumPoints();

GitHub: 195, 265

File: contracts/multisigs/GnosisSafeMultisig.sol

98           (address to, address fallbackHandler, address paymentToken, address payable paymentReceiver, uint256 payment,
99:              uint256 nonce, bytes memory payload) = _parseData(data);

102          bytes memory safeParams = abi.encodeWithSelector(GNOSIS_SAFE_SETUP_SELECTOR, owners, threshold,
103:             to, payload, fallbackHandler, paymentToken, payment, paymentReceiver);

GitHub: 98, 102

File: contracts/multisigs/GnosisSafeSameAddressMultisig.sol

103:         bytes32 multisigProxyHash = keccak256(multisig.code);

118:             (bool success, ) = multisig.call(payload);

GitHub: 103, 118

File: contracts/GenericBondCalculator.sol

76:              address token1 = pair.token1();

GitHub: 76

File: contracts/Tokenomics.sol

325:         uint256 zeroYearSecondsLeft = uint32(_timeLaunch + ONE_YEAR - block.timestamp);

674:             uint256 sumUnitIncentives = uint256(mapEpochTokenomics[epochNum].unitPoints[unitType].sumUnitTopUpsOLAS) * 100;

841:         UD60x18 codeUnits = UD60x18.wrap(codePerDev);

846:         UD60x18 fpDevsPerCapital = UD60x18.wrap(devsPerCapital);

848:         UD60x18 fpNumNewOwners = convert(numNewOwners);

1052:        uint256 accountRewards = incentives[2] + incentives[3];

1060:        uint256 accountTopUps = incentives[5] + incentives[6];

GitHub: 325, 674, 841, 846, 848, 1052, 1060

File: contracts/TokenomicsConstants.sol

33               uint96[10] memory supplyCaps = [
34                   529_659_000_00e16,
35                   569_913_084_00e16,
36                   641_152_219_50e16,
37                   708_500_141_72e16,
38                   771_039_876_00e16,
39                   828_233_282_97e16,
40                   879_860_040_11e16,
41                   925_948_139_65e16,
42                   966_706_331_40e16,
43                   1_000_000_000e18
44:              ];

70               uint88[10] memory inflationAmounts = [
71                   3_159_000_00e16,
72                   40_254_084_00e16,
73                   71_239_135_50e16,
74                   67_347_922_22e16,
75                   62_539_734_28e16,
76                   57_193_406_97e16,
77                   51_626_757_14e16,
78                   46_088_099_54e16,
79                   40_758_191_75e16,
80                   33_293_668_60e16
81:              ];

GitHub: 33, 70

File: contracts/TokenomicsProxy.sol

48:          (bool success, ) = tokenomics.delegatecall(tokenomicsData);

GitHub: 48

File: contracts/Treasury.sol

224:         uint256 reserves = mapTokenReserves[token] + tokenAmount;

235:         bool success = IToken(token).transferFrom(account, address(this), tokenAmount);

GitHub: 224, 235

</details>

[G‑24] Split revert checks to save gas

Splitting the conditions into two separate checks saves 2 gas

There are 19 instances of this issue:

<details> <summary>see instances</summary>
File: contracts/bridges/FxERC20ChildTunnel.sol

39           if (_fxChild == address(0) || _childToken == address(0) || _rootToken == address(0)) {
40               revert ZeroAddress();
41:          }

GitHub: 39

File: contracts/bridges/FxERC20RootTunnel.sol

42           if (_checkpointManager == address(0) || _fxRoot == address(0) || _childToken == address(0) ||
43               _rootToken == address(0)) {
44               revert ZeroAddress();
45:          }

GitHub: 42

File: contracts/bridges/FxGovernorTunnel.sol

64           if (_fxChild == address(0) || _rootGovernor == address(0)) {
65               revert ZeroAddress();
66:          }

GitHub: 64

File: contracts/bridges/HomeMediator.sol

64           if (_AMBContractProxyHome == address(0) || _foreignGovernor == address(0)) {
65               revert ZeroAddress();
66:          }

GitHub: 64

File: contracts/multisigs/GuardCM.sol

144          if (_timelock == address(0) || _multisig == address(0) || _governor == address(0)) {
145              revert ZeroAddress();
146:         }

453          if (targets.length != selectors.length || targets.length != statuses.length || targets.length != chainIds.length) {
454              revert WrongArrayLength(targets.length, selectors.length, statuses.length, chainIds.length);
455:         }

506          if (bridgeMediatorL1s.length != bridgeMediatorL2s.length || bridgeMediatorL1s.length != chainIds.length) {
507              revert WrongArrayLength(bridgeMediatorL1s.length, bridgeMediatorL2s.length, chainIds.length, chainIds.length);
508:         }

513              if (bridgeMediatorL1s[i] == address(0) || bridgeMediatorL2s[i] == address(0)) {
514                  revert ZeroAddress();
515:             }

GitHub: 144, 453, 506, 513

File: contracts/wveOLAS.sol

147          if (_ve == address(0) || _token == address(0)) {
148              revert ZeroAddress();
149:         }

GitHub: 147

File: contracts/AgentRegistry.sol

41               if (dependencies[iDep] < (lastId + 1) || dependencies[iDep] > componentTotalSupply) {
42                   revert ComponentNotFound(dependencies[iDep]);
43:              }

GitHub: 41

File: contracts/ComponentRegistry.sol

30               if (dependencies[iDep] < (lastId + 1) || dependencies[iDep] > maxComponentId) {
31                   revert ComponentNotFound(dependencies[iDep]);
32:              }

GitHub: 30

File: contracts/Depository.sol

111          if (_olas == address(0) || _tokenomics == address(0) || _treasury == address(0) || _bondCalculator == address(0)) {
112              revert ZeroAddress();
113:         }

363              if (pay == 0 || !matured) {
364                  revert BondNotRedeemable(bondIds[i]);
365:             }

GitHub: 111, 363

File: contracts/Dispenser.sol

36           if (_tokenomics == address(0) || _treasury == address(0)) {
37               revert ZeroAddress();
38:          }

GitHub: 36

File: contracts/GenericBondCalculator.sol

31           if (_olas == address(0) || _tokenomics == address(0)) {
32               revert ZeroAddress();
33:          }

GitHub: 31

File: contracts/Tokenomics.sol

283          if (_olas == address(0) || _treasury == address(0) || _depository == address(0) || _dispenser == address(0) ||
284              _ve == address(0) || _componentRegistry == address(0) || _agentRegistry == address(0) ||
285              _serviceRegistry == address(0)) {
286              revert ZeroAddress();
287:         }

1117             if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {
1118                 revert WrongUnitId(unitIds[i], unitTypes[i]);
1119:            }

1187             if (unitIds[i] <= lastIds[unitTypes[i]] || unitIds[i] > registriesSupply[unitTypes[i]]) {
1188                 revert WrongUnitId(unitIds[i], unitTypes[i]);
1189:            }

GitHub: 283, 1117, 1187

File: contracts/Treasury.sol

100          if (_olas == address(0) || _tokenomics == address(0) || _depository == address(0) || _dispenser == address(0)) {
101              revert ZeroAddress();
102:         }

GitHub: 100

</details>

[G‑25] Update OpenZeppelin dependency to save gas

Every release contains new gas optimizations. Use the latest version to take advantage of this

There are 8 instances of this issue:

File: contracts/GovernorOLAS.sol

5:   import {Governor, IGovernor} from "@openzeppelin/contracts/governance/Governor.sol";

7:   import {GovernorCompatibilityBravo} from "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol";

8:   import {GovernorVotes, IVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";

9:   import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";

10:  import {GovernorTimelockControl, TimelockController} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";

GitHub: 5, 7, 8, 9, 10

File: contracts/Timelock.sol

4:   import "@openzeppelin/contracts/governance/TimelockController.sol";

GitHub: 4

File: contracts/veOLAS.sol

4:   import "@openzeppelin/contracts/governance/utils/IVotes.sol";

5:   import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

GitHub: 4, 5

[G‑26] Use short-circuit evaluation to avoid external calls

By evaluating expressions involving constants, literals, and local variables before ones involving external calls, you can avoid unnecessarily executing the calls in the unhappy path.

There is one instance of this issue:

File: contracts/Tokenomics.sol

/// @audit move the expression involving `getVotes()` to the right
709                  topUpEligible = (IVotingEscrow(ve).getVotes(serviceOwner) >= veOLASThreshold  ||
710:                     IVotingEscrow(ve).getVotes(donator) >= veOLASThreshold) ? true : false;

GitHub: 709

#0 - thebrittfactor

2024-01-02T16:43:35Z

Content submitted prior to audit close.

#1 - c4-pre-sort

2024-01-10T13:41:56Z

alex-ppg marked the issue as sufficient quality report

#2 - c4-judge

2024-01-18T21:53:32Z

dmvt marked the issue as grade-b

#3 - c4-judge

2024-01-20T16:28:52Z

dmvt marked the issue as grade-a

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