You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
Instances number of this issue: 12
// src/DBR.sol19mapping(address=>uint256) public balances;
20mapping(address=>mapping(address=>uint256)) public allowance;
23mapping(address=>uint256) public nonces;
26mapping (address=>uint) public debts; // user => debt across all tracked markets27mapping (address=>uint) public dueTokensAccrued; // user => amount of due tokens accrued28mapping (address=>uint) public lastUpdated; // user => last update timestamp// src/Market.sol57mapping (address=> IEscrow) public escrows; // user => escrow58mapping (address=>uint) public debts; // user => debt59mapping(address=>uint256) public nonces; // user => nonce// src/Oracle.sol25mapping (address=> FeedData) public feeds;
26mapping (address=>uint) public fixedPrices;
27mapping (address=>mapping(uint=>uint)) public dailyLows; // token => day => price
Boolean comparison
Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value.
State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
Instances number of this issue: 4
// src/DBR.sol totalDueTokensAccrued and _totalSupply:
Functions only called outside of the contract can be declared as external
External call cost is less expensive than of public functions.
The difference is because in public functions, Solidity immediately copies array arguments to memory, while external functions can read directly from calldata. Memory allocation is expensive, whereas reading from calldata is cheap.
Instances number of this issue: 55
// src/BorrowController.solfunction allow(addressallowedContract) public onlyOperator { contractAllowlist[allowedContract] =true; }
function deny(addressdeniedContract) public onlyOperator { contractAllowlist[deniedContract] =false; }
function borrowAllowed(addressmsgSender, address, uint) publicviewreturns (bool) {
// src/DBR.solfunction setPendingOperator(addressnewOperator_) public onlyOperator {
function setReplenishmentPriceBps(uintnewReplenishmentPriceBps_) public onlyOperator {
function claimOperator() public {
function addMinter(addressminter_) public onlyOperator {
function removeMinter(addressminter_) public onlyOperator {
function addMarket(addressmarket_) public onlyOperator {
function approve() publicvirtualreturns (bool) {
function transfer() publicvirtualreturns (bool) {
function transferFrom() publicvirtualreturns (bool) {
function permit() publicvirtual {
function invalidateNonce() public {
function accrueDueTokens(addressuser) public {
function onBorrow(addressuser, uintadditionalDebt) public {
function onRepay(addressuser, uintrepaidDebt) public {
function onForceReplenish(addressuser, uintamount) public {
function burn(uintamount) public {
function mint(addressto, uintamount) public {
// src/Fed.solfunction changeGov(address_gov) public {
function changeSupplyCeiling(uint_supplyCeiling) public {
function changeChair(address_chair) public {
function resign() public {
function expansion(IMarket market, uintamount) public {
function contraction(IMarket market, uintamount) public {
function takeProfit(IMarket market) public {
// src/Market.solfunction setOracle(IOracle _oracle) public onlyGov { oracle = _oracle; }
function setBorrowController(IBorrowController _borrowController) public onlyGov { borrowController = _borrowController; }
function setGov(address_gov) public onlyGov { gov = _gov; }
function setLender(address_lender) public onlyGov { lender = _lender; }
function setPauseGuardian(address_pauseGuardian) public onlyGov { pauseGuardian = _pauseGuardian; }
function setCollateralFactorBps(uint_collateralFactorBps) public onlyGov {
function setLiquidationFactorBps(uint_liquidationFactorBps) public onlyGov {
function setReplenismentIncentiveBps(uint_replenishmentIncentiveBps) public onlyGov {
function setLiquidationIncentiveBps(uint_liquidationIncentiveBps) public onlyGov {
function setLiquidationFeeBps(uint_liquidationFeeBps) public onlyGov {
function recall(uintamount) public {
function pauseBorrows(bool_value) public {
function depositAndBorrow(uintamountDeposit, uintamountBorrow) public {
function borrowOnBehalf(addressfrom, uintamount, uintdeadline, uint8v, bytes32r, bytes32s) public {
function withdrawOnBehalf(addressfrom, uintamount, uintdeadline, uint8v, bytes32r, bytes32s) public {
function invalidateNonce() public {
function repayAndWithdraw(uintrepayAmount, uintwithdrawAmount) public {
function forceReplenish(addressuser, uintamount) public {
function liquidate(addressuser, uintrepaidDebt) public {
// src/Oracle.solfunction setPendingOperator(addressnewOperator_) public onlyOperator { pendingOperator = newOperator_; }
function setFeed(addresstoken, IChainlinkFeed feed, uint8tokenDecimals) public onlyOperator { feeds[token] =FeedData(feed, tokenDecimals); }
function setFixedPrice(addresstoken, uintprice) public onlyOperator { fixedPrices[token] = price; }
function claimOperator() public {
// src/escrows/GovTokenEscrow.solfunction pay(addressrecipient, uintamount) public {
function delegate(addressdelegatee) public {
// src/escrows/INVEscrow.solfunction pay(addressrecipient, uintamount) public {
function delegate(addressdelegatee) public {
// src/escrows/SimpleERC20Escrow.solfunction pay(addressrecipient, uintamount) public {
Duplicated functions can be written into a library
invalidateNonce() and DOMAIN_SEPARATOR():
// src/DBR.sol// src/Market.solfunction invalidateNonce() public {
nonces[msg.sender]++;
}
function DOMAIN_SEPARATOR() publicviewvirtualreturns (bytes32) {
returnblock.chainid== INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
Splitting require() statements that use &&
require() statements with multiple conditions can be split.
See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper.
X = X + Y / X = X - Y
IS CHEAPER THANX += Y / X -= Y
The demo of the gas comparison can be seen here.
Consider use
X = X + Y / X = X - Y
to save gas.Instances number of this issue: 26
Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
Instances number of this issue: 12
Boolean comparison
Comparing to a constant (true or false) is a bit more expensive than directly checking the returned boolean value.
The demo of the gas comparison can be seen here.
Instances number of this issue: 2
Suggestion:
Change to:
Add unchecked {} for subtractions where the operands cannot underflow
Some will not underflow because of a previous code or
require()
or if-statementAnd
block.timestamp
can't be less thanlastUpdated[user]
, due to line 290:lastUpdated[user] = block.timestamp;
Instances number of this issue: 22
State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
Instances number of this issue: 4
// src/DBR.sol
totalDueTokensAccrued
and_totalSupply
:dueTokensAccrued[user]
andbalances[user]
:// src/Market.sol
escrows[user]
:Functions only called outside of the contract can be declared as external
External call cost is less expensive than of public functions.
The difference is because in public functions, Solidity immediately copies array arguments to memory, while external functions can read directly from calldata. Memory allocation is expensive, whereas reading from calldata is cheap.
Instances number of this issue: 55
Duplicated functions can be written into a library
invalidateNonce()
andDOMAIN_SEPARATOR()
:Splitting
require()
statements that use &&require()
statements with multiple conditions can be split.See this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper.
The demo of the gas comparison can be seen here.
Instances number of this issue: 8
Code reuse
Some functions codes overlap, only a few lines of difference.
Consider reuse the common part and save deployment gas.
viewPrice()
andgetPrice()
getCollateralValue(address user)
andgetCollateralValueInternal(address user)
getCreditLimit(address user)
andgetCreditLimitInternal(address user)
getWithdrawalLimitInternal(address user)
andgetWithdrawalLimit(address user)
Recycle mapping storage
As time goes by, outdated
dailyLows[token][day]
in src/Oracle.sol will be useless. Consider delete the keys and get some gas refund.The text was updated successfully, but these errors were encountered: