forked from ethereum/EIPs
-
Notifications
You must be signed in to change notification settings - Fork 0
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
Multiple Native Tokens #1
Merged
Changes from 9 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
48c2ebb
feat: MNAs EIP V1
IaroslavMazur f20c886
feat: add opcodes specification
IaroslavMazur 8e985b3
feat: tx structure spec
IaroslavMazur 541d195
feat: enhance wording
IaroslavMazur 8c8b663
feat: Rationale
IaroslavMazur 83ffa0f
feat: Backwards Compatibility
IaroslavMazur fa3fa41
feat: Security Considerations
IaroslavMazur 9304f1d
refactor: prb feedback
PaulRBerg b4c9f9f
refactor: review
IaroslavMazur 4640cc0
refactor: more prb feedback
PaulRBerg 4583cf3
refactor: polishing
IaroslavMazur bf0e9ff
refactor: Abstract
IaroslavMazur 552a5ae
refactor: abstract, security, and reference implementation
PaulRBerg 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 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,7 @@ | ||
bracketSpacing: true | ||
printWidth: 120 | ||
proseWrap: "always" | ||
singleQuote: false | ||
tabWidth: 2 | ||
trailingComma: "all" | ||
useTabs: false |
This file contains 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,291 @@ | ||
--- | ||
title: Multiple Native Tokens | ||
description: | ||
Introduces Multiple Native Tokens, with balances stored in the global VM state and support for Native Token transfers in EVM | ||
opcodes | ||
author: Paul Razvan Berg (@PaulRBerg), Iaroslav Mazur (@IaroslavMazur) | ||
discussions-to: https://ethereum-magicians.org[] | ||
status: Draft | ||
type: Standards Track | ||
category: Core | ||
created: TBD | ||
--- | ||
|
||
## Abstract | ||
|
||
TODO: rewrite this at the end | ||
|
||
This EIP introduces significant changes to the EVM in order to make it recognize Multiple Native Tokens (MNTs, or just | ||
NTs) and foster innovation in L2s. The balances of these NTs are stored in the global VM state, and ETH becomes one of | ||
the NTs while retaining its unique status of the only NT that can be used to pay for EVM gas. The `MINT`, `BURN`, | ||
`BALANCEOF`, and `CALLVALUES` opcodes are introduced to control the supply of NTs and query an account's NT balances. | ||
The `CALL2`, `DELEGATECALL2`, `CALLCODE2`, `NTCREATE`, `NTCREATE2` opcodes are introduced to handle the transfer of NTs | ||
and the NT-infused contract creation, respectively. Existing opcodes and transactions are adapted to refer to the default | ||
NT, which is `ETH`. A new transaction type is introduced in which the `value` field is replaced with a collection of | ||
(`token_id`, `token_amount`) pairs. | ||
|
||
## Motivation | ||
|
||
Implementing Multiple Native Tokens in the EVM offers several compelling advantages over traditional ERC-20 smart | ||
contracts, fostering innovation and improving user experience. | ||
|
||
### Native Support for Financial Instruments | ||
|
||
Storing token balances in the VM state unlocks the potential for sophisticated financial instruments to be implemented | ||
at the protocol level. This native integration facilitates features such as recurring payments and on-chain incentives | ||
without the need for complex smart contract interactions. For instance, platforms could natively provide yield to token | ||
holders or execute airdrops natively, similar to how rollups like Blast offer yield for ETH holders. Extending this | ||
capability to any token enhances utility and encourages users to engage more deeply with the network. | ||
|
||
### Elimination of Two-Step "Approve" and "Transfer" | ||
|
||
By embedding token balances into the VM state, the cumbersome process of approving tokens before transferring them is | ||
eliminated. Token transfers can be seamlessly included into smart contract calls, simplifying transaction flows and | ||
reducing the number of steps users must take. This streamlined process not only enhances the user experience but also | ||
reduces gas costs associated with multiple contract calls, making interactions more efficient and cost-effective. | ||
|
||
### Encouraging Experimentation on Layer 2 Solutions | ||
|
||
The proposed model aims to encourage innovation on Ethereum L2s by providing a flexible framework for token management. | ||
EVM rollups can experiment with this design to develop new paradigms in decentralized finance (DeFi), gaming, and | ||
beyond. By enabling tokens to have native properties and interactions, developers are empowered to explore features that | ||
could lead to more robust and versatile applications. This experimentation is vital for the evolution of the Ethereum | ||
ecosystem, as it fosters advancements that can benefit the broader community. | ||
|
||
## Prior Art | ||
|
||
This EIP has been inspired by FuelVM's | ||
[Native Assets](https://docs.fuel.network/docs/sway/blockchain-development/native_assets/) design, as well as its | ||
[SRC-20: Native Asset](https://docs.fuel.network/docs/sway-standards/src-20-native-asset/) standard. | ||
|
||
## Specification | ||
|
||
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT | ||
RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. | ||
|
||
For increased security and consistency, the token contracts representing the NTs SHOULD NOT use an upgradeability | ||
pattern. | ||
|
||
Note: Since the EVM stack can support only up to 1024 elements, there is a natural limit to the number of tokens | ||
that can be transferred during the execution of a single opcode. Given that the total number of transferred tokens | ||
takes 1 stack element, while each token pair takes 2 stack elements, the maximum number of tokens that can be | ||
transferred by a single opcode can be calculated as follows: (1024 - 1 - [NO_OF_NON_NT_TRANSFER_RELATED_OPCODE_ARGS]) | ||
/ 2. | ||
|
||
For example, a single `NTCALL` opcode can transfer a total of (1024 - 1 - 6) / 2 = 508 tokens. | ||
|
||
### State Changes | ||
|
||
A global `token_id` -> `token_supply` mapping is introduced to keep track of the existing NTs and their circulating | ||
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 | ||
mapping. The supply of an NT increases as a result of executing the `MINT` opcode, and decreases as a result of | ||
executing the `BURN` opcode. The `token_id` of an NT is the Ethereum address of its associated smart contract. | ||
|
||
`ETH` becomes the 'Base Token', with its ID and supply initialized to zero. `ETH` is the only NT whose supply is not | ||
tracked explicitly, i.e., its supply is determined just like it currently is. | ||
|
||
### New Opcodes | ||
|
||
#### `MINT` - `0xb0` | ||
|
||
- **Gas**: TBD | ||
- **Stack inputs**: | ||
- `recipient`: the address to which the minted tokens are credited | ||
- `amount` | ||
- **Stack outputs**: | ||
- `success`: a Boolean indicating success | ||
|
||
#### `BURN` - `0xb1` | ||
|
||
- **Gas**: TBD | ||
- **Stack inputs**: | ||
- `burner`: the address from which the tokens are burned | ||
- `amount` | ||
- **Stack outputs**: | ||
- `success`: a Boolean indicating success | ||
|
||
Note: the burner MUST have an NT balance that is at least equal to `amount`. | ||
|
||
#### `BALANCEOF` - `0xb2` | ||
|
||
- **Gas**: TBD | ||
- **Stack inputs**: | ||
- `token_id`: the ID of the NT to query the balance of | ||
- `address`: the address to query the balance of | ||
- **Stack outputs**: | ||
- `balance`: the NT balance of the given address | ||
|
||
#### `CALLVALUES` - `0xb3` | ||
|
||
- **Gas**: Dynamic, proportional to the number of NTs transferred by the executing call | ||
- **Stack inputs**: None | ||
- **Stack outputs**: | ||
- `transferred_tokens_no`: the number of transferred tokens | ||
- The list of `transferred_tokens_no` (`token_id`, `amount`) pairs | ||
|
||
#### `NTCALL` - `0xb4` | ||
|
||
- **Gas**: Dynamic, proportional to the number of transferred NTs | ||
- **Stack inputs**: | ||
- `gas`: amount of gas to send to the sub context to execute. The gas that is not used by the sub context is returned to this one | ||
- `address`: the account which context to execute | ||
- `transferred_tokens_no`: the number of transferred tokens | ||
- The list of `transferred_tokens_no` (`token_id`, `amount`) pairs | ||
- `argsOffset`: byte offset in the memory in bytes, the calldata of the sub context | ||
- `argsSize`: byte size to copy (size of the calldata) | ||
- `retOffset`: byte offset in the memory in bytes, where to store the return data of the sub context | ||
- `retSize`: byte size to copy (size of the return data) | ||
|
||
- **Stack outputs**: | ||
- `success`: return 0 if the sub context reverted, 1 otherwise | ||
|
||
#### `NTCALLCODE` - `0xb6` | ||
|
||
- **Gas**: Dynamic, proportional to the number of transferred NTs | ||
- **Stack inputs**: | ||
- `gas`: amount of gas to send to the sub context to execute. The gas that is not used by the sub context is returned to this one | ||
- `address`: the account which code to execute | ||
- `transferred_tokens_no`: the number of transferred tokens | ||
- The list of `transferred_tokens_no` (`token_id`, `amount`) pairs | ||
- `argsOffset`: byte offset in the memory in bytes, the calldata of the sub context | ||
- `argsSize`: byte size to copy (size of the calldata) | ||
- `retOffset`: byte offset in the memory in bytes, where to store the return data of the sub context | ||
- `retSize`: byte size to copy (size of the return data) | ||
|
||
- **Stack outputs**: | ||
- `success`: return 0 if the sub context reverted, 1 otherwise | ||
|
||
#### `NTCREATE` - `0xb7` | ||
|
||
- **Gas**: Dynamic, proportional to the number of transferred NTs | ||
- **Stack inputs**: | ||
- `transferred_tokens_no`: the number of transferred tokens | ||
- The list of `transferred_tokens_no` (`token_id`, `amount`) pairs | ||
- `offset`: byte offset in the memory in bytes, the initialisation code for the new account | ||
- `size`: byte size to copy (size of the initialisation code) | ||
|
||
- **Stack outputs**: | ||
- `address`: the address of the deployed contract, 0 if the deployment failed. | ||
|
||
#### `NTCREATE2` - `0xb8` | ||
|
||
- **Gas**: Dynamic, proportional to the number of transferred NTs | ||
- **Stack inputs**: | ||
- `transferred_tokens_no`: the number of transferred tokens | ||
- The list of `transferred_tokens_no` (`token_id`, `amount`) pairs | ||
- `offset`: byte offset in the memory in bytes, the initialisation code of the new account | ||
- `size`: byte size to copy (size of the initialisation code) | ||
- `salt`: 32-byte value used to create the new account at a deterministic address | ||
|
||
- **Stack outputs**: | ||
- `address`: the address of the deployed contract, 0 if the deployment failed | ||
|
||
|
||
### Existing Opcodes Adaptations | ||
|
||
#### Balance Query | ||
|
||
The following opcodes are adapted to query the balance of the default NT, which is `ETH`: | ||
|
||
- `BALANCE` | ||
- `SELFBALANCE` | ||
- `CALLVALUE` | ||
|
||
#### Contract Creation | ||
|
||
The `value` field in the following opcodes will refer to the default NT, which is `ETH`: | ||
|
||
- `CREATE` | ||
- `CREATE2` | ||
|
||
#### Calling Contracts | ||
|
||
The `value` field in the following opcodes will refer to the default NT, which is `ETH`: | ||
|
||
- `CALL` | ||
- `CALLCODE` | ||
|
||
### Transaction structure | ||
|
||
#### New Transaction Types | ||
|
||
A new EIP-1559 transaction type is introduced to support the transfer of NTs. | ||
|
||
The `value` field in the transaction structure is replaced by the total number of transferred tokens | ||
(`transferred_tokens_no`), followed by a list of `transferred_tokens_no` (`token_id`, `token_amount`) pairs. | ||
|
||
#### EVM Transactions | ||
|
||
All existing EVM transactions are still valid. | ||
|
||
- A zero `value` is equivalent to an empty `transferred_tokens` list. | ||
- A non-zero `value` is equivalent to a list containing a single pair with `ETH`'s `token_id` and the `value` as the | ||
`token_amount`. | ||
|
||
## Rationale | ||
|
||
An alternative to the proposed opcode-based approach was to use precompiles, which would have worked as follows: | ||
|
||
- No new opcodes. | ||
- Existing EVM opcodes would remain unchanged. | ||
- As a result, no modifications to smart contract languages would be required. | ||
|
||
However, the precompile-based approach also has disadvantages: | ||
|
||
- Users would be required to handle low-level data manipulations to encode inputs to precompile functions and decode | ||
their outputs. This would lead to a subpar user experience. | ||
- It would require major architectural changes to the EVM implementation, as precompiles are not designed to be | ||
stateful. | ||
|
||
Considering this, the opcode-based approach was chosen for its simplicity and efficiency in handling NTs at the EVM | ||
level. | ||
|
||
## Backwards Compatibility | ||
|
||
This EIP does not introduce any breaking changes to the existing Ethereum protocol. However, it adds substantial new | ||
functionality that requires consideration across various layers of the ecosystem. | ||
|
||
Front-end Ethereum libraries, such as web3.js and wagmi, will need to adapt to the new transaction structures introduced | ||
by Multiple Native Tokens (MNTs). These libraries must update their interfaces and transaction handling mechanisms to | ||
accommodate the inclusion of token transfers within smart contract calls and the absence of traditional "approve" and | ||
"transfer" functions. | ||
|
||
Smart contract languages like Solidity will need to incorporate support for the newly introduced opcodes associated with | ||
MNTs. This includes adapting compilers and development environments to recognize and compile contracts that interact | ||
with tokens stored in the VM state. | ||
|
||
Additionally, Ethereum wallets, block explorers, and development tools will require updates to fully support MNTs. | ||
Wallets must be capable of managing multiple native token balances, signing new types of transactions, and displaying | ||
token information accurately. Explorers need to parse and present the new transaction formats and token states, while | ||
development tools should facilitate debugging and deployment in this enhanced environment. | ||
|
||
To ensure a smooth transition, the authors recommend a gradual deployment process. This phased approach allows | ||
developers, users, and infrastructure providers to adapt incrementally. By introducing MNTs in stages, the ecosystem can | ||
adjust to the new functionalities, verify compatibility, and address any issues that arise, ensuring that every | ||
component behaves correctly throughout the integration period. | ||
|
||
## Reference Implementation | ||
|
||
TBD | ||
|
||
## Security Considerations | ||
|
||
This EIP introduces a few security risks related to malicious tokens and system integrity. Below are the key | ||
considerations and how they are mitigated. | ||
|
||
1. **Malicious or Misbehaving Native Tokens**: a token that becomes a Native Token (NT) may later behave maliciously, | ||
causing disruptions in the network. | ||
|
||
Mitigation: encourage users to prefer interacting with immutable, non-upgradeable NTs. Mitigation: A governance | ||
mechanism allows validators to vote on removing misbehaving NTs. Only immutable, non-upgradeable contracts can become | ||
NTs, reducing the risk of post-approval issues. | ||
|
||
2. **Cross-Contract NT Transfers**: inter-contract NTs transfers could lead to lost tokens if contracts are not properly | ||
equipped to handle multiple tokens. | ||
|
||
Mitigation: Contracts must validate token transfers correctly, with guidance for developers on standard patterns to | ||
ensure safe cross-contract interactions. Existing EVM contracts should be audited and updated to handle NTs. | ||
|
||
## Copyright | ||
|
||
Copyright and related rights waived via [CC0](../LICENSE.md). |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wasn't the
token_id
obtained by hashing the contract address with asubID
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it was - in the design of MNTs for our L2.
For Ethereum, however, I think that it'd be better to only allow one-to-one contract-to-NT relations (i.e. a smart contract may only have 1 associated NT). For extra security - and so that every single NT that is being added would have to be explicitly validated/approved (vs approving a smart contract once - and having it create an unlimited number of NTs afterwards).
With this new design, it's no longer needed to obtain the token ids by hashing: given the 1-1 relation, the id/address of the smart contract can simply be the id of the associated NT.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I like this proposal but for a different reason, which is: simplicity and the laser-focus on replacing ERC-20s (which is the overarching goal of this EIP).
So OK great, let's keep it like this.
P.S. there might have been a misunderstanding when I asked you to start drafting this EIP. I'm sorry for that.
This EIP wasn't meant to be implemented on Ethereum Mainnet itself. It was meant to be proposed as a cool idea that could be explored in EVM-based rollups.
I clarified this in my latest review of the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with the beauty of the added simplicity
No worries, there's nothing to be sorry for! Because we weren't sure whether we want to go the EIP or RIP route, I needed to pick one in order to have a fixed mental framework to draft the proposal based off which - and picked EIP.
If now you've got more reasons to think we should, instead, make this an RIP, then, I'll look deeper/again into what creating an RIP implies - and what must, therefore, be changed about this proposal for it to qualify as one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great.
I don't think the RIP process is formal as to require research — it's just like an EIP but called RIP.
I will have a think about EIPs vs RIPs today — but I am slightly more inclined to go with the former because MNTs could be beneficial for Mainnet, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Contrary to the EIPs (that suggest changes to Ethereum), RIPs are, mainly, focused on suggesting changes that would be beneficial/applicable to all/most EVM Rollups. An example of such changes would be various standards that enhance the interoperability of the rollups - or the ease with which a tool created for a rollup can be migrated to another ones.
If we choose to go for an RIP, instead, we'd need to go through the existing RIPs/ecosystem to make sure our MNT proposal fits well as it is - or there is opportunity for a better design of MNTs, when taking the Rollup "wrapper" architecture as a base for the implementation (instead of that of Ethereum).