Platform: Code4rena
Start Date: 31/08/2023
Pot Size: $55,000 USDC
Total HM: 5
Participants: 30
Period: 6 days
Judge: hickuphh3
Total Solo HM: 2
Id: 282
League: ETH
Rank: 10/30
Findings: 2
Award: $821.21
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0x11singh99, 0x3b, 0xta, JCK, K42, ReyAdmirado, SAQ, Sathish9098, hunter_w3b, kaveyjoe, lsaudit, turvy_fuzz
259.0763 USDC - $259.08
Saves 20000 GAS
// 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) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past
FILE: 2023-08-livepeer/contracts/treasury/GovernorCountingOverridable.sol 38: bool hasVoted;
memory
can be more gas-efficient
when calling the same variable for the second time compared to using storage
.As per gas test this will save around 150 - 200 GAS .
lock.withdrawRound
called second time so memory
is more gas efficient than storage
FILE: 2023-08-livepeer/contracts/bonding/BondingManager.sol - 251: UnbondingLock storage lock = del.unbondingLocks[_unbondingLockId]; + 251: UnbondingLock memory lock = del.unbondingLocks[_unbondingLockId];
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct.
lastClaimRound
can be uint96
instead of uint256
. Saves 2000 GAS
, 1 SLOT
uint96
is a data type representing an unsigned integer with 96 bits, which can store values ranging from 0 to 2^96 - 1
. While this is a relatively large range of values, whether it's more than enough
to store timestamps and durations.
FILE: 2023-08-livepeer/contracts/bonding/BondingManager.sol 58: // Represents a delegator's current state 59: struct Delegator { 60: uint256 bondedAmount; // The amount of bonded tokens 61: uint256 fees; // The amount of fees collected 62: address delegateAddress; // The address delegated to + 65: uint96 lastClaimRound; // The last round during which the delegator claimed its earnings 63: uint256 delegatedAmount; // The amount of tokens delegated to the delegator 64: uint256 startRound; // The round the delegator transitions to bonded phase and is delegated to someone - 65: uint256 lastClaimRound; // The last round during which the delegator claimed its earnings 66: uint256 nextUnbondingLockId; // ID for the next unbonding lock created 67: mapping(uint256 => UnbondingLock) unbondingLocks; // Mapping of unbonding lock ID => unbonding lock 68: }
lastClaimRound
can be uint96
instead of uint256
. Saves 2000 GAS
, 1 SLOT
uint96
is a data type representing an unsigned integer with 96 bits, which can store values ranging from 0 to 2^96 - 1
. While this is a relatively large range of values, whether it's more than enough
to store timestamps and durations.
FILE: 2023-08-livepeer/contracts/bonding/BondingVotes.sol struct BondingCheckpoint { /** * @dev The amount of bonded tokens to another delegate as of the lastClaimRound. */ uint256 bondedAmount; /** * @dev The address of the delegate the account is bonded to. In case of transcoders this is their own address. */ address delegateAddress; + uint96 lastClaimRound; /** * @dev The amount of tokens delegated from delegators to this account. This is only set for transcoders, which * have to self-delegate first and then have tokens bonded from other delegators. */ uint256 delegatedAmount; /** * @dev The last round during which the delegator claimed its earnings. This pegs the value of bondedAmount for * rewards calculation in {EarningsPoolLIP36-delegatorCumulativeStakeAndFees}. */ - uint256 lastClaimRound; /** * @dev The last round during which the checkpointed account called {BondingManager-reward}. This is needed to * when calculating pending rewards for a delegator to this transcoder, to find the last earning pool available * for a given round. In that case we start from the delegator checkpoint and then fetch its delegate address * checkpoint as well to find the last earning pool. * * Notice that this is the only field that comes from the Transcoder struct in BondingManager, not Delegator. */ uint256 lastRewardRound; }
IF’s/require()
statements that check input arguments
should be at the top of the functionFAIL CHEEPLY INSTEAD OF COSTLY
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas) in a function that may ultimately revert in the unhappy case.
function parameter
before creating storage variables
FILE: 2023-08-livepeer/contracts/bonding/BondingManager.sol + 253: require(isValidUnbondingLock(msg.sender, _unbondingLockId), "invalid unbonding lock ID"); 250: Delegator storage del = delegators[msg.sender]; 251: UnbondingLock storage lock = del.unbondingLocks[_unbondingLockId]; 252: - 253: require(isValidUnbondingLock(msg.sender, _unbondingLockId), "invalid unbonding lock ID"); 254: require( 255: lock.withdrawRound <= roundsManager().currentRound(), 266: "withdraw round must be before or equal to the current round" 267: ); + 754: require(_amount > 0, "unbond amount must be greater than 0"); 750: require(delegatorStatus(msg.sender) == DelegatorStatus.Bonded, "caller must be bonded"); 751: 752: Delegator storage del = delegators[msg.sender]; 753: - 754: require(_amount > 0, "unbond amount must be greater than 0"); 755: require(_amount <= del.bondedAmount, "amount is greater than bonded amount");
Caching state variable with memory saves 100 GAS
, 1 SLOD
900 GAS
, 9 SLOD
FILE: 2023-08-livepeer/contracts/bonding/BondingManager.sol + uint256 withdrawRound = lock.withdrawRound; 253: require(isValidUnbondingLock(msg.sender, _unbondingLockId), "invalid unbonding lock ID"); require( withdrawRound <= roundsManager().currentRound(), "withdraw round must be before or equal to the current round" ); uint256 amount = lock.amount; - uint256 withdrawRound = lock.withdrawRound; // Delete unbonding lock delete del.unbondingLocks[_unbondingLockId]; // Tell Minter to transfer stake (LPT) to the delegator minter().trustedTransferTokens(msg.sender, amount); emit WithdrawStake(msg.sender, _unbondingLockId, amount, withdrawRound); + uint256 bondedAmount_ = del.bondedAmount ; - if (del.bondedAmount > 0) { + if (bondedAmount_ > 0) { uint256 penalty = MathUtils.percOf(delegators[_transcoder].bondedAmount, _slashAmount); // If active transcoder, resign it if (transcoderPool.contains(_transcoder)) { resignTranscoder(_transcoder); } // Decrease bonded stake - del.bondedAmount = del.bondedAmount.sub(penalty); + del.bondedAmount = bondedAmount_.sub(penalty); + uint256 delegateAddress_ = del.delegateAddress ; - 415: delegators[del.delegateAddress].delegatedAmount = delegators[del.delegateAddress].delegatedAmount.sub( + 415: delegators[delegateAddress_ ].delegatedAmount = delegators[delegateAddress_].delegatedAmount.sub( 416: penalty 417: ); + uint256 cumulativeRewards_ = t.cumulativeRewards; - t.activeCumulativeRewards = t.cumulativeRewards; + t.activeCumulativeRewards = cumulativeRewards_ ; uint256 transcoderCommissionRewards = MathUtils.percOf(_rewards, earningsPool.transcoderRewardCut); uint256 delegatorsRewards = _rewards.sub(transcoderCommissionRewards); // Calculate the rewards earned by the transcoder's earned rewards uint256 transcoderRewardStakeRewards = PreciseMathUtils.percOf( delegatorsRewards, t.activeCumulativeRewards, earningsPool.totalStake ); // Track rewards earned by the transcoder based on its earned rewards and rewardCut - t.cumulativeRewards = t.cumulativeRewards.add(transcoderRewardStakeRewards).add(transcoderCommissionRewards); + t.cumulativeRewards = cumulativeRewards_ .add(transcoderRewardStakeRewards).add(transcoderCommissionRewards); + address delegateAddress_ = del.delegateAddress ; // Only will have earnings to claim if you have a delegate // If not delegated, skip the earnings claim process - if (del.delegateAddress != address(0)) { + if (delegateAddress_ != address(0)) { (currentBondedAmount, currentFees) = pendingStakeAndFees(_delegator, _endRound); // Check whether the endEarningsPool is initialised // If it is not initialised set it's cumulative factors so that they can be used when a delegator // next claims earnings as the start cumulative factors (see delegatorCumulativeStakeAndFees()) - Transcoder storage t = transcoders[del.delegateAddress]; + Transcoder storage t = transcoders[delegateAddress_ ]; EarningsPool.Data storage endEarningsPool = t.earningsPoolPerRound[_endRound]; if (endEarningsPool.cumulativeRewardFactor == 0) { uint256 lastRewardRound = t.lastRewardRound; if (lastRewardRound < _endRound) { endEarningsPool.cumulativeRewardFactor = cumulativeFactorsPool(t, lastRewardRound) .cumulativeRewardFactor; } } if (endEarningsPool.cumulativeFeeFactor == 0) { uint256 lastFeeRound = t.lastFeeRound; if (lastFeeRound < _endRound) { endEarningsPool.cumulativeFeeFactor = cumulativeFactorsPool(t, lastFeeRound).cumulativeFeeFactor; } } - if (del.delegateAddress == _delegator) { + if (delegateAddress_ == _delegator) { t.cumulativeFees = 0; t.cumulativeRewards = 0; // activeCumulativeRewards is not cleared here because the next reward() call will set it to cumulativeRewards } } emit EarningsClaimed( - del.delegateAddress, + delegateAddress_ , _delegator, currentBondedAmount.sub(del.bondedAmount), currentFees.sub(del.fees), startRound, _endRound ); + address delegateAddress_ = del.delegateAddress ; - 1582: increaseTotalStake(del.delegateAddress, amount, _newPosPrev, _newPosNext); + 1582: increaseTotalStake(delegateAddress_, amount, _newPosPrev, _newPosNext); - emit Rebond(del.delegateAddress, _delegator, _unbondingLockId, amount); + emit Rebond(delegateAddress_ , _delegator, _unbondingLockId, amount);
bond.delegateAddress
, bond.lastClaimRound
, bond.bondedAmount
should be cached : Saves 300 GAS
, 3 SLOD
FILE: Breadcrumbs2023-08-livepeer/contracts/bonding/BondingVotes.sol + address delegateAddress_ = bond.delegateAddress ; + uint256 lastClaimRound_ = bond.lastClaimRound ; + uint256 bondedAmount_ = bond.bondedAmount ; 464: EarningsPool.Data memory startPool = getTranscoderEarningsPoolForRound( - bond.delegateAddress, + delegateAddress_, - bond.lastClaimRound + delegateAddress_ ); (uint256 rewardRound, EarningsPool.Data memory endPool) = getLastTranscoderRewardsEarningsPool( - bond.delegateAddress, + delegateAddress_, _round ); - if (rewardRound < bond.lastClaimRound) { + if (rewardRound < lastClaimRound_ ) { // If the transcoder hasn't called reward() since the last time the delegator claimed earnings, there wil be // no rewards to add to the delegator's stake so we just return the originally bonded amount. - return bond.bondedAmount; + return bondedAmount_; } (uint256 stakeWithRewards, ) = EarningsPoolLIP36.delegatorCumulativeStakeAndFees( startPool, endPool, - bond.bondedAmount, + bondedAmount_, 0 );
Saves 20-40 Gas
FILE: Breadcrumbs2023-08-livepeer/contracts/bonding/BondingManager.sol - 355: uint256 treasuryRewards = MathUtils.percOf(rewards, treasuryRewardCutRate); - 356: rewards = rewards.sub(treasuryRewards); + 356: rewards = rewards.sub(MathUtils.percOf(rewards, treasuryRewardCutRate)); - 358: uint256 transcoderCommissionRewards = MathUtils.percOf(rewards, earningsPool.transcoderRewardCut); - 359: uint256 delegatorsRewards = rewards.sub(transcoderCommissionRewards); + 359: uint256 delegatorsRewards = rewards.sub(MathUtils.percOf(rewards, earningsPool.transcoderRewardCut)); - uint256 transcoderCommissionFees = _fees.sub(delegatorsFees); // Calculate the fees earned by the transcoder's earned rewards uint256 transcoderRewardStakeFees = PreciseMathUtils.percOf( delegatorsFees, activeCumulativeRewards, totalStake ); // Track fees earned by the transcoder based on its earned rewards and feeShare - t.cumulativeFees = t.cumulativeFees.add(transcoderRewardStakeFees).add(transcoderCommissionFees); + t.cumulativeFees = t.cumulativeFees.add(transcoderRewardStakeFees).add(_fees.sub(delegatorsFees)); - address oldDelDelegate = oldDel.delegateAddress; unbondWithHint(_amount, _oldDelegateNewPosPrev, _oldDelegateNewPosNext); uint256 oldDelUnbondingLockId = oldDel.nextUnbondingLockId.sub(1); uint256 withdrawRound = oldDel.unbondingLocks[oldDelUnbondingLockId].withdrawRound; // Burn lock for current owner delete oldDel.unbondingLocks[oldDelUnbondingLockId]; // Create lock for new owner uint256 newDelUnbondingLockId = newDel.nextUnbondingLockId; newDel.unbondingLocks[newDelUnbondingLockId] = UnbondingLock({ amount: _amount, withdrawRound: withdrawRound }); newDel.nextUnbondingLockId = newDel.nextUnbondingLockId.add(1); emit TransferBond(msg.sender, _delegator, oldDelUnbondingLockId, newDelUnbondingLockId, _amount); // Claim earnings for receiver before processing unbonding lock uint256 currentRound = roundsManager().currentRound(); uint256 lastClaimRound = newDel.lastClaimRound; if (lastClaimRound < currentRound) { updateDelegatorWithEarnings(_delegator, currentRound, lastClaimRound); } // Rebond lock for new owner if (newDel.delegateAddress == address(0) && newDel.bondedAmount == 0) { // Requirements for caller // Does not trigger self-delegation require(oldDelDelegate != _delegator, "INVALID_DELEGATOR"); - newDel.delegateAddress = oldDelDelegate; + newDel.delegateAddress = oldDel.delegateAddress; } - 758: uint256 currentRound = roundsManager().currentRound(); - 759: uint256 withdrawRound = currentRound.add(unbondingPeriod); + 759: uint256 withdrawRound = roundsManager().currentRound().add(unbondingPeriod); - 872: uint256 treasuryBalance = livepeerToken().balanceOf(treasury()); - 873: if (treasuryBalance >= treasuryBalanceCeiling && nextRoundTreasuryRewardCutRate > 0) { + 873: if (livepeerToken().balanceOf(treasury()) >= treasuryBalanceCeiling && nextRoundTreasuryRewardCutRate > 0) { - 912: uint256 endRound = roundsManager().currentRound(); - 913: (uint256 stake, ) = pendingStakeAndFees(_delegator, endRound); + 913: (uint256 stake, ) = pendingStakeAndFees(_delegator, roundsManager().currentRound()); - 927: uint256 endRound = roundsManager().currentRound(); - 928: (, uint256 fees) = pendingStakeAndFees(_delegator, endRound); + 928: (, uint256 fees) = pendingStakeAndFees(_delegator, roundsManager().currentRound()); - 1271: address delegateAddr = del.delegateAddress; - 1272: bool isTranscoder = _delegator == delegateAddr; + 1272: bool isTranscoder = _delegator == del.delegateAddress;
https://github.com/code-423n4/2023-08-livepeer/blob/main/contracts/bonding/BondingManager.sol
#0 - c4-judge
2023-09-21T10:46:17Z
HickupHH3 marked the issue as grade-b
#1 - sathishpic22
2023-09-22T17:19:13Z
Hi @HickupHH3
Thank you for your evaluation, and I appreciate your assessment. Nonetheless, I firmly believe that my report contributes to greater gas savings compared to other grade A reports
While my report may contain fewer findings in terms of quantity, the impact of these findings is substantial. I hold a strong conviction that my report is the most effective in conserving gas when compared to any other reports
[[G-1] Minimizing Storage Overhead by Avoiding Unnecessary Boolean Variables - Alone saves 18000 Gas
. Absolutely, this fact holds true and is not found in bot race; it's also within the scope
[G-2] Using memory can be more gas-efficient when calling the same variable for the second time compared to using storage - 4200 Gas
[[G-3] Structs can be packed into fewer storage slots - Saves 4000 Gas
[G-4] IF’s/require() statements that check input arguments should be at the top of the function - This will save around 4000 Gas
Refactoring these findings could potentially prevent the creation of four storage variables in case of any failures that occur after variable creations
[G-5] State variables that are used multiple times in a function should be cached in stack variables - Saves 1200 Gas
, 12 SLOD
[G-6] Don't cache state variables or functions only used once - Saves 130 Gas
Indeed, the quantity of my reports is significantly lower when compared to other reports. However, it's important to note that the count alone does not determine their value
I reviewed several grade A reports, and I observed that they primarily contained suggestions, such as identifying redundancies and checking for unnecessary items - https://github.com/code-423n4/2023-08-livepeer-findings/issues/26
I respectfully reevaluate my gas reports. If I am mistaken, please kindly correct me. Thank you for providing me with this opportunity to express my perspective
Thank you
#2 - sathishpic22
2023-09-22T17:25:25Z
Also i wrote report as per C4 standard. I mention affected instance and related links, Gas Saved for every instance , and mitigation steps .
#3 - c3phas
2023-09-22T18:40:41Z
G-2: The gas saved doesn't seem accurate: average before: 110093, after 110068 G-3 : Structs can be packed- Not sure if I'm missing something but there are no bounds to what the value of lastClaimRound can be, why would uint96 be safe
Maybe a word of advice to get accurate gas saved, whenever the function is covered by the tests it would be great to show the gas benchmarks before the changes and after. This would help us get more accurate results for something like saving an SLOAD, it's not exactly 100 since we might be creating a local variable :)
#4 - sathishpic22
2023-09-22T18:52:41Z
Yes about G2 my gas mentions may be wrong . Usually when ever we use storage instead of memory this will save 4200 gas, yes the gas i mentioned may be wrong but the instance is accurate and no one find that. About G3 lastClaimRound variable only stores the timestamp values. Unit96 range more than enough to store timestamp values. Uint96 only overflows only after some hundred years. Many protocols using less range than uint96 for storing timestamps. Also this instance also not reported by anyone.
On Sat, Sep 23, 2023, 12:10 AM c3phas @.***> wrote:
G-2: The gas saved doesn't seem accurate: average before: 110093, after 110068 G-3 : Structs can be packed- Not sure if I'm missing something but there are no bounds to what the value of lastClaimRound can be, why would uint96 be safe
— Reply to this email directly, view it on GitHub https://github.com/code-423n4/2023-08-livepeer-findings/issues/172#issuecomment-1731888184, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOA6PHN6RLMHX3UFMEWZ4DLX3XLTHANCNFSM6AAAAAA4NDZYKY . You are receiving this because you commented.Message ID: @.***>
#5 - c3phas
2023-09-22T19:00:09Z
Totally get the idea of timestamps, my question was are this actually timestamps though
#6 - sathishpic22
2023-09-22T19:12:21Z
Yes , you can check the struct comments .
On Sat, Sep 23, 2023, 12:30 AM c3phas @.***> wrote:
Totally get the idea of timestamps, my question was are this actually timestamps though
— Reply to this email directly, view it on GitHub https://github.com/code-423n4/2023-08-livepeer-findings/issues/172#issuecomment-1731908870, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOA6PHMPYUZIX5LYMJ4IVITX3XN4LANCNFSM6AAAAAA4NDZYKY . You are receiving this because you commented.Message ID: @.***>
#7 - c3phas
2023-09-22T19:14:09Z
// The last round during which the delegator claimed its earnings
this?
#8 - c3phas
2023-09-22T19:16:20Z
I'm not sure about the timestamp one, but I do love the first finding.
#9 - sathishpic22
2023-09-22T19:18:41Z
I am very much sure about timestamp. I checked then only I decided to report that.
On Sat, Sep 23, 2023, 12:46 AM c3phas @.***> wrote:
Not sure about the timestamp one but I do love the first finding, maybe worth an upgrade
— Reply to this email directly, view it on GitHub https://github.com/code-423n4/2023-08-livepeer-findings/issues/172#issuecomment-1731925742, or unsubscribe https://github.com/notifications/unsubscribe-auth/AOA6PHKG7SHACSZK74CNDCTX3XPY7ANCNFSM6AAAAAA4NDZYKY . You are receiving this because you commented.Message ID: @.***>
#10 - HickupHH3
2023-09-25T01:53:08Z
The timestamp issue is subjective: while it's cheaper to pack, it's less efficient to work on vs uint256
. Some tests to show the net effect would've been great.
#11 - sathishpic22
2023-09-25T05:00:47Z
Hello @HickupHH3,
Even if we exclude the G3 finding, my report contains several other potential gas-saving discoveries in comparison to some of the Grade A reports.
While I acknowledge that the G1 finding might seem weak due to its focus on bool operations, it falls within the scope of our objectives, and this particular finding alone results in a gas savings of approximately 18,000 GAS.
Furthermore, I have submitted unique findings such as G2, G1, G4, and G6, none of which have been reported by any other wardens.
Thank you
#12 - c4-judge
2023-09-25T06:41:09Z
HickupHH3 marked the issue as grade-a
🌟 Selected for report: catellatech
Also found by: 0x3b, ADM, Banditx0x, JayShreeRAM, Krace, Sathish9098
List | Head | Details |
---|---|---|
1 | Overview of Livepeer | overview of the key components and features of Livepeer |
2 | Audit approach | Process and steps i followed |
3 | Learnings | Learnings from this protocol |
4 | Possible Systemic Risks | The possible systemic risks based on my analysis |
6 | Centralization risks | Concerns associated with centralized systems |
5 | Code Commentary and Risks | Suggestions for existing code base and risks based on codebase |
7 | Gas Optimizations | Details about my gas optimizations findings and gas savings |
9 | Non-functional aspects | General suggestions |
10 | Time spent on analysis | The Over all time spend for this reports |
Decentralized
video infrastructure protocol
powering video inweb3's
leadingsocial
andmedia
applications
Livepeer
is a protocol for developers who want to add live
or on-demand video
to their project. It aims to increase the reliability
of video streaming while reducing costs
associated with it by up to 50x
.
To achieve this Livepeer
is building p2p
infrastructure that interacts through a marketplace
secured by the Ethereum blockchain
.
Who want to build applications
that include live
or on demand video
can use Livepeer
to power their video functionality
Who want to stream video
, gaming
, coding
, entertainment
, educational courses
, and other types of content can use applications built on Livepeer
to do so.
Such as Twitch who have large audiences
and high streaming bills
or infrastructure costs
can use Livepeer
to reduce costs or infrastructure overhead
.
When Bob
opens the app and taps Record
at the start of each game, the app sends the live video
along with broadcaster fees
into the Livepeer network
. Livepeer
then transcodes
the video
into all the formats
and bitrates
that his viewers
can consume.
In Livepeer
, anyone can join the networ
k and become what's known as an orchestrator
by running software that allows you to contribute
your computer's resources (CPU, GPU, and bandwidth)
in service of transcoding
and distributing video
for paying broadcasters
and developers
For doing so, you earn fees
in the form of a cryptocurrency
like ETH
or a stablecoin
pegged to the US dollar
like DAI
In order to earn the right to do this type of work on the network
, you must first earn
or acquire Livepeer Token
, also known as LPT
Delegators
are Livepeer tokenholders
who participate in the networ
k by staking
their tokens towards orchestrators
who they believe are doing good and honest work.The purpose of the Livepeer token (LPT)
is to coordinate
, bootstrap
, and incentivize participants
to make sure the Livepeer network
is as cheap
, effective
, secure
, reliable
and useful as possible
.
When a broadcaster
pays fees into the network
, both orchestrators
and Delegators
earn a portion of those fees
as a reward for ensuring a high-quality
and secure network
.
In addition to earning fees
, Livepeer
mints new token
over time, much like Bitcoin
and Ethereum
block rewards, which are split
amongst Delegators
and orchestrators
in proportion to their total stake
relative to others in the network.
I followed below steps while analyzing and auditing the code base.
100%
Test Suitupgrade
of an existing system
- checkpointing support for an existing staking system
, abstract it as an IERC5805
token and set up an OpenZeppelin Governor
Timelock
and OZ
L2
but no bridges
involvedAnalyzed the over all codebase one iterations very fast
Study of documentation to understand each contract purposes, its functionality, how it is connected with other contracts, etc.
Then i read old audits and already known findings. Then go through the bot races findings
Then setup my testing environment things. Used yarn commands to test the protocol
depth
way I started understanding line by line code and took the necessary notes
to ask some questions to sponsors
.The first stage of the audit
During the initial stage
of the audit for Livepeer Onchain Treasury Upgrade
, the primary focus was on analyzing GAS
usage and Quality assurance (QA)
aspects. This phase of the audit aimed to ensure the efficiency of gas consumption and verify the robustness of the platform.
The second stage of the audit
In the second stage
of the audit for Livepeer Onchain Treasury Upgrade
, the focus shifted towards understanding the protocol
usage in more detail. This involved identifying and analyzing the important contracts
and functions within the system. By examining these key components, the audit aimed to gain a comprehensive understanding of the protocol's functionality
and potential risks
.
The third stage of the audit
During the third stage
of the audit for Livepeer Onchain Treasury Upgrade
, the focus was on thoroughly examining and marking any doubtful or vulnerable areas within the protocol. This stage involved conducting comprehensive vulnerability assessments
and identifying potential weaknesses
in the system. Found 60-70
vulnerable
and weakness
code parts all marked with @audit tags
.
The fourth stage of the audit
During the fourth stage
of the audit for Livepeer Onchain Treasury Upgrade
, a comprehensive analysis
and testing
of the previously identified doubtful
and vulnerable
areas were conducted. This stage involved diving deeper into these areas, performing in-depth examinations
, and subjecting them to rigorous testing
, including fuzzing
with various inputs. Finally concluded findings after all research's
and tests
. Then i reported C4
with proper formats
Livepeer
is a blockchain-based protocol
tailored for developers
seeking to integrate live
or on-demand video
streaming into their applications. It leverages
a decentralized marketplac
e secured by the Ethereum blockchain
to enhance the reliability
of video streaming
while dramatically
reducing associated costs
, potentially up to 50 times
. Within this ecosystem
, Orchestrators
play a pivotal
role by contributing computing resources
(CPU, GPU, and bandwidth) to transcode
and distribute video content for paying developers
and broadcasters
. They receive compensation in the form of cryptocurrenc
y, such as ETH
or stablecoins pegged to the US dollar (e.g., DAI). Additionally, the Livepeer token (LPT)
serves as a critical incentive mechanism
, allowing token holders to perform transcoding work on the network in exchange for fees
. Delegators
, another crucial participant group
, secure the network
by staking their LPT tokens
with Orchestrators
they trust. This system ensures high-quality
and secure video streaming experiences. Orchestrators
and Delegators
, through their active participation, also benefit
from token rewards
and network ownership growth
. The protocol operates in rounds
, with each round lasting approximately 22.4 hours
, and the inflation rate
automatically adjusts based on token staking
, further incentivizing participation to maintain network health
.
High congestio
n could disrupt
Livepeer's video transcoding
and streaming operations
, potentially resulting in reduced quality of service
and higher costs
Inconsistent Orchestrator
performance can lead to service interruptions
and affect the overall quality and reliability
of Livepeer's video streaming capabilities
Significant fluctuations in LPT'
s value can influence
the economic incentives for network participants
, potentially affecting the stability
and security of the Livepeer ecosystem
Over-centralization
, where a small number of Orchestrators
dominate the network, may compromise Livepeer's intended decentralization and security
Contracts with privileged functions that rely on a single owner
or admin
for control can introduce several risks
FILE: 2023-08-livepeer/contracts/bonding/BondingManager.sol 155: function setUnbondingPeriod(uint64 _unbondingPeriod) external onlyControllerOwner { 167: function setTreasuryRewardCutRate(uint256 _cutRate) external onlyControllerOwner { 176: function setTreasuryBalanceCeiling(uint256 _ceiling) external onlyControllerOwner { 186: function setNumActiveTranscoders(uint256 _numActiveTranscoders) external onlyControllerOwner {
setUnbondingPeriod ()
The ability to modify the unbonding period
is concentrated in the hands of the controller owner
. This means that participants
must trust this single entity
to make responsible decisions regarding the unbonding period
. If the controller owner were to act maliciously
or make unfavorable changes
, it could disrupt the protocol and potentially harm users
.
setTreasuryRewardCutRate()
Centralizing control over this parameter means that the controller owner
has significant influence over the protocol's economic incentives
. Users have no direct say in determining the cut rate
, which could affect their rewards
. Trust is placed in the controller owner
to act in the best interests of the network.
setTreasuryBalanceCeiling()
Centralized control over the treasury
balance ceiling means that decisions regarding the maximum amount
of funds the protocol can hold are made solely by the controller owner
. If this limit is set too high
or too low
, it can have significant financial implications
for the protocol
setNumActiveTranscoders()
The number of active transcoders
directly affects the network's performance
and decentralization
. Centralized control over this parameter means that the controller owner
can influence the protocol's centralization
level without the need for community consensus
The Livepeer protocol
demonstrates several gas optimization opportunities that could enhance
the efficiency
of its smart contracts and reduce transaction costs
. First, the use of the memory
keyword instead of storage
when accessing variables
for the second time
has been identified as a cost-saving measure.This optimization has been observed in the context of lock.withdrawRound
and similar variables within the protocol
. Furthermore, optimizing struct
packing by using uint96
instead of uint256
for variables like lastClaimRound
in certain data structures offers additional savings
. In addition, gas-efficient programming practices recommend placing require
statements that check input arguments at the beginning of functions
, allowing for early and less costly failure
s. Lastly, caching state
variables used multiple times within a function in stack variables
, such as withdrawRound
, bondedAmount
, and delegateAddress
, can lead to significant savings in terms of both GAS
and SLOD
operations
constructor
, consider adding input validation
to ensure that the provided _controller
address is not zero (address(0))
. This will prevent accidental deployment
with an invalid controller address
setUnbondingPeriod
, setTreasuryBalanceCeiling
, setNumActiveTranscoders
, setTreasuryRewardCutRate
,setTreasuryRewardCutRate
and other similar functions, consider adding validation checks
to ensure that the input parameters are within acceptable ranges
. For example, ensure that _unbondingPeriod
> 0
, > MIN
and < MAX
MAX_FUTURE_ROUND
, consider adding a brief explanation
of why this value is set to 2**256 - 1
. This can help other developers
understand the purpose of this constant
MAX_FUTURE_ROUND
constantchecks-effects-interactions
pattern in functions that interact with external contracts
. Ensure that state changes
occur after external calls to prevent reentrancy vulnerabilities
TranscoderUpdate
event, you could include the previous values of rewardCut
and feeShare
._cutRate
in the setTreasuryRewardCutRate
function to something like _newCutRate
for claritynaming convention
for variables
and functions
. For example, stick with camelCase
also private varibales should start with _
as per solidity naming standardbondForWithHint
and unbondWithHint
functions, consider consolidating the logic
for changing the delegate
and updating the bonded amount
into a separate internal function to enhance readability
and reduce code duplication
events
are emitted consistently when state changes
occur. This allows external applications
to listen for and react to these eventsfallback
and receive
functions for accidential ether send to contract to avoid unexpected behavioraccess control
mechanisms to restrict certain functions to authorized users or roles. OpenZeppelin's Access Control
library is a useful tool for this purposesetUnbondingPeriod
, setTreasuryRewardCutRate
, setTreasuryBalanceCeiling
, and setNumActiveTranscoders
. If the owner's address
is compromised
or acts maliciously
, it could lead to inappropriate
changes in protocol parameters
transcoders' states
in the reward function
. While the loop appears bounded by the number of transcoders
, exceeding gas limits can lead to function execution failures
Large stakeholders
or transcoders
with significant stake
can potentially manipulate the protocol's rewards
or governance decisions
bondForWithHint
and transferBond
, it's important to consider the orde
r in which transactions are processed
, especially when it involves changing delegate addresses
or transcoder positions
in the pool. Transaction ordering can impact the final state of the contract, potentially leading to unintended behavior
users
or contracts
interact with your contract
concurrently, race conditions
might arise.if
statements for validation and reverting, consider using the require
statement. This makes the code more explicit and easier to read. _round
is greater than or equal to clock() + 1
. You can consolidate this check into a helper function
to reduce code repetition
and make it more readable
bondingManager
and roundsManager
functions, you can use a bytes32 constant
to represent the contract name for getContract
. This can make the code more readable
and consistent
magic numbers
with named constants
to improve code readability
and maintainability
clock()
function relies on block timestamps
to determine the current round
. Any manipulation
of block timestamps
can affect the accuracy of round calculations
.complex calculations
and storage operations, such as delegatorCumulativeStakeAt
, may be at risk of exceeding the gas limit
for Ethereum transactions
getBondingCheckpointAt
and delegatorCumulativeStakeAt
functions, which retrieve and manipulate bonding
checkpoint data. If these functions do not accurately reflect the state of the system
, data consistenc
y issues may arisesorting operations
in functions like getTotalActiveStakeAt
and getBondingCheckpointAt
. Sorting algorithm used is inefficient
, it can lead to higher gas
costs when querying historical data
. Consider optimizing sorting algorithms
GovernorCountingOverridable.sol
Initializable
, GovernorUpgradeable
, and IERC5805Upgradeable. It also imports contracts and libraries specific to the project
comprehensive comments
and documentation throughout your code. Explain the purpose of functions
, variables
, and complex logic
. Good documentation makes it easier for other developers (and your future self) to understand and maintain the codemodifiers
to enhance code readability
and maintainability
15 Hours
15 hours
#0 - c4-judge
2023-09-22T03:14:12Z
HickupHH3 marked the issue as grade-c
#1 - sathishpic22
2023-09-22T17:49:26Z
Hi @HickupHH3
I have a strong conviction that my analysis reports warrant reconsideration. I have consistently adhered to a methodology that prioritizes code-based analysis over architectural considerations. This approach has been a hallmark of my reporting style.
My reports encompass a range of potential suggestions, focusing on critical areas such as:
Systemic Risks
: Identifying vulnerabilities that could have far-reaching implications.
Centralization Risks
: Assessing the risks associated with any centralization elements.
Gas Optimizations
: Exploring opportunities for improving gas efficiency.
Code Commentary
: This aspect of my reports revolves around offering recommendations for enhancing the overall codebase. It focuses on suggesting improvements and optimizations that can elevate the quality, efficiency, and maintainability of the code.
Risk Analysis based on codebase
: In this context, I delve into potential risks inherent in the codebase. I identify and articulate these risks with a particular emphasis on those that are pertinent to key components or contracts within the system. By doing so, I aim to provide a comprehensive view of the risk landscape, enabling better-informed decision-making and proactive risk mitigation strategies.
It's worth noting that while many wardens
concentrate on document-based analysis
, I have always remained committed to codebase-centered analysis
. I firmly believe that this approach enhances the depth
and accuracy
of my reports.
Given this, I am confident that my reports merit a higher grade
than the current grade C
.
If anything is incorrect in my reports, please provide suggestions for enhancing my report writing for upcoming contests. Thank you for providing me with this opportunity to express my perspective .
#2 - c4-judge
2023-09-25T01:54:24Z
HickupHH3 marked the issue as grade-a