Note - This is out-of-scope on HackerOne for bug reports, so here I am.
Summary:
Refund Protocol - Debt Bypass via refundBySeller
https://github.com/circlefin/refund-protocol/blob/master/src/RefundProtocol.sol
The refundBySeller function does not settle outstanding seller debts before processing refunds. This allows sellers to extract funds that should first go toward repaying the arbiter, potentially causing permanent financial loss to the arbiter.
refundBySeller lacks a call to _settleDebt(msg.sender) at the beginning of the function, unlike withdraw() which correctly settles debts first.
Debts are created when the arbiter covers chargebacks/refunds on behalf of sellers who lack sufficient balance.
The only defensible interpretation would be:
"Refunds are customer-protection sacred and must bypass debt settlement."
Steps To Reproduce:
Attack Scenario (1M USDC Example)
Step 1: Seller accumulates debt
- Seller operates high-volume business, receives 5M USDC in payments
- Seller withdraws most funds:
balances[seller] = 100K USDC
- Customer disputes/chargebacks occur totalling 1M USDC
- Arbiter calls
refundByArbiter to cover these chargebacks
- Arbiter pays from their balance:
balances[arbiter] -= 1M
- Result:
debts[seller] = 1M USDC, balances[seller] = 100K USDC
Step 2: Seller receives new payment
- New customer pays 900K USDC for goods/services
- Result:
balances[seller] = 1M USDC, debts[seller] = 1M USDC
Step 3: Seller exploits the vulnerability
- Seller (legitimately or collusively) decides to issue refund on the 900K payment
- Seller calls
refundBySeller(newPaymentID)
- Code checks: 900K <= 1M ✓ passes
- Code executes:
balances[seller] = 1M - 900K = 100K
- Customer receives 900K USDC refund
- Result:
balances[seller] = 100K, debts[seller] = 1M USDC (unchanged!)
Step 4: Seller tries to exit
- Seller attempts
withdraw() to extract remaining 100K
_settleDebt(msg.sender) executes automatically
- 100K → arbiter, reducing debt to 900K
- Withdrawal fails (insufficient funds)
- Seller gets nothing, leaves platform
- Final permanent loss: 900K USDC (the amount that should have been settled but was used for refund instead)
Supporting Material/References:
None
Note - This is out-of-scope on HackerOne for bug reports, so here I am.
Summary:
Refund Protocol - Debt Bypass via
refundBySellerhttps://github.com/circlefin/refund-protocol/blob/master/src/RefundProtocol.sol
The
refundBySellerfunction does not settle outstanding seller debts before processing refunds. This allows sellers to extract funds that should first go toward repaying the arbiter, potentially causing permanent financial loss to the arbiter.refundBySellerlacks a call to_settleDebt(msg.sender)at the beginning of the function, unlikewithdraw()which correctly settles debts first.Debts are created when the arbiter covers chargebacks/refunds on behalf of sellers who lack sufficient balance.
The only defensible interpretation would be:
Steps To Reproduce:
Attack Scenario (1M USDC Example)
Step 1: Seller accumulates debt
balances[seller]= 100K USDCrefundByArbiterto cover these chargebacksbalances[arbiter]-= 1Mdebts[seller]= 1M USDC,balances[seller]= 100K USDCStep 2: Seller receives new payment
balances[seller]= 1M USDC,debts[seller]= 1M USDCStep 3: Seller exploits the vulnerability
refundBySeller(newPaymentID)balances[seller]= 1M - 900K = 100Kbalances[seller]= 100K, debts[seller] = 1M USDC (unchanged!)Step 4: Seller tries to exit
withdraw()to extract remaining 100K_settleDebt(msg.sender)executes automaticallySupporting Material/References:
None