Platform: Code4rena
Start Date: 30/05/2023
Pot Size: $24,170 USDC
Total HM: 10
Participants: 5
Period: 3 days
Judge: hansfriese
Total Solo HM: 2
Id: 244
League: ETH
Rank: 1/5
Findings: 8
Award: $0.00
🌟 Selected for report: 3
🚀 Solo Findings: 1
🌟 Selected for report: rbserver
Also found by: adriro, bin2chen, d3e4, minhquanym
Data not available
https://github.com/code-423n4/2023-05-particle/blob/1caf678bc20c24c96fc8f6b0046383ff0e9d2a6f/contracts/protocol/ParticleExchange.sol#L212 https://github.com/code-423n4/2023-05-particle/blob/1caf678bc20c24c96fc8f6b0046383ff0e9d2a6f/contracts/protocol/ParticleExchange.sol#L744
The withdrawEthWithInterest()
function transfers ETH with interest back to the lender in case the loan is insolvent or the auction has concluded. It also transfers PnL to the borrower. However, if the borrower is a smart contract and rejects receiving ETH, the function will revert. This prevents the lender from withdrawing their ETH with interest.
The problem also exists in auctionBuyNft()
function when it transfers PnL to borrower.
The withdrawEthWithInterest()
function does a native ETH transfer
to borrower.
function withdrawEthWithInterest(Lien calldata lien, uint256 lienId) external override validateLien(lien, lienId) { ... // transfer ETH with interest back to lender payable(lien.lender).transfer(lien.price + payableInterest); // transfer PnL to borrower if (lien.credit > payableInterest) { // @audit borrower can reject ETH, making it revert payable(lien.borrower).transfer(lien.credit - payableInterest); } ... }
The borrower contract can simply revert on receive()
function to make it happen.
Manual Review
Consider applying "Pull over Push" patterns to mitigate the issue as described in https://fravoll.github.io/solidity-patterns/pull_over_push.html.
ETH-Transfer
#0 - c4-judge
2023-06-06T05:51:05Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T06:35:39Z
hansfriese marked the issue as duplicate of #21
#2 - c4-judge
2023-06-06T07:27:28Z
hansfriese marked the issue as duplicate of #31
#3 - wukong-particle
2023-06-07T01:46:21Z
Judge is right, indeed duplication. Pull based suggestion is good.
#4 - c4-sponsor
2023-06-07T01:46:25Z
wukong-particle marked the issue as sponsor confirmed
#5 - wukong-particle
2023-06-12T23:09:35Z
Fixed with pull based approach: https://github.com/Particle-Platforms/particle-exchange-protocol/pull/13
🌟 Selected for report: adriro
Also found by: minhquanym, rbserver
Data not available
In the Particle protocol, a portion of the lender's interest is kept by the treasury as described in the docs, and is also implemented in the _withdrawAccountInterest()
function. However, in the withdrawEthWithInterest()
function, the lender receives the entire payableInterest
directly, which goes against the intended design.
The withdrawEthWithInterest()
function simply transfers payableInterest
plus the lien price directly to the lender.
function withdrawEthWithInterest(Lien calldata lien, uint256 lienId) external override validateLien(lien, lienId) { ... uint256 payableInterest = _calculateCurrentPayableInterest(lien); ... payable(lien.lender).transfer(lien.price + payableInterest); // @audit not apply treasury rate here ... }
Manual Review
Consider applying the treasury rate to the lender's interest in the withdrawEthWithInterest()
function.
Other
#0 - c4-judge
2023-06-06T05:50:36Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T06:22:16Z
hansfriese marked the issue as duplicate of #20
#2 - wukong-particle
2023-06-07T01:46:56Z
Judge is correct, indeed duplication.
#3 - c4-sponsor
2023-06-07T01:46:57Z
wukong-particle marked the issue as sponsor confirmed
#4 - c4-judge
2023-06-07T17:22:12Z
hansfriese changed the severity to 3 (High Risk)
#5 - wukong-particle
2023-06-12T23:10:10Z
🌟 Selected for report: bin2chen
Also found by: minhquanym
Data not available
The function buyNftFromMarket()
allows a borrower to buy an NFT from the same collection to repay a loan. At the end of the function flow, it checks that the contract actually holds the NFT tokenId
and assumes that it is the acquired NFT. However, this is incorrect. The NFT tokenId
might already be in the contract but belong to a different lien that is supplied by another lender. As a result, an attacker can use the funds that are supposed to buy tokenId
to buy an arbitrary NFT from the marketplace and, at the same time, repay the lien that they are borrowing. At this moment, there are two liens tracking the same NFT tokenId
, and whoever withdraws later will lose the NFT.
function _execBuyNftFromMarket( ... ) internal{ ... // verify that the declared NFT is acquired and the balance decrease is correct // @audit tokenId might belong to other lien if (IERC721(collection).ownerOf(tokenId) != address(this) || balanceBefore - address(this).balance != amount) { revert Errors.InvalidNFTBuy(); } }
Consider the scenario:
buyNftFromMarket()
to acquire a BAYC and close the active loan with Bob. However, she provides the tokenId = 222
and the tradeData
is actually to buy a worthless NFT that she put to the marketplace, which is worth much less than a BAYC.Manual Review
Consider adding a check to ensure that the contract does not hold the NFT before calling the marketplace.
Invalid Validation
#0 - c4-judge
2023-06-06T05:51:57Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T10:12:09Z
hansfriese marked the issue as duplicate of #15
#2 - c4-sponsor
2023-06-07T01:45:44Z
wukong-particle marked the issue as sponsor confirmed
#3 - wukong-particle
2023-06-07T01:45:45Z
Correct duplication of https://github.com/code-423n4/2023-05-particle-findings/issues/15.
#4 - wukong-particle
2023-06-12T23:09:01Z
🌟 Selected for report: bin2chen
Also found by: d3e4, minhquanym, rbserver
Data not available
In the withdrawNftWithInterest()
function, the lender can withdraw an NFT back if the NFT is currently in the contract without an active loan. However, the function makes an incorrect assumption that if the NFT can be withdrawn, then the loan is not active. This is a vulnerability because it allows the lender of a new lien to withdraw the NFT of an old lien that has been refinanced, even when the new lien is active.
function withdrawNftWithInterest(Lien calldata lien, uint256 lienId) external override validateLien(lien, lienId) { if (msg.sender != lien.lender) { revert Errors.Unauthorized(); } // delete lien delete liens[lienId]; // transfer NFT back to lender /// @dev can withdraw means NFT is currently in contract without active loan, /// @dev the interest (if any) is already accured to lender at NFT acquiring time // @audit not correct assumption // @audit it might belong to other lien IERC721(lien.collection).safeTransferFrom(address(this), msg.sender, lien.tokenId); emit WithdrawNFT(lienId); // withdraw interest from this account too _withdrawAccountInterest(payable(msg.sender)); }
In the refinanceLoan()
function, both liens have the same tokenId
params, but that NFT should only be withdrawn by the old lien's lender, not the new lien's lender.
// update old lien liens[oldLienId] = keccak256( abi.encode( Lien({ lender: oldLien.lender, borrower: address(0), collection: oldLien.collection, tokenId: newLien.tokenId, price: oldLien.price, rate: oldLien.rate, loanStartTime: 0, credit: 0, auctionStartTime: 0 }) ) ); // update new lien liens[newLienId] = keccak256( abi.encode( Lien({ lender: newLien.lender, borrower: oldLien.borrower, collection: newLien.collection, tokenId: newLien.tokenId, price: newLien.price, rate: newLien.rate, loanStartTime: block.timestamp, credit: newCredit, auctionStartTime: 0 }) ) );
Manual Review
Consider adding a check in the withdrawNftWithInterest()
function to ensure that the lien is not active.
Invalid Validation
#0 - c4-judge
2023-06-06T05:49:14Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T10:40:25Z
hansfriese marked the issue as duplicate of #13
#2 - wukong-particle
2023-06-07T01:47:50Z
Judge is correct, indeed duplication. Solution suggested here is good too.
#3 - c4-sponsor
2023-06-07T01:47:54Z
wukong-particle marked the issue as sponsor confirmed
#4 - wukong-particle
2023-06-12T23:11:11Z
🌟 Selected for report: minhquanym
Data not available
The contract supports a "push-based" NFT supply, where the price and rate are embedded in the data bytes. This way, the lender doesn't need to additionally approve the NFT but can just transfer it directly to the contract. However, since the contract also interacts with the marketplace to buy/sell NFT, it has to prevent the issue where the marketplace also sends data bytes, which might tie 1 NFT with 2 different liens and create divergence.
function onERC721Received( address operator, address from, uint256 tokenId, bytes calldata data ) external returns (bytes4) { if (data.length == 64) { // @audit marketplace is router so the executor contract might not be whitelisted if (registeredMarketplaces[operator]) { /// @dev transfer coming from registeredMarketplaces will go through buyNftFromMarket, where the NFT /// is matched with an existing lien (realize PnL) already. If procceds here, this NFT will be tied /// with two liens, which creates divergence. revert Errors.Unauthorized(); } /// @dev MAX_PRICE and MAX_RATE should each be way below bytes32 (uint256 price, uint256 rate) = abi.decode(data, (uint256, uint256)); /// @dev the msg sender is the NFT collection (called by safeTransferFrom's _checkOnERC721Received check) _supplyNft(from, msg.sender, tokenId, price, rate); } return this.onERC721Received.selector; }
It prevents it by using the registeredMarketplaces[]
mapping, where it records the address of the marketplace. This check is explicitly commented in the codebase.
However, it is not enough. The protocol plans to integrate with Reservoir's Router contract, so only the Router address is whitelisted in registeredMarketplaces[]
. But the problem is the address that transfers the NFT is not the Router, but the specific Executor contract, which is not whitelisted.
As a result, the marketplace might bypass this check and create a new lien in onERC721Received()
during the buyNftFromMarket()
flow, thus making 2 liens track the same NFT.
Function _execBuyNftFromMarket()
does a low-level call to the exchange
// execute raw order on registered marketplace bool success; if (useToken == 0) { // use ETH // solhint-disable-next-line avoid-low-level-calls (success, ) = marketplace.call{value: amount}(tradeData); } else if (useToken == 1) { // use WETH weth.deposit{value: amount}(); weth.approve(marketplace, amount); // solhint-disable-next-line avoid-low-level-calls (success, ) = marketplace.call(tradeData); }
The contract calls to Reservoir's router contract, which then calls to a specific module to execute the buy. https://github.com/reservoirprotocol/indexer/blob/6c89d546d3fb98d5eaa505b9943e89bd91f2e8ec/packages/contracts/contracts/router/ReservoirV6_0_1.sol#L50
function _executeInternal(ExecutionInfo calldata executionInfo) internal { address module = executionInfo.module; // Ensure the target is a contract if (!module.isContract()) { revert UnsuccessfulExecution(); } (bool success, ) = module.call{value: executionInfo.value}(executionInfo.data); if (!success) { revert UnsuccessfulExecution(); } }
Manual Review
Consider adding a flag that indicates the contract is in the buyNftFromMarket()
flow and use it as a check in onERC721Received()
. For example
_marketBuyFlow = 1; _execBuyNftFromMarket(lien.collection, tokenId, amount, useToken, marketplace, tradeData); _marketBuyFlow = 0;
And in onERC721Receive()
if (data.length == 64) { if(_martketBuyFlow) { return this.onERC721Received.selector; } }
Invalid Validation
#0 - c4-judge
2023-06-06T05:50:07Z
hansfriese marked the issue as satisfactory
#1 - c4-sponsor
2023-06-06T18:33:31Z
wukong-particle marked the issue as sponsor confirmed
#2 - wukong-particle
2023-06-06T23:49:03Z
We are considering adding ReentrancyGaurd around all functions that modify the lien (to prevent other issues like https://github.com/code-423n4/2023-05-particle-findings/issues/14). Here, we should be able to re-use the ReentrancyGaurd variable to prevent divergence.
So something like this
buyNftFromMarket(...) external payable override validateLien(Lien, LienId) nonReentrant { ... }
in onERC721Received
,
if (data.length == 64) { if(_status === _ENTERED) { revert Errors.Unauthorized(); } }
We will need to modify _status to be internal
instead of private
from Openzeppelin's original ReentrancyGaurd.sol
#3 - wukong-particle
2023-06-07T22:00:34Z
🌟 Selected for report: bin2chen
Also found by: d3e4, minhquanym
Data not available
The ParticleExchange
contract does not store any data about the lien in the contract storage. Instead, users must send the entire Lien
struct when interacting with any existing lien, and the contract checks if the hash of the struct is correct. This poses a problem because normal users must know the lien information when it is not stored on-chain.
function _validateLien(Lien calldata lien, uint256 lienId) internal view returns (bool) { return liens[lienId] == keccak256(abi.encode(lien)); }
As discussed with the sponsor on Discord, the lien information is stored in a database. A service job listens to on-chain events to update the database every minute on average.
On the other hand, the addCredit()
function is public and allows anyone to add any amount of ETH to the credit of any active loan. This is where the issue occurs. Since users need to know the lien information when interacting with the protocol, an attacker can simply spam the addCredit()
function with tiny amounts of credit. The purpose of this is to continuously change the lien information, causing other users' transactions to revert because they submit the wrong lien information. The issue can become a big problem during an auction. Lenders can make borrowers or other users unable to interact with the lien, allowing them to liquidate the loan.
The impact of this issue is a denial-of-service (DoS) attack during an auction. The attacker can keep changing the lien information, making it impossible for other users to interact with the lien.
Consider the scenario
startLoanAuction()
as soon as possible to get that 10 ETH. During auction, she keeps calling addCredit()
to the loan with tiny amount (1, 2 wei).auctionBuyNft()
or to repayWithNft()
since the lien info keeps changing continously. After auction duration, Alice is able to withdraw 10 ETH even.Manual Review
There are 2 fixes for this issues
addCredit()
function.addCredit()
for their loan.DoS
#0 - c4-judge
2023-06-06T05:52:37Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T09:50:53Z
hansfriese marked the issue as primary issue
#2 - c4-judge
2023-06-06T10:37:37Z
hansfriese marked the issue as duplicate of #16
#3 - wukong-particle
2023-06-07T01:44:53Z
Judge is correct, indeed duplication. Judge nails the simplest issue as primary, we will do the fix in https://github.com/code-423n4/2023-05-particle-findings/issues/16#issuecomment-1579327687.
#4 - c4-sponsor
2023-06-07T01:44:57Z
wukong-particle marked the issue as sponsor confirmed
#5 - c4-judge
2023-06-07T06:37:48Z
hansfriese changed the severity to 2 (Med Risk)
#6 - wukong-particle
2023-06-12T23:08:25Z
🌟 Selected for report: minhquanym
Data not available
In the protocol, lenders have to pay a small treasury fee when they claim their interest. The contract owner can change this _treasuryRate
at any time using the function setTreasuryRate()
.
// @audit treasury rate should not affect existing loan function setTreasuryRate(uint256 rate) external onlyOwner { if (rate > MathUtils._BASIS_POINTS) { revert Errors.InvalidParameters(); } _treasuryRate = rate; emit UpdateTreasuryRate(rate); }
However, when the admin changes the rate, the new treasury rate will also be applied to active loans, which is not the agreed-upon term between the lenders and borrowers when they supplied the NFT and created the loan.
Consider the following scenario:
_treasuryRate = 5%
._treasuryRate
to 50%
. Now, if Alice claims the interest, she needs to pay 0.5 ETH to the treasury and keep 0.5 ETH._treasuryRate
is updated.
The point is that Alice only agreed to pay a 5%
treasury rate at the beginning, so the new rate should not apply to her.Manual Review
Consider storing the treasuryRate
in the loan struct. The loan struct is not kept in storage, so the gas cost will not increase significantly.
Alternatively, consider adding a timelock mechanism to prevent the admin from changing the treasury rate.
Other
#0 - c4-judge
2023-06-06T05:52:49Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T06:23:51Z
hansfriese marked the issue as primary issue
#2 - hansfriese
2023-06-06T06:26:08Z
Marked as primary because of the concise explanation and the mitigation suggestion.
#3 - c4-sponsor
2023-06-06T18:53:14Z
wukong-particle marked the issue as sponsor acknowledged
#4 - wukong-particle
2023-06-06T18:56:00Z
Acknowledged the issue, though the fix might not be the same as suggested. We will mitigate it with an upper bound on the treasury rate, and perhaps the timielock mechanism
#5 - c4-sponsor
2023-06-06T18:56:05Z
wukong-particle marked the issue as sponsor confirmed
#6 - c4-judge
2023-06-07T13:53:31Z
hansfriese marked the issue as selected for report
#7 - wukong-particle
2023-06-08T22:10:00Z
🌟 Selected for report: minhquanym
Also found by: adriro
Data not available
In the function _execBuyNftFromMarket()
, if the user chooses to use WETH
, the function deposits ETH and approves the amount
of WETH to the marketplace. After executing the trade at the marketplace, the function checks that the balance decrease is correct in the end. However, this check only accounts for ETH changes, not WETH changes, which is incorrect. If the trade did not use the full amount of WETH approved to the marketplace, some leftover WETH will remain in the contract. This amount of WETH/ETH will be locked in the contract, even though it should belong to the borrower who was able to get a good offer to buy the NFT at a lower price.
if (useToken == 0) { // use ETH // solhint-disable-next-line avoid-low-level-calls (success, ) = marketplace.call{value: amount}(tradeData); } else if (useToken == 1) { // use WETH weth.deposit{value: amount}(); weth.approve(marketplace, amount); // solhint-disable-next-line avoid-low-level-calls // @audit might not use all amount approved, cause ETH to locked in the protocol (success, ) = marketplace.call(tradeData); } if (!success) { revert Errors.MartketplaceFailedToTrade(); } // verify that the declared NFT is acquired and the balance decrease is correct if (IERC721(collection).ownerOf(tokenId) != address(this) || balanceBefore - address(this).balance != amount) { revert Errors.InvalidNFTBuy(); }
Consider the following scenario:
buyNftFromMarket()
to acquire an NFT with a price of 100 WETH. However, she sets the amount to 105 WETH, so the contract deposits and approves 105 WETH to the marketplace. After the trade, there is still 5 WETH approved to the marketplace.buyNftFromMarket()
to acquire an NFT with a price of 5 WETH. He specifies the useToken = 0
. However, he sets the amount = 0
and actually uses the 5 WETH left in step 1 of Alice to acquire the NFT. The result is Bob is able to steal 5 WETH approved to the marketplace.Manual Review
Consider accounting for the WETH when checking balance changes in _execBuyNftFromMarket()
.
Invalid Validation
#0 - c4-judge
2023-06-06T05:52:27Z
hansfriese marked the issue as satisfactory
#1 - c4-judge
2023-06-06T07:00:27Z
hansfriese marked the issue as primary issue
#2 - hansfriese
2023-06-06T07:02:16Z
Marked as primary to credit pointing out an interesting scenario in the PoC. Mitigation is well written at #24.
#3 - c4-sponsor
2023-06-06T18:38:46Z
wukong-particle marked the issue as sponsor confirmed
#4 - c4-judge
2023-06-07T13:53:19Z
hansfriese marked the issue as selected for report
#5 - wukong-particle
2023-06-07T19:35:33Z
🌟 Selected for report: adriro
Also found by: bin2chen, d3e4, minhquanym, rbserver
Data not available
Id | Title |
---|---|
L-1 | Function repayWithNft() should not be called when the auction is concluded |
L-2 | Lender can call stopLoanAuction() for a concluded auction |
L-3 | Attacker can create spam lien without transferring any ERC721 token |
L-4 | Use of floating pragma |
NC-1 | Lack of Natspec comments |
NC-2 | Avoidance of magic values |
repayWithNft()
should not be called when the auction is concludedA concluded auction allows the lender to liquidate the loan and take the ETH back. Thus, when the auction is concluded, the borrower should not be allowed to call repayWithNft()
.
Consider disabling repayWithNft()
if the auction is concluded for that loan.
stopLoanAuction()
for a concluded auctionThe lender can still call stopLoanAuction()
for a concluded auction, while it should only be called for a live auction.
// @done-audit can stop a concluded auction if (lien.auctionStartTime == 0) { revert Errors.AuctionNotStarted(); }
Consider disabling stopLoanAuction()
for concluded auctions.
The function onERC721Received()
accepts any sender, even EOA, to call it. Malicious users might spam call this function to create spam lien without transferring any ERC721 token. However, it will only affect the UI/UX of the protocol and not the smart contract if users do not interact with these malicious liens.
Consider disabling EOA to call onERC721Received()
. It will not completely fix the issue since the contract can still be spammed, but it at least limits the possibility that users might mistakenly call this function.
The floating pragma is used in the following locations:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17;
Consider using a fixed solidity version.
The following functions lack Natspec comments for their parameters:
For example:
// @audit Missing bips in Natspec comment /** * @dev Converts an integer bips value to a signed wad value. */ function bipsToWads(uint256 bips) public pure returns (uint256 wad) { return (bips * 1e18) / _BASIS_POINTS; }
Consider completing the missing Natspec comments.
The use of magic values should be avoided as they are easy to make mistakes with.
function calculateCurrentInterest( uint256 principal, uint256 rateBips, uint256 loanStartTime ) external view returns (uint256 interest) { uint256 loanTimeWad = (block.timestamp - loanStartTime) * 1e18; // @audit avoid magic value (1e18) interest = principal.wadMul(bipsToWads(rateBips).wadMul(loanTimeWad.wadDiv(_YEAR_WAD))); return interest; }
Create constants for these values and use the constants instead of magic values.
#0 - hansfriese
2023-06-06T06:11:35Z
L-2: I think it might make sense to allow lenders to stop the concluded auction. A lender might want to re-initiate auction with a hope to get the NFT back rather than ETH. NC-2: Invalid
L3 NC1
#1 - wukong-particle
2023-06-06T22:54:19Z
L1: Similar to buyNFTFromMarket
, if lender chooses not to withdraw ETH from concluded auction, borrower can still close position with NFT.
L2: Judge is correct.
L3: invalid, onERC721Received
is designed for EOA so they don't need to call "setApprovalForAll". Our backend can handle this level of spam and won't show to UI/UX for irrelevant NFTs.
L4: Acknowledged, similar to L1 in https://github.com/code-423n4/2023-05-particle-findings/issues/28.
NC1: Acknowledged, will update, similar to 14 in https://github.com/code-423n4/2023-05-particle-findings/issues/48.
NC2: Acknowledged, will update, similar to 11 in https://github.com/code-423n4/2023-05-particle-findings/issues/48.
#2 - c4-sponsor
2023-06-06T22:54:24Z
wukong-particle marked the issue as sponsor acknowledged
#3 - c4-judge
2023-06-07T08:32:12Z
hansfriese marked the issue as grade-b
#4 - hansfriese
2023-06-07T09:39:15Z
L1~3: Invalid
L 1 N 2
#5 - wukong-particle
2023-06-09T19:29:50Z
L4 fixed with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/4 NC1 fixed with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/16 NC2 fixed with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/11 and https://github.com/Particle-Platforms/particle-exchange-protocol/pull/15
🌟 Selected for report: adriro
Also found by: d3e4, minhquanym
Data not available
Id | Title |
---|---|
G-1 | Function withdrawEthWithInterest() calls transfer() for lender twice |
G-2 | internal functions only called once can be inlined to save gas |
G-3 | Optimize names to save gas |
G-4 | Use a more recent version of solidity |
withdrawEthWithInterest()
calls transfer()
for lender twiceFunction withdrawEthWithInterest()
does ETH transfers to lender twice in one transaction. It could be optimized to transfer only once.
payable(lien.lender).transfer(lien.price + payableInterest); // transfer PnL to borrower if (lien.credit > payableInterest) { payable(lien.borrower).transfer(lien.credit - payableInterest); } emit WithdrawETH(lienId); // withdraw interest from this account too _withdrawAccountInterest(payable(msg.sender));
ETH is transferred to lender in first line and last line in, which is twice. Consider grouping them into only 1 transfer.
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions.
Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas For example, the function IDs in the Core.sol contract will be the most used; A lower method ID may be given.
I recommend using the latest strictness version 0.8.19. Reference: https://blog.soliditylang.org/2023/02/22/solidity-0.8.19-release-announcement/
#0 - c4-judge
2023-06-06T10:45:18Z
hansfriese marked the issue as grade-b
#1 - wukong-particle
2023-06-06T23:02:13Z
G-1, acknowledged, will likely use the pull based withdraw for ETH transfer. Similar to fix suggested in https://github.com/code-423n4/2023-05-particle-findings/issues/31.
G-2, acknowledged, not inlining for readability reasons.
G-3, acknowledged, will consider, gas optimizoooor
G-4, acknowledged, will update, similar to L1 in https://github.com/code-423n4/2023-05-particle-findings/issues/28
#2 - c4-sponsor
2023-06-06T23:02:17Z
wukong-particle marked the issue as sponsor acknowledged
#3 - wukong-particle
2023-06-09T21:06:38Z
G-1 fixed (nullified) with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/13 G-4 fixed with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/4