|
| 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