From e4cb6fda2825de5e8afbe27d33789f9558faaf8d Mon Sep 17 00:00:00 2001 From: Gavin John Date: Tue, 27 Jun 2023 11:00:37 -0500 Subject: [PATCH 01/41] CI: Don't spam @lightclient with CI failed warnings (#7242) --- .github/workflows/auto-review-bot.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto-review-bot.yml b/.github/workflows/auto-review-bot.yml index 240561cd0575f..ff14c259c200f 100644 --- a/.github/workflows/auto-review-bot.yml +++ b/.github/workflows/auto-review-bot.yml @@ -25,6 +25,7 @@ jobs: - name: Auto Review Bot id: auto-review-bot uses: ethereum/eip-review-bot@3e9905fcb72cf81ae9ed732df429c28b17e155b1 + continue-on-error: true with: token: ${{ secrets.TOKEN }} config: config/eip-editors.yml From 28cbb0162f998530fd423c0062d9954de6d4a5c4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 29 Jun 2023 08:13:35 -0600 Subject: [PATCH 02/41] Add EIP: Execution layer triggerable exits (#7002) * add EL exits EIP * update to EIP 7002 * add discussions-to url * fix typo on body name * quick add * lint * Apply suggestions from code review Co-authored-by: Hsiao-Wei Wang * EIP 7002 feedbacl * add links to referenced EIPs * eip 7002: change validator_index to validator_pubkey * fix rlp encoding * update precompile var reference * fix body rlp * apply corrections * add todos to be addressed post draft stage --------- Co-authored-by: Hsiao-Wei Wang Co-authored-by: gajinder --- EIPS/eip-7002.md | 384 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 EIPS/eip-7002.md diff --git a/EIPS/eip-7002.md b/EIPS/eip-7002.md new file mode 100644 index 0000000000000..d3249f63881b9 --- /dev/null +++ b/EIPS/eip-7002.md @@ -0,0 +1,384 @@ +--- +eip: 7002 +title: Execution layer triggerable exits +description: Allows validators to trigger exits via their execution layer (0x01) withdrawal credentials +author: Danny Ryan (@djrtwo), Mikhail Kalinin (@mkalinin), Ansgar Dietrichs (@adietrichs), Hsiao-Wei Wang (@hwwhww) +discussions-to: https://ethereum-magicians.org/t/eip-7002-execution-layer-triggerable-exits/14195 +status: Draft +type: Standards Track +category: Core +created: 2023-05-09 +--- + +## Abstract + +Adds a new *stateful* precompile that allows validators to trigger exits to the beacon chain from their execution layer (0x01) withdrawal credentials. + +These new execution layer exit messages are appended to the execution layer block to reading by the consensus layer. + +## Motivation + +Validators have two keys -- an active key and a withdrawal credential. The active key takes the form of a BLS key, whereas the withdrawal credential can either be a BLS key (0x00) or an execution layer address (0x01). The active key is "hot", actively signing and performing validator duties, whereas the withdrawal credential can remain "cold", only performing limited operations in relation to withdrawing and ownership of the staked ETH. Due to this security relationship, the withdrawal credential ultimately is the key that owns the staked ETH and any rewards. + +As currently specified, only the active key can initiate a validator exit. This means that in any non-standard custody relationships (i.e. active key is separate entity from withdrawal credentials), that the ultimate owner of the funds -- the possessor of the withdrawal credentials -- cannot independently choose to exit and begin the withdrawal process. This leads to either trust issues (e.g. ETH can be "held hostage" by the active key owner) or insufficient work-arounds such as pre-signed exits. Additionally, in the event that active keys are lost, a user should still be able to recover their funds by using their cold withdrawal credentials. + +To ensure that the withdrawal credentials (owned by both EOAs and smart contracts) can trustlessly control the destiny of the staked ETH, this specification enables exits triggerable by 0x01 withdrawal credentials. + +Note, 0x00 withdrawal credentials can be changed into 0x01 withdrawal credentials with a one-time signed message. Thus any functionality enabled for 0x01 credentials is defacto enabled for 0x00 credentials. + +## Specification + +### Constants + +| Name | Value | Comment | +| - | - | - | +|`FORK_TIMESTAMP` | *TBD* | Mainnet | + +### Configuration + +| Name | Value | Comment | +| - | - | - | +| `VALIDATOR_EXIT_PRECOMPILE_ADDRESS` | *TBD* | Where to call and store relevant details about exit mechanism | +| `EXCESS_EXITS_STORAGE_SLOT` | 0 | | +| `EXIT_COUNT_STORAGE_SLOT` | 1 | | +| `EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT` | 2 | Pointer to head of the exit message queue | +| `EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT` | 3 | Pointer to the tail of the exit message queue| +| `EXIT_MESSAGE_QUEUE_STORAGE_OFFSET` | 4 | The start memory slot of the in-state exit message queue| +| `MAX_EXITS_PER_BLOCK` | 16 | Maximum number of exits that can be dequeued into a block | +| `TARGET_EXITS_PER_BLOCK` | 2 | | +| `MIN_EXIT_FEE` | 1 | | +| `EXIT_FEE_UPDATE_FRACTION` | 17 | | +| `EXCESS_RETURN_GAS_STIPEND` | 2300 | | + +### Execution layer + +#### Definitions + +* **`FORK_BLOCK`** -- the first block in a blockchain with the `timestamp` greater or equal to `FORK_TIMESTAMP`. + +#### Exit operation + +The new exit operation consists of the following fields: + +1. `source_address: Bytes20` +2. `validator_pubkey: Bytes48` + +RLP encoding of an exit **MUST** be computed as the following: + +```python +rlp_encoded_exit = RLP([ + source_address, + validator_pubkey, +]) +``` + +#### Validator Exit precompile + +The precompile requires a single `48` byte input, aliased to `validator_pubkey`. + +`CALL`s to `VALIDATOR_EXIT_PRECOMPILE_ADDRESS` perform the following: + +* Ensure enough ETH was sent to cover the current exit fee (`check_exit_fee()`) +* Increase exit count by 1 for the current block (`increment_exit_count()`) +* Insert an exit into the queue for the source address and validator pubkey (`insert_exit_to_queue()`) +* Return any unspent ETH in excess of the exit fee with an `EXCESS_RETURN_GAS_STIPEND` gas stipend (`return_excess_payment()`) + +Specifically, the functionality is defined in pseudocode as the function `trigger_exit()`: + +```python +################### +# Public function # +################### + +def trigger_exit(Bytes48: validator_pubkey): + check_exit_fee(msg.value) + increment_exit_count() + insert_exit_to_queue(msg.sender, validator_pubkey) + return_excess_payment(msg.value) + +################### +# Primary Helpers # +################### + +def check_exit_fee(int: fee_sent): + exit_fee = get_exit_fee() + require(fee_sent >= exit_fee, 'Insufficient exit fee') + # Note: consider mapping `MIN_EXIT_FEE` -> 0 fee + +def insert_exit_to_queue(address: source_address, Bytes48: validator_pubkey): + queue_tail_index = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT) + # Each exit takes 3 storage slots: 1 for source_address, 2 for validator_pubkey + queue_storage_slot = EXIT_MESSAGE_QUEUE_STORAGE_OFFSET + queue_tail_index * 3 + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot, source_address) + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 1, validator_pubkey[0:32]) + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 2, validator_pubkey[32:48]) + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, queue_tail_index + 1) + +def increment_exit_count(): + exit_count = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_COUNT_STORAGE_SLOT) + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_COUNT_STORAGE_SLOT, exit_count + 1) + +def return_excess_payment(int: fee_sent, address: source_address): + excess_payment = fee_sent - get_exit_fee() + if excess_payment > 0: + (bool sent, bytes memory data) = source_address.call{value: excess_payment, gas: EXCESS_RETURN_GAS_STIPEND}("") + require(sent, "Failed to return excess fee payment") + +###################### +# Additional Helpers # +###################### + +def get_exit_fee() -> int: + excess_exits = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXCESS_EXITS_STORAGE_SLOT) + return fake_exponential( + MIN_EXIT_FEE, + excess_exits, + EXIT_FEE_UPDATE_FRACTION + ) + +def fake_exponential(factor: int, numerator: int, denominator: int) -> int: + i = 1 + output = 0 + numerator_accum = factor * denominator + while numerator_accum > 0: + output += numerator_accum + numerator_accum = (numerator_accum * numerator) // (denominator * i) + i += 1 + return output // denominator +``` + +##### Gas cost + +TBD + +Once functionality is reviewed and solidified, we'll estimate the cost of running the above computations fully in the EVM, and then potentially apply some discount due to reduced EVM overhead of being able to execute the above logic natively. + +#### Block structure + +Beginning with the `FORK_BLOCK`, the block body **MUST** be appended with a list of exit operations. RLP encoding of the extended block body structure **MUST** be computed as follows: + +```python +block_body_rlp = RLP([ + field_0, + ..., + # Latest block body field before `exits` + field_n, + + [exit_0, ..., exit_k], +]) +``` + +Beginning with the `FORK_BLOCK`, the block header **MUST** be appended with the new **`exits_root`** field. The value of this field is the trie root committing to the list of exits in the block body. **`exits_root`** field value **MUST** be computed as follows: + +```python +def compute_trie_root_from_indexed_data(data): + trie = Trie.from([(i, obj) for i, obj in enumerate(data)]) + return trie.root + +block.header.exits_root = compute_trie_root_from_indexed_data(block.body.exits) +``` + +#### Block validity + +Beginning with the `FORK_BLOCK`, client software **MUST** extend block validity rule set with the following conditions: + +1. Value of **`exits_root`** block header field equals to the trie root committing to the list of exit operations contained in the block. To illustrate: + +```python +def compute_trie_root_from_indexed_data(data): + trie = Trie.from([(i, obj) for i, obj in enumerate(data)]) + return trie.root + +assert block.header.exits_root == compute_trie_root_from_indexed_data(block.body.exits) +``` + +2. The list of exit operations contained in the block body **MUST** be equivalent to list of exits at the head of the exit precompile's exit message queue up to the maximum of `MAX_EXITS_PER_BLOCK`, respecting the order in the queue. This validation **MUST** be run after all transactions in the current block are processed and **MUST** be run before per-block precompile storage calculations (i.e. a call to `update_exit_precompile()`) are performed. To illustrate: + +```python +class ValidatorExit(object): + source_address: Bytes20 + validator_pubkey: Bytes48 + +queue_head_index = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT) +queue_tail_index = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT) +num_exits_in_queue = queue_tail_index - queue_head_index +num_exits_to_dequeue = min(num_exits_in_queue, MAX_EXITS_PER_BLOCK) + +# Retrieve exits from the queue +expected_exits = [] +for i in range(num_exits_to_dequeue): + queue_storage_slot = EXIT_MESSAGE_QUEUE_STORAGE_OFFSET + (queue_head_index + i) * 2 + source_address = address(SLOAD(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot)[0:20] + validator_pubkey = ( + SLOAD(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 1) + SLOAD(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 1) + + SLOAD(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 1) + SLOAD(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, queue_storage_slot + 1)[0:16] + ) + exit = ValidatorExit( + source_address=Bytes20(source_address), + validator_pubkey=Bytes48(validator_pubkey), + ) + expected_exits.append(exit) + +# Compare retrieved exits to the list in the block body +assert block.body.exits == expected_exits +``` + +A block that does not satisfy the above conditions **MUST** be deemed invalid. + +#### Block processing + +##### Per-block precompile storage calculations + +At the end of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. after processing all transactions and after performing the block body exit validations): + +* The exit precompile's exit queue is updated based on exits dequeued and the exit queue head/tail are reset if the queue has been cleared (`update_exit_queue()`) +* The exit precompile’s excess exits are updated based on usage in the current block (`update_excess_exits()`) +* The exit precompile's exit count is reset to 0 (`reset_exit_count()`) + +Specifically, the functionality is defined in pseudocode as the function `update_exit_precompile()`: + +```python +################### +# Public function # +################### + +def update_exit_precompile(): + update_exit_queue() + update_excess_exits() + reset_exit_count() + +########### +# Helpers # +########### + +def update_exit_queue(): + queue_head_index = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT) + queue_tail_index = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT) + + num_exits_in_queue = queue_tail_index - queue_head_index + num_exits_dequeued = min(num_exits_in_queue, MAX_EXITS_PER_BLOCK) + new_queue_head_index = queue_head_index + num_exits_dequeued + if new_queue_head_index == queue_tail_index: + # Queue is empty, reset queue pointers + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, 0) + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_TAIL_STORAGE_SLOT, 0) + else: + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_MESSAGE_QUEUE_HEAD_STORAGE_SLOT, new_queue_head_index) + +def update_excess_exits(): + previous_excess_exits = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXCESS_EXITS_STORAGE_SLOT) + exit_count = sload(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_COUNT_STORAGE_SLOT) + + new_excess_exits = 0 + if previous_excess_exits + exit_count > TARGET_EXITS_PER_BLOCK: + new_excess_exits = previous_excess_exits + exit_count - TARGET_EXITS_PER_BLOCK + + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXCESS_EXITS_STORAGE_SLOT, new_excess_exits) + +def reset_exit_count(): + sstore(VALIDATOR_EXIT_PRECOMPILE_ADDRESS, EXIT_COUNT_STORAGE_SLOT, 0) +``` + + +### Consensus layer + + + +Sketch of spec: + +* New operation `ExecutionLayerExit` +* Will show up in `ExecutionPayload` as an SSZ List bound by length `MAX_EXITS_PER_BLOCK` +* New function in `process_execution_layer_exit` that has similar functionality to `process_voluntary_exit` but that can fail validations (e.g. validator is already exited) without the block failing (similar to deposit coming from EL) +* `process_execution_layer_exit` called in `process_operations` for each `ExecutionLayerExit` found in the `ExecutionPayload` + +## Rationale + +### Stateful precompile + + + +This specification utilizes a *stateful* precompile for simplicity and future-proofness. While precompiles are a well-known quantity, none to date have associated EVM state at the address. + +The alternative designs are (1) to utilize a precompile or opcode for the functionality and write a separate specified space in the EVM -- e.g. `0xFF..FF` -- or (2) to place the required state into the block and require the previous block header as an input into the state transition function (e.g. like [EIP-1559](./eip-1559.md) `base_fee`). + +Alternative design (1) is essentially using a stateful precompile but dissociating the state into a separate address. At first glance, this split appears unnecessarily convoluted when we could store the location of the `CALL` and the associated state in the same address. That said, there might be unexpected engineering constraints around precompiles in existing clients that make this a preferable path. + +Alternative design (2) has two main drawbacks. The first is that with the message queue contains an unbounded amount of state (as opposed to simple the `base_fee` in the similar EIP-1559 design). Additionally, even if the state was constrained to a single variable or two, this design pattern reinforces that the Ethereum state transition function signature be more than `f(pre_state, block) -> post_state` by putting another dependency on the `pre_block_header`. These additional dependencies hinder the elegance of future stateless designs. Providing these dependencies within the EVM state as specified, allows for them to show up naturally in block witnesses. + +### `validator_pubkey` field + + + +Multiple validators can utilize the same execution layer withdrawal credential, thus the `validator_pubkey` field is utilized to disambiguate which validator is being exited. + +Note, `validator_index` also disambiguates validators but is not used because the execution-layer cannot currently trustlessly ascertain this value. + +### Exit message queue + +The exit precompile maintains and in-state queue of exit messages to be dequeued each block into the block and thus into the execution layer. + +The number of exits that can be passed into the consensus layer are bound by `MAX_EXITS_PER_BLOCK` to bound the load both on the block size as well as on the consensus layer processing. `16` has been chosen for `MAX_EXITS_PER_BLOCK` to be in line with the bounds of similar operations on the beacon chain -- e.g. `VoluntaryExit` and `Deposit`. + +Although there is a maximum number of exits that can passed to the consensus layer each block, the execution layer gas limit can provide for far more calls to the exit precompile at each block. The queue then allows for these calls to successfully be made while still maintaining a system rate limit. + +The alternative design considered was to have calls to the exit precompile fail after `MAX_EXITS_PER_BLOCK` successful calls were made within the context of a single block. This would eliminate the need for the message queue, but would come at the cost of a bad UX of precompile call failures in times of high exiting. The complexity to mitigate this bad UX is relatively low and is currently favored. + +### Utilizing `CALL` to return excess payment + +Calls to the exit precompile require a fee payment defined by the current state of the precompile. Smart contracts can easily perform a read/calculation to pay the precise fee, whereas EOAs will likely need to compute and send some amount over the current fee at time of signing the transaction. This will result in EOAs having fee payment overages in the normal case. These should be returned to the caller. + +There are two potential designs to return excess fee payments to the caller (1) use an EVM `CALL` with some gas stipend or (2) have special functionality to allow the precompile to "credit" the caller's account with the excess fee. + +Option (1) has been selected in the current specification because it utilizes less exceptional functionality and is likely simpler to implement and ensure correctness. The current version sends a gas stipen of 2300. This is following the (outdated) solidity pattern primarily to simplify precompile gas accounting (allowing it to be a fixed instead of dynamic cost). The `CALL` could forward the maximum allowed gas but would then require the cost of the precompile to be dynamic. + +Option (2) utilizes custom logic (exceptional to base EVM logic) to credit the excess back to the callers balance. This would potentially simplify concerns around precompile gas costs/metering, but at the cost of non-standard EVM complexity. We are open to this path, but want to solicit more input before writing it into the speficiation. + +### Rate limiting using exit fee + +Transactions are naturally rate-limited in the execution layer via the gas limit, but an adversary willing to pay market-rate gas fees (and potentially utilize builder markets to pay for front-of-block transaction inclusion) can fill up the exit operation limits for relatively cheap, thus griefing honest validators that want to exit. + +There are two general approaches to combat this griefing -- (a) only allow validators to send such messages and with a limit per time period or (b) utilize an economic method to make such griefing increasingly costly. + +Method (a) (not used in this EIP) would require [EIP-4788](./eip-4788.md) (the `BEACON_ROOT` opcode) against which to prove withdrawal credentials in relation to validator pubkeys as well as a data-structure to track exits per-unit-time (e.g. 4 months) to ensure that a validator cannot grief the mechanism by submitting many exits. The downsides of this method are that it requires another cross-layer EIP and that it is of higher cross-layer complexity (e.g. care that might need to be taken in future upgrades if, for example, the shape of the merkle tree of `BEACON_ROOT` changes, then the exit precompile and proof structure might need to be updated). + +Method (b) has been utilized in this EIP to eliminate additional EIP requirements and to reduce cross-layer complexity to allow for correctness of this EIP (now and in the future) to be easier to analyze. The EIP-1559-style mechanism with a dynamically adjusting fee mechanism allows for users to pay `MIN_EXIT_FEE` for exits in the normal case (fewer than 2 per block on average), but scales the fee up exponentially in response to high usage (i.e. potential abuse). + +### `TARGET_EXITS_PER_BLOCK` configuration value + +`TARGET_EXITS_PER_BLOCK` has been selected as `2` such that even if all ETH is staked (~120M ETH -> 3.75M validators), the 64 validator per epoch target (`2 * 32 slots`) still exceeds the per-epoch exit churn limit on the consensus layer (defined by `get_validator_churn_limit()`) at such values -- 57 validators per epoch (`3.75M // 65536`). + +### Exit fee update rule + +The exit fee update rule is intended to approximate the formula `exit_fee = MIN_EXIT_FEE * e**(excess_exits / EXIT_FEE_UPDATE_FRACTION)`, +where `excess_exits` is the total "extra" amount of exits that the chain has processed relative to the "targeted" number (`TARGET_EXITS_PER_BLOCK` per block). + +Like EIP-1559, it’s a self-correcting formula: as the excess goes higher, the `exit_fee` increases exponentially, reducing usage and eventually forcing the excess back down. + +The block-by-block behavior is roughly as follows. If block `N` processes `X` exits, then at the end of block `N` `excess_exits` increases by `X - TARGET_EXITS_PER_BLOCK`, and so the `exit_fee` in block `N+1` increases by a factor of `e**((X - TARGET_EXITS_PER_BLOCK) / EXIT_FEE_UPDATE_FRACTION)`. Hence, it has a similar effect to the existing EIP-1559, but is more "stable" in the sense that it responds in the same way to the same total exits regardless of how they are distributed over time. + +The parameter `EXIT_FEE_UPDATE_FRACTION` controls the maximum downwards rate of change of the blob gas price. It is chosen to target a maximum downwards change rate of `e(TARGET_EXITS_PER_BLOCK / EXIT_FEE_UPDATE_FRACTION) ≈ 1.125` per block. The maximum upwards change per block is `e((MAX_EXITS_PER_BLOCK - TARGET_EXITS_PER_BLOCK) / EXIT_FEE_UPDATE_FRACTION) ≈ 2.279`. + +### Exits inside of the block + +Exits are placed into the actual body of the block (and execution payload in the consensus layer). + +There is a strong design requirement that the consensus layer and execution layer can execute independently of each other. This means, in this case, that the consensus layer cannot rely upon a synchronous call to the execution layer to get the required exits for the current block. Instead, the exits must be embedded in the shared data-structure of the execution payload such that if the execution layer is offline, the consensus layer still has the requisite data to fully execute the consensus portion of the state transition function. + +## Backwards Compatibility + +This EIP introduces backwards incompatible changes to the block structure and block validation rule set. But neither of these changes break anything related to current user activity and experience. + +## Security Considerations + +### Impact on existing custody relationships + +There might be existing custody relationships and/or products that rely upon the assumption that the withdrawal credentials *cannot* trigger exits. We are currently confident that the additional withdrawal credentials feature does not impact the security of existing validators because: + +1. The withdrawal credentials ultimately own the funds so allowing them to exit staking is natural with respect to ownership. +2. We are currently not aware of any such custody relationships and/or products that do rely on the lack of this feature. + +In the event that existing validators/custodians rely on this, then the validators can be exited and restaked utilizing 0x01 withdrawal credentials pointing to a smart contract that simulates this behaviour. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 64b7d761d2b972b1699cd03fdff8e938e3097f90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= <58347304+ulerdogan@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:50:24 +0300 Subject: [PATCH 03/41] Add EIP: Precompiled for secp256r1 Curve Support (#7212) * Add EIP: Precompiled for secp256r1 Curve Support * Update EIP: Fix the date * Update: Update with the numbers and links * Fix: Lint errors fixed * Fix: Lint errors v2 fixed * Fix: Disallowed links are removed * Update: Fix the specification misrepresentations * Update: Change license link Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> --------- Co-authored-by: Sam Wilson <57262657+SamWilsn@users.noreply.github.com> --- EIPS/eip-7212.md | 179 ++++++++++++++++++++++ assets/eip-7212/benchstat_compare_test | 42 +++++ assets/eip-7212/ecrecover_benchmark_test | 15 ++ assets/eip-7212/p256Verify_benchmark_test | 15 ++ 4 files changed, 251 insertions(+) create mode 100644 EIPS/eip-7212.md create mode 100644 assets/eip-7212/benchstat_compare_test create mode 100644 assets/eip-7212/ecrecover_benchmark_test create mode 100644 assets/eip-7212/p256Verify_benchmark_test diff --git a/EIPS/eip-7212.md b/EIPS/eip-7212.md new file mode 100644 index 0000000000000..bbd3300f72225 --- /dev/null +++ b/EIPS/eip-7212.md @@ -0,0 +1,179 @@ +--- +eip: 7212 +title: Precompiled for secp256r1 Curve Support +description: Proposal to add precompiled contract that allows signature verifications in the “secp256r1” elliptic curve. +author: Ulaş Erdoğan (@ulerdogan), Doğan Alpaslan (@doganalpaslan) +discussions-to: https://ethereum-magicians.org/t/eip-7212-precompiled-for-secp256r1-curve-support/14789 +status: Draft +type: Standards Track +category: Core +created: 2023-06-22 +--- + +## Abstract + +The proposal aims creating precompiled contract that allows signature verifications in the “secp256r1” elliptic curve by given parameters of message hash, `r` - `s` components of the signature, and `x` - `y` coordinates of the public key. So that, any EVM chain -principally Ethereum rollups- will be able to integrate this precompiled contract easily. + +## Motivation + +“secp256r1” elliptic curve is a standardized curve by NIST which has the same calculations by different input parameters with “secp256k1” elliptic curve used by the “ecrecover” precompiled contract. The cost of combined attacks and the security conditions are almost the same for both curves. Adding a precompiled contract which is similar to "ecrecover" can provide signature verifications using the “secp256r1” elliptic curve in the smart contracts and multi-faceted benefits can occur. One important factor is that this curve is widely used and supported in many modern devices such as Apple’s Secure Enclave, Webauthn, Android Keychain which proves the user adoption. Additionally, the introduction of this precompile could enable valuable features in the account abstraction which allows more efficient and flexible management of accounts by transaction signs in mobile devices. +Most of the modern devices and applications rely on the “secp256r1” elliptic curve. The addition of this precompiled contract enables the verification of device native transaction signing mechanisms. For example: + +1. **Apple's Secure Enclave:** There is a separate “Trusted Execution Environment” in Apple hardware which can sign arbitrary messages and can only be accessed by biometric identification. +2. **Webauthn:** Web Authentication (WebAuthn) is a web standard published by the World Wide Web Consortium (W3C). WebAuthn aims to standardize an interface for authenticating users to web-based applications and services using public-key cryptography. It is being used by almost all of the modern web browsers. +3. **Android Keystore:** Android Keystore is an API that manages the private keys and signing methods. The private keys are not processed while using Keystore as the applications’ signing method. Also, it can be done in the “Trusted Execution Environment” in the microchip. +4. **Passkeys:** Passkeys is utilizing FIDO Alliance and W3C standards. It replaces passwords with cryptographic key-pairs which is also can be used for the elliptic curve cryptography. + +Modern devices have these signing mechanisms that are designed to be more secure and they are able to sign transaction data, but none of the current wallets are utilizing these signing mechanisms. So, these secure signing methods can be enabled by the proposed precompiled contract to initiate the transactions natively from the devices and also, can be used for the key management. This proposal aims to reach maximum security and convenience for the key management. + +## Specification + +As of `FORK_TIMESTAMP` in the integrated EVM chain, add precompiled contract `P256VERIFY` for signature verifications in the “secp256r1” elliptic curve at address `PRECOMPILED_ADDRESS`. + +### Elliptic Curve Information + +“secp256r1” is a specific elliptic curve, also known as “P-256” and “prime256v1” curves. The curve is defined with the following equation and domain parameters: + +``` +# curve: +y^2 ≡ x^3 + ax + b + +# p: specifies reduced elliptic group +0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff + +# a: elliptic curve coefficient +0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc + +# b: elliptic curve coefficient +0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b + +# G: base point of the subgroup +(0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296, + 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5) + +# n: order of the subgroup +0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 + +# h: cofactor of the subgroup +0x1 + +``` + +### Elliptic Curve Signature Verification Steps + +The signature verifying algorithm takes the signed message hash, the signature components provided by the “secp256r1” curve algorithm, and the public key derived from the signer private key. The verification can be done with the following steps: + +``` +# h (message hash) +# pubKey = (public key of the signer private key) + +# Calculate the modular inverse of the signature proof: +s1 = s^(−1)(modn) + +# Recover the random point used during the signing: +R' = (h * s1) * G + (r * s1) * pubKey + +# Take from R' its x-coordinate: +r' = R'.x + +# Calculate the signature validation result by comparing whether: +r' == r + +``` + +### Required Checks in the Verification + +Some requirements have to be checked to understand if the given signature components are valid: + +- Verify that both `r` - `s` values are greater than 0 and less than the curve order. +- Verify that s is equal to or less than half of the order of the subgroup to prevent signature malleability. +- Verify that the point formed by (`x`, `y`) values is on the curve and both components are in between 0 and the `p` value of the curve. + +### Precompiled Contract Specification + +The `P256VERIFY` precompiled contract is proposed with the following input and outputs, which are big-endian values: + +- **Input data:** 160 bytes of data including: + - 32 bytes of the signed data `hash` + - 32 bytes of the `r` component of the signature + - 32 bytes of the `s` component of the signature + - 32 bytes of the `x` coordinate of the public key + - 32 bytes of the `y` coordinate of the public key +- **Output data:** 32 bytes of result data and error + - If the signature verification process succeeds, it returns 1 in 32 bytes format. + +### Precompiled Contract Gas Usage + +The use of signature verification cost by `P256VERIFY` is `3450` gas. Following reasons and calculations are provided in the [Rationale](#rationale) and [Test Cases](#test-cases) sections. + +## Rationale + +The "secp256r1" elliptic curve signatures consists of `v`, `r`, `s` components. Even if recovering the public key on the curve is possible, most of the applications are not generating `v` component of the signature and it causes an uncertainty of the result values. However, the signatures can be verified with only `r` - `s` values. In order to provide an exact and more compatible method, verification is preferred over recovery to propose in a precompiled. + +The signature values in `r` - `s` and the public key coordinates in the `x`- `y` provides direct computations in signing and verification part, so these formats are chose in the input data format which are 32 bytes. + +The gas cost has proposed by comparing the performances of the `P256VERIFY` and the `ECRECOVER` which is implemented in the EVM at `0x01` address. It is seen that “secp256r1” signature verification by `P256VERIFY` is ~15% slower (elaborated in the [test cases](#test-cases) part) than “secp256k1” signature recovery by `ECRECOVER`, so `3450` gas is proposed by comparison which causes similar “mgas/op” values in both precompiles. + +## Backwards Compatibility + +No backward compatibility issues found as the precompiled contract will be added to `PRECOMPILED_ADDRESS`, one of the next address in the precompiled address set. + +## Test Cases + +Functional tests are applied for multiple cases in the [reference implementation](#reference-implementation) of `P256VERIFY` precompiled contract and they succeed. Benchmark tests are also applied for both `P256VERIFY` and `ECRECOVER` with some pre-calculated data and signatures in the “go-ethereum”s precompile testing structure to propose a meaningful gas cost for the “secp256r1” signature verifications by the precompiled contract implemented in the [reference implementation](#reference-implementation). The benchmark test results by example data in the assets can be checked: + +- [P256Verify Benchmark Test Results](../assets/eip-7212/p256Verify_benchmark_test) +- [Ecrecover Benchmark Test Results](../assets/eip-7212/ecrecover_benchmark_test) + +``` +# results of geth benchmark tests of +# ECRECOVER and P256VERIFY (reference implementation) +# by benchstat tool + +goos: darwin +goarch: arm64 +pkg: github.com/ethereum/go-ethereum/core/vm + │ compare_p256Verify │ compare_ecrecover │ + │ sec/op │ sec/op │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 57.75µ ± 1% +PrecompiledEcrecover/-Gas=3000-8 50.48µ ± 1% +geomean 57.75µ 50.48µ + + │ compare_p256Verify │ compare_ecrecover │ + │ gas/op │ gas/op │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 3.450k ± 0% +PrecompiledEcrecover/-Gas=3000-8 3.000k ± 0% +geomean 3.450k 3.000k + + │ compare_p256Verify │ compare_ecrecover │ + │ mgas/s │ mgas/s │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 59.73 ± 1% +PrecompiledEcrecover/-Gas=3000-8 59.42 ± 1% +geomean 59.73 59.42 + + │ compare_p256Verify │ compare_ecrecover │ + │ B/op │ B/op │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 1.523Ki ± 0% +PrecompiledEcrecover/-Gas=3000-8 800.0 ± 0% +geomean 1.523Ki 800.0 + + │ compare_p256Verify │ compare_ecrecover │ + │ allocs/op │ allocs/op │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 33.00 ± 0% +PrecompiledEcrecover/-Gas=3000-8 7.000 ± 0% +geomean 33.00 7.000 + +``` + +## Reference Implementation + +Implementation of the `P256VERIFY` precompiled contract is applied to go-ethereum client to create a reference. Also, an “secp256r1” package has already been included in the Besu Native library which is used by Besu client. Other client implementations are in the future roadmap. + +## Security Considerations + +The changes are not directly affecting the protocol security, it is related with the applications using `P256VERIFY` for the signature verifications. The “secp256r1” curve has been using in many other protocols and services and there is not any security issues in the past. + + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/eip-7212/benchstat_compare_test b/assets/eip-7212/benchstat_compare_test new file mode 100644 index 0000000000000..d99bcf999e5b1 --- /dev/null +++ b/assets/eip-7212/benchstat_compare_test @@ -0,0 +1,42 @@ +goos: darwin +goarch: arm64 +pkg: github.com/ethereum/go-ethereum/core/vm + │ compare_p256Verify │ compare_ecrecover │ + │ sec/op │ sec/op vs base │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 57.75µ ± 1% +PrecompiledEcrecover/-Gas=3000-8 50.48µ ± 1% +geomean 57.75µ 50.48µ ? ¹ ² +¹ benchmark set differs from baseline; geomeans may not be comparable +² ratios must be >0 to compute geomean + + │ compare_p256Verify │ compare_ecrecover │ + │ gas/op │ gas/op vs base │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 3.450k ± 0% +PrecompiledEcrecover/-Gas=3000-8 3.000k ± 0% +geomean 3.450k 3.000k ? ¹ ² +¹ benchmark set differs from baseline; geomeans may not be comparable +² ratios must be >0 to compute geomean + + │ compare_p256Verify │ compare_ecrecover │ + │ mgas/s │ mgas/s vs base │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 59.73 ± 1% +PrecompiledEcrecover/-Gas=3000-8 59.42 ± 1% +geomean 59.73 59.42 ? ¹ ² +¹ benchmark set differs from baseline; geomeans may not be comparable +² ratios must be >0 to compute geomean + + │ compare_p256Verify │ compare_ecrecover │ + │ B/op │ B/op vs base │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 1.523Ki ± 0% +PrecompiledEcrecover/-Gas=3000-8 800.0 ± 0% +geomean 1.523Ki 800.0 ? ¹ ² +¹ benchmark set differs from baseline; geomeans may not be comparable +² ratios must be >0 to compute geomean + + │ compare_p256Verify │ compare_ecrecover │ + │ allocs/op │ allocs/op vs base │ +PrecompiledP256Verify/p256Verify-Gas=3450-8 33.00 ± 0% +PrecompiledEcrecover/-Gas=3000-8 7.000 ± 0% +geomean 33.00 7.000 ? ¹ ² +¹ benchmark set differs from baseline; geomeans may not be comparable +² ratios must be >0 to compute geomean diff --git a/assets/eip-7212/ecrecover_benchmark_test b/assets/eip-7212/ecrecover_benchmark_test new file mode 100644 index 0000000000000..4bf20a628d486 --- /dev/null +++ b/assets/eip-7212/ecrecover_benchmark_test @@ -0,0 +1,15 @@ +goos: darwin +goarch: arm64 +pkg: github.com/ethereum/go-ethereum/core/vm +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23295 50034 ns/op 3000 gas/op 59.95 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23734 50558 ns/op 3000 gas/op 59.33 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23823 50586 ns/op 3000 gas/op 59.30 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23913 50049 ns/op 3000 gas/op 59.94 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23721 50299 ns/op 3000 gas/op 59.64 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23760 51160 ns/op 3000 gas/op 58.63 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23151 50818 ns/op 3000 gas/op 59.03 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23744 53451 ns/op 3000 gas/op 56.12 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 22837 50315 ns/op 3000 gas/op 59.62 mgas/s 800 B/op 7 allocs/op +BenchmarkPrecompiledEcrecover/-Gas=3000-8 23823 50401 ns/op 3000 gas/op 59.52 mgas/s 800 B/op 7 allocs/op +PASS +ok github.com/ethereum/go-ethereum/core/vm 17.687s diff --git a/assets/eip-7212/p256Verify_benchmark_test b/assets/eip-7212/p256Verify_benchmark_test new file mode 100644 index 0000000000000..dc908bf8e09e2 --- /dev/null +++ b/assets/eip-7212/p256Verify_benchmark_test @@ -0,0 +1,15 @@ +goos: darwin +goarch: arm64 +pkg: github.com/ethereum/go-ethereum/core/vm +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20770 57970 ns/op 3450 gas/op 59.51 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20899 57769 ns/op 3450 gas/op 59.71 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20780 57343 ns/op 3450 gas/op 60.16 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20870 57740 ns/op 3450 gas/op 59.74 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20911 57411 ns/op 3450 gas/op 60.09 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20874 58423 ns/op 3450 gas/op 59.04 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20736 57552 ns/op 3450 gas/op 59.94 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 19700 58235 ns/op 3450 gas/op 59.24 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20814 57681 ns/op 3450 gas/op 59.80 mgas/s 1560 B/op 33 allocs/op +BenchmarkPrecompiledP256Verify/p256Verify-Gas=3450-8 20736 58806 ns/op 3450 gas/op 58.66 mgas/s 1560 B/op 33 allocs/op +PASS +ok github.com/ethereum/go-ethereum/core/vm 18.491s From c7b78b11dade88636dcd93e661b40863c6456720 Mon Sep 17 00:00:00 2001 From: Mikhail Kalinin Date: Thu, 29 Jun 2023 19:18:24 +0400 Subject: [PATCH 04/41] EIP-6110: add CL spec and extended DoS analysis (#7237) * Update data complexity section with fresh numbers * EIP-6110: Add CL spec and security considerations * Fix broken link * Update wording * Polish wording * Add analysis by @petertdavies * Polish wording * Fix link to CL specs * Update links to CL specs * Make linter happy * Fix links to CL specs --- EIPS/eip-6110.md | 67 +++++++++++++++++++++++---- assets/eip-6110/ws_period_analysis.md | 28 +++++++++++ 2 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 assets/eip-6110/ws_period_analysis.md diff --git a/EIPS/eip-6110.md b/EIPS/eip-6110.md index 54aa23419d4c1..1a15befa86b7c 100644 --- a/EIPS/eip-6110.md +++ b/EIPS/eip-6110.md @@ -2,7 +2,7 @@ eip: 6110 title: Supply validator deposits on chain description: Provides validator deposits as a list of deposit operations added to the Execution Layer block -author: Mikhail Kalinin (@mkalinin), Danny Ryan (@djrtwo) +author: Mikhail Kalinin (@mkalinin), Danny Ryan (@djrtwo), Peter Davies (@petertdavies) discussions-to: https://ethereum-magicians.org/t/eip-6110-supply-validator-deposits-on-chain/12072 status: Draft type: Standards Track @@ -30,13 +30,15 @@ Advantages of in-protocol deposit processing consist of but are not limit to the ## Specification -### Constants +### Execution Layer + +#### Constants | Name | Value | Comment | | - | - | - | |`FORK_TIMESTAMP` | *TBD* | Mainnet | -### Configuration +#### Configuration | Name | Value | Comment | | - | - | - | @@ -44,11 +46,11 @@ Advantages of in-protocol deposit processing consist of but are not limit to the `DEPOSIT_CONTRACT_ADDRESS` parameter **MUST** be included into client software binary distribution. -### Definitions +#### Definitions * **`FORK_BLOCK`** -- the first block in a blockchain with the `timestamp` greater or equal to `FORK_TIMESTAMP`. -### Deposit +#### Deposit The structure denoting the new deposit operation consists of the following fields: @@ -70,7 +72,7 @@ rlp_encoded_deposit = RLP([ ]) ``` -### Block structure +#### Block structure Beginning with the `FORK_BLOCK`, the block body **MUST** be appended with a list of deposit operations. RLP encoding of an extended block body structure **MUST** be computed as follows: @@ -95,7 +97,7 @@ def compute_trie_root_from_indexed_data(data): block.header.deposits_root = compute_trie_root_from_indexed_data(block.body.deposits) ``` -### Block validity +#### Block validity Beginning with the `FORK_BLOCK`, client software **MUST** extend block validity rule set with the following conditions: @@ -155,6 +157,29 @@ assert block.body.deposits == expected_deposits A block that does not satisfy the above conditions **MUST** be deemed invalid. +### Consensus layer + +Consensus layer changes can be summarized into the following list: + +1. `ExecutionPayload` is extended with a new `deposit_receipts` field to accomodate deposit operations list. +2. `BeaconState` is appended with `deposit_receipts_start_index` used to switch from the former deposit mechanism to the new one. +3. As a part of transition logic a new beacon block validity condition is added to constrain the usage of `Eth1Data` poll. +4. A new `process_deposit_receipt` function is added to the block processing routine to handle `deposit_receipts` processing. + +Detailed consensus layer specification can be found in following documents: + +* [`eip6110/beacon-chain.md`](https://github.com/ethereum/consensus-specs/blob/2660af05390aa61f06142e1c6311a3a3c633f720/specs/_features/eip6110/beacon-chain.md) -- state transition. +* [`eip6110/validator.md`](https://github.com/ethereum/consensus-specs/blob/2660af05390aa61f06142e1c6311a3a3c633f720/specs/_features/eip6110/validator.md) -- validator guide. +* [`eip6110/fork.md`](https://github.com/ethereum/consensus-specs/blob/2660af05390aa61f06142e1c6311a3a3c633f720/specs/_features/eip6110/fork.md) -- EIP activation. + +#### Validator index invariant + +Due to the large follow distance of `Eth1Data` poll an index of a new validator assigned during deposit processing remains the same across different branches of a block tree, i.e. with existing mechanism `(pubkey, index)` cache utilized by consensus layer clients is re-org resilient. The new deposit machinery breaks this invariant and consensus layer clients will have to deal with a fact that a validator index becomes fork dependent, i.e. a validator with the same `pubkey` can have different indexes in different block tree branches. + +#### `Eth1Data` poll deprecation + +Consensus layer clients will be able to remove `Eth1Data` poll mechanism in an uncoordinated fashion once transition period is finished. The transition period is considered as finished when a network reaches the point where `state.eth1_deposit_index == state.deposit_receipts_start_index`. + ## Rationale ### `index` field @@ -177,15 +202,37 @@ This EIP introduces backwards incompatible changes to the block structure and bl ### Data complexity -At the time of writing this document, the total number of submitted deposits is 478,402 which is 88MB of deposit data. Assuming frequency of deposit transactions remains the same, historic chain data complexity induced by this EIP can be estimated as 50MB per year which is negligible in comparison to other historic data. +At the time of the latest update of this document, the total number of submitted deposits is 824,598 which is 164MB of deposit data. Assuming frequency of deposit transactions remains the same, historic chain data complexity induced by this EIP can be estimated as 60MB per year which is negligible in comparison to other historical data. -After the beacon chain launch in December 2020, the biggest observed spike in a number of submitted deposits was on March 15, 2022. More than 6000 deposit transactions were submitted during 24 hours which on average is less than 1 deposit, or 192 bytes of data, per block. +After the beacon chain launch in December 2020, the biggest observed spike in a number of submitted deposits was on June 1, 2023. More than 12,000 deposit transactions were submitted during 24 hours which on average is less than 2 deposit, or 384 bytes of data, per block. Considering the above, we conclude that data complexity introduced by this proposal is negligible. ### DoS vectors -With 1 ETH as a minimum deposit amount, the lowest cost of a byte of deposit data is 1 ETH/192 ~ 5,208,333 Gwei. This is several orders of magnitude higher than the cost of a byte of transaction's calldata, thus adding deposit operations to a block does not increase Execution Layer DoS attack surface. +The code in the deposit contract costs 15,650 gas to run in the cheapest case (when all storage slots are hot and only a single leaf has to be modified). Some deposits in a batch deposit are more expensive, but those costs, when amortized over a large number of deposits, are small at around ~1,000 gas per deposit. Under current gas pricing rules an extra 6,900 gas is charged to make a `CALL` that transfers ETH, this is a case of inefficient gas pricing and may be reduced in the future. For future robustness the beacon chain needs to be able to withstand 1,916 deposits in a 30M gas block (15,650 gas per deposit). The limit under current rules is less than 1,271 deposits in a 30M gas block. + +#### Execution layer + +With 1 ETH as a minimum deposit amount, the lowest cost of a byte of deposit data is 1 ETH/192 ~ 5,208,333 Gwei. This is several orders of magnitude higher than the cost of a byte of transaction's calldata, thus adding deposit operations to a block does not increase DoS attack surface of the execution layer. + +#### Consensus layer + +The most consuming computation of deposit processing is signature verification. Its complexity is bounded by a maximum number of deposits per block which is around 1,271 with 30M gas block at the moment. So, it is ~1,271 signature verifications which is roughly ~1.2 seconds of processing (without optimisations like batched signatures verification). An attacker would need to spend 1,000 ETH to slow down block processing by a second which isn't sustainable and viable attack long term. + +An optimistically syncing node may be susceptible to a more severe attack scenario. Such a node can't validate a list of deposits provided in a payload which makes it possible for attacker to include as many deposits as the limitation allows to. Currently, it is 8,192 deposits (1.5MB of data) with rough processing time of 8s. Considering an attacker would need to sign off on this block with its crypto economically viable signature (which requires building an alternative chain and feeding it to a syncing node), this attack vector is not considered as viable as it can't result in a significant slow down of a sync process. + +### Optimistic sync + +An optimistically syncing node have to rely on the honest majority assumption. That is, if adversary is powerful enough to finalize a deposit sequence, a syncing node will have to apply these deposits disregarding the validity of deposit receipts with respect to the execution of a given block. Thus, an adversary that can finalize an invalid chain can also convince an honest node to accept fake deposits. The same is applicable to the validity of execution layer world state today and a new deposit processing design is within boundaries of the existing security model in that regard. + +Online nodes can't be tricked into this situation because their execution layer validates supplied deposits with respect to the block execution. + +### Weak subjectivity period + +This EIP removes a hard limit on a number of deposits per epoch and makes a block gas limit the only limitation on this number. That is, the limit on deposits per epoch shifts from `MAX_DEPOSITS * SLOTS_PER_EPOCH = 512` to `max_deposits_per_30m_gas_block * SLOTS_PER_EPOCH ~ 32,768` at 30M gas block (we consider `max_deposits_per_30m_gas_block = 1,024` for simplicity). + +This change affects a number of top ups per epoch which is one of the inputs to the weak subjectivity period computation. One can top up own validators to instantly increase a portion of stake it owns with respect to those validators that are leaking. [The analysis](../assets/eip-6110/ws_period_analysis.md) does not demonstrate significant reduction of a weak subjectivity period sizes. Moreover, such an attack is not considered as viable because it requires a decent portion of stake to be burned as one of preliminaries. ## Copyright diff --git a/assets/eip-6110/ws_period_analysis.md b/assets/eip-6110/ws_period_analysis.md new file mode 100644 index 0000000000000..d69c34354c259 --- /dev/null +++ b/assets/eip-6110/ws_period_analysis.md @@ -0,0 +1,28 @@ +Modified version of a [script](https://gist.github.com/adiasg/3aceab409b36aa9a9d9156c1baa3c248) shows the following changes in the WS period computations with increase of a number of deposits per epoch (deviations are highlighted): + +| Safety Decay | Avg. Val. Balance (ETH) | Val. Count | WS (Epochs), `16` deposits per slot | WS (Epochs), `1024` deposits per slot | +| ---- | ---- | ---- | ---- | ---- | +| 10 | 20 | 32768 | 272 | **256** | +| 10 | 20 | 65536 | 288 | **256** | +| 10 | 20 | 131072 | 320 | **257** | +| 10 | 20 | 262144 | 384 | **258** | +| 10 | 20 | 524288 | 512 | **260** | +| 10 | 20 | 1048576 | 768 | **264** | +| 10 | 24 | 32768 | 310 | 310 | +| 10 | 24 | 65536 | 365 | 365 | +| 10 | 24 | 131072 | 474 | 474 | +| 10 | 24 | 262144 | 692 | 692 | +| 10 | 24 | 524288 | 692 | 692 | +| 10 | 24 | 1048576 | 1041 | **504** | +| 10 | 28 | 32768 | 504 | 504 | +| 10 | 28 | 65536 | 752 | 752 | +| 10 | 28 | 131072 | 1248 | 1248 | +| 10 | 28 | 262144 | 2241 | 2241 | +| 10 | 28 | 524288 | 2241 | 2241 | +| 10 | 28 | 1048576 | 2241 | 2241 | +| 10 | 32 | 32768 | 665 | 665 | +| 10 | 32 | 65536 | 1075 | 1075 | +| 10 | 32 | 131072 | 1894 | 1894 | +| 10 | 32 | 262144 | 3532 | 3532 | +| 10 | 32 | 524288 | 3532 | 3532 | +| 10 | 32 | 1048576 | 3532 | 3532 | From 63f0f5053efa306d3883172d9aef98155c934334 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Fri, 30 Jun 2023 00:07:47 -0700 Subject: [PATCH 05/41] Update EIP-5656: fix eip-5656 test cases and add gas costs Merged by EIP-Bot. --- EIPS/eip-5656.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/EIPS/eip-5656.md b/EIPS/eip-5656.md index be0b1a26a2db1..2ade0452efe81 100644 --- a/EIPS/eip-5656.md +++ b/EIPS/eip-5656.md @@ -119,6 +119,8 @@ post: 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f ``` +gas used: 6 + `MCOPY 0 0 32` - copy 32 bytes from offset 0 to offset 0. pre: @@ -133,34 +135,40 @@ post: 0101010101010101010101010101010101010101010101010101010101010101 ``` +gas used: 6 + `MCOPY 0 1 8` - copy 8 bytes from offset 1 to offset 0 (overlapping). pre: ``` -000102030405060708 000000000000000000000000000000000000000000000000 +000102030405060708 0000000000000000000000000000000000000000000000 ``` post: ``` -010203040506070808 000000000000000000000000000000000000000000000000 +010203040506070808 0000000000000000000000000000000000000000000000 ``` +gas used: 6 + `MCOPY 1 0 8` - copy 8 bytes from offset 0 to offset 1 (overlapping). pre: ``` -000102030405060708 000000000000000000000000000000000000000000000000 +000102030405060708 0000000000000000000000000000000000000000000000 ``` post: ``` -000001020304050607 000000000000000000000000000000000000000000000000 +00000102030405060708 00000000000000000000000000000000000000000000 ``` +gas used: 6 + ## Security Considerations TBA From 2e0974c98c1b44017a9399fa70ac731ec8fac32d Mon Sep 17 00:00:00 2001 From: Bofu Chen Date: Fri, 30 Jun 2023 15:25:39 +0800 Subject: [PATCH 06/41] Update EIP-7053: Move to Review Merged by EIP-Bot. --- EIPS/eip-7053.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-7053.md b/EIPS/eip-7053.md index 2daf5d5de54a4..afe2481e84aa7 100644 --- a/EIPS/eip-7053.md +++ b/EIPS/eip-7053.md @@ -1,10 +1,10 @@ --- eip: 7053 title: Interoperable Digital Media Indexing -description: Interoperable Indexing of Digital Media on the EVM-compatible Blockchains. +description: A universal indexing method to record, discover and retrieve the history of digital media on EVM-compatible blockchains. author: Bofu Chen (@bafu), Tammy Yang (@tammyyang) discussions-to: https://ethereum-magicians.org/t/eip-7053-interoperable-digital-media-indexing/14394 -status: Draft +status: Review type: Standards Track category: ERC created: 2023-05-22 From 2a39205a78579eac2457f3b961a7f577371fa9e5 Mon Sep 17 00:00:00 2001 From: Jessica Chang <117890123+JessicaChg@users.noreply.github.com> Date: Fri, 30 Jun 2023 17:09:58 +0800 Subject: [PATCH 07/41] Update EIP-6239: Move to Last Call Merged by EIP-Bot. --- EIPS/eip-6239.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EIPS/eip-6239.md b/EIPS/eip-6239.md index 02dc13b63b44e..11195e11254b6 100644 --- a/EIPS/eip-6239.md +++ b/EIPS/eip-6239.md @@ -4,7 +4,8 @@ title: Semantic Soulbound Tokens description: Adding RDF triples to ERC-5192 token metadata to capture social meaning author: Jessica Chang (@JessicaChg) discussions-to: https://ethereum-magicians.org/t/eip-6239-semantic-soulbound-tokens/12334 -status: Review +status: Last Call +last-call-deadline: 2023-07-06 type: Standards Track category: ERC created: 2022-12-30 From cb61e7980ac56f9799f26e4912894622132ce8ce Mon Sep 17 00:00:00 2001 From: "gmhacker.eth" <95878230+goncaloMagalhaes@users.noreply.github.com> Date: Fri, 30 Jun 2023 16:57:55 +0100 Subject: [PATCH 08/41] fix(eip-6909): Author's github not being correctly rendered (#7258) --- EIPS/eip-6909.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-6909.md b/EIPS/eip-6909.md index b265e4b5b746c..75dec6101826f 100644 --- a/EIPS/eip-6909.md +++ b/EIPS/eip-6909.md @@ -2,7 +2,7 @@ eip: 6909 title: Minimal Multi-Token Interface description: A minimal specification for managing multiple tokens by their id in a single contract. -author: Joshua Trujillo (@jtriley) +author: Joshua Trujillo (@jtriley-eth) discussions-to: https://ethereum-magicians.org/t/eip-6909-multi-token-standard/13891 status: Draft type: Standards Track From 1bb34bad0c0703f750a84e3801e394e9fb5dc802 Mon Sep 17 00:00:00 2001 From: boyuanx Date: Mon, 3 Jul 2023 13:53:10 +0800 Subject: [PATCH 09/41] Update EIP-6066: Move to Last Call Merged by EIP-Bot. --- EIPS/eip-6066.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/EIPS/eip-6066.md b/EIPS/eip-6066.md index ad751c0d21aba..59cd0ae54781a 100644 --- a/EIPS/eip-6066.md +++ b/EIPS/eip-6066.md @@ -4,7 +4,8 @@ title: Signature Validation Method for NFTs description: A way to verify signatures when the signing entity is an ERC-721 or ERC-1155 NFT author: Jack Boyuan Xu (@boyuanx) discussions-to: https://ethereum-magicians.org/t/eip-6066-signature-validation-method-for-nfts/11943 -status: Review +status: Last Call +last-call-deadline: 2023-07-10 type: Standards Track category: ERC created: 2022-11-29 From a36e82370d00f7b7738bf1a88449eb08abd52809 Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Mon, 3 Jul 2023 15:12:02 +0200 Subject: [PATCH 10/41] Update EIP-4337: Some ERC-4337 changes Merged by EIP-Bot. --- EIPS/eip-4337.md | 116 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 101 insertions(+), 15 deletions(-) diff --git a/EIPS/eip-4337.md b/EIPS/eip-4337.md index 515ff38cd1e24..d75bf8abd8215 100644 --- a/EIPS/eip-4337.md +++ b/EIPS/eip-4337.md @@ -2,7 +2,7 @@ eip: 4337 title: Account Abstraction Using Alt Mempool description: An account abstraction proposal which completely avoids consensus-layer protocol changes, instead relying on higher-layer infrastructure. -author: Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Kristof Gazso (@kristofgazso), Namra Patel (@namrapatel), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), Tjaden Hess (@tjade273) +author: Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn), Alex Forshtat (@forshtat), Kristof Gazso (@kristofgazso), Tjaden Hess (@tjade273) discussions-to: https://ethereum-magicians.org/t/erc-4337-account-abstraction-via-entry-point-contract-specification/7160 status: Draft type: Standards Track @@ -12,7 +12,7 @@ created: 2021-09-29 ## Abstract -An account abstraction proposal which completely avoids the need for consensus-layer protocol changes. Instead of adding new protocol features and changing the bottom-layer transaction type, this proposal instead introduces a higher-layer pseudo-transaction object called a `UserOperation`. Users send `UserOperation` objects into a separate mempool. A special class of actor called bundlers (either block builders, or users that can send transactions to block builders through a bundle marketplace) package up a set of these objects into a transaction making a `handleOps` call to a special contract, and that transaction then gets included in a block. +An account abstraction proposal which completely avoids the need for consensus-layer protocol changes. Instead of adding new protocol features and changing the bottom-layer transaction type, this proposal instead introduces a higher-layer pseudo-transaction object called a `UserOperation`. Users send `UserOperation` objects into a separate mempool. A special class of actor called bundlers package up a set of these objects into a transaction making a `handleOps` call to a special contract, and that transaction then gets included in a block. ## Motivation @@ -39,19 +39,18 @@ This proposal takes a different approach, avoiding any adjustments to the consen * **UserOperation** - a structure that describes a transaction to be sent on behalf of a user. To avoid confusion, it is not named "transaction". * Like a transaction, it contains "sender", "to", "calldata", "maxFeePerGas", "maxPriorityFee", "signature", "nonce" * unlike a transaction, it contains several other fields, described below - * also, the "nonce" and "signature" fields usage is not defined by the protocol, but by each account implementation + * also, the "signature" field usage is not defined by the protocol, but by each account implementation * **Sender** - the account contract sending a user operation. * **EntryPoint** - a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint. -* **Bundler** - a node (block builder) that bundles multiple UserOperations and create an EntryPoint.handleOps() transaction. Note that not all block-builders on the network are required to be bundlers +* **Bundler** - A node (block builder) that can handle UserOperations and create an EntryPoint.handleOps() transaction and add it to the next block. Note that not all block-builders on the network are required to be bundlers. * **Aggregator** - a helper contract trusted by accounts to validate an aggregated signature. Bundlers/Clients whitelist the supported aggregators. - To avoid Ethereum consensus changes, we do not attempt to create new transaction types for account-abstracted transactions. Instead, users package up the action they want their account to take in an ABI-encoded struct called a `UserOperation`: | Field | Type | Description | - | - | - | | `sender` | `address` | The account making the operation | -| `nonce` | `uint256` | Anti-replay parameter; also used as the salt for first-time account creation | +| `nonce` | `uint256` | Anti-replay parameter (see "Semi-abstracted Nonce Support" ) | | `initCode` | `bytes` | The initCode of the account (needed if and only if the account is not yet on-chain and needs to be created) | | `callData` | `bytes` | The data to pass to the `sender` during the main execution call | | `callGasLimit` | `uint256` | The amount of gas to allocate the main execution call | @@ -159,6 +158,72 @@ interface IAggregator { * **validateSignatures()** MUST validate the aggregated signature matches for all UserOperations in the array, and revert otherwise. This method is called on-chain by `handleOps()` +#### Semi-abstracted Nonce Support + +In Ethereum protocol, the sequential transaction `nonce` value is used as a replay protection method as well as to +determine the valid order of transaction being included in blocks. + +It also contributes to the transaction hash uniqueness, as a transaction by the same sender with the same +nonce may not be included in the chain twice. + +However, requiring a single sequential `nonce` value is limiting the senders' ability to define their custom logic +with regard to transaction ordering and replay protection. + +Instead of sequential `nonce` we implement a nonce mechanism that uses a single `uint256` nonce value in the `UserOperation`, +but treats it as two values: + +* 192-bit "key" +* 64-bit "sequence" + +These values are represented on-chain in the `EntryPoint` contract. +We define the following method in the `EntryPoint` interface to expose these values: + +```solidity +function getNonce(address sender, uint192 key) external view returns (uint256 nonce); +``` + +For each `key` the `sequence` is validated and incremented sequentially and monotonically by the `EntryPoint` for +each UserOperation, however a new key can be introduced with an arbitrary value at any point. + +This approach maintains the guarantee of `UserOperation` hash uniqueness on-chain on the protocol level while allowing +wallets to implement any custom logic they may need operating on a 192-bit "key" field, while fitting the 32 byte word. + +##### Reading and validating the nonce + +When preparing the UserOp clients may make a view call to this method to determine a valid value for the `nonce` field. + +Bundler's validation of a UserOp should start with `getNonce` to ensure the transaction has a valid `nonce` field. + +If the bundler is willing to accept multiple UserOperations by the same sender into their mempool, +this bundler is supposed to track the `key` and `sequence` pair of the UserOperations already added in the mempool. + +##### Usage examples + +1. Classic sequential nonce. + + In order to require the wallet to have classic, sequential nonce, the validation function should perform: + + ```solidity + require(userOp.nonce> 64; + if (sig == ADMIN_METHODSIG) { + require(key == ADMIN_KEY, "wrong nonce-key for admin operation"); + } else { + require(key == 0, "wrong nonce-key for normal operation"); + } + ``` + #### Using signature aggregators An account signifies it uses signature aggregation returning its address from `validateUserOp`. @@ -306,12 +371,15 @@ While simulating `userOp` validation, the client should make sure that: 4. Limitation on "CALL" opcodes (`CALL`, `DELEGATECALL`, `CALLCODE`, `STATICCALL`): 1. must not use value (except from account to the entrypoint) 2. must not revert with out-of-gas - 3. destination address must have code (EXTCODESIZE>0) + 3. destination address must have code (EXTCODESIZE>0) or be a standard Ethereum precompile defined at addresses from `0x01` to `0x09` 4. cannot call EntryPoint's methods, except `depositFor` (to avoid recursion) 5. `EXTCODEHASH` of every address accessed (by any opcode) does not change between first and second simulations of the op. 6. `EXTCODEHASH`, `EXTCODELENGTH`, `EXTCODECOPY` may not access address with no code. 7. If `op.initcode.length != 0` , allow only one `CREATE2` opcode call (in the first (deployment) block), otherwise forbid `CREATE2`. +Transient Storage slots defined in [EIP-1153](./eip-1153.md) and accessed using `TLOAD` (`0x5c`) and `TSTORE` (`0x5d`) opcodes +must follow the exact same validation rules as persistent storage if Transient Storage is enabled. + #### Storage associated with an address We define storage slots as "associated with an address" as all the slots that uniquely related on this address, and cannot be related with any other address. @@ -324,7 +392,6 @@ An address `A` is associated with: 3. Slots of type `keccak256(A || X) + n` on any other address. (to cover `mapping(address => value)`, which is usually used for balance in ERC-20 tokens). `n` is an offset value up to 128, to allow accessing fields in the format `mapping(address => struct)` - #### Alternative Mempools The simulation rules above are strict and prevent the ability of paymasters and signature aggregators to grief the system. @@ -349,12 +416,30 @@ During bundling, the client should: After creating the batch, before including the transaction in a block, the client should: -* Run `eth_estimateGas` with maximum possible gas, to verify the entire `handleOps` batch transaction, and use the estimated gas for the actual transaction execution. -* If the call reverted, check the `FailedOp` event. A `FailedOp` during `handleOps` simulation is an unexpected event since it was supposed to be caught by the single-UserOperation simulation. Remove the failed op that caused the revert from the batch and drop from the mempool. - If the error is caused by a factory (error code is "AA1.") or paymaster (error code is "AA3."), then also drop from mempool all other UserOps of this entity. - Repeat until `eth_estimateGas` succeeds. - -In practice, restrictions (2) and (3) basically mean that the only external accesses that the account and the paymaster can make are reading code of other contracts if their code is guaranteed to be immutable (eg. this is useful for calling or delegatecalling to libraries). +* Run `debug_traceCall` with maximum possible gas, to enforce the validation opcode and precompile banning and storage access rules, + as well as to verify the entire `handleOps` batch transaction, + and use the consumed gas for the actual transaction execution. +* If the call reverted, check the `FailedOp` event. + A `FailedOp` during `handleOps` simulation is an unexpected event since it was supposed to be caught + by the single-UserOperation simulation. +* If any verification context rule was violated the bundlers should treat it the same as + if this UserOperation reverted with a `FailedOp` event. +* If the error is caused by a `factory` (error code is `AA1x`) or a paymaster (error code is `AA3x`), and the `sender` + of the UserOp **is not** a staked entity, then issue a "ban" (see ["Reputation, throttling and banning"](#reputation-scoring-and-throttlingbanning-for-global-entities)) + for the guilty factory or paymaster. +* If the error is caused by a `factory` (error code is `AA1x`) or a paymaster (error code is `AA3x`), and the `sender` + of the UserOp **is** a staked entity, do not ban the `factory` / `paymaster` from the mempool. + Instead, issue a "ban" for the staked sender entity. +* Repeat until `debug_traceCall` succeeds. + +As staked entries may use some kind of transient storage to communicate data between UserOperations in the same bundle, +it is critical that the exact same opcode and precompile banning rules as well as storage access rules are enforced +for the `handleOps` validation in its entirety as for individual UserOperations. +Otherwise, attackers may be able to use the banned opcodes to detect running on-chain and trigger a `FailedOp` revert. + +Banning an offending entity for a given bundler is achieved by increasing its `opsSeen` value by `1000000` +and removing all UserOperations for this entity already present in the mempool. +This change will allow the negative reputation value to deteriorate over time consistent with other banning reasons. If any of the three conditions is violated, the client should reject the `op`. If both calls succeed (or, if `op.paymaster == ZERO_ADDRESS` and the first call succeeds)without violating the three conditions, the client should accept the op. On a bundler node, the storage keys accessed by both calls must be saved as the `accessList` of the `UserOperation` @@ -395,7 +480,8 @@ The value of MIN_STAKE_VALUE is determined per chain, and specified in the "bund Under the following special conditions, unstaked entities still can be used: * An entity that doesn't use any storage at all, or only the senders's storage (not the entity's storage - that does require a stake) -* If the UserOp doesn't create a new account (that is initCode is empty), then the entity may also use [storage associated with the sender](#storage-associated-with-an-address)) +* If the UserOp doesn't create a new account (that is initCode is empty), or the UserOp creates a new account using a + staked `factory` contract, then the entity may also use [storage associated with the sender](#storage-associated-with-an-address)) * A paymaster that has a “postOp()” method (that is, validatePaymasterUserOp returns “context”) must be staked #### Specification. From d10c08011f861367be5880628cf9be1d4b1e05ec Mon Sep 17 00:00:00 2001 From: Alex Forshtat Date: Mon, 3 Jul 2023 15:34:58 +0200 Subject: [PATCH 11/41] Update EIP-4337: Fix accidentally missed commit in ERC-4337 changes Merged by EIP-Bot. --- EIPS/eip-4337.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/EIPS/eip-4337.md b/EIPS/eip-4337.md index d75bf8abd8215..8ad63200e4a64 100644 --- a/EIPS/eip-4337.md +++ b/EIPS/eip-4337.md @@ -42,7 +42,14 @@ This proposal takes a different approach, avoiding any adjustments to the consen * also, the "signature" field usage is not defined by the protocol, but by each account implementation * **Sender** - the account contract sending a user operation. * **EntryPoint** - a singleton contract to execute bundles of UserOperations. Bundlers/Clients whitelist the supported entrypoint. -* **Bundler** - A node (block builder) that can handle UserOperations and create an EntryPoint.handleOps() transaction and add it to the next block. Note that not all block-builders on the network are required to be bundlers. +* **Bundler** - a node (block builder) that can handle UserOperations, + create a valid an EntryPoint.handleOps() transaction, + and add it to the block while it is still valid. + This can be achieved by a number of ways: + * Bundler can act as a block builder itself + * If the bundler is not a block builder, it MUST work with the block building infrastructure such as `mev-boost` or + other kind of PBS (proposer-builder separation) + * The `bundler` can also rely on an experimental `eth_sendRawTransactionConditional` RPC API if it is available. * **Aggregator** - a helper contract trusted by accounts to validate an aggregated signature. Bundlers/Clients whitelist the supported aggregators. To avoid Ethereum consensus changes, we do not attempt to create new transaction types for account-abstracted transactions. Instead, users package up the action they want their account to take in an ABI-encoded struct called a `UserOperation`: @@ -424,12 +431,13 @@ After creating the batch, before including the transaction in a block, the clien by the single-UserOperation simulation. * If any verification context rule was violated the bundlers should treat it the same as if this UserOperation reverted with a `FailedOp` event. -* If the error is caused by a `factory` (error code is `AA1x`) or a paymaster (error code is `AA3x`), and the `sender` +* Remove the offending UserOperation from the current bundle and from mempool. +* If the error is caused by a `factory` (error code is `AA1x`) or a `paymaster` (error code is `AA3x`), and the `sender` of the UserOp **is not** a staked entity, then issue a "ban" (see ["Reputation, throttling and banning"](#reputation-scoring-and-throttlingbanning-for-global-entities)) for the guilty factory or paymaster. -* If the error is caused by a `factory` (error code is `AA1x`) or a paymaster (error code is `AA3x`), and the `sender` +* If the error is caused by a `factory` (error code is `AA1x`) or a `paymaster` (error code is `AA3x`), and the `sender` of the UserOp **is** a staked entity, do not ban the `factory` / `paymaster` from the mempool. - Instead, issue a "ban" for the staked sender entity. + Instead, issue a "ban" for the staked `sender` entity. * Repeat until `debug_traceCall` succeeds. As staked entries may use some kind of transient storage to communicate data between UserOperations in the same bundle, From 7f6cbc3ee6e5563a756a92716162793ed073dd28 Mon Sep 17 00:00:00 2001 From: flanagansteve <32844300+flanagansteve@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:14:20 -0400 Subject: [PATCH 12/41] Fix typo in eip-3607.md (#7270) "self desctruct" -> "self destruct" --- EIPS/eip-3607.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-3607.md b/EIPS/eip-3607.md index c4479a5b19886..78ed1ee17a83a 100644 --- a/EIPS/eip-3607.md +++ b/EIPS/eip-3607.md @@ -46,7 +46,7 @@ We note that it was always the expected that a contract account's behaviour is c This does not exclude all possible attack vectors, only the most serious one. Further possible attack vectors via address collisions between contracts and EOAs are: 1. An attacker can convince a user to send funds to an account before it is deployed. Some applications require this behaviour (e.g. state channels). 2. A chain reorg can happen after a contract is deployed. If the reorg removes the contract deployment transaction the funds can still be accessed using the private key. -3. A contract can self desctruct, with the stated intention that ERC20s (or other tokens) in the contract would be burned. However, they can now be accessed by a key for that address. +3. A contract can self destruct, with the stated intention that ERC20s (or other tokens) in the contract would be burned. However, they can now be accessed by a key for that address. All these scenarios are much harder to exploit for an attacker, and likely have much lower yield making the attacks unlikely to be economically viable. From 6a1924910c8488f59a862cdd6a920c8cae8f4a0b Mon Sep 17 00:00:00 2001 From: Jan Turk Date: Mon, 3 Jul 2023 16:31:11 +0200 Subject: [PATCH 13/41] Update EIP-6381: Move to Final Merged by EIP-Bot. --- EIPS/eip-6381.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EIPS/eip-6381.md b/EIPS/eip-6381.md index f4db26779360c..51cf1733405a1 100644 --- a/EIPS/eip-6381.md +++ b/EIPS/eip-6381.md @@ -4,8 +4,7 @@ title: Public Non-Fungible Token Emote Repository description: React to any Non-Fungible Tokens using Unicode emojis. author: Bruno Škvorc (@Swader), Steven Pineda (@steven2308), Stevan Bogosavljevic (@stevyhacker), Jan Turk (@ThunderDeliverer) discussions-to: https://ethereum-magicians.org/t/eip-6381-emotable-extension-for-non-fungible-tokens/12710 -status: Last Call -last-call-deadline: 2023-06-21 +status: Final type: Standards Track category: ERC created: 2023-01-22 From 870e33cf312b25a2221d868a9ce0a21e6df2a8a8 Mon Sep 17 00:00:00 2001 From: Joachim Date: Mon, 3 Jul 2023 16:52:59 +0200 Subject: [PATCH 14/41] Update EIP-3643: Move to Draft Merged by EIP-Bot. --- EIPS/eip-3643.md | 244 +++++++----- assets/eip-3643/ONCHAINID/IClaimIssuer.sol | 60 +++ assets/eip-3643/ONCHAINID/IERC734.sol | 112 ++++++ assets/eip-3643/ONCHAINID/IERC735.sol | 99 +++++ assets/eip-3643/ONCHAINID/IIdentity.sol | 9 + .../interfaces/IClaimTopicsRegistry.sol | 42 ++ assets/eip-3643/interfaces/ICompliance.sol | 98 +++++ assets/eip-3643/interfaces/IERC3643.sol | 374 ++++++++++++++++++ .../eip-3643/interfaces/IIdentityRegistry.sol | 195 +++++++++ .../interfaces/IIdentityRegistryStorage.sol | 137 +++++++ .../interfaces/ITrustedIssuersRegistry.sol | 101 +++++ 11 files changed, 1362 insertions(+), 109 deletions(-) create mode 100644 assets/eip-3643/ONCHAINID/IClaimIssuer.sol create mode 100644 assets/eip-3643/ONCHAINID/IERC734.sol create mode 100644 assets/eip-3643/ONCHAINID/IERC735.sol create mode 100644 assets/eip-3643/ONCHAINID/IIdentity.sol create mode 100644 assets/eip-3643/interfaces/IClaimTopicsRegistry.sol create mode 100644 assets/eip-3643/interfaces/ICompliance.sol create mode 100644 assets/eip-3643/interfaces/IERC3643.sol create mode 100644 assets/eip-3643/interfaces/IIdentityRegistry.sol create mode 100644 assets/eip-3643/interfaces/IIdentityRegistryStorage.sol create mode 100644 assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol diff --git a/EIPS/eip-3643.md b/EIPS/eip-3643.md index 9a41f2e0af7d3..5e8ee930f6b5f 100644 --- a/EIPS/eip-3643.md +++ b/EIPS/eip-3643.md @@ -1,26 +1,22 @@ --- eip: 3643 title: T-REX - Token for Regulated EXchanges -description: An institutional grade security token standard that provides interfaces for the management and compliant transfer of security tokens. +description: An institutional grade security token contract that provides interfaces for the management and compliant transfer of security tokens. author: Joachim Lebrun (@Joachim-Lebrun), Tony Malghem (@TonyMalghem), Kevin Thizy (@Nakasar), Luc Falempin (@lfalempin), Adam Boudjemaa (@Aboudjem) +discussions-to: https://ethereum-magicians.org/t/eip-3643-proposition-of-the-t-rex-token-standard-for-securities/6844 +status: Draft type: Standards Track category: ERC -status: Stagnant -requires: 20, 1822 -discussions-to: https://ethereum-magicians.org/t/eip-3643-proposition-of-the-t-rex-token-standard-for-securities/6844 created: 2021-07-09 +requires: 20, 173 --- -## Simple Summary - -The T-REX token is -This standard - - ## Abstract -Standards should be backwards compatible with [ERC-20](./eip-20.md) and should be able to interact with [ERC-735](https://github.com/ethereum/EIPs/issues/735) to validate the claims linked to an [`ONCHAINID`](https://github.com/onchain-id/solidity), based on [ERC-734](https://github.com/ethereum/EIPs/issues/734) and ERC-735. +The T-REX token is an institutional grade security token standard. This standard provides a library of interfaces for the management and compliant transfer of security tokens, using an automated onchain validator system leveraging onchain identities for eligibility checks. + The standard defines several interfaces that are described hereunder: + - Token - Identity Registry - Identity Registry Storage @@ -30,13 +26,22 @@ The standard defines several interfaces that are described hereunder: ## Motivation -Give standard interfaces for security tokens issued on Ethereum, through which any third party could interact with the security token. -The functions described by these interfaces vary and allow the appropriate users to call a range of different actions, such as forced transfers, freeze tokens (partially or totally on a wallet or even freeze the entire token), minting, burning, recover lost tokens (if an investor loses access to his wallet), etc. +The advent of blockchain technology has brought about a new era of efficiency, accessibility, and liquidity in the world of asset transfer. This is particularly evident in the realm of cryptocurrencies, where users can transfer token ownership peer-to-peer without intermediaries. However, when it comes to tokenized securities or security tokens, the situation is more complex due to the need for compliance with securities laws. These tokens cannot be permissionless like utility tokens; they must be permissioned to track ownership and ensure that only eligible investors can hold tokens. + +The existing Ethereum protocol, while powerful and versatile, does not fully address the unique challenges posed by security tokens. There is a need for a standard that supports compliant issuance and management of permissioned tokens, suitable for representing a wide range of asset classes, including small businesses and real estate. -The following requirements have been compiled following discussions with parties across financial institutions that are looking to issue securities on a DLT infrastructure such as ethereum. +The proposed [ERC-3643](./eip-3643.md) standard is motivated by this need. It aims to provide a comprehensive framework for managing the lifecycle of security tokens, from issuance to transfers between eligible investors, while enforcing compliance rules at every stage. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders. + +Moreover, the standard is designed to work in conjunction with an on-chain Identity system, allowing for the validation of the identities and credentials of investors through signed attestations issued by trusted claim issuers. This ensures compliance with legal and regulatory requirements for the trading of security tokens. + +In summary, the motivation behind the proposed standard is to bring the benefits of blockchain technology to the world of securities, while ensuring compliance with existing securities laws. It aims to provide a robust, flexible, and efficient framework for the issuance and management of security tokens, thereby accelerating the evolution of capital markets. + +## Specification + +The proposed standard has the following requirements: - **MUST** be [ERC-20](./eip-20.md) compatible. -- **MUST** be used in combination with an Identification system onchain ([ONCHAINID](https://github.com/onchain-id/solidity)) +- **MUST** be used in combination with an onchain Identity system - **MUST** be able to apply any rule of compliance that is required by the regulator or by the token issuer (about the factors of eligibility of an identity or about the rules of the token itself) - **MUST** have a standard interface to pre-check if a transfer is going to pass or fail before sending it to the blockchain - **MUST** have a recovery system in case an investor loses access to his private key @@ -46,39 +51,31 @@ The following requirements have been compiled following discussions with parties - **MUST** define an Agent role and an Owner (token issuer) role - **MUST** be able to force transfers from an Agent wallet - **MUST** be able to issue transactions in batch (to save gas and to have all the transactions performed in the same block) -- **MUST** be upgradeable (code of the smart contract should be upgradeable without changing the token smart contract address) - -## Rationale -### Transfer Restrictions +While this standard is backwards compatible with ERC-20 and all ERC-20 functions can be called on an ERC-3643 token, the implementation of these functions differs due to the permissioned nature of ERC-3643. Each token transfer under this standard involves a compliance check to validate the transfer and the eligibility of the stakeholder’s identities. -Transfers of securities can fail for a variety of reasons. This is in direct contrast to utility tokens, of which generally only require the sender to have a sufficient balance. -These conditions can be related to the status of an investor’s wallet, the identity of the sender and receiver of the securities (i.e. whether they -have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at -the token level (i.e. the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor). -For [ERC-20](./eip-20.md) tokens, the `balanceOf` and `allowance` functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be -executed both on-chain and off-chain. -For tokens representing securities, the T-REX standard introduces a function `canTransfer` which provides a more general purpose way to achieve this. I.e. when the reasons for -failure are related to the compliance rules of the token and a function `isVerified` which allows to check the eligibility status of the identity of the investor. +### Agent Role Interface -### Upgradeability +The standard defines an Agent role, which is crucial for managing access to various functions of the smart contracts. The interface for the Agent role is as follows: -The token contract should be upgradeable without changing its address on the blockchain, therefore, we decided to make it `proxiable` through [ERC-1822](./eip-1822.md) (Universal Upgradeable Proxy Standard) - -### Identity Management - -Security and compliance of transfers is issued through the management of onchain identities. -- ONCHAINID -- Claim -- Identity Storage/registry -Transfers of securities can fail for a variety of reasons in contrast to utility tokens which generally only require the sender to have a sufficient balance. - - -## Specification +```solidity +interface IAgentRole { + + // events + event AgentAdded(address indexed _agent); + event AgentRemoved(address indexed _agent); + + // functions + // setters + function addAgent(address _agent) external; + function removeAgent(address _agent) external; + + // getters + function isAgent(address _agent) external view returns (bool); +} + ``` -This standard is backwards compatible with [ERC-20](./eip-20.md), therefore, all ERC-20 functions can be called on an ERC-3643 token, the interfaces being compatible. -But the functions are not implemented in the same way as a classic ERC-20 as ERC-3643 is a permissioned token, which implies a check to be performed on each single -token transfer to validate the compliance of the transfer and the eligibility of the stakeholder’s identities. +The `IAgentRole` interface allows for the addition and removal of agents, as well as checking if an address is an agent. In this standard, it is the owner role, as defined by [ERC-173](./eip-173.md), that has the responsibility of appointing and removing agents. Any contract that fulfills the role of a Token contract or an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface. ### Main functions @@ -86,46 +83,58 @@ token transfer to validate the compliance of the transfer and the eligibility of To be able to perform a transfer on T-REX you need to fulfill several conditions : -- The sender needs to hold enough free balance (total balance - frozen tokens, if any) -- The receiver needs to be whitelisted on the Identity Registry and verified (hold the necessary claims on his [ONCHAINID](https://github.com/onchain-id/solidity)) -- The sender's wallet cannot be frozen -- The receiver's wallet cannot be frozen -- The transfer has to respect all the rules of compliance defined in the `Compliance` smart contract (`canTransfer` needs to return `TRUE`) +- The sender **MUST** hold enough free balance (total balance - frozen tokens, if any) +- The receiver **MUST** be whitelisted on the Identity Registry and verified (hold the necessary claims on his onchain Identity) +- The sender's wallet **MUST NOT** be frozen +- The receiver's wallet **MUST NOT** be frozen +- The token **MUST NOT** be paused +- The transfer **MUST** respect all the rules of compliance defined in the Compliance smart contract (canTransfer needs to return TRUE) + +Here is an example of `transfer` function implementation : -Here is an example of `transfer` function implementation : ```solidity function transfer(address _to, uint256 _amount) public override whenNotPaused returns (bool) { - require(!frozen[_to] && !frozen[msg.sender], 'wallet is frozen'); - require(_amount <= balanceOf(msg.sender).sub(frozenTokens[msg.sender]), 'Insufficient Balance'); - if (tokenIdentityRegistry.isVerified(_to) && tokenCompliance.canTransfer(msg.sender, _to, _amount)) { - tokenCompliance.transferred(msg.sender, _to, _amount); - _transfer(msg.sender, _to, _amount); - return true; - } - revert('Transfer not possible'); + require(!_frozen[_to] && !_frozen[msg.sender], "ERC-3643: Frozen wallet"); + require(_amount <= balanceOf(msg.sender) - (_frozenTokens[msg.sender]), "ERC-3643: Insufficient Balance"); + require( _tokenIdentityRegistry.isVerified(to), "ERC-3643: Invalid identity" ); + require( _tokenCompliance.canTransfer(from, to, amount), "ERC-3643: Compliance failure" ); + _transfer(msg.sender, _to, _amount); + _tokenCompliance.transferred(msg.sender, _to, _amount); + return true; } ``` - - The `transferFrom` function works the same way while the `mint` function and the `forcedTransfer` function only require the receiver to be whitelisted and verified on the Identity Registry (they bypass the compliance rules). The `burn` function bypasses all checks on eligibility. + +The `transferFrom` function works the same way while the `mint` function and the `forcedTransfer` function only require the receiver to be whitelisted and verified on the Identity Registry (they bypass the compliance rules). The `burn` function bypasses all checks on eligibility. #### isVerified -The `isVerified` function is called from within the transfer functions `transfer`, `transferFrom`, `mint` and `forcedTransfer` to instruct the `Identity Registry` to check if the receiver is a valid investor, i.e. if his wallet address is in the `Identity Registry` of the token, and if the `ONCHAINID`contract linked to his wallet contains the claims (see ERC-735) required in the `Claim Topics Registry` and if these claims are signed by an authorized Claim Issuer as required in the `Trusted Issuers Registry`. -If all the requirements are fulfilled, the `isVerified` function returns `TRUE`, otherwise it returns `FALSE`. An implementation of this function can be found on the [T-REX repository](https://github.com/TokenySolutions/T-REX). +The `isVerified` function is called from within the transfer functions `transfer`, `transferFrom`, `mint` and +`forcedTransfer` to instruct the `Identity Registry` to check if the receiver is a valid investor, i.e. if his +wallet address is in the `Identity Registry` of the token, and if the `Identity`contract linked to his wallet +contains the claims (see [Claim Holder](../assets/eip-3643/ONCHAINID/IERC735.sol)) required in the `Claim Topics Registry` and +if these claims are signed by an authorized Claim Issuer as required in the `Trusted Issuers Registry`. +If all the requirements are fulfilled, the `isVerified` function returns `TRUE`, otherwise it returns `FALSE`. An +implementation of this function can be found on the T-REX repository of Tokeny. #### canTransfer -The `canTransfer` function is also called from within transfer functions. This function checks if the transfer is compliant with global compliance rules applied to the token, in opposition with `isVerified` that only checks the eligibility of an investor to hold and receive tokens, the `canTransfer` function is looking at global compliance rules, e.g. check if the transfer is compliant in the case there is a fixed maximum number of token holders to respect (can be a limited number of holders per country as well), check if the transfer respects rules setting a maximum amount of tokens per investor, ... -If all the requirements are fulfilled, the `canTransfer` function will return `TRUE` otherwise it will return `FALSE` and the transfer will not be allowed to happen. An implementation of this function can be found on the [T-REX repository](https://github.com/TokenySolutions/T-REX). +The `canTransfer` function is also called from within transfer functions. This function checks if the transfer is compliant with global compliance rules applied to the token, in opposition with `isVerified` that only checks the eligibility of an investor to hold and receive tokens, the `canTransfer` function is looking at global compliance rules, e.g. check if the transfer is compliant in the case there is a fixed maximum number of token holders to respect (can be a limited number of holders per country as well), check if the transfer respects rules setting a maximum amount of tokens per investor, ... +If all the requirements are fulfilled, the `canTransfer` function will return `TRUE` otherwise it will return +`FALSE` and the transfer will not be allowed to happen. An implementation of this function can be found on the T-REX +repository of Tokeny. #### Other functions -Description of other functions of the ERC-3643 can be found in the `interfaces` folder. An implementation of the ERC-3643 suite of smart contracts can be found on the [T-REX repository](https://github.com/TokenySolutions/T-REX). +Description of other functions of the ERC-3643 can be found in the `interfaces` folder. An implementation of the +ERC-3643 suite of smart contracts can be found on the T-REX repository of Tokeny. ### Token interface -ERC-3643 permissioned tokens are based on a standard ERC-20 structure but with some functions being added in order to ensure compliance in the transactions of the security tokens. The functions `transfer` and `transferFrom` are implemented in a conditional way, allowing them to proceed with a transfer only IF the transaction is valid. The permissioned tokens are allowed to be transferred only to validated counterparties, in order to avoid tokens being held in wallets/ONCHAINIDs of ineligible/unauthorized investors. The ERC-3643 standard also supports the recovery of security tokens in case an investor loses his/her wallet private key. A history of recovered tokens is maintained on the blockchain for transparency reasons. ERC-3643 tokens are implementing a lot of additional functions to give the owner or his agent the possibility to manage supply, transfer rules, lockups and everything that could be required in the management of a security. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces). +ERC-3643 permissioned tokens build upon the standard ERC-20 structure, but with additional functions to ensure compliance in the transactions of the security tokens. The functions `transfer` and `transferFrom` are implemented in a conditional way, allowing them to proceed with a transfer only if the transaction is valid. The permissioned tokens are allowed to be transferred only to validated counterparties, in order to avoid tokens being held in wallets/Identity contracts of ineligible/unauthorized investors. The ERC-3643 standard also supports the recovery of security tokens in case an investor loses access to their wallet private key. A history of recovered tokens is maintained on the blockchain for transparency reasons. + +ERC-3643 tokens implement a range of additional functions to enable the owner or their appointed agents to manage supply, transfer rules, lockups, and any other requirements in the management of a security. The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of a Token contract within the context of this standard must be compatible with the `IAgentRole` interface. + +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IERC3643.sol). ```solidity interface IERC3643 is IERC20 { @@ -144,10 +153,7 @@ interface IERC3643 is IERC20 { // functions // getters - function decimals() external view returns (uint8); - function name() external view returns (string memory); function onchainID() external view returns (address); - function symbol() external view returns (string memory); function version() external view returns (string memory); function identityRegistry() external view returns (IIdentityRegistry); function compliance() external view returns (ICompliance); @@ -181,21 +187,19 @@ interface IERC3643 is IERC20 { function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external; function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; - - // roles setting - function transferOwnershipOnTokenContract(address _newOwner) external; - function addAgentOnTokenContract(address _agent) external; - function removeAgentOnTokenContract(address _agent) external; } ``` ### Identity Registry Interface -This Identity Registry is linked to storage that contains a dynamic whitelist of identities. The Identity Registry makes the link between a wallet address, an [ONCHAINID](https://tokeny.com/onchainid/) and a country code corresponding to the country of residence of the investor, this country code is set in accordance with the [ISO-3166 standard](https://www.iso.org/iso-3166-country-codes.html). It also contains a function called `isVerified()`, which returns a status based on the validity of claims (as per the security token requirements) in the user’s ONCHAINID. The Identity Registry is managed by the agent wallet(s) i.e. only the agent(s) can add or remove identities in the registry (note: the agent role on the Identity Registry is set by the owner, therefore the owner could set himself as the agent if he wants to keep everything under his own control). There is a specific identity registry for each security token. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces). +The Identity Registry is linked to storage that contains a dynamic whitelist of identities. It establishes the link between a wallet address, an Identity smart contract, and a country code corresponding to the investor's country of residence. This country code is set in accordance with the ISO-3166 standard. The Identity Registry also includes a function called `isVerified()`, which returns a status based on the validity of claims (as per the security token requirements) in the user’s Identity contract. + +The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents. Any contract that fulfills the role of an Identity Registry within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry is managed by the agent wallet(s), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry is set by the owner, therefore the owner could set themselves as the agent if they want to maintain full control. There is a specific identity registry for each security token. -Note that [`IClaimIssuer`](https://github.com/onchain-id/solidity/blob/master/contracts/interface/IClaimIssuer.sol) and [`IIdentity`](https://github.com/onchain-id/solidity/blob/master/contracts/interface/IIdentity.sol) are needed in this interface and are coming from [ONCHAINID](https://github.com/onchain-id/solidity) +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistry.sol). + +Note that [`IClaimIssuer`](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol) and [`IIdentity`](../assets/eip-3643/ONCHAINID/IIdentity.sol) are needed in this interface as they are required for the Identity eligibility checks. ```solidity interface IIdentityRegistry { @@ -234,18 +238,16 @@ interface IIdentityRegistry { function isVerified(address _userAddress) external view returns (bool); function identity(address _userAddress) external view returns (IIdentity); function investorCountry(address _userAddress) external view returns (uint16); - - // roles setters - function transferOwnershipOnIdentityRegistryContract(address _newOwner) external; - function addAgentOnIdentityRegistryContract(address _agent) external; - function removeAgentOnIdentityRegistryContract(address _agent) external; } ``` ### Identity Registry Storage Interface -The Identity Registry Storage stores the identity addresses of all the authorized investors in the security token(s) linked to the storage contract i.e. all identities of investors who have been authorized to hold the token(s) after having gone through the appropriate KYC and eligibility checks. The Identity Registry Storage can be bound to one or several Identity Registry contract(s). The goal of the Identity Registry storage is to separate the Identity Registry functions and specifications from its storage, this way it is possible to keep one single Identity Registry contract per token, with its own Trusted Issuers Registry and Claim Topics Registry but with a shared whitelist of investors used by the `isVerifed()` function implemented in the Identity Registries to check the eligibility of the receiver in a transfer transaction. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces). +The Identity Registry Storage stores the identity addresses of all the authorized investors in the security token(s) linked to the storage contract. These are all identities of investors who have been authorized to hold the token(s) after having gone through the appropriate KYC and eligibility checks. The Identity Registry Storage can be bound to one or several Identity Registry contract(s). The goal of the Identity Registry storage is to separate the Identity Registry functions and specifications from its storage. This way, it is possible to keep one single Identity Registry contract per token, with its own Trusted Issuers Registry and Claim Topics Registry, but with a shared whitelist of investors used by the `isVerifed()` function implemented in the Identity Registries to check the eligibility of the receiver in a transfer transaction. + +The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of appointing agents(in this case through the `bindIdentityRegistry` function). Any contract that fulfills the role of an Identity Registry Storage within the context of this standard must be compatible with the `IAgentRole` interface. The Identity Registry Storage is managed by the agent addresses (i.e. the bound Identity Registries), meaning only the agent(s) can add or remove identities in the registry. Note that the agent role on the Identity Registry Storage is set by the owner, therefore the owner could set themselves as the agent if they want to modify the storage manually. Otherwise it is the bound Identity Registries that are using the agent role to write in the Identity Registry Storage. + +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IIdentityRegistryStorage.sol). ```solidity interface IIdentityRegistryStorage { @@ -268,56 +270,55 @@ interface IIdentityRegistryStorage { function modifyStoredIdentity(address _userAddress, IIdentity _identity) external; // role setter - function transferOwnershipOnIdentityRegistryStorage(address _newOwner) external; function bindIdentityRegistry(address _identityRegistry) external; function unbindIdentityRegistry(address _identityRegistry) external; // getter for bound IdentityRegistry role function linkedIdentityRegistries() external view returns (address[] memory); } - ``` ### Compliance Interface -The Compliance is used to set the rules of the offering itself and ensures these rules are respected during the whole lifecycle of the token, e.g. the compliance contract will define the maximum amount of investors per country, the maximum amount of tokens per investor, the accepted countries for the circulation of the token (using the country code corresponding to each investor in the Identity Registry). The compliance smart contract is a “tailor-made” contract that is implemented in accordance with the legal requirements and following the desires of the token issuer. This contract is triggered at every transaction by the Token and returns `TRUE` if the transaction is compliant with the rules of the offering and `FALSE` otherwise. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces). +The Compliance contract is used to set the rules of the offering itself and ensures these rules are respected during the whole lifecycle of the token. For example, the Compliance contract will define the maximum amount of investors per country, the maximum amount of tokens per investor, and the accepted countries for the circulation of the token (using the country code corresponding to each investor in the Identity Registry). The Compliance smart contract can be either “tailor-made”, following the legal requirements of the token issuer, or can be deployed under a generic modular form, which can then add and remove external compliance `Modules` to fit the legal requirements of the token in the same way as a custom "tailor-made" contract would. + +This contract is triggered at every transaction by the Token and returns `TRUE` if the transaction is compliant with the rules of the offering and `FALSE` otherwise. + +The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of setting the Compliance parameters and binding the Compliance to a Token contract. + +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ICompliance.sol). ```solidity interface ICompliance { // events - event TokenAgentAdded(address _agentAddress); - event TokenAgentRemoved(address _agentAddress); event TokenBound(address _token); event TokenUnbound(address _token); // functions // initialization of the compliance contract - function addTokenAgent(address _agentAddress) external; - function removeTokenAgent(address _agentAddress) external; function bindToken(address _token) external; function unbindToken(address _token) external; // check the parameters of the compliance contract - function isTokenAgent(address _agentAddress) external view returns (bool); function isTokenBound(address _token) external view returns (bool); + function getTokenBound() external view returns (address); // compliance check and state update function canTransfer(address _from, address _to, uint256 _amount) external view returns (bool); function transferred(address _from, address _to, uint256 _amount) external; function created(address _to, uint256 _amount) external; function destroyed(address _from, uint256 _amount) external; - - // setting owner role - function transferOwnershipOnComplianceContract(address newOwner) external; } ``` ### Trusted Issuer's Registry Interface -The Trusted Issuer's Registry stores the contract addresses ([ONCHAINID](https://tokeny.com/onchainid/)) of all the trusted claim issuers for a specific security token. The [ONCHAINID](https://tokeny.com/onchainid/) of token owners (the investors) must have claims signed by the claim issuers stored in this smart contract in order to be able to hold the token. The ownership of this contract is given to the token issuer allowing them to manage this registry as per their requirements. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces) +The Trusted Issuer's Registry stores the contract addresses ([IClaimIssuer](../assets/eip-3643/ONCHAINID/IClaimIssuer.sol)) of all the trusted claim issuers for a specific security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners (the investors) must have claims signed by the claim issuers stored in this smart contract in order to be able to hold the token. + +The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add, remove, and update the list of Trusted Issuers. + +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol). ```solidity interface ITrustedIssuersRegistry { @@ -337,17 +338,18 @@ interface ITrustedIssuersRegistry { function getTrustedIssuers() external view returns (IClaimIssuer[] memory); function isTrustedIssuer(address _issuer) external view returns(bool); function getTrustedIssuerClaimTopics(IClaimIssuer _trustedIssuer) external view returns(uint[] memory); + function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory); function hasClaimTopic(address _issuer, uint _claimTopic) external view returns(bool); - - // role setter - function transferOwnershipOnIssuersRegistryContract(address _newOwner) external; } ``` ### Claim Topics Registry Interface -The Claim Topics Registry stores all the trusted claim topics for the security token. The [ONCHAINID](https://tokeny.com/onchainid/) of token owners must contain claims of the claim topics stored in this smart contract. The ownership of this contract is given to the token issuer allowing them to manage this registry as per their requirements. -A detailed description of the functions can be found in the [interfaces folder](https://github.com/TokenySolutions/EIP3643/tree/main/interfaces) +The Claim Topics Registry stores all the trusted claim topics for the security token. The Identity contract ([IIdentity](../assets/eip-3643/ONCHAINID/IIdentity.sol)) of token owners must contain claims of the claim topics stored in this smart contract. + +The standard relies on ERC-173 to define contract ownership, with the owner having the responsibility of managing this registry as per their requirements. This includes the ability to add and remove required Claim Topics. + +A detailed description of the functions can be found in the [interfaces folder](../assets/eip-3643/interfaces/IClaimTopicsRegistry.sol). ```solidity interface IClaimTopicsRegistry { @@ -363,19 +365,43 @@ interface IClaimTopicsRegistry { // getter function getClaimTopics() external view returns (uint256[] memory); - - // role setter - function transferOwnershipOnClaimTopicsRegistryContract(address _newOwner) external; } ``` -## Test Cases +## Rationale + +### Transfer Restrictions + +Transfers of securities can fail for a variety of reasons. This is in direct contrast to utility tokens, which generally only require the sender to have a sufficient balance. These conditions can be related to the status of an investor’s wallet, the identity of the sender and receiver of the securities (i.e., whether they have been through a KYC process, whether they are accredited or an affiliate of the issuer) or for reasons unrelated to the specific transfer but instead set at the token level (i.e., the token contract enforces a maximum number of investors or a cap on the percentage held by any single investor). For ERC-20 tokens, the `balanceOf` and `allowance` functions provide a way to check that a transfer is likely to succeed before executing the transfer, which can be executed both on-chain and off-chain. For tokens representing securities, the T-REX standard introduces a function `canTransfer` which provides a more general-purpose way to achieve this. I.e., when the reasons for failure are related to the compliance rules of the token and a function `isVerified` which allows checking the eligibility status of the identity of the investor. Transfers can also fail if the address of the sender and/or receiver is frozen, or if the free balance of the sender (total balance - frozen tokens) is lower than the amount to transfer. Ultimately, the transfer could be blocked if the token is `paused`. + +### Identity Management + +Security and compliance of transfers are enforced through the management of on-chain identities. These include: + +- Identity contract: A unique identifier for each investor, which is used to manage their identity and claims. +- Claim: Signed attestations issued by a trusted claim issuer that confirm certain attributes or qualifications of the token holders, such as their identity, location, investor status, or KYC/AML clearance. +- Identity Storage/Registry: A storage system for all Identity contracts and their associated wallets, which is used to + verify the eligibility of investors during transfers. + +### Token Lifecycle Management + +The T-REX standard provides a comprehensive framework for managing the lifecycle of security tokens. This includes the issuance of tokens, transfers between eligible investors, and the enforcement of compliance rules at every stage of the token's lifecycle. The standard also supports additional features such as token pausing and freezing, which can be used to manage the token in response to regulatory requirements or changes in the status of the token or its holders. + +### Additional Compliance Rules + +The T-REX standard supports the implementation of additional compliance rules through modular compliance. These modules can be used to enforce a wide range of rules and restrictions, such as caps on the number of investors or the percentage of tokens held by a single investor, restrictions on transfers between certain types of investors, and more. This flexibility allows issuers to tailor the compliance rules of their tokens to their specific needs and regulatory environment. + +## Backwards Compatibility + +T-REX tokens should be backwards compatible with ERC-20 and ERC-173 +and should be able to interact with a [Claim Holder contract](../assets/eip-3643/ONCHAINID/IERC735.sol) to validate +the claims linked to an [Identity contract](../assets/eip-3643/ONCHAINID/IIdentity.sol). -The standard is implemented and tested with full coverage on Tokeny's [T-REX repository](https://github.com/TokenySolutions/T-REX) ## Security Considerations -The suite of Smart Contracts has been audited by an external and independent company. The results can be found in [this document](https://tokeny.com/wp-content/uploads/2020/05/Tokeny-Solutions_T-REX-v3_Smart-Contract-Audit-Report_Kapersky.pdf). +This specification has been audited by Kapersky and Hacken, and no notable security considerations were found. +While the audits were primarily focused on the specific implementation by Tokeny, they also challenged and validated the core principles of the T-REX standard. The auditing teams approval of these principles provides assurance that the standard itself is robust and does not present any significant security concerns. ## Copyright diff --git a/assets/eip-3643/ONCHAINID/IClaimIssuer.sol b/assets/eip-3643/ONCHAINID/IClaimIssuer.sol new file mode 100644 index 0000000000000..bcdb4b0bd76d3 --- /dev/null +++ b/assets/eip-3643/ONCHAINID/IClaimIssuer.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "./IIdentity.sol"; + +interface IClaimIssuer is IIdentity { + + /** + * @dev Emitted when a claim is revoked. + * + * Specification: MUST be triggered when revoking a claim. + */ + event ClaimRevoked(bytes indexed signature); + + /** + * @dev Revoke a claim previously issued, the claim is no longer considered as valid after revocation. + * @notice will fetch the claim from the identity contract (unsafe). + * @param _claimId the id of the claim + * @param _identity the address of the identity contract + * @return isRevoked true when the claim is revoked + */ + function revokeClaim(bytes32 _claimId, address _identity) external returns(bool); + + /** + * @dev Revoke a claim previously issued, the claim is no longer considered as valid after revocation. + * @param signature the signature of the claim + */ + function revokeClaimBySignature(bytes calldata signature) external; + + /** + * @dev Returns revocation status of a claim. + * @param _sig the signature of the claim + * @return isRevoked true if the claim is revoked and false otherwise + */ + function isClaimRevoked(bytes calldata _sig) external view returns (bool); + + /** + * @dev Checks if a claim is valid. + * @param _identity the identity contract related to the claim + * @param claimTopic the claim topic of the claim + * @param sig the signature of the claim + * @param data the data field of the claim + * @return claimValid true if the claim is valid, false otherwise + */ + function isClaimValid( + IIdentity _identity, + uint256 claimTopic, + bytes calldata sig, + bytes calldata data) + external view returns (bool); + + /** + * @dev returns the address that signed the given data + * @param sig the signature of the data + * @param dataHash the data that was signed + * returns the address that signed dataHash and created the signature sig + */ + function getRecoveredAddress(bytes calldata sig, bytes32 dataHash) external pure returns (address); +} diff --git a/assets/eip-3643/ONCHAINID/IERC734.sol b/assets/eip-3643/ONCHAINID/IERC734.sol new file mode 100644 index 0000000000000..99aa31f9fb929 --- /dev/null +++ b/assets/eip-3643/ONCHAINID/IERC734.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +/** + * @dev interface of the ERC734 (Key Holder) standard as defined in the EIP. + */ +interface IERC734 { + + /** + * @dev Emitted when an execution request was approved. + * + * Specification: MUST be triggered when approve was successfully called. + */ + event Approved(uint256 indexed executionId, bool approved); + + /** + * @dev Emitted when an execute operation was approved and successfully performed. + * + * Specification: MUST be triggered when approve was called and the execution was successfully approved. + */ + event Executed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + + /** + * @dev Emitted when an execution request was performed via `execute`. + * + * Specification: MUST be triggered when execute was successfully called. + */ + event ExecutionRequested(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + + /** + * @dev Emitted when an execute operation was called and failed + * + * Specification: MUST be triggered when execute call failed + */ + event ExecutionFailed(uint256 indexed executionId, address indexed to, uint256 indexed value, bytes data); + + /** + * @dev Emitted when a key was added to the Identity. + * + * Specification: MUST be triggered when addKey was successfully called. + */ + event KeyAdded(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + + /** + * @dev Emitted when a key was removed from the Identity. + * + * Specification: MUST be triggered when removeKey was successfully called. + */ + event KeyRemoved(bytes32 indexed key, uint256 indexed purpose, uint256 indexed keyType); + + /** + * @dev Adds a _key to the identity. The _purpose specifies the purpose of the key. + * + * Triggers Event: `KeyAdded` + * + * Specification: MUST only be done by keys of purpose 1, or the identity + * itself. If it's the identity itself, the approval process will determine its approval. + */ + function addKey(bytes32 _key, uint256 _purpose, uint256 _keyType) external returns (bool success); + + /** + * @dev Approves an execution. + * + * Triggers Event: `Approved` + * Triggers on execution successful Event: `Executed` + * Triggers on execution failure Event: `ExecutionFailed` + */ + function approve(uint256 _id, bool _approve) external returns (bool success); + + /** + * @dev Removes _purpose for _key from the identity. + * + * Triggers Event: `KeyRemoved` + * + * Specification: MUST only be done by keys of purpose 1, or the identity itself. + * If it's the identity itself, the approval process will determine its approval. + */ + function removeKey(bytes32 _key, uint256 _purpose) external returns (bool success); + + /** + * @dev Passes an execution instruction to an ERC734 identity. + * How the execution is handled is up to the identity implementation: + * An execution COULD be requested and require `approve` to be called with one or more keys of purpose 1 or 2 to + * approve this execution. + * Execute COULD be used as the only accessor for `addKey` and `removeKey`. + * + * Triggers Event: ExecutionRequested + * Triggers on direct execution Event: Executed + */ + function execute(address _to, uint256 _value, bytes calldata _data) external payable returns (uint256 executionId); + + /** + * @dev Returns the full key data, if present in the identity. + */ + function getKey(bytes32 _key) external view returns (uint256[] memory purposes, uint256 keyType, bytes32 key); + + /** + * @dev Returns the list of purposes associated with a key. + */ + function getKeyPurposes(bytes32 _key) external view returns(uint256[] memory _purposes); + + /** + * @dev Returns an array of public key bytes32 held by this identity. + */ + function getKeysByPurpose(uint256 _purpose) external view returns (bytes32[] memory keys); + + /** + * @dev Returns TRUE if a key is present and has the given purpose. If the key is not present it returns FALSE. + */ + function keyHasPurpose(bytes32 _key, uint256 _purpose) external view returns (bool exists); +} diff --git a/assets/eip-3643/ONCHAINID/IERC735.sol b/assets/eip-3643/ONCHAINID/IERC735.sol new file mode 100644 index 0000000000000..58d02219fdc23 --- /dev/null +++ b/assets/eip-3643/ONCHAINID/IERC735.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +/** + * @dev interface of the ERC735 (Claim Holder) standard as defined in the EIP. + */ +interface IERC735 { + + /** + * @dev Emitted when a claim was added. + * + * Specification: MUST be triggered when a claim was successfully added. + */ + event ClaimAdded( + bytes32 indexed claimId, + uint256 indexed topic, + uint256 scheme, + address indexed issuer, + bytes signature, + bytes data, + string uri); + + /** + * @dev Emitted when a claim was removed. + * + * Specification: MUST be triggered when removeClaim was successfully called. + */ + event ClaimRemoved( + bytes32 indexed claimId, + uint256 indexed topic, + uint256 scheme, + address indexed issuer, + bytes signature, + bytes data, + string uri); + + /** + * @dev Emitted when a claim was changed. + * + * Specification: MUST be triggered when addClaim was successfully called on an existing claimId. + */ + event ClaimChanged( + bytes32 indexed claimId, + uint256 indexed topic, + uint256 scheme, + address indexed issuer, + bytes signature, + bytes data, + string uri); + + /** + * @dev Add or update a claim. + * + * Triggers Event: `ClaimAdded`, `ClaimChanged` + * + * Specification: Add or update a claim from an issuer. + * + * _signature is a signed message of the following structure: + * `keccak256(abi.encode(address identityHolder_address, uint256 topic, bytes data))`. + * Claim IDs are generated using `keccak256(abi.encode(address issuer_address + uint256 topic))`. + */ + function addClaim( + uint256 _topic, + uint256 _scheme, + address issuer, + bytes calldata _signature, + bytes calldata _data, + string calldata _uri) + external returns (bytes32 claimRequestId); + + /** + * @dev Removes a claim. + * + * Triggers Event: `ClaimRemoved` + * + * Claim IDs are generated using `keccak256(abi.encode(address issuer_address, uint256 topic))`. + */ + function removeClaim(bytes32 _claimId) external returns (bool success); + + /** + * @dev Get a claim by its ID. + * + * Claim IDs are generated using `keccak256(abi.encode(address issuer_address, uint256 topic))`. + */ + function getClaim(bytes32 _claimId) + external view returns( + uint256 topic, + uint256 scheme, + address issuer, + bytes memory signature, + bytes memory data, + string memory uri); + + /** + * @dev Returns an array of claim IDs by topic. + */ + function getClaimIdsByTopic(uint256 _topic) external view returns(bytes32[] memory claimIds); +} diff --git a/assets/eip-3643/ONCHAINID/IIdentity.sol b/assets/eip-3643/ONCHAINID/IIdentity.sol new file mode 100644 index 0000000000000..dfc63a4da5030 --- /dev/null +++ b/assets/eip-3643/ONCHAINID/IIdentity.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "./IERC734.sol"; +import "./IERC735.sol"; + +// solhint-disable-next-line no-empty-blocks +interface IIdentity is IERC734, IERC735 {} diff --git a/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol b/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol new file mode 100644 index 0000000000000..ea483f5f47c5c --- /dev/null +++ b/assets/eip-3643/interfaces/IClaimTopicsRegistry.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +interface IClaimTopicsRegistry { + /** + * this event is emitted when a claim topic has been added to the ClaimTopicsRegistry + * the event is emitted by the 'addClaimTopic' function + * `claimTopic` is the required claim added to the Claim Topics Registry + */ + event ClaimTopicAdded(uint256 indexed claimTopic); + + /** + * this event is emitted when a claim topic has been removed from the ClaimTopicsRegistry + * the event is emitted by the 'removeClaimTopic' function + * `claimTopic` is the required claim removed from the Claim Topics Registry + */ + event ClaimTopicRemoved(uint256 indexed claimTopic); + + /** + * @dev Add a trusted claim topic (For example: KYC=1, AML=2). + * Only owner can call. + * emits `ClaimTopicAdded` event + * cannot add more than 15 topics for 1 token as adding more could create gas issues + * @param _claimTopic The claim topic index + */ + function addClaimTopic(uint256 _claimTopic) external; + + /** + * @dev Remove a trusted claim topic (For example: KYC=1, AML=2). + * Only owner can call. + * emits `ClaimTopicRemoved` event + * @param _claimTopic The claim topic index + */ + function removeClaimTopic(uint256 _claimTopic) external; + + /** + * @dev Get the trusted claim topics for the security token + * @return Array of trusted claim topics + */ + function getClaimTopics() external view returns (uint256[] memory); +} diff --git a/assets/eip-3643/interfaces/ICompliance.sol b/assets/eip-3643/interfaces/ICompliance.sol new file mode 100644 index 0000000000000..d57bcd8718854 --- /dev/null +++ b/assets/eip-3643/interfaces/ICompliance.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +interface ICompliance { + + /// events + + /** + * this event is emitted when a token has been bound to the compliance contract + * the event is emitted by the bindToken function + * `_token` is the address of the token to bind + */ + event TokenBound(address _token); + + /** + * this event is emitted when a token has been unbound from the compliance contract + * the event is emitted by the unbindToken function + * `_token` is the address of the token to unbind + */ + event TokenUnbound(address _token); + + /// functions + + /** + * @dev binds a token to the compliance contract + * @param _token address of the token to bind + * This function can be called ONLY by the owner of the compliance contract + * Emits a TokenBound event + */ + function bindToken(address _token) external; + + /** + * @dev unbinds a token from the compliance contract + * @param _token address of the token to unbind + * This function can be called ONLY by the owner of the compliance contract + * Emits a TokenUnbound event + */ + function unbindToken(address _token) external; + + + /** + * @dev function called whenever tokens are transferred + * from one wallet to another + * this function can be used to update state variables of the compliance contract + * This function can be called ONLY by the token contract bound to the compliance + * @param _from The address of the sender + * @param _to The address of the receiver + * @param _amount The amount of tokens involved in the transfer + */ + function transferred( + address _from, + address _to, + uint256 _amount + ) external; + + /** + * @dev function called whenever tokens are created on a wallet + * this function can be used to update state variables of the compliance contract + * This function can be called ONLY by the token contract bound to the compliance + * @param _to The address of the receiver + * @param _amount The amount of tokens involved in the minting + */ + function created(address _to, uint256 _amount) external; + + /** + * @dev function called whenever tokens are destroyed from a wallet + * this function can be used to update state variables of the compliance contract + * This function can be called ONLY by the token contract bound to the compliance + * @param _from The address on which tokens are burnt + * @param _amount The amount of tokens involved in the burn + */ + function destroyed(address _from, uint256 _amount) external; + + /** + * @dev checks that the transfer is compliant. + * default compliance always returns true + * READ ONLY FUNCTION, this function cannot be used to increment + * counters, emit events, ... + * @param _from The address of the sender + * @param _to The address of the receiver + * @param _amount The amount of tokens involved in the transfer + * This function will call all checks implemented on compliance + * If all checks return TRUE, the function returns TRUE + * returns FALSE otherwise + */ + function canTransfer( + address _from, + address _to, + uint256 _amount + ) external view returns (bool); + + /** + * @dev getter for the address of the token bound + * returns the address of the token + */ + function getTokenBound() external view returns (address); +} diff --git a/assets/eip-3643/interfaces/IERC3643.sol b/assets/eip-3643/interfaces/IERC3643.sol new file mode 100644 index 0000000000000..a8ac52682b04a --- /dev/null +++ b/assets/eip-3643/interfaces/IERC3643.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "../registry/interface/IIdentityRegistry.sol"; +import "../compliance/modular/IModularCompliance.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/// @dev interface +interface IERC3643 is IERC20 { + + /// events + + /** + * this event is emitted when the token information is updated. + * the event is emitted by the token init function and by the setTokenInformation function + * `_newName` is the name of the token + * `_newSymbol` is the symbol of the token + * `_newDecimals` is the decimals of the token + * `_newVersion` is the version of the token, current version is 3.0 + * `_newOnchainID` is the address of the onchainID of the token + */ + event UpdatedTokenInformation(string indexed _newName, string indexed _newSymbol, uint8 _newDecimals, string + _newVersion, address indexed _newOnchainID); + + /** + * this event is emitted when the IdentityRegistry has been set for the token + * the event is emitted by the token constructor and by the setIdentityRegistry function + * `_identityRegistry` is the address of the Identity Registry of the token + */ + event IdentityRegistryAdded(address indexed _identityRegistry); + + /** + * this event is emitted when the Compliance has been set for the token + * the event is emitted by the token constructor and by the setCompliance function + * `_compliance` is the address of the Compliance contract of the token + */ + event ComplianceAdded(address indexed _compliance); + + /** + * this event is emitted when an investor successfully recovers his tokens + * the event is emitted by the recoveryAddress function + * `_lostWallet` is the address of the wallet that the investor lost access to + * `_newWallet` is the address of the wallet that the investor provided for the recovery + * `_investorOnchainID` is the address of the onchainID of the investor who asked for a recovery + */ + event RecoverySuccess(address indexed _lostWallet, address indexed _newWallet, address indexed _investorOnchainID); + + /** + * this event is emitted when the wallet of an investor is frozen or unfrozen + * the event is emitted by setAddressFrozen and batchSetAddressFrozen functions + * `_userAddress` is the wallet of the investor that is concerned by the freezing status + * `_isFrozen` is the freezing status of the wallet + * if `_isFrozen` equals `true` the wallet is frozen after emission of the event + * if `_isFrozen` equals `false` the wallet is unfrozen after emission of the event + * `_owner` is the address of the agent who called the function to freeze the wallet + */ + event AddressFrozen(address indexed _userAddress, bool indexed _isFrozen, address indexed _owner); + + /** + * this event is emitted when a certain amount of tokens is frozen on a wallet + * the event is emitted by freezePartialTokens and batchFreezePartialTokens functions + * `_userAddress` is the wallet of the investor that is concerned by the freezing status + * `_amount` is the amount of tokens that are frozen + */ + event TokensFrozen(address indexed _userAddress, uint256 _amount); + + /** + * this event is emitted when a certain amount of tokens is unfrozen on a wallet + * the event is emitted by unfreezePartialTokens and batchUnfreezePartialTokens functions + * `_userAddress` is the wallet of the investor that is concerned by the freezing status + * `_amount` is the amount of tokens that are unfrozen + */ + event TokensUnfrozen(address indexed _userAddress, uint256 _amount); + + /** + * this event is emitted when the token is paused + * the event is emitted by the pause function + * `_userAddress` is the address of the wallet that called the pause function + */ + event Paused(address _userAddress); + + /** + * this event is emitted when the token is unpaused + * the event is emitted by the unpause function + * `_userAddress` is the address of the wallet that called the unpause function + */ + event Unpaused(address _userAddress); + + /// functions + + /** + * @dev sets the token name + * @param _name the name of token to set + * Only the owner of the token smart contract can call this function + * emits a `UpdatedTokenInformation` event + */ + function setName(string calldata _name) external; + + /** + * @dev sets the token symbol + * @param _symbol the token symbol to set + * Only the owner of the token smart contract can call this function + * emits a `UpdatedTokenInformation` event + */ + function setSymbol(string calldata _symbol) external; + + /** + * @dev sets the onchain ID of the token + * @param _onchainID the address of the onchain ID to set + * Only the owner of the token smart contract can call this function + * emits a `UpdatedTokenInformation` event + */ + function setOnchainID(address _onchainID) external; + + /** + * @dev pauses the token contract, when contract is paused investors cannot transfer tokens anymore + * This function can only be called by a wallet set as agent of the token + * emits a `Paused` event + */ + function pause() external; + + /** + * @dev unpauses the token contract, when contract is unpaused investors can transfer tokens + * if their wallet is not blocked & if the amount to transfer is <= to the amount of free tokens + * This function can only be called by a wallet set as agent of the token + * emits an `Unpaused` event + */ + function unpause() external; + + /** + * @dev sets an address frozen status for this token. + * @param _userAddress The address for which to update frozen status + * @param _freeze Frozen status of the address + * This function can only be called by a wallet set as agent of the token + * emits an `AddressFrozen` event + */ + function setAddressFrozen(address _userAddress, bool _freeze) external; + + /** + * @dev freezes token amount specified for given address. + * @param _userAddress The address for which to update frozen tokens + * @param _amount Amount of Tokens to be frozen + * This function can only be called by a wallet set as agent of the token + * emits a `TokensFrozen` event + */ + function freezePartialTokens(address _userAddress, uint256 _amount) external; + + /** + * @dev unfreezes token amount specified for given address + * @param _userAddress The address for which to update frozen tokens + * @param _amount Amount of Tokens to be unfrozen + * This function can only be called by a wallet set as agent of the token + * emits a `TokensUnfrozen` event + */ + function unfreezePartialTokens(address _userAddress, uint256 _amount) external; + + /** + * @dev sets the Identity Registry for the token + * @param _identityRegistry the address of the Identity Registry to set + * Only the owner of the token smart contract can call this function + * emits an `IdentityRegistryAdded` event + */ + function setIdentityRegistry(address _identityRegistry) external; + + /** + * @dev sets the compliance contract of the token + * @param _compliance the address of the compliance contract to set + * Only the owner of the token smart contract can call this function + * calls bindToken on the compliance contract + * emits a `ComplianceAdded` event + */ + function setCompliance(address _compliance) external; + + /** + * @dev force a transfer of tokens between 2 whitelisted wallets + * In case the `from` address has not enough free tokens (unfrozen tokens) + * but has a total balance higher or equal to the `amount` + * the amount of frozen tokens is reduced in order to have enough free tokens + * to proceed the transfer, in such a case, the remaining balance on the `from` + * account is 100% composed of frozen tokens post-transfer. + * Require that the `to` address is a verified address, + * @param _from The address of the sender + * @param _to The address of the receiver + * @param _amount The number of tokens to transfer + * @return `true` if successful and revert if unsuccessful + * This function can only be called by a wallet set as agent of the token + * emits a `TokensUnfrozen` event if `_amount` is higher than the free balance of `_from` + * emits a `Transfer` event + */ + function forcedTransfer( + address _from, + address _to, + uint256 _amount + ) external returns (bool); + + /** + * @dev mint tokens on a wallet + * Improved version of default mint method. Tokens can be minted + * to an address if only it is a verified address as per the security token. + * @param _to Address to mint the tokens to. + * @param _amount Amount of tokens to mint. + * This function can only be called by a wallet set as agent of the token + * emits a `Transfer` event + */ + function mint(address _to, uint256 _amount) external; + + /** + * @dev burn tokens on a wallet + * In case the `account` address has not enough free tokens (unfrozen tokens) + * but has a total balance higher or equal to the `value` amount + * the amount of frozen tokens is reduced in order to have enough free tokens + * to proceed the burn, in such a case, the remaining balance on the `account` + * is 100% composed of frozen tokens post-transaction. + * @param _userAddress Address to burn the tokens from. + * @param _amount Amount of tokens to burn. + * This function can only be called by a wallet set as agent of the token + * emits a `TokensUnfrozen` event if `_amount` is higher than the free balance of `_userAddress` + * emits a `Transfer` event + */ + function burn(address _userAddress, uint256 _amount) external; + + /** + * @dev recovery function used to force transfer tokens from a + * lost wallet to a new wallet for an investor. + * @param _lostWallet the wallet that the investor lost + * @param _newWallet the newly provided wallet on which tokens have to be transferred + * @param _investorOnchainID the onchainID of the investor asking for a recovery + * This function can only be called by a wallet set as agent of the token + * emits a `TokensUnfrozen` event if there is some frozen tokens on the lost wallet if the recovery process is successful + * emits a `Transfer` event if the recovery process is successful + * emits a `RecoverySuccess` event if the recovery process is successful + * emits a `RecoveryFails` event if the recovery process fails + */ + function recoveryAddress( + address _lostWallet, + address _newWallet, + address _investorOnchainID + ) external returns (bool); + + /** + * @dev function allowing to issue transfers in batch + * Require that the msg.sender and `to` addresses are not frozen. + * Require that the total value should not exceed available balance. + * Require that the `to` addresses are all verified addresses, + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_toList.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _toList The addresses of the receivers + * @param _amounts The number of tokens to transfer to the corresponding receiver + * emits _toList.length `Transfer` events + */ + function batchTransfer(address[] calldata _toList, uint256[] calldata _amounts) external; + + /** + * @dev function allowing to issue forced transfers in batch + * Require that `_amounts[i]` should not exceed available balance of `_fromList[i]`. + * Require that the `_toList` addresses are all verified addresses + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_fromList.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _fromList The addresses of the senders + * @param _toList The addresses of the receivers + * @param _amounts The number of tokens to transfer to the corresponding receiver + * This function can only be called by a wallet set as agent of the token + * emits `TokensUnfrozen` events if `_amounts[i]` is higher than the free balance of `_fromList[i]` + * emits _fromList.length `Transfer` events + */ + function batchForcedTransfer( + address[] calldata _fromList, + address[] calldata _toList, + uint256[] calldata _amounts + ) external; + + /** + * @dev function allowing to mint tokens in batch + * Require that the `_toList` addresses are all verified addresses + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_toList.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _toList The addresses of the receivers + * @param _amounts The number of tokens to mint to the corresponding receiver + * This function can only be called by a wallet set as agent of the token + * emits _toList.length `Transfer` events + */ + function batchMint(address[] calldata _toList, uint256[] calldata _amounts) external; + + /** + * @dev function allowing to burn tokens in batch + * Require that the `_userAddresses` addresses are all verified addresses + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _userAddresses The addresses of the wallets concerned by the burn + * @param _amounts The number of tokens to burn from the corresponding wallets + * This function can only be called by a wallet set as agent of the token + * emits _userAddresses.length `Transfer` events + */ + function batchBurn(address[] calldata _userAddresses, uint256[] calldata _amounts) external; + + /** + * @dev function allowing to set frozen addresses in batch + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _userAddresses The addresses for which to update frozen status + * @param _freeze Frozen status of the corresponding address + * This function can only be called by a wallet set as agent of the token + * emits _userAddresses.length `AddressFrozen` events + */ + function batchSetAddressFrozen(address[] calldata _userAddresses, bool[] calldata _freeze) external; + + /** + * @dev function allowing to freeze tokens partially in batch + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _userAddresses The addresses on which tokens need to be frozen + * @param _amounts the amount of tokens to freeze on the corresponding address + * This function can only be called by a wallet set as agent of the token + * emits _userAddresses.length `TokensFrozen` events + */ + function batchFreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; + + /** + * @dev function allowing to unfreeze tokens partially in batch + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _userAddresses The addresses on which tokens need to be unfrozen + * @param _amounts the amount of tokens to unfreeze on the corresponding address + * This function can only be called by a wallet set as agent of the token + * emits _userAddresses.length `TokensUnfrozen` events + */ + function batchUnfreezePartialTokens(address[] calldata _userAddresses, uint256[] calldata _amounts) external; + + /** + * @dev Returns the address of the onchainID of the token. + * the onchainID of the token gives all the information available + * about the token and is managed by the token issuer or his agent. + */ + function onchainID() external view returns (address); + + /** + * @dev Returns the TREX version of the token. + * current version is 3.0.0 + */ + function version() external view returns (string memory); + + /** + * @dev Returns the Identity Registry linked to the token + */ + function identityRegistry() external view returns (IIdentityRegistry); + + /** + * @dev Returns the Compliance contract linked to the token + */ + function compliance() external view returns (IModularCompliance); + + /** + * @dev Returns true if the contract is paused, and false otherwise. + */ + function paused() external view returns (bool); + + /** + * @dev Returns the freezing status of a wallet + * if isFrozen returns `true` the wallet is frozen + * if isFrozen returns `false` the wallet is not frozen + * isFrozen returning `true` doesn't mean that the balance is free, tokens could be blocked by + * a partial freeze or the whole token could be blocked by pause + * @param _userAddress the address of the wallet on which isFrozen is called + */ + function isFrozen(address _userAddress) external view returns (bool); + + /** + * @dev Returns the amount of tokens that are partially frozen on a wallet + * the amount of frozen tokens is always <= to the total balance of the wallet + * @param _userAddress the address of the wallet on which getFrozenTokens is called + */ + function getFrozenTokens(address _userAddress) external view returns (uint256); +} diff --git a/assets/eip-3643/interfaces/IIdentityRegistry.sol b/assets/eip-3643/interfaces/IIdentityRegistry.sol new file mode 100644 index 0000000000000..4bc4205584759 --- /dev/null +++ b/assets/eip-3643/interfaces/IIdentityRegistry.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "./ITrustedIssuersRegistry.sol"; +import "./IClaimTopicsRegistry.sol"; +import "./IIdentityRegistryStorage.sol"; + +import "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol"; +import "@onchain-id/solidity/contracts/interface/IIdentity.sol"; + +interface IIdentityRegistry { + /** + * this event is emitted when the ClaimTopicsRegistry has been set for the IdentityRegistry + * the event is emitted by the IdentityRegistry constructor + * `claimTopicsRegistry` is the address of the Claim Topics Registry contract + */ + event ClaimTopicsRegistrySet(address indexed claimTopicsRegistry); + + /** + * this event is emitted when the IdentityRegistryStorage has been set for the IdentityRegistry + * the event is emitted by the IdentityRegistry constructor + * `identityStorage` is the address of the Identity Registry Storage contract + */ + event IdentityStorageSet(address indexed identityStorage); + + /** + * this event is emitted when the TrustedIssuersRegistry has been set for the IdentityRegistry + * the event is emitted by the IdentityRegistry constructor + * `trustedIssuersRegistry` is the address of the Trusted Issuers Registry contract + */ + event TrustedIssuersRegistrySet(address indexed trustedIssuersRegistry); + + /** + * this event is emitted when an Identity is registered into the Identity Registry. + * the event is emitted by the 'registerIdentity' function + * `investorAddress` is the address of the investor's wallet + * `identity` is the address of the Identity smart contract (onchainID) + */ + event IdentityRegistered(address indexed investorAddress, IIdentity indexed identity); + + /** + * this event is emitted when an Identity is removed from the Identity Registry. + * the event is emitted by the 'deleteIdentity' function + * `investorAddress` is the address of the investor's wallet + * `identity` is the address of the Identity smart contract (onchainID) + */ + event IdentityRemoved(address indexed investorAddress, IIdentity indexed identity); + + /** + * this event is emitted when an Identity has been updated + * the event is emitted by the 'updateIdentity' function + * `oldIdentity` is the old Identity contract's address to update + * `newIdentity` is the new Identity contract's + */ + event IdentityUpdated(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); + + /** + * this event is emitted when an Identity's country has been updated + * the event is emitted by the 'updateCountry' function + * `investorAddress` is the address on which the country has been updated + * `country` is the numeric code (ISO 3166-1) of the new country + */ + event CountryUpdated(address indexed investorAddress, uint16 indexed country); + + /** + * @dev Register an identity contract corresponding to a user address. + * Requires that the user doesn't have an identity contract already registered. + * This function can only be called by a wallet set as agent of the smart contract + * @param _userAddress The address of the user + * @param _identity The address of the user's identity contract + * @param _country The country of the investor + * emits `IdentityRegistered` event + */ + function registerIdentity( + address _userAddress, + IIdentity _identity, + uint16 _country + ) external; + + /** + * @dev Removes an user from the identity registry. + * Requires that the user have an identity contract already deployed that will be deleted. + * This function can only be called by a wallet set as agent of the smart contract + * @param _userAddress The address of the user to be removed + * emits `IdentityRemoved` event + */ + function deleteIdentity(address _userAddress) external; + + /** + * @dev Replace the actual identityRegistryStorage contract with a new one. + * This function can only be called by the wallet set as owner of the smart contract + * @param _identityRegistryStorage The address of the new Identity Registry Storage + * emits `IdentityStorageSet` event + */ + function setIdentityRegistryStorage(address _identityRegistryStorage) external; + + /** + * @dev Replace the actual claimTopicsRegistry contract with a new one. + * This function can only be called by the wallet set as owner of the smart contract + * @param _claimTopicsRegistry The address of the new claim Topics Registry + * emits `ClaimTopicsRegistrySet` event + */ + function setClaimTopicsRegistry(address _claimTopicsRegistry) external; + + /** + * @dev Replace the actual trustedIssuersRegistry contract with a new one. + * This function can only be called by the wallet set as owner of the smart contract + * @param _trustedIssuersRegistry The address of the new Trusted Issuers Registry + * emits `TrustedIssuersRegistrySet` event + */ + function setTrustedIssuersRegistry(address _trustedIssuersRegistry) external; + + /** + * @dev Updates the country corresponding to a user address. + * Requires that the user should have an identity contract already deployed that will be replaced. + * This function can only be called by a wallet set as agent of the smart contract + * @param _userAddress The address of the user + * @param _country The new country of the user + * emits `CountryUpdated` event + */ + function updateCountry(address _userAddress, uint16 _country) external; + + /** + * @dev Updates an identity contract corresponding to a user address. + * Requires that the user address should be the owner of the identity contract. + * Requires that the user should have an identity contract already deployed that will be replaced. + * This function can only be called by a wallet set as agent of the smart contract + * @param _userAddress The address of the user + * @param _identity The address of the user's new identity contract + * emits `IdentityUpdated` event + */ + function updateIdentity(address _userAddress, IIdentity _identity) external; + + /** + * @dev function allowing to register identities in batch + * This function can only be called by a wallet set as agent of the smart contract + * Requires that none of the users has an identity contract already registered. + * IMPORTANT : THIS TRANSACTION COULD EXCEED GAS LIMIT IF `_userAddresses.length` IS TOO HIGH, + * USE WITH CARE OR YOU COULD LOSE TX FEES WITH AN "OUT OF GAS" TRANSACTION + * @param _userAddresses The addresses of the users + * @param _identities The addresses of the corresponding identity contracts + * @param _countries The countries of the corresponding investors + * emits _userAddresses.length `IdentityRegistered` events + */ + function batchRegisterIdentity( + address[] calldata _userAddresses, + IIdentity[] calldata _identities, + uint16[] calldata _countries + ) external; + + /** + * @dev This functions checks whether a wallet has its Identity registered or not + * in the Identity Registry. + * @param _userAddress The address of the user to be checked. + * @return 'True' if the address is contained in the Identity Registry, 'false' if not. + */ + function contains(address _userAddress) external view returns (bool); + + /** + * @dev This functions checks whether an identity contract + * corresponding to the provided user address has the required claims or not based + * on the data fetched from trusted issuers registry and from the claim topics registry + * @param _userAddress The address of the user to be verified. + * @return 'True' if the address is verified, 'false' if not. + */ + function isVerified(address _userAddress) external view returns (bool); + + /** + * @dev Returns the onchainID of an investor. + * @param _userAddress The wallet of the investor + */ + function identity(address _userAddress) external view returns (IIdentity); + + /** + * @dev Returns the country code of an investor. + * @param _userAddress The wallet of the investor + */ + function investorCountry(address _userAddress) external view returns (uint16); + + /** + * @dev Returns the IdentityRegistryStorage linked to the current IdentityRegistry. + */ + function identityStorage() external view returns (IIdentityRegistryStorage); + + /** + * @dev Returns the TrustedIssuersRegistry linked to the current IdentityRegistry. + */ + function issuersRegistry() external view returns (ITrustedIssuersRegistry); + + /** + * @dev Returns the ClaimTopicsRegistry linked to the current IdentityRegistry. + */ + function topicsRegistry() external view returns (IClaimTopicsRegistry); +} diff --git a/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol b/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol new file mode 100644 index 0000000000000..ed04b94103c38 --- /dev/null +++ b/assets/eip-3643/interfaces/IIdentityRegistryStorage.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "@onchain-id/solidity/contracts/interface/IIdentity.sol"; + +interface IIdentityRegistryStorage { + + /// events + + /** + * this event is emitted when an Identity is registered into the storage contract. + * the event is emitted by the 'registerIdentity' function + * `investorAddress` is the address of the investor's wallet + * `identity` is the address of the Identity smart contract (onchainID) + */ + event IdentityStored(address indexed investorAddress, IIdentity indexed identity); + + /** + * this event is emitted when an Identity is removed from the storage contract. + * the event is emitted by the 'deleteIdentity' function + * `investorAddress` is the address of the investor's wallet + * `identity` is the address of the Identity smart contract (onchainID) + */ + event IdentityUnstored(address indexed investorAddress, IIdentity indexed identity); + + /** + * this event is emitted when an Identity has been updated + * the event is emitted by the 'updateIdentity' function + * `oldIdentity` is the old Identity contract's address to update + * `newIdentity` is the new Identity contract's + */ + event IdentityModified(IIdentity indexed oldIdentity, IIdentity indexed newIdentity); + + /** + * this event is emitted when an Identity's country has been updated + * the event is emitted by the 'updateCountry' function + * `investorAddress` is the address on which the country has been updated + * `country` is the numeric code (ISO 3166-1) of the new country + */ + event CountryModified(address indexed investorAddress, uint16 indexed country); + + /** + * this event is emitted when an Identity Registry is bound to the storage contract + * the event is emitted by the 'addIdentityRegistry' function + * `identityRegistry` is the address of the identity registry added + */ + event IdentityRegistryBound(address indexed identityRegistry); + + /** + * this event is emitted when an Identity Registry is unbound from the storage contract + * the event is emitted by the 'removeIdentityRegistry' function + * `identityRegistry` is the address of the identity registry removed + */ + event IdentityRegistryUnbound(address indexed identityRegistry); + + /// functions + + /** + * @dev adds an identity contract corresponding to a user address in the storage. + * Requires that the user doesn't have an identity contract already registered. + * This function can only be called by an address set as agent of the smart contract + * @param _userAddress The address of the user + * @param _identity The address of the user's identity contract + * @param _country The country of the investor + * emits `IdentityStored` event + */ + function addIdentityToStorage( + address _userAddress, + IIdentity _identity, + uint16 _country + ) external; + + /** + * @dev Removes an user from the storage. + * Requires that the user have an identity contract already deployed that will be deleted. + * This function can only be called by an address set as agent of the smart contract + * @param _userAddress The address of the user to be removed + * emits `IdentityUnstored` event + */ + function removeIdentityFromStorage(address _userAddress) external; + + /** + * @dev Updates the country corresponding to a user address. + * Requires that the user should have an identity contract already deployed that will be replaced. + * This function can only be called by an address set as agent of the smart contract + * @param _userAddress The address of the user + * @param _country The new country of the user + * emits `CountryModified` event + */ + function modifyStoredInvestorCountry(address _userAddress, uint16 _country) external; + + /** + * @dev Updates an identity contract corresponding to a user address. + * Requires that the user address should be the owner of the identity contract. + * Requires that the user should have an identity contract already deployed that will be replaced. + * This function can only be called by an address set as agent of the smart contract + * @param _userAddress The address of the user + * @param _identity The address of the user's new identity contract + * emits `IdentityModified` event + */ + function modifyStoredIdentity(address _userAddress, IIdentity _identity) external; + + /** + * @notice Adds an identity registry as agent of the Identity Registry Storage Contract. + * This function can only be called by the wallet set as owner of the smart contract + * This function adds the identity registry to the list of identityRegistries linked to the storage contract + * cannot bind more than 300 IR to 1 IRS + * @param _identityRegistry The identity registry address to add. + */ + function bindIdentityRegistry(address _identityRegistry) external; + + /** + * @notice Removes an identity registry from being agent of the Identity Registry Storage Contract. + * This function can only be called by the wallet set as owner of the smart contract + * This function removes the identity registry from the list of identityRegistries linked to the storage contract + * @param _identityRegistry The identity registry address to remove. + */ + function unbindIdentityRegistry(address _identityRegistry) external; + + /** + * @dev Returns the identity registries linked to the storage contract + */ + function linkedIdentityRegistries() external view returns (address[] memory); + + /** + * @dev Returns the onchainID of an investor. + * @param _userAddress The wallet of the investor + */ + function storedIdentity(address _userAddress) external view returns (IIdentity); + + /** + * @dev Returns the country code of an investor. + * @param _userAddress The wallet of the investor + */ + function storedInvestorCountry(address _userAddress) external view returns (uint16); +} diff --git a/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol b/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol new file mode 100644 index 0000000000000..786b6a203535a --- /dev/null +++ b/assets/eip-3643/interfaces/ITrustedIssuersRegistry.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity 0.8.17; + +import "@onchain-id/solidity/contracts/interface/IClaimIssuer.sol"; + +interface ITrustedIssuersRegistry { + /** + * this event is emitted when a trusted issuer is added in the registry. + * the event is emitted by the addTrustedIssuer function + * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract + * `claimTopics` is the set of claims that the trusted issuer is allowed to emit + */ + event TrustedIssuerAdded(IClaimIssuer indexed trustedIssuer, uint256[] claimTopics); + + /** + * this event is emitted when a trusted issuer is removed from the registry. + * the event is emitted by the removeTrustedIssuer function + * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract + */ + event TrustedIssuerRemoved(IClaimIssuer indexed trustedIssuer); + + /** + * this event is emitted when the set of claim topics is changed for a given trusted issuer. + * the event is emitted by the updateIssuerClaimTopics function + * `trustedIssuer` is the address of the trusted issuer's ClaimIssuer contract + * `claimTopics` is the set of claims that the trusted issuer is allowed to emit + */ + event ClaimTopicsUpdated(IClaimIssuer indexed trustedIssuer, uint256[] claimTopics); + + /** + * @dev registers a ClaimIssuer contract as trusted claim issuer. + * Requires that a ClaimIssuer contract doesn't already exist + * Requires that the claimTopics set is not empty + * Requires that there is no more than 15 claimTopics + * Requires that there is no more than 50 Trusted issuers + * @param _trustedIssuer The ClaimIssuer contract address of the trusted claim issuer. + * @param _claimTopics the set of claim topics that the trusted issuer is allowed to emit + * This function can only be called by the owner of the Trusted Issuers Registry contract + * emits a `TrustedIssuerAdded` event + */ + function addTrustedIssuer(IClaimIssuer _trustedIssuer, uint256[] calldata _claimTopics) external; + + /** + * @dev Removes the ClaimIssuer contract of a trusted claim issuer. + * Requires that the claim issuer contract to be registered first + * @param _trustedIssuer the claim issuer to remove. + * This function can only be called by the owner of the Trusted Issuers Registry contract + * emits a `TrustedIssuerRemoved` event + */ + function removeTrustedIssuer(IClaimIssuer _trustedIssuer) external; + + /** + * @dev Updates the set of claim topics that a trusted issuer is allowed to emit. + * Requires that this ClaimIssuer contract already exists in the registry + * Requires that the provided claimTopics set is not empty + * Requires that there is no more than 15 claimTopics + * @param _trustedIssuer the claim issuer to update. + * @param _claimTopics the set of claim topics that the trusted issuer is allowed to emit + * This function can only be called by the owner of the Trusted Issuers Registry contract + * emits a `ClaimTopicsUpdated` event + */ + function updateIssuerClaimTopics(IClaimIssuer _trustedIssuer, uint256[] calldata _claimTopics) external; + + /** + * @dev Function for getting all the trusted claim issuers stored. + * @return array of all claim issuers registered. + */ + function getTrustedIssuers() external view returns (IClaimIssuer[] memory); + + /** + * @dev Function for getting all the trusted issuer allowed for a given claim topic. + * @param claimTopic the claim topic to get the trusted issuers for. + * @return array of all claim issuer addresses that are allowed for the given claim topic. + */ + function getTrustedIssuersForClaimTopic(uint256 claimTopic) external view returns (IClaimIssuer[] memory); + + /** + * @dev Checks if the ClaimIssuer contract is trusted + * @param _issuer the address of the ClaimIssuer contract + * @return true if the issuer is trusted, false otherwise. + */ + function isTrustedIssuer(address _issuer) external view returns (bool); + + /** + * @dev Function for getting all the claim topic of trusted claim issuer + * Requires the provided ClaimIssuer contract to be registered in the trusted issuers registry. + * @param _trustedIssuer the trusted issuer concerned. + * @return The set of claim topics that the trusted issuer is allowed to emit + */ + function getTrustedIssuerClaimTopics(IClaimIssuer _trustedIssuer) external view returns (uint256[] memory); + + /** + * @dev Function for checking if the trusted claim issuer is allowed + * to emit a certain claim topic + * @param _issuer the address of the trusted issuer's ClaimIssuer contract + * @param _claimTopic the Claim Topic that has to be checked to know if the `issuer` is allowed to emit it + * @return true if the issuer is trusted for this claim topic. + */ + function hasClaimTopic(address _issuer, uint256 _claimTopic) external view returns (bool); +} From 94d61e581e53763824b9c8ce6a8c988628149210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 3 Jul 2023 11:53:25 -0600 Subject: [PATCH 15/41] Update EIP-6093: Replace ERC1155 approval error Merged by EIP-Bot. --- EIPS/eip-6093.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-6093.md b/EIPS/eip-6093.md index e82cc65cfed59..4f7b047715db7 100644 --- a/EIPS/eip-6093.md +++ b/EIPS/eip-6093.md @@ -208,7 +208,7 @@ Usage guidelines: - RECOMMENDED for disallowed transfers from the zero address. - MUST NOT be used for approval operations. - MUST NOT be used for balance or allowance requirements. - - Use `ERC1155InsufficientBalance` or `ERC1155InsufficientApprovalForAll` instead. + - Use `ERC1155InsufficientBalance` or `ERC1155MissingApprovalForAll` instead. #### `ERC1155InvalidReceiver(address receiver)` @@ -221,7 +221,7 @@ Usage guidelines: - RECOMMENDED for disallowed transfers to non-`ERC1155TokenReceiver` contracts or those that reject a transfer. (eg. returning an invalid response in `onERC1155Received`). - MUST NOT be used for approval operations. -#### `ERC1155InsufficientApprovalForAll(address operator, address owner)` +#### `ERC1155MissingApprovalForAll(address operator, address owner)` Indicates a failure with the `operator`'s approval in a transfer. Used in transfers. @@ -409,7 +409,7 @@ interface ERC1155Errors { error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId); error ERC1155InvalidSender(address sender); error ERC1155InvalidReceiver(address receiver); - error ERC1155InsufficientApprovalForAll(address operator, address owner) + error ERC1155MissingApprovalForAll(address operator, address owner) error ERC1155InvalidApprover(address approver); error ERC1155InvalidOperator(address operator); error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength); From 3907772a0f11d0095ee5c2e364f2774add95cfc7 Mon Sep 17 00:00:00 2001 From: adam-alchemy <127769144+adam-alchemy@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:46:55 -0700 Subject: [PATCH 16/41] Update EIP-6900: Spec update 2 Merged by EIP-Bot. --- EIPS/eip-6900.md | 100 +++++++----------- .../eip-6900/MSCA_Two_Call_Paths_Diagram.svg | 5 +- 2 files changed, 44 insertions(+), 61 deletions(-) diff --git a/EIPS/eip-6900.md b/EIPS/eip-6900.md index 37dd66eef8fe0..eaa42f56e403d 100644 --- a/EIPS/eip-6900.md +++ b/EIPS/eip-6900.md @@ -52,7 +52,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - **Execution functions** execute any custom logic allowed by the account. - **Hooks** execute custom logic and checks before and/or after an execution function. - A **validation function** is a function that validates authentication and authorization of a caller to the account. There are two types of validation functions: - - **User Operation Validator** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. The function may have any function name, and MUST take in the parameters `(UserOperation calldata, bytes32)`, representing the user operation and user operation hash. It MUST return `(uint256)`, representing packed validation data for `authorizer`, `validUntil`, and `validAfter`. + - **User Operation Validator** functions handle calls to `validateUserOp` and check the validity of an ERC-4337 user operation. - **Runtime Validator** functions run before an execution function when not called via a user operation, and enforce checks. Common checks include allowing execution only by an owner. - An **execution function** is a smart contract function that defines the main execution step of a function for a **modular account**. - The **standard execute functions** are two specific execution functions that are implemented natively by the modular account, and not on a plugin. These allow for open-ended execution. @@ -64,6 +64,16 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S - **Associated function** refers to either a validation function or a hook. - A **plugin** is a deployed smart contract that hosts any amount of the above three kinds of modular functions: execution functions, validation functions, or hooks. +### Plugin Function Signatures + +The plugin function types specified above MUST have the following function signatures: + +- **User Operation Validator** functions and **Pre User Operation Validation Hooks**: `function(UserOperation calldata, bytes32) external returns (uint256)`. The parameters sent by the account MUST be the user operation and user operation hash. The return value MUST represent packed validation data for `authorizer`, `validUntil`, and `validAfter`. The packing order MUST be `validAfter` in the first 6 bytes, `validUntil` in the next 6 bytes, and `authorizer` in the last 20 bytes. **Pre User Operation Validation Hooks** MUST NOT return an `authorizer` value other than 0 or 1. +- **Runtime Validator** functions and **Pre Runtime Validation Hooks**: `function(address, uint256, bytes calldata) external`. The parameters sent by the account MUST be the caller address, the call value, and the calldata sent. To indicate that the entire call should revert, the function MUST revert. +- **Pre Execution Hooks**: `function(address, uint256, bytes calldata) external returns (bytes memory)`. The parameters sent by the account MUST be the caller address, the call value, and the calldata sent. To indicate that the entire call should revert, the function MUST revert. The return value MUST contain any context to pass to a **Post Execution Hook**, if present. An empty bytes array MAY be returned. +- **Post Execution Hooks**: `function(bytes calldata) external`. The parameter sent by the account MUST be the context returned by the associated **Pre Execution Hook**. To indicate that the entire call should revert, the function MUST revert. +- **Execution** functions may have any function signature. + ### Overview A modular account handles two kinds of calls: either from the `Entrypoint` through ERC-4337, or through direct calls from externally owned accounts (EOAs) and other smart contracts. This standard supports both use cases. @@ -80,12 +90,18 @@ Modular Smart Contract Accounts MUST implement the `IAccount` interface from [ER #### Common types -The following types are used by both of the following interfaces. Implementors MAY use the elementary value type instead of the type alias. +The following types are common across the following interfaces. Implementors MAY use the elementary value type instead of the type alias. ```solidity type FunctionReference is bytes24; type HookGroupId is uint32; + +struct Execution { + address target; + uint256 value; + bytes data; +} ``` Variables of the type `FunctionReference` MUST be interpreted as an address in the first 20 bytes and a function selector in the remaining 4 bytes. @@ -146,8 +162,7 @@ interface IPluginUpdate { ExecutionUpdate[] calldata executionUpdates, HookUpdate[] calldata hookUpdates, HookGroupUpdate[] calldata hookGroupUpdates, - address init, - bytes calldata callData + Execution[] calldata initializationCalls ) external; } ``` @@ -192,20 +207,7 @@ Standard execution interface. Modular Smart Contract Accounts MUST implement thi ```solidity interface IStandardExecutor { - enum ExecutionMode { - CALL, - DELEGATECALL, - STATICCALL - } - - struct Execution { - address target; - uint256 value; - bytes data; - ExecutionMode mode; - } - - function execute(address dest, uint256 value, bytes calldata func, ExecutionMode mode, FunctionReference validator) + function execute(address target, uint256 value, bytes calldata data, FunctionReference validator) external payable; @@ -217,26 +219,32 @@ interface IStandardExecutor { #### Calls to `updatePlugins` -The function `updatePlugins` takes in arrays of execution updates, hook updates, and hook group updates to perform. It also takes in an optional initialization function. The function MUST perform the update operation sequentially, then, if the address provided in `init` is not `address(0)`, MUST execute `init` with the calldata `callData` through a `delegatecall`. +The function `updatePlugins` takes in arrays of execution updates, hook updates, and hook group updates to perform. It also takes in an optional array of initialization calls. The function MUST perform the update operation sequentially. Then, for each sequential member of the `initializationCalls` array, the MSCA MUST perform a `call` operation towards the specified `target` address with the specified `value` and `callData`. > **⚠️ The ability to update a plugin is very powerful. The security of the updatePlugins determines the security of the account. It is critical for modular account implementers to make sure updatePlugins has the proper security consideration and access control in place.** #### Calls to `validateUserOp` -When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validator defined for the selector in `userOp.callData`, which is in the first four bytes. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. Otherwise, the MSCA MUST execute the validator function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validator MUST be returned by `validateUserOp`. +When the function `validateUserOp` is called on modular account by the `EntryPoint`, it MUST find the user operation validator defined for the selector in `userOp.callData`, which is in the first four bytes. If there is no function defined for the selector, or if `userOp.callData.length < 4`, then execution MUST revert. + +If the execution selector has associated hook groups with pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert, the outer call MUST revert. If any return an `authorizer` value other than 0 or 1, execution MUST revert. If any return an `authorizer` value of 1, indicating an invalid signature, the returned validation data of the outer call must also be 1. If any return time-bounded validation by specifying either a `validUntil` or `validBefore` value, the resulting validation data MUST be the intersection of all time bounds provided. -If the execution selector has associated hook groups with pre user operation validation hooks, then those hooks MUST be run sequentially. If any revert or return false, the outer call MUST revert. +If the call is to a standard execution function, then the modular account MUST verify that the provided `validator` in calldata has previously been associated with either of the standard execution functions. If it was previously added, the specified user operation validator MUST be run. -If the call is to a standard execution function, then the modular account must verify that the provided `validator` in calldata has previously been associated with either of the standard execution functions. If it was previously added, the specified user operation validator MUST be run. +Then, the MSCA MUST execute the validator function with the user operation and its hash as parameters using the `call` opcode. The returned validation data from the user operation validator MUST be updated, if necessary, by the return values of any pre user operation validation hooks, then returned by `validateUserOp`. #### Calls to execution functions -When a function other than a natively defined function is called on an MSCA, it MUST find the plugin configuration for the corresponding selector added via `updatePlugins`. If no corresponding plugin is found, the MSCA MUST revert. Otherwise, the MSCA MUST perform the following steps: +When a function other than a natively defined function is called on an MSCA, it MUST find the plugin configuration for the corresponding selector added via `updatePlugins`. If no corresponding plugin is found, the MSCA MUST revert. Otherwise, the following steps MUST be performed. -- If the call is not from the `EntryPoint`, then find an associated `runtimeValidator` function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validator function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function’s calldata as parameters. If any of these functions revert, or return a boolean false, execution MUST revert. -- If there are pre execution hooks defined in the associated hook groups of the execution function, execute those hooks with the caller, value, and execution function’s calldata as parameters. If any of thee hooks returns data, it MUST be preserved until the call to the post execution hook. The operation must be done with the `call` opcode. +Alternatively, when the modular account natively implements functions in `IPluginUpdate` and `IStandardExecutor`, the same following steps MUST be performed for those functions. Other natively implemented functions MAY perform these steps. + +The steps to perform are: + +- If the call is not from the `EntryPoint`, then find an associated runtime validator function. If one does not exist, execution MUST revert. The modular account MUST execute all pre runtime validation hooks, then the runtime validator function, with the `call` opcode. All of these functions MUST receive the caller, value, and execution function’s calldata as parameters. If any of these functions revert, execution MUST revert. +- If there are pre execution hooks defined in the associated hook groups of the execution function, execute those hooks with the caller, value, and execution function’s calldata as parameters. If any of thee hooks returns data, it MUST be preserved until the call to the post execution hook. The operation MUST be done with the `call` opcode. If any of these functions revert, execution MUST revert. - Run the execution function. -- If any associated post execution hooks are defined, run the functions. If a pre execution hook in the same hook group returned data to the account, that data MUST be passed as a parameter to the post execution hook. The operation must be done with the `call` opcode. +- If any associated post execution hooks are defined, run the functions. If a pre execution hook in the same hook group returned data to the account, that data MUST be passed as a parameter to the post execution hook. The operation MUST be done with the `call` opcode. If any of these functions revert, execution MUST revert. > **⚠️ If the execution function does not have a definition for either pre runtime validation hooks, pre execution hooks or post execution hooks, the undefined functions will be skipped. The execution function will be run and it may change account state.** @@ -244,10 +252,10 @@ When a function other than a natively defined function is called on an MSCA, it When `updatePlugins` is called with `PluginAction.ADD`, the following MUST occur: -- Each execution selector must be added as a valid execution function, with the contract specified in `pluginAddress` as implementation contract to call to. -- Each validator function reference must be added to their parent execution function in the role specified by `validatorType`. -- Each hook update must add the hook type specified by `hookType` to the group specified by `hookGroupId`. If that hook type is already defined, the update MUST revert. -- Each hook group update must add that hook group to the execution selectors specified by `executionSelectors`. +- Each execution selector MUST be added as a valid execution function, with the contract specified in `pluginAddress` as implementation contract to call to. +- Each validator function reference MUST be added to their parent execution function in the role specified by `validatorType`. +- Each hook update MUST add the hook type specified by `hookType` to the group specified by `hookGroupId`. If that hook type is already defined, the update MUST revert. +- Each hook group update MUST add that hook group to the execution selectors specified by `executionSelectors`. Execution function selectors MUST be unique when added. @@ -255,41 +263,15 @@ When `updatePlugins` is called with `PluginAction.REPLACE`, each each execution When `updatePlugins` is called with `PluginAction.REMOVE`, execution function definitions and associated function definitions MUST be removed. -#### Plugin Functions - -Execution functions may have any function signature, but must be unique for the account. When added to an MSCA via `updatePlugins`, the function selector of the execution function will be mapped from the modular account to the plugin. - -User Operation Validation functions may have any function name, and MUST take in the parameters `(UserOperation calldata, bytes32)`, representing the user operation and the user operation hash. The functions MUST return `(uint256)`, representing packed validation data for `authorizer`, `validUntil`, and `validAfter`. - -Here is an example function signature of a conformant user operation validator function: - -```solidity -function validateSignature(UserOperation calldata userOp, bytes32 userOpHash) external returns (uint256 validationData); -``` - -Runtime Validation Functions may have any function name, and MUST take in the parameters `(address, uint256, bytes calldata)`. - -Here is an example function signature of a conformant runtime validator function: - -```solidity -function validateOwnership(address caller, uint256 value, bytes calldata) external; -``` - -Hooks MAY have any function name. -The pre user operation validation hook functions MUST take in the parameters `(bytes calldata callData, bytes calldata paymasterAndData)` and return `(bool)`. -The pre runtime validation hook functions MUST take in the parameters `(bytes calldata)` and return `(bool)`. -The pre execution hook functions MUST take in the parameters `(address, uint256, bytes calldata)` and return `(bytes calldata)`. -The post execution hook functions MUST take in the parameters `(bytes calldata)`. +When calling `updatePlugins`, the account MUST run the validators and hooks as they are specified before the operation is complete. Notably, the post execution hooks defined prior to the update must run after the update. ## Rationale ERC-4337 compatible accounts must implement the `IAccount` interface, which consists of only one method that bundles validation with execution: `validateUserOp`. A primary design rationale for this proposal is to extend the possible functions for a smart contract account beyond this single method by unbundling these and other functions, while retaining the benefits of account abstraction. -The diamond pattern of ERC-2535 is the logical starting point for achieving this extension into multiple functionality, given its suitability for implementing multiple execution calls to ERC-4337 compatible accounts from EntryPoint. It also meets our other primary design rationale of generalizability to calls to EOA/SC accounts. However, a strict diamond pattern is constrained by its inability to customize validation schemes and other logic linked to specific execution functions in the context of `validateUserOp`. - -This proposal includes several interfaces that build on ERC-4337 and is ispired by ERC-2535. First, we standardize a set of modular plugins that allow smart contract developers greater flexibility in bundling validation, execution and hook logic. We also propose interfaces like `IPluginUpdate` that take inspiration from the diamond standard, and provide methods for updating and querying execution functions, validation schemes, and hooks. +The function routing pattern of ERC-2535 is the logical starting point for achieving this extension into multi-functional accounts. It also meets our other primary design rationale of generalizing execution calls across multiple implementing contracts. However, a strict diamond pattern is constrained by its inability to customize validation schemes for specific execution functions in the context of `validateUserOp`, and its requirement of `delegatecall`. -The diamond standard's interfaces are not required or enforced by this standard. An MSCA implementation is considered compliant with this standard as long as it satisfies the interface requirements and expected behaviors. +This proposal includes several interfaces that build on ERC-4337 and are inspired by ERC-2535. First, we standardize a set of modular plugins that allow smart contract developers greater flexibility in bundling validation, execution and hook logic. We also propose interfaces like `IPluginUpdate` and `IPluginLoupe` that take inspiration from the diamond standard, and provide methods for updating and querying execution functions, validation functions, and hooks. ## Backwards Compatibility diff --git a/assets/eip-6900/MSCA_Two_Call_Paths_Diagram.svg b/assets/eip-6900/MSCA_Two_Call_Paths_Diagram.svg index 96d76a98747b0..7b272a7f48a05 100644 --- a/assets/eip-6900/MSCA_Two_Call_Paths_Diagram.svg +++ b/assets/eip-6900/MSCA_Two_Call_Paths_Diagram.svg @@ -1,4 +1,4 @@ - + @@ -12,5 +12,6 @@ src: url("https://excalidraw.com/Cascadia.woff2"); } + - SCAEntryPointEOA/SC callA.0B.1A.1A.2Step 1Step 2Step 3Step 4ACalls from Entrypoint (ERC-4337)BCalls from EOAs and other contractsB.2UserOpValidatorvalidateUserOpPre-UserOpValidate Hook(s)Execution FunctionPre-Execution Hook(s)Post-Execution Hook(s)A.3 B.3A.4 B.4fallback & standard executionfrom EntryPoint?RuntimeValidatorPre-RuntimeValidate Hook(s)noyesStep 5 \ No newline at end of file + SCAEntryPointA.1B.1A.2Step 1Step 2Step 3Step 4ACalls from Entrypoint (ERC-4337)BCalls from EOAs and other contractsUser Operation ValidatorvalidateUserOpPre User Operation Validation Hook(s)Execution FunctionPre Execution Hook(s)Post Execution Hook(s)A.3 B.3A.4 B.4fallback & native functionsRuntime ValidatorPre Runtime Validation Hook(s)Step 5EOA / SCB.2 \ No newline at end of file From 67f2a07bc7303faadb27ac7bfb4c3b9b7647f3c1 Mon Sep 17 00:00:00 2001 From: John Zhang Date: Wed, 5 Jul 2023 21:37:57 +0800 Subject: [PATCH 17/41] Add EIP: Social Recovery Interface Merged by EIP-Bot. --- EIPS/eip-7093.md | 430 +++++++++++++++++++++++ assets/eip-7093/social-recovery-flow.svg | 17 + 2 files changed, 447 insertions(+) create mode 100644 EIPS/eip-7093.md create mode 100644 assets/eip-7093/social-recovery-flow.svg diff --git a/EIPS/eip-7093.md b/EIPS/eip-7093.md new file mode 100644 index 0000000000000..e8574b7455afc --- /dev/null +++ b/EIPS/eip-7093.md @@ -0,0 +1,430 @@ +--- +eip: 7093 +title: Social Recovery Interface +description: Interfaces for social recovery account supporting various guardian types and customizable recovery policies. +author: John Zhang (@johnz1019), Davis Xiang (@xcshuan), Kyle Xu (@kylexyxu), George Zhang (@odysseus0) +discussions-to: https://ethereum-magicians.org/t/eip-social-recovery-interface/14494 +status: Draft +type: Standards Track +category: ERC +created: 2023-05-29 +requires: 1271 +--- + +## Abstract + +This ERC proposes a standard interface for social recovery of smart contract accounts. It separates identity and policy verification from the recovery process, allowing more ways to authenticate (known as Guardians) than just on-chain accounts. It also lets users customize recovery policies without changing the account’s smart contract. + +## Motivation + +Vitalik Buterin has long advocated for social recovery as an essential tool for user protection within the crypto space. He posits that the value of this system rests in its ability to offer users, especially those less acquainted with the technicalities of cryptography, a robust safety net when access credentials are lost. By entrusting account recovery to a network of selected individuals or entities, dubbed "Guardians," users gain a safeguard against the risk of losing access to their digital assets. + +In essence, social recovery operates by verifying the identity of the user and the chosen Guardians, and then considering a set of their signatures. Should the validated signatures reach a specified threshold, account access is reestablished. This system is equipped to enforce complex policies, such as necessitating signatures from particular Guardians or reaching signature thresholds from different Guardian categories. + +To overcome these limitations, this Ethereum Improvement Proposal (EIP) introduces a novel, customizable social recovery interface standard. This standard decouples identity and recovery policy verification from the recovery procedure itself, thereby enabling an independent, versatile definition and extension of both. This strategy accommodates a wider range of Guardian types and recovery policies, thereby offering users the following benefits: + +1. Appoint friends or family members, who do not have blockchain accounts, as Guardians for social recovery. +2. Use NFTs/SBTs as Guardians for their accounts. +3. Personalize and implement adaptable recovery policies. +4. Support novel types of Guardians and recovery policies without needing to upgrade their account contracts. +5. Enable multiple recovery mechanism support, thereby eliminating single points of failure. + +This approach enables users to customize recovery policies without the need to change the smart contract of the account itself. + +## 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. + + + +This EIP consists of four key concepts: + +- **Identity**: This denotes the representation of a Guardian's identity on the blockchain. It encapsulates traditional on-chain account types such as Externally Owned Accounts (EOA) and Smart Contract Accounts (SCA). More importantly, it extends to include any identity construct capable of producing construct able to be verified on-chain, like signatures and proofs. This could range from [Webauthn](https://www.w3.org/TR/2021/REC-webauthn-2-20210408/)/Passkey R1 keys to Email DomainKeys Identified Mail (DKIM) signatures [RFC 6376](https://www.rfc-editor.org/rfc/rfc6376), OpenID tokens, Zero-Knowledge Proofs (ZKP), Non-Fungible Tokens (NFTs), SoulBound Tokens (SBTs), and even types yet to be developed. This comprehensive approach ensures a broad, forward-compatible support for various identity types. +- **GuardianPermissionVerifier**: This component defines how to verify the signature or proof provided by the Guardian. Regardless of whether the Guardian's account is on-chain or off-chain, the GuardianPermissionVerifier is invoked during the recovery process of smart contract accounts that incorporate a social recovery system. Its primary role is to confirm the validity of the Guardian's signature or proof, thereby ensuring the authenticity of the Guardian during the recovery process. +- **RecoveryPolicyVerifier**: This component offers a flexible interface for validating recovery policies. The flexibility stems from allowing account holders or authorized parties to define and store their recovery policies. During the recovery process, the verification logic is implemented by invoking the specific function of the contract instance adopting this interface. Thus, a wide array of customizable social recovery scenarios can be catered to through different contract instances and policy configurations. +- **RecoveryAccount**: This component encapsulates the core of the social recovery functionality. It is designed to be flexible, composable, and extensible to adapt to various recovery needs. Each RecoveryAccount is defined by an instance contract, crafted by smart contract developers, which embeds the essential logic for the recovery process. + +![social_recovery_flow](../assets/eip-7093/social-recovery-flow.svg) + +### DataTypes + +### `TypesAndDecoders` + +This defines the necessary data types required by this interface standard. + +```solidity +/** + * @dev Structure representing an identity with its signature/proof verification logic. + * Represents an EOA/CA account when signer is empty,use `guardianVerifier`as the actual signer for signature verification. + * OtherWise execute IGuardianPermissionVerifier(guardianVerifier).isValidPermissions(signer,hash,signature). + */ +struct Identity { + address guardianVerifier; + bytes signer; +} + +/** + * @dev Structure representing a guardian with a property + * The property of Guardian are defined by the associated RecoveryPolicyVerifier contract. + */ +struct GuardianInfo { + Identity guardian; + uint32 property; //eg.,Weight,Percentage,Role with weight,etc. +} + +/** + * @dev Structure representing a threshold configuration + */ +struct ThresholdConfig { + uint32 threshold; // Threshold value + int48 lockPeriod; // Lock period for the threshold +} + +/** + * @dev Structure representing a recovery configuration + * A RecoveryConfig can have multiple threshold configurations for different threshold values and their lock periods + */ +struct RecoveryConfig { + GuardianInfo[] guardianInfos; + ThresholdConfig[] thresholdConfigs; +} + +/** + * @dev Structure representing a recovery policy with its verification logic (policy verifier contract address) + */ +struct RecoveryPolicy { + address policyVerifier; + RecoveryConfig config; +} + +struct Permission { + Identity guardian; + bytes signature; +} + +``` + +The `Identity` structure represents various types of guardians. The process of identity verification is as follows: + +- When the `signer` value in the declared entity is empty, this implies that the `Identity` entity is of EOA/SCA account type. In this case, `guardianVerifier` address should be the address of EOA/SCA (the actual signer). For permission verification of this `Identity` entity, it is recommended to utilize a secure library or built-in function capable of validating both ECDSA and [ERC-1271](./eip-1271.md) signatures. This helps in preventing potential security vulnerabilities, such as signature malleability attacks. +- When the `signer` value in the declared entity is non-empty, this suggests that the `Identity` entity is of non-account type. In this case, permission verification can be accomplished by calling `guardianVerifier` address contract instance through `IGuardianPermissionVerifier` interface. + + + +### Interfaces + +### `IGuardianPermissionVerifier` + +The Guardian Permission Verification Interface. Implementations MUST conform to this interface to enable identity verification of non-account type guardians. + +```solidity +/** + * @dev Interface for no-account type identity signature/proof verification + */ +interface IGuardianPermissionVerifier { + /** + * @dev Check if the signer key format is correct + */ + function isValidSigners(bytes[] signers) external returns (bool); + + /** + * @dev Validate signatures + */ + function isValidPermissions( + bytes[] signers, + bytes32 hash, + bytes[] signatures + ) external returns (bool); + + /** + * @dev Return supported signer key information, format, signature format, hash algorithm, etc. + * MAY TODO:using ERC-3668: ccip-read + */ + function getGuardianVerifierInfo() public view returns (bytes memory); +} + +``` + + + +### `IRecoveryPolicyVerifier` + +The Recovery Policy Verification Interface. Implementations MAY conform to this interface to support verification of varying recovery policies. + +```solidity +/** + * @dev Interface for recovery policy verification + */ +interface IRecoveryPolicyVerifier { + /** + * @dev Verify recovery policy and return verification success and lock period + * Verification includes checking if guardians exist in the Guardians List + */ + function verifyRecoveryPolicy(Identity[] guardians, RecoveryConfig config) + external + view + returns (bool succ, uint48 lockPeriod); + + /** + * @dev Returns supported policy settings and accompanying property definitions for Guardian. + */ + function getPolicyVerifierInfo() public view returns (bytes memory); +} + +``` + +The `verifiyRecoveryPolicy()` function is designed to validate whether the provided list of `guardians` abides by the specified recovery configuration (`config`). This function has the following constraints and effects: + +1. It checks whether each `guardian` in the input exists within the `config.guardianInfos` list. Furthermore, it ensures that guarddians are distinct, effectively avoiding duplication.This ensures that only authorized and unique Guardians participate in the recovery process. +2. For each matched `guardian`, calculations are made according to the corresponding `property` in the `config.guardianInfos` list (e.g., accumulating weight, distinguishing role while accumulating, etc.). +3. The calculation result is compared with the threshold in `config.thresholdConfigs`. If the result is greater than or equal to any threshold, the recovery policy verification is deemed successful. +4. If multiple thresholds are met, the smallest `lockPeriod` is returned. + +These constraints ensure that the provided `guardians` and `config` comply with the requirements of the recovery policy, maintaining the security and integrity of the recovery process. + + + +### `IRecoveryAccount` + +The Smart Contract Account MAY implement the `IRecoveryAccount` interface to support social recovery functionality, enabling users to customize configurations of different types of Guardians and recovery policies. + +```solidity +interface IRecoveryAccount { + modifier onlySelf() { + require(msg.sender == address(this), "onlySelf: NOT_AUTHORIZED"); + _; + } + + modifier InRecovering(address policyVerifiyAddress) { + (bool isRecovering, ) = getRecoveryStatus(policyVerifierAddress); + require(isRecovering, "InRecovering: no ongoing recovery"); + _; + } + + /** + * @dev Events for updating guardians, starting for recovery, executing recovery, and canceling recovery + */ + event GuardiansUpdated(RecoveryPolicy[] recoveryPolicies); + event RecoveryStarted(address policyVerifier, bytes newOwners, uint256 nonce, uint48 expiryTime); + event RecoveryExecuted(address policyVerifier, bytes newOwners, uint256 nonce); + event RecoveryCanceled(address policyVerifier, uint256 nonce); + + /** + * @dev Return the domain separator name and version for signatures + * Also return the domainSeparator for EIP-712 signature + */ + + /// @notice Domain separator name for signatures + function DOMAIN_SEPARATOR_NAME() external view returns (string memory); + + /// @notice Domain separator version for signatures + function DOMAIN_SEPARATOR_VERSION() external view returns (string memory); + + /// @notice returns the domainSeparator for EIP-712 signature + /// @return the bytes32 domainSeparator for EIP-712 signature + function domainSeparatorV4() external view returns (bytes32); + + /** + * @dev Update /replace guardians and recovery policies + * Multiple recovery policies can be set using an array of RecoveryPolicyConfig + */ + function updateGuardians(RecoveryPolicy[] recoveryPolicyConfig) external onlySelf; + + // Generate EIP-712 message hash, + // Iterate over signatures for verification, + // Verify recovery policy, + // Store temporary state or recover immediately based on the result returned by verifyRecoveryPolicy. + function startRecovery( + address policyVerifier, + bytes newOwner, + Permission[] permissions + ) external; + + /** + * @dev Execute recovery + * temporary state -> ownerKey rotation + */ + function executeRecovery(address policyVerifier) external; + + function cancelRecovery(address policyVerifier) external onlySelf InRecovering(policyVerifier); + + function cancelRecoveryByGuardians(address policyVerifier, Permission[] permissions) + external + InRecovering(policyVerifier); + + /** + * @dev Get wallet recovery info, recovery policy config, check if an identity is a guardian, get the nonce of social recovery, and get the recovery status of the wallet + */ + function isGuardian(identity guardian) public view returns (bool, RecoveryPolicy[] recoveryPolicyConfigs); + + function getRecoveryPolicies() public view returns (RecoveryPolicy[] recoveryPolicyConfigs); + + function getRecoveryConfigs(address policyVerifier) public view returns (RecoveryConfig config); + + function getRecoveryNonce() public view returns (uint256 nonce); + + function getRecoveryStatus(address policyVerifier) public view returns (bool isRecovering, uint48 expiryTime); +} + +``` + +- For the `Guardian`'s signable message, it SHOULD employ [EIP-712](./eip-712.md) type signature to ensure the content of the signature is readable and can be confirmed accurately during the Guardian signing process. +- `getRecoveryNonce()` SHOULD be separated from nonces associated with account asset operations, as social recovery is a function at the account layer. + + + +### **Recovery Account Workflow** + +Note: This workflow is presented as an illustrative example to clarify the coordinated usage of the associated interface components. It does not imply a mandatory adherence to this exact process. + +1. A user sets up a `recoveryPolicyConfigA` within his `RecoveryAccount`: + + ```json + { + "recoveryPolicyConfigA": { + "type": "RecoveryPolicy", + "policyVerifier": "0xA", + "config": { + "guardians": [ + { + "type": "Identity", + "name": "A", + "data": { + "guardianVerifier": "guardianVerifier1", + "signer": "signerA" + }, + "property": 30 + }, + { + "type": "Identity", + "name": "B", + "data": { + "guardianVerifier": "guardianVerifier2", + "signer": "" + }, + "property": 30 + }, + { + "type": "Identity", + "name": "C", + "data": { + "guardianVerifier": "guardianVerifier3", + "signer": "signerC" + }, + "property": 40 + } + ], + "thresholdConfigs": [ + { "threshold": 50, "lockPeriod": "24hours"}, + { "threshold": 100,"lockPeriod": "0"} + ] + } + } + } + ``` + +2. When GuardianA and GuardianB assist the user in performing account recovery, they are to confirm the [EIP-712](./eip-712.md) structured data for signing, which might look like this: + + ```json + { + "types": { + "EIP712Domain": [ + { "name": "name", "type": "string" }, + { "name": "version", "type": "string" }, + { "name": "chainId", "type": "uint256" }, + { "name": "verifyingContract", "type": "address" } + ], + "StartRecovery": [ + { "name": "policyVerifier", "type": "address" }, + { "name": "newOwners", "type": "bytes" }, + { "name": "nonce", "type": "uint256" } + ] + }, + "primaryType": "StartRecovery", + "domain": { + "name": "Recovery Account Contract", + "version": "1", + "chainId": 1, + "verifyingContract": "0xCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" + }, + "message": { + "policyVerifier": "0xA", + "newOwners": "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", + "nonce": 10 + } + } + ``` + + In this step, the guardians need to confirm that the domain separator's `verifyingContract` is the correct `RecoveryAccount` address for the user, the contract name, version, and chainId are correct, and the `policyVerifier` and `newOwners` fields in the `message` part match the user's provided data. + + The `msgHash` is then composed of: + + - `msgHash` = `keccak256("\\x19\\x01" + domainSeparatorV4() + dataHash)` + + Where, + + - `dataHash` = `keccak256(EXECUTE_RECOVERY_TYPEHASH + policyVerifier + keccak256(bytes(newOwners)) + getRecoveryNonce())` + - `EXECUTE_RECOVERY_TYPEHASH` = `keccak256("StartRecovery(address policyVerifier, bytes newOwners, uint256 nonce)")` + + The guardians sign this hash to obtain the signature: + + - `signature` = `sign(msgHash)` + + The `permission` is then constructed as: + + - `permission` = `guardian + signature` + + Once each Guardian has generated their unique `permission`, all these individual permissions are collected to form `permissions`: + + `permissions`= [`guardianA+signature`, `guardianB+signature`, ...] + + The `permissions` is an array that consists of all the permissions of the Guardians who are participating in the recovery process. + +3. A bundler or another relayer service calls the `RecoveryAccount.startRecovery(0xA, newOwners, permissions)` function. + +4. `startRecovery()` function's processing logic is as follows: + + - Generate a message hash (`msgHash`) from the input parameters `0xA`, `newOwners` and internally generated [EIP-712](./eip-712.md) signature parameters and `RecoveryNonce`. + - Extract `guardian` and corresponding `signature` from the input parameters `permissions` and process them as follows: + - If `guardianA.signer` is non-empty (Identity A), call `IGuardianPermissionVerifier(guardianVerifier1).isValidPermissions(signerA, msgHash, permissionA.signature)` to validate the signature. + - If `guardianA.signer` is empty (Identity B), call the internal function `SignatureChecker.isValidSignatureNow(guardianVerifier2, msgHash, permissionB.signature)` to validate the signature. + +5. After successful verification of all `guardians` signatures, fetch the associated `config` data for policyVerifier address `0xA` and call `IRecoveryPolicyVerifier(0xA).verifyRecoveryPolicy(guardians, config)`. The function `verifyRecoveryPolicy()` performs the following checks: + + Note that the `guardians` parameter in the function refers to the guardians whose signatures have been successfully verified. + + - Verify that `guardians` (Identity A and B) are present in `config.guardianInfos` list and are unique. + - Accumulate the `property` values of `guardians` (30 + 30 = 60). + - Compare the calculated result (60) with the `config.thresholdConfigs.threshold` ,the result is more than the first element (`threshold: 50, lockPeriod: 24 hours`) but less than the second element (`threshold: 100, lockPeriod: ""`), the validation is successful, and the lock period of 24 hours is returned. + +6. The `RecoveryAccount` saves a temporary state `{policyVerifier, newOwners, block.timestamp + 24 hours}` and increments `RecoveryNonce`. A `RecoveryStarted` event is emitted. + +7. After the expiry time, anyone (usually a relayer) can call `RecoveryAccount.executeRecovery()` to replace `newOwners`, remove the temporary state, complete the recovery, and emit a `RecoveryExecuteed` event. + + + +## Rationale + +A primary design rationale for this proposal is to extend a greater diversity of Guardian types and more flexible, customizable recovery policies for a RecoveryAccount. This is achieved by separating the verification logic from the social recovery process, ensuring that the basic logic of the account contract remains unaltered. + +The necessity of incorporating `Verifiers` from external contracts arises from the importance of maintaining the inherent recovery logic of the `RecoveryAccount`. The `Verifiers`'s logic is designed to be simple and clear, and its fixed invocation format means that any security risks posed by integrating external contracts can be effectively managed. + +The `recoveryPolicyConfigs` are critical to the `RecoveryAccount` and should be securely and effectively stored. The access and modification permissions associated with these configurations must be carefully managed and isolated to maintain security. The storage and quantity of `recoveryPolicyConfigs` are not limited to ensure the maximum flexibility of the `RecoveryAccount`'s implementation. + +The introduction of `recoveryNonce` into the `RecoveryAccount` serves to prevent potential replay attacks arising from the malicious use of Guardian's `permissions`. The `recoveryNonce` ensures each recovery process is unique, reducing the likelihood of past successful recovery attempts being maliciously reused. + +## Backwards Compatibility + +No backward compatibility issues are introduced by this standard. + +## Reference Implementation + +TBD. + +## Security Considerations + +Needs discussion. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). diff --git a/assets/eip-7093/social-recovery-flow.svg b/assets/eip-7093/social-recovery-flow.svg new file mode 100644 index 0000000000000..97bd51dc6b35b --- /dev/null +++ b/assets/eip-7093/social-recovery-flow.svg @@ -0,0 +1,17 @@ + + + + + + + + Generate 712-MsgHash+guardians + signatures(sequential Verify)PasskeyisValidPermissionsOpenID keyisValidPermissionsEmail keyisValidPermissionsCA key IERC1271 VerifyEOA key ECDSA VerifyIRecoveryAccountWalletrecoveryPolicy1:1.List of Guardians with property2.Threshold + lock period3.policyVerifier AupdateGuardians 1.recoveryPoliciesstartRecovery 1.policyVerifier A 2.newOwner 3.permissions1. VerifyPermission2.VerifyRecoveryPolicyrecoveryConfig PolicyVerifier verifyRecoveryPolicy3.Based on return (success, lockperiod),Handle recovery:executeRecovery/pendingRecoveryrecoveryPolicy2:1.List of Guardians with property2.Threshold + lock period3.policyVerifier B \ No newline at end of file From d05d4b3c3298613781a0804f851eb0201ca5a6c2 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 5 Jul 2023 15:50:59 -0600 Subject: [PATCH 18/41] Update EIP-4788: Bound precompile storage Merged by EIP-Bot. --- EIPS/eip-4788.md | 83 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 72e787a6ca11d..f66eba05ad494 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -29,7 +29,8 @@ restaking constructions, smart contract bridges, MEV mitigations and more. |--- |--- |--- | `FORK_TIMESTAMP` | TBD | | `HISTORY_STORAGE_ADDRESS` | `Bytes20(0xB)` | -| `G_beacon_root` | 2100 | gas +| `G_beacon_root` | 4200 | gas +| `HISTORICAL_ROOTS_MODULUS` | 98304 | ### Background @@ -43,7 +44,9 @@ To bound the amount of storage this construction consumes, a ring buffer is used Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST** extend the header schema with an additional field: the `parent_beacon_block_root`. This root consumes 32 bytes and is exactly the [hash tree root](https://github.com/ethereum/consensus-specs/blob/fa09d896484bbe240334fa21ffaa454bafe5842e/ssz/simple-serialize.md#merkleization) of the parent beacon block for the given execution block. -Validity is guaranteed from the consensus layer, much like how withdrawals are handled. +Validity of the parent beacon block root is guaranteed from the consensus layer, much like how withdrawals are handled. + +When verifying a block, execution clients **MUST** ensure the root value in the block header matches the one provided by the consensus client. ### EVM changes @@ -52,43 +55,75 @@ Validity is guaranteed from the consensus layer, much like how withdrawals are h At the start of processing any execution block where `block.timestamp >= FORK_TIMESTAMP` (i.e. before processing any transactions), write the parent beacon root provided in the block header into the storage of the contract at `HISTORY_STORAGE_ADDRESS`. -The root itself is used as a key into the contract's storage and the timestamp of the header is written as the key's value. -The timestamp (a 64-bit unsigned integer value) is encoded as 32 bytes in big-endian format. +In order to bound the storage used by this precompile, two ring buffers are used: one to track the latest timestamp at a given index in the ring buffer and another to track +the latest root at a given index. + +To derive the index `timestamp_index` into the timestamp ring buffer, the timestamp (a 64-bit unsigned integer value) is reduced modulo `HISTORICAL_ROOTS_MODULUS`. +To derive the index `root_index` into the root ring buffer, add `HISTORICAL_ROOTS_MODULUS` to the index into the timestamp ring buffer. +Both resulting 64-bit unsigned integers should be encoded as 32 bytes in big-endian format when writing to the storage. + +The timestamp from the header, encoded as 32 bytes in big-endian format, is the value to write behind the `timestamp_index`. +The 32 bytes of the `parent_beacon_block_root` (as provided) are the value to write behind the `root_index`. In Python pseudocode: ```python +timestamp_reduced = block_header.timestamp % HISTORICAL_ROOTS_MODULUS +timestamp_extended = timestamp_reduced + HISTORICAL_ROOTS_MODULUS +timestamp_index = to_uint256_be(timestamp_reduced) +root_index = to_uint256_be(timestamp_extended) + +timestamp_as_uint256 = to_uint256_be(block_header.timestamp) parent_beacon_block_root = block_header.parent_beacon_block_root -timestamp = to_uint256_be(block_header.timestamp) -sstore(HISTORY_STORAGE_ADDRESS, parent_beacon_block_root, timestamp) +sstore(HISTORY_STORAGE_ADDRESS, timestamp_index, timestamp_as_uint256) +sstore(HISTORY_STORAGE_ADDRESS, root_index, parent_beacon_block_root) ``` #### New stateful precompile -Beginning at the execution timestamp `FORK_TIMESTAMP`, the code and storage at `HISTORY_STORAGE_ADDRESS` constitute a "stateful" precompile. +Beginning at the execution timestamp `FORK_TIMESTAMP`, a "stateful" precompile is deployed at `HISTORY_STORAGE_ADDRESS`. + +Callers of the precompile should provide the `timestamp` they are querying encoded as 32 bytes in big-endian format. +Clients **MUST** sanitize this input call data to the precompile. +If the input is _more_ than 32 bytes, the precompile only takes the first 32 bytes of the input buffer and ignores the rest. +If the input is _less_ than 32 bytes, the precompile should revert. + +Given this input, the precompile reduces the `timestamp` in the same way during the write routine and first checks if +the `timestamp` recorded in the ring buffer matches the one supplied by the caller. -Callers of the precompile should provide the `root` they are querying encoded as 32 bytes. +If the `timestamp` **does NOT** match, the client **MUST** return the "zero" word -- the 32-byte value where each byte is `0x00`. -Alongside the existing gas for calling the precompile, there is an additional gas cost of `G_beacon_root` cost to reflect the implicit `SLOAD` from -the precompile's state. The timestamp of the corresponding root is returned as 32 bytes in the caller's provided return buffer and represents the -64-bit unsigned integer from the header in big-endian format. +If the `timestamp` **does** match, the client **MUST** read the root from the contract storage and return those 32 bytes in the caller's return buffer. In pseudocode: ```python -root = evm.calldata[:32] -timestamp = sload(HISTORY_STORAGE_ADDRESS, root) -evm.returndata[:32].set(timestamp) +timestamp = evm.calldata[:32] +if len(timestamp) != 32: + evm.revert() + return + +timestamp_reduced = to_uint64_be(timestamp) % HISTORICAL_ROOTS_MODULUS +timestamp_index = to_uint256_be(timestamp_reduced) + +recorded_timestamp = sload(HISTORY_STORAGE_ADDRESS, timestamp_index) +if recorded_timestamp != timestamp: + evm.returndata[:32].set(uint256(0)) +else: + timestamp_extended = timestamp_reduced + HISTORICAL_ROOTS_MODULUS + root_index = to_uint256_be(timestamp_extended) + root = sload(HISTORY_STORAGE_ADDRESS, root_index) + evm.returndata[:32].set(root) ``` -If there is no timestamp stored at the given root, the opcode follows the existing EVM semantics of `sload` returning `0`. +The precompile costs `G_beacon_root` gas to reflect the two (2) implicit `SLOAD`s from the precompile's state. ## Rationale ### Gas cost of precompile -The suggested gas cost reflects a cold `SLOAD` analogous to the operation performed while executing the precompile's logic. +The suggested gas cost reflects a cold `SLOAD` analogous to the operation(s) performed while executing the precompile's logic. ### Why not repurpose `BLOCKHASH`? @@ -104,6 +139,22 @@ be nonfavorable conditions. Use of block root over state root does mean proofs will require a few additional nodes but this cost is negligible (and could be amortized across all consumers, e.g. with a singleton state root contract that caches the proof per slot). +### Why two ring buffers? + +The first ring buffer only tracks `HISTORICAL_ROOTS_MODULUS` worth of roots and so for all possible timestamp values would consume a constant amount of storage. +However, this design opens the precompile to an attack where a skipped slot that has the same value modulo the ring buffer length would return an old root value, +rather than the most recent one. + +To nullify this attack while retaining a fixed memory footprint, this EIP keeps track of the pair of data `(parent_beacon_block_root, timestamp)` for each index into the +ring buffer and verifies the timestamp matches the one originally used to write the root data when being read. Given the fixed size of storage slots (only 32 bytes), the requirement +to store a pair of values necessitates two ring buffers, rather than just one. + +### Size of ring buffers + +The ring buffer data structures are sized to hold 8192 roots from the consensus layer at current slot timings (`SECONDS_PER_SLOT` is 12 seconds on mainnet, and `8192 * 12 == 98304`). +At mainnet values, 8192 roots provides about a day of coverage of the chain which gives users plenty of time to make a transaction with a verification against a given root in +the chain and get the transaction included. + ## Backwards Compatibility No issues. From a0ccd526fc04be81cc2ce25ef9182218d05207a9 Mon Sep 17 00:00:00 2001 From: dRadiant <103012174+dRadiant@users.noreply.github.com> Date: Wed, 5 Jul 2023 21:18:03 -0400 Subject: [PATCH 19/41] Update EIP-5173: Update EIP-5173 Merged by EIP-Bot. --- EIPS/eip-5173.md | 95 +++++++++++++++++++----------------------------- 1 file changed, 37 insertions(+), 58 deletions(-) diff --git a/EIPS/eip-5173.md b/EIPS/eip-5173.md index 7a1336b9add7f..cbe7a4bce776b 100644 --- a/EIPS/eip-5173.md +++ b/EIPS/eip-5173.md @@ -13,59 +13,27 @@ requires: 165, 721 ## Abstract -In this EIP, we propose the implementation of a Future Rewards (FR) extension which will enable owners of [EIP-721](./eip-721.md) tokens (NFTs) to participate in future price increases after they sell their tokens. - -Through the implementation of this nFR proposal, the creators, buyers and sellers create a giving circle in trading practice. A giving circle is formed when all participants work in a framework to build greater wealth through each other's success. One does not expect the same amount of return from the same person when they give someone a portion of their profits. There is no quid pro quo. Rather, they are confident that someone else in the same circle will give them the same benefits, with a smaller or greater monetary value, from other participants in the same circle later on. - -Owners of nFR compliant tokens can benefit in two ways from such a gift economic framework: - -1. An increase in price during their holding period; -2. They continue to receive Future Rewards (FRs) after the token is sold. - -The realized profits from the sale of nFR compliant tokens will be shared across the chain of historical ownership if the seller is not the original Minter and therefore not the very first seller. Through the NFT Future Rewards (nFR) framework, the same seller, as well as every other seller, will receive the same FR distributions. Everybody pays it forward, forming a giving circle. - -Giving circles are groups of people who work together to improve a situation that is typically much larger than it is at the moment. Some of the characteristics of a giving circle are community interdependence and delayed reciprocity. - -In a well-designed circle of giving, givers may be able to receive more than they give over time, so giving is not the only thing involved. As a result, the traditional model of platform versus user and user vs. user relationships has been fundamentally altered into one, shared objective: if others succeed, I succeed more. - +In the proposed EIP, we introduce the Future Rewards (FR) extension for [ERC-721](./eip-721.md) tokens (NFTs), allowing owners to benefit from future price increases even after selling their tokens. With [ERC-5173](./eip-5173.md), we establish a Participatory Value Amplification framework where creators, buyers, and sellers collaborate to amplify value and build wealth together. This innovative approach tackles the challenges faced by traders, creating a fairer and more profitable system that goes beyond zero-sum thinking. Aligned interests between the service provider and users create a sustainable and beneficial trading environment. ERC-5173 compliant token owners enjoy price increases during holding and continue to receive Future Rewards (FRs) even after selling. By eliminating division and promoting collective prosperity, ERC-5173 fosters strong bonds among participants. All sellers, including the original Minter, receive equal FR distributions through the NFT Future Rewards (nFR) framework, ensuring fair sharing of profits across historical ownership. ## Motivation -Not limited to NFT trading, it is common for an average trader to fall victim to spoofing, insider trading, front running, wash trading, and pump and dump, among a number of other techniques used by various actors. The current system guarantees that most traders will lose money because of their emotions, the constant oscillation between greed and fear. Under the current system, a trader has no advantage over many of the more sophisticated techniques used by various actors. - -Although this historical precedent has been followed in today's markets, just as crypto has revolutionized traditional trading, we now have the opportunity to transform this historic trail of unequal value distribution by tracking every transaction of every distinguishable token through the emergence of the non-fungible token standard. - -There needs to be a change in historical unfair trading practices so that: - -* With a success-based model, everyone is on the same page; -* A mutually beneficial economic rule benefits both buyers and sellers. - - -NFTs, in contrast to physical art and collectibles in the physical world, are not currently reflecting the contributions of their owners to their value. Since each [EIP-721](./eip-721.md) token can be tracked individually, and may be modified to record every change in the price of any specific NFT token, there is no reason that a Future Rewards program of this type should not be established. +The current trading landscape is rife with unfair practices such as spoofing, insider trading, and wash trading. These techniques often lead to losses for average traders who are caught in the cycle of greed and fear. However, with the emergence of non-fungible tokens (NFTs) and the potential to track every transaction, we have the opportunity to disrupt this unequal value distribution. -This nFR proposal establishes a standard interface for a profit sharing framework in all stages of the token's ownership history desired by all market participants. In a giving circle, art buyers/owners are compensated for their participation in the instrument’s trading price discovery process. +The implementation of ERC-5173 sets a standard for profit sharing throughout the token's ownership history, benefiting all market participants. It introduces a value-amplification system where buyers and owners are rewarded for their contributions to the price discovery process. This model aligns interests and creates a mutually beneficial economic rule for both buyers and sellers. -We embrace and promote a new gift economic model, which is similar to the Copyleft and open-source spirit as opposed to traditional copyrights. The advancement of technology has enabled such implementation in trading for the first time. In the same way that open-source software has changed the software industry and society, we can also change the financial industry. +NFTs, unlike physical art and collectibles, can accurately reflect the contributions of their owners to their value. By recording every price change of each [ERC-721](./eip-721.md) token, we can establish a Future Rewards program that fairly compensates owners. This program aims to level the playing field and provide average traders with a better chance at success. -As in trading, most traders lose money, but the proposed Future Rewards framework is designed to help average traders do better. +In addition to promoting a new gift economic model, our framework discourages any illicit deals that bypass the rules set by artists and marketplaces. It promotes transparency and integrity within the trading ecosystem. -Additionally, as we will explain later, it discourages any "under-the-table" deals that may circumvent the rules set forth by artists and marketplaces. - -### Is This Just a Ponzi Scheme? - -No, it is not. Ponzi schemes promise profits that are impossible to keep. - -As opposed to fixed-yield schemes, our proposal only distributes future profits when those profits are achieved rather than guaranteeing them. Should later holders fail to make a profit, future return shares will not be distributed. - -The early participants in price discovery will receive a share of profits as part of the FR implementation only and if a later owner has accumulated profits during their holdings of the token. +When applied to wrapped [ERC-20](./eip-20.md) token trading, this value-amplification framework revolutionizes the asset transaction industry by incorporating identities into the time and sales (T&S) data. This inclusive feature provides a comprehensive view of each transaction, adding a new dimension to trading. ## Specification The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. -The following is an extension of the [EIP-721](./eip-721.md) standard. +The following is an extension of the [ERC-721](./eip-721.md) standard. -[EIP-721](./eip-721.md)-compliant contracts MAY implement this EIP for rewards to provide a standard method of rewarding future buyers and previous owners with realized profits in the future. +[ERC-721](./eip-721.md)-compliant contracts MAY implement this EIP for rewards to provide a standard method of rewarding future buyers and previous owners with realized profits in the future. Implementers of this standard MUST have all of the following functions: @@ -88,6 +56,12 @@ interface IERC5173 is IERC165 { event FRDistributed(uint256 indexed tokenId, uint256 indexed soldPrice, uint256 indexed allocatedFR); + event Listed(uint256 indexed tokenId, uint256 indexed salePrice); + + event Unlisted(uint256 indexed tokenId); + + event Bought(uint256 indexed tokenId, uint256 indexed salePrice); + function list(uint256 tokenId, uint256 salePrice) external; function unlist(uint256 tokenId) external; @@ -137,17 +111,17 @@ The `allocatedFR` MUST be calculated using a percentage fixed point with a scali ### Default FR Info -A default `FRInfo` MUST be stored in order to be backward compatible with [EIP-721](./eip-721.md) mint functions. It MAY also have a function to update the `FRInfo`, assuming it has not been hard-coded. +A default `FRInfo` MUST be stored in order to be backward compatible with [ERC-721](./eip-721.md) mint functions. It MAY also have a function to update the `FRInfo`, assuming it has not been hard-coded. -### EIP-721 Overrides +### ERC-721 Overrides -An nFR-compliant smart contract MUST override the [EIP-721](./eip-721.md) `_mint`, `_transfer`, and `_burn` functions. When overriding the `_mint` function, a default FR model is REQUIRED to be established if the mint is to succeed when calling the [EIP-721](./eip-721.md) `_mint` function and not the nFR `_mint` function. It is also to update the owner amount and directly add the recipient address to the FR cycle. When overriding the `_transfer` function, the smart contract SHALL consider the NFT as sold for 0 ETH, and update the state accordingly after a successful transfer. This is to prevent FR circumvention. Additionally, the `_transfer` function SHALL prevent the caller from transferring the token to themselves, this can be done through a require statement that ensures the sender is not the recipient, otherwise, it’d be possible to fill up the FR sequence with one’s own address. Finally, when overriding the `_burn` function, the smart contract SHALL delete the `FRInfo` corresponding to that Token ID after a successful burn. +An nFR-compliant smart contract MUST override the [ERC-721](./eip-721.md) `_mint`, `_transfer`, and `_burn` functions. When overriding the `_mint` function, a default FR model is REQUIRED to be established if the mint is to succeed when calling the [ERC-721](./eip-721.md) `_mint` function and not the nFR `_mint` function. It is also to update the owner amount and directly add the recipient address to the FR cycle. When overriding the `_transfer` function, the smart contract SHALL consider the NFT as sold for 0 ETH, and update the state accordingly after a successful transfer. This is to prevent FR circumvention. Additionally, the `_transfer` function SHALL prevent the caller from transferring the token to themselves or an address that is already in the FR sliding window, this can be done through a require statement that ensures that the sender or an address in the FR sliding window is not the recipient, otherwise, it’d be possible to fill up the FR sequence with one’s own address or duplicate addresses. Finally, when overriding the `_burn` function, the smart contract SHALL delete the `FRInfo` and `ListInfo` corresponding to that Token ID after a successful burn. -Additionally, the [EIP-721](./eip-721.md) `_checkOnERC721Received` function MAY be explicitly called after mints and transfers if the smart contract aims to have safe transfers and mints. +Additionally, the [ERC-721](./eip-721.md) `_checkOnERC721Received` function MAY be explicitly called after mints and transfers if the smart contract aims to have safe transfers and mints. ### Safe Transfers -If the wallet/broker/auction application will accept safe transfers, then it MUST implement the [EIP-721](./eip-721.md) wallet interface. +If the wallet/broker/auction application will accept safe transfers, then it MUST implement the [ERC-721](./eip-721.md) wallet interface. ### Listing, Unlisting, and Buying @@ -169,11 +143,11 @@ function buy(uint256 tokenId) public virtual override payable { ``` -The `list` function accepts a `tokenId` and a `salePrice` and updates the corresponding `ListInfo` for that given `tokenId` after ensuring that the `msg.sender` is either approved or the owner of the token. The function signifies that the token is listed and at what price it is listed for. +The `list` function accepts a `tokenId` and a `salePrice` and updates the corresponding `ListInfo` for that given `tokenId` after ensuring that the `msg.sender` is either approved or the owner of the token. The `list` function SHOULD emit the `Listed` event. The function signifies that the token is listed and at what price it is listed for. -The `unlist` function accepts a `tokenId` and it deletes the corresponding `ListInfo` after the owner verifications have been met. +The `unlist` function accepts a `tokenId` and it deletes the corresponding `ListInfo` after the owner verifications have been met. The `unlist` function SHOULD emit the `Unlisted` event. -The `buy` function accepts a `tokenId` and MUST be payable. It MUST verify that the `msg.value` matches the token’s `salePrice` and that the token is listed, before proceeding and calling the FR `_transferFrom` function. This is to ensure the values are valid and will also allow for the necessary FR to be held in the contract. +The `buy` function accepts a `tokenId` and MUST be payable. It MUST verify that the `msg.value` matches the token’s `salePrice` and that the token is listed, before proceeding and calling the FR `_transferFrom` function. The function MUST also verify that the buyer is not already in the FR sliding window. This is to ensure the values are valid and will also allow for the necessary FR to be held in the contract. The `buy` function SHOULD emit the `Bought` event. ### Future Rewards `_transferFrom` Function @@ -188,7 +162,7 @@ function transferFrom(address from, address to, uint256 tokenId, uint256 soldPri ``` -Based on the stored `lastSoldPrice`, the smart contract will determine whether the sale was profitable after calling the [EIP-721](./eip-721.md) transfer function and transferring the NFT. If it was not profitable, the smart contract SHALL update the last sold price for the corresponding Token ID, increment the owner amount, shift the generations, and transfer all of the `msg.value` to the `lister` depending on the implementation. Otherwise, if the transaction was profitable, the smart contract SHALL call the `_distributeFR` function, then update the `lastSoldPrice`, increment the owner amount, and finally shift generations. The `_distributeFR` function MUST return the difference between the allocated FR that is to be distributed amongst the `_addressesInFR` and the `msg.value` to the `lister`. Once the operations have completed, the function MUST clear the corresponding `ListInfo`. Similarly to the `_transfer` override, the FR `_transferFrom` SHALL ensure that the recipient is not the sender of the token. +Based on the stored `lastSoldPrice`, the smart contract will determine whether the sale was profitable after calling the [ERC-721](./eip-721.md) transfer function and transferring the NFT. If it was not profitable, the smart contract SHALL update the last sold price for the corresponding Token ID, increment the owner amount, shift the generations, and transfer all of the `msg.value` to the `lister` depending on the implementation. Otherwise, if the transaction was profitable, the smart contract SHALL call the `_distributeFR` function, then update the `lastSoldPrice`, increment the owner amount, and finally shift generations. The `_distributeFR` function or the FR `_transferFrom` MUST return the difference between the allocated FR that is to be distributed amongst the `_addressesInFR` and the `msg.value` to the `lister`. Once the operations have completed, the function MUST clear the corresponding `ListInfo`. Similarly to the `_transfer` override, the FR `_transferFrom` SHALL ensure that the recipient is not the sender of the token or an address in the FR sliding window. ### Future Rewards Calculation @@ -236,7 +210,7 @@ function _distributeFR(uint256 tokenId, uint256 soldPrice) internal virtual { ``` -The `_distributeFR` function MUST be called in the FR `transferFrom` function if there is a profitable sale. The function SHALL calculate the difference between the current sale price and the `lastSoldPrice`, then it SHALL call the `_calculateFR` function to receive the proper distribution of FR. Then it SHALL distribute the FR accordingly, making order adjustments as necessary. Then, the contract SHALL calculate the total amount of FR that was distributed (`allocatedFR`), in order to return the difference of the `soldPrice` and `allocatedFR` to the `lister`. Finally, it SHALL emit the `FRDistributed` event. +The `_distributeFR` function MUST be called in the FR `_transferFrom` function if there is a profitable sale. The function SHALL determine the addresses eligible for FR, which would essentially be, excluding the last address in `addressesInFR` in order to prevent any address from paying itself. If the function determines there are no addresses eligible, i.e., it is the first sale, then it SHALL either `return 0` if `_transferFrom` is handling FR payment or send `msg.value` to the `lister`. The function SHALL calculate the difference between the current sale price and the `lastSoldPrice`, then it SHALL call the `_calculateFR` function to receive the proper distribution of FR. Then it SHALL distribute the FR accordingly, making order adjustments as necessary. Then, the contract SHALL calculate the total amount of FR that was distributed (`allocatedFR`), in order to return the difference of the `soldPrice` and `allocatedFR` to the `lister`. Finally, it SHALL emit the `FRDistributed` event. Additionally, the function MAY return the allocated FR, which would be received by the FR `_transferFrom` function, if the `_transferFrom` function is sending the `allocatedFR` to the `lister`. ### Future Rewards Claiming @@ -252,7 +226,7 @@ function releaseFR(address payable account) public virtual override { ### Owner Generation Shifting -The `_shiftGenerations` function MUST be called regardless of whether the sale was profitable or not. As a result, it will be called in the `_transfer` [EIP-721](./eip-721.md) override function and the FR `transferFrom` function. The function SHALL remove the oldest account from the corresponding `_addressesInFR` array. This calculation will take into account the current length of the array versus the total number of generations for a given token ID. +The `_shiftGenerations` function MUST be called regardless of whether the sale was profitable or not. As a result, it will be called in the `_transfer` [ERC-721](./eip-721.md) override function and the FR `transferFrom` function. The function SHALL remove the oldest account from the corresponding `_addressesInFR` array. This calculation will take into account the current length of the array versus the total number of generations for a given token ID. ## Rationale @@ -264,7 +238,7 @@ Considering Fixed-Point Arithmetic is to be enforced, it is logical to have 1e18 Since each NFT contract is independent, and while a marketplace contract can emit events when an item is sold, choosing to emit an event for payment is important. As the royalty and FR recipient may not be aware of/watching for a secondary sale of their NFT, they would never know that they received a payment except that their ETH wallet has been increased randomly. -The recipient of the secondary sale will therefore be able to verify that the payment has been received by calling the parent contract of the NFT being sold, as implemented in [EIP-2981](./eip-2981.md). +The recipient of the secondary sale will therefore be able to verify that the payment has been received by calling the parent contract of the NFT being sold, as implemented in [ERC-2981](./eip-2981.md). ### Number of Generations of All Owners ( n ) vs Number of Generations of Only Profitable Owners @@ -285,6 +259,7 @@ We propose that the marketplace operator can choose between a single generationa FR payouts directly derived from the sale proceeds are immediate and final. As part of the fraud detection detailed later in the Security Considerations section, we selected a method in which the smart contract calculates all the FR amounts for each generation of previous owners, and handles payout according to other criteria set by the marketplace, such as reduced or delayed payments for wallet addresses with low scores, or a series of consecutive orders detected using a time-heuristic analysis. ### Equal vs Linear Reward Distributions + #### Equal FR Payout ![Figure 4: Equal, linear reward distribution](../assets/eip-5173/Total_FR_Payout_Distribution-flat.png?raw=true) @@ -305,7 +280,7 @@ The profit is distributed according to the arithmetic sequence, which is 1, 2, 3 ## Backwards Compatibility -This proposal is fully compatible with current [EIP-721](./eip-721.md) standards and [EIP-2981](./eip-2981.md). It can also be easily adapted to work with [EIP-1155](./eip-1155.md). +This proposal is fully compatible with current [ERC-721](./eip-721.md) standards and [ERC-2981](./eip-2981.md). It can also be easily adapted to work with [ERC-1155](./eip-1155.md). ## Test Cases @@ -313,15 +288,17 @@ This proposal is fully compatible with current [EIP-721](./eip-721.md) standards [Here is a visualization of the test case](../assets/eip-5173/animate-1920x1080-1750-frames.gif?raw=true). +As a result of implementing ERC-5173, a new project has been launched called untrading.org. + ## Reference Implementation -This implementation uses OpenZeppelin contracts and the PRB Math library created by Paul R Berg for fixed-point arithmetic. It demonstrates the interface for the nFR standard, an nFR standard-compliant extension, and an [EIP-721](./eip-721.md) implementation using the extension. +This implementation uses OpenZeppelin contracts and the PRB Math library created by Paul R Berg for fixed-point arithmetic. It demonstrates the interface for the nFR standard, an nFR standard-compliant extension, and an [ERC-721](./eip-721.md) implementation using the extension. The code for the reference implementation is [here](../assets/eip-5173/Implementation/nFRImplementation.sol). ### Distribution of NFT Royalties to Artists and Creators -We agree that artists’ royalties should be uniform and on-chain. We support [EIP-2981](./eip-2981.md) NFT royalty Standard proposal. +We agree that artists’ royalties should be uniform and on-chain. We support [ERC-2981](./eip-2981.md) NFT royalty Standard proposal. All platforms can support royalty rewards for the same NFT based on on-chain parameters and functions: @@ -334,7 +311,7 @@ All platforms can support royalty rewards for the same NFT based on on-chain par #### Future Rewards calculation -Any realized profits (P) when an NFT is sold are distributed among the buyers/owners. The previous owners will take a fixed portion of the profix (P), and this portion is called Future Rewards (FRs). The seller takes the rest of the profits. +Any realized profits (P) when an NFT is sold are distributed among the buyers/owners. The previous owners will take a fixed portion of the profit (P), and this portion is called Future Rewards (FRs). The seller takes the rest of the profits. We define a sliding window mechanism to decide which previous owners will be involved in the profit distribution. Let's imagine the owners as a queue starting from the first hand owner to the current owner. The profit distribution window starts from the previous owner immediately to the current owner and extends towards the first owner, and the size of the windows is fixed. Only previous owners located inside the window will join the profit distribution. @@ -379,23 +356,25 @@ function _calculateFR(uint256 P, uint256 r, uint256 g, uint256 m, uint256 w) pur } ``` + The complete implementation code can be found [here](../assets/eip-5173/Implementation/nFRImplementation.sol). ## Security Considerations ### Payment Attacks -As this EIP introduces royalty and realized profit rewards collection, distribution, and payouts to the EIP-721 standard, the attack vectors increase. As discussed by Andreas Freund regarding mitigations to phishing attacks, we recommend reentrancy protection for all payment functions to reduce the most significant attack vectors for payments and payouts. +As this ERC introduces royalty and realized profit rewards collection, distribution, and payouts to the ERC-721 standard, the attack vectors increase. As discussed by Andreas Freund regarding mitigations to phishing attacks, we recommend reentrancy protection for all payment functions to reduce the most significant attack vectors for payments and payouts. ### Royalty Circumventing -Many methods are being used to avoid paying royalties to creators under the current [EIP-721](./eip-721.md) standard. Through an under-the-table transaction, the new buyer's cost basis will be reduced to zero, increasing their FR liability to the full selling price. Everyone, either the buyer or seller, would pay a portion of the previous owner's net realized profits ( P x r ). Acting in his or her own interests, the buyer rejects any loyalty circumventing proposal. +Many methods are being used to avoid paying royalties to creators under the current [ERC-721](./eip-721.md) standard. Through an under-the-table transaction, the new buyer's cost basis will be reduced to zero, increasing their FR liability to the full selling price. Everyone, either the buyer or seller, would pay a portion of the previous owner's net realized profits ( P x r ). Acting in his or her own interests, the buyer rejects any loyalty circumventing proposal. ### FR Hoarding through Wash Sales Quantexa blog and beincrypto articles have reported widespread wash trading on all unregulated cryptocurrency trading platforms and NFT marketplaces. The use of wash trading by dishonest actors can lead to an unfair advantage, as well as inflated prices and money laundering. When a single entity becomes multiple generations of owners to accumulate more rewards in the future, the validity of the system is undermined. #### Wash trading by users + Using a different wallet address, an attacker can "sell" the NFT to themselves at a loss. It is possible to repeat this process n times in order to maximize their share of the subsequent FR distributions (*Figure 6*). A wallet ranking score can partially alleviate this problem. It is evident that a brand new wallet is a red flag, and the marketplace may withhold FR distribution from it if it has a short transaction history (i.e. fewer than a certain number of transactions). We do not want a large portion of future rewards to go to a small number of wash traders. Making such practices less profitable is one way to discourage wash trading and award hoarding. It can be partially mitigated, for example, by implementing a wallet-score and holding period-based incentive system. The rewards for both parties are reduced if a new wallet is used or if a holding period is less than a certain period. From 451502d0a95d0481782675be44120dcbccac2f56 Mon Sep 17 00:00:00 2001 From: cf Date: Thu, 6 Jul 2023 14:19:02 -0400 Subject: [PATCH 20/41] Update EIP-7015: Add discussed updates Merged by EIP-Bot. --- EIPS/eip-7015.md | 62 +++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/EIPS/eip-7015.md b/EIPS/eip-7015.md index 480ec88d067f5..0dc25b908bd7b 100644 --- a/EIPS/eip-7015.md +++ b/EIPS/eip-7015.md @@ -1,19 +1,19 @@ --- eip: 7015 title: NFT Creator Attribution -description: Extending ERC-721 with cryptographically secured creator attribution. +description: Extending NFTs with cryptographically secured creator attribution. author: Carlos Flores (@strollinghome) discussions-to: https://ethereum-magicians.org/t/eip-authorship-attribution-for-erc721/14244 status: Draft type: Standards Track category: ERC created: 2023-05-11 -requires: 55, 155, 712, 721 +requires: 55, 155, 712, 721, 1155 --- ## Abstract -This Ethereum Improvement Proposal aims to solve the issue of creator attribution for [ERC-721](./eip-721.md) Non-Fungible Tokens (NFT). To achieve this, this EIP proposes a mechanism where the NFT creator signs the required parameters for the NFT creation, including the NFT metadata and a hash of any other relevant information. The signed parameters and the signature are then validated and emitted during the deployment transaction, which allows the NFT to validate the creator and NFT platforms to attribute creatorship correctly. This method ensures that even if a different wallet sends the deployment transaction, the correct account is attributed as the creator. +This Ethereum Improvement Proposal aims to solve the issue of creator attribution for Non-Fungible Token (NFT) standards ([ERC-721](./eip-721.md), [ERC-1155](./eip-1155.md)). To achieve this, this EIP proposes a mechanism where the NFT creator signs the required parameters for the NFT creation, including the NFT metadata in a hash along with any other relevant information. The signed parameters and the signature are then validated and emitted during the deployment transaction, which allows the NFT to validate the creator and NFT platforms to attribute creatorship correctly. This method ensures that even if a different wallet sends the deployment transaction, the correct account is attributed as the creator. ## Motivation @@ -23,7 +23,7 @@ Current NFT platforms assume that the wallet deploying the smart contract is the The keywords “MUST,” “MUST NOT,” “REQUIRED,” “SHALL,” “SHALL NOT,” “SHOULD,” “SHOULD NOT,” “RECOMMENDED,” “MAY,” and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. -ERC-721 compliant contracts MAY implement this NFT Creator Attribution extension to provide a standard event to be emitted that defines the NFT creator at the time of contract creation. +ERC-721 and ERC-1155 compliant contracts MAY implement this NFT Creator Attribution extension to provide a standard event to be emitted that defines the NFT creator at the time of contract creation. This EIP takes advantage of the fact that contract addresses can be precomputed before a contract is deployed. Whether the NFT contract is deployed through another contract (a factory) or through an EOA, the creator can be correctly attributed using this specification. @@ -32,45 +32,36 @@ This EIP takes advantage of the fact that contract addresses can be precomputed Creator consent is given by signing an [EIP-712](./eip-712.md) compatible message; all signatures compliant with this EIP MUST include all fields defined. The struct signed is: ```solidity -struct CreatorAttribution { - string name; - string symbol; - bytes32 salt; - address token; +struct TokenCreation { + bytes32 params; } ``` -Where `name` and `symbol` are the ERC-721 metadata parameters, `salt` is an additional randomness parameter, and `token` is the NFTs address that will be deployed. Any extra parameters used to deploy the NFT contract MAY be hashed into the `salt` parameter. +Where `params` represent the hashed data used to deploy the NFT. **Signature Validation** -Creator attribution is given through a signature verification that MUST be verified by the NFT contract and an event that MUST be emitted by the NFT contract during the deployment transaction. The event includes all the necessary fields for reconstructing the signed digest and validating the signature to ensure it matches the specified creator. The event name is `CreatorAttribution` and includes the following fields: +Creator attribution is given through a signature verification that MUST be verified by the NFT contract being deployed and an event that MUST be emitted by the NFT contract during the deployment transaction. The event includes all the necessary fields for reconstructing the signed digest and validating the signature to ensure it matches the specified creator. The event name is `CreatorAttribution` and includes the following fields: -- `name`: ERC-721Metadata name parameter -- `symbol`: ERC-721Metadata symbol parameter -- `salt`: an extra randomness parameter that encodes other relevant information for deploying the NFT contract +- `params`: hashed information for deploying the NFT contract (e.g. name, symbol, admins etc) - `domainName`: the domain name of the contract verifying the singature (for EIP-712 signature validation) - `version`: the version of the contract verifying the signature (for EIP-712 signature validation) -- `creator`: the account address for the creator of the NFT - `signature`: the creator’s signature The event is defined as follows: ```solidity event CreatorAttribution( - string name, // <------------------------------------- - string symbol, // Parameters signed by creator | - bytes32 salt, // | - string domainName, // <--- EIP-712 Domain Data ------------- - string version, // <------------------------------------- - address creator, // <--- Creator -------------------------- - bytes signature // <--- Creator Signature ---------------- + bytes32 params, + string domainName, + string version, + bytes signature ); ``` -Note that although the `chainId` parameters is necessary for [EIP-712](./eip-712.md) signatures, we omit the parameter from the event as it can be inferred through the transaction data. Similarly, the `token` parameter in the `CreatorAttribution` stuct MUST match the address of the `emitter` field of the event, hence we omit it as well. +Note that although the `chainId` parameters is necessary for [EIP-712](./eip-712.md) signatures, we omit the parameter from the event as it can be inferred through the transaction data. Similarly, the `verifyingContract` parameter for signature verification is omitted since it MUST be the same as the `emitter` field in the transaction. `emitter` MUST be the token. -A platform can verify the validity of the creator attribution by reconstructing the signature digest with the parameters emitted and recovering the signer from the `signature` parameter. The `creator` parameter MUST match the recovered signer from the `signature` parameter. After verification, platforms can safely attribute the `creator` address as the creator instead of the account that submitted the transaction. +A platform can verify the validity of the creator attribution by reconstructing the signature digest with the parameters emitted and recovering the signer from the `signature` parameter. The recovered signer MUST be the `creator` of the token. If `CreatorAttribution` event is present creator attribution MUST be given to the `creator` instead of the account that submitted the transaction. ### Reference Implementation @@ -87,12 +78,9 @@ abstract contract ERC7015 is EIP712 { error Invalid_Signature(); event CreatorAttribution( - string name, - string symbol, - bytes32 salt, + bytes32 params, string domainName, string version, - address creator, bytes signature ); @@ -102,7 +90,7 @@ abstract contract ERC7015 is EIP712 { bytes32 public constant TYPEHASH = keccak256( - "CreatorAttribution(string name,string symbol,bytes32 salt,address token)" + "CreatorAttribution(bytes32 params)" ); constructor() EIP712("ERC7015", "1") {} @@ -110,36 +98,30 @@ abstract contract ERC7015 is EIP712 { function _validateSignature( string memory name, string memory symbol, - bytes32 salt, + bytes32 params, address creator, bytes memory signature ) internal { - if (!_isValid(name, symbol, salt, address(this), creator, signature)) + if (!_isValid(params, creator, signature)) revert Invalid_Signature(); emit CreatorAttribution( - name, - symbol, - salt, + params, "ERC7015", "1", - creator, signature ); } function _isValid( - string memory name, - string memory symbol, - bytes32 salt, - address token, + bytes32 params, address signer, bytes memory signature ) internal view returns (bool) { require(signer != address(0), "cannot validate"); bytes32 digest = _hashTypedDataV4( - keccak256(abi.encode(TYPEHASH, name, symbol, salt, token)) + keccak256(abi.encode(TYPEHASH, params, token)) ); if (signer.code.length != 0) { From 25632370a3bd09eda35820fd428654b801ba98f2 Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 6 Jul 2023 17:27:26 -0300 Subject: [PATCH 21/41] Update EIP-7201: Make Solidity example compile Merged by EIP-Bot. --- EIPS/eip-7201.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-7201.md b/EIPS/eip-7201.md index 0ccfb6b453e1a..53eb95118f5ed 100644 --- a/EIPS/eip-7201.md +++ b/EIPS/eip-7201.md @@ -63,12 +63,14 @@ contract Example { uint256 y; } - bytes32 private immutable MAIN_STORAGE_LOCATION = - keccak256(bytes.concat(uint256(keccak256("example.main")) - 1)); + // keccak256(abi.encode(uint256(keccak256("example.main")) - 1)); + bytes32 private constant MAIN_STORAGE_LOCATION = + 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab5da; function _getMainStorage() private pure returns (MainStorage storage $) { + bytes32 location = MAIN_STORAGE_LOCATION; assembly { - $.slot := MAIN_STORAGE_LOCATION + $.slot := location } } From d1bfeb5143f4a132c9df62307d2cb5d4dc72d0fc Mon Sep 17 00:00:00 2001 From: Francisco Date: Thu, 6 Jul 2023 18:46:22 -0300 Subject: [PATCH 22/41] Update EIP-7201: Simplify Solidity reference implementation Merged by EIP-Bot. --- EIPS/eip-7201.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EIPS/eip-7201.md b/EIPS/eip-7201.md index 53eb95118f5ed..cc6d871d2a785 100644 --- a/EIPS/eip-7201.md +++ b/EIPS/eip-7201.md @@ -68,9 +68,8 @@ contract Example { 0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab5da; function _getMainStorage() private pure returns (MainStorage storage $) { - bytes32 location = MAIN_STORAGE_LOCATION; assembly { - $.slot := location + $.slot := MAIN_STORAGE_LOCATION } } From 6d0dc1f825276e830ec2cedd5f58b29379d95c17 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Fri, 7 Jul 2023 16:44:41 +0200 Subject: [PATCH 23/41] Update EIP-4788: fix typo Merged by EIP-Bot. --- EIPS/eip-4788.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index f66eba05ad494..83f27db77a7b4 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -128,7 +128,7 @@ The suggested gas cost reflects a cold `SLOAD` analogous to the operation(s) per ### Why not repurpose `BLOCKHASH`? The `BLOCKHASH` opcode could be repurposed to provide the beacon root instead of some execution block hash. -To minimize code change, avoid breaking changes to smart contracts, and simplify deployment to mainnet, this EIP suggests leaving `BLOCKHASH` alone and adding a new opcode with the desired semantics. +To minimize code change, avoid breaking changes to smart contracts, and simplify deployment to mainnet, this EIP suggests leaving `BLOCKHASH` alone and adding a new precompile with the desired semantics. ### Beacon block root instead of state root From b075e98daebf9bf355b1c4310d1a440cdf53e684 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 7 Jul 2023 17:02:52 +0200 Subject: [PATCH 24/41] Update EIP-4844: Clarify header fields Merged by EIP-Bot. --- EIPS/eip-4844.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-4844.md b/EIPS/eip-4844.md index 78cb7d452924a..edb161697dcb2 100644 --- a/EIPS/eip-4844.md +++ b/EIPS/eip-4844.md @@ -123,8 +123,10 @@ The signature values `y_parity`, `r`, and `s` are calculated by constructing a s ### Header extension -The current header encoding is extended with a new 64-bit unsigned integer field `data_gas_used` and a 64-bit unsigned integer field `excess_data_gas`. This is the running total of excess data gas consumed on chain since this EIP was activated. If the total amount of data gas is below the -target, `excess_data_gas` is capped at zero. +The current header encoding is extended with two new 64-bit unsigned integer fields: + +- `data_gas_used` is the total amount of data gas consumed by the transactions within the block. +- `excess_data_gas` is a running total of data gas consumed in excess of the target, prior to the block. Blocks with above-target data gas consumption increase this value, blocks with below-target data gas consumption decrease it (bounded at 0). The resulting RLP encoding of the header is therefore: From 43507321d19ef7aae5a03f3129d0b750e8e4b3ef Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 7 Jul 2023 09:37:34 -0600 Subject: [PATCH 25/41] Update EIP-4788: explicitly specify header schema for 4788 Merged by EIP-Bot. --- EIPS/eip-4788.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/EIPS/eip-4788.md b/EIPS/eip-4788.md index 83f27db77a7b4..0afb8f49df824 100644 --- a/EIPS/eip-4788.md +++ b/EIPS/eip-4788.md @@ -44,6 +44,33 @@ To bound the amount of storage this construction consumes, a ring buffer is used Beginning at the execution timestamp `FORK_TIMESTAMP`, execution clients **MUST** extend the header schema with an additional field: the `parent_beacon_block_root`. This root consumes 32 bytes and is exactly the [hash tree root](https://github.com/ethereum/consensus-specs/blob/fa09d896484bbe240334fa21ffaa454bafe5842e/ssz/simple-serialize.md#merkleization) of the parent beacon block for the given execution block. +The resulting RLP encoding of the header is therefore: + +```python +rlp([ + parent_hash, + 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347, # ommers hash + coinbase, + state_root, + txs_root, + receipts_root, + logs_bloom, + 0, # difficulty + number, + gas_limit, + gas_used, + timestamp, + extradata, + prev_randao, + 0x0000000000000000, # nonce + base_fee_per_gas, + withdrawals_root, + data_gas_used, + excess_data_gas, + parent_beacon_block_root, +]) +``` + Validity of the parent beacon block root is guaranteed from the consensus layer, much like how withdrawals are handled. When verifying a block, execution clients **MUST** ensure the root value in the block header matches the one provided by the consensus client. From d333f355327d1038adbd818c77f1b80386c41fc3 Mon Sep 17 00:00:00 2001 From: Francisco Date: Sat, 8 Jul 2023 16:00:11 -0300 Subject: [PATCH 26/41] Update EIP-7201: Add note about Solidity compiler version Merged by EIP-Bot. --- EIPS/eip-7201.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-7201.md b/EIPS/eip-7201.md index cc6d871d2a785..6c9ae4ce440fa 100644 --- a/EIPS/eip-7201.md +++ b/EIPS/eip-7201.md @@ -30,7 +30,7 @@ A _namespace id_ is a string that uniquely identifies a namespace in a contract. The storage location for a namespace is defined as `ns_loc(id: string) = keccak256(uint256(keccak256(id)) - 1)`. -A Solidity contract using namespaced storage can annotate a struct with the NatSpec tag `@custom:storage-location erc7201:` to identify it as a namespace with id ``. +A Solidity contract using namespaced storage can annotate a struct with the NatSpec tag `@custom:storage-location erc7201:` to identify it as a namespace with id ``. _(Note: The Solidity compiler includes this annotation in the AST since v0.8.20, so this is recommended as the minimum compiler version when using this pattern.)_ ## Rationale @@ -54,7 +54,7 @@ No backward compatibility issues found. ## Reference Implementation ```solidity -pragma solidity ^0.8.19; +pragma solidity ^0.8.20; contract Example { /// @custom:storage-location erc7201:example.main From bb55940264223f7063e660687475185aaed8a215 Mon Sep 17 00:00:00 2001 From: Agusx1211 Date: Sat, 8 Jul 2023 22:22:05 +0200 Subject: [PATCH 27/41] Update EIP-6492: Handle non-ready deployed wallet Merged by EIP-Bot. --- EIPS/eip-6492.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/EIPS/eip-6492.md b/EIPS/eip-6492.md index 78d7f4f6fb669..b67ff06c6ea34 100644 --- a/EIPS/eip-6492.md +++ b/EIPS/eip-6492.md @@ -53,6 +53,7 @@ The signing contract will normally be a contract wallet, but it could be any con - If the contract is deployed, produce a normal [ERC-1271](./eip-1271.md) signature - If the contract is not deployed yet, wrap the signature as follows: `concat(abi.encode((create2Factory, factoryCalldata, originalERC1271Signature), (address, bytes, bytes)), magicBytes)` +- If the contract is deployed but not ready to verify using [ERC-1271](./eip-1271.md), wrap the signature as follows: `concat(abi.encode((prepareTo, prepareData, originalERC1271Signature), (address, bytes, bytes)), magicBytes)`; `prepareTo` and `prepareData` must contain the necessary transaction that will make the contract ready to verify using [ERC-1271](./eip-1271.md) (e.g. a call to `migrate` or `update`) Note that we're passing `factoryCalldata` instead of `salt` and `bytecode`. We do this in order to make verification compliant with any factory interface. We do not need to calculate the address based on `create2Factory`/`salt`/`bytecode`, because [ERC-1271](./eip-1271.md) verification presumes we already know the account address we're verifying the signature for. @@ -62,6 +63,7 @@ Full signature verification MUST be performed in the following order: - check if the signature ends with magic bytes, in which case do an `eth_call` to a multicall contract that will call the factory first with the `factoryCalldata` and deploy the contract if it isn't already deployed; Then, call `contract.isValidSignature` as usual with the unwrapped signature - check if there's contract code at the address. If so perform [ERC-1271](./eip-1271.md) verification as usual by invoking `isValidSignature` +- if the [ERC-1271](./eip-1271.md) verification fails, and the deploy call to the `factory` was skipped due to the wallet already having code, execute the `factoryCalldata` transaction and try `isValidSignature` again - if there is no contract code at the address, try `ecrecover` verification ## Rationale @@ -76,6 +78,8 @@ The order to ensure correct verification is based on the following rules: - checking for `magicBytes` MUST happen before `ecrecover` in order to avoid trying to verify a counterfactual contract signature via `ecrecover` if such is clearly identifiable - checking `ecrecover` MUST NOT happen before [ERC-1271](./eip-1271.md) verification, because a contract may use a signature format that also happens to be a valid `ecrecover` signature for an EOA with a different address. One such example is a contract that's a wallet controlled by said EOA. +We can't determine the reason why a signature was encoded with a "deploy prefix" when the corresponding wallet already has code. It could be due to the signature being created before the contract was deployed, or it could be because the contract was deployed but not ready to verify signatures yet. As such, we need to try both options. + ## Backwards Compatibility This ERC is backward compatible with previous work on signature validation, including [ERC-1271](./eip-1271.md) and allows for easy verification of all signature types, including EOA signatures and typed data ([EIP-712](./eip-712.md)). @@ -108,7 +112,8 @@ contract UniversalSigValidator { address _signer, bytes32 _hash, bytes calldata _signature, - bool allowSideEffects + bool allowSideEffects, + bool tryPrepare ) public returns (bool) { uint contractCodeLen = address(_signer).code.length; bytes memory sigToValidate; @@ -122,7 +127,7 @@ contract UniversalSigValidator { bytes memory factoryCalldata; (create2Factory, factoryCalldata, sigToValidate) = abi.decode(_signature[0:_signature.length-32], (address, bytes, bytes)); - if (contractCodeLen == 0) { + if (contractCodeLen == 0 || tryPrepare) { (bool success, bytes memory err) = create2Factory.call(factoryCalldata); if (!success) revert ERC6492DeployFailed(err); } @@ -135,6 +140,11 @@ contract UniversalSigValidator { try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) { bool isValid = magicValue == ERC1271_SUCCESS; + // retry, but this time assume the prefix is a prepare call + if (!isValid && !tryPrepare && contractCodeLen > 0) { + return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); + } + if (contractCodeLen == 0 && isCounterfactual && !allowSideEffects) { // if the call had side effects we need to return the // result using a `revert` (to undo the state changes) @@ -145,7 +155,14 @@ contract UniversalSigValidator { } return isValid; - } catch (bytes memory err) { revert ERC1271Revert(err); } + } catch (bytes memory err) { + // retry, but this time assume the prefix is a prepare call + if (!isValid && !tryPrepare && contractCodeLen > 0) { + return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true); + } + + revert ERC1271Revert(err); + } } // ecrecover verification @@ -162,13 +179,13 @@ contract UniversalSigValidator { function isValidSigWithSideEffects(address _signer, bytes32 _hash, bytes calldata _signature) external returns (bool) { - return this.isValidSigImpl(_signer, _hash, _signature, true); + return this.isValidSigImpl(_signer, _hash, _signature, true, false); } function isValidSig(address _signer, bytes32 _hash, bytes calldata _signature) external returns (bool) { - try this.isValidSigImpl(_signer, _hash, _signature, false) returns (bool isValid) { return isValid; } + try this.isValidSigImpl(_signer, _hash, _signature, false, false) returns (bool isValid) { return isValid; } catch (bytes memory error) { // in order to avoid side effects from the contract getting deployed, the entire call will revert with a single byte result uint len = error.length; From d0252c26f94a89f76da43401467d66e5171bb5b6 Mon Sep 17 00:00:00 2001 From: Gregory Rocco Date: Mon, 10 Jul 2023 16:21:29 -0400 Subject: [PATCH 28/41] Update EIP-4361: Various Updates to EIP-4361 - Sign-In with Ethereum Merged by EIP-Bot. --- EIPS/eip-4361.md | 272 ++++++++++++++++++++++++++++++----------------- 1 file changed, 174 insertions(+), 98 deletions(-) diff --git a/EIPS/eip-4361.md b/EIPS/eip-4361.md index 5b96c43ed2e77..8ee187384a965 100644 --- a/EIPS/eip-4361.md +++ b/EIPS/eip-4361.md @@ -2,7 +2,7 @@ eip: 4361 title: Sign-In with Ethereum description: Off-chain authentication for Ethereum accounts to establish sessions. -author: Wayne Chang (@wyc), Gregory Rocco (@obstropolos), Brantly Millegan (@brantlymillegan), Nick Johnson (@Arachnid) +author: Wayne Chang (@wyc), Gregory Rocco (@obstropolos), Brantly Millegan (@brantlymillegan), Nick Johnson (@Arachnid), Oliver Terbu (@awoie) discussions-to: https://ethereum-magicians.org/t/eip-4361-sign-in-with-ethereum/7263 status: Review type: Standards Track @@ -23,79 +23,26 @@ Already, many services support workflows to authenticate Ethereum accounts using ## Specification -Sign-In with Ethereum works as follows: +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. -1. The wallet presents the user with a structured plaintext message or equivalent interface for signing with the [ERC-191](./eip-191.md) signed data format. Before signing, the `message` is prefixed with `\x19Ethereum Signed Message:\n` as defined in [ERC-191](./eip-191.md). -The `message` MUST incorporate an Ethereum `address`, `domain` requesting the signing, `version` of the message, a chain identifier `chain-id`, `uri` for scoping, `nonce` acceptable to the relying party, and `issued-at` timestamp. -2. The signature is then presented to the relying party, which checks the signature's validity and message content. -3. Additional fields, including `expiration-time`, `not-before`, `request-id`, `statement`, and `resources` MAY be incorporated as part of the sign-in process. -4. The relying party MAY further fetch data associated with the Ethereum address, such as from the Ethereum blockchain (e.g., ENS, account balances, [ERC-20](./eip-20.md)/[ERC-721](./eip-721.md)/[ERC-1155](./eip-1155.md) asset ownership), or other data sources that might or might not be permissioned. +### Overview -### Example message +Sign-In with Ethereum (SIWE) works as follows: -``` -service.invalid wants you to sign in with your Ethereum account: -0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 - -I accept the ServiceOrg Terms of Service: https://service.invalid/tos - -URI: https://service.invalid/login -Version: 1 -Chain ID: 1 -Nonce: 32891756 -Issued At: 2021-09-30T16:25:24Z -Resources: -- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ -- https://example.com/my-web2-claim.json -``` - -### Informal Message Template - -A Bash-like informal template of the full message is presented below for readability and ease of understanding. Field descriptions are provided in the following section. A full ABNF description is provided in the section thereafter. - -``` -${domain} wants you to sign in with your Ethereum account: -${address} - -${statement} - -URI: ${uri} -Version: ${version} -Chain ID: ${chain-id} -Nonce: ${nonce} -Issued At: ${issued-at} -Expiration Time: ${expiration-time} -Not Before: ${not-before} -Request ID: ${request-id} -Resources: -- ${resources[0]} -- ${resources[1]} -... -- ${resources[n]} -``` - -### Message Field Descriptions +1. The relying party generates a SIWE Message and prefixes the SIWE Message with `\x19Ethereum Signed Message:\n` as defined in [ERC-191](./eip-191.md). +2. The wallet presents the user with a structured plaintext message or equivalent interface for signing the SIWE Message with the [ERC-191](./eip-191.md) signed data format. +3. The signature is then presented to the relying party, which checks the signature's validity and SIWE Message content. +4. The relying party might further fetch data associated with the Ethereum address, such as from the Ethereum blockchain (e.g., ENS, account balances, [ERC-20](./eip-20.md)/[ERC-721](./eip-721.md)/[ERC-1155](./eip-1155.md) asset ownership), or other data sources that might or might not be permissioned. -- `domain` is the RFC 3986 authority that is requesting the signing. -- `address` is the Ethereum address performing the signing conformant to capitalization encoded checksum specified in [ERC-55](./eip-55.md) where applicable. -- `statement` (optional) is a human-readable ASCII assertion that the user will sign, and it must not contain `'\n'` (the byte `0x0a`). -- `uri` is an RFC 3986 URI referring to the resource that is the subject of the signing (as in the _subject of a claim_). -- `version` is the current version of the `message`, which MUST be `1` for this specification. -- `chain-id` is the [EIP-155](./eip-155.md) Chain ID to which the session is bound, and the network where Contract Accounts MUST be resolved. -- `nonce` is a randomized token typically chosen by the relying party and used to prevent replay attacks, at least 8 alphanumeric characters. -- `issued-at` is the ISO 8601 datetime string of the current time. -- `expiration-time` (optional) is the ISO 8601 datetime string that, if present, indicates when the signed authentication message is no longer valid. -- `not-before` (optional) is the ISO 8601 datetime string that, if present, indicates when the signed authentication message will become valid. -- `request-id` (optional) is an system-specific identifier that may be used to uniquely refer to the sign-in request. -- `resources` (optional) is a list of information or references to information the user wishes to have resolved as part of authentication by the relying party. They are expressed as RFC 3986 URIs separated by `"\n- "` where `\n` is the byte `0x0a`. +### Message Format -### ABNF Message Format +#### ABNF Message Format -The `message` MUST conform with the following Augmented Backus–Naur Form (ABNF, RFC 5234) expression (note that `%s` denotes case sensitivity for a string term, as per RFC 7405). +A SIWE Message MUST conform with the following Augmented Backus–Naur Form (ABNF, RFC 5234) expression (note that `%s` denotes case sensitivity for a string term, as per RFC 7405). ```abnf sign-in-with-ethereum = - domain %s" wants you to sign in with your Ethereum account:" LF + [ scheme "://" ] domain %s" wants you to sign in with your Ethereum account:" LF address LF LF [ statement LF ] @@ -111,6 +58,10 @@ sign-in-with-ethereum = [ LF %s"Resources:" resources ] +scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + ; See RFC 3986 for the fully contextualized + ; definition of "scheme". + domain = authority ; From RFC 3986: ; authority = [ userinfo "@" ] host [ ":" port ] @@ -153,7 +104,106 @@ resources = *( LF resource ) resource = "- " URI ``` -#### Signing and Verifying with Ethereum Accounts +#### Message Fields + +This specification defines the following SIWE Message fields that can be parsed from a SIWE Message by following the rules in [ABNF Message Format](#abnf-message-format): + +- `scheme` OPTIONAL. The URI scheme of the origin of the request. Its value MUST be a RFC 3986 URI scheme. +- `domain` REQUIRED. The domain that is requesting the signing. Its value MUST be a RFC 3986 authority. The authority includes an OPTIONAL port. If the port is not specified, the default port for the provided `scheme` is assumed (e.g., 443 for HTTPS). If `scheme` is not specified, HTTPS is assumed by default. +- `address` REQUIRED. The Ethereum address performing the signing. Its value SHOULD be conformant to mixed-case checksum address encoding specified in [ERC-55](./eip-55.md) where applicable. +- `statement` OPTIONAL. A human-readable ASCII assertion that the user will sign which MUST NOT include `'\n'` (the byte `0x0a`). +- `uri` REQUIRED. An RFC 3986 URI referring to the resource that is the subject of the signing (as in the _subject of a claim_). +- `version` REQUIRED. The current version of the SIWE Message, which MUST be `1` for this specification. +- `chain-id` REQUIRED. The [EIP-155](./eip-155.md) Chain ID to which the session is bound, and the network where Contract Accounts MUST be resolved. +- `nonce` REQUIRED. A random string typically chosen by the relying party and used to prevent replay attacks, at least 8 alphanumeric characters. +- `issued-at` REQUIRED. The time when the message was generated, typically the current time. Its value MUST be an ISO 8601 datetime string. +- `expiration-time` OPTIONAL. The time when the signed authentication message is no longer valid. Its value MUST be an ISO 8601 datetime string. +- `not-before` OPTIONAL. The time when the signed authentication message will become valid. Its value MUST be an ISO 8601 datetime string. +- `request-id` OPTIONAL. An system-specific identifier that MAY be used to uniquely refer to the sign-in request. +- `resources` OPTIONAL. A list of information or references to information the user wishes to have resolved as part of authentication by the relying party. Every resource MUST be a RFC 3986 URI separated by `"\n- "` where `\n` is the byte `0x0a`. + +#### Informal Message Template + +A Bash-like informal template of the full SIWE Message is presented below for readability and ease of understanding, and it does not reflect the allowed optionality of the fields. Field descriptions are provided in the following section. A full ABNF description is provided in [ABNF Message Format](#abnf-message-format). + +``` +${scheme}:// ${domain} wants you to sign in with your Ethereum account: +${address} + +${statement} + +URI: ${uri} +Version: ${version} +Chain ID: ${chain-id} +Nonce: ${nonce} +Issued At: ${issued-at} +Expiration Time: ${expiration-time} +Not Before: ${not-before} +Request ID: ${request-id} +Resources: +- ${resources[0]} +- ${resources[1]} +... +- ${resources[n]} +``` + +#### Examples + +The following is an example SIWE Message with an implicit scheme: + +``` +example.com wants you to sign in with your Ethereum account: +0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/login +Version: 1 +Chain ID: 1 +Nonce: 32891756 +Issued At: 2021-09-30T16:25:24Z +Resources: +- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ +- https://example.com/my-web2-claim.json +``` + +The following is an example SIWE Message with an implicit scheme and explicit port: + +``` +example.com:3388 wants you to sign in with your Ethereum account: +0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/login +Version: 1 +Chain ID: 1 +Nonce: 32891756 +Issued At: 2021-09-30T16:25:24Z +Resources: +- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ +- https://example.com/my-web2-claim.json +``` + +The following is an example SIWE Message with an explicit scheme: + +``` +https://example.com wants you to sign in with your Ethereum account: +0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 + +I accept the ExampleOrg Terms of Service: https://example.com/tos + +URI: https://example.com/login +Version: 1 +Chain ID: 1 +Nonce: 32891756 +Issued At: 2021-09-30T16:25:24Z +Resources: +- ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ +- https://example.com/my-web2-claim.json +``` + +### Signing and Verifying Messages with Ethereum Accounts - For Externally Owned Accounts (EOAs), the verification method specified in [ERC-191](./eip-191.md) MUST be used. @@ -172,38 +222,65 @@ resource = "- " URI ### Relying Party Implementer Steps -#### Verifying a signed `message` +#### Specifying the Request Origin -- The message MUST be checked for conformance to the ABNF above, checked against expected term values after parsing, and its signature MUST be verified. +- The `domain` and, if present, the `scheme`, in the SIWE Message MUST correspond to the origin from where the signing request was made. For instance, if the signing request is made within a cross-origin iframe embedded in a parent browser window, the `domain` (and, if present, the `scheme`) have to match the origin of the iframe, rather than the origin of the parent. This is crucial to prevent the iframe from falsely asserting the origin of one of its ancestor windows for security reasons. This behavior is enforced by conforming wallets. -#### Creating sessions +#### Verifying a signed Message + +- The SIWE Message MUST be checked for conformance to the ABNF Message Format in the previous sections, checked against expected values after parsing (e.g., `expiration-time`, `nonce`, `request-uri` etc.), and its signature MUST be checked as defined in [Signing and Verifying Messages with Ethereum Accounts](#signing-and-verifying-messages-with-ethereum-accounts). + +#### Creating Sessions - Sessions MUST be bound to the `address` and not to further resolved resources that can change. -#### Interpreting and resolving `resources` +#### Interpreting and resolving Resources -- The listed `resources` MUST be RFC 3986 URIs, but their interpretation is out of scope of this specification. -- Implementers SHOULD ensure that that URIs are human-friendly when expressed in plaintext form. +- Implementers SHOULD ensure that that URIs in the listed `resources` are human-friendly when expressed in plaintext form. +- The intepretation of the listed `resources` in the SIWE Message is out of scope of this specification. ### Wallet Implementer Steps -#### Verifying `message` +#### Verifying the Message Format -- The full `message` MUST be checked for conformance to the ABNF above. +- The full SIWE message MUST be checked for conformance to the ABNF defined in [ABNF Message Format](#abnf-message-format). - Wallet implementers SHOULD warn users if the substring `"wants you to sign in with your Ethereum account"` appears anywhere in an [ERC-191](./eip-191.md) message signing - request unless the message fully conforms to the format defined in this specification. + request unless the message fully conforms to the format defined [ABNF Message Format](#abnf-message-format). + +#### Verifying the Request Origin + +- Wallet implementers MUST prevent phishing attacks by verifying the origin of the request against the `scheme` and `domain` fields in the SIWE Message. For example, when processing the SIWE message beginning with `"example.com wants you to sign in..."`, the wallet checks that the request actually originated from `https://example.com`. +- The origin SHOULD be read from a trusted data source such as the browser window or over WalletConnect ([ERC-1328](./eip-1328.md)) sessions for comparison against the signing message contents. +- Wallet implementers MAY warn instead of rejecting the verification if the origin is pointing to localhost. + +The following is a RECOMMENDED algorithm for Wallets to conform with the requirements on request origin verification defined by this specification. + +The algorithm takes the following input variables: + +- fields from the SIWE message. +- `origin` of the signing request - in the case of a browser wallet implementation - the origin of the page which requested the signin via the provider. +- `allowedSchemes` - a list of schemes allowed by the Wallet. +- `defaultScheme` - a scheme to assume when none was provided. Wallet implementers in the browser SHOULD use `https`. +- developer mode indication - a setting deciding if certain risks should be a warning instead of rejection. Can be manually configured or derived from `origin` being localhost. -#### Verifying `domain` binding +The algorithm is described as follows: -- Wallet implementers MUST prevent phishing attacks by matching on the `domain` term when processing a signing request. For example, when processing the message beginning with `"service.invalid wants you to sign in..."`, the wallet checks that the request actually originated from `service.invalid`. -- The domain SHOULD be read from a trusted data source such as the browser window or over WalletConnect ([ERC-1328](./eip-1328.md)) sessions for comparison against the signing message contents. +- If `scheme` was not provided, then assign `defaultScheme` as `scheme`. +- If `scheme` is not contained in `allowedSchemes`, then the `scheme` is not expected and the Wallet MUST reject the request. Wallet implementers in the browser SHOULD limit the list of `allowedSchemes` to just `'https'` unless a developer mode is activated. +- If `scheme` does not match the scheme of `origin`, the Wallet SHOULD reject the request. Wallet implementers MAY show a warning instead of rejecting the request if a developer mode is activated. In that case the Wallet continues processing the request. +- If the `host` part of the `domain` and `origin` do not match, the Wallet MUST reject the request unless the Wallet is in developer mode. In developer mode the Wallet MAY show a warning instead and continues procesing the request. +- If `domain` and `origin` have mismatching subdomains, the Wallet SHOULD reject the request unless the Wallet is in developer mode. In developer mode the Wallet MAY show a warning instead and continues procesing the request. +- Let `port` be the port component of `domain`, and if no port is contained in `domain`, assign `port` the default port specified for the `scheme`. +- If `port` is not empty, then the Wallet SHOULD show a warning if the `port` does not match the port of `origin`. +- If `port` is empty, then the Wallet MAY show a warning if `origin` contains a specific port. (Note 'https' has a default port of 443 so this only applies if `allowedSchemes` contain unusual schemes) +- Return request origin verification completed. -#### Creating Sign-In with Ethereum interfaces +#### Creating Sign-In with Ethereum Interfaces -- Wallet implementers MUST display to the user the following terms from the Sign-In with Ethereum signing request by default and prior to signing, if they are present: `domain`, `address`, `statement`, and `resources`. Other present terms MUST also be made available to the user prior to signing either by default or through an extended interface. -- Wallet implementers displaying a plaintext `message` to the user SHOULD require the user to scroll to the bottom of the text area prior to signing. -- Wallet implementers MAY construct a custom Sign-In With Ethereum user interface by parsing the ABNF terms into data elements for use in the interface. The display rules above still apply to custom interfaces. +- Wallet implementers MUST display to the user the following fields from the SIWE Message request by default and prior to signing, if they are present: `scheme`, `domain`, `address`, `statement`, and `resources`. Other present fields MUST also be made available to the user prior to signing either by default or through an extended interface. +- Wallet implementers displaying a plaintext SIWE Message to the user SHOULD require the user to scroll to the bottom of the text area prior to signing. +- Wallet implementers MAY construct a custom SIWE user interface by parsing the ABNF terms into data elements for use in the interface. The display rules above still apply to custom interfaces. #### Supporting internationalization (i18n) @@ -247,7 +324,7 @@ The following concerns are out of scope for this version of the specification to - Additional authentication not based on Ethereum addresses. - Authorization to server resources. -- Interpretation of the URIs in the `resources` term as claims or other resources. +- Interpretation of the URIs in the `resources` field as claims or other resources. - The specific mechanisms to ensure domain-binding. - The specific mechanisms to generate nonces and evaluation of their appropriateness. - Protocols for use without TLS connections. @@ -269,55 +346,54 @@ The following items are considered for future support in either through an itera ## Reference Implementation -A reference implementation is available [here](../assets/eip-4361/example.js). +A reference implementation is available [here](../assets/eip-4361/example.js). ## Security Considerations -### Identifier reuse +### Identifier Reuse - Towards perfect privacy, it would be ideal to use a new uncorrelated identifier (e.g., Ethereum address) per digital interaction, selectively disclosing the information required and no more. - This concern is less relevant to certain user demographics who are likely to be early adopters of this specification, such as those who manage an Ethereum address and/or ENS names intentionally associated with their public presence. These users often prefer identifier reuse to maintain a single correlated identity across many services. - This consideration will become increasingly important with mainstream adoption. There are several ways to move towards this model, such as using HD wallets, signed delegations, and zero-knowledge proofs. However, these approaches are out of scope for this specification and better suited for follow-on specifications. -### Key management +### Key Management - Sign-In with Ethereum gives users control through their keys. This is additional responsibility that mainstream users may not be accustomed to accepting, and key management is a hard problem especially for individuals. For example, there is no "forgot password" button as centralized identity providers commonly implement. - Early adopters of this specification are likely to be already adept at key management, so this consideration becomes more relevant with mainstream adoption. - Certain wallets can use smart contracts and multisigs to provide an enhanced user experiences with respect to key usage and key recovery, and these can be supported via [ERC-1271](./eip-1271.md) signing. -### Wallet and relying party combined security +### Wallet and Relying Party combined Security -- Both the wallet and relying party have to implement this specification for improved security to the end user. Specifically, the wallet MUST confirm that the message is for the correct `domain` or provide the user means to do so manually (such as instructions to visually confirming the correct domain in a TLS-protected website prior to connecting via QR code or deeplink), otherwise the user is subject to phishing attacks. +- Both the wallet and relying party have to implement this specification for improved security to the end user. Specifically, the wallet has to confirm that the SIWE Message is for the correct request origin or provide the user means to do so manually (such as instructions to visually confirming the correct domain in a TLS-protected website prior to connecting via QR code or deeplink), otherwise the user is subject to phishing attacks. -### Minimizing wallet and server interaction +### Minimizing Wallet and Server Interaction -- In some implementions of wallet sign-in workflows, the server first sends parameters of the `message` to the wallet. Others generate the message for signing entirely in the client side (e.g., dapps). The latter approach without initial server interaction SHOULD be preferred when there is a user privacy advantage by minimizing wallet-server interaction. Often, the backend server first produces a `nonce` to prevent replay attacks, which it verifies after signing. Privacy-preserving alternatives are suggested in the next section on preventing replay attacks. -- Before the wallet presents the message signing request to the user, it MAY consult the server for the proper contents of the message to be signed, such as an acceptable `nonce` or requested set of `resources`. When communicating to the server, the wallet SHOULD take precautions to protect user privacy by mitigating user information revealed as much as possible. +- In some implementions of wallet sign-in workflows, the server first sends parameters of the SIWE Message to the wallet. Others generate the SIWE message for signing entirely in the client side (e.g., dapps). The latter approach without initial server interaction SHOULD be preferred when there is a user privacy advantage by minimizing wallet-server interaction. Often, the backend server first produces a `nonce` to prevent replay attacks, which it verifies after signing. Privacy-preserving alternatives are suggested in the next section on preventing replay attacks. +- Before the wallet presents the SIWE message signing request to the user, it MAY consult the server for the proper contents of the message to be signed, such as an acceptable `nonce` or requested set of `resources`. When communicating to the server, the wallet SHOULD take precautions to protect user privacy by mitigating user information revealed as much as possible. - Prior to signing, the wallet MAY consult the user for preferences, such as the selection of one `address` out of many, or a preferred ENS name out of many. -### Preventing replay attacks +### Preventing Replay Attacks - A `nonce` SHOULD be selected per session initiation with enough entropy to prevent replay attacks, a man-in-the-middle attack in which an attacker is able to capture the user's signature and resend it to establish a new session for themselves. - Implementers MAY consider using privacy-preserving yet widely-available `nonce` values, such as one derived from a recent Ethereum block hash or a recent Unix timestamp. -### Verification of domain binding +### Preventing Phishing Attacks -- Wallets MUST check that the `domain` matches the actual signing request source. -- This value SHOULD be checked against a trusted data source such as the browser window or over another protocol. +- To prevent phishing attacks Wallets have to implement request origin verification as described in [Verifying the Request Origin](#verifying-the-request-origin). -### Channel security +### Channel Security - For web-based applications, all communications SHOULD use HTTPS to prevent man-in-the-middle attacks on the message signing. - When using protocols other than HTTPS, all communications SHOULD be protected with proper techniques to maintain confidentiality, data integrity, and sender/receiver authenticity. -### Session invalidation +### Session Invalidation There are several cases where an implementer SHOULD check for state changes as they relate to sessions. - If an [ERC-1271](./eip-1271.md) implementation or dependent data changes the signature computation, the server SHOULD invalidate sessions appropriately. - If any resources specified in `resources` change, the server SHOULD invalidate sessions appropriately. However, the interpretation of `resources` is out of scope of this specification. -### Maximum lengths for ABNF terms +### Maximum Lengths for ABNF Terms - While this specification does not contain normative requirements around maximum string lengths, implementers SHOULD choose maximum lengths for terms that strike a balance across the prevention of denial of service attacks, support for arbitrary use cases, and user readability. From bd168d370b1298c123fe7442f8563075b4160cea Mon Sep 17 00:00:00 2001 From: Manu NALEPA Date: Mon, 10 Jul 2023 23:13:12 +0200 Subject: [PATCH 29/41] Update EIP-3076: Fix HTML list issue Merged by EIP-Bot. --- EIPS/eip-3076.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/EIPS/eip-3076.md b/EIPS/eip-3076.md index 47075b8fa6c80..24b8245676872 100644 --- a/EIPS/eip-3076.md +++ b/EIPS/eip-3076.md @@ -176,13 +176,14 @@ After importing an interchange file with data field `data`, a signer must respec 4. Refuse to sign any attestation with source epoch less than the minimum source epoch present in that signer's attestations (as seen in `data.signed_attestations`). In pseudocode: - ```python3 - source.epoch < - min(att.source_epoch - for att in data.signed_attestations - if att.pubkey == attester_pubkey) - ``` +```python3 +source.epoch < + min(att.source_epoch + for att in data.signed_attestations + if att.pubkey == attester_pubkey) +``` +{:start="5"} 5. Refuse to sign any attestation with target epoch less than or equal to the minimum target epoch present in that signer's attestations (as seen in `data.signed_attestations`), except if it is a repeat signing as determined by the `signing_root`. In pseudocode: ```python3 From 5d3b7219f83d22beb49f2f04731b7238f7edcde2 Mon Sep 17 00:00:00 2001 From: Yamen Merhi Date: Tue, 11 Jul 2023 12:27:08 +0300 Subject: [PATCH 30/41] Update EIP-725: Remove overloading from ERC725 Merged by EIP-Bot. --- EIPS/eip-725.md | 77 +++++++++++---------------------------- assets/eip-725/ERC725.sol | 16 ++++---- 2 files changed, 29 insertions(+), 64 deletions(-) diff --git a/EIPS/eip-725.md b/EIPS/eip-725.md index 08452e2464d57..64d68734b8907 100644 --- a/EIPS/eip-725.md +++ b/EIPS/eip-725.md @@ -28,7 +28,7 @@ These standards (`ERC725` X and Y) can also be used separately as `ERC725Y` can ### Ownership This contract is controlled by a single owner. The owner can be a smart contract or an external account. -This standard requires [EIP-173](./eip-173.md) and SHOULD implement the functions: +This standard requires [ERC-173](./eip-173.md) and SHOULD implement the functions: - `owner() view` - `transferOwnership(address newOwner)` @@ -41,9 +41,9 @@ And the event: ### `ERC725X` -**`ERC725X`** interface id according to [EIP-165](./eip-165.md): `0x570ef073`. +**`ERC725X`** interface id according to [ERC-165](./eip-165.md): `0x7545acac`. -Smart contracts implementing the `ERC725X` standard MUST implement the [EIP-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725X` interface ids. +Smart contracts implementing the `ERC725X` standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725X` interface ids. ### `ERC725X` Methods @@ -106,13 +106,13 @@ data = + + See [EIP-1014: Skinny CREATE2](./eip-1014.md) for more information. -#### execute (Array) +#### executeBatch ```solidity -function execute(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas) external payable returns(bytes[] memory) +function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes[] memory datas) external payable returns(bytes[] memory) ``` -Function Selector: `0x13ced88d` +Function Selector: `0x31858452` Executes a batch of calls on any other smart contracts, transfers the blockchain native token, or deploys a new smart contract. @@ -135,25 +135,6 @@ _Returns:_ `bytes[]` , array list of returned data of the called function, or th **Triggers Event:** [ContractCreated](#contractcreated), [Executed](#executed) on each call iteration - -**Note:** The `execute()` functions use function overloading, therefore it is better to reference them by the given function signature as follows: - -```js -// web3.js example - -// execute -myContract.methods['execute(uint256,address,uint256,bytes)'](OPERATION_CALL, target.address, 2WEI, "0x").send(); -// execute Array -myContract.methods['execute(uint256[],address[],uint256[],bytes[])']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send(); - -// OR - -// execute -myContract.methods['0x44c028fe'](OPERATION_CALL, target.address, 2WEI, "0x").send(); -// execute Array -myContract.methods['0x13ced88d']([OPERATION_CALL, OPERATION_CREATE], [target.address, ZERO_ADDRESS], [2WEI, 0WEI], ["0x", CONTRACT_BYTECODE]).send(); -``` - ### `ERC725X` Events #### Executed @@ -176,9 +157,9 @@ MUST be triggered when `execute` creates a new contract using the `operationType ### `ERC725Y` -**`ERC725Y`** interface id according to [EIP-165](./eip-165.md): `0x714df77c`. +**`ERC725Y`** interface id according to [ERC-165](./eip-165.md): `0x629aa694`. -Smart contracts implementing the `ERC725Y` standard MUST implement the [EIP-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725Y` interface ids. +Smart contracts implementing the `ERC725Y` standard MUST implement the [ERC-165](./eip-165.md) `supportsInterface(..)` function and MUST support the `ERC165` and `ERC725Y` interface ids. ### `ERC725Y` Methods @@ -200,13 +181,13 @@ _Parameters:_ _Returns:_ `bytes` , The data for the requested data key. -#### getData (Array) +#### getDataBatch ```solidity -function getData(bytes32[] memory dataKeys) external view returns(bytes[] memory) +function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory) ``` -Function Selector: `0x4e3e6e9c` +Function Selector: `0xdedff9c6` Gets array of data at multiple given data keys. @@ -237,13 +218,13 @@ _Requirements:_ **Triggers Event:** [DataChanged](#datachanged) -#### setData (Array) +#### setDataBatch ```solidity -function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) external +function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external ``` -Function Selector: `0x14a6e293` +Function Selector: `0x97902421` Sets array of data at multiple data keys. MUST only be called by the current owner of the contract. @@ -259,24 +240,6 @@ _Requirements:_ **Triggers Event:** [DataChanged](#datachanged) -**Note:** `setData()` and `getData()` uses function overloading, therefore it is better to reference them by the given function signature as follows: - -```js -// web3.js example - -// setData -myContract.methods['setData(bytes32,bytes)'](dataKey, dataValue).send(); -// setData Array -myContract.methods['setData(bytes32[],bytes[])']([dataKeys, ...], [dataValues, ...]).send(); - -// OR - -// setData -myContract.methods['0x7f23690c'](dataKey, dataValue).send(); -// setData Array -myContract.methods['0x14a6e293']([dataKeys, ...], [dataValues, ...]).send(); -``` - ### `ERC725Y` Events #### DataChanged @@ -304,6 +267,8 @@ The data stored in an `ERC725Y` smart contract is not only readable/writable by All contracts since `ERC725v2` from 2018/19 should be compatible with the current version of the standard. Mainly interface ID and Event parameters have changed, while `getData(bytes32[])` and `setData(bytes32[], bytes[])` was added as an efficient way to set/get multiple keys at once. The same applies to execution, as `execute(..[])` was added as an efficient way to batch calls. +From 2023 onward, overloading was removed from `ERC-725` (including `ERC725-X` and `ERC725-Y`). This is because, while overloading is accommodated in Solidity, it isn't broadly supported across most blockchain languages. In order to make the standard language-independent, it was decided to shift from overloading to simply attach the term "Batch" to the functions that accept an array as parameters. + ## Reference Implementation Reference implementations can be found in [`ERC725.sol`](../assets/eip-725/ERC725.sol). @@ -321,7 +286,7 @@ When using the operation type `4` for `delegatecall`, it is important to conside pragma solidity >=0.5.0 <0.7.0; -// ERC165 identifier: `0x570ef073` +// ERC165 identifier: `0x7545acac` interface IERC725X /* is ERC165, ERC173 */ { event Executed(uint256 indexed operationType, address indexed target, uint256 indexed value, bytes4 data); @@ -330,19 +295,19 @@ interface IERC725X /* is ERC165, ERC173 */ { function execute(uint256 operationType, address target, uint256 value, bytes memory data) external payable returns(bytes memory); - function execute(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes memory datas) external payable returns(bytes[] memory); + function executeBatch(uint256[] memory operationsType, address[] memory targets, uint256[] memory values, bytes memory datas) external payable returns(bytes[] memory); } -// ERC165 identifier: `0x714df77c` +// ERC165 identifier: `0x629aa694` interface IERC725Y /* is ERC165, ERC173 */ { event DataChanged(bytes32 indexed dataKey, bytes dataValue); function getData(bytes32 dataKey) external view returns(bytes memory); - function getData(bytes32[] memory dataKeys) external view returns(bytes[] memory); + function getDataBatch(bytes32[] memory dataKeys) external view returns(bytes[] memory); function setData(bytes32 dataKey, bytes memory dataValue) external; - function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) external; + function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external; } interface IERC725 /* is IERC725X, IERC725Y */ { } diff --git a/assets/eip-725/ERC725.sol b/assets/eip-725/ERC725.sol index fbab76f8ef12f..1d760cfe63273 100644 --- a/assets/eip-725/ERC725.sol +++ b/assets/eip-725/ERC725.sol @@ -178,7 +178,7 @@ interface IERC725X is IERC165 { * Emits an {Executed} event, when a call is made with `operationType` 0 (CALL), 3 (STATICCALL) or 4 (DELEGATECALL) * Emits a {ContractCreated} event, when deploying a contract with `operationType` 1 (CREATE) or 2 (CREATE2) */ - function execute( + function executeBatch( uint256[] memory operationsType, address[] memory targets, uint256[] memory values, @@ -187,7 +187,7 @@ interface IERC725X is IERC165 { } // ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725X = 0x570ef073; +bytes4 constant _INTERFACEID_ERC725X = 0x7545acac; // ERC725X OPERATION TYPES uint256 constant OPERATION_0_CALL = 0; @@ -278,7 +278,7 @@ contract ERC725X is ERC173, ERC165, IERC725X { /** * @inheritdoc IERC725X */ - function execute( + function executeBatch( uint256[] memory operationsType, address[] memory targets, uint256[] memory values, @@ -536,7 +536,7 @@ interface IERC725Y is IERC165 { * @param dataKeys The array of keys which values to retrieve * @return dataValues The array of data stored at multiple keys */ - function getData(bytes32[] memory dataKeys) + function getDataBatch(bytes32[] memory dataKeys) external view returns (bytes[] memory dataValues); @@ -559,12 +559,12 @@ interface IERC725Y is IERC165 { * * Emits a {DataChanged} event. */ - function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) + function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) external; } // ERC165 INTERFACE IDs -bytes4 constant _INTERFACEID_ERC725Y = 0x714df77c; +bytes4 constant _INTERFACEID_ERC725Y = 0x629aa694; /** * @dev reverts when there is not the same number of elements in the lists of data keys and data values @@ -615,7 +615,7 @@ contract ERC725Y is ERC173, ERC165, IERC725Y { /** * @inheritdoc IERC725Y */ - function getData(bytes32[] memory dataKeys) + function getDataBatch(bytes32[] memory dataKeys) public view virtual @@ -644,7 +644,7 @@ contract ERC725Y is ERC173, ERC165, IERC725Y { /** * @inheritdoc IERC725Y */ - function setData(bytes32[] memory dataKeys, bytes[] memory dataValues) + function setDataBatch(bytes32[] memory dataKeys, bytes[] memory dataValues) public virtual onlyOwner From 4f3af2acae72bc7117956cb98663de70856086d4 Mon Sep 17 00:00:00 2001 From: Joachim Date: Tue, 11 Jul 2023 16:02:42 +0200 Subject: [PATCH 31/41] Update EIP-3643: Move to Review Merged by EIP-Bot. --- EIPS/eip-3643.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-3643.md b/EIPS/eip-3643.md index 5e8ee930f6b5f..68efd27186b54 100644 --- a/EIPS/eip-3643.md +++ b/EIPS/eip-3643.md @@ -4,7 +4,7 @@ title: T-REX - Token for Regulated EXchanges description: An institutional grade security token contract that provides interfaces for the management and compliant transfer of security tokens. author: Joachim Lebrun (@Joachim-Lebrun), Tony Malghem (@TonyMalghem), Kevin Thizy (@Nakasar), Luc Falempin (@lfalempin), Adam Boudjemaa (@Aboudjem) discussions-to: https://ethereum-magicians.org/t/eip-3643-proposition-of-the-t-rex-token-standard-for-securities/6844 -status: Draft +status: Review type: Standards Track category: ERC created: 2021-07-09 From a8911f9604683d1e1c22d75ff5b1d892feda4ae3 Mon Sep 17 00:00:00 2001 From: boyuanx Date: Tue, 11 Jul 2023 22:09:57 +0800 Subject: [PATCH 32/41] Update EIP-6066: Move to Final Merged by EIP-Bot. --- EIPS/eip-6066.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EIPS/eip-6066.md b/EIPS/eip-6066.md index 59cd0ae54781a..0b72c77d6374a 100644 --- a/EIPS/eip-6066.md +++ b/EIPS/eip-6066.md @@ -4,8 +4,7 @@ title: Signature Validation Method for NFTs description: A way to verify signatures when the signing entity is an ERC-721 or ERC-1155 NFT author: Jack Boyuan Xu (@boyuanx) discussions-to: https://ethereum-magicians.org/t/eip-6066-signature-validation-method-for-nfts/11943 -status: Last Call -last-call-deadline: 2023-07-10 +status: Final type: Standards Track category: ERC created: 2022-11-29 From 52852e51d6a0f57731c175225fb7a9e08f3dc28a Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Tue, 11 Jul 2023 10:11:42 -0400 Subject: [PATCH 33/41] Update EIP-5202: Move to Final Merged by EIP-Bot. --- EIPS/eip-5202.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EIPS/eip-5202.md b/EIPS/eip-5202.md index bfd327a2f974b..b504c4e281a88 100644 --- a/EIPS/eip-5202.md +++ b/EIPS/eip-5202.md @@ -4,8 +4,7 @@ title: Blueprint contract format description: Define a bytecode container format for indexing and utilizing blueprint contracts author: Charles Cooper (@charles-cooper), Edward Amor (@skellet0r) discussions-to: https://ethereum-magicians.org/t/erc-5202-standard-factory-contract-format/9851 -status: Last Call -last-call-deadline: 2023-06-01 +status: Final type: Standards Track category: ERC created: 2022-06-23 From 4ca0d663ef0aec3da02678cf556b23f6949b2d3e Mon Sep 17 00:00:00 2001 From: Jessica Chang <117890123+JessicaChg@users.noreply.github.com> Date: Tue, 11 Jul 2023 22:12:41 +0800 Subject: [PATCH 34/41] Update EIP-6239: Move to Final Merged by EIP-Bot. --- EIPS/eip-6239.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/EIPS/eip-6239.md b/EIPS/eip-6239.md index 11195e11254b6..cb06821a920f5 100644 --- a/EIPS/eip-6239.md +++ b/EIPS/eip-6239.md @@ -4,8 +4,7 @@ title: Semantic Soulbound Tokens description: Adding RDF triples to ERC-5192 token metadata to capture social meaning author: Jessica Chang (@JessicaChg) discussions-to: https://ethereum-magicians.org/t/eip-6239-semantic-soulbound-tokens/12334 -status: Last Call -last-call-deadline: 2023-07-06 +status: Final type: Standards Track category: ERC created: 2022-12-30 From 0e6a9c936c58972d64dcb614fbd7af8ffa04eedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ula=C5=9F=20Erdo=C4=9Fan?= <58347304+ulerdogan@users.noreply.github.com> Date: Tue, 11 Jul 2023 18:11:02 +0300 Subject: [PATCH 35/41] Update EIP-7212: Move to Review Merged by EIP-Bot. --- EIPS/eip-7212.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/EIPS/eip-7212.md b/EIPS/eip-7212.md index bbd3300f72225..d6f83e832d153 100644 --- a/EIPS/eip-7212.md +++ b/EIPS/eip-7212.md @@ -4,7 +4,7 @@ title: Precompiled for secp256r1 Curve Support description: Proposal to add precompiled contract that allows signature verifications in the “secp256r1” elliptic curve. author: Ulaş Erdoğan (@ulerdogan), Doğan Alpaslan (@doganalpaslan) discussions-to: https://ethereum-magicians.org/t/eip-7212-precompiled-for-secp256r1-curve-support/14789 -status: Draft +status: Review type: Standards Track category: Core created: 2023-06-22 @@ -12,7 +12,7 @@ created: 2023-06-22 ## Abstract -The proposal aims creating precompiled contract that allows signature verifications in the “secp256r1” elliptic curve by given parameters of message hash, `r` - `s` components of the signature, and `x` - `y` coordinates of the public key. So that, any EVM chain -principally Ethereum rollups- will be able to integrate this precompiled contract easily. +This proposal creates a precompiled contract that allows signature verifications in the “secp256r1” elliptic curve by given parameters of message hash, `r` - `s` components of the signature, and `x` - `y` coordinates of the public key. So that, any EVM chain -principally Ethereum rollups- will be able to integrate this precompiled contract easily. ## Motivation @@ -28,7 +28,7 @@ Modern devices have these signing mechanisms that are designed to be more secure ## Specification -As of `FORK_TIMESTAMP` in the integrated EVM chain, add precompiled contract `P256VERIFY` for signature verifications in the “secp256r1” elliptic curve at address `PRECOMPILED_ADDRESS`. +As of `FORK_TIMESTAMP` in the integrated EVM chain, add precompiled contract `P256VERIFY` for signature verifications in the “secp256r1” elliptic curve at address `PRECOMPILED_ADDRESS` in `0x19`. ### Elliptic Curve Information @@ -112,6 +112,8 @@ The "secp256r1" elliptic curve signatures consists of `v`, `r`, `s` components. The signature values in `r` - `s` and the public key coordinates in the `x`- `y` provides direct computations in signing and verification part, so these formats are chose in the input data format which are 32 bytes. +The `PRECOMPILED_ADDRESS` is chosed in `0x19` as it is the next available address in the precompiled address set. + The gas cost has proposed by comparing the performances of the `P256VERIFY` and the `ECRECOVER` which is implemented in the EVM at `0x01` address. It is seen that “secp256r1” signature verification by `P256VERIFY` is ~15% slower (elaborated in the [test cases](#test-cases) part) than “secp256k1” signature recovery by `ECRECOVER`, so `3450` gas is proposed by comparison which causes similar “mgas/op” values in both precompiles. ## Backwards Compatibility From b53b74cac40c8c131c0f1d8e8d7c289e6112745d Mon Sep 17 00:00:00 2001 From: 0xGh <107277922+0xGh@users.noreply.github.com> Date: Tue, 11 Jul 2023 17:21:31 +0200 Subject: [PATCH 36/41] Add EIP: ERC-721 Multi-Metadata Extension Merged by EIP-Bot. --- EIPS/eip-7160.md | 203 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 EIPS/eip-7160.md diff --git a/EIPS/eip-7160.md b/EIPS/eip-7160.md new file mode 100644 index 0000000000000..8427bfef0c012 --- /dev/null +++ b/EIPS/eip-7160.md @@ -0,0 +1,203 @@ +--- +eip: 7160 +title: ERC-721 Multi-Metadata Extension +description: Multiple metadata URIs per token, with the option to pin a primary URI. +author: 0xG (@0xGh), Marco Peyfuss (@mpeyfuss) +discussions-to: https://ethereum-magicians.org/t/erc721-multi-metadata-extension/14629 +status: Draft +type: Standards Track +category: ERC +created: 2023-06-09 +requires: 165, 721 +--- + +## Abstract + +This EIP proposes an extension to the [ERC-721](./eip-721.md) standard to support multiple metadata URIs per token. It introduces a new interface, `IERC721MultiMetadata`, which provides methods for accessing the metadata URIs associated with a token, including a pinned URI index and a list of all metadata URIs. The extension is designed to be backward compatible with existing `ERC721Metadata` implementations. + +## Motivation + +The current [ERC-721](./eip-721.md) standard allows for a single metadata URI per token with the `ERC721Metadata` implementation. However, there are use cases where multiple metadata URIs are desirable. Some example use cases are listed below: + +- A token represents a collection of (cycling) assets with individual metadata +- An on-chain history of revisions to token metadata +- Appending metadata with different aspect ratios so that it can be displayed properly on all screens +- Dynamic and evolving metadata +- Collaborative and multi-artist tokens + +This extension enables such use cases by introducing the concept of multi-metadata support. + +The primary reason for having a multi-metadata standard in addition to the existing `ERC721Metadata` standard is that dapps and marketplaces don't have a mechanism to infer and display all the token URIs. Giving a standard way for marketplaces to offer collectors a way to pin/unpin one of the metadata choices also enables quick and easy adoption of this functionality. + +## Specification + +The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. + +**The multi-metadata extension is OPTIONAL for [ERC-721](./eip-721.md) contracts and it is RECOMMENDED to be used in conjuction with the [ERC-4906](./eip-4906.md) standard if implemented**. + +```solidity +/// @title EIP-721 Multi-Metdata Extension +/// @dev See https://eips.ethereum.org/EIPS/eip-721 +/// Note: the ERC-165 identifier for this interface is 0x06e1bc5b. +interface IERC721MultiMetadata /* is IERC721Metadata */ { + /// @dev This event emits when a token uri is pinned and is + /// useful for indexing purposes. + event TokenUriPinned(uint256 indexed tokenId, uint256 indexed index, address indexed sender); + + /// @dev This event emits when a token uri is unpinned and is + /// useful for indexing purposes. + event TokenUriUnpinned(uint256 indexed tokenId, address indexed sender); + + /// @notice Get all token uris associated with a particular token + /// @dev If a token uri is pinned, the index returned should be the index in the string array + /// @param tokenId The identifier for the nft + /// @return index An unisgned integer that specifies which uri is pinned for a token (or the default uri if unpinned) + /// @return uris A string array of all uris associated with a token + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris); + + /// @notice Pin a specific token uri for a particular token + /// @param tokenId The identifier of the nft + /// @param index The index in the string array returned from the `tokenURIs` function that should be pinned for the token + function pinTokenURI(uint256 tokenId, uint256 index) external; + + /// @notice Unpin metadata for a particular token + /// @dev This should reset the token to the default uri + /// @param tokenId The identifier of the nft + function unpinTokenURI(uint256 tokenId) external; + + /// @notice Check on-chain if a token id has a pinned uri or not + /// @dev Useful for on-chain mechanics + /// @param tokenId The identifier of the nft + /// @return pinned A bool specifying if a token has metadata pinned or not + function hasPinnedTokenURI(uint256 tokenId) external view returns (bool pinned); +} +``` + +The `TokenUriPinned` event MUST be emitted when pinning a token uri with the `pinTokenUri` function. + +The `TokenUriUnpinned` event MUST be emitted when unpinning a token uri with the `unpinTokenUri` function. + +The `tokenURI` function defined in the ERC-721 Metadata standard MUST return the pinned URI when a token has a pinned uri. The `tokenURI` fucntion MUST return a default uri when a token has an unpinned uri. Which uri is returned when unpinned is up to the developer and is not specified in this standard. This ensures backwards compatibility with existing contracts and applications that rely on the single metadata URI. + +The `supportsInterface` method MUST return `true` when called with `0x06e1bc5b`. + +Implementing functionality to add uris to a token MUST be implemented separately from this standard. A `MetadataUpdate` or `BatchMetadataUpdate` event SHOULD be emitted when adding a uri to a token. + +See the [Implementation](#reference-implementation) section for an example. + +## Rationale + +The `tokenURIs` function MUST revert if the token does not exist. + +The `tokenURIs` function returns both the pinned URI index and the list of all metadata URIs to provide flexibility in accessing the metadata. + +The pinned URI can be used as a default or primary URI for the token, while the list of metadata URIs can be used to access individual assets' metadata within the token. Marketplaces could present these as a gallery or media carousels. + +Depending on the implementation, the `pinTokenURI` function allows the contract owner or token owner to specify a particular fixed metadata URI index for a token. This enables the selection of a preferred URI by index from the list of available metadata. + +When unpinned, it is recommended to return the last URI for the token. However the behavior in the case of unpinned tokens is at the discretion of the implementation and depends on the specific purpose of the token. + +## Backwards Compatibility + +This extension is designed to be backward compatible with existing [ERC-721](./eip-721.md) contracts. The implementation of the `tokenURI` method must either return the pinned token uri (if pinned) or some default uri (if unpinned). + +## Reference Implementation + +An open-source reference implementation of the `IERC721MultiMetadata` interface can be provided, demonstrating how to extend an existing [ERC-721](./eip-721.md) contract to support multi-metadata functionality. This reference implementation can serve as a guide for developers looking to implement the extension in their own contracts. + +```solidity +// SPDX-License-Identifier: CC0-1.0 + +pragma solidity ^0.8.19; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol"; +import {IERC721MultiMetadata} from "./IERC721MultiMetadata.sol"; + +contract MultiMetadata is ERC721, Ownable, IERC721MultiMetadata, IERC4906 { + mapping(uint256 => string[]) private _tokenURIs; + mapping(uint256 => uint256) private _pinnedURIIndices; + mapping(uint256 => bool) private _hasPinnedTokenURI; + + constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) Ownable() { + _mint(msg.sender, 1); + } + + // @notice Returns the pinned URI index or the last token URI index (length - 1). + function _getTokenURIIndex(uint256 tokenId) internal view returns (uint256) { + return _hasPinnedTokenURI[tokenId] ? _pinnedURIIndices[tokenId] : _tokenURIs[tokenId].length - 1; + } + + // @notice Implementation of ERC721.tokenURI for backwards compatibility. + // @inheritdoc ERC721.tokenURI + function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { + _requireMinted(tokenId); + + uint256 index = _getTokenURIIndex(tokenId); + string[] memory uris = _tokenURIs[tokenId]; + string memory uri = uris[index]; + + // Revert if no URI is found for the token. + require(bytes(uri).length > 0, "ERC721: not URI found"); + return uri; + } + + /// @inheritdoc IERC721MultiMetadata.tokenURIs + function tokenURIs(uint256 tokenId) external view returns (uint256 index, string[] memory uris) { + _requireMinted(tokenId); + return (_getTokenURIIndex(tokenId), _tokenURIs[tokenId]); + } + + /// @inheritdoc IERC721MultiMetadata.pinTokenURI + function pinTokenURI(uint256 tokenId, uint256 index) external { + require(msg.sender == ownerOf(tokenId), "Unauthorized"); + _pinnedURIIndices[tokenId] = index; + _hasPinnedTokenURI[tokenId] = true; + emit TokenUriPinned(tokenId, index, msg.sender); + } + + /// @inheritdoc IERC721MultiMetadata.unpinTokenURI + function unpinTokenURI(uint256 tokenId) external { + require(msg.sender == ownerOf(tokenId), "Unauthorized"); + _pinnedURIIndices[tokenId] = 0; + _hasPinnedTokenURI[tokenId] = false; + emit TokenUriUnpinned(tokenId, msg.sender); + } + + /// @inheritdoc IERC721MultiMetadata.hasPinnedTokenURI + function hasPinnedTokenURI(uint256 tokenId) external view returns (bool isPinned) { + return _hasPinnedTokenURI[tokenId]; + } + + /// @notice Sets a specific metadata URI for a token at the given index. + function setUri(uint256 tokenId, uint256 index, string calldata uri) external onlyOwner { + if (_tokenURIs[tokenId].length > index) { + _tokenURIs[tokenId][index] = uri; + } else { + _tokenURIs[tokenId].push(uri); + } + + // Emit a MetadataUpdate event (see EIP-4906). + emit MetadataUpdate(tokenId); + } + + // Overrides supportsInterface to include IERC721MultiMetadata interface support. + function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) { + return ( + interfaceId == type(IERC721MultiMetadata).interfaceId || + super.supportsInterface(interfaceId) + ); + } +} +``` + +## Security Considerations + +Care should be taken when specifying access controls for state changing events, such as those that allow uris to be added to tokens +and those specified in this standard: the `pinTokenUri` and `unpinTokenUri` functions. This is up to the developers to specify +as each application may have different requirements to allow for pinning and unpinning. + +## Copyright + +Copyright and related rights waived via [CC0](../LICENSE.md). From 61a7314781210a1da0151c043e7e46e29386a523 Mon Sep 17 00:00:00 2001 From: yus Date: Wed, 12 Jul 2023 11:24:05 +0800 Subject: [PATCH 37/41] Update EIP-5585: Add a new interface 'transferUserRights()' Merged by EIP-Bot. --- EIPS/eip-5585.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/EIPS/eip-5585.md b/EIPS/eip-5585.md index 8e5462b3bd9e2..92521bea8eb69 100644 --- a/EIPS/eip-5585.md +++ b/EIPS/eip-5585.md @@ -54,6 +54,12 @@ interface IERC5585 { /// @param rights Rights autorised to the user, such as renting, distribution or display etc /// @param duration The period of time the authorization lasts function authorizeUser(uint256 tokenId, address user, string[] rights, uint duration) external; + + /// @notice The user of the NFT transfers his rights to the new user + /// @dev The zero address indicates there is no user + /// @param tokenId The rights of this NFT is transferred to the new user + /// @param newUser The new user + function transferUserRights(uint256 tokenId, address newUser) external; /// @notice NFT holder extends the duration of authorization /// @dev The zero address indicates there is no user. It will throw exception when the rights are not defined by this NFT project @@ -123,6 +129,8 @@ The `authorizeUser(uint256 tokenId, address user, uint duration)` function MAY b The `authorizeUser(uint256 tokenId, address user, string[] rights; uint duration)` function MAY be implemented as `public` or `external`. +The `transferUserRights(uint256 tokenId, address newUser)` function MAY be implemented as `public` or `external`. + The `extendDuration(uint256 tokenId, address user, uint duration)` function MAY be implemented as `public` or `external`. The `updateUserRights(uint256 tokenId, address user, string[] rights)` function MAY be implemented as `public` or `external`. From 8de9fbd016e6cf1bd97fd69d2441625612a3a66f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20Wahrst=C3=A4tter?= <51536394+nerolation@users.noreply.github.com> Date: Wed, 12 Jul 2023 10:48:00 +0200 Subject: [PATCH 38/41] Update EIP-5564: update contract name and scheme id Merged by EIP-Bot. --- EIPS/eip-5564.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/EIPS/eip-5564.md b/EIPS/eip-5564.md index 1a19f585bf58c..4e7ab8456abda 100644 --- a/EIPS/eip-5564.md +++ b/EIPS/eip-5564.md @@ -85,7 +85,7 @@ function computeStealthKey( The implementation of these methods is scheme-specific. The specification of a new stealth address scheme MUST specify the implementation for each of these methods. Additionally, although these function interfaces are specified in Solidity, they do not necessarily ever need to be implemented in Solidity, but any library or SDK conforming to this specification MUST implement these methods with compatible function interfaces. -A one byte integer (`schemeId`) is used to identify stealth address schemes. The `schemeId` represents an incrementing integer beginning at 0. A mapping from the `schemeId` to its specification MUST be declared in the EIP that proposes to standardize a new stealth address scheme. Furthermore, the schemeId MUST be added to [this overview](../assets/eip-5564/scheme_ids.md). These EIP extensions MUST specify: +A 256 bit integer (`schemeId`) is used to identify stealth address schemes. A mapping from the schemeId to its specification MUST be declared in the EIP that proposes to standardize a new stealth address scheme. It is RECOMMENDED that `schemeId`s are chosen to be monotonically incrementing integers for simplicity, but arbitrary or meaningful `schemeId`s may be chosen. Furthermore, the schemeId MUST be added to [this overview](../assets/eip-5564/scheme_ids.md). These extensions MUST specify: - The integer identifier for the scheme. @@ -113,7 +113,7 @@ contract IERC5564Announcer { ); /// @dev Called by integrators to emit an `Announcement` event. - /// @param schemeId The applied stealth address scheme (f.e. secp25k1). + /// @param schemeId The integer specifying the applied stealth address scheme. /// @param stealthAddress The computed stealth address for the recipient. /// @param ephemeralPubKey Ephemeral public key used by the sender. /// @param metadata An arbitrary field MUST include the view tag in the first byte. @@ -280,3 +280,4 @@ Thus, the sender may attach a small amount of ETH to each stealth address transa ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md). + From 2e05704d86356ff23eb5eb7136e5d9fc7eac0545 Mon Sep 17 00:00:00 2001 From: Andreas Freund Date: Wed, 12 Jul 2023 07:16:54 -0700 Subject: [PATCH 39/41] Update EIP-4910: Move to Final Merged by EIP-Bot. --- EIPS/eip-4910.md | 174 ++++++++++++++++++++++++++++++----------------- 1 file changed, 113 insertions(+), 61 deletions(-) diff --git a/EIPS/eip-4910.md b/EIPS/eip-4910.md index 2deb8cd39b8a6..bd02bf70f7a21 100644 --- a/EIPS/eip-4910.md +++ b/EIPS/eip-4910.md @@ -4,8 +4,7 @@ title: Royalty Bearing NFTs description: Extension of ERC-721 to correctly define, process, and pay (hierarchical) onchain NFT royalties. author: Andreas Freund (@Therecanbeonlyone1969) discussions-to: https://ethereum-magicians.org/t/royalty-bearing-nfts/8453 -status: Last Call -last-call-deadline: 2023-03-31 +status: Final type: Standards Track category: ERC created: 2022-03-14 @@ -13,11 +12,13 @@ requires: 165, 721 --- ## Abstract + The proposal directly connects NFTs and royalties in a smart contract architecture extending the [ERC-721](./eip-721.md) standard, with the aim of precluding central authorities from manipulating or circumventing payments to those who are legally entitled to them. The proposal builds upon the OpenZeppelin Smart Contract Toolbox architecture, and extends it to include royalty account management (CRUD), royalty balance and payments management, simple trading capabilities -- Listing/De-Listing/Buying -- and capabilities to trace trading on exchanges. The royalty management capabilities allow for hierarchical royalty structures, referred to herein as royalty trees, to be established by logically connecting a "parent" NFT to its "children", and recursively enabling NFT "children" to have more children. ## Motivation + The management of royalties is an age-old problem characterized by complex contracts, opaque management, plenty of cheating and fraud. The above is especially true for a hierarchy of royalties, where one or more assets is derived from an original asset such as a print from an original painting, or a song is used in the creation of another song, or distribution rights and compensation are managed through a series of affiliates. @@ -36,11 +37,13 @@ In order to solve for the complicated inheritance problem, this proposal breaks This affords creators, and the distributors of art derived from the original, the opportunity to achieve passive income from the creative process, enhancing the value of an NFT, since it now not only has intrinsic value but also comes with an attached cash flow. ## Specification + The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119. ### Outline This proposal introduces several new concepts as extensions to the ERC-721 standard that warrant explanation: + * **Royalty Account (RA)** * A Royalty Account is attached to each NFT through its `tokenId` and consists of several sub-accounts which can be accounts of individuals or other RAs. A Royalty Account is identified by an account identifier. * **Account Type** @@ -88,6 +91,7 @@ Authorized user addresses can list NFTs for sale for non-exchange mediated NFT p To avoid royalty circumvention, a buyer will always pay the NFT contract directly and not the seller. The seller is paid through the royalty distribution and can later request a payout. The payment process depends on whether the payment is received in ETH or an [ERC-20](./eip-20.md) token: + * ERC-20 Token 1. The Buyer must `approve` the NFT contract for the purchase price, `payment` for the selected payment token (ERC-20 contract address). 2. For an ERC-20 payment token, the Buyer must then call the `executePayment` in the NFT contract -- the ERC-20 is not directly involved. @@ -98,6 +102,7 @@ The payment process depends on whether the payment is received in ETH or an [ERC The input parameters must satisfy several requirements for the NFT to be transferred AFTER the royalties have been properly distributed. Furthermore, the ability to transfer more than one token at a time is also considered. The proposal defines: + * Input parameter validation * Payment Parameter Validation * Distributing Royalties @@ -114,12 +119,15 @@ This is the final part of the proposal. There are two versions of the payout function -- a `public` function and an `internal` function. The public function has the following interface: + ``` function royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual nonReentrant returns (bool) ``` + where we only need the `tokenId`, the RA Sub Account address, `_RAsubaccount` which is the `owner`, and the amount to be paid out, `_amount`. Note that the function has `nonReentrant` modifier protection, because funds are being payed out. To finally send a Payout payment, the following steps need to be taken: + * find the RA Sub Account based on `RAaccount` and the `subaccountPos` and extract the balance * extract `tokenType` from the Sub Account * based on the token type, send the payout payment (not exceeding the available balance) @@ -202,6 +210,7 @@ Below are the definitions and interfaces for the Royalty Account RUD (Read-Updat There is only one get function required because a Royalty Account and its sub accounts can be retrieved through the `tokenId` in the `ancestry` field of the Royalty Account. **[R12]** *The `getRoyaltyAccount` function interface MUST adhere to the definition below:* + ``` /// @dev Function to fetch a Royalty Account for a given tokenId /// @param tokenId is the identifier of the NFT to which a Royalty Account is attached @@ -215,6 +224,7 @@ function getRoyaltyAccount (uint256 tokenId) public view virtual returns (addres **[R13]** *The following business rules MUST be enforced in the `getRoyaltyAccount` function:* + * *`tokenId` exists and is not burned* #### Update a Royalty Account @@ -223,6 +233,7 @@ In order to update a Royalty Account, the caller must have both the 'tokenId' an **[R14]** *The `updateRoyaltyAccount` function interface MUST adhere to the definition below:* + ``` /// @dev Function to update a Royalty Account and its Sub Accounts /// @param tokenId is the identifier of the NFT to which the Royalty Account to be updated is attached @@ -234,6 +245,7 @@ function updateRoyaltyAccount (uint256 _tokenId, `RoyaltyAccount memory _raAccou The update functionality of a Royalty Account, while straightforward, is also highly nuanced. To avoid complicated change control rules such as multi-signature rules, Royalty Account changes are kept simple. **[R15]** *The business rules for the update function are as follows:* + 1. *An NFTs asset identifier MUST NOT be changed.* 2. *An NFTs ancestor MUST NOT be updated.* 3. *An NFTs token type accepted for payment MUST NOT be updated.* @@ -246,19 +258,19 @@ The update functionality of a Royalty Account, while straightforward, is also hi 9.1 *the Sub Account belonging to the account identifier MUST NOT be removed* - 9.2 * A royalty split MUST only be decreased, and either the existing sub account's royalty split MUST be increased accordingly such that the sum of all royalty splits remains equal to 1 or its numerical equivalent, or one or more new Royalty Sub Accounts MUST be added according to rule 10.* + 9.2 *A royalty split MUST only be decreased, and either the existing sub account's royalty split MUST be increased accordingly such that the sum of all royalty splits remains equal to 1 or its numerical equivalent, or one or more new Royalty Sub Accounts MUST be added according to rule 10.* - 9.3 * a royalty balance MUST NOT be changed* + 9.3 *a royalty balance MUST NOT be changed* - 9.4 * an account identifier MUST NOT be NULL* + 9.4 *an account identifier MUST NOT be NULL* 10. *If `msg.sender` is equal to the account identifier of one of the Sub Account owners which is not the parent NFT, an additional Royalty Sub Accounts MAY be added* 10.1 *if the royalty split of the Royalty Sub Account belonging to `msg.sender` is reduced* - 10.1.1 *then the royalty balance in each new Royalty Sub Account MUST be zero* + * then the royalty balance in each new Royalty Sub Account MUST be zero - 10.1.2 *the sum of the new royalty splits data MUST be equal to the royalty split of the Royalty Sub Account of `msg.sender` before it was modified* + * and the sum of the new royalty splits data MUST be equal to the royalty split of the Royalty Sub Account of `msg.sender` before it was modified 10.2 *new account identifier MUST not be NULL* @@ -269,6 +281,7 @@ The update functionality of a Royalty Account, while straightforward, is also hi While sometimes deleting a Royalty Account is necessary, even convenient, it is a very costly function in terms of gas, and should not be used unless one is absolutely sure that the conditions enumerated below are met. **[R16]** *The `deleteRoyaltyAccount` function interface MUST adhere to the definition below:* + ``` /// @dev Function to delete a Royalty Account /// @param tokenId is the identifier of the NFT to which the Royalty Account to be updated is attached @@ -277,6 +290,7 @@ function deleteRoyaltyAccount (uint256 _tokenId) public virtual returns (bool) ``` **[R17]** *The business rules for this function are as follows:* + * *`_tokenId` MUST be burned, i.e., have owner `address(0)`.* * *all `tokenId` numbers genealogically related to `_tokenId` either as ancestors or offspring MUST also be burnt.* * *all balances in the Royalty Sub Accounts MUST be zero.* @@ -298,6 +312,7 @@ To this end the specification utilizes the ERC-721 `_safemint` function in a new This strange choice in the two requirements above is necessary, because the NFT contract functions as an escrow for payments and royalties, and, hence, needs to be able to track payments received from buyers and royalties due to recipients, and to associate them with a valid `tokenId`. **[R21]** *For compactness of the input, and since the token meta data might vary from token to token the MUST be a minimal data structure containing:* + ``` /// @param parent is the parent tokenId of the (child) token, and if set to 0 then there is no parent. /// @param canBeParent indicates if a tokenId can have children or not. @@ -307,6 +322,7 @@ This strange choice in the two requirements above is necessary, because the NFT ``` **[R22]** *The `mint` function interface MUST adhere to the definition below:* + ``` /// @dev Function creates one or more new NFTs with its relevant meta data necessary for royalties, and a Royalty Account with its associated met data for `to` address. The tokenId(s) will be automatically assigned (and available on the emitted {IERC-721-Transfer} event). /// @param to is the address to which the NFT(s) are minted @@ -317,22 +333,24 @@ function mint(address to, NFTToken[] memory nfttoken, address tokenType) public ``` **[R23]** *The following business rules for the `mint` function's input data MUST be fulfilled:* -- *The number of tokens to be minted MUST NOT be zero.* -- *`msg.sender` MUST have either the `MINTER_ROLE` or the `CREATOR_Role` identifying the creator of the first NFT.* -- *`to` address MUST NOT be the zero address.* -- *`to` address MUST NOT be a contract, unless it has been whitelisted -- see [Security Considerations](#security-considerations) for more details.* -- *`tokenType` MUST be a token type supported by the contract.* -- *`royaltySplitForItsChildren` MUST be less or equal to 100% or numerical equivalent thereof less any constraints such as platform fees* -- *If the new NFT(s) cannot have children, `royaltySplitForItsChildren` MUST be zero.* -- *If the new NFT(s) has a parent, the parent NFT `tokenId` MUST exist.* -- *The ancestry level of the parent MUST be less than the maximum number of allowed NFT generations, if specified.* -- *The number of allowed children for an NFT to be minted MUST be less than the maximum number of allowed children, if specified.* + +* *The number of tokens to be minted MUST NOT be zero.* +* *`msg.sender` MUST have either the `MINTER_ROLE` or the `CREATOR_Role` identifying the creator of the first NFT.* +* *`to` address MUST NOT be the zero address.* +* *`to` address MUST NOT be a contract, unless it has been whitelisted -- see [Security Considerations](#security-considerations) for more details.* +* *`tokenType` MUST be a token type supported by the contract.* +* *`royaltySplitForItsChildren` MUST be less or equal to 100% or numerical equivalent thereof less any constraints such as platform fees* +* *If the new NFT(s) cannot have children, `royaltySplitForItsChildren` MUST be zero.* +* *If the new NFT(s) has a parent, the parent NFT `tokenId` MUST exist.* +* *The ancestry level of the parent MUST be less than the maximum number of allowed NFT generations, if specified.* +* *The number of allowed children for an NFT to be minted MUST be less than the maximum number of allowed children, if specified.* ### Listing and De-Listing of NFTs for Direct Sales In the sales process, we need to minimally distinguish two types of transactions -- Exchange-mediated sales -- Direct sales + +* Exchange-mediated sales +* Direct sales The first type of transaction does not require that the smart contract is aware of a sales listing since the exchange contract will trigger payment and transfer transactions directly with the NFT contract as the owner. However, for the latter transaction type it is essential, since direct sales are required to be mediated at every step by the smart contract. @@ -343,6 +361,7 @@ Exchange-mediated sales will be discussed when this document discusses payments. In direct sales, authorized user addresses can list NFTs for sale, see the business rules below. **[R25]** *The `listNFT` function interface MUST adhere to the definition below:* + ``` /// @dev Function to list one or more NFTs for direct sales /// @param tokenIds is the array of tokenIds to be included in the listing @@ -351,30 +370,35 @@ In direct sales, authorized user addresses can list NFTs for sale, see the busin function listNFT (uint256[] calldata tokenIds, uint256 price, address tokenType) public virtual returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. **[R26]** *The business rules of the `listNFT` function are as follows:* -- there MUST NOT already be a listing for one or more NFTs in the `listedNFT` mapping of the proposed listing. -- `seller` MUST be equal to `getApproved(tokenId[i])` for all NFTs in the proposed listing. -- `tokenType` MUST be supported by the smart contract. -- `price` MUST be larger than `0`. + +* there MUST NOT already be a listing for one or more NFTs in the `listedNFT` mapping of the proposed listing. +* `seller` MUST be equal to `getApproved(tokenId[i])` for all NFTs in the proposed listing. +* `tokenType` MUST be supported by the smart contract. +* `price` MUST be larger than `0`. **[R27]** *If the conditions in [**[R26]**](#r26) are met, then the NFT sales list MUST be updated.* Authorized user addresses can also remove a direct sale listing of NFTs. **[R28]** *The `removeNFTListing` function interface MUST adhere to the definition below:* + ``` /// @dev Function to de-list one or more NFTs for direct sales /// @param listingId is the identifier of the NFT listing function removeNFTListing (uint256 listingId) public virtual returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. **[R29]** *The business rules of the `removeNFTListing` function below MUST be adhered to:* -- * the registered payment entry MUST be NULL* -- *`msg.sender = getApproved(tokenId)` for the NFT listing* + +* *the registered payment entry MUST be NULL* +* *`msg.sender = getApproved(tokenId)` for the NFT listing* **[R30]** *If the conditions in [**[R29]**](#r29) are met, then the NFT sales listing MUST be removed.* @@ -383,13 +407,15 @@ The Boolean return value is `true` for a successful function execution, and `fal As noted before, a buyer will always pay the NFT contract directly and not the seller. The seller is paid through the royalty distribution and can later request a payout to their wallet. **[R31]** *The payment process requires either one or two steps:* + 1. *For an ERC-20 token* - - *The buyer MUST `approve` the NFT contract for the purchase price, `payment`, for the selected payment token type.* - - *The buyer MUST call the `executePayment` function.* + * *The buyer MUST `approve` the NFT contract for the purchase price, `payment`, for the selected payment token type.* + * *The buyer MUST call the `executePayment` function.* 2. *For a protocol token* - - *The buyer MUST call a payment fallback function with `msg.data` not NULL.* + * *The buyer MUST call a payment fallback function with `msg.data` not NULL.* **[R32]** *For an ERC-20 token type, the required `executePayment` function interface MUST adhere to the definition below*: + ``` /// @dev Function to make a NFT direct sales or exchange-mediate sales payment /// @param receiver is the address of the receiver of the payment @@ -401,23 +427,26 @@ As noted before, a buyer will always pay the NFT contract directly and not the s function executePayment (address receiver, address seller, uint 256[] tokenIds, uint256 payment, string tokenType, int256 trxnType) public virtual nonReentrant returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. **[R33]** *Independent of `trxnType`, the business rules for the input data are as follows:* -- *All purchased NFTs in the `tokenIds` array MUST exist and MUST NOT be burned.* -- *`tokenType` MUST be a supported token.* -- *`trxnType` MUST be set to either `0` (direct sale) or `1` (exchange-mediate sale), or another supported type.* -- *`receiver` MAY be NULL but MUST NOT be the Zero Address.* -- *`seller` MUST be the address in the corresponding listing.* -- *`msg.sender` MUST not be a contract, unless it is whitelisted in the NFT contract.* + +* *All purchased NFTs in the `tokenIds` array MUST exist and MUST NOT be burned.* +* *`tokenType` MUST be a supported token.* +* *`trxnType` MUST be set to either `0` (direct sale) or `1` (exchange-mediate sale), or another supported type.* +* *`receiver` MAY be NULL but MUST NOT be the Zero Address.* +* *`seller` MUST be the address in the corresponding listing.* +* *`msg.sender` MUST not be a contract, unless it is whitelisted in the NFT contract.* In the following, this document will only discuss the differences between the two minimally required transaction types. **[R34]** *For `trxnType = 0`, the payment data MUST to be validated against the listing, based on the following rules:* -- *NFT(s) MUST be listed* -- *`payment` MUST be larger or equal to the listing price.* -- *The listed NFT(s) MUST match the NFT(s) in the payment data.* -- *The listed NFT(s) MUST be controlled by `seller`.* + +* *NFT(s) MUST be listed* +* *`payment` MUST be larger or equal to the listing price.* +* *The listed NFT(s) MUST match the NFT(s) in the payment data.* +* *The listed NFT(s) MUST be controlled by `seller`.* **[R35]** *If all checks in [**[R33]**](#r33), and in [**[R34]**](#r34) for `trxnType = 0`, are passed, the `executePayment` function MUST call the `transfer` function in the ERC-20 contract identified by `tokenType` with `recipient = address(this)` and `amount = payment`.* @@ -445,26 +474,30 @@ Note that since the information for which NFTs the payment was for must be passe **[R43]** *For `trxnType` equal to either '0' or '1', the requirements [**[R33]**](#r33) through [**[R38]**](#r38) MUST be satisfied for the fallback function to successfully execute, otherwise the fallback function MUST `revert`.* **[R44]** *In case of a transaction failure (for direct sales, `trxnType = 0`), or the buyer of the NFT listing changing their mind (for exchange-mediated sales, `trxnType = 1`), the submitted payment MUST be able to revert using the `reversePayment` function where the function interface is defined below:* + ``` /// @dev Definition of the function enabling the reversal of a payment before the sale is complete /// @param paymentId is the unique identifier for which a payment was made /// @param tokenType is the type of payment token used in the payment function reversePayment(uint256 paymentId, string memory tokenType) public virtual returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. Note, `reentrancy` protection through e.g. `nonReentrant` from the Open Zeppelin library is strongly advised since funds are being paid out. **[R45]** *The business rules for the `reversePayment` function are as follows:* -- *There MUST be registered payment for a given `paymentId` and `tokenType`.* -- *`msg.sender` MUST be the buyer address in the registered payment.* -- *The payment amount must be larger than `0`.* -- *The registered payment MUST be removed when the payment has been successfully reverted, otherwise the function must fail.* + +* *There MUST be registered payment for a given `paymentId` and `tokenType`.* +* *`msg.sender` MUST be the buyer address in the registered payment.* +* *The payment amount must be larger than `0`.* +* *The registered payment MUST be removed when the payment has been successfully reverted, otherwise the function must fail.* ### Modified NFT Transfer function This document adheres to the ERC-721 interface format for the `safeTransferFrom` function as given below: + ``` function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) external virtual override ``` @@ -472,6 +505,7 @@ function safeTransferFrom(address from, address to, uint256 tokenId, bytes memor Note, that the input parameters must satisfy several requirements for the NFT(s) to be transferred AFTER royalties have been properly distributed. Note also, that the ability to transfer more than one token at a time is required. However, the standard interface only allows one token to be transferred at a time. In order to remain compliant with the ERC-721 standard, this document uses `tokenId` only for the first NFT to be transferred. All other transfer relevant data is encoded in `_data`. The high-level requirements are as follows: + * The payment parameters of the trade encoded in `_data` must be validated. * The seller and the sold NFT token(s) must exist, and the seller must be the owner of the token. * `msg.sender` must be the seller address or an approved address. @@ -485,6 +519,7 @@ Also, note that in order to avoid royalty circumvention attacks, there is only o This can be achieved through for example a `revert` statement in an `override` function. **[R47]** *The requirements on input parameters of the function are as follows*: + * *`from` MUST not be `address(0)`.* * *`from` MUST be the owner or `approved` for `tokenId` and the other tokens included in `_data`.* * *`from` MUST not be a smart contract unless whitelisted.* @@ -497,6 +532,7 @@ Note, that in the context of this document only the scenario where the calling c Turning to the `_data` object. **[R48]** *The `_data` object MUST minimally contain the following payment parameters:* + * *Seller Address as `address`.* * *Buyer Address as `address`.* * *Receiver Address as `address.* @@ -507,6 +543,7 @@ Turning to the `_data` object. * *blockchain ID, `block.chainid`, of the underlying blockchain.* **[R49]** *The following business rules MUST be met for the payment data in '_data':* + * *`seller == from`.* * *`tokenId[0] == tokenId`.* * *Each token in `_tokenId` has an associated Royalty Account.* @@ -529,6 +566,7 @@ The approach to distributing royalties is to break down the hierarchical structu Note, that the distribution function assumes that the payment made is for ALL tokens in the requested transfer. That means, that `payment` for the distribution function is equally divided between all NFTs included in the payment. **[R50]** *The `distributePayment` function interface MUST adhere to the definition below: + ``` /// @dev Function to distribute a payment as royalties to a chain of Royalty Accounts /// @param tokenId is a tokenId included in the sale and used to look up the associated Royalty Account @@ -536,6 +574,7 @@ Note, that the distribution function assumes that the payment made is for ALL to function distributePayment (uint256 tokenId, uint265 payment) internal virtual returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. As mentioned before, the internal `distributePayment` function is called within the modified `safeTransferFrom` function. @@ -543,6 +582,7 @@ As mentioned before, the internal `distributePayment` function is called within Note, that it is necessary to multiply two `uint256` numbers with each other -- the payment amount with the royalty split percentage expressed as a whole number e.g. `10000 = 100%`. And then divide the result by the whole number representing `100%` in order to arrive at the correct application of the royalty split percentage to the payment amount. This requires careful treatment of numbers in the implementation to prevent issues such as buffer over or under runs. **[R51]** *The processing logic of `distributePayment` function MUST be as follows:* + * *Load the Royalty Account (`RA`) and associated Royalty Sub Accounts using the passed `tokenId`.* * *For each Royalty Sub Account in `RA` apply the following rules:* * *If a Royalty Sub Account in `RA` has `isIndividual` set to `true` then* @@ -562,6 +602,7 @@ Note, that it is necessary to multiply two `uint256` numbers with each other -- In order to simplify the ownership transfer, first the approved address -- the non-contract NFT owner --, `from`, is paid out its share of the royalties. And then the Royalty Sub Account is updated with the new owner, `to`. This step repeats for each token to be transferred. **[R52]** *The business rules are as follows:* + * *the internal version of the`royaltyPayOut` function MUST pay out the entire royalty balance of the Royalty Sub Account owned by the `from` address to the `from` address.* * *the Royalty Sub Account MUST only be updated with the new owner only once the payout function has successfully completed and the `royaltybalance = 0`.* @@ -586,6 +627,7 @@ Only after the real ownership of the NFT, the approved address, has been updated There are two versions of the payout function -- a `public` and an `internal` function -- depending on whether there is a payout during a purchase, or a payout is requested by a Royalty Sub Account owner. **[R55]** *The public `royaltyPayOut` function interface MUST adhere to the definition below:* + ``` /// @dev Function to payout a royalty payment /// @param tokenId is the identifier of the NFT token @@ -595,11 +637,13 @@ There are two versions of the payout function -- a `public` and an `internal` fu function royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual nonReentrant returns (bool) ``` + The Boolean return value is `true` for a successful function execution, and `false` for an unsuccessful function execution. Note, that the function has `reentrancy` protection through `nonReentrant` from the Open Zeppelin library since funds are being paid out. **[R56]** *The input parameters of the `royaltyPayOut` function MUST satisfy the following requirements:* + * *`msg.sender == RAsubaccount`.* * *`tokenId` must exist and must not be burned.* * *`tokenId` must be associated with a Royalty Account.* @@ -608,15 +652,18 @@ Note, that the function has `reentrancy` protection through `nonReentrant` from * *`amount <= royaltybalance` of the Royalty Sub Account, `RAsubaccount.*` **[R57]** *The internal `_royaltyPayOut` function interface MUST adhere to the definition below*: + ``` function _royaltyPayOut (uint256 tokenId, address RAsubaccount, address payable payoutAccount, uint256 amount) public virtual returns (bool) ``` **[R58]** *The internal `_royaltyPayOut` function MUST perform the following actions: + * *send the payment to the `payoutaccount`.* * *update the `royaltybalance` of the `RAsubaccount` of the Royalty Account upon successful transfer.* **[R59]** *The following steps MUST be taken to send out a royalty payment to its recipient:* + * *find the Royalty Sub Account.* * *extract `tokenType` from the Royalty Sub Account.* * *based on the token type send to the `payoutAccount` either* @@ -630,7 +677,7 @@ Royalties for NFTs is at its core a distribution licensing problem. A buyer obta In order to solve for the complicated inheritance problem, this proposal design breaks down the recursive problem of the hierarchy first into a tree of depth N. And the further breaks down the tree structure into N separate problems, one for each layer. This design allows one to traverse the tree from its lowest level upwards to its root most efficiently. This is achieved with the design for the `distributePayment` function and the NFT data structures allowing for the tree structure e.g. `ancestry`,`royaltyAccount`, `RAsubaccount`. -In order to avoid massive gas costs during the payout of royalties, possibly exceeding block gas limits for large royalty trees, the design needed to create a royalty accounting system to maintain royalty balances for recipients as done with the `royaltyAccount`, 'RAsubaccount' data structures and the associated CRUD operations, as well as require that royalty payouts are done by indvidual and by request, only, as is achieved with the `royaltyPayout` function design. +In order to avoid massive gas costs during the payout of royalties, possibly exceeding block gas limits for large royalty trees, the design needed to create a royalty accounting system to maintain royalty balances for recipients as done with the `royaltyAccount`, 'RAsubaccount' data structures and the associated CRUD operations, as well as require that royalty payouts are done by individual and by request, only, as is achieved with the `royaltyPayout` function design. Furthermore, the design had to ensure that in order to account for and payout royalties the smart contract must be in the "know" of all buying and selling of an NFT including the exchange of monies. This buying and selling can be either direct through the NFT contract or can be exchange-mediated as is most often the case today -- which is a centralizing factor! The chosen design for purchasing is accounting for those two modes. @@ -639,6 +686,7 @@ Keeping the NFT contract in the "know" at the beginning of the purchase process The design needed to avoid royalty circumvention during the purchase process, therefore, the NFT must be kept in the "know", a buyer will always have to pay the NFT contract directly and not the seller for both purchasing modes. The seller is subsequently paid through the royalty distribution function in the NFT contract. As a consequence, and a key design choice, and to stay compliant with ERC-721, the NFT contract must be the owner of the NFT, and the actual owner is an `approved` address. The specification design also needed to account for that the payment process depends on whether the payment is received in ETH or an ERC-20 token: + * ERC-20 Token 1. The Buyer must `approve` the NFT contract for the purchase price, `payment` for the selected payment token (ERC-20 contract address). 2. For an ERC-20 payment token, the Buyer must then call the `executePayment` in the NFT contract -- the ERC-20 is not directly involved. @@ -658,37 +706,41 @@ An NFT with a royalty account can be burned. However, several things have to be ## Backwards Compatibility + This EIP is backwards compatible to the ERC-721 standard introducing new interfaces and functionality but retaining the core interfaces and functionality of the ERC-721 standard. ## Test Cases + A full test suite is part of the reference implementation. ## Reference Implementation + The Treetrunk reference implementation of the standard can be found in the public treetrunkio Github repo under treetrunk-nft-reference-implementation. ## Security Considerations + Given that this EIP introduces royalty collection, distribution, and payouts to the ERC-721 standard, the number of attack vectors increases. The most important attack vector categories and their mitigation are discussed below: -- **Payments and Payouts**: - - Reentrancy attacks are mitigated through a reentrancy protection on all payment functions. See for example the Open Zeppelin reference implementation . - - Payouts from unauthorized accounts. Mitigation: Royalty Sub Accounts require at least that `msg.sender` is the Royalty Sub Account owner. - - Payments could get stuck in the NFT contract if the `executePayment` function fails. Mitigation: For exchange-mediated sales, a buyer can always reverse a payment with `reversePayment` if the `executePayment` function fails. For direct sales, `reversePayment` will be directly triggered in the `executePayment` function. -- **Circumventing Royalties**: - - Offchain Key exchanges - - Exchanging a private key for money off chain can not be prevented in any scenario. - - Smart Contract Wallets as NFT owners - - A Smart Contract Wallet controlled by multiple addresses could own an NFT and the owners could transfer the asset within the wallet with an off chain money exchange. Mitigation: Prohibit that Smart Contracts can own an NFT unless explicitly allowed to accommodate special scenarios such as collections. - - Denial of Royalty Disbursement - - An attacker who has purchased one or more NFTs in a given generation of an NFT family can cause out of gas errors or run time errors for the contract, if they add many spurious royalty sub-accounts with very low royalty split percentages, and then mint more prints of those purchased NFTs, and then repeat that step until the set `maxGeneration` limit is reached. An NFT trade at the bottom of the hierarchy will then require a lot of code cycles because of the recursive nature of the royalty distribution function. Mitigation: Limit the number of royalty sub-accounts per NFT and impose a royalty split percentage limit. - - Following the same approach as above but now targeting the `addListNFT` function, an attacker can force an out of gas error or run time errors in the `executePayment` function by listing many NFTs at a low price, and then performing a purchase from another account. Mitigation: Limit the number of NFTs that can be included in one listing. - - The creator of the NFT family could set the number of generations too high such that the royalty distribution function could incur and out of gas or run time error because of the recursive nature of the function. Mitigation: Limiting the `maxNumberGeneration` by the creator. - - General Considerations: The creator of an NFT family must carefully consider the business model for the NFT family and then set the parameters such as maximum number of generations, royalty sub-accounts, number of prints per print, number of NFTs in a listing, and the maximum and minimum royalty split percentage allowed. - -- **Phishing Attacks** - - NFT phishing attacks often target the `approve` and `setApprovalForAll` functions by tricking owners of NFTs to sign transactions adding the attacker account as approved for one or all NFTs of the victim. Mitigation: This contract is not vulnerable to these type of phishing attacks because all NFT transfers are sales, and the NFT contract itself is the owner of all NFTs. This means that transfers after a purchase are achieved by setting the new owner in the `_approve` function. Calling the public `approve` function will cause the function call to error out because `msg.sender` of the malicious transaction cannot be the NFT owner. - - NFT phishing attack targeting the `addListNFT` function to trick victim to list one or more NFTs at a very low price and the attacker immediately registering a payment, and executing that payment right away. Mitigation: Implement a waiting period for a purchase can be affected giving the victim time to call the `removeListNFT` function. In addition, an implementer could require Two-Factor-Authentication either built into the contract or by utilizing an authenticator app such as Google Authenticator built into a wallet software. +* **Payments and Payouts**: + * Reentrancy attacks are mitigated through a reentrancy protection on all payment functions. See for example the Open Zeppelin reference implementation . + * Payouts from unauthorized accounts. Mitigation: Royalty Sub Accounts require at least that `msg.sender` is the Royalty Sub Account owner. + * Payments could get stuck in the NFT contract if the `executePayment` function fails. Mitigation: For exchange-mediated sales, a buyer can always reverse a payment with `reversePayment` if the `executePayment` function fails. For direct sales, `reversePayment` will be directly triggered in the `executePayment` function. +* **Circumventing Royalties**: + * Offchain Key exchanges + * Exchanging a private key for money off chain can not be prevented in any scenario. + * Smart Contract Wallets as NFT owners + * A Smart Contract Wallet controlled by multiple addresses could own an NFT and the owners could transfer the asset within the wallet with an off chain money exchange. Mitigation: Prohibit that Smart Contracts can own an NFT unless explicitly allowed to accommodate special scenarios such as collections. + * Denial of Royalty Disbursement + * An attacker who has purchased one or more NFTs in a given generation of an NFT family can cause out of gas errors or run time errors for the contract, if they add many spurious royalty sub-accounts with very low royalty split percentages, and then mint more prints of those purchased NFTs, and then repeat that step until the set `maxGeneration` limit is reached. An NFT trade at the bottom of the hierarchy will then require a lot of code cycles because of the recursive nature of the royalty distribution function. Mitigation: Limit the number of royalty sub-accounts per NFT and impose a royalty split percentage limit. + * Following the same approach as above but now targeting the `addListNFT` function, an attacker can force an out of gas error or run time errors in the `executePayment` function by listing many NFTs at a low price, and then performing a purchase from another account. Mitigation: Limit the number of NFTs that can be included in one listing. + * The creator of the NFT family could set the number of generations too high such that the royalty distribution function could incur and out of gas or run time error because of the recursive nature of the function. Mitigation: Limiting the `maxNumberGeneration` by the creator. + * General Considerations: The creator of an NFT family must carefully consider the business model for the NFT family and then set the parameters such as maximum number of generations, royalty sub-accounts, number of prints per print, number of NFTs in a listing, and the maximum and minimum royalty split percentage allowed. +* **Phishing Attacks** + * NFT phishing attacks often target the `approve` and `setApprovalForAll` functions by tricking owners of NFTs to sign transactions adding the attacker account as approved for one or all NFTs of the victim. Mitigation: This contract is not vulnerable to these type of phishing attacks because all NFT transfers are sales, and the NFT contract itself is the owner of all NFTs. This means that transfers after a purchase are achieved by setting the new owner in the `_approve` function. Calling the public `approve` function will cause the function call to error out because `msg.sender` of the malicious transaction cannot be the NFT owner. + * NFT phishing attack targeting the `addListNFT` function to trick victim to list one or more NFTs at a very low price and the attacker immediately registering a payment, and executing that payment right away. Mitigation: Implement a waiting period for a purchase can be affected giving the victim time to call the `removeListNFT` function. In addition, an implementer could require Two-Factor-Authentication either built into the contract or by utilizing an authenticator app such as Google Authenticator built into a wallet software. Besides the usage of professional security analysis tools, it is also recommended that each implementation performs a security audit of its implementation. ## Copyright + Copyright and related rights waived via [CC0](../LICENSE.md). From 1589be80ad2a88f4c7d84d72d308142a11c9e29c Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 13 Jul 2023 15:12:19 +0200 Subject: [PATCH 40/41] Update EIP-4844: Move 'Gas Accounting' section to improve readability Merged by EIP-Bot. --- EIPS/eip-4844.md | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/EIPS/eip-4844.md b/EIPS/eip-4844.md index edb161697dcb2..070004915f7c5 100644 --- a/EIPS/eip-4844.md +++ b/EIPS/eip-4844.md @@ -166,6 +166,30 @@ def calc_excess_data_gas(parent: Header) -> int: For the first post-fork block, both `parent.data_gas_used` and `parent.excess_data_gas` are evaluated as `0`. +### Gas accounting + +We introduce data gas as a new type of gas. It is independent of normal gas and follows its own targeting rule, similar to EIP-1559. +We use the `excess_data_gas` header field to store persistent data needed to compute the data gas price. For now, only blobs are priced in data gas. + +```python +def calc_data_fee(header: Header, tx: SignedBlobTransaction) -> int: + return get_total_data_gas(tx) * get_data_gasprice(header) + +def get_total_data_gas(tx: SignedBlobTransaction) -> int: + return DATA_GAS_PER_BLOB * len(tx.blob_versioned_hashes) + +def get_data_gasprice(header: Header) -> int: + return fake_exponential( + MIN_DATA_GASPRICE, + header.excess_data_gas, + DATA_GASPRICE_UPDATE_FRACTION + ) +``` + +The block validity conditions are modified to include data gas checks (see the [Execution layer validation](#execution-layer-validation) section below). + +The actual `data_fee` as calculated via `calc_data_fee` is deducted from the sender balance before transaction execution and burned, and is not refunded in case of transaction failure. + ### Opcode to get versioned hashes We add an instruction `BLOBHASH` (with opcode `HASH_OPCODE_BYTE`) which reads `index` from the top of the stack @@ -206,30 +230,6 @@ def point_evaluation_precompile(input: Bytes) -> Bytes: The precompile MUST reject non-canonical field elements (i.e. provided field elements MUST be strictly less than `BLS_MODULUS`). -### Gas accounting - -We introduce data gas as a new type of gas. It is independent of normal gas and follows its own targeting rule, similar to EIP-1559. -We use the `excess_data_gas` header field to store persistent data needed to compute the data gas price. For now, only blobs are priced in data gas. - -```python -def calc_data_fee(header: Header, tx: SignedBlobTransaction) -> int: - return get_total_data_gas(tx) * get_data_gasprice(header) - -def get_total_data_gas(tx: SignedBlobTransaction) -> int: - return DATA_GAS_PER_BLOB * len(tx.blob_versioned_hashes) - -def get_data_gasprice(header: Header) -> int: - return fake_exponential( - MIN_DATA_GASPRICE, - header.excess_data_gas, - DATA_GASPRICE_UPDATE_FRACTION - ) -``` - -The block validity conditions are modified to include data gas checks (see the [Execution layer validation](#execution-layer-validation) section below). - -The actual `data_fee` as calculated via `calc_data_fee` is deducted from the sender balance before transaction execution and burned, and is not refunded in case of transaction failure. - ### Consensus layer validation On the consensus layer the blobs are referenced, but not fully encoded, in the beacon block body. From 1abd9b1881925340aae1702e30115f1da60aae93 Mon Sep 17 00:00:00 2001 From: Lion - dapplion <35266934+dapplion@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:16:04 +0200 Subject: [PATCH 41/41] Update EIP-7044: Move to Review Merged by EIP-Bot. --- EIPS/eip-7044.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EIPS/eip-7044.md b/EIPS/eip-7044.md index 111aa42132eb3..43ee9b615a9c1 100644 --- a/EIPS/eip-7044.md +++ b/EIPS/eip-7044.md @@ -4,7 +4,7 @@ title: Perpetually Valid Signed Voluntary Exits description: Lock voluntary exit signature domain on capella for perpetual validity author: Lion (@dapplion) discussions-to: https://ethereum-magicians.org/t/eip-7044-perpetually-valid-signed-voluntary-exits/14348 -status: Draft +status: Review type: Standards Track category: Core created: 2023-05-18