Skip to content

Reset approval for USDT-like tokens in settlement encoding#4340

Open
anxolin wants to merge 4 commits intomainfrom
fix/usdt-approval-reset
Open

Reset approval for USDT-like tokens in settlement encoding#4340
anxolin wants to merge 4 commits intomainfrom
fix/usdt-approval-reset

Conversation

@anxolin
Copy link
Copy Markdown

@anxolin anxolin commented Apr 16, 2026

Summary

Adds some basic logic to reset the approval of the tokens when the driver encdes the transaction

Motivation

This order took 2 minutes to execute because during that time only baseline solver provided a solution.

https://explorer.cow.fi/orders/0xf462e720f5349836bb6089d08db5d11ca3e7c9096981e1e56fa798334d021b74daf84d22d4a0bffdec02052c24262c9f6cd31e3869e0dfd5

Auction12727305 has a solution by baseline which reverts because USDT has aprevious approval, and the contract requires a reset to zero before setting the new value.

See simulation https://www.tdly.co/shared/simulation/c3d99278-0b92-4f1b-a072-6e9fbfea85d6

During next 10 auction, baseline keeps trying and fails to settle. This is when brr succeeds.

Details

See https://etherscan.io/token/0xdAC17F958D2ee523a2206206994597C13D831ec7#code
image

  • USDT on Mainnet reverts when approve() is called with a non-zero value if the current allowance is already non-zero
  • The settlement encoding path was missing the reset-to-zero step, which could cause settlements involving USDT approvals to fail
  • Added Approval::requires_reset() to identify affected tokens, and emit approve(0) before the actual approval for those tokens
  • This matches the existing pattern in quote verification (quote.rs) and Solidity simulation contracts (Swapper.sol, Trader.sol)

Test plan

cargo test

USDT on Mainnet reverts when approving a non-zero value if the current
allowance is already non-zero. When the baseline solver needs to set an
approval for these tokens, emit an approve(0) first to reset the
allowance before the actual approval.

This matches the pattern already used in quote verification and the
Solidity simulation contracts (Swapper.sol, Trader.sol).
@anxolin anxolin requested a review from a team as a code owner April 16, 2026 20:17
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces logic to handle tokens that require an allowance reset to zero before setting a new value, specifically targeting USDT on Ethereum Mainnet. The changes include a new requires_reset method in the Approval struct and logic in the transaction encoding to prepend a revoke interaction when necessary. Feedback suggests expanding the list of tokens requiring this behavior, such as BNT, and highlights that hardcoding addresses may limit portability across different networks.

Comment thread crates/eth-domain-types/src/allowance.rs Outdated
@anxolin anxolin marked this pull request as draft April 16, 2026 20:26
/// current allowance is also non-zero. For these tokens the allowance must
/// be reset to 0 before setting a new value.
pub fn requires_reset(&self, chain: Chain) -> bool {
let tokens: &[Address] = match chain {
Copy link
Copy Markdown
Author

@anxolin anxolin Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea here, was to map all the tokens that requires reset. I thought there would be multiple tokens in multiple chains (this is why I went for this mapping of addresses to an tokens). When reviewing the code chain by chain, I saw that only mainnet has this behaviour, and I didn't find more cases.

So, not i'm tempted to not over-complicate this part and just check for a single address.

  • PRO of this approach: It will be easy to add USDT-like tokens
  • CONS: Overcomplicated for just checking a single friking address

Let me know what you prefer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

If we expect more tokens to be added we should make this configurable, though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRO of this approach: It will be easy to add USDT-like tokens

Better is to have it in a config like @fafk mentioned
But if this is something exclusive to one or two tokens, I think this is ok as is 🤔

@anxolin anxolin marked this pull request as ready for review April 16, 2026 21:23
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a mechanism to handle tokens, such as USDT on Ethereum Mainnet, that require an allowance reset to zero before a new allowance can be set. This is achieved by introducing a requires_reset check and updating the transaction encoding to insert a revoke interaction when necessary. Feedback was provided to include Chain::Hardhat in the reset logic to ensure consistent behavior during simulations that fork Mainnet.

/// be reset to 0 before setting a new value.
pub fn requires_reset(&self, chain: Chain) -> bool {
let tokens: &[Address] = match chain {
Chain::Mainnet => &[address!("dAC17F958D2ee523a2206206994597C13D831ec7")], // https://etherscan.io/token/0xdAC17F958D2ee523a2206206994597C13D831ec7
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requires_reset check currently only targets Ethereum Mainnet. However, CoW Protocol is often tested or simulated on Chain::Hardhat (line 17 of crates/chain/src/lib.rs), which frequently forks Mainnet. If a simulation is run on a Hardhat fork of Mainnet, the USDT contract will still exhibit the revert behavior, but the driver will not insert the reset interaction, potentially leading to misleading simulation results. Consider including Chain::Hardhat in the match arm if it's expected to mirror Mainnet behavior for these tokens.

Suggested change
Chain::Mainnet => &[address!("dAC17F958D2ee523a2206206994597C13D831ec7")], // https://etherscan.io/token/0xdAC17F958D2ee523a2206206994597C13D831ec7
Chain::Mainnet | Chain::Hardhat => &[address!("dAC17F958D2ee523a2206206994597C13D831ec7")], // https://etherscan.io/token/0xdAC17F958D2ee523a2206206994597C13D831ec7

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants