Platform: Code4rena
Start Date: 08/03/2023
Pot Size: $60,500 USDC
Total HM: 2
Participants: 123
Period: 7 days
Judge: hansfriese
Id: 220
League: ETH
Rank: 95/123
Findings: 1
Award: $29.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xSmartContract
Also found by: 0x1f8b, 0x6980, 0xAgro, 0xSolus, 0xhacksmithh, 0xkazim, ABA, BPZ, BowTiedOriole, ChainReview, DadeKuma, DeFiHackLabs, Deathstore, DevABDee, Diana, Dravee, Dug, Englave, Go-Langer, Haipls, IceBear, Inspex, Jeiwan, Kek, Kresh, Madalad, MatricksDeCoder, MyFDsYours, RaymondFam, Rolezn, SAAJ, Sathish9098, Taloner, Udsen, Viktor_Cortess, atharvasama, ayden, brgltd, btk, carlitox477, catellatech, chaduke, codeislight, deadrxsezzz, descharre, erictee, fatherOfBlocks, favelanky, glcanvas, handsomegiraffe, jasonxiale, jekapi, joestakey, lemonr, luxartvinsec, martin, matrix_0wl, minhquanym, mrpathfindr, nadin, oyc_109, parsely, peanuts, pfedprog, rbserver, rokso, saian, santipu_, scokaf, slvDev, tsvetanovv, ubl4nk, ulqiorra, yamapyblack, zaskoh
29.6697 USDC - $29.67
ID | Title | Severity |
---|---|---|
[L-01] | contracts should inherit their interfacese | low |
[L-02] | lack of zero address check may cause problems | low |
[L-03] | state change must be done in two step | low |
[L-04] | Gas griefing/theft is possible on unsafe external call | low |
[L-05] | no check if the burn amount is zero or not | low |
[L-06] | A single point of failure | low |
Contract implementations should inherit their interfaces. Extending an interface ensures that all function signatures are correct, and catches mistakes introduced (e.g. through errant keystrokes)
file: staking/BYTES2.sol contract BYTES2 is PermitControl, ERC20("BYTES", "BYTES") {
add the interface to the contract :
contract BYTES2 is PermitControl, IByteContract , ERC20("BYTES", "BYTES") {
Checking addresses against zero-address during initialization or during setting is a security best-practice. However, such checks are missing in address variable initializations/changes in many places.
set zero-address check for immutable variables in the constructor
file: contract/staking/BYTES2.sol constructor ( address _bytes, address _s1Citizen, address _staker, address _treasury ) payable{ //@audit set zero address check for immutable variables BYTES1 = _bytes; S1_CITIZEN = _s1Citizen; STAKER = _staker; TREASURY = _treasury; }
file: contract/staking/NeoTokyoStaker.sol constructor ( address _bytes, address _s1Citizen, address _s2Citizen, address _lpToken, address _identity, address _vault, uint256 _vaultCap, uint256 _noVaultCap ) { //@audit lack of zero-address check BYTES = _bytes; S1_CITIZEN = _s1Citizen; S2_CITIZEN = _s2Citizen; LP = _lpToken; IDENTITY = _identity; VAULT = _vault; VAULT_CAP = _vaultCap; NO_VAULT_CAP = _noVaultCap; } }
you can add zero-address checks to these functions for better secuirty practice :
file: contract/staking/BYTES2.sol function changeStakingContractAddress ( address _staker ) hasValidPermit(UNIVERSAL, ADMIN) external { STAKER = _staker; }
file: contract/staking/BYTES2.sol function changeTreasuryContractAddress ( address _treasury ) hasValidPermit(UNIVERSAL, ADMIN) external { TREASURY = _treasury; }
add zero-address check for the functions and contructor above :
require(exampleAddress != address(0), "invailed address")`
Critical Address Changes Should Use Two-step Procedure The critical procedures should be two step process.
it is always better to doing the state changes or address change with two step:
file: contract/staking/BYTES2.sol function changeStakingContractAddress ( address _staker ) hasValidPermit(UNIVERSAL, ADMIN) external { STAKER = _staker; }
file: contract/staking/BYTES2.sol function changeTreasuryContractAddress ( address _treasury ) hasValidPermit(UNIVERSAL, ADMIN) external { TREASURY = _treasury; }
it's better and safer to change the critical state changes/set using two steps.
return data (bool success,) has to be stored due to EVM architecture, if in a usage like below, ‘out’ and ‘outsize’ values are given (0,0) . Thus, this storage disappears and may come from external contracts a possible Gas griefing/theft problem is avoided. this may cause a medium risk but i put it as a low because it set to low in a previous report, check this link for similar sevirity:
file: contract/staking/NeoTokyoStaker.sol function _assetTransferFrom ( address _asset, address _from, address _to, uint256 _idOrAmount ) private { // @audit gas griefing (bool success, bytes memory data) = _asset.call( abi.encodeWithSelector( _TRANSFER_FROM_SELECTOR, _from, _to, _idOrAmount ) ); // Revert if the low-level call fails. if (!success) { revert(string(data)); } }
file: contract/staking/NeoTokyoStaker.sol function _assetTransfer ( address _asset, address _to, uint256 _amount ) private { //@audit same above (bool success, bytes memory data) = _asset.call( abi.encodeWithSelector( _TRANSFER_SELECTOR, _to, _amount ) ); // Revert if the low-level call fails. if (!success) { revert(string(data)); } }
you can do the transactions above similar to this one :
bool success; assembly { success := call(3000, receiver, amount, 0, 0, 0, 0) }
if the amount is zero so the unhecked block will divided zero on 3 and we use gas for nothing !
if we set zero we may pass the _burn
checks, i know it is passed by only permit but it's better
to avoid this happen because it's seting by human and it means it can be set with 0 balance to burn !
file: contract/staking/BYTES2.sol function burn ( address _from, uint256 _amount ) hasValidPermit(UNIVERSAL, BURN) external { _burn(_from, _amount); /* We are aware that this math does not round perfectly for all values of `_amount`. We don't care. */ uint256 treasuryShare; unchecked { treasuryShare = _amount * 2 / 3; } _mint(TREASURY, treasuryShare); }
add zero balance checks to avoid this to happen or add mapping to get permit addressess balance and check if their balance is zero or not
the PermitControl.sol
(out of scope) use ownable and can be used to delegate specific rights to
external addresses, that's mean Even if protocol admins/developers are not malicious there is still a chance for Owner keys to be stolen. In such a case,
the attacker can cause serious damage by adding another contract address to the PermitContro
to the project due to important functions. and this ,ay cause vulnarability to this function below and users who have invested in project will suffer high financial losses
file: contract/staking/BYTES2.sol function burn ( address _from, uint256 _amount ) hasValidPermit(UNIVERSAL, BURN) external {
file: contract/staking/BYTES2.sol function changeStakingContractAddress ( address _staker ) hasValidPermit(UNIVERSAL, ADMIN) external
file: contract/staking/BYTES2.sol function changeTreasuryContractAddress ( address _treasury ) hasValidPermit(UNIVERSAL, ADMIN) external
file: contract/staking/NeoTokyoStaker.sol function configureLP ( address _lp ) external hasValidPermit(UNIVERSAL, CONFIGURE_LP)
file: contract/staking/NeoTokyoStaker.sol function lockLP () external hasValidPermit(UNIVERSAL, CONFIGURE_LP) { lpLocked = true;
file: contract/staking/NeoTokyoStaker.sol function configureTimelockOptions ( AssetType _assetType, uint256[] memory _timelockIds, uint256[] memory _encodedSettings ) external hasValidPermit(bytes32(uint256(_assetType)), CONFIGURE_TIMELOCKS)
file: contract/staking/NeoTokyoStaker.sol function configureIdentityCreditYields ( uint256[] memory _citizenRewardRates, string[] memory _vaultRewardRates, string[] memory _identityCreditYields ) hasValidPermit(UNIVERSAL, CONFIGURE_CREDITS) external {
file: contract/staking/NeoTokyoStaker.sol function configureIdentityCreditPoints ( string[] memory _identityCreditYields, uint256[] memory _points ) hasValidPermit(UNIVERSAL, CONFIGURE_CREDITS) external
file: contract/staking/NeoTokyoStaker.sol function configureVaultCreditMultipliers ( string[] memory _vaultCreditMultipliers, uint256[] memory _multipliers ) hasValidPermit(UNIVERSAL, CONFIGURE_CREDITS) external
file: contract/staking/NeoTokyoStaker.sol function configurePools ( PoolConfigurationInput[] memory _inputs ) hasValidPermit(UNIVERSAL, CONFIGURE_POOLS) external
file: contract/staking/NeoTokyoStaker.sol ffunction configureCaps ( uint256 _vaultedCap, uint256 _unvaultedCap ) hasValidPermit(UNIVERSAL, CONFIGURE_CAPS) external {
Add a time lock to critical functions. Admin-only functions that change critical parameters should emit events and have timelocks.
#0 - c4-judge
2023-03-17T02:40:51Z
hansfriese marked the issue as grade-b
#1 - TimTinkers
2023-03-21T02:17:22Z
Most of this is admin-related and out of scope. Acknowledging some of the findings.
#2 - c4-sponsor
2023-03-21T02:17:27Z
TimTinkers marked the issue as sponsor acknowledged