Python: Permit2 approval in CdpSmartWalletActionProvider.swap always reports failure (integer vs string receipt status)
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 74/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- python
- Ambito
- backend, blockchain, testing
Direzione di ricerca
Leggi CdpSmartWalletActionProvider.swap() e CdpSmartWalletProvider.wait_for_transaction_receipt per confermare il tipo di stato della ricevuta di approvazione. Poi esamina tests/action_providers/cdp/test_cdp_smart_wallet_action_provider.py, il cui run_until_complete simulato salta la coroutine, ed esegui la riproduzione descritta nell’issue. Il lavoro è completato quando lo stato intero di successo 1 viene accettato e il test di approvazione esegue il ramo pertinente.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
CdpSmartWalletActionProvider.swap() sends a Permit2 approval transaction and then
checks the receipt:
receipt = wallet_provider.wait_for_transaction_receipt(approval_tx_hash)
if receipt.status != "complete":
return {"success": False, "error": "Approval transaction failed"}
CdpSmartWalletProvider.wait_for_transaction_receipt returns
self._web3.eth.wait_for_transaction_receipt(...), a web3.py receipt whose status is
the integer 1 on success and 0 on revert. 1 != "complete" is always true, so a
successful approval is always reported as a failure.
This costs money rather than just being wrong. The approval is sent before the check
and approves the maximum:
args=[PERMIT2_ADDRESS, 2**256 - 1], # Max uint256
So each attempt spends gas and grants an unlimited Permit2 allowance on-chain, and the
caller is told it failed. An agent that retries approves again. The swap never completes
through this path.
Why the existing test does not catch it. In
tests/action_providers/cdp/test_cdp_smart_wallet_action_provider.py,
test_swap_with_approval_transaction sets
mock_loop.run_until_complete.return_value = async_result, so the _execute_swap
coroutine containing the check never runs — pytest reports
RuntimeWarning: coroutine '_execute_swap' was never awaited. Changing the mocked
receipt status from "complete" to 1 leaves the test passing, because neither value
is ever read.
Reproduction. A test that actually executes the coroutine:
"""EmberTail A1: the Permit2 approval path always reports failure.
cdp_smart_wallet_action_provider.swap() sends an approval transaction, then checks
receipt = wallet_provider.wait_for_transaction_receipt(approval_tx_hash)
if receipt.status != "complete":
return {"success": False, "error": "Approval transaction failed"}
CdpSmartWalletProvider.wait_for_transaction_receipt returns
self._web3.eth.wait_for_transaction_receipt(...), a web3.py receipt whose `status` is the
INTEGER 1 on success. `1 != "complete"` is always true, so a successful approval is
reported as a failure.
AgentKit's own test for this path mocks asyncio's run_until_complete to return a canned
success dict, so the coroutine containing this check never executes. This test runs it.
pytest tests/embertail_a1_approval_status.py -q
"""
from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, Mock, patch
import pytest
from coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider import (
CdpSmartWalletActionProvider,
)
from coinbase_agentkit.network import Network
FROM_TOKEN = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
TO_TOKEN = "0x4200000000000000000000000000000000000006"
def _provider_with(receipt_status):
"""A smart-wallet provider whose approval receipt carries `receipt_status`."""
wallet = Mock()
wallet.get_network.return_value = Network(protocol_family="evm", network_id="base-mainnet")
wallet.send_transaction.return_value = "0xapproval"
wallet.wait_for_transaction_receipt.return_value = Mock(status=receipt_status)
wallet._paymaster_url = None
# A quote that reports an allowance issue, so the approval branch is taken.
quote = Mock()
quote.liquidity_available = True
issues = Mock(spec=["allowance"]) # spec so hasattr(issues, "balance") is False
issues.allowance = Mock(required_allowance="1", current_allowance="0")
quote.issues = issues
quote.execute = AsyncMock(return_value=Mock(user_op_hash="0xuserop"))
smart_account = Mock()
smart_account.quote_swap = AsyncMock(return_value=quote)
# The user-operation receipt genuinely is a string status; only the approval
# receipt above comes from web3.py.
smart_account.wait_for_user_operation = AsyncMock(return_value=Mock(status="complete"))
wallet._get_smart_account = AsyncMock(return_value=smart_account)
@asynccontextmanager
async def _client(*_a, **_k):
yield Mock()
wallet.get_client.return_value = _client()
return wallet
def _run_swap(wallet):
provider = CdpSmartWalletActionProvider()
with (
patch.object(provider, "_get_client", return_value=wallet.get_client.return_value),
patch(
"coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider.get_token_details",
return_value={
"from_token_decimals": 6,
"to_token_decimals": 18,
"from_token_name": "USDC",
"to_token_name": "WETH",
},
),
patch(
"coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider.Web3"
) as web3,
):
web3.return_value.eth.contract.return_value.encodeABI.return_value = "0xdata"
return provider.swap(
wallet,
{
"from_token": FROM_TOKEN,
"to_token": TO_TOKEN,
"from_amount": "1",
"slippage_bps": 100,
},
)
def test_integer_receipt_status_reports_a_successful_approval_as_failed():
"""What the real provider returns: web3.py's integer status 1."""
result = _run_swap(_provider_with(1))
print(f"\n receipt.status = 1 (what web3.py returns) -> {result}")
assert "Approval transaction failed" in str(result), (
"expected the always-false comparison to report failure"
)
def test_string_receipt_status_succeeds():
"""The control: the status the code is written for. Shows the test harness is sound."""
result = _run_swap(_provider_with("complete"))
print(f"\n receipt.status = 'complete' (what the code expects) -> {result}")
assert "Approval transaction failed" not in str(result)
Results: with status = 1, the result is
{"success": false, "error": "Approval transaction failed"}. With status = "complete"
it passes the approval branch.
Suggested fix. Accept both shapes:
status = getattr(receipt, "status", None)
if status not in (1, "complete", "success"):
return {"success": False, "error": "Approval transaction failed"}
and give the existing test a real event loop so the branch is covered.
Note that line 322 in the same function compares a user operation receipt to
"complete", which is correct — only the web3.py receipt at the approval step is
affected.
Found by EmberTail (https://embertail.xyz). The analysis was assisted by AI tools; the result above comes from running the code. We would be glad to open a pull request with the fix and a covering test.
Abhinav
EmberTail
- Lingua principale
- TypeScript
- Stelle
- 1.3k
- Fork
- 871
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di coinbase/agentkit
-
Moonwell redeem fails on Base Sepolia and uses 18 decimals for cbBTCForse già presa @sebxt l’ha presa 5 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
coinbase/agentkit#1578 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
ERC20 transfer reports "Transferred" when the transaction reverts (receipt status ignored)Forse già presa @AgenticFinanceGraph l’ha presa 13 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 65/100
coinbase/agentkit#1558 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[TypeScript] `ViemWalletProvider` rejects a wallet client from a newer viem than the exactly pinned one (TS2345, `tokens` missing on `Client`)Forse già presa @PrabalParihar l’ha presa 4 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
coinbase/agentkit#1532 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
coinbase-agentkit 0.7.x fails on fresh install: imports solana.rpc.api, which solana>=0.37 removedForse già presa @JulienKervarrec l’ha presa 32 giorni fa. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
I maintainer di solito rispondono entro 1 giorno
-
[Showcase / Integration] Algernon x402 Oracle: B2B Compliance & Security Verification on BaseAperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 53/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di coinbase/agentkit
Issue simili
-
[good first issue, hacktoberfest] 🏯 Hacktoberfest Task - Add new Japan Fact 898 (good-first-issue)Apertacommunity first-timers-only good first issue hacktoberfest help wanted low hanging fruit up-for-grabs
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 86/100
lingdojo/kana-dojo#33145 · 1 commento · 5 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 76/100
code-yeongyu/senpi#4278 ·
I maintainer di solito rispondono entro 1 giorno
-
[Bug] Activity | The activity list ignores taskIds and returns the activities of every taskForse già presa @NdekoCode l’ha presa oggi. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
ever-co/ever-gauzy#11469 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
griffinwork40/agent-afk#4408 ·
I maintainer di solito rispondono entro 1 giorno