-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add ERC: Permissioned Pull #1401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
recurmj
wants to merge
23
commits into
ethereum:master
Choose a base branch
from
recurmj:ppo-ercs-draft
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+443
−0
Open
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
dabf664
Create erc-ppo-authorization-object.md
recurmj e0d9d46
Create erc-permissioned-pull-executor.md
recurmj b41aca8
Update and rename erc-ppo-authorization-object.md to erc-8103.md
recurmj 214bbd0
Update and rename erc-permissioned-pull-executor.md to erc-8102.md
recurmj 048fb18
Rename erc-8103.md to erc-8103.md
recurmj 4c1d55a
Update erc-8102.md
recurmj 44c4ff4
Update erc-8103.md
recurmj cdc1191
Update erc-8103.md
recurmj eb0d615
Update erc-8103.md
recurmj 0d24c1f
Update erc-8103.md
recurmj 28af7f8
Update erc-8102.md
recurmj 096fbdf
Update erc-8102.md
recurmj b328ff6
Update erc-8102.md
recurmj 16aea74
Update erc-8103.md
recurmj ecd24f1
Update erc-8102.md
recurmj 2e0496a
Update erc-8103.md
recurmj 98004fe
Update erc-8102.md
recurmj 323023e
Update erc-8103.md
recurmj 4ec4150
Update erc-8102.md
recurmj e76e48b
Update erc-8103.md
recurmj 840b79c
Update erc-8102.md
recurmj d295277
Update erc-8103.md
recurmj 7867c0f
Apply suggestions from code review
SamWilsn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,221 @@ | ||
| --- | ||
| eip: 8102 | ||
| title: Permissioned Pull | ||
| description: Pull-based token transfers authorized by signed, time-bounded Permissioned Authorization Objects (PPOs). | ||
| author: Mats Heming Julner (@recurmj) | ||
| discussions-to: https://ethereum-magicians.org/t/erc-8102-permissioned-pull/25931 | ||
| status: Draft | ||
| type: Standards Track | ||
| category: ERC | ||
| created: 2025-12-11 | ||
| requires: 712, 8103 | ||
| --- | ||
|
|
||
| ## Abstract | ||
|
|
||
| This EIP defines a standard **pull executor interface** for executing token transfers under a Permissioned Authorization Object (PPO) as specified in [ERC-8103](./eip-8103.md). | ||
|
|
||
| It provides a common, interoperable execution surface so wallets and integrators can reason about pull permissions consistently, and observers can track usage and revocation across executor implementations. | ||
|
|
||
| It specifies: | ||
|
|
||
| - the `Authorization` struct used at execution time, | ||
| - the `pull` function signature, | ||
| - required view functions for nonce usage and domain separation, | ||
| - canonical events and errors, | ||
| - normative execution rules (validation, revocation, transfer). | ||
|
Comment on lines
+16
to
+26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does a good job covering what your proposal is, but doesn't really include the purpose. Doesn't have to be long (that's what the motivation is for), but a sentence or two about why we need this proposal is usually good in the abstract. |
||
|
|
||
| ## Motivation | ||
|
|
||
| Given a shared authorization primitive (ERC-8103), we need a **common way to execute pulls**: | ||
|
|
||
| - so integrators can rely on consistent behavior across executors, | ||
| - so wallets can display and reason about pull permissions, | ||
| - so registries and observers can track usage and revocation. | ||
|
|
||
| Today, protocols implement their own ad-hoc execution logic. This EIP defines a minimal interface that: | ||
|
|
||
| - can be implemented by any executor contract, | ||
| - works with any [ERC-20](./eip-20.md) token, | ||
| - supports revocation and replay protection, | ||
| - cleanly separates authorization (off-chain) from execution (on-chain). | ||
|
|
||
| ## Specification | ||
|
|
||
| ### Interface | ||
|
|
||
| Compliant contracts MUST implement the following Solidity interface (or an ABI-compatible equivalent): | ||
|
|
||
| ```solidity | ||
| pragma solidity ^0.8.20; | ||
|
|
||
| interface IPermissionedPullExecutor { | ||
| struct Authorization { | ||
| address grantor; | ||
| address grantee; | ||
| address token; | ||
| uint256 maxPerPull; | ||
| uint256 validAfter; | ||
| uint256 validBefore; | ||
| bytes32 nonce; | ||
| } | ||
|
|
||
| // Views | ||
|
|
||
| /// @notice EIP-712 domain separator used for Authorization digests. | ||
| function domainSeparator() external view returns (bytes32); | ||
|
|
||
| /// @notice Returns true if the given nonce has been used for this grantor. | ||
| function isNonceUsed(address grantor, bytes32 nonce) external view returns (bool); | ||
|
|
||
| /// @notice Returns true if the given nonce has been locally canceled for this grantor. | ||
| function isNonceCanceled(address grantor, bytes32 nonce) external view returns (bool); | ||
|
|
||
| // Mutations | ||
|
|
||
| /// @notice Executes a token pull from grantor to msg.sender (must equal auth.grantee), | ||
| /// under the constraints encoded in the Authorization. | ||
| function pull( | ||
| uint256 amount, | ||
| Authorization calldata auth, | ||
| bytes calldata signature | ||
| ) external; | ||
|
|
||
| /// @notice Locally cancels a nonce for the caller, preventing future use. | ||
| function cancel(bytes32 nonce) external; | ||
|
|
||
| // Events | ||
|
|
||
| event PullExecuted( | ||
| address indexed grantor, | ||
| address indexed grantee, | ||
| address indexed token, | ||
| uint256 amount, | ||
| bytes32 structHash | ||
| ); | ||
|
|
||
| event NonceUsed( | ||
| address indexed grantor, | ||
| bytes32 indexed nonce | ||
| ); | ||
|
|
||
| event NonceCanceled( | ||
| address indexed grantor, | ||
| bytes32 indexed nonce | ||
| ); | ||
|
|
||
| // Errors | ||
|
|
||
| error BadSignature(); | ||
| error NotYetValid(); | ||
| error Expired(); | ||
| error OverCap(); | ||
| error NonceAlreadyUsed(); | ||
| error Revoked(); | ||
| error WrongGrantee(); | ||
| error ZeroAddress(); | ||
| error ZeroAmount(); | ||
| error TransferFailed(); | ||
| } | ||
| ``` | ||
|
|
||
| ### Execution Rules (Normative) | ||
|
|
||
| An implementation of `pull` MUST: | ||
|
|
||
| 1. **Compute digest** | ||
|
|
||
| - Compute `structHash` exactly as specified in ERC-8103. | ||
| - Compute `digest` as: | ||
|
|
||
| ```solidity | ||
| bytes32 digest = keccak256( | ||
| abi.encodePacked( | ||
| "\x19\x01", | ||
| domainSeparator(), | ||
| structHash | ||
| ) | ||
| ); | ||
|
|
||
| ``` | ||
|
|
||
| 2. **Verify signature** | ||
|
|
||
| - Recover signer from `digest` and `signature`. | ||
| - Signer MUST equal `auth.grantor`. | ||
| - Implementations MUST reject malleable signatures by enforcing [EIP-2](./eip-2.md) rules (low-`s` values and valid `v`), as specified in ERC-8103. | ||
| - Otherwise, revert with `BadSignature()`. | ||
|
|
||
| Implementations MAY support: | ||
| - EOAs via `ecrecover` | ||
| - Smart contract wallets via [EIP-1271](./eip-1271.md) (`isValidSignature`), but behavior MUST be documented. | ||
|
|
||
| 3. **Validate caller** | ||
|
|
||
| - `msg.sender` MUST equal `auth.grantee`. | ||
| - Otherwise, revert with `WrongGrantee()`. | ||
|
|
||
| 4. **Validate time window** | ||
|
|
||
| - If `block.timestamp < auth.validAfter`, revert `NotYetValid()`. | ||
| - If `block.timestamp >= auth.validBefore`, revert `Expired()`. | ||
|
|
||
| 5. **Validate amount and addresses** | ||
|
|
||
| - If `amount == 0`, revert `ZeroAmount()`. | ||
| - If `auth.token == address(0)`, revert `ZeroAddress()`. | ||
| - If `amount > auth.maxPerPull`, revert `OverCap()`. | ||
|
|
||
| 6. **Check revocation** | ||
|
|
||
| - If `isNonceCanceled(auth.grantor, auth.nonce)` returns true, revert `Revoked()`. | ||
| - If implementation also integrates a shared registry (out of scope of this EIP), it MAY check an external `isRevoked` view and MUST revert `Revoked()` if revoked. | ||
|
|
||
| 7. **Check and mark nonce used** | ||
|
|
||
| - If `isNonceUsed(auth.grantor, auth.nonce)` is true, revert `NonceAlreadyUsed()`. | ||
| - Otherwise, implementations MUST atomically mark `(auth.grantor, auth.nonce)` as used **before** any external token transfer. | ||
| - Nonce marking MUST follow checks-effects-interactions; state changes MUST precede external calls. | ||
|
|
||
| 8. **Execute transfer** | ||
|
|
||
| - Call `IERC20(auth.token).transferFrom(auth.grantor, auth.grantee, amount)`. | ||
| - If the transfer fails (according to the token’s semantics), revert `TransferFailed()`. | ||
|
|
||
| 9. **Emit events** | ||
|
|
||
| - Emit `NonceUsed(auth.grantor, auth.nonce)`. | ||
| - Emit `PullExecuted(auth.grantor, auth.grantee, auth.token, amount, structHash)`. | ||
|
|
||
| ### Domain Separation | ||
|
|
||
| - `domainSeparator()` MUST return a stable [EIP-712](./eip-712.md) domain separator used for all PPO digests. | ||
| - Deployments on different chains SHOULD use: | ||
| - `name` and `version` fields appropriate to the executor, and | ||
| - the chain’s `chainId` per EIP-712. | ||
|
|
||
| This prevents cross-executor and cross-chain replay unless the same executor domain is intentionally replicated. | ||
|
|
||
| ## Rationale | ||
|
|
||
| - **Separate executor standard**: Keeps execution semantics independent of any specific registry or higher-level coordination logic. | ||
| - **Single `pull` entry point**: Simple mental model for users and integrators. | ||
| - **Canonical errors**: Makes integration, testing, and UX (e.g., decoding revert reasons) easier. | ||
| - **Explicit nonces**: Allow clear auditability and prevent ambiguous replay behavior. | ||
|
|
||
| ## Backwards Compatibility | ||
|
|
||
| - Works with any ERC-20 token that supports `transferFrom`. | ||
| - Compatible with existing allowance patterns; PPO-based pulls can coexist with traditional approvals. | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| The rules required for secure behavior are specified normatively in the Specification section. This section discusses implications and common pitfalls: | ||
|
|
||
| - Implementations must consider token quirks (fee-on-transfer, non-standard return values) and should document behavior with such tokens. | ||
| - Time windows reduce exposure of leaked signatures; however, key security is still critical. | ||
| - Malicious grantees cannot exceed `maxPerPull` in a single call, but they can call `pull` multiple times if the authorization semantic allows. Systems requiring cumulative caps should implement them in higher layers or registries. | ||
|
|
||
| ## Copyright | ||
|
|
||
| Copyright and related rights waived via [CC0](../LICENSE.md). | ||
Oops, something went wrong.
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.