GithubHelp home page GithubHelp logo

review-hopprotocol-contracts-2021-04's People

Watchers

 avatar  avatar

review-hopprotocol-contracts-2021-04's Issues

Emit events when adding and removing bonders

Optimize `_ceilLog2` by using `uint256`

Description

The method Lib_MerkleTree._ceilLog2 is used in the getMerkleRoot method to calculate the total siblings count.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/5e66c267c52206b7e77d5515f64d939e85539f04/code/contracts/libraries/Lib_MerkleTree.sol#L144-L147

As stated in the source code, it was copied from the Solidity examples repository.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/5e66c267c52206b7e77d5515f64d939e85539f04/code/contracts/libraries/Lib_MerkleTree.sol#L202-L203

The Solidity example repository includes this method as an example for other developers to learn how to use the language. Because it does not strive to be the optimal implementation, its purpose is to show different features of the language itself.

https://github.com/ethereum/solidity-examples/blob/f44fe3b3b4cca94afe9c2a2d5b7840ff0fafb72e/src/bits/Bits.sol#L87-L99

This is why some of the examples in the Solidity repository might not be gas optimized.

Using uint8 in the loop forces the compiler to create sub-optimal code.

The following Solidity contract example was used to measure the difference between using uint8 and uint256 with a few example inputs.

contract FindBit {
    function find_highest_set_bit(
        uint value
    ) 
        public 
        pure 
        returns (
            uint
        ) 
    {
        uint _in = value;
        
        if (_in == 1) {
            return 0;
        }

        // Find the highest set bit (will be floor(log_2)).
        // Borrowed with <3 from https://github.com/ethereum/solidity-examples
        uint256 val = _in;
        uint256 highest = 0;
        for (uint8 i = 128; i >= 1; i >>= 1) {
            if (val & (uint(1) << i) - 1 << i != 0) {
                highest += i;
                val >>= i;
            }
        }

        // Increment by one if this is not a perfect logarithm.
        if ((uint(1) << highest) != _in) {
            highest += 1;
        }

        return highest;        
    }
}

Changing the line from uint8 to uint256

        for (uint8 i = 128; i >= 1; i >>= 1) {
        for (uint256 i = 128; i >= 1; i >>= 1) {

Gives the following different gas costs, while returning the same, identical results.

Solidity version 0.8.3+commit.8d00100c and 200 optimization runs were used in Remix IDE for tests.

input output uint8 gas uint256 gas difference
8 3 1876 1692 184
15 4 1951 1767 184
100 7 1951 1767 184
128 7 1980 1779 201
4294967295 (0xffffffff) 32 2263 2028 235
1099511627775 (0xffffffffff) 40 2159 1941 218
18446744073709552000 (0xffffffffffffffff) 64 2367 2115 252

Recommendation

Modify the loop iterator from uint8 to uint256 to reduce gas costs, while having the same output.

References

Improve gas usage in `L2_AmmWrapper`

Description

The contract L2_AmmWrapper has a few storage variables.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L13-L17

These storage vars are set at deploy time.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L19-L34

None of them are changed after the initial set.

This is why they could be defined as immutable. Solidity does not reserve a storage slot for immutables, but every occurrence is replaced in the bytecode by the respective value. This greatly reduces gas costs.

To have a better understanding of the gas cost reduction, two contracts were created.

The first contract defines a storage variable and sets it at deploy time. It also set the variable to public, this way Solidity will create a getter for it. This getter is important to measure the gas cost to access it.

contract A {
    bool public isSet;
    
    constructor(bool _isSet) public {
        isSet = _isSet;
    }
}

The second contract uses an immutable instead of a storage slot. That is the only difference between the two.

contract B {
    bool public immutable isSet;
    
    constructor(bool _isSet) public {
        isSet = _isSet;
    }
}

We've created the following table to show the differences in gas costs between the two approaches.

deploy gas cost getter gas cost
storage slot 47801 988
immutable 32403 182

Both examples were tested in Remix IDE, Solidity version 0.6.12, optimization runs 200.

Recommendation

Change all state variables to immutable to greatly reduce gas costs.

Similarly, change the state variables to immutable in L2_Bridge.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/bridges/L2_Bridge.sol#L78-L80

But also in these cases (not a complete list):

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/wrappers/ArbitrumMessengerWrapper.sol#L36-L39

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/wrappers/OptimismMessengerWrapper.sol#L29

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/wrappers/XDaiMessengerWrapper.sol#L35-L36

There are instances where some state variables are never changed in other contracts. If you think you won't change them in the future, consider checking all contract constructors.

References

The method `getChainId` can be restricted to `pure` in Solidity <0.8.x

Description

This method is set as view but can be restricted to pure as long as the Solidity version is <0.8.x.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/51418c09ffde685e7f03be3308d723aa96fad13c/code/contracts/bridges/Bridge.sol#L112

Also, the hack to silence the state mutability can also be removed.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/51418c09ffde685e7f03be3308d723aa96fad13c/code/contracts/bridges/Bridge.sol#L113

If you need the view mutability in order to allow other layer 2 implementations to first save and then retrieve the value from the contract state, keep the view modifier, otherwise consider switching to pure. Solidity does not do too many things differently but uses STATICCALL when calling the method to make sure the state is not changed.

Recommendation

Unless there's a reason to keep the view, consider changing to pure.

Events exist but they're not emitted in `Accounting`

`L2_Bridge._distribute` might mint tokens for the Layer 1 Bridge and they'll be locked

Description

The method distribute can only be called by the Layer 1 Bridge.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_Bridge.sol#L176-L184

The external method calls the internal method _distribute in order to distribute tokens.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_Bridge.sol#L263

If the fee is positive, it will mint some tokens for the caller.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_Bridge.sol#L264-L266

In this case, the caller is the Layer 1 Bridge as stated above.

However, the L1 Bridge doesn't seem to have a method to move the tokens.

 +  L1_Bridge (Bridge)
    - [Pub] <Constructor> #
       - modifiers: Bridge
    - [Ext] sendToL2 ($)
    - [Ext] bondTransferRoot #
       - modifiers: onlyBonder,requirePositiveBalance
    - [Ext] confirmTransferRoot #
       - modifiers: onlyL2Bridge
    - [Int] _distributeTransferRoot #
    - [Ext] challengeTransferBond ($)
    - [Ext] resolveChallenge #
    - [Int] _additionalDebit
    - [Int] _requireIsGovernance #
    - [Ext] setGovernance #
       - modifiers: onlyGovernance
    - [Ext] setCrossDomainMessengerWrapper #
       - modifiers: onlyGovernance
    - [Ext] setChainIdDepositsPaused #
       - modifiers: onlyGovernance
    - [Ext] setChallengeAmountMultiplier #
       - modifiers: onlyGovernance
    - [Ext] setChallengeAmountDivisor #
       - modifiers: onlyGovernance
    - [Ext] setChallengePeriodAndTimeSlotSize #
       - modifiers: onlyGovernance
    - [Ext] setChallengeResolutionPeriod #
       - modifiers: onlyGovernance
    - [Ext] setMinTransferRootBondDelay #
       - modifiers: onlyGovernance
    - [Pub] getBondForTransferAmount
    - [Pub] getChallengeAmountForTransferAmount
    - [Pub] getTimeSlot

In which case the tokens that represent the fee, will remain locked in the Layer 1 Bridge contract.

Recommendation

Consider adding an additional argument to point to the receiver of the fee and pass that along through the internal _distribute method. This is especially useful for the bondWithdrawalAndDistribute method.

The fee doesn't seem necessary if the Layer 1 Bridge calls the external distribute method because the amount is not bonded.

Update comments to be more swapper agnostic

`L2_Bridge.setMinimumBonderFeeRequirements` should check `minBonderBps` for validity

Description

The governance can set a minimum fee, as well as a minimum percentage by calling setMinimumBonderFeeRequirements.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_Bridge.sol#L334-L337

The values could be checked for sanity, especially the percentage.

Recommendation

Check the value of minBonderBps to be something less than 10000.

Some error messages in `L2_AmmWrapper` refer to `L2_Bridge`

Description

There are a couple of error messages that refer to the L2 Bridge

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L50

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L53

Typically the L2_BRG error prefix is found in Bridge and L2_Bridge.

Also, the other error messages seem to refer to the Uniswap Wrapper.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L56

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L59

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L72-L73

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L88

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L95

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/c5a6d5eead68465168373c39891ff1c5f197567a/code/contracts/bridges/L2_AmmWrapper.sol#L97

Recommendation

Update the error messages to correctly reflect the current contract.

Make `L2_Bridge.send` external

Description

The method is only called externally. To signal it will not be called internally by the contract or by another implementation that extends the L2_Bridge it could be defined as external.

Recommendation

Consider defining the method as external if it's not called internally.

Committing transfers should fail early if the `destinationChainId` isn't supported

Description

Anyone can call the method commitTransfers to commit the current pending transfers.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/bridges/L2_Bridge.sol#L153-L160

The Bonder can call this method anytime they want.

https://github.com/monoceros-alpha/review-hopprotocol-contracts-2021-04/blob/24ccdb4d4528ecb4e92c3d7ac5e1909bf8de34f7/code/contracts/bridges/L2_Bridge.sol#L160

But any other actor can call the method if enough time has passed since the previous commit time.

If they specify an incorrect (unsupported) chain id, the method will still try to call _commitTransfers. In which case it should fail if no transfers are pending. It is better to fail earlier if the chain id is unsupported.

Recommendation

Check the provided destinationChainId to be valid.

require(supportedChainIds[chainId], "L2_BRG: destinationChainId is not supported");

In case the chain was previously supported and it is disabled right now, some transfers can remain in a pending state, still uncommitted. It is better to be more explicit about each state you want to support and how.

In case there are pending transfers, you should still allow committing the transfers, even if the chain is not supported anymore, but not allow creating new pending transfers.

Consider allowing pending transfers if the minimum time has passed, even if the chain id is not supported anymore, only if there are pending transfers. If the logic seems messy or too complicated to be handled in commitTransfers, consider creating a flushTransfers method just for this use case.

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.