Skip to content

Commit 315ed68

Browse files
Multiple Native Tokens
--------- Co-authored-by: Paul Razvan Berg <[email protected]>
1 parent a9c9d36 commit 315ed68

File tree

1 file changed

+352
-0
lines changed

1 file changed

+352
-0
lines changed

EIPS/_eip-mnt.md

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
---
2+
title: Multiple Native Tokens
3+
description: Fungible tokens with native-like properties in the EVM
4+
author: Paul Razvan Berg (@PaulRBerg), Iaroslav Mazur (@IaroslavMazur)
5+
discussions-to: TBD
6+
status: Draft
7+
type: Standards Track
8+
category: Core
9+
created: 2024-11-07
10+
requires: 2718, 2930
11+
---
12+
13+
## Abstract
14+
15+
This EIP introduces Multiple Native Tokens (MNTs, or just NTs) as a backward-compatible extension of the EVM, enabling
16+
fungible tokens to function with native-like properties. Unlike ERC-20 tokens, MNTs are
17+
integrated into the global VM state, allowing for direct transfers through newly defined opcodes and eliminating the
18+
traditional two-step "approve" and "transfer" pattern. Ether (ETH) is designated as one of the MNTs while retaining its
19+
unique role as the exclusive token for gas fee payments. The EIP introduces the new opcodes `MINT`, `BURN`, `BALANCEOF`,
20+
and `CALLVALUES` to manage NT supply and query account balances. Additional opcodes such as `NTCALL`, `NTCALLCODE`,
21+
`NTCREATE`, and `NTCREATE2` facilitate NT transfers and NT-infused contract creation. Existing opcodes and transactions
22+
are adapted to refer to the default NT, which is `ETH`. A new transaction type is introduced in which the `value` field
23+
is replaced with a collection of (`token_id`, `token_amount`) pairs, enabling multi-token transactions. By embedding
24+
tokens natively in the EVM, this proposal aims to improve the user experience of token management and facilitate
25+
advanced innovating use-cases, particularly on L2s.
26+
27+
## Motivation
28+
29+
Implementing Multiple Native Tokens in the EVM offers several compelling advantages over traditional ERC-20 smart
30+
contracts, fostering innovation and improving user experience.
31+
32+
### Native Support for Financial Instruments
33+
34+
Storing token balances in the VM state unlocks the potential for sophisticated financial instruments to be implemented
35+
at the protocol level. This native integration facilitates features such as recurring payments and on-chain incentives
36+
without the need for complex smart contract interactions. For instance, platforms could natively provide yield to token
37+
holders or execute airdrops natively, similar to how rollups like Blast offer yield for ETH holders. Extending this
38+
capability to any token enhances utility and encourages users to engage more deeply with the network.
39+
40+
### Elimination of Two-Step "Approve" and "Transfer"
41+
42+
By embedding token balances into the VM state, the cumbersome process of approving tokens before transferring them is
43+
eliminated. Token transfers can be seamlessly included into smart contract calls, simplifying transaction flows and
44+
reducing the number of steps users must take. This streamlined process not only enhances the user experience but also
45+
reduces gas costs associated with multiple contract calls, making interactions more efficient and cost-effective.
46+
47+
### Encouraging Experimentation on Layer 2 Solutions
48+
49+
The proposed model aims to encourage innovation on Ethereum L2s by providing a flexible framework for token management.
50+
EVM rollups can experiment with this design to develop new paradigms in decentralized finance (DeFi), gaming, and
51+
beyond. By enabling tokens to have native properties and interactions, developers are empowered to explore features that
52+
could lead to more robust and versatile applications. This experimentation is vital for the evolution of the Ethereum
53+
ecosystem, as it fosters advancements that can benefit the broader community.
54+
55+
## Prior Art
56+
57+
This EIP has been inspired by FuelVM's
58+
[Native Assets](https://docs.fuel.network/docs/sway/blockchain-development/native_assets/) design, as well as its
59+
[SRC-20: Native Asset](https://docs.fuel.network/docs/sway-standards/src-20-native-asset/) standard.
60+
61+
The key distinction from Fuel's Native Assets is that, in this EIP, each contract is limited to a single native token
62+
(NT). A contract can mint only one NT, and the contract's address itself serves as the NT's ID. Basically, this EIP is
63+
meant to be an alternative to ERC-20.
64+
65+
## Specification
66+
67+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT
68+
RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
69+
70+
### State Changes
71+
72+
A global `token_id` -> `token_supply` mapping is introduced to keep track of the existing NTs and their circulating
73+
supply. This mapping is also used to validate the supported NTs. An NT exists if and only if its ID can be found in the
74+
mapping. The supply of an NT increases as a result of executing the `MINT` opcode, and decreases as a result of
75+
executing the `BURN` opcode. The `token_id` of an NT is the Ethereum address of its associated smart contract.
76+
77+
`ETH` becomes the 'Base Token', with its ID and supply initialized to zero. `ETH` is the only NT whose supply is not
78+
tracked explicitly, i.e., its supply is determined just like it currently is.
79+
80+
For increased security and consistency, the token contracts representing the NTs SHOULD NOT use an upgradeability
81+
pattern.
82+
83+
### Stack
84+
85+
Since the EVM stack can support only up to 1024 elements, there is a natural limit to the number of tokens that can be
86+
transferred during the execution of a single opcode. Given that a token pair takes 2 stack slots, while the number of
87+
transferred tokens occupies another one, the maximum number of tokens that can be transferred can be calculated as
88+
follows:
89+
90+
$$
91+
(1024 - 1 - N) / 2
92+
$$
93+
94+
Where $N$ is the number of non-NT-related arguments.
95+
96+
For example, a single `NTCALL` opcode can transfer up to (1024 - 1 - 6) / 2 = 508 tokens.
97+
98+
### New Opcodes
99+
100+
#### `MINT` - `0xb0`
101+
102+
- **Gas**: Constant
103+
- **Stack inputs**:
104+
- `recipient`: the address to which the minted tokens are credited
105+
- `token_amount`
106+
- **Stack outputs**:
107+
- `success`: a Boolean indicating success
108+
109+
#### `BURN` - `0xb1`
110+
111+
- **Gas**: Constant
112+
- **Stack inputs**:
113+
- `burner`: the address from which the tokens are burned
114+
- `token_amount`
115+
- **Stack outputs**:
116+
- `success`: a Boolean indicating success
117+
118+
Note: the burner MUST have an NT balance that is at least equal to `token_amount`.
119+
120+
#### `BALANCEOF` - `0xb2`
121+
122+
- **Gas**: Constant
123+
- **Stack inputs**:
124+
- `token_id`: the ID of the NT to query the balance of
125+
- `address`: the address to query the balance of
126+
- **Stack outputs**:
127+
- `balance`: the NT balance of the given address
128+
129+
#### `CALLVALUES` - `0xb3`
130+
131+
- **Gas**: Dynamic, proportional to the number of NTs transferred by the executing call
132+
- **Stack inputs**: None
133+
- **Stack outputs**:
134+
- `transferred_tokens_length`: the number of transferred tokens
135+
- The list of `transferred_tokens_length` (`token_id`, `token_amount`) pairs
136+
137+
#### `NTCALL` - `0xb4`
138+
139+
- **Gas**: Dynamic, proportional to the number of transferred NTs
140+
- **Stack inputs**:
141+
142+
- `gas`: amount of gas to send to the sub context to execute. The gas that is not used by the sub context is returned
143+
to this one
144+
- `address`: the account which context to execute
145+
- `transferred_tokens_length`: the number of transferred tokens
146+
- The list of `transferred_tokens_length` (`token_id`, `token_amount`) pairs
147+
- `argsOffset`: byte offset in the memory in bytes, the calldata of the sub context
148+
- `argsSize`: byte size to copy (size of the calldata)
149+
- `retOffset`: byte offset in the memory in bytes, where to store the return data of the sub context
150+
- `retSize`: byte size to copy (size of the return data)
151+
152+
- **Stack outputs**:
153+
- `success`: return 0 if the sub context reverted, 1 otherwise
154+
155+
#### `NTCALLCODE` - `0xb5`
156+
157+
- **Gas**: Dynamic, proportional to the number of transferred NTs
158+
- **Stack inputs**:
159+
160+
- `gas`: amount of gas to send to the sub context to execute. The gas that is not used by the sub context is returned
161+
to this one
162+
- `address`: the account which code to execute
163+
- `transferred_tokens_length`: the number of transferred tokens
164+
- The list of `transferred_tokens_length` (`token_id`, `token_amount`) pairs
165+
- `argsOffset`: byte offset in the memory in bytes, the calldata of the sub context
166+
- `argsSize`: byte size to copy (size of the calldata)
167+
- `retOffset`: byte offset in the memory in bytes, where to store the return data of the sub context
168+
- `retSize`: byte size to copy (size of the return data)
169+
170+
- **Stack outputs**:
171+
- `success`: return 0 if the sub context reverted, 1 otherwise
172+
173+
#### `NTCREATE` - `0xb6`
174+
175+
- **Gas**: Dynamic, proportional to the number of transferred NTs
176+
- **Stack inputs**:
177+
178+
- `transferred_tokens_length`: the number of transferred tokens
179+
- The list of `transferred_tokens_length` (`token_id`, `token_amount`) pairs
180+
- `offset`: byte offset in the memory in bytes, the initialization code for the new account
181+
- `size`: byte size to copy (size of the initialization code)
182+
183+
- **Stack outputs**:
184+
- `address`: the address of the deployed contract, 0 if the deployment failed.
185+
186+
#### `NTCREATE2` - `0xb7`
187+
188+
- **Gas**: Dynamic, proportional to the number of transferred NTs
189+
- **Stack inputs**:
190+
191+
- `transferred_tokens_length`: the number of transferred tokens
192+
- The list of `transferred_tokens_length` (`token_id`, `token_amount`) pairs
193+
- `offset`: byte offset in the memory in bytes, the initialization code of the new account
194+
- `size`: byte size to copy (size of the initialization code)
195+
- `salt`: 32-byte value used to create the new account at a deterministic address
196+
197+
- **Stack outputs**:
198+
- `address`: the address of the deployed contract, 0 if the deployment failed
199+
200+
### Existing Opcodes
201+
202+
#### Balance Query
203+
204+
The following opcodes are adapted to query the balance of the default NT, which is `ETH`:
205+
206+
- `BALANCE`
207+
- `SELFBALANCE`
208+
- `CALLVALUE`
209+
210+
#### Contract Creation
211+
212+
The `value` field in the following opcodes will refer to the default NT, which is `ETH`:
213+
214+
- `CREATE`
215+
- `CREATE2`
216+
217+
#### Calling Contracts
218+
219+
The `value` field in the following opcodes will refer to the default NT, which is `ETH`:
220+
221+
- `CALL`
222+
- `CALLCODE`
223+
224+
### Transaction structure
225+
226+
### Parameters
227+
228+
| Parameter | Value |
229+
| ----------------------- | ---------------------------------- |
230+
| `MNT_TX_TYPE` | > 0x03 ([EIP-4844](./eip-4844.md)) |
231+
| `PER_NATIVE_TOKEN_COST` | `2500` |
232+
233+
#### New Transaction
234+
235+
A new [EIP-2718](./eip-2718.md) transaction is introduced with `TransactionType` = `MNT_TX_TYPE`.
236+
237+
The [EIP-2718](./eip-2718.md) `TransactionPayload` for this transaction is:
238+
239+
```
240+
rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, native_tokens_list, data, access_list, signature_y_parity, signature_r, signature_s])
241+
```
242+
243+
The `signatureYParity, signatureR, signatureS` elements of this transaction represent a secp256k1 signature over
244+
`keccak256(0x01 || rlp([chainId, nonce, gasPrice, gasLimit, to, native_tokens_list, data, accessList]))`.
245+
246+
The `native_tokens_list` element consists of the `transferred_tokens_length` variable, which specifies the number of
247+
tokens being transferred, followed by the (`token_id`, `token_amount`) pairs.
248+
249+
For the transaction to be valid, `native_tokens_list` must be of type `[{2 bytes}, [{32 bytes},{32 bytes},...]]`.
250+
251+
The [EIP-2718](./eip-2718.md) `ReceiptPayload` for this transaction is
252+
`rlp([status, cumulativeGasUsed, logsBloom, logs])`.
253+
254+
#### Gas Costs
255+
256+
The intrinsic cost of the new transaction follows the model defined in [EIP-2930](./eip-2930.md), specifically
257+
`21000 + 16 * non-zero calldata bytes + 4 * zero calldata bytes + 1900 * access list storage key count + 2400 * access list address count`.
258+
259+
In addition, a cost of `PER_NATIVE_TOKEN_COST` \* `transferred_tokens_length` is charged for each token in
260+
`native_tokens_list`.
261+
262+
#### EVM Transactions
263+
264+
All existing EVM transactions remain valid.
265+
266+
- A zero `value` is equivalent to an empty `transferred_tokens` list.
267+
- A non-zero `value` is equivalent to a list containing a single pair with `ETH`'s `token_id` (which is zero) and the
268+
`value` as `token_amount`.
269+
270+
## Rationale
271+
272+
An alternative to the proposed opcode-based approach was to use precompiles, which would have worked as follows:
273+
274+
- No new opcodes.
275+
- Existing EVM opcodes would remain unchanged.
276+
- As a result, no modifications to smart contract languages would be required.
277+
278+
However, the precompile-based approach also has disadvantages:
279+
280+
- It would require major architectural changes to the EVM implementation, as precompiles are not designed to be
281+
stateful.
282+
- Users would be required to handle low-level data manipulations to encode inputs to precompile functions and decode
283+
their outputs. This would lead to a subpar user experience.
284+
285+
Considering this, the opcode-based approach was chosen for its simplicity and efficiency in handling NTs at the EVM
286+
level.
287+
288+
## Backwards Compatibility
289+
290+
This EIP does not introduce any breaking changes to the existing Ethereum protocol. However, it adds substantial new
291+
functionality that requires consideration across various layers of the ecosystem.
292+
293+
- Front-end Ethereum libraries, such as web3.js and wagmi, will need to adapt to the new transaction structures introduced
294+
by MNTs. These libraries must update their interfaces and transaction handling mechanisms to accommodate the inclusion
295+
of token transfers within smart contract calls and the absence of traditional "approve" and "transfer" functions.
296+
- Smart contract languages like Solidity will need to incorporate support for the newly introduced opcodes associated with
297+
MNTs. This includes adapting compilers and development environments to recognize and compile contracts that interact
298+
with tokens stored in the VM state.
299+
- Additionally, Ethereum wallets, block explorers, and development tools will require updates to fully support MNTs.
300+
Wallets must be capable of managing multiple native token balances, signing new types of transactions, and displaying
301+
token information accurately. Explorers need to parse and present the new transaction formats and token states, while
302+
development tools should facilitate debugging and deployment in this enhanced environment.
303+
304+
To ensure a smooth transition, the authors recommend a gradual deployment process. This phased approach allows
305+
developers, users, and infrastructure providers to adapt incrementally. By introducing MNTs in stages, the ecosystem can
306+
adjust to the new functionalities, verify compatibility, and address any issues that arise, ensuring that every
307+
component behaves correctly throughout the integration period.
308+
309+
## Reference Implementation
310+
311+
The authors have begun implementing this EIP in Sablier's [SabVM repository](https://github.com/sablier-labs/sabvm), a
312+
fork of [REVM](https://github.com/bluealloy/revm) that supports MNTs. Unlike the proposed EIP, SabVM uses precompiles
313+
instead of opcodes because that was easier to implement at the time.
314+
315+
A particularly relevant resource in SabVM is this
316+
[draft Solidity spec](https://github.com/sablier-labs/sabvm/discussions/87), which details support for MNTs in Solidity.
317+
318+
Additionally, the [SRFs repository](https://github.com/sablier-labs/SRFs) (Sablier Requests for Comments) hosts the
319+
SRF-20 standard: an application-level standard designed to replicate the ERC-20 standard specifically for MNTs.
320+
321+
| Name | Link | Description |
322+
| ------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
323+
| SabVM | [github.com/sablier-labs/sabvm](https://github.com/sablier-labs/sabvm) | Fork of REVM that implements MNTs with precompiles |
324+
| SRFs | [github.com/sablier-labs/SRFs](https://github.com/sablier-labs/SRFs) | Sablier Requests for Comments |
325+
| stdlib | [github.com/sablier-labs/stdlib](https://github.com/sablier-labs/stdlib) | Sablier Standard Library, providing precompiles, standards, and testing utilities |
326+
327+
## Security Considerations
328+
329+
This EIP introduces a few security risks related to malicious tokens and system integrity. Below are the key
330+
considerations and how they are mitigated.
331+
332+
1. **Malicious or Misbehaving Native Tokens**: a token that becomes a Native Token (NT) may later behave maliciously,
333+
causing disruptions in the network.
334+
335+
Mitigation: Users are encouraged to prefer using immutable, non-upgradeable NTs.
336+
337+
2. **Cross-Contract NT Transfers**: inter-contract NTs transfers could lead to lost tokens if contracts are not properly
338+
equipped to handle multiple tokens.
339+
340+
Mitigation: Contracts must validate token transfers correctly, with guidance for developers on standard patterns to
341+
ensure safe cross-contract interactions. Existing EVM contracts should be audited and updated to handle NTs.
342+
343+
3. **Gas Bombs**: Users may become stuck if they hold an excessive number of NTs, causing the gas required for
344+
processing their transactions to exceed the block gas limit.
345+
346+
Mitigation: All introduced opcodes operate with constant-time complexity. The stack limit of 1024 elements effectively
347+
prevents the creation of gas bombs when calling contracts. Although an opcode for querying all NT balances of an account
348+
was initially considered, it was ultimately omitted to eliminate the risk of gas bomb exploits.
349+
350+
## Copyright
351+
352+
Copyright and related rights waived via [CC0](../LICENSE.md).

0 commit comments

Comments
 (0)