Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
287 changes: 196 additions & 91 deletions ERCS/erc-0000.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
eip: 0000
title: Custom Encoding Layout for ERC-7730
description: Format to describes any non-standard byte encodings for ERC-7730

Check failure on line 4 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `description` should not contain `standard` (or similar words.)

error[preamble-re-description]: preamble header `description` should not contain `standard` (or similar words.) --> ERCS/erc-0000.md:4:13 | 4 | description: Format to describes any non-standard byte encodings for ERC-7730 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ prohibited pattern was matched | = info: the pattern in question: `(?i)standar\w*\b` = help: see https://ethereum.github.io/eipw/preamble-re-description/

Check failure on line 4 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `description` should not contain `standard` (or similar words.)

error[preamble-re-description]: preamble header `description` should not contain `standard` (or similar words.) --> ERCS/erc-0000.md:4:13 | 4 | description: Format to describes any non-standard byte encodings for ERC-7730 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ prohibited pattern was matched | = info: the pattern in question: `(?i)standar\w*\b` = help: see https://ethereum.github.io/eipw/preamble-re-description/
author: Alex Forshtat (@forshtat)
discussions-to: https://github.com/ethereum/ERCs/pull/1925

Check failure on line 6 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `discussions-to` should point to a thread on ethereum-magicians.org

error[preamble-re-discussions-to]: preamble header `discussions-to` should point to a thread on ethereum-magicians.org --> ERCS/erc-0000.md:6:16 | 6 | discussions-to: https://github.com/ethereum/ERCs/pull/1925 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required pattern was not matched | = info: the pattern in question: `^https://ethereum-magicians.org/t/[^/]+/[0-9]+$` = help: see https://ethereum.github.io/eipw/preamble-re-discussions-to/

Check failure on line 6 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

preamble header `discussions-to` should point to a thread on ethereum-magicians.org

error[preamble-re-discussions-to]: preamble header `discussions-to` should point to a thread on ethereum-magicians.org --> ERCS/erc-0000.md:6:16 | 6 | discussions-to: https://github.com/ethereum/ERCs/pull/1925 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required pattern was not matched | = info: the pattern in question: `^https://ethereum-magicians.org/t/[^/]+/[0-9]+$` = help: see https://ethereum.github.io/eipw/preamble-re-discussions-to/
status: Draft
type: Standards Track
category: ERC
Expand All @@ -26,7 +26,7 @@
## Specification


### `layout`
### `customEncoding`

The main mechanism for declaring any parameter whose contents cannot be expressed using Solidity-friendly ABI-encoded data structures.

