Fix benchmark build break and review nits on ERC-20 precompile transfers - #1985
Open
F-OBrien wants to merge 1 commit into
Open
Fix benchmark build break and review nits on ERC-20 precompile transfers#1985F-OBrien wants to merge 1 commit into
F-OBrien wants to merge 1 commit into
Conversation
`SettlementFnTrait::transfer_funds` takes a sixth `bench_base_weight` parameter under `#[cfg(feature = "runtime-benchmarks")]`. Both new call sites in the precompile passed five arguments, so any runtime built with `--features runtime-benchmarks` failed with E0061. Pass `false` under the same cfg, matching every other call site in the workspace and the `base_transfer_asset` call these replaced. Also: drop a stale comment referring to `base_transfer_asset`, use `is_some()` over `if let Some(_)`, drop an unnecessary clone, and shorten the deferred-transfer revert string to a documented stable sentinel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on top of #1984 (
precompiles-fix), targeting that branch rather thandevelop.The deferred-transfer fix in #1984 is correct — I traced
Ok(None)back throughbase_try_execute_instruction(pallets/settlement/src/lib.rs:3834) andexecute_instruction_retryable(:2003-2006) to confirm it genuinely means "tokens moved",with no "executed but silently failed" case leaking through. Reverting before the
Transferlog is emitted is the right shape, and the allowance spend is inside the rolled-back scope,
so
transferFromno longer burns allowance on the deferred path.This PR fixes a build break and applies review nits on top.
Blocking fix: benchmark builds don't compile
SettlementFnTrait::transfer_fundsdeclares a 6th parameter under#[cfg(feature = "runtime-benchmarks")](primitives/src/traits/settlement.rs:75-82). Both newcall sites in
erc20.rspassed 5 arguments.pallets/precompiles/Cargo.tomlhas aruntime-benchmarksfeature that enablespolymesh-primitives/runtime-benchmarks, and all threeruntimes enable
pallet-precompiles/runtime-benchmarks, so the cfgs are in lockstep.Confirmed against
cargo check -p polymesh-runtime-develop --features runtime-benchmarks:Fixed by passing
#[cfg(feature = "runtime-benchmarks")] falseat both sites, matching everyother call site in the workspace (
pallets/nft/src/lib.rs:790,pallets/asset/src/lib.rs:2908,and the
base_transfer_assetcall this replaced). The default build was always clean — this ispurely feature-gated, which raises a question: does CI run a
--features runtime-benchmarksjob? If not, this class of break reaches
developunnoticed.Nits
base_transfer_asset, which is no longer called.if let Some(_) = inst_id→inst_id.is_some()(clippyredundant_pattern_matching).from.clone()— the earlier borrow has ended.ERR_INST_NOT_EXECUTEDrevert string to a stable sentinel(
"TransferDeferred: receiver affirmation or mediator required") and documented it. Revertdata is returned to the caller on every failed call, so ~110 chars of prose is worth trimming.
Reviewed and deliberately left alone
The
with_transactionwrapper inmod.rs— kept. It is redundant, but harmless, and@HenriqueNogara mentioned test-based reasons for adding it, so it should not be removed without seeing
that test case. The analysis:
exec.rs:1379, insideStack::run(). Everyentry path (
run_callat:861, nested calls at:939/:1915/:2131, instantiate at:2054) funnels throughrun().run()wraps its whole body — including that call — inwith_transactionand rolls back onthe REVERT flag (
exec.rs:1456-1466).Instance::callsets that flag for bothError::Revertand
Error::Panic(precompiles.rs:314-320);Error::Errorpropagates asErrand also hitsthe Rollback arm.
EventCount::put,Events::append,EventTopics::appendinframe_system/src/lib.rs:1889-1918), so they are inside that sameoverlay transaction and are discarded too.
So the revert alone already undoes storage and substrate events. What the wrapper does not fix
is the one thing that genuinely is not rolled back:
Ext::deposit_eventalso appends to theenvironmental!AccumulateReceiptbuffer (evm/block_storage.rs:47,124-128), which has notransactional behaviour — a log emitted before a revert stays in the eth receipt and bloom no
matter how many
with_transactionlayers wrap it.Best reconstruction: the "instruction persisted" behaviour predates the fix, because the
precompile used to return
Okon the deferred path — no revert, so no rollback. Both changeslanded in one commit, which would make the wrapper look load-bearing. @HenriqueNogara — if you have the
failing test, please share it; if it was receipt/log-based, the fix came from reordering the
emit after the check, not from the wrapper. Cost of keeping it is one overlay layer per call
(depth 25 x 2, against frame_support's limit of 255), so there's no urgency either way.
No gas refund on the error path. I initially suggested making
adjust_gassymmetric on theErrarm; that was wrong. The settlement pallet charges its benchmarked base weight atpallets/settlement/src/lib.rs:1606-1609, afterensure_origin_call_permissions, the DIDlookups and the
SenderSameAsReceivercheck. A call failing early therefore hasweight_meter.consumed() == 0, and refunding to that would make failing transfers nearly freewhile still performing real DB reads. The
transfer_fundsextrinsic doesn't refund on erroreither. Current behaviour is correct.
transferno longer emitsCreatedAssetTransfer. Bypassingbase_transfer_asset(
pallets/asset/src/lib.rs:2911-2918) means EVM-initiated transfers now emitFundsTransferred/settlement events instead. Confirmed as intended — it makes
transferconsistent withtransferFrom— but flagging it here so it's a recorded decision, and it's worth checkingagainst middleware/subquery before this reaches a public network.
Follow-ups, not in this PR
pallets/runtime/tests/. Deliberately out of scope here, but it should not ship without atleast: receiver with
MandatoryReceiverAffirmation→transferreverts, balances unchanged,no
Transferlog, allowance untouched; asset withMandatoryMediators→ same; happy pathemits exactly one
Transfer.ExecutionMode::RequireImmediatein the settlement pallet. Today the deferred path doesall the work — creates the instruction, affirms the sender leg, locks the funds, spends the
allowance — and then throws it away. Every EVM transfer of a mediator-configured asset pays
full weight to fail. A pre-check before instruction creation, using
Asset::<T>::skip_asset_holder_affirmationandMandatoryMediators(the only two deferralsources today,
pallets/settlement/src/lib.rs:1950-1957), would give both a cheap failure anda specific error. It would also move the invariant into the runtime so
call_runtime/ink!callers get it too (F1 in
allowance-transfer-funds-smart-contract-review.md). Needs adecision — it touches settlement, asset, nft and benchmarks.
precompiles::Erroronly hasRevert(Revert)(alloy'sRevertisspecifically
Error(string)),Panic(PanicKind)andError(ExecError). There is no variantcarrying raw ABI-encoded revert data, so
error TransferDeferred()cannot be returned withoutan upstream
pallet-revivechange. Worth raising upstream.AccumulateReceiptnote above.A contract that calls this precompile inside
try/catchand then succeeds produces a receiptwith status 1 that still carries the phantom
Transferlog. Worth an upstream report plus aregression test asserting
eth_getTransactionReceiptlogs are empty in that scenario.real transfer paths: zero-value transfers revert (ERC-20 says they MUST succeed and emit
Transfer); self-transfers revert (SenderSameAsReceiverat:1596,SameSenderReceiverat:2890); contract callers need a DID, sincetransfer_fundsrunsensure_origin_call_permissionsagainst the contract's derivedAccountId, which rules outmost vault/router composability. Plus
decimals()hardcoded to 6 and the unimplementedpermit/nonces/DOMAIN_SEPARATOR.🤖 Generated with Claude Code