Platform: Code4rena
Start Date: 16/12/2021
Pot Size: $100,000 USDC
Total HM: 21
Participants: 25
Period: 7 days
Judge: alcueca
Total Solo HM: 12
Id: 66
League: ETH
Rank: 5/25
Findings: 5
Award: $9,204.87
🌟 Selected for report: 14
🚀 Solo Findings: 1
WatchPug
function _badChainlinkResponse(ChainlinkResponse memory _response) internal view returns (bool) { // Check for response call reverted if (!_response.success) {return true;} // Check for an invalid roundId that is 0 if (_response.roundId == 0) {return true;} // Check for an invalid timeStamp that is 0, or in the future if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {return true;} // Check for non-positive price if (_response.answer <= 0) {return true;} return false; }
On PriceFeed.sol
, we are using latestRoundData
, but there is no check if the return value indicates stale data. This could lead to stale prices according to the Chainlink documentation:
Consider adding missing checks for stale data.
For example:
function _badChainlinkResponse(ChainlinkResponse memory _response) internal view returns (bool) { // Check for response call reverted if (!_response.success) {return true;} // Check for an invalid roundId that is 0 if (_response.roundId == 0) {return true;} // Check for stale price if (_response.answeredInRound < _response.roundID) {return true;} // Check for an invalid timeStamp that is 0, or in the future if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {return true;} // Check for non-positive price if (_response.answer <= 0) {return true;} return false; }
#0 - kingyetifinance
2022-01-05T18:27:41Z
Duplicate #91
WatchPug
function wrap(uint _amount, address _from, address _to, address _rewardOwner) external override { JLP.transferFrom(_from, address(this), _amount); JLP.approve(address(_MasterChefJoe), _amount); // stake LP tokens in Trader Joe's. // In process of depositing, all this contract's // accumulated JOE rewards are sent into this contract _MasterChefJoe.deposit(_poolPid, _amount); // update user reward tracking _userUpdate(_rewardOwner, _amount, true); _mint(_to, _amount); }
Funds are transferred from the from
parameter, and the output tokens are transferred to the to
parameter, both passed by the caller without proper access control.
This issue allows anyone to call wrap()
and steal almost all their wallet balances for all the users who have approved the contract before.
WJLP
contractwrap()
with _from
= Alice's address, and _to
= Bob's address, get WJLP.Consider replacing from
with msg.sender
:
function wrap(uint _amount, address _to, address _rewardOwner) external override { JLP.transferFrom(msg.sender, address(this), _amount); JLP.approve(address(_MasterChefJoe), _amount); // stake LP tokens in Trader Joe's. // In process of depositing, all this contract's // accumulated JOE rewards are sent into this contract _MasterChefJoe.deposit(_poolPid, _amount); // update user reward tracking _userUpdate(_rewardOwner, _amount, true); _mint(_to, _amount); }
#0 - kingyetifinance
2022-01-07T06:47:20Z
Duplicate #58
#1 - alcueca
2022-01-15T06:41:54Z
Duplicate #208
🌟 Selected for report: WatchPug
WatchPug
_updateWAssetsRewardOwner(collsToUpdate, _borrower, yetiFinanceTreasury);
In _liquidateNormalMode()
, WAsset rewards for collToRedistribute will accrue to Yeti Finance Treasury, However, if a borrower wrap WJLP
and set _rewardOwner
to other address, _updateWAssetsRewardOwner()
will fail due to failure of IWAsset(token).updateReward()
.
function wrap(uint _amount, address _from, address _to, address _rewardOwner) external override { JLP.transferFrom(_from, address(this), _amount); JLP.approve(address(_MasterChefJoe), _amount); // stake LP tokens in Trader Joe's. // In process of depositing, all this contract's // accumulated JOE rewards are sent into this contract _MasterChefJoe.deposit(_poolPid, _amount); // update user reward tracking _userUpdate(_rewardOwner, _amount, true); _mint(_to, _amount); }
wrap()
some JLP
to WJLP
and set _rewardOwner
to another address;WJLP
as a collateral asset and borrowed the max amount of YUSD;batchLiquidateTroves()
when Alice defaulted, the transaction will fail.Consider checking if the user have sufficient reward amount to the balance of collateral in BorrowerOperations.sol#_transferCollateralsIntoActivePool()
.
#0 - 0xtruco
2022-01-12T21:06:16Z
Duplicate #136 and is more specific about the exact error. For issue 136, had to extrapolate to find the real error there, and this issue is a better description.
🌟 Selected for report: defsec
Also found by: 0x1f8b, Jujic, WatchPug, broccolirob, certora, cmichel, csanuragjain, hyh, jayjonah8, kenzo, robee, sirhashalot
11.5426 USDC - $11.54
WatchPug
It is usually good to add a require-statement that checks the return value or to use something like safeTransfer; unless one is sure the given token reverts in case of a failure.
Instances include:
function sendAllocatedYETI() external { require(yetiSet); require(!allocationClaimed); for (uint i = 0; i < 7; i++) { address member = team[i]; uint amount = allocations[i]; require(YETI.transfer(member, amount)); } allocationClaimed = true; } function sendUnallocatedYETI(address _to, uint _amount) external onlyTeam { require(allocationClaimed); YETI.transfer(_to, _amount); }
L77 应该类似 L69 check return value for token.transfer
call
IERC20(assets[i]).transfer(_to, amounts[i]);
_token.transfer(_to, _amount);
Consider adding a require-statement or using safeTransfer
.
#0 - kingyetifinance
2022-01-06T06:32:19Z
@LilYeti: Duplicate #1
#1 - kingyetifinance
2022-01-06T06:32:39Z
Is severity level 2 for issue #1 and its duplicates
#2 - kingyetifinance
2022-01-10T06:28:02Z
Fixed
#3 - alcueca
2022-01-15T16:32:48Z
Duplicate of #94
WatchPug
The WJLP.sol#setAddresses()
function that initializes important contract state can be called by anyone.
The attacker can initialize the contract before the legitimate deployer, hoping that the victim continues to use the same contract.
In the best case for the victim, they notice it and have to redeploy their contract costing gas.
Consider using the constructor to initialize non-proxied contracts.
Or, make sure to call it immediately after deployment and verify the transaction succeeded.
function setAddresses( address _activePool, address _TML, address _TMR, address _defaultPool, address _stabilityPool, address _YetiFinanceTreasury) external { require(!addressesSet); activePool = _activePool; TML = _TML; TMR = _TMR; defaultPool = _defaultPool; stabilityPool = _stabilityPool; YetiFinanceTreasury = _YetiFinanceTreasury; addressesSet = true; }
#0 - kingyetifinance
2022-01-06T06:54:18Z
@LilYeti: Duplicate #105
WatchPug
It's a best practice to use the latest compiler version.
The specified minimum compiler version is not up to date. Older compilers might be susceptible to some bugs. We recommend changing the solidity version pragma to the latest version to enforce the use of an up to date compiler.
List of known compiler bugs and their severity can be found here: https://etherscan.io/solcbuginfo
#0 - kingyetifinance
2022-01-05T18:00:35Z
@LilYeti: Duplicate #21
#1 - alcueca
2022-01-15T07:04:21Z
Duplicate #158
WatchPug
function updateTeamWallet(address _newTeamWallet) external onlyTeam { teamWallet = _newTeamWallet; }
YetiFinanceTreasury.teamWallet
is a critical role, if the current teamWallet
mistakenly called updateTeamWallet()
with a wrong address, it can result in all the onlyTeam()
methods being unaccessible, and it cannot be undo.
Consider changing the updateTeamWallet()
function to first nominate an address as the pending teamWallet
and adding an acceptTeamWallet()
function which is called by the pending teamWallet
to confirm the transfer.
#0 - kingyetifinance
2022-01-05T17:54:01Z
@LilYeti: Duplicate #115
#1 - alcueca
2022-01-15T06:06:04Z
Taking this as main
143.4496 USDC - $143.45
WatchPug
// ------------------------------------------------------------------------ // Mint new tokens to a given _to address // ------------------------------------------------------------------------ function _mint(address _to, uint _num_tokens) internal returns (bool success) { balances[_to] = balances[_to] + _num_tokens; emit Transfer(address(0), _to, _num_tokens); return true; } // ------------------------------------------------------------------------ // Burn tokens owned by _holder // ------------------------------------------------------------------------ function _burn(address _holder, uint _num_tokens) internal returns (bool success) { balances[_holder] = balances[_holder].sub(_num_tokens); emit Transfer(_holder, address(0), _num_tokens); return true; }
totalSupply
is one of the essential view methods of an ERC20 contract. When tokens get mint
and burn
, it is supposed to update the totalSupply
.
The current implementation does provide a totalSupply()
view function, but the storage variable _totalSupply
will never be updated.
#0 - kingyetifinance
2022-01-05T18:30:28Z
Duplicate #128
#1 - alcueca
2022-01-14T21:48:27Z
Taking as main
#2 - alcueca
2022-01-15T15:36:26Z
Assets are not a risk, code is incorrect as to spec. Low severity.
🌟 Selected for report: WatchPug
531.2947 USDC - $531.29
WatchPug
There are ERC20 tokens that charge fee for every transfer()
or transferFrom()
.
In the current implementation, BorrowerOperations.sol#_transferCollateralsIntoActivePool()
assumes that the received amount is the same as the transfer amount, and uses it as collateral.
function _transferCollateralsIntoActivePool( address _from, address[] memory _colls, uint256[] memory _amounts ) internal returns (bool) { uint256 len = _amounts.length; for (uint256 i = 0; i < len; i++) { address collAddress = _colls[i]; uint256 amount = _amounts[i]; IERC20 coll = IERC20(collAddress); bool transferredToActivePool = coll.transferFrom(_from, address(activePool), amount); if (!transferredToActivePool) { return false; } } return true; }
balanceOf
to get the actual transferred amount;#0 - kingyetifinance
2022-01-06T06:34:01Z
@LilYeti: We will not be taking any collaterals with transfer fees / have a strong vetting process for whitelisting. Severity 0 due to this.
#1 - alcueca
2022-01-15T16:32:15Z
There is no mention in the accompanying documentation about fee-on-transfer tokens not being supported. Low severity sustained.
🌟 Selected for report: WatchPug
531.2947 USDC - $531.29
WatchPug
In the current design/implementation of WJLP
and collateral redeem system, adding JLP
as collateral via WJLP
can obtain rights to the future rewards.
However, when the collaterals are redeemed (from other borrowers), the rights to future rewards are not revoked.
This can be exploited as an economic attack vector.
Given:
JLP
tokens wrapped as WJLP
and used as collateral;JLP
is the only whitelisted collateral.The attacker can do the following steps:
JLP
to WJLP
, the WJLP.userInfo[attacker]
is the amount of $1M worth of JLP
;WJLP
received as collateral;redeemCollateral()
to redeem $1M worth of JLP
with 1M YUSD;WJLP.userInfo[attacker]
is still the amount of $1M worth of JLP
, and can continuously call WJLP.getPendingRewards()
to collect the rewards.
The attack can be amplified with falshloan.
#0 - kingyetifinance
2022-01-07T07:13:05Z
@LilYeti : In TroveManagerRedemptions, https://github.com/code-423n4/2021-12-yetifinance/blob/main/packages/contracts/contracts/TroveManagerRedemptions.sol#L311 WJLP.updateReward is called so this specific attack vector would not be possible, as the amount of reward that the borrower would be eligible for is updated to 0.
#1 - alcueca
2022-01-16T06:40:54Z
The attack vector is proven to be not possible, but the functionality is still incorrect as to spec, downgraded to low severity.
43.9157 USDC - $43.92
WatchPug
The following source units are imported but not referenced in the contract:
import "./BoringCrypto/BoringBatchable.sol";
import "hardhat/console.sol";
Check all imports and remove all unused/unreferenced and unnecessary imports.
#0 - kingyetifinance
2022-01-06T09:18:08Z
Duplicate #2
WatchPug
Using ++i
is more gas efficient than i++
, especially in for loops.
For example:
for (uint i = 0; i < _tokens.length; i++) {
for (uint i = 0; i < _tokens.length; i++) {
for (uint256 i = 0; i < len; i++) {
for (uint256 i = 0; i < _tokens.length; i++) {
for (uint256 i = 0; i < _amountsAdded.length; i++) {
for (uint i = 0; i < _tokens.length; i++) {
for (uint256 i = 0; i < vars.collToLiquidate.tokens.length; i++) {
#0 - kingyetifinance
2022-01-06T09:17:49Z
Duplicate #12
26.3494 USDC - $26.35
WatchPug
Unused named returns increase contract size and gas usage at deployment.
function _userUpdate(address _user, uint256 _amount, bool _isDeposit) private returns (uint pendingJoeSent) {
pendingJoeSent
is unused.
#0 - kingyetifinance
2022-01-06T09:37:33Z
@LilYeti : Duplicate #288
#1 - alcueca
2022-01-14T21:32:14Z
It can't be a duplicate of itself
#2 - alcueca
2022-01-15T16:55:02Z
Duplicate of #24
WatchPug
Every reason string takes at least 32 bytes.
Use short reason strings that fits in 32 bytes or it will become more expensive.
Instances include:
require( msg.sender == borrowerOperationsAddress || msg.sender == defaultPoolAddress, "ActivePool: Caller is neither BO nor Default Pool");
function _requireCallerIsBOorTroveM() internal view { require( msg.sender == borrowerOperationsAddress || msg.sender == troveManagerAddress, "ActivePool: Caller is neither BorrowerOperations nor TroveManager"); }
require( _debtRepayment <= _currentDebt.sub(YUSD_GAS_COMPENSATION), "BorrowerOps: Amount repaid must not be larger than the Trove's debt" );
#0 - kingyetifinance
2022-01-06T09:24:16Z
Duplicate #66
17.7859 USDC - $17.79
WatchPug
Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.
The advantages of versions 0.8.* over <0.8.0 are:
pragma solidity 0.6.11;
pragma solidity 0.6.11;
pragma solidity 0.6.11;
#0 - kingyetifinance
2022-01-06T09:23:52Z
Duplicate #81
43.9157 USDC - $43.92
WatchPug
Setting uint
variables to 0
is redundant as they default to 0
.
uint public _totalSupply;
constructor(string memory ERC20_symbol, string memory ERC20_name, uint8 ERC20_decimals, IERC20 _JLP, IERC20 _JOE, IMasterChefJoeV2 MasterChefJoe, uint256 poolPid) { _symbol = ERC20_symbol; _name = ERC20_name; _decimals = ERC20_decimals; _totalSupply = 0; JLP = _JLP; JOE = _JOE; _MasterChefJoe = MasterChefJoe; _poolPid = poolPid; }
Consider removing _totalSupply = 0;
, make the code simpler and save some gas.
#0 - kingyetifinance
2022-01-06T09:21:09Z
Duplicate with #87
43.9157 USDC - $43.92
WatchPug
uint constant public SECONDS_IN_ONE_MINUTE = 60;
uint constant public SECONDS_IN_ONE_MINUTE = 60;
uint constant public SECONDS_IN_ONE_YEAR = 31536000;
For the constants that should not be public
, changing them to private
/ internal
can save some gas.
#0 - kingyetifinance
2022-01-06T09:25:46Z
Duplicate #95
26.3494 USDC - $26.35
WatchPug
console.log()
should not be in production code, removing them can also save gas.
https://hardhat.org/hardhat-network/reference/#:~:text=do%20spend%20a%20minimal%20amount%20of%20gas
Instances include:
console.log("Active Pool JLP Balance"); console.log(JLP.balanceOf(activePool)); console.log("Active Pool WJLP Balance"); console.log(balanceOf(activePool)); console.log("stability Pool JLP Balance"); console.log(JLP.balanceOf(stabilityPool)); console.log("stability Pool WJLP Balance"); console.log(balanceOf(stabilityPool));
#0 - kingyetifinance
2022-01-06T09:20:05Z
Duplicate issue #106
43.9157 USDC - $43.92
WatchPug
// send fee from user to YETI stakers contractsCache.yusdToken.transferFrom( _redeemer, address(contractsCache.sYETI), totals.YUSDfee );
Since totals.YUSDfee
can be 0
. Checking if (totals.YUSDfee > 0)
before the transfer can potentially save an external call and the unnecessary gas cost of a 0 token transfer.
#0 - kingyetifinance
2022-01-06T09:27:04Z
Duplicate #171
43.9157 USDC - $43.92
WatchPug
IYETIToken public yetiToken;
yetiToken
will never change, use immutable variable instead of storage variable can save gas.
#0 - kingyetifinance
2022-01-06T09:26:16Z
Duplicate #132
🌟 Selected for report: Dravee
Also found by: WatchPug, certora, sirhashalot
17.7859 USDC - $17.79
WatchPug
Contracts use assert() instead of require() in multiple places.
Per to Solidity’s documentation:
"Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will cause a Panic.”
assert(vars.compositeDebt > 0);
assert(closedStatus != Status.nonExistent && closedStatus != Status.active);
assert(sender != address(0)); assert(recipient != address(0));
Change to require()
.
#0 - kingyetifinance
2022-01-06T06:54:42Z
@LilYeti: Duplicate of #51
#1 - alcueca
2022-01-15T06:58:47Z
Duplicate #157
#2 - alcueca
2022-01-15T16:07:09Z
Despite the recommendation from the solidity manual, the practical impact of this issue is that of an excessive gas use in some conditions.
17.7859 USDC - $17.79
WatchPug
uint256[] memory amounts = IRouter(routerAddress).swapExactTokensForTokens(YUSDToSell, YETIOutMin, path, address(this), block.timestamp + 5 minutes);
deadline
is designed for the caller to specify a deadline time for the transaction to be packed. However, since the deadline
is calculated based on block.timestamp + 5 minutes
, it won't be effective (it always passes).
Therefore, changing it to block.timestamp
will make the code simpler and save some gas.
Change to:
uint256[] memory amounts = IRouter(routerAddress).swapExactTokensForTokens(YUSDToSell, YETIOutMin, path, address(this), block.timestamp);
#0 - kingyetifinance
2022-01-06T09:24:49Z
Duplicate #92
#1 - alcueca
2022-01-15T06:22:39Z
Duplicate of #211
17.7859 USDC - $17.79
WatchPug
SafeMath
is no longer needed starting with Solidity 0.8. The compiler now has built in overflow checking.
Removing SafeMath
can save some gas.
#0 - kingyetifinance
2022-01-06T09:21:33Z
Duplicate #53
#1 - alcueca
2022-01-15T06:55:28Z
Duplicate #246
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.
For example:
require(_num_tokens <= balances[msg.sender], "You are trying to transfer more tokens than you have"); balances[msg.sender] = balances[msg.sender] - _num_tokens;
balances[msg.sender] - _num_tokens
will never underflow.
#0 - kingyetifinance
2022-01-06T09:23:29Z
Not sure if we will do this -- somewhere between ack and confirmed
🌟 Selected for report: WatchPug
Also found by: SolidityScan, cccz, heiho1, robee
WatchPug
For public functions, the input parameters are copied to memory automatically which costs gas. If a function is only called externally, making its visibility as external will save gas because external functions' parameters are not copied into memory and are instead read from calldata directly.
For example:
function batchLiquidateTroves(address[] memory _troveArray, address _liquidator) public { // ... }
function getAllCollateral() public view override returns (address[] memory, uint256[] memory) { return (poolColl.tokens, poolColl.amounts); }
#0 - kingyetifinance
2022-01-06T09:25:16Z
Duplicate #9
#1 - alcueca
2022-01-15T07:22:58Z
Taking as main
WatchPug
uint256 redeemedYUSDFraction = _YUSDDrawn.mul(10**18).div(_totalYUSDSupply);
10 ** 18
can be changed to 1e18
to avoid unnecessary arithmetic operation and save gas.
#0 - kingyetifinance
2022-01-06T09:27:31Z
Duplicate #16
#1 - alcueca
2022-01-15T07:14:56Z
Taking as main
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
function setAddresses( address _sortedTrovesAddress, address _troveManagerAddress, address _whitelistAddress ) external onlyOwner { checkContract(_sortedTrovesAddress); checkContract(_troveManagerAddress); checkContract(_whitelistAddress); sortedTroves = ISortedTroves(_sortedTrovesAddress); troveManager = ITroveManager(_troveManagerAddress); whitelist = IWhitelist(_whitelistAddress); emit SortedTrovesAddressChanged(_sortedTrovesAddress); emit TroveManagerAddressChanged(_troveManagerAddress); emit WhitelistAddressChanged(_troveManagerAddress); _renounceOwnership(); }
Across the codebase, including in HintHelpers.sol
, setAddresses()
is being used as a initializer function, it's a onlyOwner
function and it will _renounceOwnership()
at the end of the function.
There are no other onlyOwner
functions, the addresses set in the function are immutable after being set.
Therefore, setAddresses()
can be replaced with constructor
and all the addresses can be declared as immutable
variables instead of storage
variables.
And Ownable
can be removed to further reduce gas costs.
This issue also applies to:
ActivePool.sol
BorrowerOperations.sol
CollSurplusPool.sol
DefaultPool.sol
PriceFeed.sol
StabilityPool.sol
TroveManager.sol
TroveManagerLiquidations.sol
TroveManagerRedemptions.sol
Whitelist.sol
#0 - kingyetifinance
2022-01-06T09:29:42Z
@LilYeti : Confirm for HintHelpers. This issue does not apply to other contracts other than hinthelpers, since we need the other addresses to set them. No other contract takes hinthelpers though, so this could be used in the constructor technically.
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
function _requireCallerIsTMLorSP() internal view { require( msg.sender == stabilityPoolAddress || msg.sender == troveManagerLiquidationsAddress, "YUSD: Caller is neither TroveManagerLiquidator nor StabilityPool"); }
function returnFromPool(address _poolAddress, address _receiver, uint256 _amount) external override { _requireCallerIsTMLorSP(); _transfer(_poolAddress, _receiver, _amount); }
_requireCallerIsTMLorSP()
is unnecessary as it's being used only once. Therefore it can be inlined in returnFromPool()
to make the code simpler and save gas.
Change to:
function returnFromPool(address _poolAddress, address _receiver, uint256 _amount) external override { require( msg.sender == stabilityPoolAddress || msg.sender == troveManagerLiquidationsAddress, "YUSD: Caller is neither TroveManagerLiquidator nor StabilityPool"); _transfer(_poolAddress, _receiver, _amount); }
Other examples include:
TroveManagerRedemptions.sol#_getRedemptionFee()
can be inlined in TroveManagerRedemptions.sol#redeemCollateral()
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
function _updateWAssetsRewardOwner(newColls memory _colls, address _borrower, address _newOwner) internal { for (uint i = 0; i < _colls.tokens.length; i++) { address token = _colls.tokens[i]; uint amount = _colls.amounts[i]; if (whitelist.isWrapped(token)) { IWAsset(token).updateReward(_borrower, _newOwner, amount); } } }
Since amount
is only needed in if (whitelist.isWrapped(token)) {...}
, so uint amount = _colls.amounts[i];
should be moved to inside if (whitelist.isWrapped(token)) {...}
.
Furthermore, considering that amount
is only used once, it can be replaced with _colls.amounts[i]
.
Change to:
function _updateWAssetsRewardOwner(newColls memory _colls, address _borrower, address _newOwner) internal { for (uint i = 0; i < _colls.tokens.length; i++) { address token = _colls.tokens[i]; if (whitelist.isWrapped(token)) { IWAsset(token).updateReward(_borrower, _newOwner, _colls.amounts[i]); } } }
WatchPug
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack.
Caching the array length in the stack saves around 3 gas per iteration.
Instances include:
ActivePool.sol#sendCollaterals()
BorrowerOperations.sol#_getTotalVariableDepositFee()
DefaultPool.sol#sendCollsToActivePool()
TroveManager.sol#_updateTroveRewardSnapshots()
StabilityPool.sol#_computeRewardsPerUnitStaked()
#0 - kingyetifinance
2022-01-06T09:36:59Z
Duplicate #14
#1 - alcueca
2022-01-15T07:16:32Z
Taking as main
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
For the arithmetic operations that will never over/underflow, using SafeMath will cost more gas.
For example:
if (_debtChange > _variableYUSDFee) { // if debt decrease, and greater than variable fee, decrease newDebt = _troveManager.decreaseTroveDebt(_borrower, _debtChange.sub(_variableYUSDFee)); } else { // otherwise increase by opposite subtraction newDebt = _troveManager.increaseTroveDebt(_borrower, _variableYUSDFee.sub(_debtChange)); }
_debtChange - _variableYUSDFee
at L792 and _variableYUSDFee - _debtChange
at L794 will never underflow.
_totalSupply = _totalSupply.add(amount); _balances[account] = _balances[account].add(amount);
_balances[account] + amount
will not overflow if _totalSupply.add(amount)
dose not overflow.
_balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance"); _totalSupply = _totalSupply.sub(amount);
_totalSupply - amount
will not underflow if _balances[account].sub(amount)
dose not underflow.
#0 - kingyetifinance
2022-01-06T09:35:23Z
Similar to #85 but not exactly same.
🌟 Selected for report: WatchPug
97.5905 USDC - $97.59
WatchPug
For the storage variables that will be accessed multiple times, cache them in the stack can save ~100 gas from each extra read (SLOAD
after Berlin).
For example:
troveManager
in TroveManagerRedemptions#_updateBaseRateFromRedemption()
function _updateBaseRateFromRedemption(uint256 _YUSDDrawn, uint256 _totalYUSDSupply) internal returns (uint256) { uint256 decayedBaseRate = troveManager.calcDecayedBaseRate(); /* Convert the drawn Collateral back to YUSD at face value rate (1 YUSD:1 USD), in order to get * the fraction of total supply that was redeemed at face value. */ uint256 redeemedYUSDFraction = _YUSDDrawn.mul(10**18).div(_totalYUSDSupply); uint256 newBaseRate = decayedBaseRate.add(redeemedYUSDFraction.div(BETA)); newBaseRate = LiquityMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100% troveManager.updateBaseRate(newBaseRate); return newBaseRate; }