Expand All @@ -36,22 +36,28 @@
{
"sendPacked(bytes data)": {
"fields": [
{
{
"path": "data",
"layout": {
"customEncoding": {
"type": "object",
"fields": [
{ "name": "to", "schema": { "type": "address" } },
{ "name": "amount", "schema": { "type": "uint", "bytes": 32 } }
]
}
{
"name": "to",
"schema": { "type": "address" }
},
{
"name": "amount",
"schema": { "type": "uint", "bytes": 32 }
}
]
}
]
}
}
]
}
}
```

Every `layout` node consumes a well-defined, computable number of bytes from its buffer.
Every `customEncoding` node consumes a well-defined, computable number of bytes from its buffer.
The `switch`'s path-sourced form is an exception as it reads an already-resolved value instead of parsing bytes.

### `sequence`
Expand All @@ -63,21 +69,44 @@
For example, for a byte array with each byte representing a different element:

```json
{ "runCommands(bytes commands)": {
"fields": [
{ "path": "commands", "layout": { "type": "sequence", "element": { "type": "uint", "bytes": 1 } } }
]
}}
{
"runCommands(bytes commands)": {
"fields": [
{
"path": "commands",
"customEncoding": {
"type": "sequence",
"element": { "type": "uint", "bytes": 1 }
}
}
]
}
}
```

Alternatively, `count` MAY give a literal element count, or `countFrom` may name a sibling field decoded earlier in the same object, sizing the sequence from a previously-decoded value – as with a Wormhole VAA's guardian-signature array, sized by its own `numSignatures` byte, in the Test Case below.

```json
{ "name": "signatures", "schema": { "type": "sequence", "countFrom": "numSignatures",
"element": { "type": "object", "fields": [
{ "name": "guardianIndex", "schema": { "type": "uint", "bytes": 1 } },
{ "name": "signature", "schema": { "type": "bytes", "length": 65 } }
]}}}
{
"name": "signatures",
"schema": {
"type": "sequence",
"countFrom": "numSignatures",
"element": {
"type": "object",
"fields": [
{
"name": "guardianIndex",
"schema": { "type": "uint", "bytes": 1 }
},
{
"name": "signature",
"schema": { "type": "bytes", "length": 65 }
}
]
}
}
}
```

### `object`
Expand All @@ -87,33 +116,62 @@
It can also be used as a stand-in for any other complex data structure in the formating process.

```json
{ "batchCalls(bytes transactions)": {
"fields": [
{ "path": "transactions", "layout": { "type": "sequence", "element": { "type": "object", "fields": [
{ "name": "operation", "schema": { "type": "uint", "bytes": 1 } },
{ "name": "to", "schema": { "type": "address" } }
]}}}
]
}}
{
"batchCalls(bytes transactions)": {
"fields": [
{
"path": "transactions",
"customEncoding": {
"type": "sequence",
"element": {
"type": "object",
"fields": [
{
"name": "operation",
"schema": { "type": "uint", "bytes": 1 }
},
{
"name": "to",
"schema": { "type": "address" }
}
]
}
}
}
]
}
}
```

An `object`'s field entries may carry `format`,`params`, `label` and `schema` parameters.
This allows a packed field to declare how it should be displayed using relative paths to that object's own sibling members.

```json
{ "name": "callData", "schema": { "type": "bytes" }, "label": "Execution", "format": "calldata",
"params": { "calleePath": "target", "amountPath": "value" } }
{
"name": "callData",
"schema": { "type": "bytes" },
"label": "Execution",
"format": "calldata",
"params": { "calleePath": "target", "amountPath": "value" }
}
```

### `bitfield`

A fixed-width value whose individual bits or bit ranges each carry independent, named meaning – unlike `object`, whose fields are always byte-aligned and never overlap.

```json
{ "type": "bitfield", "bytes": 20, "fields": [
{ "name": "beforeSwap", "bit": 7 },
{ "name": "poolId", "bits": [19, 8] }
]}
{
"type": "bitfield",
"bytes": 20,
"fields": [
{ "name": "beforeSwap", "bit": 7 },
{
"name": "poolId",
"bits": [19, 8]
}
]
}
```

Each entry is either `{name, bit}` (a single flag, decoded as `bool`) or `{name, bits: [hi, lo]}` (an inclusive bit range, decoded as an unsigned integer).
Expand All @@ -123,56 +181,82 @@
The mechanism that allows the decoding to choose the format based on a certain parameter decoded previously. Represents a common pattern of carrying the decoding format flag separately form the data being decoded.

```json
{ "execute(uint8 kind,bytes data)": {
"fields": [
{ "path": "data", "switch": {
"expression": { "path": "kind" },
"cases": {
"0x00": { "(address to,uint256 amount)": {
"fields": [
{ "path": "to", "label": "To" },
{ "path": "amount", "label": "Amount" }
]
}},
"0x01": { "(address from,address to,uint256 amount,uint256 deadline)": {
"fields": [
{ "path": "from", "label": "From" },
{ "path": "to", "label": "To" },
{ "path": "amount", "label": "Amount" },
{ "path": "deadline", "label": "Deadline", "format": "date", "params": { "encoding": "timestamp" } }
]
}}
{
"execute(uint8 kind,bytes data)": {
"fields": [
{
"path": "data",
"switch": {
"expression": { "path": "kind" },
"cases": {
"0x00": {
"(address to,uint256 amount)": {
"fields": [
{ "path": "to", "label": "To" },
{ "path": "amount", "label": "Amount" }
]
}
},
"0x01": {
"(address from,address to,uint256 amount,uint256 deadline)": {
"fields": [
{ "path": "from", "label": "From" },
{ "path": "to", "label": "To" },
{ "path": "amount", "label": "Amount" },
{
"path": "deadline",
"label": "Deadline",
"format": "date",
"params": { "encoding": "timestamp" }
}
]
}
}
}
}
}
}}
]
}}
]
}
}
```

When a `switch` case's tuple resolves to an array (`(...)[]`), its own `fields` can address that array's elements with `.[]` in place of the missing array name, e.g. `.[].callData`.
`#.` inside a case's own `fields` still resolves against the absolute root of the structured data.

`switch` can also appear as a `layout` node instead of a field-level key:
`switch` can also appear as a `customEncoding` node instead of a field-level key:

```json
{ "exampleCall(uint256 outputReference)": {
"fields": [
{ "path": "outputReference", "label": "Save result as", "layout": {
{
"exampleCall(uint256 outputReference)": {
"fields": [
{
"path": "outputReference",
"label": "Save result as",
"customEncoding": {
"type": "switch",
"expression": { "type": "uint", "bytes": 32, "mask": "0xfff0000000000000000000000000000000000000000000000000000000000000" },
"expression": {
"type": "uint",
"bytes": 32,
"mask": "0xfff0000000000000000000000000000000000000000000000000000000000000"
},
"cases": {
"0xba10000000000000000000000000000000000000000000000000000000000000": { "label": "Set by an earlier step, not known yet", "intent": "info" },
"0xba10000000000000000000000000000000000000000000000000000000000000": {
"label": "Set by an earlier step, not known yet",
"intent": "info"
},
"$default": { "format": "raw" }
}
}}
]
}
}
]
}
}
```

`mask` is available on any `switch` expression and is applied to the raw value before matching `cases`.
It lets a dispatch tag share space with unrelated bits, as with `UniversalRouter`'s revert-allowed flag in the Test Case below.

Inside a `layout` tree, `switch`'s inline form may also use `payloadFrom` in place of `$index`, naming a sibling ABI-decoded array to read at the same index as the enclosing `sequence` element.
Inside a `customEncoding` tree, `switch`'s inline form may also use `payloadFrom` in place of `$index`, naming a sibling ABI-decoded array to read at the same index as the enclosing `sequence` element.

### `operation`

Expand All @@ -193,16 +277,24 @@
This is an equivalent of `calldata` format from ERC-7730 for contracts that perform their own encoding of the calldata, or execute `delegatecall` and `staticcall` operations.

```json
{ "executeSendReward(address account,uint256 amount)": {
"fields": [
{ "path": "account", "label": "Account" },
{ "path": "amount", "label": "Amount" },
{ "interaction": {
"to": "target",
"signature": "grantReward(address,uint256)",
"args": [ { "path": "account" }, { "path": "amount" } ] } }
]
}}
{
"executeSendReward(address account,uint256 amount)": {
"fields": [
{ "path": "account", "label": "Account" },
{ "path": "amount", "label": "Amount" },
{
"interaction": {
"to": "target",
"signature": "grantReward(address,uint256)",
"args": [
{ "path": "account" },
{ "path": "amount" }
]
}
}
]
}
}
```

A wallet MUST resolve the matched target's own `intent`/`interpolatedIntent`/`fields` using the bound `args` values in place of that target's own decoded parameters, applying the same unknown-selector fallback if `to`'s descriptor has no entry matching `signature`.
Expand All @@ -214,23 +306,36 @@
A mechanism for element in a `sequence` to reference their position for indexing into other `sequence` or array-like parameters.

```json
{ "execute(bytes commands,bytes[] inputs)": {
"fields": [
{ "path": "commands", "layout": { "type": "sequence", "element": { "type": "uint", "bytes": 1 } } },
{ "path": "inputs[]", "switch": {
"expression": { "path": "commands[$index]" },
"cases": {
"0x00": { "(address to)": {
"fields": [
{ "path": "to", "label": "To" }
]
}}
{
"execute(bytes commands,bytes[] inputs)": {
"fields": [
{
"path": "commands",
"customEncoding": {
"type": "sequence",
"element": { "type": "uint", "bytes": 1 }
}
},
{
"path": "inputs[]",
"switch": {
"expression": { "path": "commands[$index]" },
"cases": {
"0x00": {
"(address to)": {
"fields": [
{ "path": "to", "label": "To" }
]
}
}
}
}
}
}}
]
}}
]
}
}
```
## Test Cases

Check failure on line 338 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

section `Test Cases` is out of order

error[markdown-order-section]: section `Test Cases` is out of order --> ERCS/erc-0000.md | 338 | ## Test Cases | = help: `Test Cases` should come after `Rationale` = help: see https://ethereum.github.io/eipw/markdown-order-section/

Check failure on line 338 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

section `Test Cases` is out of order

error[markdown-order-section]: section `Test Cases` is out of order --> ERCS/erc-0000.md | 338 | ## Test Cases | = help: `Test Cases` should come after `Rationale` = help: see https://ethereum.github.io/eipw/markdown-order-section/

### Safe{Wallet} - `MultiSend` Contract

Expand Down Expand Up @@ -274,7 +379,7 @@

Using ERC-0000, this input can be described for Clear Signing – see [Universal Router Example](../assets/erc-0000/example-universal-router.json).

### ERC-7579 `execute` function

Check failure on line 382 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

the first match of the given pattern must be a link

error[markdown-link-first]: the first match of the given pattern must be a link --> ERCS/erc-0000.md | 382 | ### ERC-7579 `execute` function | = info: the pattern in question: `(?i)(?:eip|erc)-([0-9])+` = help: see https://ethereum.github.io/eipw/markdown-link-first/

Check failure on line 382 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

the first match of the given pattern must be a link

error[markdown-link-first]: the first match of the given pattern must be a link --> ERCS/erc-0000.md | 382 | ### ERC-7579 `execute` function | = info: the pattern in question: `(?i)(?:eip|erc)-([0-9])+` = help: see https://ethereum.github.io/eipw/markdown-link-first/

The [ERC-7579](./erc-7579.md) `execute` function, which encodes the data in the following format:

Expand Down Expand Up @@ -322,7 +427,7 @@

Using ERC-0000, this input can be described for Clear Signing – see [Balancer Relayer Multicall Example](../assets/erc-0000/example-balancer-relayer-multicall.json) and [Balancer Relayer Library Example](../assets/erc-0000/example-balancer-relayer-library.json).

### ERC-7683 `open`

Check failure on line 430 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

the first match of the given pattern must be a link

error[markdown-link-first]: the first match of the given pattern must be a link --> ERCS/erc-0000.md | 430 | ### ERC-7683 `open` | = info: the pattern in question: `(?i)(?:eip|erc)-([0-9])+`

Check failure on line 430 in ERCS/erc-0000.md

View workflow job for this annotation

GitHub Actions / EIP Walidator

the first match of the given pattern must be a link

error[markdown-link-first]: the first match of the given pattern must be a link --> ERCS/erc-0000.md | 430 | ### ERC-7683 `open` | = info: the pattern in question: `(?i)(?:eip|erc)-([0-9])+`

The `AcrossOriginSettler` contract encodes the data in the following format:

Expand All @@ -339,7 +444,7 @@
#### What makes this encoding unusual

1. `orderData`'s ABI type is selected by `orderDataType`, a `bytes32` equal to the `keccak256` hash of the target tuple's own Solidity type string (`keccak256("AcrossOrderData(address inputToken,...)")`) rather than a small, contract-defined enum – an open-ended, hash-keyed dispatch.
2. Both the tag and the payload are already plain sibling ABI parameters of `open` itself, so no `layout` node is needed – only `switch`.
2. Both the tag and the payload are already plain sibling ABI parameters of `open` itself, so no `customEncoding` node is needed – only `switch`.

Using ERC-0000, this input can be described for Clear Signing – see [ERC-7683 Order Example](../assets/erc-0000/example-erc7683-order.json).

Expand Down Expand Up @@ -373,7 +478,7 @@
#### What makes this encoding unusual

1. `key.hooks` is an ordinary ABI `address` parameter, but its lowest 14 bits are individually meaningful flags (`beforeSwap`, `afterSwap`, `beforeAddLiquidity`, etc.) chosen by **mining a vanity address at hook-deployment time** – the address *is* the bitfield, with no separate flags parameter anywhere in the call.
2. Unlike every other Test Case here, this needs no `sequence` or `switch` at all – just a `bitfield` layout attached directly to an already-ABI-decoded scalar, to tell a signer which of a hook's callbacks it is trusting to run on every swap/mint/burn against this pool.
2. Unlike every other Test Case here, this needs no `sequence` or `switch` at all – just a `bitfield` `customEncoding` node attached directly to an already-ABI-decoded scalar, to tell a signer which of a hook's callbacks it is trusting to run on every swap/mint/burn against this pool.

Using ERC-0000, this input can be described for Clear Signing – see [Uniswap v4 Initialize Example](../assets/erc-0000/example-uniswap-v4-initialize.json).

Expand Down
Loading
Loading