Platform: Code4rena
Start Date: 05/01/2023
Pot Size: $90,500 USDC
Total HM: 55
Participants: 103
Period: 14 days
Judge: Picodes
Total Solo HM: 18
Id: 202
League: ETH
Rank: 59/103
Findings: 2
Award: $88.11
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ladboy233
Also found by: 0x1f8b, 0xAgro, 0xSmartContract, 0xbepresent, 0xkato, Aymen0909, CodingNameKiki, Cryptor, Deekshith99, Deivitto, HE1M, IllIllI, Kaysoft, Koolex, PaludoX0, Qeew, RaymondFam, Rolezn, Sathish9098, Tointer, a12jmx, arialblack14, ast3ros, ayeslick, bin2chen, btk, caventa, ch0bu, chaduke, chrisdior4, delfin454000, descharre, evan, fatherOfBlocks, georgits, gz627, jasonxiale, joestakey, kaden, lukris02, nicobevi, nogo, oberon, oyc_109, pfapostol, rbserver, sakshamguruji, seeu, shark, simon135, slvDev, synackrst, tnevler, whilom, zaskoh
51.3151 USDC - $51.32
L-N | Issue | Instances |
---|---|---|
[L‑01] | Signature malleability for ecrecover | 1 |
[L‑02] | Prefer using _safeMint over _mint | 2 |
[L‑03] | Critical Changes Should Use Two-step Procedure` | 1 |
[L‑04] | Missing zero address check in function releaseToAddress in CollateralToken.sol | 1 |
Total: 5 instances over 4 issues
N-C | Issue | Instances |
---|---|---|
[N‑01] | Constants should be defined rather than using magic numbers | 3 |
[N‑02] | Unclear comments in ClearningHouse.sol | 1 |
[N‑03] | Unnamed parameter in function _execute | 1 |
[N‑04] | Functions setApprovalForAll and safeBatchTransferFrom are missing implementation and event emission | 2 |
[N‑05] | Typos in comments | 2 |
[N‑06] | Unused imports | 2 |
[N‑07] | Missing natspec | 1 |
Total: 12 instances over 7 issues
ecrecover
File: VaultImplementation.sol
The implementation of a cryptographic signature system in Ethereum contracts often assumes that the signature is unique, but signatures can be altered without the possession of the private key and still be valid. The EVM specification defines several so-called ‘precompiled’ contracts one of them being ecrecover which executes the elliptic curve public key recovery. A malicious user can slightly modify the three values v, r and s to create other valid signatures. A system that performs signature verification on contract level might be susceptible to attacks if the signature is part of the signed message hash. Valid signatures could be created by a malicious user to replay previously signed messages.
address recoveredAddress = ecrecover(
Line of code:
Use OpenZeppelin ECDSA library.
Both LienToken::_createLien and CollateralToken::onERC721Received use ERC721's _mint method, which is missing the check if the account to mint the NFT to is a smart contract that can handle ERC721 tokens. The _safeMint method does exactly this, so prefer using it over _mint but always add a nonReentrant modifier, since calls to _safeMint can reenter.
_mint(from_, collateralId);
_mint(params.receiver, newLienId);
Lines of code:
The critical procedures should be two step process.
See similar findings in previous Code4rena contests for reference: https://code4rena.com/reports/2022-06-illuminate/#2-critical-changes-should-use-two-step-procedure
File: VaultImplementation.sol
function setDelegate(address delegate_) external
Lines of code:
releaseToAddress
in CollateralToken.sol
File: CollateralToken.sol
function _releaseToAddress( CollateralStorage storage s, Asset memory underlyingAsset, uint256 collateralId, address `releaseTo`
Lines of code:
Consider adding zero address check.
File: PublicVault.sol
uint256 fee = x.mulDivDown(VAULT_FEE(), 10000);
Lines of code:
File: AstariaRouter.sol
s.minInterestBPS = uint32((uint256(1e15) * 5) / (365 days));
s.maxInterestRate = ((uint256(1e16) * 200) / (365 days)).safeCastTo88();
Lines of code:
ClearningHouse.sol
The comments quality is poor, please revisit the commenting in this contract and consider improving it for better
_execute
File: ClearingHouse.sol
function _execute( address tokenContract, // collateral token sending the fake nft address to, // buyer uint256 encodedMetaData, //retrieve token address from the encoded data uint256 // space to encode whatever is needed,
Lines of code:
File: ClearingHouse.sol
function setApprovalForAll(address operator, bool approved) external {}
function safeBatchTransferFrom( address from, address to, uint256[] calldata ids, uint256[] calldata amounts, bytes calldata data ) public {}
Lines of code:
File: Vault.sol
Change vautls
to vaults
File: PublicVault.sol
Change calcualtion
to calculation
Consider removing them from the contracts
File: ClearingHouse.sol
import {ConduitControllerInterface} from "seaport/interfaces/ConduitControllerInterface.sol";
File: LienToken.sol
import {Initializable} from "./utils/Initializable.sol";
NatSpec documentation to all public methods and variables is essential for better understanding of the code by developers and auditors and is strongly recommended.
Most of the functions in the contract are missing natspec, some of them are missing only couple of params.
Consider adding natspec documentation for all the code.
#0 - c4-judge
2023-01-26T14:55:02Z
Picodes marked the issue as grade-b
🌟 Selected for report: c3phas
Also found by: 0x1f8b, 0xAgro, 0xSmartContract, 0xackermann, 0xkato, Aymen0909, Bnke0x0, CloudX, IllIllI, PaludoX0, Rageur, Rahoz, RaymondFam, ReyAdmirado, Rolezn, SadBase, SaeedAlipoor01988, caventa, chaduke, chrisdior4, fatherOfBlocks, fs0c, kaden, nogo, pfapostol, shark, synackrst
36.79 USDC - $36.79
G-O | Issue |
---|---|
[G‑01] | THERE’S NO NEED TO SET DEFAULT VALUES FOR VARIABLES |
[G‑02] | Remove public visibility from constant variables |
[G‑03] | Use uint256 instead of uint8 for storage variables and function parameters |
[G‑04] | Use custom errors instead of revert strings |
[G‑05] | Use x != 0 instead of x > 0 for uint types |
[G‑06] | x = x - y costs less gas than x -= y, same for addition |
[G‑07] | USE CALLDATA INSTEAD OF MEMORY |
[G‑08] | Splitting require() statements that use && saves gas |
[G‑09] | USING STORAGE INSTEAD OF MEMORY FOR STRUCTS/ARRAYS SAVES GAS -3 |
[G‑10] | Caching the array length is more gas efficient |
Total: 10 issues
If a variable is not set/initialized, the default value is assumed (0, false, 0x0 … depending on the data type). You are simply wasting gas if you directly initialize it with its default value.
File: LienToken.sol
uint256 potentialDebt = 0;
Lines of code:
Constant variables are custom to the contract and won't need to be read on-chain - anyone can just see their values from the source code and, if needed, hardcode them into other contracts. Removing the public visibility will optimise deployment cost since no automatically generated getters will exist in the bytecode of the contract.
File: VaultImplementation.sol
bytes32 public constant STRATEGY_TYPEHASH
Lines of code:
Unless you are doing variable-packing in storage it is best to use always use uint256 because all other uint types get automatically padded to uint256 in memory anyway. Same applies for function parameters
File: VaultImplementation.sol
uint8 position
Lines of code:
Solidity 0.8.4 added the custom errors functionality, which can be use instead of revert strings, resulting in big gas savings on errors. Replace all revert statements in the contracts with custom error ones.
File: ClearingHouse.sol
Example:
require(payment >= currentOfferPrice, "not enough funds received");
Lines of code:
The != operator costs less gas than > and for uint types you can use it to check for non-zero values to save gas
File: ClearingHouse.sol
if (ERC20(paymentToken).balanceOf(address(this)) > 0) {
Lines of code:
You can replace all -= and += occurrences to save gas
Files: AstariaRouter.sol
totalBorrowed += payout;
Lines of code:
Files: LienToken.sol
potentialDebt += _getOwed(
Lines of code:
Some methods are declared as external but the arguments are defined as memory instead of as calldata. By marking the function as external it is possible to use calldata in the arguments shown below and save significant gas.
File: ClearingHouse.sol
function validateOrder(Order memory order) external
Lines of code:
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 by 3 gas
File: Vault.sol
require(s.allowList[msg.sender] && receiver == owner());
Lines of code:
When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.
File: ClearingHouse.sol
Order[] memory listings = new Order[](1);
Lines of code:
File: LienToken.sol
Stack memory newStackSlot;
Point memory point = Point({
Lines of code:
This is because access to a local variable in solidity is more efficient than query storage / calldata / memory
We recommend to change from:
for (uint256 i=0; i<array.length; i++) { ... } to: uint len = array.length for (uint256 i=0; i<len; i++) { ... }
This issue persist all through the contracts.
#0 - c4-judge
2023-01-25T23:59:44Z
Picodes marked the issue as grade-c
#1 - c4-judge
2023-01-25T23:59:53Z
Picodes marked the issue as grade-b