Platform: Code4rena
Start Date: 29/03/2022
Pot Size: $50,000 USDC
Total HM: 16
Participants: 42
Period: 5 days
Judge: 0xean
Total Solo HM: 9
Id: 105
League: ETH
Rank: 7/42
Findings: 3
Award: $2,815.61
🌟 Selected for report: 2
🚀 Solo Findings: 1
🌟 Selected for report: TerrierLover
2075.8711 USDC - $2,075.87
PaladinRewardReserve.sol
may have potential bugs if it uses new tokens as rewards.
Currently, PaladinRewardReserve.sol
has following behaviors:
mapping(address => bool) public approvedSpenders
does not store the info regarding which token it targetssetNewSpender
, updateSpenderAllowance
, removeSpender
and transferToken
functions can set token
arbitrarilyHence, some corner cases may happen as follows:
approvedSpenders
was intended for TokenA. So it is possible that following corner cases happen:
setNewSpender
function cannot set new tokenapprovedSpenders
for TokenA, it can call updateSpenderAllowance
.Statis code analysis
Do either of followings depending on the product specification:
(1) If PAL token is only used and other token will never be used at PaladinRewardReserve.sol
, stop having address token
argument at setNewSpender
, updateSpenderAllowance
, removeSpender
and transferToken
functions. Instead, set token
at the constructor or other ways, and limit the ability to flexibly set token
from functions.
(2) If other tokens potentially will be used at PaladinRewardReserve.sol
, update data structure of approvedSpenders
mapping and change the logic.
Firstly, it should also contain the info which token
it targets such as mapping(address => address => bool)
.
Secondly, it should rewrite the require
logic at each function as follows.
require(!approvedSpenders[spender][token], "Already Spender on the specified Token");
require(approvedSpenders[spender][token], "Not approved Spender on the specified Token");
#0 - Kogaroshi
2022-04-03T10:02:27Z
Changes made in the PR: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/11
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0xDjango, 0xkatana, 0xmint, Czar102, Dravee, Funen, Ruhum, TerrierLover, defsec, gzeon, hake, hyh, jah, kenta, minhquanym, okkothejawa, pmerkleplant, rayn, reassor, robee, sorrynotsorry, sseefried, teryanarmen
240.8467 USDC - $240.85
Ownable.sol
Ownable.sol
used in Paladin project seems to be old.
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/open-zeppelin/utils/Ownable.sol
Using the latest openzeppelin source code may be ideal.
This is a latest definition of Ownable.sol. https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2a4ca654046e65553af89355e24785e50735a92c/contracts/access/Ownable.sol
The notable difference is that it defines _transferOwnership
internal function.
Address.sol
Address.sol
used in Paladin project is 0.8.0 which seems to be old.
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/open-zeppelin/utils/Address.sol
Address.sol
in the 0.8.0 version uses assembly directly, but the compiler of solidity 0.8.1 had the improvement as mentioned in https://github.com/ethereum/solidity/releases/tag/v0.8.1.
Update it to the latest Address.sol
in the 0.8.2 version.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2a4ca654046e65553af89355e24785e50735a92c/contracts/utils/Address.sol
In the new version, isContract
function is as follows:
function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; }
// If the user does not deelegate currently, automatically self-delegate
// If the user does not delegate currently, automatically self-delegate
_
at its prefix at arguments of constructor in HolyPaladinToken.solconstructor( address palToken, address _admin, address _rewardsVault, uint256 _startDropPerSecond, uint256 _endDropPerSecond, uint256 _dropDecreaseDuration, uint256 _baseLockBonusRatio, uint256 _minLockBonusRatio, uint256 _maxLockBonusRatio ){
palToken
variable is the only variable without _
. If there is no rationale to do so, it should add _
to align with other arguments.
constructor( address _palToken, address _admin, address _rewardsVault, uint256 _startDropPerSecond, uint256 _endDropPerSecond, uint256 _dropDecreaseDuration, uint256 _baseLockBonusRatio, uint256 _minLockBonusRatio, uint256 _maxLockBonusRatio ){ require(_palToken != address(0)); require(_admin != address(0)); pal = IERC20(_palToken);
In the following codes, it initializes with 0 for uint256 variable. https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L516 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L688 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L796 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L807 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L945 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L977 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1009
There is no need to initialize with 0 for uint256 variable for the above mentioned codes.
uint256 low;
return ((amount * _senderCooldown) + (receiverBalance * receiverCooldown)) / (amount + receiverBalance);
return (amount * _senderCooldown + receiverBalance * receiverCooldown) / (amount + receiverBalance);
vars.periodBonusRatio = newBonusRatio + ((vars.userRatioDecrease + vars.bonusRatioDecrease) / 2);
vars.periodBonusRatio = newBonusRatio + (vars.userRatioDecrease + vars.bonusRatioDecrease) / 2;
#0 - Kogaroshi
2022-04-02T17:38:20Z
Update for OZ contracts: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/1/commits/4d0840c9c9fe8a1a4b043d2a33696fea8bf9176c
For unnecessary parenthesis, this is wanted as a way to pesent the calculations so they can be understood quickly
Other QA & gas optimizations changes are done in the PR: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/6 (some changes/tips were implemented, others are noted but won't be applied)
🌟 Selected for report: TerrierLover
Also found by: 0v3rf10w, 0xDjango, 0xNazgul, 0xkatana, 0xmint, Cityscape, Czar102, Dravee, Funen, IllIllI, Tomio, antonttc, defsec, gzeon, hake, kenta, minhquanym, pmerkleplant, rayn, rfa, robee, saian, securerodd, teryanarmen
498.886 USDC - $498.89
Ownable.sol
and use _transferOwnership
function at constructor of PaladinRewardReserve.sol
Currently, it uses transferOwnership
function at the constructor of PaladinRewardReserve.sol
.
constructor(address _admin) { transferOwnership(_admin); }
With following steps, it can reduce the deployment gas cost.
Step1: Use the latest Ownable.sol https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2a4ca654046e65553af89355e24785e50735a92c/contracts/access/Ownable.sol
Step2: _transferOwnership
internal function instead of transferOwnership
function in PaladinRewardReserve.sol
Contract | Before | After | Change |
---|---|---|---|
PaladinRewardReserve | 752599 | 749055 | -3544 |
Ownable.sol
and use _transferOwnership
function at constructor of HolyPaladinToken.sol
Currently, it uses transferOwnership
function at the constructor of HolyPaladinToken.sol
.
constructor( address palToken, address _admin, address _rewardsVault, uint256 _startDropPerSecond, uint256 _endDropPerSecond, uint256 _dropDecreaseDuration, uint256 _baseLockBonusRatio, uint256 _minLockBonusRatio, uint256 _maxLockBonusRatio ){ ... transferOwnership(_admin); }
With following steps, it can reduce the deployment gas cost.
Step1: Use the latest Ownable.sol https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2a4ca654046e65553af89355e24785e50735a92c/contracts/access/Ownable.sol
Step2: _transferOwnership
internal function instead of transferOwnership
function in HolyPaladinToken.sol
Contract | Before | After | Change |
---|---|---|---|
PaladinRewardReserve | 752599 | 749055 | -3544 |
Errors
can reduce gas cost and contract size at PaladinRewardReserve.solIt uses require
but using Errors
in solidity can reduce the deployment gas cost.
// Define error error AlreadySpender(); error NotApprovedSpender(); // Update the logic accordingly if (approvedSpenders[spender]) { revert AlreadySpender(); } if (!approvedSpenders[spender]) { revert NotApprovedSpender(); }
Contract | Before | After | Change |
---|---|---|---|
PaladinRewardReserve | 752599 | 729041 | -23558 |
emergency
variable in HolyPaladinToken.solThe default value of bool is false. So there is no need to set false at emergency
variable.
bool public emergency;
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5502829 | -2402 |
Errors
can reduce gas cost and contract size at HolyPaladinToken.solWhere it uses require
in HolyPaladinToken.sol
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol
There are 39 callers of require
.
Use Errors
instead of require
. https://docs.soliditylang.org/en/v0.8.13/contracts.html?highlight=error#errors-and-the-revert-statement
The gas and size improvements after using Errors
instead of require
with "hPAL: No Lock" is shown below:
// Before change require(..., "hPAL: No Lock"); // After change if (...) revert NoLock();
There are 8 callers of require(..., "hPAL: No Lock")
.
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L270
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L286
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L301
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L348
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1237
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1246
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1272
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1281
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5498562 | -6669 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.987 | -0.031 |
Only converting the above mentioned 8 callers has -6669 deployment gas cost reduction and -0.031 size reduction. There are 31 callers of require
in HolyPaladinToken.sol, using Errors
instead of require
may potentially reduce many gas costs and sizes.
!= 0
instead of > 0
in HolyPaladinToken.solhttps://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L229 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L385 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L758 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L800 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L809 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L819 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L822 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1026 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1051 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1062 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1078 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1237 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1246 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1272 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1281 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1342
Use != 0
instead of > 0
at the above mentioned codes. The variable is uint, so it will not be below 0 so it can just check != 0
.
require(balanceOf(msg.sender) != 0, "hPAL: No balance");
Following methods in HolyPaladinToken.sol can reduce gas (from 5 to 20) by the above mentioned changes.
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5501226 | -4005 |
// Cannot claim more than accrued rewards, but we can use a higher amount to claim all the rewards uint256 claimAmount = amount < claimableRewards[msg.sender] ? amount : claimableRewards[msg.sender]; // remove the claimed amount from the claimable mapping for the user, // and transfer the PAL from the rewardsVault to the user claimableRewards[msg.sender] -= claimAmount;
claimAmount
will not be more than claimableRewards[msg.sender]
. Therefore, claimableRewards[msg.sender] -= claimAmount
can be wrapped by unchecked.
Wrap claimableRewards[msg.sender] -= claimAmount
with unchecked.
// Cannot claim more than accrued rewards, but we can use a higher amount to claim all the rewards uint256 claimAmount = amount < claimableRewards[msg.sender] ? amount : claimableRewards[msg.sender]; // remove the claimed amount from the claimable mapping for the user, // and transfer the PAL from the rewardsVault to the user unchecked { claimableRewards[msg.sender] -= claimAmount; }
Contract | Method | Before | After | Change |
---|---|---|---|---|
HolyPaladinToken | claim | 97548 | 97469 | -79 |
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5502014 | -3217 |
https://github.com/code-423n4/2022-03-paladin/blob/main/contracts/open-zeppelin/utils/Math.sol#L29
function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. return (a & b) + (a ^ b) / 2; }
The above logic would not overflow, so can use unchecked.
function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow. unchecked { return (a & b) + (a ^ b) / 2; } }
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5498742 | -6489 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.988 | -0.03 |
low = mid + 1
used in the binary search can be wrapped by unchecked
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L526 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L698 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L955 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L987 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1019
while (low < high) { mid = Math.average(low, high); if (totalLocks[mid].fromBlock == blockNumber) { return totalLocks[mid]; } if (totalLocks[mid].fromBlock > blockNumber) { high = mid; } else { low = mid + 1; } }
Wrap low = mid + 1
by unchecked.
while (low < high) { mid = Math.average(low, high); if (totalLocks[mid].fromBlock == blockNumber) { return totalLocks[mid]; } if (totalLocks[mid].fromBlock > blockNumber) { high = mid; } else { unchecked { low = mid + 1; } } }
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5499198 | -6033 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.990 | -0.028 |
currentUserLockIndex
varaible does not need to be defined in HolyPalatinToken.solhttps://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L272-L273 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L288-L289 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1173-L1174 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1241-L1242 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1276-L1277 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L1347-L1348
// Find the current Lock uint256 currentUserLockIndex = userLocks[msg.sender].length - 1; UserLock storage currentUserLock = userLocks[msg.sender][currentUserLockIndex];
Some currentUserLockIndex
varaibles is defined even though they are used once. Not defining variables can reduce gas cost and contract size.
Avoid defining currentUserLockIndex
variable as follows:
// Find the current Lock UserLock storage currentUserLock = userLocks[msg.sender][userLocks[msg.sender].length - 1];
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5471346 | -33885 |
Contract | Method | Before | After | Change |
---|---|---|---|---|
HolyPaladinToken | emergencyWithdraw | 191020 | 190966 | -54 |
HolyPaladinToken | increaseLock | 141196 | 140997 | -199 |
HolyPaladinToken | increaseLockDuration | 116450 | 116251 | -199 |
HolyPaladinToken | kick | 231670 | 231561 | -109 |
HolyPaladinToken | lock | 336775 | 336764 | -11 |
HolyPaladinToken | stakeAndIncreaseLock | 227758 | 227649 | -109 |
HolyPaladinToken | unlock | 134273 | 134164 | -109 |
It can reduce about 0.6% of the size of HolyPaladinToken.sol.
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.861 | -0.157 |
EmergencyBlock()
in emergency can be put in private functionhttps://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L221 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L244 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L254 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L269 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L285 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L300 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L312 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L328 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L347 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L372 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L381 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L403 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L412
Following check is called by 13 callsites. This can be put into the private function to reduce the contract size and deployment gas cost.
if(emergency) revert EmergencyBlock();
Define a private function to include the above mentioned logic, and use isEmergent function at each callsite.
function isEmergent() private view { if(emergency) revert EmergencyBlock(); }
Please note that the method gas fee increases while deployment gas fee reduces. So it depends on what this project prioritizes.
Method gas fee Some methods have 10 ~ 30 increase of method gas cost.
Deployment gas fee
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5433502 | -71729 |
It can reduce about 1.3% of the size of HolyPaladinToken.sol.
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.686 | -0.332 |
https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L456-L457 https://github.com/code-423n4/2022-03-paladin/blob/9c26ec8556298fb1dc3cf71f471aadad3a5c74a0/contracts/HolyPaladinToken.sol#L709-L710
uint256 lastUserLockIndex = userLocks[user].length - 1; return userLocks[user][lastUserLockIndex];
emergencyWithdraw increaseLock increaseLockDuration kick stakeAndIncreaseLock unlock unstake updateUserRewardState
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5433502 | -71729 |
It can reduce about 0.2% of the size of HolyPaladinToken.sol.
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.959 | -0.059 |
function getNewReceiverCooldown(address sender, address receiver, uint256 amount) external view returns(uint256) { uint256 senderCooldown = cooldowns[sender]; uint256 receiverBalance = balanceOf(receiver); return _getNewReceiverCooldown( senderCooldown, amount, receiver, receiverBalance ); }
Definitions senderCooldown and receiverBalance variables are not necessary at getNewReceiverCooldown function in HolyPaladinToken.sol
Avoid defining the above mentioned variables.
function getNewReceiverCooldown(address sender, address receiver, uint256 amount) external view returns(uint256) { return _getNewReceiverCooldown( cooldowns[sender], amount, receiver, balanceOf(receiver) ); }
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5503083 | -2148 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 24.008 | -0.01 |
uint256 previousToBalance = balanceOf(to); cooldowns[to] = _getNewReceiverCooldown(fromCooldown, amount, to, previousToBalance);
uint256 previousFromBalance = balanceOf(from); if(previousFromBalance == amount && fromCooldown != 0) {
Avoid defining previousToBalance and previousFromBalance.
cooldowns[to] = _getNewReceiverCooldown(fromCooldown, amount, to, balanceOf(to));
if(balanceOf(from) == amount && fromCooldown != 0) {
Following methods in HolyPaladinToken.sol can reduce around 10~20 gas cost
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5502651 | -2580 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 24.006 | -0.012 |
uint256 balance = balanceOf(user); // If the contract was blocked (emergency mode) or // If the user has no Lock // then available == staked if(emergency || userLocks[user].length == 0) { return( balance, 0, balance ); } // If a Lock exists // Then return // total staked balance // locked balance // available balance (staked - locked) uint256 lastUserLockIndex = userLocks[user].length - 1; return( balance, uint256(userLocks[user][lastUserLockIndex].amount), balance - uint256(userLocks[user][lastUserLockIndex].amount) );
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5500878 | -4353 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.998 | -0.02 |
ellapsedTime variable does not need to be defined.
uint256 ellapsedTime = block.timestamp - lastRewardUpdate; ... uint256 accruedBaseAmount = ellapsedTime * baseDropPerSecond;
Avoid defining ellapsedTime variable.
uint256 accruedBaseAmount = (block.timestamp - lastRewardUpdate) * baseDropPerSecond;
This change reduces the method gas cost of more than 10 functions. The reductions are around 10~20.
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5500878 | -4353 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 24.015 | -0.002 |
uint256 _currentDropPerSecond = _updateDropPerSecond(); // Update the index uint256 newIndex = _getNewIndex(_currentDropPerSecond); rewardIndex = newIndex; lastRewardUpdate = block.timestamp; return newIndex;
// Update the index rewardIndex = _getNewIndex(_updateDropPerSecond()); lastRewardUpdate = block.timestamp; return rewardIndex;
This change reduces the method gas cost of more than 10 functions. The reductions are around 10~20.
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5503323 | -1908 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 24.009 | -0.009 |
if(balanceOf(user) > 0){ // calculate the base rewards for the user staked balance // (using avaialable balance to count the locked balance with the multiplier later in this function) uint256 indexDiff = currentRewardsIndex - userLastIndex; uint256 stakingRewards = (userStakedBalance * indexDiff) / UNIT; uint256 lockingRewards = 0; if(userLocks[user].length > 0){ UserLockRewardVars memory vars; // and if an user has a lock, calculate the locked rewards vars.lastUserLockIndex = userLocks[user].length - 1; // using the locked balance, and the lock duration userLockedBalance = uint256(userLocks[user][vars.lastUserLockIndex].amount); // Check that the user's Lock is not empty if(userLockedBalance > 0 && userLocks[user][vars.lastUserLockIndex].duration != 0){ vars.previousBonusRatio = userCurrentBonusRatio[user]; if(vars.previousBonusRatio > 0){ vars.userRatioDecrease = userBonusRatioDecrease[user]; // Find the new multiplier for user: // From the last Ratio, where we remove userBonusRatioDecrease for each second since last update vars.bonusRatioDecrease = (block.timestamp - rewardsLastUpdate[user]) * vars.userRatioDecrease; newBonusRatio = vars.bonusRatioDecrease >= vars.previousBonusRatio ? 0 : vars.previousBonusRatio - vars.bonusRatioDecrease; if(vars.bonusRatioDecrease >= vars.previousBonusRatio){ // Since the last update, bonus ratio decrease under 0 // We count the bonusRatioDecrease as the difference between the last Bonus Ratio and 0 vars.bonusRatioDecrease = vars.previousBonusRatio; // In the case this update is made far after the end of the lock, this method would mean // the user could get a multiplier for longer than expected // We count on the Kick logic to avoid that scenario } // and calculate the locking rewards based on the locked balance & // a ratio based on the rpevious one and the newly calculated one vars.periodBonusRatio = newBonusRatio + ((vars.userRatioDecrease + vars.bonusRatioDecrease) / 2); lockingRewards = (userLockedBalance * ((indexDiff * vars.periodBonusRatio) / UNIT)) / UNIT; } } } // sum up the accrued rewards, and return it accruedRewards = stakingRewards + lockingRewards; }
UserLockRewardVars
struct does not need to be used.
Here is an example codebase which avoids using UserLockRewardVars memory vars
.
if(balanceOf(user) > 0){ // calculate the base rewards for the user staked balance // (using avaialable balance to count the locked balance with the multiplier later in this function) uint256 indexDiff = currentRewardsIndex - userLastIndex; uint256 lockingRewards = 0; if(userLocks[user].length > 0){ // and if an user has a lock, calculate the locked rewards uint256 lastUserLockIndex = userLocks[user].length - 1; // using the locked balance, and the lock duration userLockedBalance = uint256(userLocks[user][lastUserLockIndex].amount); // Check that the user's Lock is not empty if(userLockedBalance > 0 && userLocks[user][lastUserLockIndex].duration != 0){ uint256 previousBonusRatio = userCurrentBonusRatio[user]; if(previousBonusRatio > 0){ uint256 userRatioDecrease = userBonusRatioDecrease[user]; // Find the new multiplier for user: // From the last Ratio, where we remove userBonusRatioDecrease for each second since last update uint256 bonusRatioDecrease = (block.timestamp - rewardsLastUpdate[user]) * userRatioDecrease; newBonusRatio = bonusRatioDecrease >= previousBonusRatio ? 0 : previousBonusRatio - bonusRatioDecrease; if(bonusRatioDecrease >= previousBonusRatio){ // Since the last update, bonus ratio decrease under 0 // We count the bonusRatioDecrease as the difference between the last Bonus Ratio and 0 bonusRatioDecrease = previousBonusRatio; // In the case this update is made far after the end of the lock, this method would mean // the user could get a multiplier for longer than expected // We count on the Kick logic to avoid that scenario } // and calculate the locking rewards based on the locked balance & // a ratio based on the rpevious one and the newly calculated one uint256 periodBonusRatio = newBonusRatio + ((userRatioDecrease + bonusRatioDecrease) / 2); lockingRewards = (userLockedBalance * ((indexDiff * periodBonusRatio) / UNIT)) / UNIT; } } } // sum up the accrued rewards, and return it accruedRewards = (userStakedBalance * indexDiff) / UNIT + lockingRewards; }
In this example, it also does not define userStakedBalance
variable.
This change reduces the method gas cost of more than 10 functions. The reductions are around 100~200.
Contract | Before | After | Change |
---|---|---|---|
HolyPaladinToken | 5505231 | 5481972 | -23259 |
Contract | Before | After | Change(KB) |
---|---|---|---|
HolyPaladinToken | 24.018 | 23.910 | -0.108 |
#0 - Kogaroshi
2022-04-02T17:39:48Z
For update of Ownable inherited contract and use of the internal transfer ownership method: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/1/commits/4d0840c9c9fe8a1a4b043d2a33696fea8bf9176c
#1 - Kogaroshi
2022-04-04T15:02:05Z
QA & gas optimizations changes are done in the PR: https://github.com/PaladinFinance/Paladin-Tokenomics/pull/6 (some changes/tips were implemented, others are noted but won't be applied)
#2 - Kogaroshi
2022-04-05T07:08:50Z
Really high quality Gas optimizations report