akiratechhq / review-hopprotocol-contracts-2021-04 Goto Github PK
View Code? Open in Web Editor NEWLicense: Apache License 2.0
License: Apache License 2.0
Description
The methods addBonder
and removeBonder
respectively add and remove bonders.
Also, the constructor adds a list of bonders to the bonder list.
It might be beneficial to emit events when adding and removing bonders.
Recommendation
Consider emitting events when adding or removing bonders.
Description
The method Lib_MerkleTree._ceilLog2
is used in the getMerkleRoot
method to calculate the total siblings count.
As stated in the source code, it was copied from the Solidity examples repository.
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.
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
Description
All of these methods update important configurations for the bridge.
Recommendation
Consider emitting events with the new values (and possibly old values) when these changes go through.
Similarly can be done for L1_Bridge
.
Description
The contract L2_AmmWrapper
has a few storage variables.
These storage vars are set at deploy time.
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
.
But also in these cases (not a complete list):
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
Description
This method is set as view
but can be restricted to pure
as long as the Solidity version is <0.8.x
.
Also, the hack to silence the state mutability can also be removed.
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
.
Description
The Accounting contract takes care of low-level accounting and has a couple of external
methods that handle staking and unstaking of funds.
A couple of matching events exist in the contract.
But they are not emitted in this contract or in any contract that inherits Accounting
.
Recommendation
Emit events when staking and unstaking.
Description
The method distribute
can only be called by the Layer 1 Bridge.
The external
method calls the internal
method _distribute
in order to distribute tokens.
If the fee
is positive, it will mint some tokens for the caller.
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.
Description
Uniswap is mentioned, make sure to replace with something like AMM.
Recommendation
Make sure to update the error messages to reflect the current contract.
Description
The governance can set a minimum fee, as well as a minimum percentage by calling setMinimumBonderFeeRequirements
.
The values could be checked for sanity, especially the percentage.
Recommendation
Check the value of minBonderBps
to be something less than 10000
.
Description
There are a couple of error messages that refer to the L2 Bridge
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.
Recommendation
Update the error messages to correctly reflect the current contract.
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.
Description
Anyone can call the method commitTransfers
to commit the current pending transfers.
The Bonder can call this method anytime they want.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.