Coder Social home page Coder Social logo

2022-08-olympus-findings's Introduction

Olympus Contest

Unless otherwise discussed, this repo will be made public after contest completion, sponsor review, judging, and two-week issue mitigation window.

Contributors to this repo: prior to report publication, please review the Agreements & Disclosures issue.


Contest findings are submitted to this repo

Typically most findings come in on the last day of the contest, so don't be alarmed at all if there's nothing here but crickets until the end of the contest.

As a sponsor, you have four critical tasks in the contest process:

  1. Handle duplicate issues.
  2. Weigh in on severity.
  3. Respond to issues.
  4. Share your mitigation of findings.

Let's walk through each of these.

High and Medium Risk Issues

Handle duplicates

Because wardens submit issues without seeing each other's submissions, there will always be findings that are clear duplicates. Other findings may use different language that ultimately describes the same issue, but from different angles. Use your best judgment in identifying duplicates, and don't hesitate to reach out (in your private contest channel) to ask C4 for advice.

  1. For all issues labeled 3 (High Risk) or 2 (Medium Risk), determine the best and most thorough description of the finding among the set of duplicates. (At least a portion of the content of the most useful description will be used in the audit report.)
  2. Close the other duplicate issues and label them with duplicate
  3. Mention the primary issue # when closing the issue (using the format Duplicate of #issueNumber), so that duplicate issues get linked.

Note: QA and Gas reports do not need to be de-duped. Please see the "QA and Gas reports" section below for more details.

Weigh in on severity

Judges have the ultimate discretion in determining severity of issues, as well as whether/how issues are considered duplicates. However, sponsor input is a significant criteria.

For a detailed breakdown of severity criteria and how to estimate risk, please refer to the judging criteria in our documentation.

If you disagree with a finding's severity, leave the original severity label set by the warden and add the label disagree with severity, along with a comment indicating your opinion for the judges to review. It is possible for issues to be considered 0 (Non-critical).

Feel free to use the question label for anything you would like additional C4 input on.

Please don't change the severity labels; that's up to the judge's discretion.

Respond to issues

Label each High or Medium risk finding as one of these:

  • sponsor confirmed, meaning: "Yes, this is a problem and we intend to fix it."
  • sponsor disputed, meaning either: "We cannot duplicate this issue" or "We disagree that this is an issue at all."
  • sponsor acknowledged, meaning: "Yes, technically the issue is correct, but we are not going to resolve it for xyz reasons."

