Hello -
I am learning Solidity and using this contract to learn how to think and review. I was doing this with the aid of several LLMs. They seemed to find some false flags, I think because the design itself is odd. And I was able to work with ChatGPT especially to come to a conclusion about some shortcoming of the design. So the following is written by ChatGPT - note that I have tuned mine to be very succinct and to always be pushing back at me, so it comes across quite rude.
Note also that between the LLMs and with me providing human review, I am also finding other bugs but I'll post them here if I think they're legit. I don't know what the etiquette is around all this, re. posting potentially exploitable information on GitHub. Also I don't know how serious this contract is and whether it is here for people to go ahead and use.
I've been coding for 40 years now, 20 in TradFi, but I am new to smart contracts, so I might be wrong!
Summary
This does not appear to be a traditional security vulnerability (assuming a non-reentrant, well-behaved ERC-20 like USDC).
However, the current design introduces significant accounting ambiguity and economic risk that would create real problems in production use.
In short:
A recipient can withdraw funds from a payment, yet that same payment can later be refunded in full, with losses implicitly absorbed by the recipient’s remaining balance, the arbiter, or the protocol as a whole.
This may be intentional, but if so it is under-specified and dangerous.
Observed Behaviour
- Payments are tracked individually (
Payment.amount, withdrawnAmount, refunded)
- Funds are pooled at the address level via
balances[address]
- Withdrawals reduce
balances[to]
- Refunds always transfer
payment.amount, regardless of prior withdrawals
As a result:
-
A seller can withdraw funds after the lockup period
-
The same payment can later be refunded in full
-
The refund is not limited to unwithdrawn funds
-
The loss is absorbed by:
- the seller’s remaining balance (if available),
- otherwise the arbiter’s balance,
- otherwise the refund reverts (protocol insolvency)
This turns each payment into a long-lived liability, even after settlement.
Why this is a problem (even if intentional)
1. Escrow vs chargeback ambiguity
The contract behaves neither like:
- a strict escrow (where funds remain reserved), nor
- a clearly defined chargeback system (with collateral and limits)
Instead, it mixes both models:
- Withdrawals feel final
- Refunds remain possible indefinitely
- Funding source for refunds is implicit and path-dependent
This makes it hard for any participant to reason about risk.
2. Loss attribution is implicit, not enforced
Who pays for a refund after withdrawal?
- Sometimes the seller
- Sometimes the arbiter
- Sometimes nobody (revert)
This is encoded via side effects on pooled balances, not explicit policy.
Second-order effects:
- Sellers cannot know their true exposure
- Arbiters may unknowingly underwrite sellers
- Risk depends on call ordering and timing, not rules
3. Broken accounting invariants
Even under a “chargeback” interpretation, a basic invariant should hold:
withdrawnAmount + refundedAmount ≤ payment.amount
Currently this is not enforced.
A single payment can cause more value to move through the system than it originally deposited, relying on unrelated balances to absorb the difference.
This is not flexibility — it is undefined economic behaviour.
4. UX and legal mismatch
From a seller’s perspective:
- “Withdrawal” typically implies settlement finality
- Here it actually means borrowing against a revocable sale
From a buyer’s perspective:
- Refunds appear guaranteed
- In reality they depend on third-party solvency
This mismatch would surface as disputes, not exploits.
Security note
This issue is not a reentrancy or signature vulnerability by itself, assuming a standard ERC-20.
It is primarily a design and accounting problem, not a code-execution bug.
However, the ambiguity significantly increases the blast radius of:
- frontend bugs
- arbiter mistakes
- or future token changes
Suggested clarifications / mitigations
If this behaviour is intended, the contract should make that explicit and enforceable. For example:
- Limit refunds to
amount − withdrawnAmount
- Introduce a refund expiry window
- Require seller collateralisation
- Explicitly cap arbiter exposure
- Separate escrowed funds from chargeback pools
Without one of these, the system implicitly socialises losses in ways that are difficult to reason about or defend.
Conclusion
This contract may work mechanically, but its economic semantics are unclear and hazardous.
Even if no attacker is involved, real users would encounter surprising and potentially catastrophic outcomes.
At minimum, the intended refund/withdrawal model should be clarified and enforced on-chain.
Hello -
I am learning Solidity and using this contract to learn how to think and review. I was doing this with the aid of several LLMs. They seemed to find some false flags, I think because the design itself is odd. And I was able to work with ChatGPT especially to come to a conclusion about some shortcoming of the design. So the following is written by ChatGPT - note that I have tuned mine to be very succinct and to always be pushing back at me, so it comes across quite rude.
Note also that between the LLMs and with me providing human review, I am also finding other bugs but I'll post them here if I think they're legit. I don't know what the etiquette is around all this, re. posting potentially exploitable information on GitHub. Also I don't know how serious this contract is and whether it is here for people to go ahead and use.
I've been coding for 40 years now, 20 in TradFi, but I am new to smart contracts, so I might be wrong!
Summary
This does not appear to be a traditional security vulnerability (assuming a non-reentrant, well-behaved ERC-20 like USDC).
However, the current design introduces significant accounting ambiguity and economic risk that would create real problems in production use.
In short:
A recipient can withdraw funds from a payment, yet that same payment can later be refunded in full, with losses implicitly absorbed by the recipient’s remaining balance, the arbiter, or the protocol as a whole.
This may be intentional, but if so it is under-specified and dangerous.
Observed Behaviour
Payment.amount,withdrawnAmount,refunded)balances[address]balances[to]payment.amount, regardless of prior withdrawalsAs a result:
A seller can withdraw funds after the lockup period
The same payment can later be refunded in full
The refund is not limited to unwithdrawn funds
The loss is absorbed by:
This turns each payment into a long-lived liability, even after settlement.
Why this is a problem (even if intentional)
1. Escrow vs chargeback ambiguity
The contract behaves neither like:
Instead, it mixes both models:
This makes it hard for any participant to reason about risk.
2. Loss attribution is implicit, not enforced
Who pays for a refund after withdrawal?
This is encoded via side effects on pooled balances, not explicit policy.
Second-order effects:
3. Broken accounting invariants
Even under a “chargeback” interpretation, a basic invariant should hold:
Currently this is not enforced.
A single payment can cause more value to move through the system than it originally deposited, relying on unrelated balances to absorb the difference.
This is not flexibility — it is undefined economic behaviour.
4. UX and legal mismatch
From a seller’s perspective:
From a buyer’s perspective:
This mismatch would surface as disputes, not exploits.
Security note
This issue is not a reentrancy or signature vulnerability by itself, assuming a standard ERC-20.
It is primarily a design and accounting problem, not a code-execution bug.
However, the ambiguity significantly increases the blast radius of:
Suggested clarifications / mitigations
If this behaviour is intended, the contract should make that explicit and enforceable. For example:
amount − withdrawnAmountWithout one of these, the system implicitly socialises losses in ways that are difficult to reason about or defend.
Conclusion
This contract may work mechanically, but its economic semantics are unclear and hazardous.
Even if no attacker is involved, real users would encounter surprising and potentially catastrophic outcomes.
At minimum, the intended refund/withdrawal model should be clarified and enforced on-chain.