Rounding and billing fix#90
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
🟡 Heimdall Review Status
|
ilikesymmetry
left a comment
There was a problem hiding this comment.
Did not review tests, but looks like a relatively simple modification. Good principle learned: prefer custom net-amount args as calldata with math + checks against constraints versus passing in fields that derive the net-amount and restrict rounding preferences.
|
|
||
| // Pull tokens into this contract | ||
| IERC3009(token).receiveWithAuthorization({ | ||
| IERC3009(token) |
There was a problem hiding this comment.
are you on the latest version of foundry? wonder if there's a way to remove some of this lint thrashing... not blocking
| /// @param feeAmount Absolute fee in token units (must fall within payer-approved bounds) | ||
| /// @param feeReceiver Address to receive fees (should match the paymentInfo.feeReceiver unless that is 0 in which case it can be any address) | ||
| function capture(PaymentInfo calldata paymentInfo, uint256 amount, uint16 feeBps, address feeReceiver) | ||
| function capture(PaymentInfo calldata paymentInfo, uint256 amount, uint256 feeAmount, address feeReceiver) |
There was a problem hiding this comment.
one thing that came up in our original call was the question as to whether the new version of the AuthCaptureEscrow should retain its old interface too. I.e. should have have a net-new version of capture that offers the feeAmount while also offering a version with feeBps in case we want to migrate existing integrations to flow through the same AuthCaptureEscrow. A question for Fab and team. Given that we can run multiple authcaptureescrows in parallel, we would never be blocked on this, but might be good to consider one more time
Summary
Replace basis-points-encoded fees (
feeBps) with an absolutefeeAmount(in raw token units) oncapture()andcharge(). The payer-approvedminFeeBps/maxFeeBpsonPaymentInfoare unchanged and continue to bound the fee, but they are now applied to the operator-supplied absolute amount rather than used to compute it:This lets operators pass cent-aligned fees computed off-chain without losing precision to on-chain BPS truncation, while preserving the same payer-side guarantees.
Design doc: https://docs.google.com/document/d/1RCIXH0Q46XcwyYcu3027g9bKXHljrEX-y_ciBIdGNIQ
Implementation follows the approach used in PPS.
Changes
Contracts
AuthCaptureEscrow.capture(...)andcharge(...):uint16 feeBps→uint256 feeAmount._validateFeenow takesamount+ absolutefeeAmountand checks it against the min/max bounds derived from bps._distributeTokensuses the suppliedfeeAmountdirectly (no on-chain multiplication/division).FeeBpsOutOfRange(feeBps, minFeeBps, maxFeeBps)→FeeAmountOutOfRange(feeAmount, minFee, maxFee).PaymentCharged/PaymentCapturedevents:uint16 feeBps→uint256 feeAmount.ERC3009PaymentCollector,Permit2PaymentCollector) updated to the new signature.Docs
docs/Fees.md,docs/operations/Capture.md,docs/operations/Charge.mdrewritten to describe absolute-fee semantics with worked examples and updated error list.Tooling
[profile.ci]tofoundry.toml.Testing
Tests updated across the suite to pass absolute
feeAmountvalues instead of bps:test/src/PaymentEscrow/{authorize,capture,charge,refund,reentrancy,e2eCoinbaseSmartWallet}.t.soltest/src/collectors/SpendPermissionPaymentCollector.t.soltest/gas/gasBenchmark.t.soltest/base/AuthCaptureEscrowBase.sol,test/base/AuthCaptureEscrowSmartWalletBase.solFeeAmountOutOfRangebounds cases (below-min, above-max, and both-zero) are exercised incapture.t.sol/charge.t.sol.Backwards compatibility
Breaking public ABI change:
charge(),capture(),PaymentCharged, andPaymentCapturedall change parameter/field types. Integrators must migrate before upgrading.