Full Report
In this protocol, it's a standard auction but the lowest price wins. If a user gets outbid, they get a refund but must call a function in order to perform the refund. The sale is considered ended when the last NFT is sold. This triggers the payout to the seller and fee collector. The verification for paying out is done by checking if the current ID matches the final sale id. There is an additional check that checks that that the current id does not equal the final id. Otherwise, we've tried to get an NFT that already been sold. In the code, the function buy(uint256 _amount) amount variable is for the AMOUNT of NFTs you want to buy. If a user buys 0 NFTs, then the currentId is not set properly. The check from before about the auction being over doesn't work since the currentId is not set from the iteration within a loop. It's obviously lower than the final ID, since it's set to 0. This results in the sale being made with a larger value than the current bid. Since the auction has ended, we're able to get a refund from this still. More importantly, the check on whether to transfer the funds to the feeReceiver and saleReceiver can be hit over and over again because of the if statement failing. Zero iteration in the array causes this problem. Overall, a pretty neat bug that is common in the web3 space.
Analysis Summary
# Vulnerability: LPDA Sale Logic Error via Zero-Amount Purchase
## CVE Details
- **CVE ID**: N/A (Web3/Smart Contract specific finding)
- **CVSS Score**: 7.5 (High)
- **CWE**: CWE-691: Insufficient Control Flow Complexity / CWE-20: Improper Input Validation
## Affected Systems
- **Products**: Escher Protocol
- **Versions**: Production codebase as of December 2022 (Audit period)
- **Configurations**: Linear Price Dutch Auction (LPDA) contract (`LPDA.sol`)
## Vulnerability Description
The vulnerability exists in the `buy(uint256 _amount)` function of the LPDA (Linear Price Dutch Auction) contract. The contract tracks the progress of the sale by comparing the `currentId` of the NFT being sold against a `finalId`.
When a user calls `buy()` with an `_amount` of `0`, the following occurs:
1. **Loop Bypass**: The internal loop responsible for incrementing the `currentId` and minting NFTs does not execute.
2. **State Inconsistency**: Because the loop is bypassed, the `currentId` remains unchanged (lower than the `finalId`).
3. **Validation Failure**: The check to determine if the auction has ended triggers incorrectly or can be bypassed because the `currentId` does not increment to meet the `finalId` requirement.
4. **Fund Redirection Error**: An attacker or the protocol can trigger the logic that transfers funds to the `feeReceiver` and `saleReceiver` multiple times. Because the auction status is checked against an improperly updated `currentId`, the contract fails to recognize the sale is officially "closed," allowing the final payout logic to be executed repeatedly.
## Exploitation
- **Status**: PoC available (Discovered during Code4rena audit)
- **Complexity**: Low
- **Attack Vector**: Network (Smart Contract Interaction)
## Impact
- **Confidentiality**: None
- **Integrity**: High (Unauthorized movement of contract funds)
- **Availability**: Medium (Can result in stuck funds or drained balances)
The primary impact is that the `saleReceiver` and `feeReceiver` can potentially drain the contract of funds intended for user refunds after a sale has concluded.
## Remediation
### Patches
- The protocol has updated the `buy` function to include a `require(_amount > 0)` check to ensure the state updates correctly via the minting loop.
### Workarounds
- **Input Validation**: Ensure all UI/front-end integrations prevent zero-amount transactions.
- **Circuit Breaker**: Implement an emergency pause if the contract balance deviates from expected refund requirements.
## Detection
- **Indicators of compromise**: Multiple `End` events emitted for a single auction; repeated transfers to `feeReceiver` or `saleReceiver` from the same LPDA contract address.
- **Detection methods and tools**:
- Use Static Analysis (Slither) to identify loops where state variables are updated.
- Monitor blockchain for `buy(0)` function calls on the Escher LPDA contracts.
## References
- Code4rena Audit Report: [https://code4rena.com/reports/2022-12-escher#h-03-salereceiver-and-feereceiver-can-steal-refunds-after-sale-has-ended](https://code4rena.com/reports/2022-12-escher#h-03-salereceiver-and-feereceiver-can-steal-refunds-after-sale-has-ended)
- Escher GitHub Repository: [https://github.com/code-423n4/2022-12-escher](https://github.com/code-423n4/2022-12-escher)