compound-finance / comet Goto Github PK
View Code? Open in Web Editor NEWAn efficient money market protocol for Ethereum and compatible chains (aka Compound III, Compound v3).
License: Other
An efficient money market protocol for Ethereum and compatible chains (aka Compound III, Compound v3).
License: Other
Non-explicit imports are used throughout all protocol contracts, which reduces code readability and could lead to conflicts between names defined locally and the ones imported.
The inheritance chains within the protocol are long, and for this, the codebase would benefit from refactoring specifically which definitions are being imported in each contract.
On the principle that clearer code is better code, consider using named import syntax (import {A, B, C} from "X"
) to explicitly declare which contracts are being imported.
Spider currently expects addresses in relations.ts
to be all lower case. We should make it case-insensitive.
e.g.
'0xDbcd5BafBAA8c1B326f14EC0c8B125DB57A5cC4c': {
artifact: 'contracts/ERC20.sol:ERC20',
},
will not work right now
The deployAndUpgrade
function of the CometProxyAdmin
is restricted in access to be called exclusively by the governance. It deploys a new Comet
instance through the Configurator
and upgrades the implementation address in the proxy.
However, it doesn't call the initializeStorage
function of the Comet
contract through the proxy, leaving the new implementation not initialized. The function doesn't take any input parameter and it is meant to be called only once without the need to call it again on new implementation upgrades.
Whether the first initialization is performed on a separate transaction or if in the future it will be possible to re-initialize the Comet
instance with some input values, any user can front-run any governance attempt to initialize the new deployed Comet
.
Consider taking into account that deployAndUpgrade
and initializeStorage
should be done in one unique transaction to avoid the front-run scenario, especially if input parameters or re-initializations are meant to happen in future developments.
Once the protocol is deployed, the Comet
and Bulker
contracts will be two important pieces of the system. The Comet
is the main protocol contract while the Bulker
is a useful tool to execute multiple protocol calls into one single transaction.
For different purposes, both the Comet
and the Bulker
support deposits of ETH into the contract.
In the case of the Comet
contract, the delegation toward the CometExt
is in the fallback function, which is declared as payable
, but there are no parts of both contracts that use them. Moreover, there is no direct way to withdraw any ETH balance present in the contract.
In the case of the Bulker
, the contract has a receive
payable function to accept ETH which is needed to be used in the ACTION_WITHDRAW_ETH
according to the docstrings.
In this case, any ETH sent through the receive
function will be again lost in the contract, with no possibility to upgrade to a new contract as the Comet
can. The reason is that the ACTION_WITHDRAW_ETH
is implemented into the withdrawEthTo
function. This function unwraps WETH by sending tokens to the WETH contract and receiving back ETH in the same amount in the Bulker
contract which are then sent to the user. In this case, the contract's ETH balance will be untouched as only ETH coming from the unwrap will be used.
Lastly, a separate reference must be done also on ERC20 tokens. The Comet
contract has the approveThis
function which is enough to let a manager
move any ERC20 fund that might get lost in Comet
balance. However, this is not the case for the Bulker
contract, where also ERC20 tokens might get lost.
Consider establishing mechanisms to avoid such scenarios, as it results in a direct loss of user funds.
The withdrawReserves
uses the getReserves()
function which checks the present value of the totalSupply
and totalBorrow
.
If the accrue
is not called beforehand, the present values might not be accurate and the reserve amount that is withdrawn will also not be accurate.
Since this is an onlyGovernance function this is not a big issue, but if the governance forgets to call accrue
before it might cause a problem.
Each Comet
contract implementation that the protocol might decide to create is meant to be deployed in the Configurator
contract.
This contract is an intermediate logic to bootstrap the entire protocol configuration and then apply it to a newly deployed Comet
contract.
To do this, the Configurator
makes use of a CometFactory
contract which has a clone
function whose sole role is to create a new Comet
contract and return its address.
Whenever all protocol parameters are set, the deploy
function in the Configurator
contract is called. This function calls the clone
function in the factory and emits an event to signal that a new Comet
is ready with the configuration set so far.
The deploy
and clone
functions are both public callable functions, so anyone can, at any moment, generate the Comet
instance either in the factory, passing arbitrary configurations, or either through the Configurator
with the configuration set until that moment.
Is not clear why those functions are public as there is no benefit for users to call them. Moreover, leaving open the door for anyone to deploy instances of Comet
with arbitrary configuration or emitting arbitrary events through the Configurator
is a concern to take into account as it might be misused by malicious actors trying to create pishing-like attacks.
Consider restricting access to those functions and let them be called only by the governor or consider properly documenting such a design decision, raising awareness over the way it might be misused.
There is a problem in updateAssetIn
with the mask size that is used to update the userBasic[account].assetsIn
.
The update is as following:
userBasic[account].assetsIn |= (uint8(1) << assetInfo.offset);
As we can see uint(8)
is used to create the matching mask by shifting it by assetInfo.offset
to the left.
The problem is that there are more than 8 assets in the system so an 8 bits mask is not enough.
For example, let's assume an asset with assetInfo.offset = 10
.
uint8(1) << 10 = 0
so the required mask won't be used and a 0 mask will be used instead.
As a result userBasic[account].assetsIn
won't get updated correcly.
The same problem happens in isInAsset
when we use an 8 bits mask to get inforamtion from userBasic[account].assetsIn
:
return (assetsIn & (uint8(1) << assetOffset) != 0);
It's possible to borrow less than the baseBorrowMin
by borrowing more than the baseBorrowMin
and then repaying a portion of it.
For example, assuming baseBorrowMin = 100
. A user can borrow 101 and right after repay 2 which leaves him with 99 < baseBorrowMin
.
userBasic
is accessed twice in isBorrowCollateralized()
, you can save the struct in a local variable instead of accessing a storage variable twice.totals.lastAccrualTime = now_;
in accrue()
can be inside the if (timeElapsed > 0)
to save gasuint128 uint128 uint64 unit64 uint64 uin64
is better than uint128 uint64 uint128 uint64 uint64 uint64
seizeAmount > 0
because of isInAssest()
in absorbInternal()
buyCollateral()
, the second require can be reduced to reserves < int(targetReserves)
(or save targetReserves
as int
in the first place).accrue()
can be called in absorb()
instead of absorbInternal()
in every loop iterationIn the Comet
contract, the accrueAccount
function is publicly callable and it accrues interest rates and updates tracking indexes on the account specified as the input parameter.
Specifically, the tracking indexes are updated in the updateBasePrincipal
function where if newPrincipal >= 0
then the baseTrackingIndex
is set to a meaningful value.
So anyone is able to set this value for accounts that do not even exist on the protocol. Even if it is not a security issue on its own, consider restricting this function to users that have a principal strictly greater than zero.
While compiling the codebase contracts. The compiler raises some warnings. Specifically:
totalSupply
and totalBorrow
variables of the getUtilization
and of the getReserves
functions have the same name as the totalSupply
and totalBorrow
public functions.withdrawAndBorrowAmount
function visibility can be restricted to pure
.WETH9
contract is missing the SPDX license identifier.Consider resolving all compiler warnings.
It's possible to borrow less than the baseBorrowMin
by borrowing more than the baseBorrowMin
and then transfer some base tokens from another account to this, reducing the borrow position.
For example, assuming baseBorrowMin = 100
. A user can borrow 101 and right after transfer 2 base tokens from another account to this, which leaves him with 99 < baseBorrowMin
.
To favor explicitness and readability, several parts of the contracts may benefit from better naming. Here are some examples:
CometProxyAdmin
suggests that it is only the admin of the Comet proxy, but in reality, it will also have the same role on the Configurator
proxy. Choose another name to avoid confusion.TransparentUpgradeableConfiguratorProxy
can be called ConfiguratorProxy
. There is no need to use the inherited contract name as a prefix.CometMath
, change InvalidUInt
to InvalidUint
and toUInt
to toUint
.rescale
and descale
have different names but the same value, consider using one variable name scale
.Hi there, I'm a getting numerous errors in the following scripts: OnChainLiquidator.sol, LiquidateUnderWaterBorrowers.ts, and index.ts.
The main issues are VS Code can't find specific modules, especially "from build/types" in the OnChainLiquidator smart contract, the errors are multiple "Expected indentation of 4 spaces but found 32 [indent]" , multiple "line 33:68 extraneous input ')' expecting {';', '='} and " line 63:29 extraneous input 'balancerVault' expecting {';', '='}". If anyone can help me with these and re-format the scripts and fix all of the issues, then that would be very helpful. Thank you.
comet/src/deploy/Development.ts
Line 71 in ad6a420
If use ETH_PK
in .env, the pauseGuardianSigner will be null
The TransparentUpgradeableConfiguratorProxy
overrides the _beforeCallback
function.
Concretely, the _beforeCallback
function is implemented in the TransparentUpgradeableProxy
contract to avoid the admin
of the contract to call the implementation logic directly.
This feature is removed in TransparentUpgradeableConfiguratorProxy
contract, where the admin
is allowed now to call directly the implementation by triggering the fallback function as a normal user would do. This is needed because of the deployAndUpgrade
function of the CometProxyAdmin
which sees the admin
calling the Configurator.deploy
function.
This is not a security issue on its own but it opens the door for potential clashes to happen. If one function is added either on the proxy or the logic contract, this can clash with any of the other contract functions. At this point, the admin will stop to be able to call the implementation contract (users will still be directed toward the implementation because of the ifAdmin
modifier).
This article specifies how crafting clashes may not be too hard of a computational task. This article showcases how a new function in the proxy might actually enable clashes.
To avoid any unwanted behaviors, consider ensuring that the upgrade mechanism for Configurator
always checks for potential clashes between the logic implementation and the proxy, especially if new functions are added.
The function principalValue()
is identical to the function presentValue()
, shouldn't the principalValue()
function call the functions principalValueSupply()
and principalValueBorrow()
instead of presentValueSupply()
and presentValueBorrow()
?
There are some places in the code base that might benefit from some sanity checks on the input provided:
transferGovernor
and setGovernor
functions of the Configurator
are not checking the address to be non-zero.CometRewards
constructor is missing the same check over the address parameter.Comet
contract, the priceFeed
is set but is not checked to retrieve a valid price.withdrawAndBorrowAmount
and repayAndSupplyAmount
functions assume certain values over the newPrincipal
but those should be required instead.To reduce possible errors and make the code more rodust, consider adding sanity checks where needed.
I think that in the function absorbInternal()
the accrue()
should be called before the isLiquidatble()
check and not after:
require(isLiquidatable(account), "account is not underwater");
TotalsBasic memory totals = totalsBasic;
totals = accrue(totals);
That's because the accrue
function updates the indexes which need to be updated for the isLiquidatble()
check.
For example it is possible that a isLiquidatable
check will return false
for a certain user but after the accrue()
it'll reutrn true
, causing the absorbInternal
call to return without liquidating when actually it shouldn't.
what do you think?
For accounts that use the protocol, the protocol provides a built-in system for tracking rewards. Comet
contracts keep account of all accrued incentives for suppliers and borrowers of the base token, and users can claim them on CometRewards
contract. It should be emphasized that all rewards from all the Comet
s from the same chain are claimed in the same CometRewards
contract.
The latter has a governor
variable defined which is the role that can set the reward settings and withdraw rewards from the contract. Since the contract does not have an upgradeability mechanism, it is inconvenient to define this variable as immutable
. If the governor
needs to be changed, a new contract must be deployed and the old contract's state must be migrated to the new one.
Consider adding this variable to the contract storage and specifying a setter function so that the governor
can be changed any time it is needed.
As pointed out by spencerperkins on Discord, Comet and CometRewards is missing some events that make off-chain indexing difficult:
TransferBase
event in CometRewardClaimed
event is missing a parameter that specifies which comet
the reward is claimed forInconsistencies in coding style were identified throughout the code base. Some examples are:
version
and name
and not in UPPER_CASE like other constant variables_reserved
to fill slots, but others do not.I
is used as a prefix, sometimes the word Interface
at the end or in some cases the contract even misses both.Taking into consideration how much value a consistent coding style adds to the project’s readability, enforcing a standard coding style with help of linter tools such as Solhint is recommended.
The COMP
token, the governance module (GovernorBravo
) and the Timelock
are the three components that make up Compound governance, allowing for the management and upgrade of the protocol.
The Timelock
contract, which is also the administrator of the Compound v2 protocol, is meant to be in charge of all instances of the Comet
protocol. Each proxy, the Configurator
implementation, the Comet
factory, the CometRewards
and the Comet
implementations are all under the governance control.
Within the Comet
implementation, there is a variable called governor
that will be set to be the Timelock
address. The contract also has a function called approveThis
that only the governor can execute and it approves anyone to transfer the base and collateral assets outside the Comet
contract. Taking into account that the possibility of a governance attack exists (Beanstalk and Curve cases), user funds could be at risk.
If the function is meant to be used for specific purposes and can't be removed, consider properly documenting this risk and establish verification measures on governance proposals to avoid misuse of this feature. To reduce the possibilities of a governance attack, be sure to always rely on a delay mechanism for proposals to be executed and eventually a pause guardian that can take action if a malicious proposal is spotted.
Scenarios:
Expectation:
The test shall change supply cap dynamically via executing bumpSupplyCaps
if needed to make the supply cap fit to the scenarios.
Actual:
The test will fail if supply cap is to low in configuration.
In the Comet
contract, during an absorbeInternal
call, the updateBasePrincipal
function is called, updating the accrued interests on the liquidated user position.
If the seizing of collateral brought the new user's balance to positive values the user will:
accrueInternal
will update interest rates and the updateBasePrincipal
will update the tracking indexes.Moreover, an underwater can decide to even liquidate himself, earning additional liquidation points.
Consider the extra value that an underwater can extract from his position to determine if this can be leveraged to create some attacks. If those are intended behaviors, consider improving the docstrings around the absorption mechanism to reflect these details.
The Comet contract has an immutable value that defines the target amount of reserves of the base token. This value is closely related to the buyCollateral function. This function cannot be called successful if the protocol reserves are greater than or equal to this target.
If targetReserves
is set to a small value, the contract could easily reach the level. The problem is that the absorptions can continue but the protocol will not be able to sell the collateral because the buyCollateral
function cannot be used and the protocol could be in a situation where it would hold assets that may lose value over time.
In the opposite case, where targetReserves
is set to a large value, the chance of reaching this level would be much lower so it could be a useless constraint.
Keeping in mind that setting this variable to a small value is more of a problem, be sure to set it to a large value. Also if the value of the target is too high to not have a useful or practical use, consider re-design the system to not make use of it.
If you set the arguments src
and dst
in the function transferBase()
to be the same, the increment of the dst
balance overwrites the decrement of the src
balance, effectively increasing the balance by the amount given.
Property violated:
sum of all userBasic.principal equals to totalSupplyBase
sum(userBasic[u].principal) == totalsBasic.totalSupplyBase
The Comet
contract delegates some feature implementations into the CometExt
contract. This contract is meant to be an actual extension of Comet
logic and implement some functions and parameters, including the version
parameter.
At the same time, Comet
is built through an upgradeable proxy pattern that requires a new Comet
version to be deployed and the proxy pointed toward the new implementation.
Doing an upgrade is convenient to upgrade the version number to a greater value and to do so the protocol must deploy also a new CometExt
implementation contract to indicate a new version.
This is because the version
parameter is declared as constant and can't be changed as a result of an upgrade mechanism without actually changing it in the contract's code.
In the worst-case scenario, an error is performed in the upgrade mechanism, the version number is not incremented and potential systems integrated with the protocol might depend on that version number to establish some other logic.
To avoid deploying a new CometExt
even if its implementation didn't change, consider moving the version parameter into the Comet
contract code.
Eventually consider keeping version
in both contracts to differentiate between them, as both contracts might require some implementation changes.
As pointed out by Jared, the deploy scripts currently redundantly declare the addresses of each asset it may use. If an asset is an asset in Comet, we should just derive it from the configuration.json
file instead.
The Configurator
contract has an initializable function that is meant to be called by the proxy. However, nothing prevents users from directly calling the initialize
function on the contract implementantion. If someone does so, the state of the implementation would be initialized to some meaningful value.
We suggest adding a constructor that sets the version to an incredibly high number so that any attempt to call the implementation at the initialize function would revert with an AlreadyInitialized
error or even a specific one to signal that initializing the implementation is prohibited.
Leaving an implementation contract not initialized is generally an insecure pattern to follow. In this case, it might lead to a situation where an attacker can take control over the implementation contract by passing himself as governor
and try to mislead users toward malicious contracts. This is not a security issue on its own but we strongly suggest avoiding such scenarios in all implementation contracts.
As a source of inspiration, here there's an example of how the scenario can be avoided in general situations.
In many parts of the codebase, repeated or similar lines of code can be seen. Here are some examples:
timeElapsed > 0
check is performed two times, when accrueInternal is called, and when internally accruedInterestIndices is called.allow
and approve
in CometExt
do the same thing.Consider reusing the same code defined in just one place or, if appropriate, removing duplicate code.
What if we develop and add such a client? I think I can develop and open-source such a client by myself.
in absorbInternal
you set the user's collateral to 0 but you don't call updateAssetsIn()
.
this means that the collateral balance of a user is 0, but the bit is still on and the function isInAsset()
will return true.
We can't find any major problems that this cause but it is an incorrect behavior of the function and also a waste of gas in all the functions that go over all the collaterals of the user
Many functions that could be in CometMath
, a utility contract that contains pure mathematical functions, are scattered throughout the codebase and min
, one of the functions within the contract, is not being used.
Consider consolidating all the helper math functions in one place and removing unused ones.
The protocol heavily relies on different sized variables, which can have also positive or negative values and different scaling factors. Thanks to this, the deployment and execution of the codebase will decrease gas costs.
Given the fact that it adds some complexity and undermines the readability of the codebase, it is of utter importance to maintain explicitness and consistency across different contracts.
Specifically, implicit castings and sizes should be avoided.
To improve the overall quality of the codebase, consider reviewing it in its entirety, changing all occurrences of uint
to uint256
and of int
to int256
. Consider also reviewing implicit castings from small to bigger sizes and always use the appropriate size for each variable.
Line 96 of the CometExt
contract has a typo "whi" and the number of decimals showed here is wrong, as it should be one zero less.
To improve correctness and readability, consider reviewing both the contracts and the documentation for typos.
Gas optimization:
withdrawCollateral
you can replace saving totalsCollateral
in a local variable since you accesse it only onceunsigned104
with unit104
in: presentValue()
, prinicipalValue()
, repayAndSupplyAmount()
, withdrawAndBorrowAmount()
and in absorbInternal()
(-oldBalance
and newBalance
>= 0)absorbInternal
, can replace
newBalance = newBalance < 0 ? int104(0) : newBalance;
if(newBalance < 0) { newBalance = 0;}
Code Size:
isBorrowCollateralized
, getBorrowLiquidity
, isLiquidatable
, getLiquidationMargin
use a lot of the same code, if you want to save code size over gas optimization you can merge themupdateBaseBalance()
you can replace: if (principal >= 0) {
uint indexDelta = totals.trackingSupplyIndex - basic.baseTrackingIndex;
basic.baseTrackingAccrued += safe64(uint104(principal) * indexDelta / baseIndexScale); // XXX decimals
} else {
uint indexDelta = totals.trackingBorrowIndex - basic.baseTrackingIndex;
basic.baseTrackingAccrued += safe64(uint104(-principal) * indexDelta / baseIndexScale); // XXX decimals
}
with:
uint indexDelta;
if (principal >= 0) {
indexDelta = totals.trackingSupplyIndex - basic.baseTrackingIndex;
} else {
indexDelta = totals.trackingBorrowIndex - basic.baseTrackingIndex;
principal = -principal;
}
basic.baseTrackingAccrued += safe64(uint104(principal) * indexDelta / baseIndexScale);
if you prefer code size over gas optimization
SupplyRate
and BorrowRate
are being calculated as following:
function getSupplyRate() public view returns (uint64) {
uint utilization = getUtilization();
uint reserveScalingFactor = utilization * (FACTOR_SCALE - reserveRate) / FACTOR_SCALE;
if (utilization <= kink) {
// (interestRateBase + interestRateSlopeLow * utilization) * utilization * (1 - reserveRate)
return safe64(mulFactor(reserveScalingFactor, (perSecondInterestRateBase + mulFactor(perSecondInterestRateSlopeLow, utilization))));
} else {
// (interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink)) * utilization * (1 - reserveRate)
return safe64(mulFactor(reserveScalingFactor, (perSecondInterestRateBase + mulFactor(perSecondInterestRateSlopeLow, kink) + mulFactor(perSecondInterestRateSlopeHigh, (utilization - kink)))));
}
}
function getBorrowRate() public view returns (uint64) {
uint utilization = getUtilization();
if (utilization <= kink) {
// interestRateBase + interestRateSlopeLow * utilization
return safe64(perSecondInterestRateBase + mulFactor(perSecondInterestRateSlopeLow, utilization));
} else {
// interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink)
return safe64(perSecondInterestRateBase + mulFactor(perSecondInterestRateSlopeLow, kink) + mulFactor(perSecondInterestRateSlopeHigh, (utilization - kink)));
}
}
Note that getBorrowRate() * utilization * (1 - reserveRate) = getSupplyRate()
,
That is because the protocol wants to keep part of the borrow rate to the reserves and not to give it all to the suppliers.
The problem is that in some cases where utilization > 1
, utilization * (1 - reserveRate)
is greater than 1 and then getSupplyRate() > getBorrowRate()
and all the borrowRate income will go to the suppliers and the difference between the interestRate for the suppliers to this income would come from the reserves.
We made a graph in demos that shows what is this transition point (utilization ratio) where getSupplyRate() > getBorrowRate()
.
We restricted the kink to be lower than 1 (k), the percentage that goes to the reserves to be lower than 1 (P), and the high slope to be GE to the low slope (i think this is a reasonable assumption). You can play with it and try out concrete values and limits.
https://www.desmos.com/calculator/x6onz1rrpt
We also came to the conclusion that this transition point where getSupplyRate() > getBorrowRate()
is = 1/(1-p) where p is the reserveRate
.
If you set the arguments src
and dst
in the function transferCollateral
to be the same, the increment of the dst
balance overwrites the decrement of the src
balance, effectively increasing the balance by the amount given.
Property violated:
for each asset, sum of all userCollateral.balance equals to totalSupplyAsset
sum(userCollateral[u][asset].balance) == totalsCollateral[asset].totalSupplyAsset
In the codebase, we found two places where reentrancy can occur. However, those do not pose any security issue or concern but awareness should be raised:
invoke
function of the Bulker
contract can be re-entered.doTransferIn
function of the Comet
contract is often executed at the very beginning of executions, being it an anti-pattern to follow against reentrancy.To improve clarity, consider either reducing the attack surface by making those functions non-reentrant or clearly stating this risk in the docstrings.
In CometRewards
it is possible to change the reward token of each Comet
through _setRewardConfig
. However, if the reward token is changed, the number of previous reward tokens claimed will persist and once someone claims their new reward asset, it will be added to rewardsClaimed
despite being different assets.
Consider removing the ability to change the reward asset once set or changing the way the claimed rewards are stored if the reward asset changes.
If there might be a case of a user being alone in the system at the beginning with no initial supply, this user can easily force himself into liquidation and get as many points as he wants.
This can be accomplished like this:
If the points are worth more than the total gas of this attack, I will make a profit.
besides the points I earn from the absorb, the collateral I supplied for the 10,000 users is now on discount, and if I can buy it for less than 100$, that my 10,000 users borrowed, I made a profit
There is no convenient structure in the contracts
folder to easily navigate between them. All contracts, regardless of their type or module they belong to, are mixed in a single folder.
To favor the developer experience and the maintenance of the codebase, consider adding additional folders following the structure within the vendor
directory or separating protocol components into different internal folders.
In the Comet
contract, there are functions like the supply
or withdraw
that return calls to internal functions even if there's no actual final value returned at the end.
Consider reviewing all the occurrences where this happens and avoid returning when not necessary. This will improve readability and correctness.
A constant called PRICE_SCALE
is defined in the CometCore
contract and is supposed to be used to scale the prices returned by Chainlink aggregators.
Although the function priceScale
returns the value stored in the bytecode, the constant is not used anywhere else.
Consider removing this constant or alternatively integrating it into the codebase.
Across the codebase, some contracts lack proper documentation and docstrings. In particular, the IWETH9
and CometMath
functions or the CometConfiguration
and CometStorage
variables are having little comments or nothing at all.
Consider thoroughly documenting all functions and parameters that are part of the contracts' public API. Functions or parameters implementing sensitive functionality, even if not public, should be clearly documented as well. When writing docstrings, consider following the Ethereum Natural Specification Format (NatSpec).
Moreover, the Configurator
contract explicitly states that BaseToken
and TrackingIndexScale
have no setters but it doesn't specify where those are set, eventually in the config
parameter passed as input in the Configurator
initialization.
Consider writing this in the docstrings of the initialize
function to improve clarity.
The CometDeployed
, GovernorTransferred
, SetFactory
, SetGovernor
, SetPauseGuardian
, SetBaseTokenPriceFeed
, SetExtensionsDelegate
events lack of any indexed parameter.
Consider indexing event parameters to avoid hindering the task of off-chain services searching and filtering for specific events.
There are many places throughout the codebase where changes can be made to improve gas consumption. For example:
for
loops inside absorb
and invoke
functions do not cache the length of the array and perform unnecessary operations on each iteration.unchecked
blocks for loop counters are not used consistently throughout the codebase. It is only used in the Invoke
function, although it could be implemented in loops of the rest of the protocol contracts.Comet
should not check principal = 0
because in that case nothing will happen and gas will be wasted.Comet
's fallback, it is recommended to read directly from storage without any intermediate steps.transferCollateral
within the Comet
contract, it is recommended to consult with getAssetInfoByAddress
at the beginning of the function so that in case the token passed by the parameters is not within the collaterals of the protocol, the function fails early and without wasting gas unnecessarily.unchecked { ++i; }
over unchecked { i++; }
.When performing these changes, aim to reach an optimal tradeoff between gas optimization and readability. Having a codebase that is easy to understand reduces the chance of errors in the future and improves transparency for the community.
The asborbInternal
function of the Comet
contract contains important logic of the protocol where users that are liquidatable have their debts absorbed by the protocol. To do this task frequently and maintain health in the system, users will call this function whenever they detect a liquidatable user position.
As a reward for doing this task recurrently, absorbers (users calling the absorb
function) are accounted for their gas expenditure into liquidation points, with the promise of redeeming those points for reward tokens in a separate contract.
The reward mechanism for the liquidation points is out of the scope for the current audit so we can't assess the incentives alignments in performing this task with profitable rewards.
However, how the gas used is measured doesn't reflect entirely the actual transaction cost for the user. In particular:
block.basefee
but tx.gasprice = block.basefee + priority fee
should be used instead.Consider taking these suggestions into account and changing the way the gas used is measured to improve transparency and design correctness.
Lines 1219-1221 of the asborbInternal
function in the Comet
contract are:
uint104 debtAbsorbed = unsigned104(newBalance - oldBalance);
uint valueOfDebtAbsorbed = mulPrice(debtAbsorbed, basePrice, uint64(baseScale));
emit AbsorbDebt(absorber, account, debtAbsorbed, valueOfDebtAbsorbed);
Where, during an absorb oldBalance
is negative (otherwise the isLiquidatable
modifier at the beginning of the absorb
function would have exited earlier) and newBalance >=0
. But if newBalance > 0
the debtAbsorbed
should be unsigned104(-oldBalance - newBalance)
instead since the positive excess is not actual debt that has been absorbed.
Consider emitting the correct value in the AsborbDebt
event or renaming the debtAbsorbed
variable to reflect that it does not account only for the debt absorbed but also the excess collateral exchanged for the base asset.
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.