Skip to content

std: add call_with_default and std::evm::erc20 safe-transfer helpers#1514

Draft
cburgdorf wants to merge 1 commit into
argotorg:masterfrom
cburgdorf:call_with_default
Draft

std: add call_with_default and std::evm::erc20 safe-transfer helpers#1514
cburgdorf wants to merge 1 commit into
argotorg:masterfrom
cburgdorf:call_with_default

Conversation

@cburgdorf

Copy link
Copy Markdown
Collaborator

Typed external calls strictly decode returndata, so a -> bool message reverts when a token (USDT-style) executes but returns no data. Add two opt-in layers; the strict call<M> path is untouched:

  • Call::call_with_default<M> (+ Address::call_with_default): identical to call<M> except that a successful call with EMPTY returndata yields a caller-supplied default instead of a decode revert. Reverts still bubble verbatim and non-empty returndata is still decoded strictly. Caveat (as in Vyper's default_return_value): a call to a codeless address yields the default.

  • std::evm::erc20 with safe_transfer / safe_transfer_from / safe_approve (OpenZeppelin SafeERC20 semantics): tolerates empty returndata, reverts on a false return, and closes the no-code hole with an extcodesize check performed only on the empty-returndata path.

Fixture tests cover the full mock matrix for both layers: compliant, reverting (revert data asserted verbatim), no-return, false-returning, malformed (<32 bytes) returndata, and no-code targets, plus a non-bool return type exercising both the empty and non-empty paths.

@cburgdorf

Copy link
Copy Markdown
Collaborator Author

I'm not quite sure if we really want the erc20 stuff in the std lib or if that should be in an external ingot. But I think the call_with_default is a good API to deal with the different ways that a call might return.

@sbillig

sbillig commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

I'm not quite sure if we really want the erc20 stuff in the std lib or if that should be in an external ingot

For now I think std should have rich support for all common interfaces. We can optionally kick it out to an external ingot later.

Typed external calls strictly decode returndata, so a `-> bool` message
reverts when a token (USDT-style) executes but returns no data. Add two
opt-in layers; the strict `call<M>` path is untouched:

- `Call::call_with_default<M>` (+ `Address::call_with_default`): identical
  to `call<M>` except that a successful call with EMPTY returndata yields
  a caller-supplied default instead of a decode revert. Reverts still
  bubble verbatim and non-empty returndata is still decoded strictly.
  Caveat (as in Vyper's default_return_value): a call to a codeless
  address yields the default.

- `std::evm::erc20` with `safe_transfer` / `safe_transfer_from` /
  `safe_approve` (OpenZeppelin SafeERC20 semantics): tolerates empty
  returndata, reverts on a `false` return, and closes the no-code hole
  with an extcodesize check performed only on the empty-returndata path.

Fixture tests cover the full mock matrix for both layers: compliant,
reverting (revert data asserted verbatim), no-return, false-returning,
malformed (<32 bytes) returndata, and no-code targets, plus a non-bool
return type exercising both the empty and non-empty paths.
@cburgdorf
cburgdorf force-pushed the call_with_default branch from ab8f8a3 to 78a0d92 Compare July 16, 2026 15:11
@cburgdorf

Copy link
Copy Markdown
Collaborator Author

@sbillig sounds good. Then, this should be ready for human review.

@cburgdorf
cburgdorf requested a review from sbillig July 16, 2026 15:12
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.

2 participants