(Note: please don't use sponsor disputed for a finding if you think it should be considered of lower or higher severity. Instead, use the label disagree with severity and add comments to recommend a different severity level -- and include your reasoning.)

Add any necessary comments explaining your rationale for your evaluation of the issue. Note that when the repo is public, after all issues are mitigated, wardens will read these comments.

QA and Gas Reports

For low and non-critical findings (AKA QA), as well as gas optimizations: all warden submissions in these three categories should now be submitted as bulk listings of issues and recommendations:

  • QA reports should include all low severity and non-critical findings, along with a summary statement.
  • Gas reports should include all gas optimization recommendations, along with a summary statement.

For QA and Gas reports, we ask that you:

  • Leave a comment for the judge on any reports you consider to be particularly high quality. (These reports will be awarded on a curve.)
  • Add the sponsor disputed label to any reports that you think should be completely disregarded by the judge, i.e. the report contains no valid findings at all.

Once de-duping and labelling is complete

When you have marked all duplicates and labelled all findings, drop the C4 team a note in your private Discord backroom channel and let us know you've completed the sponsor review process. At this point, we will pass the repo over to the judge, and they'll get to work while you work on mitigation.

Share your mitigation of findings

Note: this section does not need to be completed in order to move to the judging phase. You can continue work on mitigations while judging is occurring and even beyond that. Ultimately we won't publish the final audit report until you give us the ok.

For each non-duplicate finding which you have confirmed, you will want to mitigate the issue before the contest report is made public.

As you undertake that process, we request that you take the following steps:

  1. Within your own GitHub repo, create a pull request for each finding.
  2. Link the PR to the issue that it resolves within your contest findings repo.

This will allow for complete transparency in showing the work of mitigating the issues found in the contest. Do not close the issue; simply label it as resolved. If the issue in question has duplicates, please link to your PR from the issue you selected as the best and most thoroughly articulated one.

2022-08-olympus-findings's People

Contributors

c4-staff avatar code423n4 avatar kartoonjoy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

2022-08-olympus-findings's Issues

Executor can change the admin then call the admin functions

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#L253

Vulnerability details

Impact

When the contract is created the msg.sender have the executor permission and the admin permission. The admin may give the executor permission to another user and leave for himself the manage role as the manage role has a modifier onlyAdmin which means an executor should not be able to call it, else their would have been one function.
But because the executor can change the admin he can make himself the admin and then be able to change the roles.

function grantRole(Role role_, address addr_) public onlyAdmin {
        if (hasRole[addr_][role_]) revert Kernel_AddressAlreadyHasRole(addr_, role_);

        ensureValidRole(role_);
        if (!isRole[role_]) isRole[role_] = true;

        hasRole[addr_][role_] = true;

        emit RoleGranted(role_, addr_);
    }

    /// @notice Function to revoke policy-defined roles from some address. Can only be called by admin.
    function revokeRole(Role role_, address addr_) public onlyAdmin {
        if (!isRole[role_]) revert Kernel_RoleDoesNotExist(role_);
        if (!hasRole[addr_][role_]) revert Kernel_AddressDoesNotHaveRole(addr_, role_);

        hasRole[addr_][role_] = false;

        emit RoleRevoked(role_, addr_);
    }

Proof of Concept

https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#L253

Recommended Mitigation Steps

Make an extrac check when change the admin or make the admin the only one possible to change the admin

QA Report

Missing fee parameter validation

Some fee parameters of functions are not checked for invalid values. Validate the parameters:

Code instances:

    PRICE.constructor (ohmEthPriceFeed_)
    PRICE.constructor (reserveEthPriceFeed_)

safeApprove of openZeppelin is deprecated

You use safeApprove of openZeppelin although it's deprecated.
(see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/566a774222707e424896c0c390a84dc3c13bdcb2/contracts/token/ERC20/utils/SafeERC20.sol#L38)
You should change it to increase/decrease Allowance as OpenZeppilin says.

Code instances:

    Deprecated safeApprove in Operator.sol line 166: ohm.safeApprove(address(MINTR), type(uint256).max);
    Deprecated safeApprove in TransferHelper.sol line 40: abi.encodeWithSelector(ERC20.approve.selector, to, amount)
    Deprecated safeApprove in BondCallback.sol line 56: ohm.safeApprove(address(MINTR), type(uint256).max);

Require with empty message

The following requires are with empty messages.
This is very important to add a message for any require. So the user has enough information to know the reason of failure.

Code instance:

    Solidity file: FullMath.sol, In line 44 with Empty Require message.

Not verified input

external / public functions parameters should be validated to make sure the address is not 0.
Otherwise if not given the right input it can mistakenly lead to loss of user funds.

Code instances:

    MINTR.sol.mintOhm to_
    Kernel.sol.revokeRole addr_
    OlympusERC20.sol.burnFrom account_
    OlympusERC20.sol.mint account_
    VOTES.sol.mintTo wallet_
    VOTES.sol.transferFrom to_
    TreasuryCustodian.sol.revokePolicyApprovals policy_
    TreasuryCustodian.sol.increaseDebt debtor_
    VoterRegistration.sol.issueVotesTo wallet_
    TRSRY.sol.setDebt debtor_
    TRSRY.sol.setApprovalFor withdrawer_
    Kernel.sol.grantRole addr_
    VOTES.sol.transferFrom from_
    VOTES.sol.burnFrom wallet_
    Kernel.sol.executeAction target_
    MINTR.sol.burnOhm from_
    VoterRegistration.sol.revokeVotesFrom wallet_
    TreasuryCustodian.sol.grantApproval for_
    TRSRY.sol.withdrawReserves to_
    BondCallback.sol.whitelist teller_
    TreasuryCustodian.sol.decreaseDebt debtor_

Solidity compiler versions mismatch

The project is compiled with different versions of solidity, which is not recommended because it can lead to undefined behaviors.

Code instance:

Hardcoded WETH

WETH address is hardcoded but it may differ on other chains, e.g. Polygon,
so make sure to check this before deploying and update if necessary
You should consider injecting WETH address via the constructor.
(previous issue: code-423n4/2021-10-ambire-findings#54)

Code instance:

    Hardcoded weth address in Deploy.sol

Named return issue

Users can mistakenly think that the return value is the named return, but it is actually the actualreturn statement that comes after. To know that the user needs to read the code and is confusing.
Furthermore, removing either the actual return or the named return will save gas.

Code instances:

    TRSRY.sol, VERSION
    INSTR.sol, VERSION
    MINTR.sol, VERSION
    BondCallback.sol, amountsForMarket
    FullMath.sol, mulDiv
    PRICE.sol, VERSION
    VOTES.sol, VERSION
    RANGE.sol, VERSION

Missing non reentrancy modifier

The following functions are missing reentrancy modifier although some other pulbic/external functions does use reentrancy modifer.
Even though I did not find a way to exploit it, it seems like those functions should have the nonReentrant modifier as the other functions have it as well..

Code instances:

    BondCallback.sol, batchToTreasury is missing a reentrancy modifier
    TRSRY.sol, withdrawReserves is missing a reentrancy modifier
    TRSRY.sol, getLoan is missing a reentrancy modifier
    Operator.sol, configureDependencies is missing a reentrancy modifier
    Operator.sol, operate is missing a reentrancy modifier

Assert instead require to validate user inputs

    From solidity docs: Properly functioning code should never reach a failing assert statement; if this happens there is a bug in your contract which you should fix.
    With assert the user pays the gas and with require it doesn't. The ETH network gas isn't cheap and users can see it as a scam.

Code instance:

    OlympusERC20.sol : reachable assert in line 612

In the following public update functions no value is returned

In the following functions no value is returned, due to which by default value of return will be 0.
We assumed that after the update you return the latest new value.
(similar issue here: code-423n4/2021-10-badgerdao-findings#85).

Code instances:

    RANGE.sol, updateCapacity
    RANGE.sol, updateMarket
    PRICE.sol, updateMovingAverage
    RANGE.sol, updatePrices

Never used parameters

Those are functions and parameters pairs that the function doesn't use the parameter. In case those functions are external/public this is even worst since the user is required to put value that never used and can misslead him and waste its time.

Code instances:

    VOTES.sol: function transfer parameter amount_ isn't used. (transfer is public)
    VOTES.sol: function transfer parameter to_ isn't used. (transfer is public)

Open TODOs

Open TODOs can hint at programming or architectural errors that still need to be fixed.
These files has open TODOs:

Code instances:

Open TODO in TreasuryCustodian.sol line 55 : // TODO Make sure policy_ is an actual policy and not a random address.

Open TODO in Deploy.sol line 144 : ] // TODO verify initial parameters

Open TODO in Operator.sol line 656 : /// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?

Open TODO in TreasuryCustodian.sol line 50 : // TODO Currently allows anyone to revoke any approval EXCEPT activated policies.

Open TODO in OlympusERC20.sol line 663 : // TODO comment actual hash value.

Open TODO in TreasuryCustodian.sol line 51 : // TODO must reorg policy storage to be able to check for deactivated policies.

Check transfer receiver is not 0 to avoid burned money

Transferring tokens to the zero address is usually prohibited to accidentally avoid "burning" tokens by sending them to an unrecoverable zero address.

Code instances:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Heart.sol#L112
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/BondCallback.sol#L113
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L298
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L259
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L99
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Heart.sol#L151
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/VOTES.sol#L61
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/BondCallback.sol#L159
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L312
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L82
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L108

Missing commenting

    The following functions are missing commenting as describe below:

Code instance:

    PriceConfig.sol, changeObservationFrequency (external), parameter observationFrequency_ not commented

Add a timelock

To give more trust to users: functions that set key/critical variables should be put behind a timelock.

Code instances:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L64
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L559
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/RANGE.sol#L242
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L527

Must approve 0 first

Some tokens (like USDT) do not work when changing the allowance from an existing non-zero allowance value.
They must first be approved by zero and then the actual allowance must be approved.

Code instances:

approve without approving 0 first Operator.sol, 166, ohm.safeApprove(address(MINTR), type(uint256).max);

approve without approving 0 first BondCallback.sol, 56, ohm.safeApprove(address(MINTR), type(uint256).max);

Unsafe Cast

use openzeppilin's safeCast in:

Code instances:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L266 : unsafe cast uint32(newObservations)
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L240 : unsafe cast uint32(newObservations)

Unbounded loop on array that can only grow can lead to DoS

A malicious attacker that is also a protocol owner can push unlimitedly to an array, that some function loop over this array.
If increasing the array size enough, calling the function that does a loop over the array will always revert since there is a gas limit.
This is a Med Risk issue since it can lead to DoS with a reasonable chance of having untrusted owner or even an owner that did a mistake in good faith.

Code instances:

    Kernel.sol (L360): Unbounded loop on the array activePolicies that can be publicly pushed by ['_activatePolicy'] and can't be pulled
    Kernel.sol (L351): Unbounded loop on the array allKeycodes that can be publicly pushed by ['_installModule'] and can't be pulled

Div by 0

Division by 0 can lead to accidentally revert,
(An example of a similar issue - code-423n4/2021-10-defiprotocol-findings#84)

Code instances:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L177 reserveEthPrice might be 0
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L136 numObs might be 0
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L230 numObs might be 0
    https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L138 numObs might be 0

Tokens with fee on transfer are not supported

There are ERC20 tokens that charge fee for every transfer() / transferFrom().

Vault.sol#addValue() assumes that the received amount is the same as the transfer amount,
and uses it to calculate attributions, balance amounts, etc.
But, the actual transferred amount can be lower for those tokens.
Therefore it's recommended to use the balance change before and after the transfer instead of the amount.
This way you also support the tokens with transfer fee - that are popular.

Code instance:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/BondCallback.sol#L113

QA Report

2022-08-olympus-code4rena QA Report

Files Description Table

File Name SHA-1 Hash
2022-08-olympus/src/modules/PRICE.sol eb3c920eaaf30e31cffbef13d8510dc18341d5ab

QA Report

Issues found

[N-01]: Typos

Impact

None.

Code Affected and Mitigation

diff --git a/src/modules/PRICE.sol b/src/modules/PRICE.sol
index 55d85d3..c3867d1 100644
--- a/src/modules/PRICE.sol
+++ b/src/modules/PRICE.sol
@@ -123,7 +123,7 @@ contract OlympusPrice is Module {
         // Revert if not initialized
         if (!initialized) revert Price_NotInitialized();
 
-        // Cache numbe of observations to save gas.
+        // Cache number of observations to save gas.
         uint32 numObs = numObservations;
 
         // Get earliest observation in window

Tools used

VS Code

Gas Optimizations

[G-01] Using bools for storage incurs overhead.

Impact

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27 Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past

Findings:

src/modules/PRICE.sol:L62    bool public initialized;
src/policies/Heart.sol:L33    bool public active;

src/policies/Operator.sol:L63    bool public initialized;

src/policies/Operator.sol:L66    bool public active;

src/policies/BondCallback.sol:L24    mapping(address => mapping(uint256 => bool)) public approvedMarkets;

src/policies/Governance.sol:L105    mapping(uint256 => bool) public proposalHasBeenActivated;

src/policies/Governance.sol:L117    mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal;

[G-02] Cache Array Length Outside of Loop

Impact

Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.

Findings:

src/policies/Governance.sol:L278        for (uint256 step; step < instructions.length; ) {

[G-03] Empty blocks should be removed or emit something

Impact

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting.

Findings:

src/modules/VOTES.sol:L19    {}

src/modules/TRSRY.sol:L45    constructor(Kernel kernel_) Module(kernel_) {}

src/modules/INSTR.sol:L20    constructor(Kernel kernel_) Module(kernel_) {}

src/policies/PriceConfig.sol:L15    constructor(Kernel kernel_) Policy(kernel_) {}

src/policies/VoterRegistration.sol:L16    constructor(Kernel kernel_) Policy(kernel_) {}

src/policies/TreasuryCustodian.sol:L24    constructor(Kernel kernel_) Policy(kernel_) {}

src/policies/Governance.sol:L59    constructor(Kernel kernel_) Policy(kernel_) {}

[G-04] Use a more recent version of solidity.

Impact

Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.

Findings:

src/interfaces/IBondCallback.sol:L2      pragma solidity >=0.8.0;

src/policies/interfaces/IOperator.sol:L2      pragma solidity >=0.8.0;

src/policies/interfaces/IHeart.sol:L2      pragma solidity >=0.8.0;

[G-05] Functions guaranteed to revert when called by normal users can be declared as payable.

Impact

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. 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.

Findings:

src/policies/Heart.sol:L130    function resetBeat() external onlyRole("heart_admin") {

src/policies/Heart.sol:L135    function toggleBeat() external onlyRole("heart_admin") {

src/policies/Heart.sol:L150    function withdrawUnspentRewards(ERC20 token_) external onlyRole("heart_admin") {

src/policies/VoterRegistration.sol:L45    function issueVotesTo(address wallet_, uint256 amount_) external onlyRole("voter_admin") {

src/policies/VoterRegistration.sol:L53    function revokeVotesFrom(address wallet_, uint256 amount_) external onlyRole("voter_admin") {

src/policies/Operator.sol:L195    function operate() external override onlyWhileActive onlyRole("operator_operate") {

src/policies/Operator.sol:L510    function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {

src/policies/Operator.sol:L516    function setCushionFactor(uint32 cushionFactor_) external onlyRole("operator_policy") {

src/policies/Operator.sol:L548    function setReserveFactor(uint32 reserveFactor_) external onlyRole("operator_policy") {

src/policies/Operator.sol:L598    function initialize() external onlyRole("operator_admin") {

src/policies/Operator.sol:L618    function regenerate(bool high_) external onlyRole("operator_admin") {

src/policies/Operator.sol:L624    function toggleActive() external onlyRole("operator_admin") {

src/policies/BondCallback.sol:L152    function batchToTreasury(ERC20[] memory tokens_) external onlyRole("callback_admin") {

src/policies/BondCallback.sol:L190    function setOperator(Operator operator_) external onlyRole("callback_admin") {

[G-06] X += Y costs more gas than X = X + Y for state variables.

Impact

Consider changing X += Y to X = X + Y to save gas.

Findings:

src/modules/VOTES.sol:L58            balanceOf[to_] += amount_;

src/modules/PRICE.sol:L136            _movingAverage += (currentPrice - earliestPrice) / numObs;

src/modules/PRICE.sol:L222            total += startObservations_[i];

src/modules/TRSRY.sol:L96        reserveDebt[token_][msg.sender] += amount_;

src/modules/TRSRY.sol:L97        totalDebt[token_] += amount_;

src/modules/TRSRY.sol:L131        if (oldDebt < amount_) totalDebt[token_] += amount_ - oldDebt;

src/policies/Heart.sol:L103        lastBeat += frequency();

src/policies/BondCallback.sol:L143        _amountsPerMarket[id_][0] += inputAmount_;

src/policies/BondCallback.sol:L144        _amountsPerMarket[id_][1] += outputAmount_;

src/policies/Governance.sol:L198        totalEndorsementsForProposal[proposalId_] += userVotes;

src/policies/Governance.sol:L252            yesVotesForProposal[activeProposal.proposalId] += userVotes;

src/policies/Governance.sol:L254            noVotesForProposal[activeProposal.proposalId] += userVotes;

[G-07] ++i costs less gas than i++, especially when it's used in for loops.

Impact

Saves 6 gas per instance.

Findings:

src/policies/Operator.sol:L488            decimals++;

src/policies/Operator.sol:L670                _status.low.count++;

src/policies/Operator.sol:L686                _status.high.count++;

src/utils/KernelUtils.sol:L49            i++;

src/utils/KernelUtils.sol:L64            i++;

[G-08] Using private rather than public for constants to saves gas.

Impact

If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table.

Findings:

src/modules/PRICE.sol:L59    uint8 public constant decimals = 18;

src/modules/RANGE.sol:L65    uint256 public constant FACTOR_SCALE = 1e4;

src/policies/Operator.sol:L89    uint32 public constant FACTOR_SCALE = 1e4;

src/policies/Governance.sol:L121    uint256 public constant SUBMISSION_REQUIREMENT = 100;

src/policies/Governance.sol:L124    uint256 public constant ACTIVATION_DEADLINE = 2 weeks;

src/policies/Governance.sol:L127    uint256 public constant GRACE_PERIOD = 1 weeks;

src/policies/Governance.sol:L130    uint256 public constant ENDORSEMENT_THRESHOLD = 20;

src/policies/Governance.sol:L133    uint256 public constant EXECUTION_THRESHOLD = 33;

src/policies/Governance.sol:L137    uint256 public constant EXECUTION_TIMELOCK = 3 days;

[G-09] Explicit initialization with zero not required

Impact

Explicit initialization with zero is not required for variable declaration because uints are 0 by default. Removing this will reduce contract size and save a bit of gas.

Findings:

src/utils/KernelUtils.sol:L43    for (uint256 i = 0; i < 5; ) {

src/utils/KernelUtils.sol:L58    for (uint256 i = 0; i < 32; ) {

Permissioned or Custodian address may set and manipulate the debt amount as 0 for any debtor a

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L122
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/TreasuryCustodian.sol#L71
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/TreasuryCustodian.sol#L80

Vulnerability details

The Permissioned and custodian address may SetDebt, increaseDebt and decreaseDebt on Treasury contract. The other debtors may feel risky enough to getLoan on Treasury assets.

It may lead to a potential rug pull by the Administrative. There is no need to manipulate the debt amount of debtors.

Multiple call by same user to beat() function will sweep the reward token balance of heart contract.

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Heart.sol#L92

Vulnerability details

A User can call multiple times to beat() function on heart contract and receive rewards n times. There is no check to prevent the user has already claimed the rewards.

Impact

It can drain the reward token balance of the contract.

Proof of Concept

  1. A malicious repeatedly calls a beat() function when he bypasses if (block.timestamp < lastBeat + frequency()) check.
  2. Malicious user can call when block.timestamp is greater than lastbeat + Frequency

Recommended Mitigation Steps

require(!claimed[msg.sender],"Alredy Claimed");
claimed[msg.sender] = true;

Gas Optimizations

issue
1 <x> += <y> costs more gas than <x> = <x> + <y> for state variables (same with -= )
2 can make the variable outside the loop to save gas
3 ++i costs less gas than i++, especially when it’s used in for-loops (--i/i-- too)
4 it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied
5 using calldata instead of memory for read-only arguments in external functions saves gas
6 using bools for storage incurs overhead
7 internal functions only called once can be inlined to save gas
8 usage of uint/int smaller than 32 bytes (256 bits) incurs overhead
9 using private rather than public for constants, saves gas
10 not using the named return variables when a function returns, wastes deployment gas
11 state variables only set in the constructor should be declared
12 <array>.length should not be looked up in every loop of a for-loop

1. <x> += <y> costs more gas than <x> = <x> + <y> for state variables (same with -= )

2. can make the variable outside the loop to save gas

3. ++i costs less gas than i++, especially when it’s used in for-loops (--i/i-- too)

Saves 6 gas per loop

4. it costs more gas to initialize non-constant/non-immutable variables to zero than to let the default of zero be applied

5. using calldata instead of memory for read-only arguments in external functions saves gas

6. using bools for storage incurs overhead

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past

7. 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.

8. usage of uint/int smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed

9. using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public constants. Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it’s used, and not adding another entry to the method ID table

10. not using the named return variables when a function returns, wastes deployment gas

11. state variables only set in the constructor should be declared

avoids a gsset (20000 gas)

12. <array>.length should not be looked up in every loop of a for-loop

This reduce gas cost as show here https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5

QA Report

QA Report

Qa

Unsafe ERC20 Operation(s)

examples

2022-08-olympus\src\policies\Governance.sol::259 => VOTES.transferFrom(msg.sender, address(this), userVotes);
2022-08-olympus\src\policies\Governance.sol::312 => VOTES.transferFrom(address(this), msg.sender, userVotes);

Do not use Deprecated Library Functions

examples

2022-08-olympus\src\policies\BondCallback.sol::57 => ohm.safeApprove(address(MINTR), type(uint256).max);
2022-08-olympus\src\policies\Operator.sol::167 => ohm.safeApprove(address(MINTR), type(uint256).max);

QA Report

[L-01] approve() and safeApprove() should be replaced with safeIncreaseAllowance() / safeDecreaseAllowance()

Impact

approve() & safeApprove() are deprecated and subject to a known front-running attack. Consider using safeIncreaseAllowance() & safeDecreaseAllowance() instead.

Findings:

src/policies/Operator.sol:L167                 ohm.safeApprove(address(MINTR), type(uint256).max);

src/policies/BondCallback.sol:L57                 ohm.safeApprove(address(MINTR), type(uint256).max);

[L-02] decimals() not part of ERC20 standard.

Impact

decimals() is not part of the official ERC20 standard and might fall for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a potential issue.

Findings:

src/modules/PRICE.sol:L84        uint8 ohmEthDecimals = _ohmEthPriceFeed.decimals();

src/modules/PRICE.sol:L87        uint8 reserveEthDecimals = _reserveEthPriceFeed.decimals();

src/policies/Operator.sol:L122        ohmDecimals = tokens_[0].decimals();

src/policies/Operator.sol:L124        reserveDecimals = tokens_[1].decimals();

src/policies/Operator.sol:L375            uint256 oracleScale = 10**uint8(int8(PRICE.decimals()) - priceDecimals);

src/policies/Operator.sol:L418            uint8 oracleDecimals = PRICE.decimals();

src/policies/Operator.sol:L493        return decimals - int8(PRICE.decimals());

src/policies/Operator.sol:L754                10**ohmDecimals * 10**PRICE.decimals()

src/policies/Operator.sol:L764                10**ohmDecimals * 10**PRICE.decimals(),

src/policies/Operator.sol:L784                    10**ohmDecimals * 10**PRICE.decimals(),

[L-03] Avoid floating pragmas for non-library contracts.

Impact

While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.

A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.

It is recommended to pin to a concrete compiler version.

Findings:

src/interfaces/IBondCallback.sol:L2     pragma solidity >=0.8.0;

src/policies/interfaces/IOperator.sol:L2     pragma solidity >=0.8.0;

src/policies/interfaces/IHeart.sol:L2     pragma solidity >=0.8.0;

[L-04] _safemint() should be used rather than _mint() wherever possible

Impact

_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function

Findings:

src/modules/VOTES.sol:L36        _mint(wallet_, amount_);

[L-05] Events not emitted for important state changes.

Impact

When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.

Findings:

src/policies/Operator.sol:L498             function setSpreads(uint256 cushionSpread_, uint256 wallSpread_)

src/policies/Operator.sol:L510             function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {

src/policies/Operator.sol:L586             function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)

src/policies/BondCallback.sol:L190             function setOperator(Operator operator_) external onlyRole("callback_admin") {

[L-06] Unsafe ERC20 Operation(s)

Impact

ERC20 operations can be unsafe due to different implementations and vulnerabilities in the standard.

It is therefore recommended to always either use OpenZeppelin's SafeERC20 library or at least to wrap each operation in a require statement.

Findings:

src/policies/Governance.sol:L259        VOTES.transferFrom(msg.sender, address(this), userVotes);

src/policies/Governance.sol:L312        VOTES.transferFrom(address(this), msg.sender, userVotes);

[N-01] Open TODOs

Impact

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment.

Findings:

src/policies/Operator.sol:L657        /// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?

src/policies/TreasuryCustodian.sol:L51    // TODO Currently allows anyone to revoke any approval EXCEPT activated policies.

src/policies/TreasuryCustodian.sol:L52    // TODO must reorg policy storage to be able to check for deactivated policies.

src/policies/TreasuryCustodian.sol:L56        // TODO Make sure `policy_` is an actual policy and not a random address.

Gas Optimizations

[G-01] Don't Initialize Variables with Default Value

Uninitialized variables are assigned with the types default value. Explicitly initializing a variable with it's default value costs unnecesary gas.

2022-08-olympus/src/Kernel.sol::397 => for (uint256 i = 0; i < reqLength; ) {
2022-08-olympus/src/utils/KernelUtils.sol::43 => for (uint256 i = 0; i < 5; ) {
2022-08-olympus/src/utils/KernelUtils.sol::58 => for (uint256 i = 0; i < 32; ) {

[G-02] Cache Array Length Outside of Loop

Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.

2022-08-olympus/src/policies/Governance.sol::278 => for (uint256 step; step < instructions.length; ) {

[G-03] Use Shift Right/Left instead of Division/Multiplication if possible

A division/multiplication by any number x being a power of 2 can be calculated by shifting log2(x) to the right/left.

While the DIV opcode uses 5 gas, the SHR opcode only uses 3 gas. Furthermore, Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting.

2022-08-olympus/src/policies/Operator.sol::372 => int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);
2022-08-olympus/src/policies/Operator.sol::419 => uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;
2022-08-olympus/src/policies/Operator.sol::420 => uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;
2022-08-olympus/src/policies/Operator.sol::427 => int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);

[G-04] Using private rather than public for constants, saves gas

If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table

2022-08-olympus/src/modules/MINTR.sol::9 => OHM public immutable ohm;
2022-08-olympus/src/modules/RANGE.sol::68 => ERC20 public immutable ohm;
2022-08-olympus/src/modules/RANGE.sol::71 => ERC20 public immutable reserve;
2022-08-olympus/src/policies/Operator.sol::82 => ERC20 public immutable ohm;
2022-08-olympus/src/policies/Operator.sol::83 => uint8 public immutable ohmDecimals;
2022-08-olympus/src/policies/Operator.sol::85 => ERC20 public immutable reserve;
2022-08-olympus/src/policies/Operator.sol::86 => uint8 public immutable reserveDecimals;

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

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. 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

2022-08-olympus/src/Kernel.sol::76 => function changeKernel(Kernel newKernel_) external onlyKernel {
2022-08-olympus/src/Kernel.sol::105 => function INIT() external virtual onlyKernel {}
2022-08-olympus/src/Kernel.sol::126 => function setActiveStatus(bool activate_) external onlyKernel {
2022-08-olympus/src/Kernel.sol::235 => function executeAction(Actions action_, address target_) external onlyExecutor {
2022-08-olympus/src/Kernel.sol::439 => function grantRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/Kernel.sol::451 => function revokeRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/policies/BondCallback.sol::152 => function batchToTreasury(ERC20[] memory tokens_) external onlyRole("callback_admin") {
2022-08-olympus/src/policies/BondCallback.sol::190 => function setOperator(Operator operator_) external onlyRole("callback_admin") {
2022-08-olympus/src/policies/Heart.sol::130 => function resetBeat() external onlyRole("heart_admin") {
2022-08-olympus/src/policies/Heart.sol::135 => function toggleBeat() external onlyRole("heart_admin") {
2022-08-olympus/src/policies/Heart.sol::150 => function withdrawUnspentRewards(ERC20 token_) external onlyRole("heart_admin") {
2022-08-olympus/src/policies/Operator.sol::195 => function operate() external override onlyWhileActive onlyRole("operator_operate") {
2022-08-olympus/src/policies/Operator.sol::510 => function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {
2022-08-olympus/src/policies/Operator.sol::516 => function setCushionFactor(uint32 cushionFactor_) external onlyRole("operator_policy") {
2022-08-olympus/src/policies/Operator.sol::548 => function setReserveFactor(uint32 reserveFactor_) external onlyRole("operator_policy") {
2022-08-olympus/src/policies/Operator.sol::618 => function regenerate(bool high_) external onlyRole("operator_admin") {
2022-08-olympus/src/policies/Operator.sol::624 => function toggleActive() external onlyRole("operator_admin") {
2022-08-olympus/src/policies/VoterRegistration.sol::45 => function issueVotesTo(address wallet_, uint256 amount_) external onlyRole("voter_admin") {
2022-08-olympus/src/policies/VoterRegistration.sol::53 => function revokeVotesFrom(address wallet_, uint256 amount_) external onlyRole("voter_admin") {

[G-06] Empty blocks should be removed or emit something

The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting.

2022-08-olympus/src/Kernel.sol::85 => constructor(Kernel kernel_) KernelAdapter(kernel_) {}
2022-08-olympus/src/Kernel.sol::95 => function KEYCODE() public pure virtual returns (Keycode) {}
2022-08-olympus/src/Kernel.sol::100 => function VERSION() external pure virtual returns (uint8 major, uint8 minor) {}
2022-08-olympus/src/Kernel.sol::105 => function INIT() external virtual onlyKernel {}
2022-08-olympus/src/Kernel.sol::115 => constructor(Kernel kernel_) KernelAdapter(kernel_) {}
2022-08-olympus/src/Kernel.sol::139 => function configureDependencies() external virtual returns (Keycode[] memory dependencies) {}
2022-08-olympus/src/Kernel.sol::143 => function requestPermissions() external view virtual returns (Permissions[] memory requests) {}
2022-08-olympus/src/modules/INSTR.sol::20 => constructor(Kernel kernel_) Module(kernel_) {}
2022-08-olympus/src/modules/TRSRY.sol::45 => constructor(Kernel kernel_) Module(kernel_) {}
2022-08-olympus/src/modules/VOTES.sol::19 => {}
2022-08-olympus/src/policies/Governance.sol::59 => constructor(Kernel kernel_) Policy(kernel_) {}
2022-08-olympus/src/policies/PriceConfig.sol::15 => constructor(Kernel kernel_) Policy(kernel_) {}
2022-08-olympus/src/policies/TreasuryCustodian.sol::24 => constructor(Kernel kernel_) Policy(kernel_) {}
2022-08-olympus/src/policies/VoterRegistration.sol::16 => constructor(Kernel kernel_) Policy(kernel_) {}

[G-07] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

2022-08-olympus/src/modules/PRICE.sol::44 => uint32 public nextObsIndex;
2022-08-olympus/src/modules/PRICE.sol::47 => uint32 public numObservations;
2022-08-olympus/src/modules/PRICE.sol::59 => uint8 public constant decimals = 18;
2022-08-olympus/src/modules/PRICE.sol::84 => uint8 ohmEthDecimals = _ohmEthPriceFeed.decimals();
2022-08-olympus/src/modules/PRICE.sol::87 => uint8 reserveEthDecimals = _reserveEthPriceFeed.decimals();
2022-08-olympus/src/modules/PRICE.sol::127 => uint32 numObs = numObservations;
2022-08-olympus/src/modules/PRICE.sol::185 => uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;
2022-08-olympus/src/policies/Operator.sol::83 => uint8 public immutable ohmDecimals;
2022-08-olympus/src/policies/Operator.sol::86 => uint8 public immutable reserveDecimals;
2022-08-olympus/src/policies/Operator.sol::89 => uint32 public constant FACTOR_SCALE = 1e4;
2022-08-olympus/src/policies/Operator.sol::485 => int8 decimals;
2022-08-olympus/src/policies/Operator.sol::665 => uint32 observe = _config.regenObserve;
2022-08-olympus/src/policies/interfaces/IOperator.sol::13 => uint32 cushionFactor; // percent of capacity to be used for a single cushion deployment, assumes 2 decimals (i.e. 1000 = 10%)
2022-08-olympus/src/policies/interfaces/IOperator.sol::14 => uint32 cushionDuration; // duration of a single cushion deployment in seconds
2022-08-olympus/src/policies/interfaces/IOperator.sol::15 => uint32 cushionDebtBuffer; // Percentage over the initial debt to allow the market to accumulate at any one time. Percent with 3 decimals, e.g. 1_000 = 1 %. See IBondAuctioneer for more info.
2022-08-olympus/src/policies/interfaces/IOperator.sol::16 => uint32 cushionDepositInterval; // Target frequency of deposits. Determines max payout of the bond market. See IBondAuctioneer for more info.
2022-08-olympus/src/policies/interfaces/IOperator.sol::17 => uint32 reserveFactor; // percent of reserves in treasury to be used for a single wall, assumes 2 decimals (i.e. 1000 = 10%)
2022-08-olympus/src/policies/interfaces/IOperator.sol::18 => uint32 regenWait; // minimum duration to wait to reinstate a wall in seconds
2022-08-olympus/src/policies/interfaces/IOperator.sol::19 => uint32 regenThreshold; // number of price points on other side of moving average to reinstate a wall
2022-08-olympus/src/policies/interfaces/IOperator.sol::20 => uint32 regenObserve; // number of price points to observe to determine regeneration
2022-08-olympus/src/policies/interfaces/IOperator.sol::31 => uint32 count; // current number of price points that count towards regeneration
2022-08-olympus/src/policies/interfaces/IOperator.sol::33 => uint32 nextObservation; // index of the next observation in the observations array

[G-08] Using bools for storage incurs overhead

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.
Use uint256(1) and uint256(2) for true/false instead

2022-08-olympus/src/Kernel.sol::113 => bool public isActive;
2022-08-olympus/src/Kernel.sol::181 => mapping(Keycode => mapping(Policy => mapping(bytes4 => bool))) public modulePermissions;
2022-08-olympus/src/Kernel.sol::194 => mapping(address => mapping(Role => bool)) public hasRole;
2022-08-olympus/src/Kernel.sol::197 => mapping(Role => bool) public isRole;
2022-08-olympus/src/modules/PRICE.sol::62 => bool public initialized;
2022-08-olympus/src/policies/BondCallback.sol::24 => mapping(address => mapping(uint256 => bool)) public approvedMarkets;
2022-08-olympus/src/policies/Governance.sol::105 => mapping(uint256 => bool) public proposalHasBeenActivated;
2022-08-olympus/src/policies/Governance.sol::117 => mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal;
2022-08-olympus/src/policies/Heart.sol::33 => bool public active;
2022-08-olympus/src/policies/Operator.sol::63 => bool public initialized;
2022-08-olympus/src/policies/Operator.sol::66 => bool public active;

[G-09] += costs more gas than = + for state variables

use = + or = - instead to save gas

2022-08-olympus/src/modules/PRICE.sol::136 => _movingAverage += (currentPrice - earliestPrice) / numObs;
2022-08-olympus/src/modules/PRICE.sol::138 => _movingAverage -= (earliestPrice - currentPrice) / numObs;
2022-08-olympus/src/modules/PRICE.sol::222 => total += startObservations_[i];
2022-08-olympus/src/modules/TRSRY.sol::96 => reserveDebt[token_][msg.sender] += amount_;
2022-08-olympus/src/modules/TRSRY.sol::97 => totalDebt[token_] += amount_;
2022-08-olympus/src/modules/TRSRY.sol::115 => reserveDebt[token_][msg.sender] -= received;
2022-08-olympus/src/modules/TRSRY.sol::116 => totalDebt[token_] -= received;
2022-08-olympus/src/modules/TRSRY.sol::131 => if (oldDebt < amount_) totalDebt[token_] += amount_ - oldDebt;
2022-08-olympus/src/modules/TRSRY.sol::132 => else totalDebt[token_] -= oldDebt - amount_;
2022-08-olympus/src/modules/VOTES.sol::56 => balanceOf[from_] -= amount_;
2022-08-olympus/src/modules/VOTES.sol::58 => balanceOf[to_] += amount_;
2022-08-olympus/src/policies/BondCallback.sol::143 => _amountsPerMarket[id_][0] += inputAmount_;
2022-08-olympus/src/policies/BondCallback.sol::144 => _amountsPerMarket[id_][1] += outputAmount_;
2022-08-olympus/src/policies/Governance.sol::194 => totalEndorsementsForProposal[proposalId_] -= previousEndorsement;
2022-08-olympus/src/policies/Governance.sol::198 => totalEndorsementsForProposal[proposalId_] += userVotes;
2022-08-olympus/src/policies/Governance.sol::252 => yesVotesForProposal[activeProposal.proposalId] += userVotes;
2022-08-olympus/src/policies/Governance.sol::254 => noVotesForProposal[activeProposal.proposalId] += userVotes;
2022-08-olympus/src/policies/Heart.sol::103 => lastBeat += frequency();

[G-11] Use a more recent version of solidity

Use a solidity version of at least 0.8.2 to get compiler automatic inlining
Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads
Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings
Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

2022-08-olympus/src/policies/interfaces/IHeart.sol::2 => pragma solidity >=0.8.0;
2022-08-olympus/src/policies/interfaces/IOperator.sol::2 => pragma solidity >=0.8.0;

[G-12] Prefix increments cheaper than Postfix increments

++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)
Saves 5 gas PER LOOP

2022-08-olympus/src/utils/KernelUtils.sol::49 => i++;
2022-08-olympus/src/utils/KernelUtils.sol::64 => i++;

[G-13] Don't compare boolean expressions to boolean literals

The extran comparison wastes gas
if ( == true) => if (), if ( == false) => if (!)

2022-08-olympus/src/policies/Governance.sol::223 => if (proposalHasBeenActivated[proposalId_] == true) {
2022-08-olympus/src/policies/Governance.sol::306 => if (tokenClaimsForProposal[proposalId_][msg.sender] == true) {

[G-14] 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 and can save gas by doing so.

2022-08-olympus/src/Kernel.sol::439 => function grantRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/Kernel.sol::451 => function revokeRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/modules/INSTR.sol::28 => function VERSION() public pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/INSTR.sol::37 => function getInstructions(uint256 instructionsId_) public view returns (Instruction[] memory) {
2022-08-olympus/src/modules/MINTR.sol::20 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/MINTR.sol::33 => function mintOhm(address to_, uint256 amount_) public permissioned {
2022-08-olympus/src/modules/MINTR.sol::37 => function burnOhm(address from_, uint256 amount_) public permissioned {
2022-08-olympus/src/modules/PRICE.sol::108 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/RANGE.sol::110 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/TRSRY.sol::47 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/VOTES.sol::22 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/VOTES.sol::45 => function transfer(address to_, uint256 amount_) public pure override returns (bool) {
2022-08-olympus/src/policies/Governance.sol::145 => function getMetadata(uint256 proposalId_) public view returns (ProposalMetadata memory) {
2022-08-olympus/src/policies/Governance.sol::151 => function getActiveProposal() public view returns (ActivatedProposal memory) {

[G-15] Not using the named return variables when a function returns, wastes deployment gas

It is not necessary to have both a named return and a return statement.

2022-08-olympus/src/modules/INSTR.sol::28 => function VERSION() public pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/MINTR.sol::25 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/PRICE.sol::113 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/RANGE.sol::115 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/TRSRY.sol::51 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/VOTES.sol::27 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/policies/BondCallback.sol::177 => returns (uint256 in_, uint256 out_)

[G-16] Multiple address mappings can be combined into a single mapping of an address to a struct, where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, 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.

2022-08-olympus/src/modules/TRSRY.sol::33 => mapping(address => mapping(ERC20 => uint256)) public withdrawApproval;
2022-08-olympus/src/modules/TRSRY.sol::39 => mapping(ERC20 => mapping(address => uint256)) public reserveDebt;
2022-08-olympus/src/policies/Governance.sol::102 => mapping(uint256 => mapping(address => uint256)) public userEndorsementsForProposal;
2022-08-olympus/src/policies/Governance.sol::114 => mapping(uint256 => mapping(address => uint256)) public userVotesForProposal;
2022-08-olympus/src/policies/Governance.sol::117 => mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal;

[G-17] Use assembly to check for address(0)

Saves 6 gas per instance if using assembly to check for address(0)

e.g.

assembly {
 if iszero(_addr) {
  mstore(0x00, "zero address")
  revert(0x00, 0x20)
 }
}

instances:

2022-08-olympus/src/Kernel.sol::269 => if (address(getModuleForKeycode[keycode]) != address(0))

[G-18] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read.

Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

2022-08-olympus/src/Kernel.sol::379 => Policy[] memory dependents = moduleDependents[keycode_];

[G-19] 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.

2022-08-olympus/src/Kernel.sol::131 => function getModuleAddress(Keycode keycode_) internal view returns (address) {
2022-08-olympus/src/Kernel.sol::266 => function _installModule(Module newModule_) internal {
2022-08-olympus/src/Kernel.sol::279 => function _upgradeModule(Module newModule_) internal {
2022-08-olympus/src/Kernel.sol::295 => function _activatePolicy(Policy policy_) internal {
2022-08-olympus/src/Kernel.sol::325 => function _deactivatePolicy(Policy policy_) internal {
2022-08-olympus/src/Kernel.sol::351 => function _migrateKernel(Kernel newKernel_) internal {
2022-08-olympus/src/Kernel.sol::378 => function _reconfigurePolicies(Keycode keycode_) internal {
2022-08-olympus/src/Kernel.sol::409 => function _pruneFromDependents(Policy policy_) internal {
2022-08-olympus/src/policies/Heart.sol::111 => function _issueReward(address to_) internal {
2022-08-olympus/src/policies/Operator.sol::652 => function _addObservation() internal {

[G-20] State variables only set in the constructor should be declared immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas).

2022-08-olympus/src/policies/BondCallback.sol::43 => aggregator = aggregator_;
2022-08-olympus/src/policies/BondCallback.sol::44 => ohm = ohm_;
2022-08-olympus/src/policies/Heart.sol::60 => _operator = operator_;
2022-08-olympus/src/policies/Operator.sol::144 => _status = Status({low: regen, high: regen});

[G-21] internal functions not called by the contract should be removed to save deployment gas

If the functions are required by an interface, the contract should inherit from that interface and use the override keyword

2022-08-olympus/src/Kernel.sol::131 => function getModuleAddress(Keycode keycode_) internal view returns (address) {

Gas Optimizations

Low effect on readability

[G-01] Use != 0 instead of > 0 for unsigned integers.

A uint can't be below zero, so != 0 is sufficient and is gas more efficient.

1 instance:

Consider replacing > by !=.

save 3 gas

[G-02] Unnecessary initialization of variable

Some data type have a default value which is already the desired one. The default value of uint is 0, it is so unnecessary to initialize these again.

3 instances:

Consider removing = 0

save 3 gas each

[G-03] Transformation of post-increment to pre-increment

A pre-increment is cheaper than a post one. When it is possible, it is a good practice to apply pre-increment.

5 instances:

Consider transforming those.

With those changes, these evolutions in gas average report can be observe:

Operator: operate: 122263 -> 122255 (-8)

[G-04] Expression like x = x + y are cheaper than x += y for states variables.

4 instances:

Consider replacing += and -=

With those changes, these evolutions in gas average report can be observe:

OlympusPrice: Deployment: 1117743 -> 1115143 (-2600)
OlympusHeart: Deployment: 934119 -> 932719 (-1400)
OlympusHeart: beat: 29228 -> 29221 (-7)

[G-05] Some operations can be marked unchecked

If an operation can't overflow, it is cheaper to mark it as unchecked to avoid the automatic check of overflow.
In this case:

while  (price_ >=  10)  {
	price_ = price_ /  10;
	decimals++;
}

The operation can't overflow or undeflow

1 instances

Consider marking it unchecked

With this changes, these evolutions in gas average report can be observe:

Operator: Deployment: 4679925 -> 4670317 (-9608)
Operator: operate: 122263 -> 121936 (-327)

This part can already be subjected to two improvements, however this one is still largely ineffective, especially for large numbers up to 2^256. It would be very useful to import a log10 function from an external mathematical library. The gain can be very important.

[G-06] Unnecessary public constant

Declaring a private constant is cheaper than a public one. In some case, a constant can be declared as private to save gas. It is the case if the constant don't need to be called outside the contract. A user could still read the value directly in the code instead of calling it, if needed.

8 instances:

Consider changing those constants to private. (The code still pass all the test with these changes.)

With those changes, these evolutions in gas average report can be observe:

OlympusRange: Deployment: 1125279 -> 1121272 (-4007)
OlympusRange: spread: 655 -> 545 (-110)
OlympusGovernance: Deployment: 1638243 -> 1604601 (-33642)
OlympusGovernance: activateProposal: 52753 -> 52730 (-23)
OlympusGovernance: configureDependencies: 48513 -> 48490 (-23)
OlympusGovernance: endorseProposal: 39015 -> 39037 (+22)
OlympusGovernance: executeProposal: 171376 -> 171467 (+91)
OlympusGovernance: getMetadata: 2195 -> 2172 (-23)
OlympusGovernance: isActive: 696 -> 740 (+44)
OlympusGovernance: noVotesForProposal: 549 -> 571 (+22)
OlympusGovernance: proposalHasBeenActivated:486 -> 463 (-23)
OlympusGovernance: reclaimVotes: 10009 -> 9927 (82)
OlympusGovernance: requestPermissions: 2953 -> 2997 (+44)
OlympusGovernance: tokenClaimsForProposal: 684 -> 728 (+44)
OlympusGovernance: totalEndorsementsForProposal: 529 -> 506 (-23)
OlympusGovernance: userEndorsementsForProposal: 727 ->  639 (-88)
OlympusGovernance: userVotesForProposal: 662 -> 706 (-23)
OlympusGovernance: vote: 61568 -> 61612 (+44)
OlympusGovernance: yesVotesForProposal: 506 -> 483 (-23)
Operator: Deployment: 4679925 -> 4671717 (-8208)
Operator: auctioneer: 437 -> 372 (-65)
Operator: callback: 439 -> 372 (-67)
Operator: config: 1224 -> 1246 (+19)
Operator: configureDependencies: 121016 -> 121038 (+22)
Operator: fullCapacity: 5237 -> 5204 (-33)
Operator: initialize: 316017 -> 315844 (-173)
Operator: initialized: 1356 -> 1379 (+23)
Operator: isActive: 439 -> 373 (-66)
Operator: operate: 122263 -> 122281 (+18)
Operator: regenerate: 17622 -> 17612 (-10)
Operator: requestPermissions: 6634 -> 6656 (+22)
Operator: setBondContracts: 5267 -> 5289 (+22)
Operator: setRegenParams: 11480 -> 11413 (-67)
Operator: setSpreads: 9650 -> 9672 (+22)
Operator: setThresholdFactor: 12113 -> 12135 (+22)
Operator: status: 8988 -> 9010 (+22)
Operator: swap: 54322 -> 54342 (+20)
Operator: toggleActive: 7460 -> 7482 (+22)

[G-07] Using storage instead of memory can be cheaper.

A storage structure is pre allocated by the contract, by contrast, a memory one is newly created. Depending on the case both can be used to optimize the gas cost because simply, a storage is cheaper to create but more expensive to read from and to return and a memory on the other hand is more expensive to create but cheaper to read from and to return. We can optimize with trials and errors instead of complex calculations (which will probably work a bit better, but it's not done here).

Following this, we can deduce 7 cases that can be swapped to optimize runtime cost and deployment cost:

Consider changing memory to storage in these lines

With these changes, these evolutions in gas average report can be observed:

Kernel: Deployment:  1473364->1456343 (-17021)
OlympusRange: Deployment:   1125279 -> 1121272 (-4007)
OlympusRange: spread: 655 -> 545 (-110)
BondCallback: Deployment: 1408325 ->  1391912  (-16413)
BondCallback: amountsForMarket: 1921 -> 1669 (-252)
OlympusGovernance: Deployment: 1638243 -> 1596194 (-42049)
OlympusGovernance: activateProposal: 52753 -> 51723 (-1030)
Operator: Deployment: 4679925 -> 4566769 (-113156)
Operator: fullCapacity: 5237 -> 5182 (-55)
Operator: initialize: 316017 -> 315911 (-106)
Operator: operate: 122263 -> 118511 (-3752)
Operator: regenerate: 17622 -> 17593 (-29)
ModuleTestFixture :Deployment: 422065 -> 399069 (-22996)

[G-08] Using calldata instead of memory for read only argument in external function

If a function parameter is read only, it is cheaper in gas to use calldata instead of memory.

4 instances:

Consider changing memory to calldata in these lines/

With these changes, these evolutions in gas average report can be observed:

OlympusPrice: Deployment: 1117743 -> 1101930 (-15813)
OlympusPrice: initialize: 432495 -> 430562 (-1933)
BondCallback: Deployment: 1408325 -> 1386305 (-22020)
BondCallback: batchToTreasury: 12729 -> 12543 (-186)
OlympusPriceConfig: initialize: 491657 -> 486274 (-5383)
TreasuryCustodian: Deployment: 739696 -> 719277 (-20419)
TreasuryCustodian: revokePolicyApprovals: 6956 -> 6842 (-114)

Medium effect in use

[G-09] external function for the admin can be marked as payable

If a function is guaranteed to revert when called by a normal user, this function can be marked as payable to avoid the check to know if a payment is provided.

2 instances:

Consider adding payable keyword.

Save 21 gas cost

High effect on readability

[G-10] Optimise function name

Every function have a keccak256 hash, this hash defines the order of the function in the contract. The best the ranking, the minimum the gas usage to access the function. Each time a function is called, the EVM need to pass through all the functions better ranked (going through a function cost 22 gas), and this operation cost gas. This can be optimized, the ranking is defined by the first four bytes of the kekkack256 hash of the function name. The name can be changed to improve the ranking, which greatly impacts the readability. That's why it's not practical to change all the names, but it's possible to change only the ones of the functions called a lot of times. This change can be done on the following functions according to their number of uses in the tests and their current ranking.

  1. Kernel.sol: f166d9eb - modulePermissions(bytes5,address,bytes4)
    Must outrank: 000dd95d - moduleDependents(bytes5,uint256)
    New name: 00097fbb - modulePermissions_1055(bytes5,address,bytes4)
    Rank: 14 -> 1
    Save 286 gas each call

  2. Kernel.sol: c4d1f8f1 - executeAction(uint8,address)
    Must outrank: 000dd95d - moduleDependents(bytes5,uint256)
    New name: 000a8da2 - executeAction_11563(uint8,address)
    Rank: 11 -> 2
    Save 198 gas each call

  3. MINTR.sol: 1ae7ec2e - KEYCODE()
    Must outrank: 02b1d239 - ohm()
    New name: 00906b26 - KEYCODE_342()
    Rank: 2-> 1
    Save 22 gas each call

  4. RANGE.sol: bf30142b - capacity(bool)
    Must outrank: 00d16739 - regenerate(bool,uint256)
    New name: 00e60c55 - capacity_81(bool)
    Rank: 14 -> 1
    Save 286 gas each call

  5. TRSRY.sol: 1ae7ec2e - KEYCODE()
    Must outrank: 15226b54 - getReserveBalance(address)
    New name: 00906b26 - KEYCODE_342()
    Rank: 2-> 1
    Save 22 gas each call

  6. VOTE.sol: 1ae7ec2e - KEYCODE()
    Must outrank: 06fdde03 - name()
    New name: 00906b26 - KEYCODE_342()
    Rank: 3 -> 1
    Save 44 gas each call

  7. Governance.sol: d1755067 - endorseProposal(uint256)
    Must outrank: 01153876 - proposalHasBeenActivated(uint256)
    New name: 007fedae - endorseProposal_861(uint256)
    Rank: 22 -> 1
    Save 462 gas each call

  8. Governance.sol: 9459b875 - configureDependencies()
    Must outrank: 01153876 - proposalHasBeenActivated(uint256)
    New name: 00aced39 - configureDependencies_1382()
    Rank: 18 -> 2
    Save gas each call

  9. Operator.sol: 7159a618 - operate()
    Must outrank: 01de9ba8 - setReserveFactor(uint32)
    New name: 000b8875 - operate_53()
    Rank: 18 -> 1
    Save 352 gas each call

  10. Operator.sol: ec7404b1 - setActiveStatus(bool)
    Must outrank: 01de9ba8 - setReserveFactor(uint32)
    New name: 00d3138f - setActiveStatus_78(bool)
    Rank: 31 -> 2
    Save 638 gas each call

Consider optimizing these function names.

Governance Does Not Handle the Return Value of Tokens Which May Cause a Contradiction

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Governance.sol#L259
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Governance.sol#L312
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Heart.sol#L112

Vulnerability details

Description

In the following lines of code, there exists a transferFrom call (called in two places) in the governance.sol contract. One of these transfer methods follows an emit event:

        // Example on line 261 of governance.sol

        VOTES.transferFrom(msg.sender, address(this), userVotes);
        emit WalletVoted(activeProposal.proposalId, msg.sender, for_, userVotes);

The return value for the above example is not handled.

Impact

OpenZeppelin's documentation specifies that the ERC20 transferFrom method returns a boolean value to represent the success or the failure of the transaction. Since the return value is not checked, in the event of a failure the emit event is still triggered which may cause downstream applications to record a falsified transaction / state of the contract in the above example. This was also observed in the Heart.sol contract when attempting to issue rewards.

Recommendations

I recommend implementing the following piece of code where voting tokens are being handled to ensure transactions are successful:

        bool success = VOTES.transferFrom(msg.sender, address(this), userVotes);
        require(success, "Failed to transfer voting tokens from the user");

        emit WalletVoted(activeProposal.proposalId, msg.sender, for_, userVotes);

Gas Optimizations

Kernel.sol

  • L70/88/119/223/229 - Gas can be saved if instead of using a modifier a private view function is used, this would reduce all the costs of validating the access control of an address.

  • L397 - When you want to set a variable with its default value, it is less expensive not to set it since it has that default value, this reduces some gas units without losing understanding of the code.

KernelUtils.sol

  • L43/58 - When you want to set a variable with its default value, it is less expensive not to set it since it has that default value, this reduces some gas units without losing understanding of the code.

  • L49/64 - It is less expensive to do the ++i operation than to do i++, without losing understanding of the code.

TRSRY.sol

  • L131/132 - When previously it is validated that the operation returns a value without overflow/underflow, it can be wrapped with unchecked in order to spend less gas when performing the mathematical operation. What could be unchecked is the operation, not the validation of the if.

PRICE.sol

  • L6 - The ERC20 class is imported, but it is never used, this generates unnecessary extra gas costs.

  • L136/138 - When it is previously validated that the operation returns a value without overflow/underflow, it can be wrapped with unchecked in order to spend less gas when performing the mathematical operation. What could be unchecked is the operation, not the validation of the if.

BondCallback.sol

  • L120 - It is necessary to validate that outputAmount_ > inputAmount_ so that it does not throw an exception without any underflow message. In addition, the outputAmount_ - inputAmount_ operation can become unchecked so that what has already been validated is not validated.

  • L223/306 - Instead of validating "validation == true" or "validation == false" it is much simpler and less expensive to validate "validation" or "!validation".

  • L278 - In a for loop instead of consulting the length of the array to be iterated in each iteration, the least expensive thing is to create a variable in memory of the value of array.length

RANGE.sol

  • L133/145 - It is less expensive in a validation that the less expensive operation is ahead, since it avoids executing the second more expensive validation.

Heart.sun

  • L26 - an error is created that is not used anywhere, it should be eliminated.

TreasuryCustodian.sol

  • L11 - an error is created that is not used anywhere, it should be eliminated.

Operator.sol

  • L188 - The modifier can generate much less gas cost, if instead of a modifier it were a private view function.

  • L488/670/675 - It is less expensive to make ++variable than to make variable++, without modifying the understanding of the code.

Gas Optimizations

2022-08-olympus-code4rena QA Report

Files Description Table

File Name SHA-1 Hash
2022-08-olympus/src/modules/PRICE.sol eb3c920eaaf30e31cffbef13d8510dc18341d5ab
2022-08-olympus/src/Kernel.sol 702fd864c142f5c93781482371d168379d6b10df
2022-08-olympus/src/utils/KernelUtils.sol b103389226af6aa16880e2568c5de4de143d7950
2022-08-olympus/src/modules/INSTR.sol b2c9521b73b50db74fa17b59b12e9b25269a83cc
2022-08-olympus/src/modules/RANGE.sol 3b34f485fcb242d7a254307b239f055524ed2e6b
2022-08-olympus/src/modules/TRSRY.sol 7626a2b1c998b640c51d08c8e665498ba73efca0
2022-08-olympus/src/modules/VOTES.sol 5e22b6aff627c48b8cedabbede375c1f5a468985
2022-08-olympus/src/modules/MINTR.sol e3ba147c72850c7463b5a3da587a77550ad6da1e
2022-08-olympus/src/modules/PRICE.sol eb3c920eaaf30e31cffbef13d8510dc18341d5ab
2022-08-olympus/src/policies/PriceConfig.sol 988825fff850ed5efb9713ac352628ca77f78cbc
2022-08-olympus/src/policies/BondCallback.sol 6be071dd7f9ccc578d929670bff27ed8f72a9f62
2022-08-olympus/src/policies/TreasuryCustodian.sol 752907434e36330542e6f3f18ae2e3a89e746c52
2022-08-olympus/src/policies/Governance.sol 88ae920ee84d217efdd686cb29939d820cbbd632
2022-08-olympus/src/policies/Operator.sol f185cfaa901424dd55c533b88a7b801f08b35367
2022-08-olympus/src/policies/VoterRegistration.sol 74328138074d3796580439636955db37e4ffa9b2
2022-08-olympus/src/policies/Heart.sol f1a6dcb7778663cba55f2278e6e2d9044b7ec69c

Gas Optimizations

[G-01]: For-Loops: No need to explicitly initialize variables with default values

Impact

If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0, etc depending on the data type). If you explicitly initialize it with its default value, you are just wasting gas.

Code Affected

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/Kernel.sol#L397

for (uint256 i = 0; i < reqLength; ) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/utils/KernelUtils.sol#L43

for (uint256 i = 0; i < 5; ) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/utils/KernelUtils.sol#L58

for (uint256 i = 0; i < 32; ) {

Mitigation

Do not initialize variables with default values.

Tools used

VS Code

[G-02]: >= is cheaper than >

Impact

Non-strict inequalities (>=) are cheaper than strict ones (>). This is due to some supplementary checks (ISZERO, 3 gas).

Code Affected

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L90

if (exponent > 38) revert Price_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L244-L249

if (
    wallSpread_ > 10000 ||
    wallSpread_ < 100 ||
    cushionSpread_ > 10000 ||
    cushionSpread_ < 100 ||
    cushionSpread_ > wallSpread_

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L215

if (startObservations_.length != numObs || lastObservationTime_ > uint48(block.timestamp))

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L264

if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L212

if (block.timestamp > proposal.submissionTimestamp + ACTIVATION_DEADLINE) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L103

if (configParams[1] > uint256(7 days) || configParams[1] < uint256(1 days))

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L108

if (configParams[3] < uint32(1 hours) || configParams[3] > configParams[1])

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L111

if (configParams[4] > 10000 || configParams[4] < 100) revert Operator_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L113-L115

        if (
            configParams[5] < 1 hours ||
            configParams[6] > configParams[7] ||

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L237

if (currentPrice > range.cushion.low.price || currentPrice < range.wall.low.price) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L243

if (currentPrice < range.cushion.low.price && currentPrice > range.wall.low.price) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L254

currentPrice < range.cushion.high.price || currentPrice > range.wall.high.price

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L262

currentPrice > range.cushion.high.price && currentPrice < range.wall.high.price

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L518

if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L533

if (duration_ > uint256(7 days) || duration_ < uint256(1 days))

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L536

if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L550

if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L565

if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L758

if (amountOut > RANGE.capacity(false)) revert Operator_InsufficientCapacity();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L769

if (amountOut > RANGE.capacity(true)) revert Operator_InsufficientCapacity();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/utils/KernelUtils.sol#L46

if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_); // A-Z only

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/utils/KernelUtils.sol#L60

if ((char < 0x61 || char > 0x7A) && char != 0x5f && char != 0x00) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L165

if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L171

if (updatedAt < block.timestamp - uint256(observationFrequency))

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L133

if (capacity_ < _range.high.threshold && _range.high.active) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L145

if (capacity_ < _range.low.threshold && _range.low.active) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L131

if (oldDebt < amount_) totalDebt[token_] += amount_ - oldDebt;

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L144

if (approval < amount_) revert TRSRY_NotApproved();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/BondCallback.sol#L114

if (quoteToken.balanceOf(address(this)) < priorBalances[quoteToken] + inputAmount_)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L164

if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L227

if (block.timestamp < activeProposal.activationTimestamp + GRACE_PERIOD) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L268

if (netVotes * 100 < VOTES.totalSupply() * EXECUTION_THRESHOLD) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L272

if (block.timestamp < activeProposal.activationTimestamp + EXECUTION_TIMELOCK) {

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Heart.sol#L94

if (block.timestamp < lastBeat + frequency()) revert Heart_OutOfCycle();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L106

if (configParams[2] < uint32(10_000)) revert Operator_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L289

if (amountOut < minAmountOut_)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L320

if (amountOut < minAmountOut_)

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L535

if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L741

RANGE.capacity(high_) < auctioneer.currentCapacity(market))

Mitigation

Replace > / < with >= / <= without breaking the logic of the code.

Example

Change

if (exponent > 38) revert Price_InvalidParams();

to

if (exponent >= 39) revert Price_InvalidParams();

Tools used

VS Code

QA Report

-> EVENT IS MISSING INDEXED FIELDS
Each event should use three indexed fields if there are three or more fields.

https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#:~:text=event%20PermissionsUpdated(,bool%20granted_
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=event%20ApprovedForWithdrawal(address%20indexed%20policy_%2C%20ERC20%20indexed%20token_%2C%20uint256%20amount_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=event%20DebtIncurred(ERC20%20indexed%20token_%2C%20address%20indexed%20policy_%2C%20uint256%20amount_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=event%20DebtRepaid(ERC20%20indexed%20token_%2C%20address%20indexed%20policy_%2C%20uint256%20amount_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=event%20DebtSet(ERC20%20indexed%20token_%2C%20address%20indexed%20policy_%2C%20uint256%20amount_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/RANGE.sol#:~:text=event%20WallUp(bool%20high_%2C%20uint256%20timestamp_%2C%20uint256%20capacity_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/RANGE.sol#:~:text=event%20WallDown(bool%20high_%2C%20uint256%20timestamp_%2C%20uint256%20capacity_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/RANGE.sol#:~:text=event%20CushionUp(bool%20high_%2C%20uint256%20timestamp_%2C%20uint256%20capacity_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/RANGE.sol#:~:text=event%20CushionDown(bool%20high_%2C%20uint256%20timestamp_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/RANGE.sol#:~:text=event%20PricesChanged(,uint256%20wallHighPrice_
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=event%20NewObservation(uint256%20timestamp_%2C%20uint256%20price_%2C%20uint256%20movingAverage_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=event%20CushionParamsChanged(uint32%20duration_%2C%20uint32%20debtBuffer_%2C%20uint32%20depositInterval_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Governance.sol#:~:text=event%20ProposalEndorsed(uint256%20proposalId%2C%20address%20voter%2C%20uint256%20amount)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Governance.sol#:~:text=event%20WalletVoted(uint256%20proposalId%2C%20address%20voter%2C%20bool%20for_%2C%20uint256%20userVotes)%3B

-> _SAFEMINT() SHOULD BE USED RATHER THAN _MINT() WHEREVER POSSIBLE

_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver

https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/MINTR.sol#:~:text=ohm.mint(to_%2C%20amount_)%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/VOTES.sol#:~:text=_mint(wallet_%2C%20amount_)%3B

Gas Optimizations

[G-01] Redundant zero initialization

Solidity does not recognize null as a value, so uint variables are initialized to zero. Setting a uint variable to zero is redundant and can waste gas.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/utils/KernelUtils.sol#L43
https://github.com/code-423n4/2022-08-olympus/tree/main/src/utils/KernelUtils.sol#L58
https://github.com/code-423n4/2022-08-olympus/tree/main/src/test/lib/UserFactory.sol#L25
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L397

Recommended Mitigation Steps

Remove the redundant zero initialization
uint256 i; instead of uint256 i = 0;

[G-02] Use prefix not postfix in loops

Using a prefix increment (++i) instead of a postfix increment (i++) saves gas for each loop cycle and so can have a big gas impact when the loop executes on a large number of elements.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/utils/KernelUtils.sol#L49
https://github.com/code-423n4/2022-08-olympus/tree/main/src/utils/KernelUtils.sol#L64

Recommended Mitigation Steps

Use prefix not postfix to increment in a loop

[G-03] Use iszero assembly for zero checks

Comparing a value to zero can be done using the iszero EVM opcode. This can save gas

Source from t11s https://twitter.com/transmissions11/status/1474465495243898885

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L79
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L185
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L221
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L242
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L268
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L106
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/INSTR.sol#L48
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L565
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L183
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L188
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L243
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L298
https://github.com/code-423n4/2022-08-olympus/tree/main/src/utils/KernelUtils.sol#L36

Recommended Mitigation Steps

Use the assembly iszero evm opcode to compare values to zero

[G-04] Add payable to functions that won't receive ETH

Identifying a function as payable saves gas. Functions that have a modifier like onlyOwner or auth cannot be called by normal users and will not mistakenly receive ETH. These functions can be payable to save gas.

There are many functions that have the auth modifier in the contracts. Some examples are
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L439
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L451

Recommended Mitigation Steps

Add payable to these functions for gas savings

[G-05] Add payable to constructors that won't receive ETH

Identifying a constructor as payable saves gas. Constructors should only be called by the admin or deployer and should not mistakenly receive ETH. Constructors can be payable to save gas.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/MINTR.sol#L15
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L71
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/RANGE.sol#L77
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/VOTES.sol#L16
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/TRSRY.sol#L45
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/INSTR.sol#L20
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/TreasuryCustodian.sol#L24
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/BondCallback.sol#L38
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/VoterRegistration.sol#L16
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/PriceConfig.sol#L15
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L92
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L59
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Heart.sol#L54
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L65
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L85
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L115
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L217

Recommended Mitigation Steps

Add payable to these functions for gas savings

[G-06] Use internal function in place of modifier

An internal function can save gas vs. a modifier. A modifier inlines the code of the original function but an internal function does not.

Source https://blog.polymath.network/solidity-tips-and-tricks-to-save-gas-and-reduce-bytecode-size-c44580b218e6#dde7

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L70
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L88
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L119
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L223
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L229

Recommended Mitigation Steps

Use internal functions in place of modifiers to save gas.

[G-07] Use uint not bool

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L62
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L63
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L66
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Heart.sol#L33
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L113
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L207
https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L394

Recommended Mitigation Steps

Replace bool variables with uints

[G-08] Use uint256 not smaller ints

From the solidity docs

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L44
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L47
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L50
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L53
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L56
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L59

Recommended Mitigation Steps

Replace smaller int or uint variables with uint256 variables

[G-09] Use simple comparison in if statement

The comparison operators >= and <= use more gas than >, <, or ==. Replacing the >= and ≤ operators with a comparison operator that has an opcode in the EVM saves gas

The existing code is
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L683-L693

if (currentPrice <= movingAverage) {
    if (!regen.observations[regen.nextObservation]) {
        _status.high.observations[regen.nextObservation] = true;
        _status.high.count++;
    }
} else {
    if (regen.observations[regen.nextObservation]) {
        _status.high.observations[regen.nextObservation] = false;
        _status.high.count--;
    }
}

A simple comparison can be used for gas savings by reversing the logic

if (currentPrice > movingAverage) {
    if (regen.observations[regen.nextObservation]) {
        _status.high.observations[regen.nextObservation] = false;
        _status.high.count--;
    }
} else {
    if (!regen.observations[regen.nextObservation]) {
        _status.high.observations[regen.nextObservation] = true;
        _status.high.count++;
    }
}

Recommended Mitigation Steps

Replace the comparison operator and reverse the logic to save gas using the suggestions above

[G-10] Bitshift for divide by 2

When multiply or dividing by a power of two, it is cheaper to bitshift than to use standard math operations.

There is a divide by 2 operation on these lines
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L372
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L427

Recommended Mitigation Steps

Bitshift right by one bit instead of dividing by 2 to save gas

[G-11] Non-public variables save gas

Many constant variables are public, but changing the visibility of these variables to private or internal can save gas.

Locations where this was found include
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/PRICE.sol#L59
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/RANGE.sol#L65
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L89
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L121
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L124
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L127
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L130
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L133
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Governance.sol#L137
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/MINTR.sol#L9
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/RANGE.sol#L68
https://github.com/code-423n4/2022-08-olympus/tree/main/src/modules/RANGE.sol#L71
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L82
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L83
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L85
https://github.com/code-423n4/2022-08-olympus/tree/main/src/policies/Operator.sol#L86

Recommended Mitigation Steps

Declare some public variables as private or internal to save gas

[G-12] Write contracts in vyper

The contracts are all written entirely in solidity. Writing contracts with vyper instead of solidity can save gas.

Source https://twitter.com/eiber_david/status/1515737811881807876
doggo demonstrates https://twitter.com/fubuloubu/status/1528179581974417414?t=-hcq_26JFDaHdAQZ-wYxCA&s=19

Recommended Mitigation Steps

Write some or all of the contracts in vyper to save gas

Gas Optimizations

gas optimization

Don't Initialize Variables with Default Value

examples

2022-08-olympus\src\Kernel.sol::397 => for (uint256 i = 0; i < reqLength; ) {
2022-08-olympus\src\utils\KernelUtils.sol::43 => for (uint256 i = 0; i < 5; ) {
2022-08-olympus\src\utils\KernelUtils.sol::58 => for (uint256 i = 0; i < 32; ) {

Use != 0 instead of > 0 for Unsigned Integer Comparison

examples

2022-08-olympus\src\policies\Governance.sol::247 => if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) {
2022-08-olympus\src\utils\KernelUtils.sol::46 => if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_); // A-Z only
2022-08-olympus\src\utils\KernelUtils.sol::60 => if ((char < 0x61 || char > 0x7A) && char != 0x5f && char != 0x00) {

Long Revert Strings

examples

2022-08-olympus\src\modules\PRICE.sol::4 => import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
2022-08-olympus\src\modules\TRSRY.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
2022-08-olympus\src\modules\VOTES.sol::18 => ERC20("OlympusDAO Dummy Voting Tokens", "VOTES", 0)
2022-08-olympus\src\policies\BondCallback.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
2022-08-olympus\src\policies\Heart.sol::4 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
2022-08-olympus\src\policies\Heart.sol::7 => import {IOperator} from "policies/interfaces/IOperator.sol";
2022-08-olympus\src\policies\Operator.sol::4 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
2022-08-olympus\src\policies\Operator.sol::7 => import {IOperator} from "policies/interfaces/IOperator.sol";

Use Shift Right/Left instead of Division/Multiplication if possible

examples

2022-08-olympus\src\interfaces\IBondAuctioneer.sol::41 => /// @dev                        Should be calculated as: (payoutDecimals - quoteDecimals) - ((payoutPriceDecimals - quotePriceDecimals) / 2)
2022-08-olympus\src\policies\Operator.sol::372 => int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);
2022-08-olympus\src\policies\Operator.sol::419 => uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;
2022-08-olympus\src\policies\Operator.sol::420 => uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;
2022-08-olympus\src\policies\Operator.sol::427 => int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);
2022-08-olympus\src\policies\Operator.sol::786 => ) * (FACTOR_SCALE + RANGE.spread(true) * 2)) /

Gas Optimizations

QA Report

[1]

  • Important address changes should follow a "propose" pattern instead of directly setting variables in order to avoid human errors.
  • Missing validation on address changes.

e.g. Kernel.executeAction(Actions,address).target_ on Actions.ChangeExecutor and Actions.ChangeAdmin:

Before

        } else if (action_ == Actions.ChangeExecutor) {
            executor = target_;
        } else if (action_ == Actions.ChangeAdmin) {
            admin = target_;
        }

After

        } else if (action_ == Actions.ChangeExecutor) {
            if(target_ == address(0)) revert Kernel_InvalidAddress();
            proposedExecutor = target_;
        } else if (action_ == Actions.ChangeAdmin) {
            if(target_ == address(0)) revert Kernel_InvalidAddress();
            proposedAdmin = target_;
        }

        // ...

        function acceptProposedExecutor() external {
            if(msg.sender != proposedExecutor) revert Kernel_InvalidExecutor();
            executor = proposedExecutor;
        }
        function acceptProposedAdmin() external {
            if(msg.sender != proposedAdmin) revert Kernel_InvalidAdmin();
            admin = proposedAdmin;
        }

[2]
Missing RewardUpdated event on constructor from Heart.sol

Before

    constructor(
        Kernel kernel_,
        IOperator operator_,
        ERC20 rewardToken_,
        uint256 reward_
    ) Policy(kernel_) {
        _operator = operator_;

        active = true;
        lastBeat = block.timestamp;
        rewardToken = rewardToken_;
        reward = reward_;
    }

After

    constructor(
        Kernel kernel_,
        IOperator operator_,
        ERC20 rewardToken_,
        uint256 reward_
    ) Policy(kernel_) {
        _operator = operator_;

        active = true;
        lastBeat = block.timestamp;
        rewardToken = rewardToken_;
        reward = reward_;
        emit RewardUpdated(rewardToken_; reward_);
    }

Gas Optimizations

Gas optimization in src/policies/Governance.sol function endorseProposal(uint256 proposalId_)

Before

        // undo any previous endorsement the user made on these instructions
        uint256 previousEndorsement = userEndorsementsForProposal[proposalId_][msg.sender];
        totalEndorsementsForProposal[proposalId_] -= previousEndorsement;

        // reapply user endorsements with most up-to-date votes
        userEndorsementsForProposal[proposalId_][msg.sender] = userVotes;
        totalEndorsementsForProposal[proposalId_] += userVotes;

// gas reporter
│ endorseProposal                                        ┆ 6874            ┆ 39015  ┆ 30774  ┆ 52674  ┆ 43      │

After

        // undo any previous endorsement the user made on these instructions
        uint256 previousEndorsement = userEndorsementsForProposal[proposalId_][msg.sender];

        // reapply user endorsements with most up-to-date votes
        userEndorsementsForProposal[proposalId_][msg.sender] = userVotes;
        totalEndorsementsForProposal[proposalId_] += userVotes - previousEndorsement;
// gas reporter
│ endorseProposal                                        ┆ 6449            ┆ 38610  ┆ 30349  ┆ 52249  ┆ 43      │

QA Report

Oracle price could not be fresh

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L161
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L170

Vulnerability details

Vulnerability

In PRICE.getCurrentPrice, we are using latestRoundData, but there are no validations that the data is not stale.

The current code is:

    function getCurrentPrice() public view returns (uint256) {
        if (!initialized) revert Price_NotInitialized();
        // Get prices from feeds
        uint256 ohmEthPrice;
        uint256 reserveEthPrice;
        {
            (, int256 ohmEthPriceInt, , uint256 updatedAt, ) = _ohmEthPriceFeed.latestRoundData();
            // Use a multiple of observation frequency to determine what is too old to use.
            // Price feeds will not provide an updated answer if the data doesn't change much.
            // This would be similar to if the feed just stopped updating; therefore, we need a cutoff.
            if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))
                revert Price_BadFeed(address(_ohmEthPriceFeed));
            ohmEthPrice = uint256(ohmEthPriceInt);

            int256 reserveEthPriceInt;
            (, reserveEthPriceInt, , updatedAt, ) = _reserveEthPriceFeed.latestRoundData();
            if (updatedAt < block.timestamp - uint256(observationFrequency))
                revert Price_BadFeed(address(_reserveEthPriceFeed));
            reserveEthPrice = uint256(reserveEthPriceInt);
        }
        // Convert to OHM/RESERVE price
        uint256 currentPrice = (ohmEthPrice * _scaleFactor) / reserveEthPrice;
        return currentPrice;
    }

But is missing the checks to validate the data is stale:

-           (, int256 ohmEthPriceInt, , uint256 updatedAt, ) = _ohmEthPriceFeed.latestRoundData();
+           (uint80 round, int256 ohmEthPriceInt, , uint256 updatedAt, uint80 answeredInRound) = _ohmEthPriceFeed.latestRoundData();
            // Use a multiple of observation frequency to determine what is too old to use.
            // Price feeds will not provide an updated answer if the data doesn't change much.
            // This would be similar to if the feed just stopped updating; therefore, we need a cutoff.
+           if (ohmEthPriceInt <= 0 || answeredInRound < round)
+               revert Price_BadFeed(address(_ohmEthPriceFeed));
            if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))
                revert Price_BadFeed(address(_ohmEthPriceFeed));
            ohmEthPrice = uint256(ohmEthPriceInt);

            int256 reserveEthPriceInt;
-           (, reserveEthPriceInt, , updatedAt, ) = _reserveEthPriceFeed.latestRoundData();
+           (uint80 round, reserveEthPriceInt, , updatedAt, uint80 answeredInRound) = _reserveEthPriceFeed.latestRoundData();
+           if (reserveEthPriceInt <= 0 || answeredInRound < round)
+               revert Price_BadFeed(address(_ohmEthPriceFeed));
            if (updatedAt < block.timestamp - uint256(observationFrequency))
                revert Price_BadFeed(address(_reserveEthPriceFeed));
            reserveEthPrice = uint256(reserveEthPriceInt);

Note that an inaccurate price data could quickly lead to a large loss of funds.

Out-of-cycle beats in the Heart policy cause reward token drains

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Heart.sol#L92-L109

Vulnerability details

Impact

The beat function can be called multiple times within the same block or in next blocks without triggering the Heart_OutOfCycle error. The function is expected to be called not often than once in frequency times (a related test case). However, if a beat gets delayed for a longer period or observation frequency was reduced, further beats will be able to circumvent the restriction. As a result, the caller of beat (which can be any address) will get extra amount of reward tokens for calling beat multiple times in a block.

Proof of Concept

Here's a Forge test case that reproduces the issue (part of HeartTest):

function testCorrectness_beatOutOfCycle() public {
    uint256 frequency = heart.frequency();
    vm.warp(block.timestamp + frequency);
    uint256 startBalance = rewardToken.balanceOf(address(this));

    heart.beat();

    vm.warp(block.timestamp + frequency);
    heart.beat();

    vm.warp(block.timestamp + frequency * 5);
    heart.beat();
    heart.beat();

    vm.warp(block.timestamp + 1);
    heart.beat();
    heart.beat();
    heart.beat();

    uint256 endBalance = rewardToken.balanceOf(address(this));
    assertEq(endBalance, startBalance + heart.reward() * 7);
}

First two beats work as expected. The third beat is delayed, which sets lastBeat to an older timestamp. This makes it possible to call beat multiple beats in the same block or call beat again in the next block. In the end, the caller was rewarded 7 times for calling beat, including the out-of-cycle calls. Since calling beat multiple times in a block triggers changes in the Price policy and the Operator contract only once (during the first call in a block), the rest calls get rewarded for doing no useful work.

Another situation than can cause the issue is when price observation frequency was reduced:

function testCorrectness_beatOutOfCycle2() public {
    // frequency was set to 8 hours initially
    uint256 frequency = heart.frequency();
    vm.warp(block.timestamp + frequency);
    uint256 startBalance = rewardToken.balanceOf(address(this));

    heart.beat();

    price.changeObservationFrequency(2 hours);

    vm.warp(block.timestamp + frequency);
    heart.beat();
    heart.beat();
    heart.beat();
    heart.beat();

    uint256 endBalance = rewardToken.balanceOf(address(this));
    assertEq(endBalance, startBalance + heart.reward() * 5);
}

In this case, the delay is shorter and equals to the old frequency value (it can be even shorter to allow only one extra beat). Such situation can happen when observation frequency was reduced but those who trigger beat were not aware or notified about that (or they delayed the next call intentionally). As a result, they'll be able to get extra reward by calling beat multiple times in a block.

Tools Used

Recommended Mitigation Steps

In the beat function, set lastBeat to block.timestamp.

QA Report

Kernel.sol

  • L236 to L258 - This type of validation structure can be replaced by a switch, where the parameter passed first is action and then each case is the value of the Actions enum.

  • L439 - Currently the functionalities of setting the role to an address and creating a new role are coupled. This can generate confusion since unconsciously roles can be created due to mistakes in the name of the role, since that is not really the objective. Therefore, it would be best to have a function to create a new role and another function to grant the role to a specific address.

TRSRY.sol

  • L20/24/27/28/29/33/36/39/59/66/77/92/105/123/139 - In multiple functions, an ERC20 with the name token_ is requested as input, mostly. But no internal function of this type of token is ever used. This generates extra gas costs, since it could simply be used as an address.

  • L33/37 - The Executor can define who mints and burns tokens, this has a very high centralization point, since the address that has access to it, passing the require permissioned, will be able to burn and mint tokens to whoever it wants. Generating a very high risk point.

VOTES.sol

  • L35/39/51 - The Executor can define who lies, burns and transfers the token of whoever he wants, this has a very high centralization point, since the address that has access to it, pass the require permissioned, can burn, lie and transfer tokens to whoever he wants. Generating a very high risk point.

RANGE.sol

  • L33 - If the struct that is created has only one element, it would be nicer for it to be just a variable and not a struct.

Gas Optimizations

Unnecessary equals boolean

Boolean variables can be checked within conditionals directly without the use of equality operators to true/false.

Code instances:

    Governance.sol, 223: if (proposalHasBeenActivated[proposalId_] == true) {
    Governance.sol, 306: if (tokenClaimsForProposal[proposalId_][msg.sender] == true) {
    Operator.sol, 355: if (id_ == RANGE.market(false)) {
    Operator.sol, 351: if (id_ == RANGE.market(true)) {

Change transferFrom to transfer

'transferFrom(address(this), , **)' could be replaced by the following more gas efficient 'transfer(, **)'
This replacement is more gas efficient and improves the code quality.

Code instance:

    Governance.sol, 312 : VOTES.transferFrom(address(this), msg.sender, userVotes);

Caching array length can save gas

Caching the array length is more gas efficient.
This is because access to a local variable in solidity is more efficient than query storage / calldata / memory.
We recommend to change from:

for (uint256 i=0; i<array.length; i++) { ... }

to:

uint len = array.length  
for (uint256 i=0; i<len; i++) { ... }

Code instance:

    Governance.sol, instructions, 278

Unnecessary index init

In for loops you initialize the index to start from 0, but it already initialized to 0 in default and this assignment cost gas.
It is more clear and gas efficient to declare without assigning 0 and will have the same meaning:

Code instance:

    Kernel.sol, 397

Storage double reading. Could save SLOAD

Reading a storage variable is gas costly (SLOAD). In cases of multiple read of a storage variable in the same scope, caching the first read (i.e saving as a local variable) can save gas and decrease the
overall gas uses. The following is a list of functions and the storage variables that you read twice:

Code instance:

    PRICE.sol: nextObsIndex is read twice in getLastPrice

Rearrange state variables

You can change the order of the storage variables to decrease memory uses.

Code instance:

In Operator.sol,rearranging the storage fields can optimize to: 11 slots from: 13 slots.
The new order of types (you choose the actual variables):
1. Status
2. Config
3. OlympusPrice
4. OlympusRange
5. OlympusTreasury
6. OlympusMinter
7. IBondAuctioneer
8. IBondCallback
9. ERC20
10. ERC20
11. uint32
12. uint8
13. uint8
14. bool
15. bool

Use bytes32 instead of string to save gas whenever possible

Use bytes32 instead of string to save gas whenever possible.
String is a dynamic data structure and therefore is more gas consuming then bytes32.

Code instance:

    OlympusERC20.sol (L39), string internal UNAUTHORIZED = "UNAUTHORIZED"; 

Use != 0 instead of > 0

Using != 0 is slightly cheaper than > 0. (see code-423n4/2021-12-maple-findings#75 for similar issue)

Code instance:

    FullMath.sol, 35: change 'denominator > 0' to 'denominator != 0'

Unnecessary cast

Code instances:

    Role Kernel.sol.grantRole - unnecessary casting Role(role_)
    Kernel Kernel.sol._migrateKernel - unnecessary casting Kernel(newKernel_)

Use unchecked to save gas for certain additive calculations that cannot overflow

You can use unchecked in the following calculations since there is no risk to overflow:

Code instances:

    Operator.sol (L#209) - if ( uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&
    Operator.sol (L#215) - if ( uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&
    Operator.sol (L#395) - payoutToken: ohm, quoteToken: reserve, callbackAddr: address(callback), capacityInQuote: false, capacity: marketCapacity, formattedInitialPrice: initialPrice, formattedMinimumPrice: minimumPrice, debtBuffer: config_.cushionDebtBuffer, vesting: uint48(0), conclusion: uint48(block.timestamp + config_.cushionDuration), depositInterval: config_.cushionDepositInterval, scaleAdjustment: scaleAdjustment
    Governance.sol (L#212) - if (block.timestamp > proposal.submissionTimestamp + ACTIVATION_DEADLINE) {
    Heart.sol (L#94) - if (block.timestamp < lastBeat + frequency()) revert Heart_OutOfCycle(); 
    Operator.sol (L#447) - payoutToken: reserve, quoteToken: ohm, callbackAddr: address(callback), capacityInQuote: false, capacity: marketCapacity, formattedInitialPrice: initialPrice, formattedMinimumPrice: minimumPrice, debtBuffer: config_.cushionDebtBuffer, vesting: uint48(0), conclusion: uint48(block.timestamp + config_.cushionDuration), depositInterval: config_.cushionDepositInterval, scaleAdjustment: scaleAdjustment
    Governance.sol (L#272) - if (block.timestamp < activeProposal.activationTimestamp + EXECUTION_TIMELOCK) {
    Governance.sol (L#227) - if (block.timestamp < activeProposal.activationTimestamp + GRACE_PERIOD) {

Inline one time use functions

The following functions are used exactly once. Therefore you can inline them and save gas and improve code clearness.

Code instances:

    Kernel.sol, _upgradeModule
    Kernel.sol, _activatePolicy
    Kernel.sol, _reconfigurePolicies
    FullMath.sol, mulDiv
    Kernel.sol, _deactivatePolicy
    Operator.sol, _addObservation
    Kernel.sol, _pruneFromDependents
    Kernel.sol, _migrateKernel
    Kernel.sol, _installModule

Cache powers of 10 used several times

You calculate the power of 10 every time you use it instead of caching it once as a constant variable and using it instead.
Fix the following code lines:

Code instances:

Operator.sol, 374 : You should cache the used power of 10 as constant state variable since it's used several times (2): uint256 oracleScale = 10**uint8(int8(PRICE.decimals()) - priceDecimals);

Operator.sol, 419 : You should cache the used power of 10 as constant state variable since it's used several times (2): uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;

Operator.sol, 783 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10ohmDecimals * 10PRICE.decimals(),

Operator.sol, 784 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10**reserveDecimals * RANGE.price(true, true)

Operator.sol, 752 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10**reserveDecimals * RANGE.price(true, false),

Operator.sol, 764 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10**reserveDecimals * RANGE.price(true, true)

Operator.sol, 763 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10ohmDecimals * 10PRICE.decimals(),

Operator.sol, 753 : You should cache the used power of 10 as constant state variable since it's used several times (3): 10ohmDecimals * 10PRICE.decimals()

Operator.sol, 429 : You should cache the used power of 10 as constant state variable since it's used several times (2): uint256 oracleScale = 10**uint8(int8(oracleDecimals) - priceDecimals);

Operator.sol, 418 : You should cache the used power of 10 as constant state variable since it's used several times (2): uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;

Upgrade pragma to at least 0.8.4

Using newer compiler versions and the optimizer gives gas optimizations
and additional safety checks are available for free.

The advantages of versions 0.8.* over <0.8.0 are:

    1. Safemath by default from 0.8.0 (can be more gas efficient than library based safemath.)
    2. Low level inliner : from 0.8.2, leads to cheaper runtime gas. Especially relevant when the contract has small functions. For example, OpenZeppelin libraries typically have a lot of small helper functions and if they are not inlined, they cost an additional 20 to 40 gas because of 2 extra jump instructions and additional stack operations needed for function calls.
    3. Optimizer improvements in packed structs: Before 0.8.3, storing packed structs, in some cases used an additional storage read operation. After EIP-2929, if the slot was already cold, this means unnecessary stack operations and extra deploy time costs. However, if the slot was already warm, this means additional cost of 100 gas alongside the same unnecessary stack operations and extra deploy time costs.
    4. Custom errors from 0.8.4, leads to cheaper deploy time cost and run time cost. Note: the run time cost is only relevant when the revert condition is met. In short, replace revert strings by custom errors.

Code instance:

    OlympusERC20.sol

Do not cache msg.sender

We recommend not to cache msg.sender since calling it is 2 gas while reading a variable is more.

Code instance:

    https://github.com/code-423n4/2022-08-olympus/tree/main/src/Kernel.sol#L219

gap in the two validations of thresholdFactor

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L101
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L263

Vulnerability details

Impact

The setting of the thresholdFactor variable is found in two places: the constructor and the setThresholdFactor() function, nothing is validated in the constructor, therefore it can receive any value even if it is not correct. Instead in setThresholdFactor make it clear that 100 < thresholdFactor_ < 10000 with the phrase "Percent of capacity that the wall should close below, assumes 2 decimals (i.e. 1000 = 10%)."

Recommended Mitigation Steps

The solution would be to add the same validation from line 264 to the constructor.

Agreements & Disclosures

Agreements

If you are a C4 Certified Contributor by commenting or interacting with this repo prior to public release of the contest report, you agree that you have read the Certified Warden docs and agree to be bound by:

To signal your agreement to these terms, add a 👍 emoji to this issue.

Code4rena staff reserves the right to disqualify anyone from this role and similar future opportunities who is unable to participate within the above guidelines.

Disclosures

Sponsors may elect to add team members and contractors to assist in sponsor review and triage. All sponsor representatives added to the repo should comment on this issue to identify themselves.

To ensure contest integrity, the following potential conflicts of interest should also be disclosed with a comment in this issue:

  1. any sponsor staff or sponsor contractors who are also participating as wardens
  2. any wardens hired to assist with sponsor review (and thus presenting sponsor viewpoint on findings)
  3. any wardens who have a relationship with a judge that would typically fall in the category of potential conflict of interest (family, employer, business partner, etc)
  4. any other case where someone might reasonably infer a possible conflict of interest.

QA Report

[L-01] Unspecific Compiler Version Pragma

Avoid floating pragmas for non-library contracts.

While floating pragmas make sense for libraries to allow them to be included with multiple different versions of applications, it may be a security risk for application implementations.

A known vulnerable compiler version may accidentally be selected or security tools might fall-back to an older compiler version ending up checking a different EVM compilation that is ultimately deployed on the blockchain.

It is recommended to pin to a concrete compiler version.

2022-08-olympus/src/policies/interfaces/IHeart.sol::2 => pragma solidity >=0.8.0;
2022-08-olympus/src/policies/interfaces/IOperator.sol::2 => pragma solidity >=0.8.0;

[L-02] Use of Block.timestamp

Block timestamps have historically been used for a variety of applications, such as entropy for random numbers (see the Entropy Illusion for further details), locking funds for periods of time, and various state-changing conditional statements that are time-dependent. Miners have the ability to adjust timestamps slightly, which can prove to be dangerous if block timestamps are used incorrectly in smart contracts.

2022-08-olympus/src/modules/PRICE.sol::143 => lastObservationTime = uint48(block.timestamp);
2022-08-olympus/src/modules/PRICE.sol::146 => emit NewObservation(block.timestamp, currentPrice, _movingAverage);
2022-08-olympus/src/modules/PRICE.sol::165 => if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))
2022-08-olympus/src/modules/PRICE.sol::171 => if (updatedAt < block.timestamp - uint256(observationFrequency))
2022-08-olympus/src/modules/PRICE.sol::215 => if (startObservations_.length != numObs || lastObservationTime_ > uint48(block.timestamp))
2022-08-olympus/src/modules/RANGE.sol::85 => lastActive: uint48(block.timestamp),
2022-08-olympus/src/modules/RANGE.sol::92 => lastActive: uint48(block.timestamp),
2022-08-olympus/src/modules/RANGE.sol::136 => _range.high.lastActive = uint48(block.timestamp);
2022-08-olympus/src/modules/RANGE.sol::138 => emit WallDown(true, block.timestamp, capacity_);
2022-08-olympus/src/modules/RANGE.sol::148 => _range.low.lastActive = uint48(block.timestamp);
2022-08-olympus/src/modules/RANGE.sol::150 => emit WallDown(false, block.timestamp, capacity_);
2022-08-olympus/src/modules/RANGE.sol::191 => lastActive: uint48(block.timestamp),
2022-08-olympus/src/modules/RANGE.sol::200 => lastActive: uint48(block.timestamp),
2022-08-olympus/src/modules/RANGE.sol::207 => emit WallUp(high_, block.timestamp, capacity_);
2022-08-olympus/src/modules/RANGE.sol::231 => emit CushionDown(high_, block.timestamp);
2022-08-olympus/src/modules/RANGE.sol::233 => emit CushionUp(high_, block.timestamp, marketCapacity_);
2022-08-olympus/src/policies/Governance.sol::171 => block.timestamp,
2022-08-olympus/src/policies/Governance.sol::212 => if (block.timestamp > proposal.submissionTimestamp + ACTIVATION_DEADLINE) {
2022-08-olympus/src/policies/Governance.sol::227 => if (block.timestamp < activeProposal.activationTimestamp + GRACE_PERIOD) {
2022-08-olympus/src/policies/Governance.sol::231 => activeProposal = ActivatedProposal(proposalId_, block.timestamp);
2022-08-olympus/src/policies/Governance.sol::235 => emit ProposalActivated(proposalId_, block.timestamp);
2022-08-olympus/src/policies/Governance.sol::272 => if (block.timestamp < activeProposal.activationTimestamp + EXECUTION_TIMELOCK) {
2022-08-olympus/src/policies/Heart.sol::63 => lastBeat = block.timestamp;
2022-08-olympus/src/policies/Heart.sol::94 => if (block.timestamp < lastBeat + frequency()) revert Heart_OutOfCycle();
2022-08-olympus/src/policies/Heart.sol::108 => emit Beat(block.timestamp);
2022-08-olympus/src/policies/Heart.sol::131 => lastBeat = block.timestamp - frequency();
2022-08-olympus/src/policies/Operator.sol::128 => lastRegen: uint48(block.timestamp),
2022-08-olympus/src/policies/Operator.sol::210 => uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&
2022-08-olympus/src/policies/Operator.sol::216 => uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&
2022-08-olympus/src/policies/Operator.sol::404 => conclusion: uint48(block.timestamp + config_.cushionDuration),
2022-08-olympus/src/policies/Operator.sol::456 => conclusion: uint48(block.timestamp + config_.cushionDuration),
2022-08-olympus/src/policies/Operator.sol::708 => _status.high.lastRegen = uint48(block.timestamp);
2022-08-olympus/src/policies/Operator.sol::720 => _status.low.lastRegen = uint48(block.timestamp);

[L-03] safeApprove() is deprecated

safeApprove() is deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead

2022-08-olympus/src/policies/BondCallback.sol::57 => ohm.safeApprove(address(MINTR), type(uint256).max);
2022-08-olympus/src/policies/Operator.sol::167 => ohm.safeApprove(address(MINTR), type(uint256).max);

[L-04] Open TODOs

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

2022-08-olympus/src/policies/Operator.sol::657 => /// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?
2022-08-olympus/src/policies/TreasuryCustodian.sol::51 => // TODO Currently allows anyone to revoke any approval EXCEPT activated policies.
2022-08-olympus/src/policies/TreasuryCustodian.sol::52 => // TODO must reorg policy storage to be able to check for deactivated policies.
2022-08-olympus/src/policies/TreasuryCustodian.sol::56 => // TODO Make sure `policy_` is an actual policy and not a random address.

[L-05] decimals() not part of ERC20 standard

decimals() is not part of the official ERC20 standard and might fail for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a potential issue.

2022-08-olympus/src/modules/PRICE.sol::84 => uint8 ohmEthDecimals = _ohmEthPriceFeed.decimals();
2022-08-olympus/src/modules/PRICE.sol::87 => uint8 reserveEthDecimals = _reserveEthPriceFeed.decimals();
2022-08-olympus/src/policies/Operator.sol::122 => ohmDecimals = tokens_[0].decimals();
2022-08-olympus/src/policies/Operator.sol::124 => reserveDecimals = tokens_[1].decimals();
2022-08-olympus/src/policies/Operator.sol::375 => uint256 oracleScale = 10**uint8(int8(PRICE.decimals()) - priceDecimals);
2022-08-olympus/src/policies/Operator.sol::418 => uint8 oracleDecimals = PRICE.decimals();
2022-08-olympus/src/policies/Operator.sol::493 => return decimals - int8(PRICE.decimals());
2022-08-olympus/src/policies/Operator.sol::754 => 10**ohmDecimals * 10**PRICE.decimals()
2022-08-olympus/src/policies/Operator.sol::764 => 10**ohmDecimals * 10**PRICE.decimals(),
2022-08-olympus/src/policies/Operator.sol::784 => 10**ohmDecimals * 10**PRICE.decimals(),

[L-06] Unsafe use of transfer()/transferFrom() with IERC20

Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer() and transferFrom() functions do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20, their function signatures do not match and therefore the calls made, revert. Use OpenZeppelin’s SafeERC20's safeTransfer()/safeTransferFrom() instead

2022-08-olympus/src/policies/Governance.sol::259 => VOTES.transferFrom(msg.sender, address(this), userVotes);
2022-08-olympus/src/policies/Governance.sol::312 => VOTES.transferFrom(address(this), msg.sender, userVotes);

[L-07] Events not emitted for important state changes

When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.

2022-08-olympus/src/Kernel.sol::126 => function setActiveStatus(bool activate_) external onlyKernel {
2022-08-olympus/src/policies/BondCallback.sol::190 => function setOperator(Operator operator_) external onlyRole("callback_admin") {
2022-08-olympus/src/policies/Operator.sol::498 => function setSpreads(uint256 cushionSpread_, uint256 wallSpread_)
2022-08-olympus/src/policies/Operator.sol::510 => function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {
2022-08-olympus/src/policies/Operator.sol::586 => function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)
2022-08-olympus/src/policies/interfaces/IHeart.sol::34 => function setRewardTokenAndAmount(ERC20 token_, uint256 reward_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::74 => function setSpreads(uint256 cushionSpread_, uint256 wallSpread_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::80 => function setThresholdFactor(uint256 thresholdFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::85 => function setCushionFactor(uint32 cushionFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::92 => function setCushionParams(
2022-08-olympus/src/policies/interfaces/IOperator.sol::101 => function setReserveFactor(uint32 reserveFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::109 => function setRegenParams(
2022-08-olympus/src/policies/interfaces/IOperator.sol::119 => function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_) external;

[L-08] _safeMint() should be used rather than _mint() wherever possible

Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer() and transferFrom() functions do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20, their function signatures do not match and therefore the calls made, revert. Use OpenZeppelin’s SafeERC20's safeTransfer()/safeTransferFrom() instead

2022-08-olympus/src/modules/VOTES.sol::36 => _mint(wallet_, amount_);

[L-09] Missing check for 0 transfer

Some ERC20 tokens will revert when transferring zero. A require statement should be added to check that amount_ is not 0

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L82
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L99
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L110

[L-10] Lack of zero address check

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/MINTR.sol#L16
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/RANGE.sol#L102-L103
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L83
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L86
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Operator.sol#L119-L121
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/BondCallback.sol#L43-L44

[L-11] Unsafe cast

Precision may be lost when casting uint48 to uint32

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L97

[L-12] RoundID not validatated

When getting prices using latestRoundData(), roundID should also be validated against answeredInRound

eg

  (uint80 roundID, int256 _price, , uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData();
  require(answeredInRound >= roundID, "ChainLink: Stale price");
  require(updatedAt != 0, "ChainLink: Round not complete");

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L161
https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/PRICE.sol#L170

[N-01] Use a more recent version of solidity

Use a solidity version of at least 0.8.4 to get bytes.concat() instead of abi.encodePacked(,)
Use a solidity version of at least 0.8.12 to get string.concat() instead of abi.encodePacked(,)
Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions

2022-08-olympus/src/policies/interfaces/IHeart.sol::2 => pragma solidity >=0.8.0;
2022-08-olympus/src/policies/interfaces/IOperator.sol::2 => pragma solidity >=0.8.0;

[N-02] Large multiples of ten should use scientific notation

Use (e.g. 1e6) rather than decimal literals (e.g. 1000000), for better code readability

2022-08-olympus/src/modules/RANGE.sol::245 => wallSpread_ > 10000 ||
2022-08-olympus/src/modules/RANGE.sol::247 => cushionSpread_ > 10000 ||
2022-08-olympus/src/modules/RANGE.sol::264 => if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();
2022-08-olympus/src/policies/Governance.sol::164 => if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)
2022-08-olympus/src/policies/Operator.sol::111 => if (configParams[4] > 10000 || configParams[4] < 100) revert Operator_InvalidParams();
2022-08-olympus/src/policies/Operator.sol::518 => if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();
2022-08-olympus/src/policies/Operator.sol::550 => if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();

[N-03] Event is missing indexed fields

Each event should use three indexed fields if there are three or more fields

2022-08-olympus/src/modules/INSTR.sol::11 => event InstructionsStored(uint256 instructionsId);
2022-08-olympus/src/modules/PRICE.sol::26 => event NewObservation(uint256 timestamp_, uint256 price_, uint256 movingAverage_);
2022-08-olympus/src/modules/PRICE.sol::27 => event MovingAverageDurationChanged(uint48 movingAverageDuration_);
2022-08-olympus/src/modules/PRICE.sol::28 => event ObservationFrequencyChanged(uint48 observationFrequency_);
2022-08-olympus/src/modules/RANGE.sol::20 => event WallUp(bool high_, uint256 timestamp_, uint256 capacity_);
2022-08-olympus/src/modules/RANGE.sol::21 => event WallDown(bool high_, uint256 timestamp_, uint256 capacity_);
2022-08-olympus/src/modules/RANGE.sol::22 => event CushionUp(bool high_, uint256 timestamp_, uint256 capacity_);
2022-08-olympus/src/modules/RANGE.sol::23 => event CushionDown(bool high_, uint256 timestamp_);
2022-08-olympus/src/modules/RANGE.sol::30 => event SpreadsChanged(uint256 cushionSpread_, uint256 wallSpread_);
2022-08-olympus/src/modules/RANGE.sol::31 => event ThresholdFactorChanged(uint256 thresholdFactor_);
2022-08-olympus/src/policies/Governance.sol::86 => event ProposalSubmitted(uint256 proposalId);
2022-08-olympus/src/policies/Governance.sol::87 => event ProposalEndorsed(uint256 proposalId, address voter, uint256 amount);
2022-08-olympus/src/policies/Governance.sol::88 => event ProposalActivated(uint256 proposalId, uint256 timestamp);
2022-08-olympus/src/policies/Governance.sol::89 => event WalletVoted(uint256 proposalId, address voter, bool for_, uint256 userVotes);
2022-08-olympus/src/policies/Governance.sol::90 => event ProposalExecuted(uint256 proposalId);
2022-08-olympus/src/policies/Heart.sol::28 => event Beat(uint256 timestamp_);
2022-08-olympus/src/policies/Heart.sol::29 => event RewardIssued(address to_, uint256 rewardAmount_);
2022-08-olympus/src/policies/Heart.sol::30 => event RewardUpdated(ERC20 token_, uint256 rewardAmount_);
2022-08-olympus/src/policies/Operator.sol::51 => event CushionFactorChanged(uint32 cushionFactor_);
2022-08-olympus/src/policies/Operator.sol::52 => event CushionParamsChanged(uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_);
2022-08-olympus/src/policies/Operator.sol::53 => event ReserveFactorChanged(uint32 reserveFactor_);
2022-08-olympus/src/policies/Operator.sol::54 => event RegenParamsChanged(uint32 wait_, uint32 threshold_, uint32 observe_);

[N-04] Missing NatSpec

Code should include NatSpec

2022-08-olympus/src/policies/TreasuryCustodian.sol::1 => // SPDX-License-Identifier: AGPL-3.0
2022-08-olympus/src/utils/KernelUtils.sol::1 => // SPDX-License-Identifier: AGPL-3.0-only

[N-05] Constants should be defined rather than using magic numbers

It is bad practice to use numbers directly in code without explanation

2022-08-olympus/src/modules/PRICE.sol::90 => if (exponent > 38) revert Price_InvalidParams();
2022-08-olympus/src/modules/RANGE.sol::245 => wallSpread_ > 10000 ||
2022-08-olympus/src/modules/RANGE.sol::246 => wallSpread_ < 100 ||
2022-08-olympus/src/modules/RANGE.sol::247 => cushionSpread_ > 10000 ||
2022-08-olympus/src/modules/RANGE.sol::248 => cushionSpread_ < 100 ||
2022-08-olympus/src/modules/RANGE.sol::264 => if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();
2022-08-olympus/src/policies/Governance.sol::164 => if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)
2022-08-olympus/src/policies/Governance.sol::217 => (totalEndorsementsForProposal[proposalId_] * 100) <
2022-08-olympus/src/policies/Governance.sol::268 => if (netVotes * 100 < VOTES.totalSupply() * EXECUTION_THRESHOLD) {
2022-08-olympus/src/policies/Operator.sol::111 => if (configParams[4] > 10000 || configParams[4] < 100) revert Operator_InvalidParams();
2022-08-olympus/src/policies/Operator.sol::378 => 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals
2022-08-olympus/src/policies/Operator.sol::433 => 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals
2022-08-olympus/src/policies/Operator.sol::518 => if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();
2022-08-olympus/src/policies/Operator.sol::550 => if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();
2022-08-olympus/src/utils/KernelUtils.sol::58 => for (uint256 i = 0; i < 32; ) {

[N-06] 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.

2022-08-olympus/src/Kernel.sol::439 => function grantRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/Kernel.sol::451 => function revokeRole(Role role_, address addr_) public onlyAdmin {
2022-08-olympus/src/modules/INSTR.sol::28 => function VERSION() public pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/INSTR.sol::37 => function getInstructions(uint256 instructionsId_) public view returns (Instruction[] memory) {
2022-08-olympus/src/modules/MINTR.sol::20 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/MINTR.sol::33 => function mintOhm(address to_, uint256 amount_) public permissioned {
2022-08-olympus/src/modules/MINTR.sol::37 => function burnOhm(address from_, uint256 amount_) public permissioned {
2022-08-olympus/src/modules/PRICE.sol::108 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/RANGE.sol::110 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/TRSRY.sol::47 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/VOTES.sol::22 => function KEYCODE() public pure override returns (Keycode) {
2022-08-olympus/src/modules/VOTES.sol::45 => function transfer(address to_, uint256 amount_) public pure override returns (bool) {
2022-08-olympus/src/policies/Governance.sol::145 => function getMetadata(uint256 proposalId_) public view returns (ProposalMetadata memory) {
2022-08-olympus/src/policies/Governance.sol::151 => function getActiveProposal() public view returns (ActivatedProposal memory) {

[N-07] Adding a return statement when the function defines a named return variable, is redundant

It is not necessary to have both a named return and a return statement.

2022-08-olympus/src/modules/INSTR.sol::28 => function VERSION() public pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/MINTR.sol::25 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/PRICE.sol::113 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/RANGE.sol::115 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/TRSRY.sol::51 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/modules/VOTES.sol::27 => function VERSION() external pure override returns (uint8 major, uint8 minor) {
2022-08-olympus/src/policies/BondCallback.sol::177 => returns (uint256 in_, uint256 out_)

[N-08] Missing event for critical parameter change

Emitting events after sensitive changes take place, to facilitate tracking and notify off-chain clients following changes to the contract.

2022-08-olympus/src/Kernel.sol::126 => function setActiveStatus(bool activate_) external onlyKernel {
2022-08-olympus/src/policies/BondCallback.sol::190 => function setOperator(Operator operator_) external onlyRole("callback_admin") {
2022-08-olympus/src/policies/Operator.sol::498 => function setSpreads(uint256 cushionSpread_, uint256 wallSpread_)
2022-08-olympus/src/policies/Operator.sol::510 => function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {
2022-08-olympus/src/policies/Operator.sol::586 => function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)
2022-08-olympus/src/policies/interfaces/IHeart.sol::34 => function setRewardTokenAndAmount(ERC20 token_, uint256 reward_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::74 => function setSpreads(uint256 cushionSpread_, uint256 wallSpread_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::80 => function setThresholdFactor(uint256 thresholdFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::85 => function setCushionFactor(uint32 cushionFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::92 => function setCushionParams(
2022-08-olympus/src/policies/interfaces/IOperator.sol::101 => function setReserveFactor(uint32 reserveFactor_) external;
2022-08-olympus/src/policies/interfaces/IOperator.sol::109 => function setRegenParams(
2022-08-olympus/src/policies/interfaces/IOperator.sol::119 => function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_) external;

[N-09] Lines are too long

Usually lines in source code are limited to 80 characters. Today's screens are much larger so it's reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

2022-08-olympus/src/modules/PRICE.sol::31 => /// @dev    Price feeds. Chainlink typically provides price feeds for an asset in ETH. Therefore, we use two price feeds against ETH, one for OHM and one for the Reserve asset, to calculate the relative price of OHM in the Reserve asset.
2022-08-olympus/src/modules/RANGE.sol::40 => uint256 spread; // Spread of the band (increase/decrease from the moving average to set the band prices), percent with 2 decimal places (i.e. 1000 = 10% spread)
2022-08-olympus/src/modules/RANGE.sol::46 => uint256 capacity; // Amount of tokens that can be used to defend the side of the range. Specified in OHM tokens on the high side and Reserve tokens on the low side.
2022-08-olympus/src/modules/RANGE.sol::61 => /// @notice Threshold factor for the change, a percent in 2 decimals (i.e. 1000 = 10%). Determines how much of the capacity must be spent before the side is taken down.
2022-08-olympus/src/policies/Operator.sol::97 => uint32[8] memory configParams // [cushionFactor, cushionDuration, cushionDebtBuffer, cushionDepositInterval, reserveFactor, regenWait, regenThreshold, regenObserve]
2022-08-olympus/src/policies/Operator.sol::657 => /// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?
2022-08-olympus/src/policies/interfaces/IOperator.sol::15 => uint32 cushionDebtBuffer; // Percentage over the initial debt to allow the market to accumulate at any one time. Percent with 3 decimals, e.g. 1_000 = 1 %. See IBondAuctioneer for more info.
2022-08-olympus/src/policies/interfaces/IOperator.sol::90 => /// @param  debtBuffer_ - Percentage over the initial debt to allow the market to accumulate at any one time. Percent with 3 decimals, e.g. 1_000 = 1 %. See IBondAuctioneer for more info.

Gas Optimizations

Gas Optimizations

Summary

Gas savings are estimated using the gas report of existing FORGE_GAS_REPORT=true forge test tests (the sum of all deployment costs and the sum of the costs of calling all methods) and may vary depending on the implementation of the fix. I keep my version of the fix for each finding and can provide them if you need.
Some optimizations (mostly logical) cannot be scored with a exact gas quantity.

Gas Optimizations

Issue Instances Estimated gas(deployments) Estimated gas(method call)
1 Replace modifier with function 6 460 154 -
2 storage pointer to a structure is cheaper than copying each value of the structure into memory, same for array and mapping 7 188 639 5 032
3 Using private rather than public for constants, saves gas 8 45 857 308
4 Use elementary types or a user-defined type instead of a struct that has only one member 1 30 714 1 037
5 State variables should be cached in stack variables rather than re-reading them from storage 7 24 021 614
6 Using bools for storage incurs overhead 6 23 611 4 485
7 State variables can be packed into fewer storage slots 3 23 292 1 711
8 Expressions that cannot be overflowed can be unchecked 5 23 016 -
9 Increment optimization 18
9.1 Prefix increments are cheaper than postfix increments, especially when it's used in for-loops 3 400 -
9.2 <x> = <x> + 1 even more efficient than pre increment 18 14 217 -
10 Use named returns for local variables where it is possible 3 5 400 -
11 x = x + y is cheaper than x += y; 6 5 000 -
12 Deleting a struct is cheaper than creating a new struct with null values. 1 4 207 -
13 Don't compare boolean expressions to boolean literals 2 1 607 -
14 revert operator should be in the code as early as reasonably possible 3 200 1 559+
15 Duplicated require()/revert() checks should be refactored to a modifier or function 4 - 8 111

Total: 83 instances over 15 issues


  1. Replace modifier with function (6 instances)

    modifiers make code more elegant, but cost more than normal functions

    Deployment Gas Saved: 460 154

    All modifiers except permissioned due to unresolved error flow

    70     modifier onlyKernel() {
    71         if (msg.sender != address(kernel)) revert KernelAdapter_OnlyKernel(msg.sender);
    72         _;
    73     }
    ...
    119    modifier onlyRole(bytes32 role_) {
    120        Role role = toRole(role_);
    121        if (!kernel.hasRole(msg.sender, role)) revert Policy_OnlyRole(role);
    122        _;
    123    }
    ...
    223    modifier onlyExecutor() {
    224        if (msg.sender != executor) revert Kernel_OnlyExecutor(msg.sender);
    225        _;
    226    }
    227
    228    /// @notice Modifier to check if caller is the roles admin.
    229    modifier onlyAdmin() {
    230        if (msg.sender != admin) revert Kernel_OnlyAdmin(msg.sender);
    231        _;
    232    }
    • src/policies/Operator.sol:188-191
    188    modifier onlyWhileActive() {
    189        if (!active) revert Operator_Inactive();
    190        _;
    191    }
    if (!initialized) revert Price_NotInitialized(); // @note 4 instances
  2. storage pointer to a structure is cheaper than copying each value of the structure into memory, same for array and mapping (7 instances)

    Deployment Gas Saved: 188 639
    Method Call Gas Saved: 5 032

    It may not be obvious, but every time you copy a storage struct/array/mapping to a memory variable, you are literally copying each member by reading it from storage, which is expensive. And when you use the storage keyword, you are just storing a pointer to the storage, which is much cheaper.

    • src/Kernel.sol:379
    379        Policy[] memory dependents = moduleDependents[keycode_];

    fix(the same for others):

    Policy[] storage dependents = moduleDependents[keycode_];
    • src/policies/BondCallback.sol:179
    179        uint256[2] memory marketAmounts = _amountsPerMarket[id_];
    • src/policies/Governance.sol:206
    206        ProposalMetadata memory proposal = getProposalMetadata[proposalId_];
    205        /// Cache config in memory
    206        Config memory config_ = _config;
    ...
    384            /// Cache config struct to avoid multiple SLOADs
    385            Config memory config_ = _config;
    ...
    439            /// Cache config struct to avoid multiple SLOADs
    440            Config memory config_ = _config;
    ...
    666        Regen memory regen = _status.low;
  3. Using private rather than public for constants, saves gas (8 instances)

    If needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table

    Deployment Gas Saved: 45 857
    Method Call Gas Saved: 308

    • src/policies/Governance.sol:119-137
    119    /// @notice The amount of votes a proposer needs in order to submit a proposal as a percentage of total supply (in basis points).
    120    /// @dev    This is set to 1% of the total supply.
    121    uint256 public constant SUBMISSION_REQUIREMENT = 100;
    122
    123    /// @notice Amount of time a submitted proposal has to activate before it expires.
    124    uint256 public constant ACTIVATION_DEADLINE = 2 weeks;
    125
    126    /// @notice Amount of time an activated proposal must stay up before it can be replaced by a new activated proposal.
    127    uint256 public constant GRACE_PERIOD = 1 weeks;
    128
    129    /// @notice Endorsements required to activate a proposal as percentage of total supply.
    130    uint256 public constant ENDORSEMENT_THRESHOLD = 20;
    131
    132    /// @notice Net votes required to execute a proposal on chain as a percentage of total supply.
    133    uint256 public constant EXECUTION_THRESHOLD = 33;
    134
    135    /// @notice Required time for a proposal to be active before it can be executed.
    136    /// @dev    This amount should be greater than 0 to prevent flash loan attacks.
    137    uint256 public constant EXECUTION_TIMELOCK = 3 days;
    • src/policies/Operator.sol:89
    89     uint32 public constant FACTOR_SCALE = 1e4;
    • src/modules/RANGE.sol:65
    65     uint256 public constant FACTOR_SCALE = 1e4;
  4. Use elementary types or a user-defined type instead of a struct that has only one member. (1 instances)

    Deployment Gas Saved: 30 714
    Method Call Gas Saved: 1 037

    • src/modules/RANGE.sol:33-35
    33     struct Line {
    34         uint256 price; // Price for the specified level
    35     }
  5. State variables should be cached in stack variables rather than re-reading them from storage

    Deployment Gas Saved: 24 021
    Method Call Gas Saved: 614

    SLOADs are expensive (100 gas after the 1st one) compared to MLOADs/MSTOREs (3 gas each). Storage values read multiple times should instead be cached in memory the first time (costing 1 SLOAD) and then read from this cache to avoid multiple SLOADs.

    112        rewardToken.safeTransfer(to_, reward);
    113        emit RewardIssued(to_, reward);

    fix:

            uint256 reward = reward;
            rewardToken.safeTransfer(to_, reward);
            emit RewardIssued(to_, reward);
    • src/policies/BondCallback.sol:68-75
    68         Keycode TRSRY_KEYCODE = TRSRY.KEYCODE();
    69         Keycode MINTR_KEYCODE = MINTR.KEYCODE();
    70
    71         requests = new Permissions[](4);
    72         requests[0] = Permissions(TRSRY_KEYCODE, TRSRY.setApprovalFor.selector);
    73         requests[1] = Permissions(TRSRY_KEYCODE, TRSRY.withdrawReserves.selector);
    74         requests[2] = Permissions(MINTR_KEYCODE, MINTR.mintOhm.selector);
    75         requests[3] = Permissions(MINTR_KEYCODE, MINTR.burnOhm.selector);

    fix(similar for other policies):

        OlympusTreasury ltrsry = TRSRY;
        OlympusMinter lmintr = MINTR;
        Keycode TRSRY_KEYCODE = ltrsry.KEYCODE();
        Keycode MINTR_KEYCODE = lmintr.KEYCODE();
    
        requests = new Permissions[](4);
    
        requests[0] = Permissions(TRSRY_KEYCODE, ltrsry.setApprovalFor.selector);
        requests[1] = Permissions(TRSRY_KEYCODE, ltrsry.withdrawReserves.selector);
        requests[2] = Permissions(MINTR_KEYCODE, lmintr.mintOhm.selector);
        requests[3] = Permissions(MINTR_KEYCODE, lmintr.burnOhm.selector);
    • src/policies/Governance.sol:77-79
    77         requests = new Permissions[](2);
    78         requests[0] = Permissions(INSTR.KEYCODE(), INSTR.store.selector);
    79         requests[1] = Permissions(VOTES.KEYCODE(), VOTES.transferFrom.selector);
    • src/policies/Operator.sol:172-185
    172        Keycode RANGE_KEYCODE = RANGE.KEYCODE();
    173        Keycode TRSRY_KEYCODE = TRSRY.KEYCODE();
    174        Keycode MINTR_KEYCODE = MINTR.KEYCODE();
    175
    176        requests = new Permissions[](9);
    177        requests[0] = Permissions(RANGE_KEYCODE, RANGE.updateCapacity.selector);
    178        requests[1] = Permissions(RANGE_KEYCODE, RANGE.updateMarket.selector);
    179        requests[2] = Permissions(RANGE_KEYCODE, RANGE.updatePrices.selector);
    180        requests[3] = Permissions(RANGE_KEYCODE, RANGE.regenerate.selector);
    181        requests[4] = Permissions(RANGE_KEYCODE, RANGE.setSpreads.selector);
    182        requests[5] = Permissions(RANGE_KEYCODE, RANGE.setThresholdFactor.selector);
    183        requests[6] = Permissions(TRSRY_KEYCODE, TRSRY.setApprovalFor.selector);
    184        requests[7] = Permissions(MINTR_KEYCODE, MINTR.mintOhm.selector);
    185        requests[8] = Permissions(MINTR_KEYCODE, MINTR.burnOhm.selector);
    • src/policies/PriceConfig.sol:32-34
    32        permissions[0] = Permissions(PRICE.KEYCODE(), PRICE.initialize.selector);
    33        permissions[1] = Permissions(PRICE.KEYCODE(), PRICE.changeMovingAverageDuration.selector);
    34        permissions[2] = Permissions(PRICE.KEYCODE(), PRICE.changeObservationFrequency.selector);
    • src/policies/TreasuryCustodian.sol:35-39
    35        Keycode TRSRY_KEYCODE = TRSRY.KEYCODE();
    36
    37        requests = new Permissions[](2);
    38        requests[0] = Permissions(TRSRY_KEYCODE, TRSRY.setApprovalFor.selector);
    39        requests[1] = Permissions(TRSRY_KEYCODE, TRSRY.setDebt.selector);
    • src/policies/VoterRegistration.sol:33-35
    33        permissions = new Permissions[](2);
    34        permissions[0] = Permissions(VOTES.KEYCODE(), VOTES.mintTo.selector);
    35        permissions[1] = Permissions(VOTES.KEYCODE(), VOTES.burnFrom.selector);
  6. Using bools for storage incurs overhead (6 instances)

    Deployment Gas Saved: 23 611
    Method Call Gas Saved: 4 485

    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.
    

    Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from 'false' to 'true', after having been 'true' in the past

    Important: This rule doesn't always work, sometimes a bool is packed with another variable in the same slot, sometimes it's packed into a struct, sometimes the optimizer makes bool more efficient. You can see the @note in the code for each case

    181    mapping(Keycode => mapping(Policy => mapping(bytes4 => bool))) public modulePermissions; //@note D:3200 M:1754
    ...
    194    mapping(address => mapping(Role => bool)) public hasRole; //@note D:−3016 M:2298
    ...
    197    mapping(Role => bool) public isRole; //@note D:2407
    • src/policies/Governance.sol:105, 117,
    105    mapping(uint256 => bool) public proposalHasBeenActivated; //@note D:3007
    ...
    117    mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal; //@note D:3007
    • src/modules/PRICE.sol:62
    62     bool public initialized; //@note D:11813

    Expensive method calls:

    It's just to show which bool is better left in the code

    • src/policies/Operator.sol
    63     bool public initialized; //@note D:5808 M:-22036
    ...
    66     bool public active; //@note D:-32775 M:-48896
    • src/policies/Heart.sol
    33     bool public active; //@note D:-382
    • src/policies/BondCallback.sol
    24     mapping(address => mapping(uint256 => bool)) public approvedMarkets; //@note D:-44192
    • src/Kernel.sol
    113    bool public isActive; //@note D:20923 M:-247184
  7. State variables can be packed into fewer storage slots (3 instances)

    If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper

    NOTE: one slot = 32 bytes

    Deployment Gas Saved: 23 292
    Method Call Gas Saved: 1 711

    • src/policies/Heart.sol:32-48

    uint256(32), address(20), bool(1)

    32     /// @notice Status of the Heart, false = stopped, true = beating
    33     bool public active; // @note put below _operator
    34
    35     /// @notice Timestamp of the last beat (UTC, in seconds)
    36     uint256 public lastBeat;
    37
    38     /// @notice Reward for beating the Heart (in reward token decimals)
    39     uint256 public reward;
    40
    41     /// @notice Reward token address that users are sent for beating the Heart
    42     ERC20 public rewardToken;
    43
    44     // Modules
    45     OlympusPrice internal PRICE;
    46
    47     // Policies
    48     IOperator internal _operator;

    fix:

    uint256 public lastBeat;
    uint256 public reward;
    ERC20 public rewardToken;
    OlympusPrice internal PRICE;
    IOperator internal _operator;
    bool public active;
    • src/modules/PRICE.sol:31-65

    NOTE: PRICE is Module, Module is KernelAdapter, so real first variable in PRICE is kernel from KernelAdapter

    uint256(32), uint32(4), uint48(6), uint8(1), array(32), address(20), bool(1)

    inherit Kernel public kernel;
    ...
    31     /// @dev    Price feeds. Chainlink typically provides price feeds for an asset in ETH. Therefore, we use two price feeds against ETH, one for OHM and one for the Reserve asset, to calculate the relative price of OHM in the Reserve asset.
    32     AggregatorV2V3Interface internal immutable _ohmEthPriceFeed;
    33     AggregatorV2V3Interface internal immutable _reserveEthPriceFeed;
    34
    35     /// @dev Moving average data
    36     uint256 internal _movingAverage; /// See getMovingAverage()
    37
    38     /// @notice Array of price observations. Check nextObsIndex to determine latest data point.
    39     /// @dev    Observations are stored in a ring buffer where the moving average is the sum of all observations divided by the number of observations.
    40     ///         Observations can be cleared by changing the movingAverageDuration or observationFrequency and must be re-initialized.
    41     uint256[] public observations;
    42
    43     /// @notice Index of the next observation to make. The current value at this index is the oldest observation.
    44     uint32 public nextObsIndex;
    45
    46     /// @notice Number of observations used in the moving average calculation. Computed from movingAverageDuration / observationFrequency.
    47     uint32 public numObservations;
    48
    49     /// @notice Frequency (in seconds) that observations should be stored.
    50     uint48 public observationFrequency;
    51
    52     /// @notice Duration (in seconds) over which the moving average is calculated.
    53     uint48 public movingAverageDuration;
    54
    55     /// @notice Unix timestamp of last observation (in seconds).
    56     uint48 public lastObservationTime;
    57
    58     /// @notice Number of decimals in the price values provided by the contract.
    59     uint8 public constant decimals = 18;
    60
    61     /// @notice Whether the price module is initialized (and therefore active).
    62     bool public initialized;
    63
    64     // Scale factor for converting prices, calculated from decimal values.
    65     uint256 internal immutable _scaleFactor;

    fix:

    uint48 public observationFrequency;
    uint48 public movingAverageDuration;
    AggregatorV2V3Interface internal immutable _ohmEthPriceFeed;
    AggregatorV2V3Interface internal immutable _reserveEthPriceFeed;
    uint256 internal _movingAverage; /// See getMovingAverage()
    uint256[] public observations;
    uint32 public nextObsIndex;
    uint32 public numObservations;
    uint48 public lastObservationTime;
    uint8 public constant decimals = 18;
    bool public initialized;
    uint256 internal immutable _scaleFactor;
    • src/policies/Operator.sol:58-89

    uint32(4), uint8(1), address(20), bool(1)

    58     /// Operator variables, defined in the interface on the external getter functions
    59     Status internal _status;
    60     Config internal _config;
    61
    62     /// @notice    Whether the Operator has been initialized
    63     bool public initialized;
    64
    65     /// @notice    Whether the Operator is active
    66     bool public active;
    67
    68     /// Modules
    69     OlympusPrice internal PRICE;
    70     OlympusRange internal RANGE;
    71     OlympusTreasury internal TRSRY;
    72     OlympusMinter internal MINTR;
    73
    74     /// External contracts
    75     /// @notice     Auctioneer contract used for cushion bond market deployments
    76     IBondAuctioneer public auctioneer;
    77     /// @notice     Callback contract used for cushion bond market payouts
    78     IBondCallback public callback;
    79
    80     /// Tokens
    81     /// @notice     OHM token contract
    82     ERC20 public immutable ohm;
    83     uint8 public immutable ohmDecimals;
    84     /// @notice     Reserve token contract
    85     ERC20 public immutable reserve;
    86     uint8 public immutable reserveDecimals;
    87
    88     /// Constants
    89     uint32 public constant FACTOR_SCALE = 1e4;

    fix:

    Status internal _status;
    Config internal _config;
    OlympusPrice internal PRICE;
    OlympusRange internal RANGE;
    OlympusTreasury internal TRSRY;
    OlympusMinter internal MINTR;
    IBondAuctioneer public auctioneer;
    IBondCallback public callback;
    bool public initialized;
    bool public active;
    ERC20 public immutable ohm;
    uint8 public immutable ohmDecimals;
    ERC20 public immutable reserve;
    uint8 public immutable reserveDecimals;
    uint32 public constant FACTOR_SCALE = 1e4;
  8. Expressions that cannot be overflowed can be unchecked (5 instances)

    Deployment Gas Saved: 23 016

    299        activePolicies.push(policy_);
    300        getPolicyIndex[policy_] = activePolicies.length - 1; // @note cannot be overflowed due to a previous push
    ...
    309            moduleDependents[keycode].push(policy_);
    310            getDependentIndex[keycode][policy_] = moduleDependents[keycode].length - 1; // @note cannot be overflowed due to a previous push
    89         uint256 exponent = decimals + reserveEthDecimals - ohmEthDecimals; //@note overflow is not possible, if an underflow occurs, the next statement will revert
    ...
    144        nextObsIndex = (nextObsIndex + 1) % numObs; //@note numObs can not be equal 0 during to check in constructor
    ...
    171            if (updatedAt < block.timestamp - uint256(observationFrequency)) // @note can not be underflowed due to ` - 3 * uint256(observationFrequency)` in 165
  9. Increment optimization (18 instances)

    For a uint256 i variable, the following is true with the Optimizer enabled at 10k:

    Increment:

    i += 1 is the most expensive form
    i++ costs 6 gas less than i += 1
    ++i costs 5 gas less than i++ (11 gas less than i += 1)
    Decrement:

    i -= 1 is the most expensive form
    i-- costs 11 gas less than i -= 1
    --i costs 5 gas less than i-- (16 gas less than i -= 1)

    1. Prefix increments are cheaper than postfix increments, especially when it's used in for-loops (3 instances).

    Deployment Gas Saved: 400

    • src/utils/KernelUtils.sol:49, 64
    49            i++;
    ...
    64            i++;
    • src/policies/Operator.sol:488

    NOTE: in case of 670 675 686 691 not applicable and gas will be lost

    488            decimals++;
    1. <x> = <x> + 1 even more efficient than pre increment.(18 instances)

    Deployment Gas Saved: 14 217

    • src/utils/KernelUtils.sol:49, 64
    49            i++;
    ...
    64            i++;
    488            decimals++;
    ...
    670                _status.low.count++;
    ...
    675                _status.low.count--;
    ...
    686                _status.high.count++;
    ...
    691                _status.high.count--;
    313                ++i;
    ...
    357                ++i;
    ...
    369                ++j;
    ...
    386                ++i;
    ...
    404                ++i;
    ...
    429                ++i;
    • src/modules/INSTR.sol:72
    72                ++i;
    • src/modules/PRICE.sol:225
    225                ++i;
    • src/policies/BondCallback.sol:163
    163                ++i;
    • src/policies/Governance.sol:281
    281                ++step;
    • src/policies/TreasuryCustodian.sol:62
    62                ++j;
  10. Use named returns for local variables where it is possible (3 instances)

    Deployment Gas Saved: 5 400

    130    /// @notice Function to grab module address from a given keycode.
    131    function getModuleAddress(Keycode keycode_) internal view returns (address) {
    132        address moduleForKeycode = address(kernel.getModuleForKeycode(keycode_));
    133        if (moduleForKeycode == address(0)) revert Policy_ModuleDoesNotExist(keycode_);
    134        return moduleForKeycode;
    135    }

    fix:

     function getModuleAddress(Keycode keycode_) internal view returns (address moduleForKeycode) {
         moduleForKeycode = address(kernel.getModuleForKeycode(keycode_));
         if (moduleForKeycode == address(0)) revert Policy_ModuleDoesNotExist(keycode_);
     }
    • src/modules/INSTR.sol:41-79
    41    /// @notice Store a list of Instructions to be executed in the future.
    42    function store(Instruction[] calldata instructions_) external permissioned returns (uint256) {
    43        uint256 length = instructions_.length;
    44        uint256 instructionsId = ++totalInstructions;
    45
    46        Instruction[] storage instructions = storedInstructions[instructionsId];
    ...
    76        emit InstructionsStored(instructionsId);
    77
    78        return instructionsId;
    79    }
    153    /// @notice Get the current price of OHM in the Reserve asset from the price feeds
    154    function getCurrentPrice() public view returns (uint256) {
    ...
    177        uint256 currentPrice = (ohmEthPrice * _scaleFactor) / reserveEthPrice;
    178
    179        return currentPrice;
    180    }
  11. x = x + y is cheaper than x += y; (6 instances)

    Deployment Gas Saved: 5 000

    Usually does not work with struct and mappings

    136            _movingAverage += (currentPrice - earliestPrice) / numObs;
    ...
    138            _movingAverage -= (earliestPrice - currentPrice) / numObs;
    ...
    222            total += startObservations_[i];
    • src/modules/VOTES.sol:56, 58
    56        balanceOf[from_] -= amount_;
    ...
    58            balanceOf[to_] += amount_;
    • src/policies/Heart.sol:103
    103        lastBeat += frequency();
  12. Deleting a struct is cheaper than creating a new struct with null values. (1 instances)

    Deployment Gas Saved: 4 207
    Method Call Gas Saved: 40

    • src/policies/Governance.sol:288
    288        activeProposal = ActivatedProposal(0, 0);

    fix:

     delete activeProposal;
  13. Don't compare boolean expressions to boolean literals (2 instances)

    Deployment Gas Saved: 1 607

    • src/policies/Governance.sol:223, 306
    223        if (proposalHasBeenActivated[proposalId_] == true) {
    ...
    306        if (tokenClaimsForProposal[proposalId_][msg.sender] == true) {
  14. revert operator should be in the code as early as reasonably possible (3 instances)

    Deployment Gas Saved: 200
    Method Call Gas Saved: 1 559+

    • src/modules/INSTR.sol:43-48
    43        uint256 length = instructions_.length;
    44        uint256 instructionsId = ++totalInstructions;
    45
    46        Instruction[] storage instructions = storedInstructions[instructionsId];
    47
    48        if (length == 0) revert INSTR_InstructionsCannotBeEmpty(); // @note after 43
    180    function endorseProposal(uint256 proposalId_) external {
    181        uint256 userVotes = VOTES.balanceOf(msg.sender); // @note put after revert
    182
    183        if (proposalId_ == 0) {
    184            revert CannotEndorseNullProposal();
    185        }
    186
    187        Instruction[] memory instructions = INSTR.getInstructions(proposalId_);
    188        if (instructions.length == 0) {
    189            revert CannotEndorseInvalidProposal();
    190        }
    191
    241        uint256 userVotes = VOTES.balanceOf(msg.sender); // @note put after revert
    242
    243        if (activeProposal.proposalId == 0) {
    244            revert NoActiveProposalDetected();
    245        }
    246
    247        if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) {
    248            revert UserAlreadyVoted();
    249        }
  15. Duplicated require()/revert() checks should be refactored to a modifier or function

    Method Call Gas Saved: 8 111

    if (!initialized) revert Price_NotInitialized(); // @note 4 instances

QA Report

This is a test for liveactionallama

QA Report

none-critical issue foundings

N01: EVENT IS MISSING INDEXED FIELDS

prof

policies/Governance.sol, 86, b' event ProposalSubmitted(uint256 proposalId);\r'
policies/Governance.sol, 87, b' event ProposalEndorsed(uint256 proposalId, address voter, uint256 amount);\r'
policies/Governance.sol, 88, b' event ProposalActivated(uint256 proposalId, uint256 timestamp);\r'
policies/Governance.sol, 89, b' event WalletVoted(uint256 proposalId, address voter, bool for_, uint256 userVotes);\r'
policies/Governance.sol, 90, b' event ProposalExecuted(uint256 proposalId);\r'
policies/Heart.sol, 28, b' event Beat(uint256 timestamp_);\r'
policies/Heart.sol, 29, b' event RewardIssued(address to_, uint256 rewardAmount_);\r'
policies/Heart.sol, 30, b' event RewardUpdated(ERC20 token_, uint256 rewardAmount_);\r'
modules/INSTR.sol, 11, b' event InstructionsStored(uint256 instructionsId);\r'
policies/Operator.sol, 51, b' event CushionFactorChanged(uint32 cushionFactor_);\r'
policies/Operator.sol, 52, b' event CushionParamsChanged(uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_);\r'
policies/Operator.sol, 53, b' event ReserveFactorChanged(uint32 reserveFactor_);\r'
policies/Operator.sol, 54, b' event RegenParamsChanged(uint32 wait_, uint32 threshold_, uint32 observe_);\r'
modules/PRICE.sol, 26, b' event NewObservation(uint256 timestamp_, uint256 price_, uint256 movingAverage_);'
modules/PRICE.sol, 27, b' event MovingAverageDurationChanged(uint48 movingAverageDuration_);'
modules/PRICE.sol, 28, b' event ObservationFrequencyChanged(uint48 observationFrequency_);'
modules/RANGE.sol, 20, b' event WallUp(bool high_, uint256 timestamp_, uint256 capacity_);\r'
modules/RANGE.sol, 21, b' event WallDown(bool high_, uint256 timestamp_, uint256 capacity_);\r'
modules/RANGE.sol, 22, b' event CushionUp(bool high_, uint256 timestamp_, uint256 capacity_);\r'
modules/RANGE.sol, 23, b' event CushionDown(bool high_, uint256 timestamp_);\r'
modules/RANGE.sol, 29, b' event PricesChanged(\r\n uint256 wallLowPrice_,\r\n uint256 cushionLowPrice_,\r\n uint256 cushionHighPrice_,\r\n uint256 wallHighPrice_\r\n );\r'
modules/RANGE.sol, 30, b' event SpreadsChanged(uint256 cushionSpread_, uint256 wallSpread_);\r'
modules/RANGE.sol, 31, b' event ThresholdFactorChanged(uint256 thresholdFactor_);\r'

N02: INCONSISTENT VERSION OF ENGLISH BEING USED

Some functions use American English, whereas others use British English. A single project should use only one of the two

prof

interfaces/IBondCallback.sol, 2, b'pragma solidity >=0.8.0;\r'
policies/Governance.sol, 2, b'pragma solidity 0.8.15;\r'
policies/Heart.sol, 2, b'pragma solidity 0.8.15;\r'
interfaces/IBondCallback.sol, 2, b'pragma solidity >=0.8.0;\r'
policies/interfaces/IHeart.sol, 2, b'pragma solidity >=0.8.0;\r'
modules/INSTR.sol, 2, b'pragma solidity 0.8.15;\r'
policies/interfaces/IOperator.sol, 2, b'pragma solidity >=0.8.0;\r'
Kernel.sol, 2, b'pragma solidity 0.8.15;\r'
utils/KernelUtils.sol, 2, b'pragma solidity 0.8.15;\r'
modules/MINTR.sol, 2, b'pragma solidity 0.8.15;\r'
policies/Operator.sol, 2, b'pragma solidity 0.8.15;\r'
modules/PRICE.sol, 2, b'pragma solidity 0.8.15;'
policies/PriceConfig.sol, 2, b'pragma solidity 0.8.15;\r'
modules/RANGE.sol, 2, b'pragma solidity 0.8.15;\r'
policies/TreasuryCustodian.sol, 2, b'pragma solidity 0.8.15;\r'
modules/TRSRY.sol, 2, b'pragma solidity 0.8.15;\r'
policies/VoterRegistration.sol, 2, b'pragma solidity 0.8.15;\r'
modules/VOTES.sol, 2, b'pragma solidity 0.8.15;\r'

Low risk foundings

L01: RETURN VALUES OF TRANSFER()/TRANSFERFROM() NOT CHECKED

problem

Not all IERC20 implementations revert() when there’s a failure in transfer()/transferFrom(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment

prof

policies/Governance.sol, 259, b' VOTES.transferFrom(msg.sender, address(this), userVotes);\r'
policies/Governance.sol, 312, b' VOTES.transferFrom(address(this), msg.sender, userVotes);\r'
policies/Heart.sol, 112, b' rewardToken.safeTransfer(to_, reward);\r'
policies/Heart.sol, 151, b' token_.safeTransfer(msg.sender, token_.balanceOf(address(this)));\r'
policies/Operator.sol, 330, b' reserve.safeTransferFrom(msg.sender, address(TRSRY), amountIn_);\r'
policies/Operator.sol, 299, b' ohm.safeTransferFrom(msg.sender, address(this), amountIn_);\r'
modules/TRSRY.sol, 82, b' token_.safeTransfer(to_, amount_);\r'
modules/TRSRY.sol, 99, b' token_.safeTransfer(msg.sender, amount_);\r'
modules/TRSRY.sol, 110, b' token_.safeTransferFrom(msg.sender, address(this), amount_);\r'

L02: _SAFEMINT() SHOULD BE USED RATHER THAN _MINT() WHEREVER POSSIBLE

problem

_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements IERC721Receiver. Both OpenZeppelin and solmate have versions of this function

prof

modules/VOTES.sol, 36, b' mint(wallet, amount_);\r'

L03: address variable should check if it is zero

problem

MISSING CHECKS FOR ADDRESS(0X0) WHEN ASSIGNING VALUES TO ADDRESS STATE VARIABLES

prof

BondCallback.sol, 43-44

L04: SAFEAPPROVE() Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance().

problem

If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead

prof

policies/BondCallback.sol, 57, b' ohm.safeApprove(address(MINTR), type(uint256).max);\r'
policies/Operator.sol, 167, b' ohm.safeApprove(address(MINTR), type(uint256).max);\r'

QA Report

SAFEAPPROVE() IS DEPRECATED

Deprecated in favor of safeIncreaseAllowance() and safeDecreaseAllowance(). If only setting the initial allowance to the value that means infinite, safeIncreaseAllowance() can be used instead

    File: src/policies/BondCallback.sol 
    57┆         ohm.safeApprove(address(MINTR), type(uint256).max);
    167┆         ohm.safeApprove(address(MINTR), type(uint256).max);

SAFETRANSFER/SAFETRANSFERFROM CONSISTENTLY INSTEAD OF TRANSFER/TRANSFERFROM

It is good to add a require() statement that checks the return value of token transfers or
to use something like OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the
given token reverts in case of a failure. Failure to do so will cause silent failures of
transfers and affect token accounting in contract.

File: src/policies/Governance.sol 
259┆         VOTES.transferFrom(msg.sender, address(this), userVotes);
    ⋮┆----------------------------------------
312┆         VOTES.transferFrom(address(this), msg.sender, userVotes);
    ⋮┆----------------------------------------

Data Location Optimization

The linked function arguments are set as memory yet are declared in external functions.

File: src/modules/PRICE.sol:
  205      function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_)
  206:         external



File: src/policies/BondCallback.sol:
  151      /// @param  tokens_ - Array of tokens to send
  152:     function batchToTreasury(ERC20[] memory tokens_) external onlyRole("callback_admin") {


File: src/policies/Governance.sol:

   70      function requestPermissions()
   71:         external
   72          view

  162          string memory proposalURI_
  163:     ) external {


File: src/policies/PriceConfig.sol:
  45      function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_)
  46:         external
  47          onlyRole("price_admin")

File: src/policies/TreasuryCustodian.sol
    53┆     function revokePolicyApprovals(address policy_, ERC20[] memory tokens_) external {
    54┆         if (Policy(policy_).isActive()) revert PolicyStillActive();

Open TODO



src/policies/Operator.sol:
  656          /// Get latest price
  657:         /// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?
  658          /// Current price is guaranteed to be up to date, but may be a bad value if not checked?

src/policies/TreasuryCustodian.sol:
  50      // Anyone can call to revoke a deactivated policy's approvals.
  51:     // TODO Currently allows anyone to revoke any approval EXCEPT activated policies.
  52:     // TODO must reorg policy storage to be able to check for deactivated policies.
  53      function revokePolicyApprovals(address policy_, ERC20[] memory tokens_) external {

  55  
  56:         // TODO Make sure `policy_` is an actual policy and not a random address.
  57  


QA Report

EVENTS NOT EMITTED FOR IMPORTANT STATE CHANGES

When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Operator.sol#L498

 function setSpreads(uint256 cushionSpread_, uint256 wallSpread_)

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Operator.sol#L510

 function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Operator.sol#L586

    function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Operator.sol#L624

    function toggleActive() external onlyRole("operator_admin") {

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Heart.sol#L130

    function resetBeat() external onlyRole("heart_admin") {

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Heart.sol#L135

    function toggleBeat() external onlyRole("heart_admin") {

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/Kernel.sol#L76

    function toggleBeat() external onlyRole("heart_admin") {

approving MAX_UINT amount of ERC20 token is not safe

approving the maximum value of uint26 is a known practive to safe gas.

However , this pattern was proven to increase the impact of an attack many times in the pass.

in case the approved contract get hacked.

We recommand approving the exact amount that's needed to be transfered

or add an external function that allows the revovaction of approvals.

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/BondCallback.sol#L57

ohm.safeApprove(address(MINTR), type(uint256).max);

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/BondCallback.sol#L95

TRSRY.setApprovalFor(address(this), payoutToken, type(uint256).max);

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/modules/TRSRY.sol#L147

      if (approval != type(uint256).max) {
            unchecked {
                withdrawApproval[withdrawer_][token_] = approval - amount_;
            }
        }

Unhandled return value when doing transfer

we recommand that we handle the external function call return value in case of the slient failure
of an external function.

We recommand use safeTransferFrom or safeTransfer instead of transfer or transferFrom

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Governance.sol#L259

    VOTES.transferFrom(msg.sender, address(this), userVotes);

https://github.com/code-423n4/2022-08-olympus/blob/b5e139d732eb4c07102f149fb9426d356af617aa/src/policies/Governance.sol#L312

     VOTES.transferFrom(address(this), msg.sender, userVotes);

QA Report

[L-01] Unsafe calls to optional ERC20 functions (decimals(), name() and symbol()

are optional parts of the ERC20 specification, so there are tokens that
do not implement them. It’s not safe to cast arbitrary token addresses
in order to call these functions. If IERC20Metadata is to be relied on, that should be the variable type of the token variable, rather than it being address, so the compiler can verify that types correctly match, rather than this being a runtime failure. See this prior instance of this issue which was marked as Low risk. Do this to resolve the issue.):

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 84):

    uint8 ohmEthDecimals = _ohmEthPriceFeed.decimals();

  2. File: 2022-08-olympus/src/modules/PRICE.sol (line 87):

    uint8 reserveEthDecimals = _reserveEthPriceFeed.decimals();

  3. File: 2022-08-olympus/src/policies/Operator.sol (line 122):

    ohmDecimals = tokens_[0].decimals();

  4. File: 2022-08-olympus/src/policies/Operator.sol (line 124):

    reserveDecimals = tokens_[1].decimals();

  5. File: 2022-08-olympus/src/policies/Operator.sol (line 418):

    uint8 oracleDecimals = PRICE.decimals();

  6. File: 2022-08-olympus/src/policies/Operator.sol (line 493):

    return decimals - int8(PRICE.decimals());

[L-02] Missing checks for address(0x0) when assigning values to address state variables:

  1. File: 2022-08-olympus/src/Kernel.sol (line 66):

    kernel = kernel_;

  2. File: 2022-08-olympus/src/Kernel.sol (line 77):

    kernel = newKernel_;

  3. File: 2022-08-olympus/src/Kernel.sol (line 127):

    isActive = activate_;

  4. File: 2022-08-olympus/src/modules/RANGE.sol (line 265):

    thresholdFactor = thresholdFactor_;

  5. File: 2022-08-olympus/src/policies/BondCallback.sol (line 43-44):

    aggregator = aggregator_; ohm = ohm_;

[L-03] initialize functions can be front-run (See this finding from a prior badger-dao contest for details):

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 205):

    function initialize(

  2. File: 2022-08-olympus/src/policies/Operator.sol (line 598):

    function initialize()

  3. File: 2022-08-olympus/src/policies/PriceConfig.sol (line 45):

    function initialize(

  4. File: 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 125):

    function initialize(

Non-Critical Issues

[N-01] dding a return statement when the function defines a named return variable, is redundant:

  1. File: 2022-08-olympus/src/modules/RANGE.sol (line 276):

return _range;

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 784):

return _status;

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 799):

return _config;

[N-02] constants should be defined rather than using magic numbers:

  1. File: 2022-08-olympus/src/utils/KernelUtils.sol (line 46):

    if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_);

  2. File: 2022-08-olympus/src/utils/KernelUtils.sol (line 60):

    if ((char < 0x61 || char > 0x7A) && char != 0x5f && char != 0x00) {

  3. File: 2022-08-olympus/src/modules/RANGE.sol (line 65):

    uint256 public constant FACTOR_SCALE = 1e4;

  4. File: 2022-08-olympus/src/modules/RANGE.sol (line 79-80):

    ERC20[2] memory tokens_, uint256[3] memory rangeParams_

  5. File: 2022-08-olympus/src/modules/RANGE.sol (line 43-44):

    wallSpread_ > 10000 || wallSpread_ < 100 || cushionSpread_ > 10000 || cushionSpread_ < 100 ||

  6. File: 2022-08-olympus/src/modules/RANGE.sol (line 264):

    if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();

  7. File: 2022-08-olympus/src/modules/PRICE.sol (line 59):

    uint8 public constant decimals = 18;

  8. File: 2022-08-olympus/src/modules/PRICE.sol (line 90-91):

    if (exponent > 38) revert Price_InvalidParams(); _scaleFactor = 10**exponent;

  9. File: 2022-08-olympus/src/modules/PRICE.sol (line 165):

    if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))

  10. File: 2022-08-olympus/src/policies/Operator.sol (line 79-80):

uint32 public constant FACTOR_SCALE = 1e4;

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 106):

if (configParams[2] < uint32(10_000)) revert Operator_InvalidParams();

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 111):

if (configParams[4] > 10000 || configParams[4] < 100) revert Operator_InvalidParams();

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 375-379):

uint256 oracleScale = 10**uint8(int8(PRICE.decimals()) - priceDecimals); uint256 bondScale = 10 ** uint8( 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals );

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 419-434):

` uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;
uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;

        /// Calculate scaleAdjustment for bond market
        /// Price decimals are returned from the perspective of the quote token
        /// so the operations assume payoutPriceDecimal is zero and quotePriceDecimals
        /// is the priceDecimal value
        int8 priceDecimals = _getPriceDecimals(invCushionPrice);
        int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);

        /// Calculate oracle scale and bond scale with scale adjustment and format prices for bond market
        uint256 oracleScale = 10**uint8(int8(oracleDecimals) - priceDecimals);
        uint256 bondScale = 10 **
            uint8(
                36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals
            );`
  1. File: 2022-08-olympus/src/policies/Operator.sol (line 518):

if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 535):

if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 550):

if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 753-766):

` 10reserveDecimals * RANGE.price(true, false),
10
ohmDecimals * 10**PRICE.decimals()
);

        /// Revert if amount out exceeds capacity
        if (amountOut > RANGE.capacity(false)) revert Operator_InsufficientCapacity();

        return amountOut;
    } else if (tokenIn_ == reserve) {
        /// Calculate amount out
        uint256 amountOut = amountIn_.mulDiv(
            10**ohmDecimals * 10**PRICE.decimals(),
            10**reserveDecimals * RANGE.price(true, true)
        );`
  1. File: 2022-08-olympus/src/policies/Operator.sol (line 784-786):

10**ohmDecimals * 10**PRICE.decimals(), 10**reserveDecimals * RANGE.price(true, true) ) * (FACTOR_SCALE + RANGE.spread(true) * 2)) /

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 268):

if (netVotes * 100 < VOTES.totalSupply() * EXECUTION_THRESHOLD) {

[N-03] Event is missing indexed fields (Each event should use three indexed fields if there are three or more fields):

  1. File: 2022-08-olympus/src/Kernel.sol (line 203-208):

    event PermissionsUpdated( Keycode indexed keycode_, Policy indexed policy_, bytes4 funcSelector_, bool granted_ );

  2. File: 2022-08-olympus/src/modules/TRSRY.sol (line 20):

    event ApprovedForWithdrawal(address indexed policy_, ERC20 indexed token_, uint256 amount_);

  3. File: 2022-08-olympus/src/modules/TRSRY.sol (line 27-29):

    event DebtIncurred(ERC20 indexed token_, address indexed policy_, uint256 amount_); event DebtRepaid(ERC20 indexed token_, address indexed policy_, uint256 amount_); event DebtSet(ERC20 indexed token_, address indexed policy_, uint256 amount_);

  4. File: 2022-08-olympus/src/modules/RANGE.sol (line 20-31):

    event WallUp(bool high_, uint256 timestamp_, uint256 capacity_); event WallDown(bool high_, uint256 timestamp_, uint256 capacity_); event CushionUp(bool high_, uint256 timestamp_, uint256 capacity_); event CushionDown(bool high_, uint256 timestamp_); event PricesChanged( uint256 wallLowPrice_, uint256 cushionLowPrice_, uint256 cushionHighPrice_, uint256 wallHighPrice_ ); event SpreadsChanged(uint256 cushionSpread_, uint256 wallSpread_); event ThresholdFactorChanged(uint256 thresholdFactor_);

  5. File: 2022-08-olympus/src/modules/PRICE.sol (line 26):

    event NewObservation(uint256 timestamp_, uint256 price_, uint256 movingAverage_);

  6. File: 2022-08-olympus/src/policies/Operator.sol (line 45-50):

    event Swap( ERC20 indexed tokenIn_, ERC20 indexed tokenOut_, uint256 amountIn_, uint256 amountOut_ );

  7. File: 2022-08-olympus/src/policies/Operator.sol (line 52):

    event CushionParamsChanged(uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_);

  8. File: 2022-08-olympus/src/policies/Operator.sol (line 54):

    event RegenParamsChanged(uint32 wait_, uint32 threshold_, uint32 observe_);

  9. File: 2022-08-olympus/src/policies/Heart.sol (line 28-30):

    event RewardIssued(address to_, uint256 rewardAmount_); event RewardUpdated(ERC20 token_, uint256 rewardAmount_);

  10. File: 2022-08-olympus/src/policies/Governance.sol (line 87-89):

event ProposalEndorsed(uint256 proposalId, address voter, uint256 amount); event ProposalActivated(uint256 proposalId, uint256 timestamp); event WalletVoted(uint256 proposalId, address voter, bool for_, uint256 userVotes);

[N-04] Use of sensitive/non-inclusive terms:

  1. File: 2022-08-olympus/src/modules/RANGE.sol (line 44):

bool active;

  1. File: 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 34):

bool[] observations;

[N-05] Open TODOs (Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment):

  1. File: 022-08-olympus/src/policies/TreasuryCustodian.sol (line 51-52):

// TODO Currently allows anyone to revoke any approval EXCEPT activated policies. // TODO must reorg policy storage to be able to check for deactivated policies.

  1. File: 022-08-olympus/src/policies/TreasuryCustodian.sol (line 56):

// TODO Make sure policy_ is an actual policy and not a random address.

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 657):

/// TODO determine if this should use the last price from the MA or recalculate the current price, ideally last price is ok since it should have been just updated and should include check against secondary?

[N-06] 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 public to external.):

  1. File: 2022-08-olympus/src/Kernel.sol (line 95):

function KEYCODE() public pure virtual returns (Keycode) {}

  1. File: 2022-08-olympus/src/Kernel.sol (line 439):

function grantRole(Role role_, address addr_) public onlyAdmin {

  1. File: 2022-08-olympus/src/Kernel.sol (line 451):

function revokeRole(Role role_, address addr_) public onlyAdmin {

  1. File: 2022-08-olympus/src/modules/TRSRY.sol (line 75-79):

function withdrawReserves( address to_, ERC20 token_, uint256 amount_ ) public {

  1. File: 2022-08-olympus/src/modules/MINTR.sol (line 33-39):

` function mintOhm(address to_, uint256 amount_) public permissioned {
ohm.mint(to_, amount_);
}

function burnOhm(address from_, uint256 amount_) public permissioned {
    ohm.burnFrom(from_, amount_);
}`
  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 154):

function getCurrentPrice() public view returns (uint256) {

  1. File: 2022-08-olympus/src/policies/Heart.sol (line 37):

function getInstructions(uint256 instructionsId_) public view returns (Instruction[] memory) {

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 121):

function frequency() public view returns (uint256) {

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 145-153):

` function getMetadata(uint256 proposalId_) public view returns (ProposalMetadata memory) {
return getProposalMetadata[proposalId_];
}

/// @notice Return the currently active proposal in governance.
/// @dev    Used to return & access the entire struct active proposal struct in solidity.
function getActiveProposal() public view returns (ActivatedProposal memory) {
    return activeProposal;
}`

[N-07] Numeric values having to do with time should use time units for readability (There are units for seconds, minutes, hours, days, and weeks):

  1. File: 2022-08-olympus/src/Kernel.sol (line 121-137):

` uint256 public constant SUBMISSION_REQUIREMENT = 100;

/// @notice Amount of time a submitted proposal has to activate before it expires.
uint256 public constant ACTIVATION_DEADLINE = 2 weeks;

/// @notice Amount of time an activated proposal must stay up before it can be replaced by a new activated proposal.
uint256 public constant GRACE_PERIOD = 1 weeks;

/// @notice Endorsements required to activate a proposal as percentage of total supply.
uint256 public constant ENDORSEMENT_THRESHOLD = 20;

/// @notice Net votes required to execute a proposal on chain as a percentage of total supply.
uint256 public constant EXECUTION_THRESHOLD = 33;

/// @notice Required time for a proposal to be active before it can be executed.
/// @dev    This amount should be greater than 0 to prevent flash loan attacks.
uint256 public constant EXECUTION_TIMELOCK = 3 days;`

QA Report

issue
1 typo in comments
2 event is missing indexed fields
3 _safemint() should be used rather than _mint() wherever possible
4 constants should be defined rather than using magic numbers
5 lines are too long
6 event is missing old value
7 function should have emit
8 inconsistent use of named return variables
9 open todos

1. typo in comments

numbe --> number

deactive --> deactivate

2. event is missing indexed fields

Each event should use three indexed fields if there are three or more fields

3. _safemint() should be used rather than _mint() wherever possible

4. constants should be defined rather than using magic numbers

Even assembly can benefit from using readable constants instead of hex/numeric literals

5. lines are too long

Usually lines in source code are limited to 80 characters. Today’s screens are much larger so it’s reasonable to stretch this in some cases. Since the files will most likely reside in GitHub, and GitHub starts using a scroll bar in all cases when the length is over 164 characters, the lines below should be split when they reach that length

6. event is missing old value

throughout the codebase, events are generally emitted when sensitive changes are made to the contracts. here emit is missing the old value.

7. function should have emit

8. inconsistent use of named return variables

there is an inconsistent use of named return variables in the contract some functions return named variables, others return explicit values. consider adopting a consistent approach.
this would improve both the explicitness and readability of the code, and it may also help reduce regressions during future code refactors.

most files have functions without named return but here they are not:

9. open todos

Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment

ERC20 token balance of contract may vary

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L105-L119

Vulnerability details

Impact

Currently code is using prevBalance and afterBalance technique. This can be dangerous. For example contract can receive same ERC20 token from another place while that transaction executes.

Proof of Concept

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/modules/TRSRY.sol#L105-L119

Tools Used

Static code analysis

Recommended Mitigation Steps

https://gist.github.com/dplamenov/8b418e46bc355a51fb7c037722a0e61e

setDebt() causes repayLoan() to fail

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#L122-L135

Vulnerability details

Impact

setDebt() will reduce the totalDebt[token_] parameter. If the user repayloan() after this, then totalDebt[token_] may be 0, but in fact there may be users who have not returned the token. But since totalDebt[token_] is 0 , calling repayLoan() will always fail

Proof of Concept

1.alice getLoan(100) tokena
2.tom getLoan(100) tokena
3.setDebt(tokena,alice,50)
now:
reserveDebt[tokena][alice] = 50
totalDebt[tokena] = 50

4.alice repayLoan(tokena,50)
totalDebt[tokena] = 0

5.tom repayLoan(tokena,100) will fail

Tools Used

vscode

Recommended Mitigation Steps

Gas Optimizations

-> X = X + Y IS CHEAPER THAN X += Y (same for X = X - Y IS CHEAPER THAN X -= Y)

https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=reserveDebt%5Btoken_%5D%5Bmsg.sender%5D%20%2B%3D%20amount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=totalDebt%5Btoken_%5D%20%2B%3D%20amount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=reserveDebt%5Btoken_%5D%5Bmsg.sender%5D%20%2D%3D%20received%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=totalDebt%5Btoken_%5D%20%2D%3D%20received%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=oldDebt%20%3C%20amount_)-,totalDebt%5Btoken_%5D%20%2B%3D%20amount_,-%2D%20oldDebt%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=totalDebt%5Btoken_%5D%20%2D%3D%20oldDebt
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=_movingAverage%20%2B%3D%20(currentPrice%20%2D%20earliestPrice)%20/%20numObs%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=_movingAverage%20%2D%3D%20(earliestPrice%20%2D%20currentPrice)%20/%20numObs%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=total%20%2B%3D%20startObservations_%5Bi%5D%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/VOTES.sol#:~:text=balanceOf%5Bfrom_%5D%20%2D%3D%20amount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/VOTES.sol#:~:text=balanceOf%5Bto_%5D%20%2B%3D%20amount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/BondCallback.sol#:~:text=_amountsPerMarket%5Bid_%5D%5B0%5D%20%2B%3D%20inputAmount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/BondCallback.sol#:~:text=_amountsPerMarket%5Bid_%5D%5B1%5D%20%2B%3D%20outputAmount_%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Heart.sol#:~:text=lastBeat%20%2B%3D%20frequency()%3B

->STATE VARIABLES ONLY SET IN THE CONSTRUCTOR SHOULD BE DECLARED IMMUTABLE

Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32 (3 gas)

https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=uint256%20internal%20immutable%20_scaleFactor%3B

-> ++i costs less gas compared to i++ or i += 1 (Also --i costs less gas compared to i-- or i -= 1)

https://github.com/code-423n4/2022-08-olympus/blob/main/src/utils/KernelUtils.sol#:~:text=A%2DZ%20only-,unchecked%20%7B,i%2B%2B%3B,-%7D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/utils/KernelUtils.sol#:~:text=%7D-,unchecked%20%7B,i%2B%2B%3B,-%7D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=/%2010%3B-,decimals%2B%2B,-%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=%3D%20true%3B-,_status.low.count%2B%2B,-%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=%3D%20false%3B-,_status.low.count%2D%2D,-%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=_status.high.count%2B%2B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=_status.high.count%2D%2D

-> USAGE OF UINTS/INTS SMALLER THAN 32 BYTES (256 BITS) INCURS OVERHEAD
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#:~:text=%3D%3E%20mapping(-,bytes4,-%3D%3E%20bool)))
https://github.com/code-423n4/2022-08-olympus/blob/main/src/utils/KernelUtils.sol#:~:text=)%20pure%20returns-,(bytes5),-%7B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=override%20returns%20(-,uint8%20major%2C,-uint8%20minor)%20%7B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/TRSRY.sol#:~:text=uint8%20major%2C-,uint8%20minor),-%7B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=by%20the%20contract.-,uint8,-public%20constant%20decimals
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=_ohmEthPriceFeed%20%3D%20ohmEthPriceFeed_%3B-,uint8,-ohmEthDecimals%20%3D%20_ohmEthPriceFeed
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/INSTR.sol#:~:text=override%20returns%20(-,uint8%20major,-%2C%20uint8%20minor
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/INSTR.sol#:~:text=uint8%20major%2C-,uint8%20minor),-%7B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=public%20immutable%20ohm%3B-,uint8,-public%20immutable%20ohmDecimals
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=public%20immutable%20reserve%3B-,uint8,-public%20immutable%20reserveDecimals
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=is%20the%20priceDecimal%20value-,int8,-priceDecimals%20%3D%20_getPriceDecimals(range
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=cushion.high.price)%3B-,int8,-scaleAdjustment%20%3D%20int8
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=int8%20scaleAdjustment%20%3D-,int8,-(ohmDecimals)%20%2D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=(ohmDecimals)%20%2D-,int8,-(reserveDecimals)%20%2B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=uint256%20bondScale%20%3D%2010%20**-,uint8(,-36%20%2B%20scaleAdjustment%20%2B%20int8(reserveDecimals
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=the%20low%20side-,uint8,-oracleDecimals%20%3D%20PRICE
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=is%20the%20priceDecimal%20value-,int8,-priceDecimals%20%3D%20_getPriceDecimals(invCushionPrice
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=_getPriceDecimals(invCushionPrice)%3B-,int8,-scaleAdjustment%20%3D%20int8
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=int8%20scaleAdjustment%20%3D-,int8,-(reserveDecimals)%20%2D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=(reserveDecimals)%20%2D-,int8,-(ohmDecimals)%20%2B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=%3D%2010**-,uint8,-(int8(oracleDecimals
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=**uint8(-,int8,-(oracleDecimals)%20%2D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=%2B%20scaleAdjustment%20%2B-,int8,-(ohmDecimals)%20%2D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=(ohmDecimals)%20%2D-,int8,-(reserveDecimals)%20%2D
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=return%20decimals%20%2D-,int8,-(PRICE.decimals

->IT COSTS MORE GAS TO INITIALIZE NON-CONSTANT/NON-IMMUTABLE VARIABLES TO ZERO THAN TO LET THE DEFAULT OF ZERO BE APPLIED

https://github.com/code-423n4/2022-08-olympus/blob/main/src/utils/KernelUtils.sol#:~:text=for%20(-,uint256%20i%20%3D%200%3B,-i%20%3C%205
https://github.com/code-423n4/2022-08-olympus/blob/main/src/utils/KernelUtils.sol#:~:text=for-,(uint256%20i%20%3D%200,-%3B%20i%20%3C%2032

->USING BOOLS FOR STORAGE INCURS OVERHEAD

https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#:~:text=mapping(bytes4%20%3D%3E%20bool)))
https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#:~:text=mapping(Role%20%3D%3E%20bool))
https://github.com/code-423n4/2022-08-olympus/blob/main/src/Kernel.sol#:~:text=if%20role%20exists.-,mapping(Role%20%3D%3E%20bool),-public%20isRole%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/modules/PRICE.sol#:~:text=and%20therefore%20active).-,bool,-public%20initialized%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=bool%20public%20initialized%3B
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Operator.sol#:~:text=Operator%20is%20active-,bool%20public%20active%3B,-///%20Modules
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/BondCallback.sol#:~:text=mapping(uint256%20%3D%3E%20bool)
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Heart.sol#:~:text=stopped%2C%20true%20%3D%20beating-,bool%20public%20active%3B,-///%20%40notice%20Timestamp%20of
https://github.com/code-423n4/2022-08-olympus/blob/main/src/policies/Governance.sol#:~:text=mapping(address%20%3D%3E%20bool)

QA Report

Consider emitting an event when after tokens are safely reclaimed by the user. This allows for off chain monitoring in addition to allowing end users to observe and trust that these changes have occurred correctly.

This was observed on line 312 of reclaimVotes in the Governance.sol contract.

Potential Reward tokens Rug Pull by heart admin.

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Heart.sol#L150

Vulnerability details

The heart_admin of the Heart contract can transfer all rewards tokens to himself. There is no check to prevent that reward token not being withdrawn. Since it is issued as rewards when calling beat() function

Impact

heart_admin can sweep reward token balances of the heart contract.

Recommended Mitigation Steps

Can add this check:

require(_token != rewardToken, "Not Withdrawable!");

OlympusGovernance: Users can only vote once for an activeProposal

Lines of code

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L240-L262

Vulnerability details

Impact

In the vote function of the OlympusGovernance contract, each user can only vote once for an activeProposal, which prevents the user from voting for the activeProposal even if new VOTES are obtained after voting.
Consider the following scenario:

  1. User A calls the vote function to vote for an activeProposal
  2. voter_admin calls the issueVotesTo function of the VoterRegistration contract to assign new VOTES to user A, or user A calls the reclaimVotes function to reclaim VOTES
  3. Since user A's userVotesForProposal > 0, user A cannot use the newly obtained VOTES to vote for the activeProposal

Proof of Concept

https://github.com/code-423n4/2022-08-olympus/blob/2a0b515012b4a40076f6eac487f7816aafb8724a/src/policies/Governance.sol#L240-L262

Tools Used

None

Recommended Mitigation Steps

In the vote function, when the user's balance is greater than userVotesForProposal, the user is allowed to vote using the difference

Gas Optimizations

gas optimization

G01: COMPARISONS WITH ZERO FOR UNSIGNED INTEGERS

problem

0 is less gas efficient than !0 if you enable the optimizer at 10k AND you’re in a require statement. Detailed explanation with the opcodes https://twitter.com/gzeon/status/1485428085885640706

prof

policies/Governance.sol, 247, b' if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) {\r'

G02: PREFIX INCREMENT SAVE MORE GAS

problem

prefix increment ++i is more cheaper than postfix i++

prof

policies/Operator.sol, 488, b' decimals++;\r'
policies/Operator.sol, 675, b' _status.low.count--;\r'
policies/Operator.sol, 670, b' _status.low.count++;\r'
policies/Operator.sol, 691, b' _status.high.count--;\r'
policies/Operator.sol, 686, b' _status.high.count++;\r'

G03: X += Y COSTS MORE GAS THAN X = X + Y FOR STATE VARIABLES

prof

policies/BondCallback.sol, 143, b' amountsPerMarket[id][0] += inputAmount_;\r'
policies/BondCallback.sol, 144, b' amountsPerMarket[id][1] += outputAmount_;\r'
policies/Governance.sol, 194, b' totalEndorsementsForProposal[proposalId_] -= previousEndorsement;\r'
policies/Governance.sol, 198, b' totalEndorsementsForProposal[proposalId_] += userVotes;\r'
policies/Governance.sol, 254, b' noVotesForProposal[activeProposal.proposalId] += userVotes;\r'
policies/Governance.sol, 252, b' yesVotesForProposal[activeProposal.proposalId] += userVotes;\r'
policies/Heart.sol, 103, b' lastBeat += frequency();\r'
modules/PRICE.sol, 138, b' movingAverage -= (earliestPrice - currentPrice) / numObs;'
modules/PRICE.sol, 136, b' movingAverage += (currentPrice - earliestPrice) / numObs;'
modules/TRSRY.sol, 96, b' reserveDebt[token
][msg.sender] += amount
;\r'
modules/TRSRY.sol, 97, b' totalDebt[token_] += amount_;\r'
modules/TRSRY.sol, 115, b' reserveDebt[token_][msg.sender] -= received;\r'
modules/TRSRY.sol, 116, b' totalDebt[token_] -= received;\r'
modules/TRSRY.sol, 132, b' else totalDebt[token_] -= oldDebt - amount_;\r'
modules/TRSRY.sol, 131, b' if (oldDebt < amount_) totalDebt[token_] += amount_ - oldDebt;\r'

G04: USING BOOLS FOR STORAGE INCURS OVERHEAD

problem

// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.

prof

policies/Governance.sol, 105, b' mapping(uint256 => bool) public proposalHasBeenActivated;\r'
policies/Governance.sol, 117, b' mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal;\r'
Kernel.sol, 181, b' mapping(Keycode => mapping(Policy => mapping(bytes4 => bool))) public modulePermissions;\r'
Kernel.sol, 194, b' mapping(address => mapping(Role => bool)) public hasRole;\r'
Kernel.sol, 197, b' mapping(Role => bool) public isRole;\r'

G05: resign the default value to the variables.

problem

resign the default value to the variables will cost more gas.

prof

Kernel.sol, 397, b' for (uint256 i = 0; i < reqLength; ) {\r'

G06: ++I/I++ SHOULD BE UNCHECKED{++I}/UNCHECKED{I++} WHEN IT IS NOT POSSIBLE FOR THEM TO OVERFLOW, AS IS THE CASE WHEN USED IN FOR- AND WHILE-LOOPS

problem

The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop

prof

modules/INSTR.sol, 44, b' uint256 instructionsId = ++totalInstructions;\r'
policies/Operator.sol, 488, b' decimals++;\r'
policies/Operator.sol, 675, b' _status.low.count--;\r'
policies/Operator.sol, 670, b' _status.low.count++;\r'
policies/Operator.sol, 691, b' _status.high.count--;\r'
policies/Operator.sol, 686, b' _status.high.count++;\r'

G07: FUNCTIONS GUARANTEED TO REVERT WHEN CALLED BY NORMAL USERS CAN BE MARKED PAYABLE

problem

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. 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

prof

policies/Heart.sol, 132, b' function resetBeat() external onlyRole("heart_admin") '
policies/Heart.sol, 132, b' function resetBeat() external onlyRole("heart_admin") '
policies/Heart.sol, 137, b' function toggleBeat() external onlyRole("heart_admin") '
policies/Heart.sol, 137, b' function toggleBeat() external onlyRole("heart_admin") '
policies/Heart.sol, 147, b' function setRewardTokenAndAmount(ERC20 token_, uint256 reward_)\r\n external\r\n onlyRole("heart_admin")\r\n '
policies/Heart.sol, 152, b' function withdrawUnspentRewards(ERC20 token_) external onlyRole("heart_admin") '
policies/Heart.sol, 152, b' function withdrawUnspentRewards(ERC20 token_) external onlyRole("heart_admin") '
Kernel.sol, 260, b' function executeAction(Actions action_, address target_) external onlyExecutor '
Kernel.sol, 448, b' function grantRole(Role role_, address addr_) public onlyAdmin '
Kernel.sol, 448, b' function grantRole(Role role_, address addr_) public onlyAdmin '
Kernel.sol, 458, b' function revokeRole(Role role_, address addr_) public onlyAdmin '
Kernel.sol, 458, b' function revokeRole(Role role_, address addr_) public onlyAdmin '
policies/Operator.sol, 595, b' function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)\r\n external\r\n onlyRole("operator_admin")\r\n '
policies/Operator.sol, 615, b' function initialize() external onlyRole("operator_admin") '
policies/Operator.sol, 615, b' function initialize() external onlyRole("operator_admin") '
policies/Operator.sol, 621, b' function regenerate(bool high_) external onlyRole("operator_admin") '
policies/Operator.sol, 621, b' function regenerate(bool high_) external onlyRole("operator_admin") '
policies/Operator.sol, 627, b' function toggleActive() external onlyRole("operator_admin") '
policies/Operator.sol, 627, b' function toggleActive() external onlyRole("operator_admin") '
policies/PriceConfig.sol, 50, b' function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_)\r\n external\r\n onlyRole("price_admin")\r\n '
policies/PriceConfig.sol, 63, b' function changeMovingAverageDuration(uint48 movingAverageDuration_)\r\n external\r\n onlyRole("price_admin")\r\n '
policies/PriceConfig.sol, 74, b' function changeObservationFrequency(uint48 observationFrequency_)\r\n external\r\n onlyRole("price_admin")\r\n '
policies/VoterRegistration.sol, 48, b' function issueVotesTo(address wallet_, uint256 amount_) external onlyRole("voter_admin") '
policies/VoterRegistration.sol, 48, b' function issueVotesTo(address wallet_, uint256 amount_) external onlyRole("voter_admin") '
policies/VoterRegistration.sol, 56, b' function revokeVotesFrom(address wallet_, uint256 amount_) external onlyRole("voter_admin") '
policies/VoterRegistration.sol, 56, b' function revokeVotesFrom(address wallet_, uint256 amount_) external onlyRole("voter_admin") '

G08: USING PRIVATE RATHER THAN PUBLIC FOR CONSTANTS, SAVES GAS

problem:

We can save getter function of public constants.

prof:

policies/Governance.sol, 121, b' uint256 public constant SUBMISSION_REQUIREMENT = 100;\r'
policies/Governance.sol, 124, b' uint256 public constant ACTIVATION_DEADLINE = 2 weeks;\r'
policies/Governance.sol, 127, b' uint256 public constant GRACE_PERIOD = 1 weeks;\r'
policies/Governance.sol, 130, b' uint256 public constant ENDORSEMENT_THRESHOLD = 20;\r'
policies/Governance.sol, 133, b' uint256 public constant EXECUTION_THRESHOLD = 33;\r'
policies/Governance.sol, 137, b' uint256 public constant EXECUTION_TIMELOCK = 3 days;\r'
modules/MINTR.sol, 9, b' OHM public immutable ohm;\r'
policies/Operator.sol, 82, b' ERC20 public immutable ohm;\r'
policies/Operator.sol, 83, b' uint8 public immutable ohmDecimals;\r'
policies/Operator.sol, 85, b' ERC20 public immutable reserve;\r'
policies/Operator.sol, 86, b' uint8 public immutable reserveDecimals;\r'
policies/Operator.sol, 89, b' uint32 public constant FACTOR_SCALE = 1e4;\r'
modules/PRICE.sol, 59, b' uint8 public constant decimals = 18;'
modules/RANGE.sol, 65, b' uint256 public constant FACTOR_SCALE = 1e4;\r'
modules/RANGE.sol, 68, b' ERC20 public immutable ohm;\r'
modules/RANGE.sol, 71, b' ERC20 public immutable reserve;\r'

G09: USAGE OF UINTS/INTS SMALLER THAN 32 BYTES (256 BITS) INCURS OVERHEAD

problem

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

prof

policies/Governance.sol, 164, b' if (VOTES.balanceOf(msg.sender) * 10000 < VOTES.totalSupply() * SUBMISSION_REQUIREMENT)\r'
policies/Governance.sol, 183, b' if (proposalId_ == 0) {\r'
policies/Governance.sol, 188, b' if (instructions.length == 0) {\r'
policies/Governance.sol, 243, b' if (activeProposal.proposalId == 0) {\r'
policies/Governance.sol, 247, b' if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) {\r'
policies/Governance.sol, 268, b' if (netVotes * 100 < VOTES.totalSupply() * EXECUTION_THRESHOLD) {\r'
policies/Governance.sol, 298, b' if (userVotes == 0) {\r'
policies/Heart.sol, 122, b' return uint256(PRICE.observationFrequency());\r'
policies/Heart.sol, 122, b' return uint256(PRICE.observationFrequency());\r'
modules/INSTR.sol, 29, b' return (1, 0);\r'
modules/INSTR.sol, 48, b' if (length == 0) revert INSTR_InstructionsCannotBeEmpty();\r'
modules/INSTR.sol, 61, b' } else if (instruction.action == Actions.ChangeExecutor && i != length - 1) {\r'
modules/INSTR.sol, 70, b' instructions.push(instructions_[i]);\r'
modules/INSTR.sol, 70, b' instructions.push(instructions_[i]);\r'
Kernel.sol, 133, b' if (moduleForKeycode == address(0)) revert Policy_ModuleDoesNotExist(keycode_);\r'
Kernel.sol, 269, b' if (address(getModuleForKeycode[keycode]) != address(0))\r'
Kernel.sol, 274, b' allKeycodes.push(keycode);\r'
Kernel.sol, 283, b' if (address(oldModule) == address(0) || oldModule == newModule_)\r'
Kernel.sol, 299, b' activePolicies.push(policy_);\r'
Kernel.sol, 309, b' moduleDependents[keycode].push(policy_);\r'
Kernel.sol, 337, b' activePolicies.pop();\r'
Kernel.sol, 397, b' for (uint256 i = 0; i < reqLength; ) {\r'
Kernel.sol, 422, b' dependents.pop();\r'
Kernel.sol, 422, b' dependents.pop();\r'
modules/MINTR.sol, 26, b' return (1, 0);\r'
policies/Operator.sol, 72, b' OlympusMinter internal MINTR;\r'
policies/Operator.sol, 83, b' uint8 public immutable ohmDecimals;\r'
policies/Operator.sol, 86, b' uint8 public immutable reserveDecimals;\r'
policies/Operator.sol, 89, b' uint32 public constant FACTOR_SCALE = 1e4;\r'
policies/Operator.sol, 164, b' MINTR = OlympusMinter(getModuleAddress(dependencies[3]));\r'
policies/Operator.sol, 167, b' ohm.safeApprove(address(MINTR), type(uint256).max);\r'
policies/Operator.sol, 174, b' Keycode MINTR_KEYCODE = MINTR.KEYCODE();\r'
policies/Operator.sol, 202, b' updateCapacity(true, 0);\r'
policies/Operator.sol, 203, b' updateCapacity(false, 0);\r'
policies/Operator.sol, 210, b' uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config
.regenWait) &&\r'
policies/Operator.sol, 210, b' uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config
.regenWait) &&\r'
policies/Operator.sol, 210, b' uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 210, b' uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 210, b' uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 211, b' status.high.count >= config.regenThreshold\r'
policies/Operator.sol, 211, b' status.high.count >= config.regenThreshold\r'
policies/Operator.sol, 216, b' uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 216, b' uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 216, b' uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 216, b' uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 216, b' uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&\r'
policies/Operator.sol, 217, b' status.low.count >= config.regenThreshold\r'
policies/Operator.sol, 217, b' status.low.count >= config.regenThreshold\r'
policies/Operator.sol, 333, b' MINTR.mintOhm(msg.sender, amountOut);\r'
policies/Operator.sol, 302, b' MINTR.burnOhm(address(this), amountIn_);\r'
policies/Operator.sol, 418, b' uint8 oracleDecimals = PRICE.decimals();\r'
policies/Operator.sol, 418, b' uint8 oracleDecimals = PRICE.decimals();\r'
policies/Operator.sol, 418, b' uint8 oracleDecimals = PRICE.decimals();\r'
policies/Operator.sol, 419, b' uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;\r'
policies/Operator.sol, 419, b' uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;\r'
policies/Operator.sol, 420, b' uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;\r'
policies/Operator.sol, 420, b' uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;\r'
policies/Operator.sol, 426, b' int8 priceDecimals = getPriceDecimals(invCushionPrice);\r'
policies/Operator.sol, 426, b' int8 priceDecimals = getPriceDecimals(invCushionPrice);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 427, b' int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10
uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10
uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10
uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 430, b' uint256 oracleScale = 10
uint8(int8(oracleDecimals) - priceDecimals);\r'
policies/Operator.sol, 431, b' uint256 bondScale = 10 **\r'
policies/Operator.sol, 434, b' uint8(\r\n 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r\n );\r'
policies/Operator.sol, 432, b' uint8(\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 433, b' 36 + scaleAdjustment + int8(ohmDecimals) - int8(reserveDecimals) - priceDecimals\r'
policies/Operator.sol, 443, b' uint256 marketCapacity = range.low.capacity.mulDiv(config
.cushionFactor, FACTOR_SCALE);\r'
policies/Operator.sol, 443, b' uint256 marketCapacity = range.low.capacity.mulDiv(config
.cushionFactor, FACTOR_SCALE);\r'
policies/Operator.sol, 446, b' IBondAuctioneer.MarketParams memory params = IBondAuctioneer.MarketParams({\r'
policies/Operator.sol, 454, b' debtBuffer: config_.cushionDebtBuffer,\r'
policies/Operator.sol, 455, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 455, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 455, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 456, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 456, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 456, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 457, b' depositInterval: config_.cushionDepositInterval,\r'
policies/Operator.sol, 458, b' scaleAdjustment: scaleAdjustment\r'
policies/Operator.sol, 371, b' int8 priceDecimals = getPriceDecimals(range.cushion.high.price);\r'
policies/Operator.sol, 371, b' int8 priceDecimals = getPriceDecimals(range.cushion.high.price);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 372, b' int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10
uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10
uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10
uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10
uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 375, b' uint256 oracleScale = 10**uint8(int8(PRICE.decimals()) - priceDecimals);\r'
policies/Operator.sol, 376, b' uint256 bondScale = 10 **\r'
policies/Operator.sol, 379, b' uint8(\r\n 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r\n );\r'
policies/Operator.sol, 377, b' uint8(\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 378, b' 36 + scaleAdjustment + int8(reserveDecimals) - int8(ohmDecimals) - priceDecimals\r'
policies/Operator.sol, 389, b' config
.cushionFactor,\r'
policies/Operator.sol, 390, b' FACTOR_SCALE\r'
policies/Operator.sol, 394, b' IBondAuctioneer.MarketParams memory params = IBondAuctioneer.MarketParams({\r'
policies/Operator.sol, 402, b' debtBuffer: config
.cushionDebtBuffer,\r'
policies/Operator.sol, 403, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 403, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 403, b' vesting: uint48(0), // Instant swaps\r'
policies/Operator.sol, 404, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 404, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 404, b' conclusion: uint48(block.timestamp + config_.cushionDuration),\r'
policies/Operator.sol, 405, b' depositInterval: config_.cushionDepositInterval,\r'
policies/Operator.sol, 406, b' scaleAdjustment: scaleAdjustment\r'
policies/Operator.sol, 477, b' RANGE.updateMarket(high_, type(uint256).max, 0);\r'
policies/Operator.sol, 485, b' int8 decimals;\r'
policies/Operator.sol, 486, b' while (price_ >= 10) {\r'
policies/Operator.sol, 488, b' decimals++;\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 493, b' return decimals - int8(PRICE.decimals());\r'
policies/Operator.sol, 518, b' if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 518, b' if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 518, b' if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 518, b' if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 521, b' config.cushionFactor = cushionFactor;\r'
policies/Operator.sol, 533, b' if (duration_ > uint256(7 days) || duration_ < uint256(1 days))\r'
policies/Operator.sol, 533, b' if (duration_ > uint256(7 days) || duration_ < uint256(1 days))\r'
policies/Operator.sol, 533, b' if (duration_ > uint256(7 days) || duration_ < uint256(1 days))\r'
policies/Operator.sol, 533, b' if (duration_ > uint256(7 days) || duration_ < uint256(1 days))\r'
policies/Operator.sol, 535, b' if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();\r'
policies/Operator.sol, 535, b' if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();\r'
policies/Operator.sol, 535, b' if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();\r'
policies/Operator.sol, 535, b' if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 536, b' if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)\r'
policies/Operator.sol, 540, b' config.cushionDuration = duration;\r'
policies/Operator.sol, 541, b' config.cushionDebtBuffer = debtBuffer;\r'
policies/Operator.sol, 542, b' config.cushionDepositInterval = depositInterval;\r'
policies/Operator.sol, 550, b' if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 550, b' if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 550, b' if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 550, b' if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();\r'
policies/Operator.sol, 553, b' config.reserveFactor = reserveFactor;\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 565, b' if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)\r'
policies/Operator.sol, 569, b' config.regenWait = wait;\r'
policies/Operator.sol, 570, b' config.regenThreshold = threshold;\r'
policies/Operator.sol, 571, b' config.regenObserve = observe;\r'
policies/Operator.sol, 574, b' status.high.count = 0;\r'
policies/Operator.sol, 575, b' status.high.nextObservation = 0;\r'
policies/Operator.sol, 578, b' status.low.count = 0;\r'
policies/Operator.sol, 579, b' status.low.nextObservation = 0;\r'
policies/Operator.sol, 590, b' if (address(auctioneer
) == address(0) || address(callback
) == address(0))\r'
policies/Operator.sol, 590, b' if (address(auctioneer
) == address(0) || address(callback
) == address(0))\r'
policies/Operator.sol, 665, b' uint32 observe = config.regenObserve;\r'
policies/Operator.sol, 665, b' uint32 observe = config.regenObserve;\r'
policies/Operator.sol, 675, b' status.low.count--;\r'
policies/Operator.sol, 670, b' status.low.count++;\r'
policies/Operator.sol, 678, b' status.low.nextObservation = (regen.nextObservation + 1) % observe;\r'
policies/Operator.sol, 691, b' status.high.count--;\r'
policies/Operator.sol, 686, b' status.high.count++;\r'
policies/Operator.sol, 694, b' status.high.nextObservation = (regen.nextObservation + 1) % observe;\r'
policies/Operator.sol, 717, b' status.low.count = uint32(0);\r'
policies/Operator.sol, 719, b' status.low.nextObservation = uint32(0);\r'
policies/Operator.sol, 720, b' status.low.lastRegen = uint48(block.timestamp);\r'
policies/Operator.sol, 705, b' status.high.count = uint32(0);\r'
policies/Operator.sol, 707, b' status.high.nextObservation = uint32(0);\r'
policies/Operator.sol, 708, b' status.high.lastRegen = uint48(block.timestamp);\r'
policies/Operator.sol, 764, b' 10ohmDecimals * 10PRICE.decimals(),\r'
policies/Operator.sol, 764, b' 10ohmDecimals * 10PRICE.decimals(),\r'
policies/Operator.sol, 764, b' 10ohmDecimals * 10PRICE.decimals(),\r'
policies/Operator.sol, 764, b' 10ohmDecimals * 10PRICE.decimals(),\r'
policies/Operator.sol, 764, b' 10ohmDecimals * 10PRICE.decimals(),\r'
policies/Operator.sol, 765, b' 10reserveDecimals * RANGE.price(true, true)\r'
policies/Operator.sol, 765, b' 10
reserveDecimals * RANGE.price(true, true)\r'
policies/Operator.sol, 753, b' 10reserveDecimals * RANGE.price(true, false),\r'
policies/Operator.sol, 753, b' 10
reserveDecimals * RANGE.price(true, false),\r'
policies/Operator.sol, 754, b' 10ohmDecimals * 10PRICE.decimals()\r'
policies/Operator.sol, 754, b' 10ohmDecimals * 10PRICE.decimals()\r'
policies/Operator.sol, 754, b' 10ohmDecimals * 10PRICE.decimals()\r'
policies/Operator.sol, 754, b' 10ohmDecimals * 10PRICE.decimals()\r'
policies/Operator.sol, 754, b' 10ohmDecimals * 10PRICE.decimals()\r'
policies/Operator.sol, 780, b' uint256 capacity = (reservesInTreasury * config.reserveFactor) / FACTOR_SCALE;\r'
modules/PRICE.sol, 44, b' uint32 public nextObsIndex;'
modules/PRICE.sol, 47, b' uint32 public numObservations;'
modules/PRICE.sol, 50, b' uint48 public observationFrequency;'
modules/PRICE.sol, 53, b' uint48 public movingAverageDuration;'
modules/PRICE.sol, 56, b' uint48 public lastObservationTime;'
modules/PRICE.sol, 59, b' uint8 public constant decimals = 18;'
modules/PRICE.sol, 114, b' return (1, 0);'
modules/PRICE.sol, 127, b' uint32 numObs = numObservations;'
modules/PRICE.sol, 127, b' uint32 numObs = numObservations;'
modules/PRICE.sol, 143, b' lastObservationTime = uint48(block.timestamp);'
modules/PRICE.sol, 144, b' nextObsIndex = (nextObsIndex + 1) % numObs;'
modules/PRICE.sol, 165, b' if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))'
modules/PRICE.sol, 165, b' if (updatedAt < block.timestamp - 3 * uint256(observationFrequency))'
modules/PRICE.sol, 171, b' if (updatedAt < block.timestamp - uint256(observationFrequency))'
modules/PRICE.sol, 185, b' uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;'
modules/PRICE.sol, 185, b' uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;'
modules/PRICE.sol, 185, b' uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;'
modules/PRICE.sol, 185, b' uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;'
modules/PRICE.sol, 215, b' if (startObservations
.length != numObs || lastObservationTime
> uint48(block.timestamp))'
modules/PRICE.sol, 215, b' if (startObservations
.length != numObs || lastObservationTime
> uint48(block.timestamp))'
modules/PRICE.sol, 215, b' if (startObservations
.length != numObs || lastObservationTime
> uint48(block.timestamp))'
modules/PRICE.sol, 221, b' if (startObservations
[i] == 0) revert Price_InvalidParams();'
modules/PRICE.sol, 231, b' lastObservationTime = lastObservationTime
;'
modules/PRICE.sol, 242, b' if (movingAverageDuration
== 0 || movingAverageDuration
% observationFrequency != 0)'
modules/PRICE.sol, 242, b' if (movingAverageDuration
== 0 || movingAverageDuration
% observationFrequency != 0)'
modules/PRICE.sol, 242, b' if (movingAverageDuration
== 0 || movingAverageDuration
% observationFrequency != 0)'
modules/PRICE.sol, 242, b' if (movingAverageDuration
== 0 || movingAverageDuration_ % observationFrequency != 0)'
modules/PRICE.sol, 242, b' if (movingAverageDuration_ == 0 || movingAverageDuration_ % observationFrequency != 0)'
modules/PRICE.sol, 242, b' if (movingAverageDuration_ == 0 || movingAverageDuration_ % observationFrequency != 0)'
modules/PRICE.sol, 246, b' uint256 newObservations = uint256(movingAverageDuration_ / observationFrequency);'
modules/PRICE.sol, 246, b' uint256 newObservations = uint256(movingAverageDuration_ / observationFrequency);'
modules/PRICE.sol, 246, b' uint256 newObservations = uint256(movingAverageDuration_ / observationFrequency);'
modules/PRICE.sol, 253, b' lastObservationTime = 0;'
modules/PRICE.sol, 255, b' nextObsIndex = 0;'
modules/PRICE.sol, 256, b' movingAverageDuration = movingAverageDuration_;'
modules/PRICE.sol, 257, b' numObservations = uint32(newObservations);'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 268, b' if (observationFrequency_ == 0 || movingAverageDuration % observationFrequency_ != 0)'
modules/PRICE.sol, 272, b' uint256 newObservations = uint256(movingAverageDuration / observationFrequency_);'
modules/PRICE.sol, 272, b' uint256 newObservations = uint256(movingAverageDuration / observationFrequency_);'
modules/PRICE.sol, 272, b' uint256 newObservations = uint256(movingAverageDuration / observationFrequency_);'
modules/PRICE.sol, 285, b' lastObservationTime = 0;'
modules/PRICE.sol, 287, b' nextObsIndex = 0;'
modules/PRICE.sol, 288, b' observationFrequency = observationFrequency_;'
modules/PRICE.sol, 289, b' numObservations = uint32(newObservations);'
policies/PriceConfig.sol, 49, b' PRICE.initialize(startObservations_, lastObservationTime_);\r'
policies/PriceConfig.sol, 62, b' PRICE.changeMovingAverageDuration(movingAverageDuration_);\r'
policies/PriceConfig.sol, 62, b' PRICE.changeMovingAverageDuration(movingAverageDuration_);\r'
policies/PriceConfig.sol, 73, b' PRICE.changeObservationFrequency(observationFrequency_);\r'
policies/PriceConfig.sol, 73, b' PRICE.changeObservationFrequency(observationFrequency_);\r'
modules/RANGE.sol, 116, b' return (1, 0);\r'
modules/RANGE.sol, 148, b' range.low.lastActive = uint48(block.timestamp);\r'
modules/RANGE.sol, 136, b' range.high.lastActive = uint48(block.timestamp);\r'
modules/RANGE.sol, 221, b' if (market
== type(uint256).max && marketCapacity
!= 0) revert RANGE_InvalidParams();\r'
modules/RANGE.sol, 245, b' wallSpread_ > 10000 ||\r'
modules/RANGE.sol, 246, b' wallSpread_ < 100 ||\r'
modules/RANGE.sol, 247, b' cushionSpread_ > 10000 ||\r'
modules/RANGE.sol, 248, b' cushionSpread_ < 100 ||\r'
modules/RANGE.sol, 264, b' if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();\r'
modules/RANGE.sol, 264, b' if (thresholdFactor_ > 10000 || thresholdFactor_ < 100) revert RANGE_InvalidParams();\r'
modules/RANGE.sol, 344, b' return range.low.lastActive;\r'
modules/RANGE.sol, 342, b' return range.high.lastActive;\r'
policies/TreasuryCustodian.sol, 60, b' TRSRY.setApprovalFor(policy
, tokens
[j], 0);\r'
modules/TRSRY.sol, 52, b' return (1, 0);\r'
modules/TRSRY.sol, 106, b' if (reserveDebt[token_][msg.sender] == 0) revert TRSRY_NoDebtOutstanding();\r'
modules/VOTES.sol, 28, b' return (1, 0);\r'

G10:USE A MORE RECENT VERSION OF SOLIDITY

Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

Gas Optimizations

[G-01] tate variables only set in the constructor should be declared immutable (Avoids a Gsset (20000 gas)):

  1. File: 2022-08-olympus/src/Kernel.sol (line 155-158):

    ` address public executor;

    /// @notice Address that is responsible for assigning policy-defined roles to addresses.
    address public admin;`

  2. File: 2022-08-olympus/src/Kernel.sol (line 188):

    Policy[] public activePolicies;

  3. File: 2022-08-olympus/src/modules/PRICE.sol (line 41-56):

    ` uint256[] public observations;

    /// @notice Index of the next observation to make. The current value at this index is the oldest observation.
    uint32 public nextObsIndex;

    /// @notice Number of observations used in the moving average calculation. Computed from movingAverageDuration / observationFrequency.
    uint32 public numObservations;

    /// @notice Frequency (in seconds) that observations should be stored.
    uint48 public observationFrequency;

    /// @notice Duration (in seconds) over which the moving average is calculated.
    uint48 public movingAverageDuration;

    /// @notice Unix timestamp of last observation (in seconds).
    uint48 public lastObservationTime;`

  4. File: 2022-08-olympus/src/modules/INSTR.sol (line 13):

    uint256 public totalInstructions;

  5. File: 2022-08-olympus/src/policies/Operator.sol (line 76-78):

    IBondAuctioneer public auctioneer; /// @notice Callback contract used for cushion bond market payouts IBondCallback public callback;

  6. File: 2022-08-olympus/src/policies/BondCallback.sol (line 28-32):

    IBondAggregator public aggregator; OlympusTreasury public TRSRY; OlympusMinter public MINTR; Operator public operator; ERC20 public ohm;

  7. File: 2022-08-olympus/src/policies/Heart.sol (line 36-42):

    ` uint256 public lastBeat;

    /// @notice Reward for beating the Heart (in reward token decimals)
    uint256 public reward;

    /// @notice Reward token address that users are sent for beating the Heart
    ERC20 public rewardToken;`

  8. File: 2022-08-olympus/src/policies/VoterRegistration.sol (line 10):

    OlympusVotes public VOTES;

[G-02] x = x + y is cheaper than x += y:

  1. File: 2022-08-olympus/src/modules/TRSRY.sol (line 115-116):

    reserveDebt[token_][msg.sender] -= received; totalDebt[token_] -= received;

  2. File: 2022-08-olympus/src/modules/TRSRY.sol (line 132):

    else totalDebt[token_] -= oldDebt - amount_;

  3. File: 2022-08-olympus/src/modules/PRICE.sol (line 138):

    _movingAverage -= (earliestPrice - currentPrice) / numObs;

  4. File: 2022-08-olympus/src/modules/VOTES.sol (line 56):

    balanceOf[from_] -= amount_;

  5. File: 2022-08-olympus/src/policies/Governance.sol (line 194):

    totalEndorsementsForProposal[proposalId_] -= previousEndorsement;

  6. File: 2022-08-olympus/src/policies/BondCallback.sol (line 28-32):

    IBondAggregator public aggregator; OlympusTreasury public TRSRY; OlympusMinter public MINTR; Operator public operator; ERC20 public ohm;

  7. File: 2022-08-olympus/src/modules/TRSRY.sol (line 96-97):

    reserveDebt[token_][msg.sender] += amount_; totalDebt[token_] += amount_;

  8. File: 2022-08-olympus/src/modules/TRSRY.sol (line 131):

    if (oldDebt < amount_) totalDebt[token_] += amount_ - oldDebt;

  9. File: 2022-08-olympus/src/modules/PRICE.sol (line 136):

    _movingAverage += (currentPrice - earliestPrice) / numObs;

  10. File: 2022-08-olympus/src/modules/PRICE.sol (line 222):

total += startObservations_[i];

  1. File: 2022-08-olympus/src/modules/VOTES.sol (line 58):

balanceOf[to_] += amount_;

  1. File: 2022-08-olympus/src/policies/BondCallback.sol (line 143-144):

_amountsPerMarket[id_][0] += inputAmount_; _amountsPerMarket[id_][1] += outputAmount_;

  1. File: 2022-08-olympus/src/policies/Heart.sol (line 103):

lastBeat += frequency();

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 198):

totalEndorsementsForProposal[proposalId_] += userVotes;

  1. File: 2022-08-olympus/src/policies/Governance.sol 252-254):

yesVotesForProposal[activeProposal.proposalId] += userVotes; } else { noVotesForProposal[activeProposal.proposalId] += userVotes;

[G-03] <array>.length should not be looked up in every loop of a for loop:

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 278):

    for (uint256 step; step < instructions.length; ) {

[G-04] Not using the named return variables when a function returns, wastes deployment gas:

  1. File: 2022-08-olympus/src/modules/RANGE.sol (line 276):

    return _range;

  2. File: 2022-08-olympus/src/policies/Operator.sol (line 784):

    return _status;

  3. File: 2022-08-olympus/src/policies/Operator.sol (line 799):

    return _config;

[G-05] It costs more gas to initialize variables to zero than to let the default of zero be applied:

  1. File: 2022-08-olympus/src/Kernel.sol (line 276):

    for (uint256 i = 0; i < reqLength; ) {

  2. File: 2022-08-olympus/src/utils/KernelUtils.sol (line 58):

    for (uint256 i = 0; i < 32; ) {

  3. File: 2022-08-olympus/src/utils/KernelUtils.sol (line 43):

    for (uint256 i = 0; i < 5; ) {

  4. File: 2022-08-olympus/src/modules/PRICE.sol (line 253-255):

    lastObservationTime = 0; _movingAverage = 0; nextObsIndex = 0;

  5. File: 2022-08-olympus/src/modules/PRICE.sol (line 285-287):

    lastObservationTime = 0; _movingAverage = 0; nextObsIndex = 0;

  6. File: 2022-08-olympus/src/policies/Operator.sol(line 574-575):

    _status.high.count = 0; _status.high.nextObservation = 0;

  7. File: 2022-08-olympus/src/policies/Operator.sol (line 578-579):

    _status.low.count = 0; _status.low.nextObservation = 0;

[G-06] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead (> When using elements that are smaller than 32 bytes, your

contract’s gas usage may be higher. This is because the EVM operates on
32 bytes at a time. Therefore, if the element is smaller than that, the
EVM must use more operations in order to reduce the size of the element
from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Use a larger size then downcast where needed):

  1. File: 2022-08-olympus/src/Kernel.sol (line 100):

    function VERSION() external pure virtual returns (uint8 major, uint8 minor) {}

  2. File: 2022-08-olympus/src/modules/TRSRY.sol (line 51):

    function VERSION() external pure override returns (uint8 major, uint8 minor) {

  3. File: 2022-08-olympus/src/modules/MINTR.sol (line 25):

    function VERSION() external pure override returns (uint8 major, uint8 minor) {

  4. File: 2022-08-olympus/src/modules/RANGE.sol (line 45):

    uint48 lastActive;

  5. File: 2022-08-olympus/src/modules/RANGE.sol (line 115):

    function VERSION() external pure override returns (uint8 major, uint8 minor) {

  6. File: 2022-08-olympus/src/modules/RANGE.sol(line 136):

    _range.high.lastActive = uint48(block.timestamp);

  7. File: 2022-08-olympus/src/modules/RANGE.sol (line 148):

    _range.low.lastActive = uint48(block.timestamp);

  8. File: 2022-08-olympus/src/modules/PRICE.sol (line 27-28):

    event MovingAverageDurationChanged(uint48 movingAverageDuration_); event ObservationFrequencyChanged(uint48 observationFrequency_);

  9. File: 2022-08-olympus/src/modules/PRICE.sol (line 44-59):

    ` uint32 public nextObsIndex;

    /// @notice Number of observations used in the moving average calculation. Computed from movingAverageDuration / observationFrequency.
    uint32 public numObservations;

    /// @notice Frequency (in seconds) that observations should be stored.
    uint48 public observationFrequency;

    /// @notice Duration (in seconds) over which the moving average is calculated.
    uint48 public movingAverageDuration;

    /// @notice Unix timestamp of last observation (in seconds).
    uint48 public lastObservationTime;

    /// @notice Number of decimals in the price values provided by the contract.
    uint8 public constant decimals = 18;`

  10. File: 2022-08-olympus/src/policies/Operator.sol(line 75-76):

uint48 observationFrequency_, uint48 movingAverageDuration_

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 84):

uint8 ohmEthDecimals = _ohmEthPriceFeed.decimals();

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 87):

uint8 reserveEthDecimals = _reserveEthPriceFeed.decimals();

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 97):

numObservations = uint32(movingAverageDuration_ / observationFrequency_);

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 113):

function VERSION() external pure override returns (uint8 major, uint8 minor) {

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 127):

uint32 numObs = numObservations;

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 143):

lastObservationTime = uint48(block.timestamp);

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 185):

uint32 lastIndex = nextObsIndex == 0 ? numObservations - 1 : nextObsIndex - 1;

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 215):

if (startObservations_.length != numObs || lastObservationTime_ > uint48(block.timestamp))

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 240):

function changeMovingAverageDuration(uint48 movingAverageDuration_) external permissioned {

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 257):

numObservations = uint32(newObservations);

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 266):

function changeObservationFrequency(uint48 observationFrequency_) external permissioned {

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 289):

numObservations = uint32(newObservations);

  1. 2022-08-olympus/src/modules/VOTES.sol (line 27):

function VERSION() external pure override returns (uint8 major, uint8 minor) {

  1. 2022-08-olympus/src/modules/INSTR.sol (line 28):

function VERSION() public pure override returns (uint8 major, uint8 minor) {

  1. 2022-08-olympus/src/policies/Operator.sol (line 51-54):

event CushionFactorChanged(uint32 cushionFactor_); event CushionParamsChanged(uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_); event ReserveFactorChanged(uint32 reserveFactor_); event RegenParamsChanged(uint32 wait_, uint32 threshold_, uint32 observe_);

  1. 2022-08-olympus/src/policies/Operator.sol (line 83):

uint8 public immutable ohmDecimals;

  1. 2022-08-olympus/src/policies/Operator.sol (line 86-89):

` uint8 public immutable reserveDecimals;

/// Constants
uint32 public constant FACTOR_SCALE = 1e4;`
  1. 2022-08-olympus/src/policies/Operator.sol (line 51-54):

uint32[8] memory configParams

  1. 2022-08-olympus/src/policies/Operator.sol (line 106-108):

` if (configParams[2] < uint32(10_000)) revert Operator_InvalidParams();

    if (configParams[3] < uint32(1 hours) || configParams[3] > configParams[1])`
  1. 2022-08-olympus/src/policies/Operator.sol (line 116):

configParams[7] == uint32(0)

  1. 2022-08-olympus/src/policies/Operator.sol (line 127-129):

count: uint32(0), lastRegen: uint48(block.timestamp), nextObservation: uint32(0),

  1. 2022-08-olympus/src/policies/Operator.sol (line 210):

uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) &&

  1. 2022-08-olympus/src/policies/Operator.sol (line 216):

uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) &&

  1. 2022-08-olympus/src/policies/Operator.sol (line 403-404):

vesting: uint48(0), // Instant swaps conclusion: uint48(block.timestamp + config_.cushionDuration),

  1. 2022-08-olympus/src/policies/Operator.sol (line 418):

uint8 oracleDecimals = PRICE.decimals();

  1. 2022-08-olympus/src/policies/Operator.sol (line 455-456):

vesting: uint48(0), // Instant swaps conclusion: uint48(block.timestamp + config_.cushionDuration),

  1. 2022-08-olympus/src/policies/Operator.sol (line 516):

function setCushionFactor(uint32 cushionFactor_) external onlyRole("operator_policy") {

  1. 2022-08-olympus/src/policies/Operator.sol (line 528-530):

uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_

  1. 2022-08-olympus/src/policies/Operator.sol (line 535-536):

if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams(); if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)

  1. 2022-08-olympus/src/policies/Operator.sol (line 548):

function setReserveFactor(uint32 reserveFactor_) external onlyRole("operator_policy") {

  1. 2022-08-olympus/src/policies/Operator.sol (line 560-562):

uint32 wait_, uint32 threshold_, uint32 observe_

  1. 2022-08-olympus/src/policies/Operator.sol (line 665):

uint32 observe = _config.regenObserve;

  1. 2022-08-olympus/src/policies/Operator.sol (line 560-562):

uint32 wait_, uint32 threshold_, uint32 observe_

  1. 2022-08-olympus/src/policies/Operator.sol (line 705):

_status.high.count = uint32(0);

  1. 2022-08-olympus/src/policies/Operator.sol (line 707-708):

_status.high.nextObservation = uint32(0); _status.high.lastRegen = uint48(block.timestamp);

  1. 2022-08-olympus/src/policies/Operator.sol (line 717):

_status.low.count = uint32(0);

  1. 2022-08-olympus/src/policies/Operator.sol (line 719-720):

_status.low.nextObservation = uint32(0); _status.low.lastRegen = uint48(block.timestamp);

  1. 2022-08-olympus/src/policies/PriceConfig.sol (line 58):

function changeMovingAverageDuration(uint48 movingAverageDuration_)

  1. 2022-08-olympus/src/policies/PriceConfig.sol (line 69):

function changeObservationFrequency(uint48 observationFrequency_)

  1. 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 13-20):

uint32 cushionFactor; // percent of capacity to be used for a single cushion deployment, assumes 2 decimals (i.e. 1000 = 10%) uint32 cushionDuration; // duration of a single cushion deployment in seconds uint32 cushionDebtBuffer; // Percentage over the initial debt to allow the market to accumulate at any one time. Percent with 3 decimals, e.g. 1_000 = 1 %. See IBondAuctioneer for more info. uint32 cushionDepositInterval; // Target frequency of deposits. Determines max payout of the bond market. See IBondAuctioneer for more info. uint32 reserveFactor; // percent of reserves in treasury to be used for a single wall, assumes 2 decimals (i.e. 1000 = 10%) uint32 regenWait; // minimum duration to wait to reinstate a wall in seconds uint32 regenThreshold; // number of price points on other side of moving average to reinstate a wall uint32 regenObserve; // number of price points to observe to determine regeneration

  1. 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 31-33):

uint32 count; // current number of price points that count towards regeneration uint48 lastRegen; // timestamp of the last regeneration uint32 nextObservation; // index of the next observation in the observations array

  1. 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 93-95):

uint32 duration_, uint32 debtBuffer_, uint32 depositInterval_

  1. 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 101):

function setReserveFactor(uint32 reserveFactor_) external;

  1. 2022-08-olympus/src/policies/interfaces/IOperator.sol (line 110-112):

uint32 wait_, uint32 threshold_, uint32 observe_

[G-06] Functions guaranteed to revert when called by normal users can be marked payable (If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.):

  1. File: 2022-08-olympus/src/policies/TreasuryCustodian.sol (line 42-46):

    function grantApproval( address for_, ERC20 token_, uint256 amount_ ) external onlyRole("custodian") {

  2. File: 2022-08-olympus/src/policies/TreasuryCustodian.sol (line 71-75):

    function increaseDebt( ERC20 token_, address debtor_, uint256 amount_ ) external onlyRole("custodian") {

  3. File: 2022-08-olympus/src/policies/TreasuryCustodian.sol (line 80-85):

    function decreaseDebt( ERC20 token_, address debtor_, uint256 amount_ ) external onlyRole("custodian") {

  4. File: 2022-08-olympus/src/policies/Operator.sol (line 195):

    function operate() external override onlyWhileActive onlyRole("operator_operate") {

  5. File: 2022-08-olympus/src/policies/Operator.sol (line 346-350):

    function bondPurchase(uint256 id_, uint256 amountOut_) external onlyWhileActive onlyRole("operator_reporter") {

  6. File: 2022-08-olympus/src/policies/Operator.sol(line 498-624):

    ` function setSpreads(uint256 cushionSpread_, uint256 wallSpread_)
    external
    onlyRole("operator_policy")
    {
    /// Set spreads on the range module
    RANGE.setSpreads(cushionSpread_, wallSpread_);

     /// Update range prices (wall and cushion)
     _updateRangePrices();
    

    }

    /// @inheritdoc IOperator
    function setThresholdFactor(uint256 thresholdFactor_) external onlyRole("operator_policy") {
    /// Set threshold factor on the range module
    RANGE.setThresholdFactor(thresholdFactor_);
    }

    /// @inheritdoc IOperator
    function setCushionFactor(uint32 cushionFactor_) external onlyRole("operator_policy") {
    /// Confirm factor is within allowed values
    if (cushionFactor_ > 10000 || cushionFactor_ < 100) revert Operator_InvalidParams();

     /// Set factor
     _config.cushionFactor = cushionFactor_;
    
     emit CushionFactorChanged(cushionFactor_);
    

    }

    /// @inheritdoc IOperator
    function setCushionParams(
    uint32 duration_,
    uint32 debtBuffer_,
    uint32 depositInterval_
    ) external onlyRole("operator_policy") {
    /// Confirm values are valid
    if (duration_ > uint256(7 days) || duration_ < uint256(1 days))
    revert Operator_InvalidParams();
    if (debtBuffer_ < uint32(10_000)) revert Operator_InvalidParams();
    if (depositInterval_ < uint32(1 hours) || depositInterval_ > duration_)
    revert Operator_InvalidParams();

     /// Update values
     _config.cushionDuration = duration_;
     _config.cushionDebtBuffer = debtBuffer_;
     _config.cushionDepositInterval = depositInterval_;
    
     emit CushionParamsChanged(duration_, debtBuffer_, depositInterval_);
    

    }

    /// @inheritdoc IOperator
    function setReserveFactor(uint32 reserveFactor_) external onlyRole("operator_policy") {
    /// Confirm factor is within allowed values
    if (reserveFactor_ > 10000 || reserveFactor_ < 100) revert Operator_InvalidParams();

     /// Set factor
     _config.reserveFactor = reserveFactor_;
    
     emit ReserveFactorChanged(reserveFactor_);
    

    }

    /// @inheritdoc IOperator
    function setRegenParams(
    uint32 wait_,
    uint32 threshold_,
    uint32 observe_
    ) external onlyRole("operator_policy") {
    /// Confirm regen parameters are within allowed values
    if (wait_ < 1 hours || threshold_ > observe_ || observe_ == 0)
    revert Operator_InvalidParams();

     /// Set regen params
     _config.regenWait = wait_;
     _config.regenThreshold = threshold_;
     _config.regenObserve = observe_;
    
     /// Re-initialize regen structs with new values (except for last regen)
     _status.high.count = 0;
     _status.high.nextObservation = 0;
     _status.high.observations = new bool[](observe_);
    
     _status.low.count = 0;
     _status.low.nextObservation = 0;
     _status.low.observations = new bool[](observe_);
    
     emit RegenParamsChanged(wait_, threshold_, observe_);
    

    }

    /// @inheritdoc IOperator
    function setBondContracts(IBondAuctioneer auctioneer_, IBondCallback callback_)
    external
    onlyRole("operator_admin")
    {
    if (address(auctioneer_) == address(0) || address(callback_) == address(0))
    revert Operator_InvalidParams();
    /// Set contracts
    auctioneer = auctioneer_;
    callback = callback_;
    }

    /// @inheritdoc IOperator
    function initialize() external onlyRole("operator_admin") {
    /// Can only call once
    if (initialized) revert Operator_AlreadyInitialized();

     /// Request approval for reserves from TRSRY
     TRSRY.setApprovalFor(address(this), reserve, type(uint256).max);
    
     /// Update range prices (wall and cushion)
     _updateRangePrices();
    
     /// Regenerate sides
     _regenerate(true);
     _regenerate(false);
    
     /// Set initialized and active flags
     initialized = true;
     active = true;
    

    }

    /// @inheritdoc IOperator
    function regenerate(bool high_) external onlyRole("operator_admin") {
    /// Regenerate side
    regenerate(high);
    }

    /// @inheritdoc IOperator
    function toggleActive() external onlyRole("operator_admin") {`

  7. File: 2022-08-olympus/src/policies/BondCallback.sol (line 152):

    function batchToTreasury(ERC20[] memory tokens_) external onlyRole("callback_admin") {

  8. File: 2022-08-olympus/src/policies/BondCallback.sol (line 190):

    function setOperator(Operator operator_) external onlyRole("callback_admin") {

  9. File: 2022-08-olympus/src/policies/Heart.sol (line 130-152):

    ` function resetBeat() external onlyRole("heart_admin") {
    lastBeat = block.timestamp - frequency();
    }

    /// @inheritdoc IHeart
    function toggleBeat() external onlyRole("heart_admin") {
    active = !active;
    }

    /// @inheritdoc IHeart
    function setRewardTokenAndAmount(ERC20 token_, uint256 reward_)
    external
    onlyRole("heart_admin")
    {
    rewardToken = token_;
    reward = reward_;
    emit RewardUpdated(token_, reward_);
    }

    /// @inheritdoc IHeart
    function withdrawUnspentRewards(ERC20 token_) external onlyRole("heart_admin") {
    token_.safeTransfer(msg.sender, token_.balanceOf(address(this)));
    }`

  10. File: 2022-08-olympus/src/policies/PriceConfig.sol (line 45-74):

` function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_)
external
onlyRole("price_admin")
{
PRICE.initialize(startObservations_, lastObservationTime_);
}

/// @notice                         Change the moving average window (duration)
/// @param movingAverageDuration_   Moving average duration in seconds, must be a multiple of observation frequency
/// @dev Setting the window to a larger number of observations than the current window will clear
///      the data in the current window and require the initialize function to be called again.
///      Ensure that you have saved the existing data and can re-populate before calling this
///      function with a number of observations larger than have been recorded.
function changeMovingAverageDuration(uint48 movingAverageDuration_)
    external
    onlyRole("price_admin")
{
    PRICE.changeMovingAverageDuration(movingAverageDuration_);
}

/// @notice   Change the observation frequency of the moving average (i.e. how often a new observation is taken)
/// @param    observationFrequency_   Observation frequency in seconds, must be a divisor of the moving average duration
/// @dev      Changing the observation frequency clears existing observation data since it will not be taken at the right time intervals.
///           Ensure that you have saved the existing data and/or can re-populate before calling this function.
function changeObservationFrequency(uint48 observationFrequency_)
    external
    onlyRole("price_admin")
{
    PRICE.changeObservationFrequency(observationFrequency_);
}` 
  1. File: 2022-08-olympus/src/policies/VoterRegistration.sol (line 45-56):

` function issueVotesTo(address wallet_, uint256 amount_) external onlyRole("voter_admin") {
// Issue the votes in the VOTES module
VOTES.mintTo(wallet_, amount_);
}

/// @notice Burn votes from a wallet
/// @param  wallet_ - The address losing the votes.
/// @param  amount_ - The amount of votes to burn from the wallet.
function revokeVotesFrom(address wallet_, uint256 amount_) external onlyRole("voter_admin") {
    // Revoke the votes in the VOTES module
    VOTES.burnFrom(wallet_, amount_);
}`

[G-07] Bitshift for divide by 2 (When multiply or dividing by a power of two, it is cheaper to bitshift than to use standard math operations.):

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 372):

    int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);

  2. File: 2022-08-olympus/src/policies/Operator.sol (line 427):

    int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);

[G-08] Use simple comparison in trinary logic (The comparison operators >= and <= use more gas than >,

<, or ==. Replacing the >= and ≤ operators with a comparison
operator that has an opcode in the EVM saves gas):

  1. File: 2022-08-olympus/src/policies/Operator.sol (line 210-218):

    uint48(block.timestamp) >= RANGE.lastActive(true) + uint48(config_.regenWait) && _status.high.count >= config_.regenThreshold ) { _regenerate(true); } if ( uint48(block.timestamp) >= RANGE.lastActive(false) + uint48(config_.regenWait) && _status.low.count >= config_.regenThreshold ) {

[G-09] Use calldata instead of memory for function parameters (Use calldata instead of memory for function parameters. Having function arguments use calldata instead of memory can save gas.):

  1. File: 2022-08-olympus/src/modules/PRICE.sol (line 205-208):

    function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_) external permissioned {

  2. File: 2022-08-olympus/src/policies/BondCallback.sol (line 152):

    function batchToTreasury(ERC20[] memory tokens_) external onlyRole("callback_admin") {

  3. File: 2022-08-olympus/src/policies/PriceConfig.sol (line 45):

    function initialize(uint256[] memory startObservations_, uint48 lastObservationTime_)

  4. File: 2022-08-olympus/src/policies/Operator.sol (line 195):

    function operate() external override onlyWhileActive onlyRole("operator_operate") {

[G-10] Amounts should be checked for 0 before calling a transfer (Checking non-zero transfer values can avoid an expensive external call and save gas.

While this is done at some places, it’s not consistently done in the solution.):

  1. File: 2022-08-olympus/src/modules/TRSRY.sol (line 82):

    token_.safeTransfer(to_, amount_);

  2. File: 2022-08-olympus/src/modules/TRSRY.sol (line 99):

    token_.safeTransfer(msg.sender, amount_);

  3. File: 2022-08-olympus/src/modules/TRSRY.sol (line 110):

    token_.safeTransferFrom(msg.sender, address(this), amount_);

  4. File: 2022-08-olympus/src/policies/Operator.sol (line 299):

    ohm.safeTransferFrom(msg.sender, address(this), amountIn_);

  5. File: 2022-08-olympus/src/policies/Operator.sol (line 330):

    reserve.safeTransferFrom(msg.sender, address(TRSRY), amountIn_);

  6. File: 2022-08-olympus/src/policies/BondCallback.sol (line 124):

    payoutToken.safeTransfer(msg.sender, outputAmount_);

  7. File: 2022-08-olympus/src/policies/BondCallback.sol (line 159):

    token.safeTransfer(address(TRSRY), balance);

  8. File: 2022-08-olympus/src/policies/Heart.sol (line 112):

    rewardToken.safeTransfer(to_, reward);

  9. File: 2022-08-olympus/src/policies/Heart.sol (line 151):

    token_.safeTransfer(msg.sender, token_.balanceOf(address(this)));

  10. File: 2022-08-olympus/src/policies/Governance.sol (line 259):

VOTES.transferFrom(msg.sender, address(this), userVotes);

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 312):

VOTES.transferFrom(address(this), msg.sender, userVotes);

[G-11] Using bools for storage incurs overhead.

While this is done at some places, it’s not consistently done in the solution.):

  1. File: 2022-08-olympus/src/Kernel.sol (line 113):

    bool public isActive;

  2. File: 2022-08-olympus/src/Kernel.sol (line 197):

    mapping(Role => bool) public isRole;

  3. File: 2022-08-olympus/src/modules/PRICE.sol (line 62):

    bool public initialized;

  4. File: 2022-08-olympus/src/policies/Operator.sol (line 63):

    bool public initialized;

  5. File: 2022-08-olympus/src/policies/Operator.sol (line 66):

    bool public active;

  6. File: 2022-08-olympus/src/policies/BondCallback.sol (line 24):

    mapping(address => mapping(uint256 => bool)) public approvedMarkets;

  7. File: 2022-08-olympus/src/policies/Heart.sol (line 33):

    bool public active;

  8. File: 2022-08-olympus/src/policies/Governance.sol (line 105):

    mapping(uint256 => bool) public proposalHasBeenActivated;

  9. File: 2022-08-olympus/src/policies/Governance.sol (line 117):

    mapping(uint256 => mapping(address => bool)) public tokenClaimsForProposal;

[G-12] Using private rather than public for constants, saves gas (If needed, the value can be read from the verified contract source

code. Savings are due to the compiler not having to create non-payable
getter functions for deployment calldata, and not adding another entry
to the method ID table):

  1. File: 2022-08-olympus/src/modules/RANGE.sol (line 65):

    uint256 public constant FACTOR_SCALE = 1e4;

  2. File: 2022-08-olympus/src/modules/PRICE.sol (line 59):

    uint8 public constant decimals = 18;

  3. File: 2022-08-olympus/src/policies/Operator.sol (line 89):

    uint32 public constant FACTOR_SCALE = 1e4;

  4. File: 2022-08-olympus/src/policies/Governance.sol (line 121-137):

    ` uint256 public constant SUBMISSION_REQUIREMENT = 100;

    /// @notice Amount of time a submitted proposal has to activate before it expires.
    uint256 public constant ACTIVATION_DEADLINE = 2 weeks;

    /// @notice Amount of time an activated proposal must stay up before it can be replaced by a new activated proposal.
    uint256 public constant GRACE_PERIOD = 1 weeks;

    /// @notice Endorsements required to activate a proposal as percentage of total supply.
    uint256 public constant ENDORSEMENT_THRESHOLD = 20;

    /// @notice Net votes required to execute a proposal on chain as a percentage of total supply.
    uint256 public constant EXECUTION_THRESHOLD = 33;

    /// @notice Required time for a proposal to be active before it can be executed.
    /// @dev This amount should be greater than 0 to prevent flash loan attacks.
    uint256 public constant EXECUTION_TIMELOCK = 3 days;`

[G-13] Empty blocks should be removed or emit something [The code should be refactored such that they no longer exist, or the

block should do something useful, such as emitting an event or
reverting. If the contract is meant to be extended, the contract should
be abstract and the function signatures be added without
any default implementation. If the block is an empty if-statement block
to avoid doing subsequent checks in the else-if/else conditions, the
else-if/else conditions should be nested under the negation of the
if-statement, because they involve different classes of checks, which
may lead to the introduction of errors when the code is later modified (if(x){}else if(y){...}else{...} => if(!x){if(y){...}else{...}})]:

  1. File: 2022-08-olympus/src/Kernel.sol (line 85):

    constructor(Kernel kernel_) KernelAdapter(kernel_) {}

  2. File: 2022-08-olympus/src/Kernel.sol (line 95):

    function KEYCODE() public pure virtual returns (Keycode) {}

  3. File: 2022-08-olympus/src/Kernel.sol (line 100):

    function VERSION() external pure virtual returns (uint8 major, uint8 minor) {};

  4. File: 2022-08-olympus/src/Kernel.sol (line 105):

    function INIT() external virtual onlyKernel {}

  5. File: 2022-08-olympus/src/Kernel.sol (line 115):

    constructor(Kernel kernel_) KernelAdapter(kernel_) {}

  6. File: 2022-08-olympus/src/Kernel.sol (line 139-143):

    ` function configureDependencies() external virtual returns (Keycode[] memory dependencies) {}

    /// @notice Function called by kernel to set module function permissions.
    /// @return requests - Array of keycodes and function selectors for requested permissions.
    function requestPermissions() external view virtual returns (Permissions[] memory requests) {}`

  7. File: 2022-08-olympus/src/modules/TRSRY.sol (line 45):

    constructor(Kernel kernel_) Module(kernel_) {}

  8. File: 2022-08-olympus/src/modules/VOTES.sol (line 19):

    {}

  9. File: 2022-08-olympus/src/modules/INSTR.sol (line 20):

    constructor(Kernel kernel_) Module(kernel_) {}

  10. File: 2022-08-olympus/src/policies/TreasuryCustodian.sol (line 24):

constructor(Kernel kernel_) Policy(kernel_) {}

  1. File: 2022-08-olympus/src/policies/PriceConfig.sol (line 15):

constructor(Kernel kernel_) Policy(kernel_) {}

  1. File: 2022-08-olympus/src/policies/Governance.sol (line 59):

constructor(Kernel kernel_) Policy(kernel_) {}

  1. File: 2022-08-olympus/src/policies/VoterRegistration.sol (line 16):

constructor(Kernel kernel_) Policy(kernel_) {}

QA Report

Unsafe ERC20 Operation(s)

Impact

Issue Information: L001

Findings:

src/policies/Governance.sol::259 => VOTES.transferFrom(msg.sender, address(this), userVotes);
src/policies/Governance.sol::312 => VOTES.transferFrom(address(this), msg.sender, userVotes);
src/test/lib/bonds/BondFixedTermTeller.sol::115 => underlying_.transferFrom(msg.sender, address(this), amount_);
src/test/modules/MINTR.t.sol::87 => ohm.approve(address(MINTR), amount_);
src/test/modules/MINTR.t.sol::107 => ohm.approve(users[0], amount_);
src/test/modules/TRSRY.t.sol::156 => ngmi.approve(address(TRSRY), amount_);
src/test/modules/VOTES.t.sol::50 => VOTES.transfer(address(0), 10);
src/test/modules/VOTES.t.sol::74 => VOTES.transferFrom(address(1), address(2), 3);
src/test/policies/BondCallback.t.sol::207 => ohm.approve(address(operator), testOhm * 20);
src/test/policies/BondCallback.t.sol::209 => reserve.approve(address(operator), testReserve * 20);
src/test/policies/BondCallback.t.sol::212 => ohm.approve(address(teller), testOhm * 20);
src/test/policies/BondCallback.t.sol::214 => reserve.approve(address(teller), testReserve * 20);
src/test/policies/Governance.t.sol::96 => VOTES.approve(address(governance), type(uint256).max);
src/test/policies/Governance.t.sol::98 => VOTES.approve(address(governance), type(uint256).max);
src/test/policies/Governance.t.sol::100 => VOTES.approve(address(governance), type(uint256).max);
src/test/policies/Governance.t.sol::102 => VOTES.approve(address(governance), type(uint256).max);
src/test/policies/Governance.t.sol::104 => VOTES.approve(address(governance), type(uint256).max);
src/test/policies/Operator.t.sol::192 => ohm.approve(address(operator), testOhm * 20);
src/test/policies/Operator.t.sol::194 => reserve.approve(address(operator), testReserve * 20);
src/test/policies/Operator.t.sol::197 => ohm.approve(address(teller), testOhm * 20);
src/test/policies/Operator.t.sol::199 => reserve.approve(address(teller), testReserve * 20);

Tools used

c4udit

Unspecific Compiler Version Pragma

Impact

Issue Information: L003

Findings:

src/external/OlympusERC20.sol::2 => pragma solidity >=0.7.5;
src/interfaces/AggregatorV2V3Interface.sol::2 => pragma solidity ^0.8.0;
src/interfaces/IBondAggregator.sol::2 => pragma solidity >=0.8.0;
src/interfaces/IBondAuctioneer.sol::2 => pragma solidity >=0.8.0;
src/interfaces/IBondCallback.sol::2 => pragma solidity >=0.8.0;
src/interfaces/IBondTeller.sol::2 => pragma solidity >=0.8.0;
src/interfaces/IWETH9.sol::2 => pragma solidity >=0.8.0;
src/libraries/FullMath.sol::2 => pragma solidity >=0.8.0;
src/libraries/TransferHelper.sol::2 => pragma solidity >=0.8.0;
src/policies/interfaces/IHeart.sol::2 => pragma solidity >=0.8.0;
src/policies/interfaces/IOperator.sol::2 => pragma solidity >=0.8.0;
src/test/lib/ModuleTestFixtureGenerator.sol::2 => pragma solidity >=0.8.0;
src/test/lib/UserFactory.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondAggregator.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondAuctioneer.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondCDA.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondCallback.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondFixedTermTeller.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/interfaces/IBondTeller.sol::2 => pragma solidity >=0.8.0;
src/test/lib/bonds/lib/ERC1155.sol::2 => pragma solidity >=0.8.0;
src/test/lib/larping.sol::1 => pragma solidity >=0.8.0;
src/test/mocks/MockPriceFeed.sol::2 => pragma solidity ^0.8.0;
src/test/modules/INSTR.t.sol::2 => pragma solidity >=0.8.0;
src/test/modules/PRICE.t.sol::2 => pragma solidity >=0.8.0;
src/test/modules/RANGE.t.sol::2 => pragma solidity >=0.8.0;
src/test/modules/VOTES.t.sol::3 => pragma solidity >=0.8.0;
src/test/policies/BondCallback.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/Governance.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/Heart.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/Operator.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/PriceConfig.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/TreasuryCustodian.t.sol::2 => pragma solidity >=0.8.0;
src/test/policies/VoterRegistration.t.sol::2 => pragma solidity >=0.8.0;

Tools used

c4udit

Do not use Deprecated Library Functions

Impact

Issue Information: L005

Findings:

src/libraries/TransferHelper.sol::35 => function safeApprove(
src/policies/BondCallback.sol::57 => ohm.safeApprove(address(MINTR), type(uint256).max);
src/policies/Operator.sol::167 => ohm.safeApprove(address(MINTR), type(uint256).max);

Tools used

c4udit

Gas Optimizations

c4udit Report

Files analyzed

  • src/Kernel.sol
  • src/external/OlympusERC20.sol
  • src/interfaces/AggregatorV2V3Interface.sol
  • src/interfaces/IBondAggregator.sol
  • src/interfaces/IBondAuctioneer.sol
  • src/interfaces/IBondCallback.sol
  • src/interfaces/IBondTeller.sol
  • src/interfaces/IWETH9.sol
  • src/libraries/FullMath.sol
  • src/libraries/TransferHelper.sol
  • src/modules/INSTR.sol
  • src/modules/MINTR.sol
  • src/modules/PRICE.sol
  • src/modules/RANGE.sol
  • src/modules/TRSRY.sol
  • src/modules/VOTES.sol
  • src/policies/BondCallback.sol
  • src/policies/Governance.sol
  • src/policies/Heart.sol
  • src/policies/Operator.sol
  • src/policies/PriceConfig.sol
  • src/policies/TreasuryCustodian.sol
  • src/policies/VoterRegistration.sol
  • src/policies/interfaces/IHeart.sol
  • src/policies/interfaces/IOperator.sol
  • src/scripts/Deploy.sol
  • src/test/Kernel.t.sol
  • src/test/lib/ModuleTestFixtureGenerator.sol
  • src/test/lib/UserFactory.sol
  • src/test/lib/bonds/BondAggregator.sol
  • src/test/lib/bonds/BondFixedTermCDA.sol
  • src/test/lib/bonds/BondFixedTermTeller.sol
  • src/test/lib/bonds/bases/BondBaseCDA.sol
  • src/test/lib/bonds/bases/BondBaseTeller.sol
  • src/test/lib/bonds/interfaces/IBondAggregator.sol
  • src/test/lib/bonds/interfaces/IBondAuctioneer.sol
  • src/test/lib/bonds/interfaces/IBondCDA.sol
  • src/test/lib/bonds/interfaces/IBondCallback.sol
  • src/test/lib/bonds/interfaces/IBondFixedTermTeller.sol
  • src/test/lib/bonds/interfaces/IBondTeller.sol
  • src/test/lib/bonds/lib/ERC1155.sol
  • src/test/lib/larping.sol
  • src/test/lib/quabi/Quabi.sol
  • src/test/mocks/Faucet.sol
  • src/test/mocks/KernelTestMocks.sol
  • src/test/mocks/MockInvalidModule.sol
  • src/test/mocks/MockModuleWriter.sol
  • src/test/mocks/MockPrice.sol
  • src/test/mocks/MockPriceFeed.sol
  • src/test/mocks/MockValidModule.sol
  • src/test/mocks/MockValidUpgradedModule.sol
  • src/test/modules/INSTR.t.sol
  • src/test/modules/MINTR.t.sol
  • src/test/modules/PRICE.t.sol
  • src/test/modules/RANGE.t.sol
  • src/test/modules/TRSRY.t.sol
  • src/test/modules/VOTES.t.sol
  • src/test/policies/BondCallback.t.sol
  • src/test/policies/Governance.t.sol
  • src/test/policies/Heart.t.sol
  • src/test/policies/Operator.t.sol
  • src/test/policies/PriceConfig.t.sol
  • src/test/policies/TreasuryCustodian.t.sol
  • src/test/policies/VoterRegistration.t.sol
  • src/utils/KernelUtils.sol

Issues found

Don't Initialize Variables with Default Value

Impact

Issue Information: G001

Findings:

src/Kernel.sol::397 => for (uint256 i = 0; i < reqLength; ) {
src/scripts/Deploy.sol::239 => for (uint i = 0; i < 90; i++) {
src/test/lib/UserFactory.sol::25 => for (uint256 i = 0; i < userNum; i++) {
src/utils/KernelUtils.sol::43 => for (uint256 i = 0; i < 5; ) {
src/utils/KernelUtils.sol::58 => for (uint256 i = 0; i < 32; ) {

Tools used

c4udit

Cache Array Length Outside of Loop

Impact

Issue Information: G002

Findings:

src/Kernel.sol::300 => getPolicyIndex[policy_] = activePolicies.length - 1;
src/Kernel.sol::304 => uint256 depLength = dependencies.length;
src/Kernel.sol::310 => getDependentIndex[keycode][policy_] = moduleDependents[keycode].length - 1;
src/Kernel.sol::334 => Policy lastPolicy = activePolicies[activePolicies.length - 1];
src/Kernel.sol::352 => uint256 keycodeLen = allKeycodes.length;
src/Kernel.sol::361 => uint256 policiesLen = activePolicies.length;
src/Kernel.sol::380 => uint256 depLength = dependents.length;
src/Kernel.sol::396 => uint256 reqLength = requests_.length;
src/Kernel.sol::411 => uint256 depcLength = dependencies.length;
src/Kernel.sol::418 => Policy lastPolicy = dependents[dependents.length - 1];
src/external/OlympusERC20.sol::105 => revert("ECDSA: invalid signature length");
src/external/OlympusERC20.sol::138 => // Check the signature length
src/external/OlympusERC20.sol::141 => if (signature.length == 65) {
src/external/OlympusERC20.sol::153 => } else if (signature.length == 64) {
src/external/OlympusERC20.sol::285 => // 32 is the length in bytes of hash,
src/interfaces/IBondAggregator.sol::65 => /// @dev                Should be used if length exceeds max to query entire array
src/interfaces/IBondAuctioneer.sol::36 => /// @dev                    8. Is fixed term ? Vesting length (seconds) : Vesting expiration (timestamp).
src/libraries/TransferHelper.sol::20 => require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");
src/libraries/TransferHelper.sol::32 => require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");
src/libraries/TransferHelper.sol::44 => require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
src/modules/INSTR.sol::43 => uint256 length = instructions_.length;
src/modules/INSTR.sol::48 => if (length == 0) revert INSTR_InstructionsCannotBeEmpty();
src/modules/INSTR.sol::50 => for (uint256 i; i < length; ) {
src/modules/INSTR.sol::61 => } else if (instruction.action == Actions.ChangeExecutor && i != length - 1) {
src/modules/PRICE.sol::201 => /// @param  startObservations_ - Array of observations to initialize the moving average with. Must be of length numObservations.
src/modules/PRICE.sol::212 => uint256 numObs = observations.length;
src/modules/PRICE.sol::215 => if (startObservations_.length != numObs || lastObservationTime_ > uint48(block.timestamp))
src/policies/BondCallback.sol::155 => uint256 len = tokens_.length;
src/policies/Governance.sol::188 => if (instructions.length == 0) {
src/policies/Governance.sol::278 => for (uint256 step; step < instructions.length; ) {
src/policies/PriceConfig.sol::41 => /// @param startObservations_   Array of observations to initialize the moving average with. Must be of length numObservations.
src/policies/TreasuryCustodian.sol::58 => uint256 len = tokens_.length;
src/test/lib/ModuleTestFixtureGenerator.sol::18 => uint256 len = requests_.length;
src/test/lib/ModuleTestFixtureGenerator.sol::36 => uint256 len = _requests.length;
src/test/lib/ModuleTestFixtureGenerator.sol::63 => uint256 num = selectors.length;
src/test/lib/bonds/BondAggregator.sol::156 => uint256 len = mkts.length;
src/test/lib/bonds/BondAggregator.sol::182 => uint256 len = forPayout.length;
src/test/lib/bonds/BondAggregator.sol::213 => uint256 len = ids.length;
src/test/lib/bonds/BondFixedTermTeller.sol::160 => uint256 len = tokenIds_.length;
src/test/lib/bonds/bases/BondBaseCDA.sol::199 => length: secondsToConclusion,
src/test/lib/bonds/bases/BondBaseCDA.sol::216 => // Initial target debt is equal to capacity scaled by the ratio of the debt decay interval and the length of the market.
src/test/lib/bonds/bases/BondBaseCDA.sol::543 => uint256(meta.length) - timeRemaining,
src/test/lib/bonds/bases/BondBaseCDA.sol::544 => uint256(meta.length)
src/test/lib/bonds/bases/BondBaseCDA.sol::564 => // Calculate target debt from the timeNeutralCapacity and the ratio of debt decay interval and the length of the market
src/test/lib/bonds/bases/BondBaseCDA.sol::567 => uint256(meta.length)
src/test/lib/bonds/bases/BondBaseTeller.sol::101 => uint256 len = tokens_.length;
src/test/lib/bonds/interfaces/IBondAggregator.sol::65 => /// @dev                Should be used if length exceeds max to query entire array
src/test/lib/bonds/interfaces/IBondAuctioneer.sol::36 => /// @dev                    8. Is fixed term ? Vesting length (seconds) : Vesting expiration (timestamp).
src/test/lib/bonds/interfaces/IBondCDA.sol::28 => uint48 vesting; // length of time from deposit to maturity if fixed-term, vesting timestamp if fixed-expiry
src/test/lib/bonds/interfaces/IBondCDA.sol::37 => uint32 length; // time from creation to conclusion.
src/test/lib/bonds/interfaces/IBondCDA.sol::75 => // l = length of program
src/test/lib/bonds/lib/ERC1155.sol::65 => to.code.length == 0
src/test/lib/bonds/lib/ERC1155.sol::80 => uint256 idsLength = ids.length; // Saves MLOADs.
src/test/lib/bonds/lib/ERC1155.sol::82 => require(idsLength == amounts.length, "LENGTH_MISMATCH");
src/test/lib/bonds/lib/ERC1155.sol::97 => // An array can't have a total length
src/test/lib/bonds/lib/ERC1155.sol::107 => to.code.length == 0
src/test/lib/bonds/lib/ERC1155.sol::126 => uint256 ownersLength = owners.length; // Saves MLOADs.
src/test/lib/bonds/lib/ERC1155.sol::128 => require(ownersLength == ids.length, "LENGTH_MISMATCH");
src/test/lib/bonds/lib/ERC1155.sol::167 => to.code.length == 0
src/test/lib/bonds/lib/ERC1155.sol::186 => uint256 idsLength = ids.length; // Saves MLOADs.
src/test/lib/bonds/lib/ERC1155.sol::188 => require(idsLength == amounts.length, "LENGTH_MISMATCH");
src/test/lib/bonds/lib/ERC1155.sol::193 => // An array can't have a total length
src/test/lib/bonds/lib/ERC1155.sol::203 => to.code.length == 0
src/test/lib/bonds/lib/ERC1155.sol::221 => uint256 idsLength = ids.length; // Saves MLOADs.
src/test/lib/bonds/lib/ERC1155.sol::223 => require(idsLength == amounts.length, "LENGTH_MISMATCH");
src/test/lib/bonds/lib/ERC1155.sol::228 => // An array can't have a total length
src/test/lib/quabi/Quabi.sol::35 => uint256 len = response.length;
src/test/mocks/MockModuleWriter.sol::15 => uint256 len = requests_.length;
src/test/mocks/MockModuleWriter.sol::30 => uint256 len = _requests.length;
src/test/mocks/MockModuleWriter.sol::42 => if (output.length == 0) revert();

Tools used

c4udit

Use != 0 instead of > 0 for Unsigned Integer Comparison

Impact

Issue Information: G003

Findings:

src/external/OlympusERC20.sol::245 => if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
src/external/OlympusERC20.sol::611 => require(b > 0, errorMessage);
src/libraries/FullMath.sol::35 => require(denominator > 0);
src/libraries/FullMath.sol::122 => if (mulmod(a, b, denominator) > 0) {
src/policies/Governance.sol::247 => if (userVotesForProposal[activeProposal.proposalId][msg.sender] > 0) {
src/test/modules/TRSRY.t.sol::98 => vm.assume(amount_ > 0);
src/test/modules/TRSRY.t.sol::108 => vm.assume(amount_ > 0);
src/test/modules/TRSRY.t.sol::126 => vm.assume(amount_ > 0);
src/test/modules/TRSRY.t.sol::143 => vm.assume(amount_ > 0);
src/utils/KernelUtils.sol::46 => if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_); // A-Z only
src/utils/KernelUtils.sol::60 => if ((char < 0x61 || char > 0x7A) && char != 0x5f && char != 0x00) {

Tools used

c4udit

Use immutable for OpenZeppelin AccessControl's Roles Declarations

Impact

Issue Information: G006

Findings:

src/external/OlympusERC20.sol::287 => return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
src/external/OlympusERC20.sol::304 => return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
src/external/OlympusERC20.sol::315 => * they need in their contracts using a combination of `abi.encode` and `keccak256`.
src/external/OlympusERC20.sol::360 => bytes32 hashedName = keccak256(bytes(name));
src/external/OlympusERC20.sol::361 => bytes32 hashedVersion = keccak256(bytes(version));
src/external/OlympusERC20.sol::362 => bytes32 typeHash = keccak256(
src/external/OlympusERC20.sol::398 => return keccak256(abi.encode(typeHash, nameHash, versionHash, chainID, address(this)));
src/external/OlympusERC20.sol::408 => * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
src/external/OlympusERC20.sol::409 => *     keccak256("Mail(address to,string contents)"),
src/external/OlympusERC20.sol::411 => *     keccak256(bytes(mailContents))
src/external/OlympusERC20.sol::665 => bytes32 private constant ERC20TOKEN_ERC1820_INTERFACE_ID = keccak256("ERC20Token");
src/external/OlympusERC20.sol::835 => keccak256(
src/external/OlympusERC20.sol::860 => bytes32 structHash = keccak256(
src/test/lib/UserFactory.sol::9 => address(bytes20(uint160(uint256(keccak256("hevm cheat code")))));
src/test/lib/UserFactory.sol::13 => bytes32 internal nextUser = keccak256(abi.encodePacked("users"));
src/test/lib/UserFactory.sol::18 => nextUser = keccak256(abi.encodePacked(nextUser));
src/test/lib/bonds/BondFixedTermTeller.sol::231 => keccak256(abi.encodePacked(underlying_, expiry_ / uint48(1 days)))
src/test/lib/larping.sol::9 => address(bytes20(uint160(uint256(keccak256("hevm cheat code")))));
src/test/lib/quabi/Quabi.sol::8 => Vm internal constant vm = Vm(address(bytes20(uint160(uint256(keccak256("hevm cheat code"))))));
src/test/modules/PRICE.t.sol::101 => change = int256(uint256(keccak256(abi.encodePacked(nonce, i)))) % int256(1000);
src/test/modules/PRICE.t.sol::128 => change = int256(uint256(keccak256(abi.encodePacked(nonce, i)))) % int256(1000);
src/test/policies/PriceConfig.t.sol::124 => change = int256(uint256(keccak256(abi.encodePacked(nonce, i)))) % int256(1000);

Tools used

c4udit

Long Revert Strings

Impact

Issue Information: G007

Findings:

src/external/OlympusERC20.sol::107 => revert("ECDSA: invalid signature 's' value");
src/external/OlympusERC20.sol::109 => revert("ECDSA: invalid signature 'v' value");
src/external/OlympusERC20.sol::363 => "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
src/external/OlympusERC20.sol::597 => require(c / a == b, "SafeMath: multiplication overflow");
src/external/OlympusERC20.sol::738 => _allowances[sender][msg.sender].sub(amount, "ERC20: transfer amount exceeds allowance")
src/external/OlympusERC20.sol::758 => "ERC20: decreased allowance below zero"
src/external/OlympusERC20.sol::769 => require(sender != address(0), "ERC20: transfer from the zero address");
src/external/OlympusERC20.sol::770 => require(recipient != address(0), "ERC20: transfer to the zero address");
src/external/OlympusERC20.sol::774 => _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
src/external/OlympusERC20.sol::788 => require(account != address(0), "ERC20: burn from the zero address");
src/external/OlympusERC20.sol::792 => _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
src/external/OlympusERC20.sol::802 => require(owner != address(0), "ERC20: approve from the zero address");
src/external/OlympusERC20.sol::803 => require(spender != address(0), "ERC20: approve to the zero address");
src/external/OlympusERC20.sol::836 => "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
src/external/OlympusERC20.sol::925 => "ERC20: burn amount exceeds allowance"
src/modules/PRICE.sol::4 => import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
src/modules/TRSRY.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/modules/VOTES.sol::18 => ERC20("OlympusDAO Dummy Voting Tokens", "VOTES", 0)
src/policies/BondCallback.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/policies/Heart.sol::4 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/policies/Heart.sol::7 => import {IOperator} from "policies/interfaces/IOperator.sol";
src/policies/Operator.sol::4 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/policies/Operator.sol::7 => import {IOperator} from "policies/interfaces/IOperator.sol";
src/scripts/Deploy.sol::4 => import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
src/scripts/Deploy.sol::271 => console2.log("RESERVE-ETH Price Feed deployed to:", address(reserveEthPriceFeed));
src/test/Kernel.t.sol::77 => err = abi.encodeWithSignature("InvalidKeycode(bytes5)", Keycode.wrap("inval"));
src/test/Kernel.t.sol::81 => err = abi.encodeWithSignature("InvalidKeycode(bytes5)", Keycode.wrap(""));
src/test/Kernel.t.sol::89 => err = abi.encodeWithSignature("InvalidRole(bytes32)", Role.wrap("INVALID_ID"));
src/test/Kernel.t.sol::150 => err = abi.encodeWithSignature("InvalidKeycode(bytes5)", Keycode.wrap("badkc"));
src/test/Kernel.t.sol::156 => "Kernel_ModuleAlreadyInstalled(bytes5)",
src/test/Kernel.t.sol::169 => err = abi.encodeWithSignature("Policy_ModuleDoesNotExist(bytes5)", testKeycode);
src/test/Kernel.t.sol::187 => err = abi.encodeWithSignature("Kernel_PolicyAlreadyActivated(address)", address(policy));
src/test/Kernel.t.sol::246 => err = abi.encodeWithSignature("Kernel_PolicyAlreadyActivated(address)", address(policy));
src/test/Kernel.t.sol::254 => err = abi.encodeWithSignature("Module_PolicyNotPermitted(address)", address(policy));
src/test/Kernel.t.sol::275 => err = abi.encodeWithSignature("Kernel_InvalidModuleUpgrade(bytes5)", Keycode.wrap("MOCKY"));
src/test/Kernel.t.sol::281 => err = abi.encodeWithSignature("Kernel_InvalidModuleUpgrade(bytes5)", Keycode.wrap("MOCKY"));
src/test/Kernel.t.sol::359 => err = abi.encodeWithSignature("Policy_OnlyRole(bytes32)", Role.wrap("tester"));
src/test/lib/bonds/BondFixedTermTeller.sol::7 => import {IBondFixedTermTeller} from "./interfaces/IBondFixedTermTeller.sol";
src/test/lib/bonds/bases/BondBaseCDA.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/test/lib/bonds/bases/BondBaseCDA.sol::11 => import {IBondAggregator} from "../interfaces/IBondAggregator.sol";
src/test/lib/bonds/bases/BondBaseTeller.sol::5 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/test/lib/bonds/bases/BondBaseTeller.sol::10 => import {IBondAggregator} from "../interfaces/IBondAggregator.sol";
src/test/lib/bonds/bases/BondBaseTeller.sol::11 => import {IBondAuctioneer} from "../interfaces/IBondAuctioneer.sol";
src/test/lib/bonds/interfaces/IBondCDA.sol::5 => import {IBondAuctioneer} from "../interfaces/IBondAuctioneer.sol";
src/test/lib/quabi/Quabi.sol::17 => inputs[2] = string(bytes.concat("./src/test/lib/quabi/jq.sh ", bytes(query), " ", bytes(path), ""));
src/test/lib/quabi/Quabi.sol::27 => inputs[2] = string(bytes.concat("./src/test/lib/quabi/path.sh ", bytes(contractName), ".json", ""));
src/test/lib/quabi/Quabi.sol::48 => string memory query = "'[.ast.nodes[-1].nodes[] | if .nodeType == \"FunctionDefinition\" and .kind == \"function\" then .functionSelector else empty end ]'";
src/test/lib/quabi/Quabi.sol::55 => string memory query = string(bytes.concat("'[.ast.nodes[-1].nodes[] | if .nodeType == \"FunctionDefinition\" and .kind == \"function\" and ([.modifiers[] | .modifierName.name == \"", bytes(modifierName), "\" ] | any ) then .functionSelector else empty end ]'"));
src/test/mocks/Faucet.sol::6 => import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
src/test/mocks/MockPrice.sol::4 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/mocks/MockPriceFeed.sol::4 => import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
src/test/modules/INSTR.t.sol::7 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/modules/INSTR.t.sol::15 => import {MockValidUpgradedModule} from "test/mocks/MockValidUpgradedModule.sol";
src/test/modules/MINTR.t.sol::10 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/modules/PRICE.t.sol::7 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/modules/PRICE.t.sol::9 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/modules/RANGE.t.sol::7 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/modules/RANGE.t.sol::9 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/modules/TRSRY.t.sol::7 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/modules/TRSRY.t.sol::9 => import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/modules/VOTES.t.sol::8 => import {ModuleTestFixtureGenerator} from "test/lib/ModuleTestFixtureGenerator.sol";
src/test/policies/BondCallback.t.sol::8 => import {BondFixedTermCDA} from "test/lib/bonds/BondFixedTermCDA.sol";
src/test/policies/BondCallback.t.sol::9 => import {BondAggregator} from "test/lib/bonds/BondAggregator.sol";
src/test/policies/BondCallback.t.sol::10 => import {BondFixedTermTeller} from "test/lib/bonds/BondFixedTermTeller.sol";
src/test/policies/BondCallback.t.sol::11 => import {IBondAuctioneer as LibAuctioneer} from "test/lib/bonds/interfaces/IBondAuctioneer.sol";
src/test/policies/BondCallback.t.sol::12 => import {RolesAuthority, Authority as SolmateAuthority} from "solmate/auth/authorities/RolesAuthority.sol";
src/test/policies/BondCallback.t.sol::14 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/policies/BondCallback.t.sol::375 => "Callback_MarketNotSupported(uint256)",
src/test/policies/BondCallback.t.sol::431 => "Callback_MarketNotSupported(uint256)",
src/test/policies/Governance.t.sol::7 => import {ModuleTestFixtureGenerator} from "../lib/ModuleTestFixtureGenerator.sol";
src/test/policies/Governance.t.sol::118 => governance.submitProposal(instructions_, "proposalName", "This is the proposal URI");
src/test/policies/Governance.t.sol::128 => governance.submitProposal(instructions_, "proposalName", "This is the proposal URI");
src/test/policies/Governance.t.sol::139 => governance.submitProposal(instructions_, "proposalName", "This is the proposal URI");
src/test/policies/Governance.t.sol::150 => governance.submitProposal(instructions_, "proposalName", "This is the proposal URI");
src/test/policies/Heart.t.sol::8 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/policies/Heart.t.sol::17 => import {IOperator, ERC20, IBondAuctioneer, IBondCallback} from "policies/interfaces/IOperator.sol";
src/test/policies/Operator.t.sol::8 => import {BondFixedTermCDA} from "test/lib/bonds/BondFixedTermCDA.sol";
src/test/policies/Operator.t.sol::9 => import {BondAggregator} from "test/lib/bonds/BondAggregator.sol";
src/test/policies/Operator.t.sol::10 => import {BondFixedTermTeller} from "test/lib/bonds/BondFixedTermTeller.sol";
src/test/policies/Operator.t.sol::11 => import {RolesAuthority, Authority as SolmateAuthority} from "solmate/auth/authorities/RolesAuthority.sol";
src/test/policies/Operator.t.sol::13 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/policies/Operator.t.sol::409 => "Operator_AmountLessThanMinimum(uint256,uint256)",
src/test/policies/Operator.t.sol::424 => "Operator_AmountLessThanMinimum(uint256,uint256)",
src/test/policies/PriceConfig.t.sol::8 => import {MockERC20, ERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/policies/TreasuryCustodian.t.sol::7 => import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol";
src/test/policies/TreasuryCustodian.t.sol::13 => import {TreasuryCustodian} from "src/policies/TreasuryCustodian.sol";

Tools used

c4udit

Use Shift Right/Left instead of Division/Multiplication if possible

Impact

Issue Information: G008

Findings:

src/external/OlympusERC20.sol::243 => // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
src/external/OlympusERC20.sol::520 => * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
src/external/OlympusERC20.sol::641 => // this feature: see https://github.com/ethereum/solidity/issues/4637
src/interfaces/IBondAuctioneer.sol::41 => /// @dev                        Should be calculated as: (payoutDecimals - quoteDecimals) - ((payoutPriceDecimals - quotePriceDecimals) / 2)
src/libraries/FullMath.sol::13 => /// @dev Credit to Remco Bloemen under MIT license https://xn--2-umb.com/21/muldiv
src/libraries/FullMath.sol::21 => // Compute the product mod 2**256 and mod 2**256 - 1
src/libraries/FullMath.sol::24 => // variables such that product = prod1 * 2**256 + prod0
src/libraries/FullMath.sol::42 => // Make sure the result is less than 2**256.
src/libraries/FullMath.sol::76 => // to flip `twos` such that it is 2**256 / twos.
src/libraries/FullMath.sol::83 => // Invert denominator mod 2**256
src/libraries/FullMath.sol::85 => // modulo 2**256 such that denominator * inv = 1 mod 2**256.
src/libraries/FullMath.sol::87 => // correct for four bits. That is, denominator * inv = 1 mod 2**4
src/libraries/FullMath.sol::92 => inv *= 2 - denominator * inv; // inverse mod 2**8
src/libraries/FullMath.sol::97 => inv *= 2 - denominator * inv; // inverse mod 2**256
src/libraries/FullMath.sol::101 => // correct result modulo 2**256. Since the precoditions guarantee
src/libraries/FullMath.sol::102 => // that the outcome is less than 2**256, this is the final result.
src/policies/Operator.sol::372 => int8 scaleAdjustment = int8(ohmDecimals) - int8(reserveDecimals) + (priceDecimals / 2);
src/policies/Operator.sol::419 => uint256 invCushionPrice = 10**(oracleDecimals * 2) / range.cushion.low.price;
src/policies/Operator.sol::420 => uint256 invWallPrice = 10**(oracleDecimals * 2) / range.wall.low.price;
src/policies/Operator.sol::427 => int8 scaleAdjustment = int8(reserveDecimals) - int8(ohmDecimals) + (priceDecimals / 2);
src/policies/Operator.sol::786 => ) * (FACTOR_SCALE + RANGE.spread(true) * 2)) /
src/scripts/Deploy.sol::144 => uint32(7) // regenObserve    // 21
src/test/lib/bonds/bases/BondBaseCDA.sol::157 => // scaleAdjustment should be equal to (payoutDecimals - quoteDecimals) - ((payoutPriceDecimals - quotePriceDecimals) / 2)
src/test/lib/bonds/bases/BondBaseCDA.sol::520 => // 2. If a tune interval has passed since last tune adjustment and the market is undersold
src/test/lib/bonds/bases/BondBaseTeller.sol::134 => // 2. Calculate protocol fee as the total expected fee amount minus the referrer fee
src/test/lib/bonds/bases/BondBaseTeller.sol::251 => int256 num1 = __days + 68569 + 2440588; // 2440588 = OFFSET19700101
src/test/lib/bonds/bases/BondBaseTeller.sol::253 => num1 = num1 - (146097 * num2 + 3) / 4;
src/test/lib/bonds/bases/BondBaseTeller.sol::255 => num1 = num1 - (1461 * _year) / 4 + 31;
src/test/lib/bonds/bases/BondBaseTeller.sol::256 => int256 _month = (80 * num1) / 2447;
src/test/lib/bonds/bases/BondBaseTeller.sol::257 => int256 _day = num1 - (2447 * _month) / 80;
src/test/lib/bonds/interfaces/IBondAuctioneer.sol::41 => /// @dev                        Should be calculated as: (payoutDecimals - quoteDecimals) - ((payoutPriceDecimals - quotePriceDecimals) / 2)
src/test/lib/larping.sol::13 => // ,address
src/test/lib/larping.sol::38 => // ,bool
src/test/lib/larping.sol::63 => // ,bytes32
src/test/lib/larping.sol::88 => // ,string
src/test/lib/larping.sol::113 => // ,uint256
src/test/lib/larping.sol::138 => // ,uint8
src/test/modules/PRICE.t.sol::34 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
src/test/modules/RANGE.t.sol::36 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
src/test/modules/TRSRY.t.sol::174 => TRSRY.setDebt(ngmi, debtor, INITIAL_TOKEN_AMOUNT / 2);
src/test/modules/TRSRY.t.sol::176 => assertEq(TRSRY.reserveDebt(ngmi, debtor), INITIAL_TOKEN_AMOUNT / 2);
src/test/modules/TRSRY.t.sol::177 => assertEq(TRSRY.totalDebt(ngmi), INITIAL_TOKEN_AMOUNT / 2);
src/test/modules/TRSRY.t.sol::191 => TRSRY.setDebt(ngmi, debtor, INITIAL_TOKEN_AMOUNT / 2);
src/test/policies/BondCallback.t.sol::81 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
src/test/policies/BondCallback.t.sol::200 => ohm.mint(alice, testOhm * 20);
src/test/policies/BondCallback.t.sol::201 => reserve.mint(alice, testReserve * 20);
src/test/policies/BondCallback.t.sol::207 => ohm.approve(address(operator), testOhm * 20);
src/test/policies/BondCallback.t.sol::209 => reserve.approve(address(operator), testReserve * 20);
src/test/policies/BondCallback.t.sol::212 => ohm.approve(address(teller), testOhm * 20);
src/test/policies/BondCallback.t.sol::214 => reserve.approve(address(teller), testReserve * 20);
src/test/policies/BondCallback.t.sol::221 => // 2. Internal bond (OHM -> OHM)
src/test/policies/BondCallback.t.sol::225 => // 4. Regular OHM bond that will not be whitelisted
src/test/policies/BondCallback.t.sol::271 => uint256 minimumPrice = (priceSignificand / 2) *
src/test/policies/Heart.t.sol::64 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
src/test/policies/Operator.t.sol::73 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)
src/test/policies/Operator.t.sol::185 => ohm.mint(alice, testOhm * 20);
src/test/policies/Operator.t.sol::186 => reserve.mint(alice, testReserve * 20);
src/test/policies/Operator.t.sol::192 => ohm.approve(address(operator), testOhm * 20);
src/test/policies/Operator.t.sol::194 => reserve.approve(address(operator), testReserve * 20);
src/test/policies/Operator.t.sol::197 => ohm.approve(address(teller), testOhm * 20);
src/test/policies/Operator.t.sol::199 => reserve.approve(address(teller), testReserve * 20);
src/test/policies/Operator.t.sol::869 => uint256 amountIn = auctioneer.maxAmountAccepted(id, guardian) / 2;
src/test/policies/Operator.t.sol::900 => uint256 amountIn = auctioneer.maxAmountAccepted(id, guardian) / 2;
src/test/policies/Operator.t.sol::2044 => ) * (1e4 + range.spread(true) * 2)) / 1e4;
src/test/policies/PriceConfig.t.sol::38 => vm.warp(51 * 365 * 24 * 60 * 60); // Set timestamp at roughly Jan 1, 2021 (51 years since Unix epoch)

Tools used

c4udit

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.