The intended behavior of the buySnow
function is to allow users to purchase S
tokens using one of two mutually exclusive payment methods: either by sending the exact required amount of native ETH with the transaction, or by having the contract pull the required amount of WETH tokens via transferFrom
.
The contract's logic incorrectly conflates these two payment paths. If a user attempts to pay with ETH but sends an amount that does not exactly match the required price, the function's else
block is executed. If that user also has a sufficient WETH allowance approved for the contract, the transaction will succeed using their WETH, but the native ETH sent in the msg.value
becomes permanently locked in the contract with no mechanism for withdrawal, resulting in a direct loss of funds for the user.
The risk is assessed as Medium. This rating is based on a Medium likelihood of occurrence combined with a High impact on the affected user.
Reason 1: This fund loss occurs when a user interacts directly with the contract (e.g., via Etherscan or a custom script) and makes a manual calculation error, sending an ETH amount that is not precisely equal to the calculated purchase price. Such "fat-finger" errors are common during direct contract interactions.
Reason 2: The vulnerability is triggered during interactions with a poorly implemented or buggy frontend. A user-facing application that miscalculates the required msg.value
due to rounding errors, incorrect price data, or other bugs will cause its users to lose funds, even when they follow the intended user flow correctly.
Impact 1: The primary impact is a direct and permanent financial loss for the user. The ETH sent with the transaction becomes irrecoverably trapped within the contract's balance, as there is no function for withdrawal or refund by any party, including the contract owner.
Impact 2: The secondary impact is significant reputational damage to the project. Users who lose funds due to this predictable design flaw will lose trust in the protocol's safety and competence, leading to negative community sentiment and deterring future potential users from interacting with the ecosystem.
This PoC simulates a realistic user error scenario to prove the vulnerability:
Objective: To show that a user attempting to buy Snow
tokens can have their ETH locked in the contract if they send an incorrect amount while also having an active WETH approval.
Setup:
A MockWETH
contract is created to simulate WETH token functionality.
A user
is given a balance of 10 WETH.
The user
approves the Snow
contract to spend 1 WETH. This is a critical precondition for the bug to manifest.
Execution:
The user
intends to buy 1 Snow
token, which costs 1 WETH (or 1 ETH).
The user
makes a mistake and sends only 0.5 ETH
with the transaction instead of the required 1 ETH
.
They call the buySnow(1)
function with msg.value = 0.5 ether
.
Expected Malicious Outcome:
The if (msg.value == ...)
check fails.
The else
block executes, successfully transferring 1 WETH
from the user.
The user receives 1 Snow
token.
The 0.5 ETH
sent by the user remains trapped in the Snow
contract.
Success Criteria: The test succeeds by asserting that the Snow
contract's ETH balance is now 0.5 ETH
, proving the funds are locked.
You can save this code as test/SnowFundLock.t.sol
in a Foundry project.
The vulnerability should be resolved by explicitly separating the payment logic for native ETH and WETH tokens. The contract must never assume a payment method; it must validate one or the other but never both simultaneously.
The recommended approach is to check if msg.value
is greater than zero. If it is, the function should treat the transaction exclusively as an ETH payment and validate the amount accordingly. If msg.value
is zero, it should proceed as a WETH payment. This creates two distinct and mutually exclusive execution paths, eliminating the ambiguity that leads to locked funds.
Restructure the buySnow
function's logic.
Instead of an if-else
block based on the result of a strict equality check, use the presence of msg.value
to determine the payment type.
(Optional but Recommended) Add a new custom error.
For better clarity and gas efficiency, define a custom error for incorrect ETH payments.
Here is the final, secure version of the buySnow
function after applying the mitigation.
Eliminates Fund Loss: It is now impossible for a user to send an incorrect ETH amount and also be charged WETH. The transaction will simply revert if the ETH value is wrong, protecting the user's funds.
Clear and Unambiguous Logic: The code's intent is now explicit. It clearly separates the two payment methods, reducing complexity and the chance of future bugs.
Improved User Experience: By failing early with a clear error message (S__IncorrectEthAmount
), the contract provides immediate feedback to users or frontends about why a transaction failed, allowing them to correct the issue.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.