-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add RIP: Native AA JSON_RPC API #58
base: master
Are you sure you want to change the base?
Changes from 4 commits
6eb0638
8c2253f
88dcbc9
860e3a4
0151f66
5077128
fbfe42e
8e080ff
70360ef
18e99c0
31bde52
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,313 @@ | ||||||||||
--- | ||||||||||
rip: 0000 | ||||||||||
title: Native Account Abstraction RPC methods | ||||||||||
description: Native Account Abstraction specific RPC methods providing support for common user flows such as transaction gas estimation and view-only calls to smart contracts | ||||||||||
author: Vitalik Buterin (@vbuterin), Yoav Weiss (@yoavw), Alex Forshtat (@forshtat), Dror Tirosh (@drortirosh), Shahaf Nacson (@shahafn) | ||||||||||
discussions-to: | ||||||||||
status: Draft | ||||||||||
type: Standards Track | ||||||||||
category: Core | ||||||||||
created: 2025-01-01 | ||||||||||
requires: 7560 | ||||||||||
--- | ||||||||||
|
||||||||||
## Abstract | ||||||||||
|
||||||||||
With the introduction of Account Abstraction as it is defined by [RIP-7560](./rip-7560.md) | ||||||||||
a number of existing RPC APIs are not capable of providing the necessary functionality. | ||||||||||
The required behaviour changes are significant enough to require an introduction of completely new methods | ||||||||||
to the Ethereum L2 clients implementing Account Abstraction. | ||||||||||
Comment on lines
+18
to
+19
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
## Motivation | ||||||||||
|
||||||||||
The process of creating a transaction has been getting increasingly more complex and interactive process | ||||||||||
with the growing complexity of on-chain protocols. | ||||||||||
Comment on lines
+23
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
The introduction of Account Abstraction introduces a new dimension for this complexity. | ||||||||||
|
||||||||||
As the AA transactions are executed atomically but consist of multiple sequential frames, | ||||||||||
it is not possible to reliably prepare parts of the AA transaction individually. | ||||||||||
Comment on lines
+27
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
For example, when creating an AA transaction that also performs a deployment of a `sender` smart account, | ||||||||||
it is all but impossible to perform a gas estimation for the execution frame using the existing `eth_estimateGas` API. | ||||||||||
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Instead, an API specifically designed with Account Abstraction in mind must be used. | ||||||||||
|
||||||||||
## Specification | ||||||||||
|
||||||||||
This document defines the API that the RIP-7560 compatible Ethereum nodes provide to the Smart Wallet applications. | ||||||||||
|
||||||||||
### eth_estimateGasAA | ||||||||||
|
||||||||||
Estimate the amount of gas needed per frame of an AA transaction. | ||||||||||
Performs a search for gas limit values required to make each of the frames of the RIP-7560 transaction execute | ||||||||||
successfully and without running out of gas. | ||||||||||
Comment on lines
+42
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
This method is an equivalent to the legacy Ethereum `eth_estimateGas` RPC API method. | ||||||||||
This method is also a native AA equivalent to the `eth_estimateUserOperationGas` method for | ||||||||||
[ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) UserOperations as defined in | ||||||||||
[ERC-7769](https://eips.ethereum.org/EIPS/eip-7769). | ||||||||||
Comment on lines
+46
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
For validation frames, only valid calls to an appropriate `AA_ENTRY_POINT` callback, | ||||||||||
such as `acceptAccount`, `acceptPaymaster`, `sigFailAccount` and `sigFailPaymaster`, are considered a success. | ||||||||||
Comment on lines
+50
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Note, that in order for `eth_esimateGasAA` to function, both the wallet and the paymaster must correctly | ||||||||||
implement signature checks as described in RIP-7560.\ | ||||||||||
Comment on lines
+53
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
Most notably, the fields `senderValidationData` and `paymasterData` usually contain signatures for the transaction | ||||||||||
and therefore cannot be known before the transaction preparation is complete.\ | ||||||||||
Comment on lines
+55
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
The recommended solution to this problem is for the wallet to provide **stub data** for these contracts, | ||||||||||
as described in the [Suggested Paymaster Flow](#suggested-paymaster-flow) section. | ||||||||||
Comment on lines
+57
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Once a signature verification over the **stub data** fails, the validation code must not revert or finish execution, | ||||||||||
but instead proceed as usual until making a `sigFailAccount` or `sigFailPaymaster` call accordingly.\ | ||||||||||
Comment on lines
+60
to
+61
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
A contract that does not provide proper gas estimation capability, such as described above, may cause `eth_esimateGasAA` to provide inconsistent results and risks incorrect reverts of validation frames, thus making the transaction invalid. | ||||||||||
|
||||||||||
The `eth_esimateGasAA` API optionally accepts the `State Override Set` object to allow users to modify | ||||||||||
the state during the gas estimation. | ||||||||||
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
This field as well as its behavior is equivalent to the ones defined for the `eth_call` RPC method. | ||||||||||
|
||||||||||
If it fails to find such a value, returns an error message with the detailed description of the failure reason. | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
Parameters: | ||||||||||
|
||||||||||
1. OBJECT - The RIP-7560 transaction object. | ||||||||||
The `validationGasLimit`, paymasterValidationGasLimit, `paymasterGasLimit` and `callGasLimit` fields are optional. | ||||||||||
2. QUANTITY | TAG - integer block number, or the string "latest", "earliest", "pending", "safe" or "finalized" | ||||||||||
3. OBJECT - `State Override Set` | ||||||||||
The `State Override Set` option allows you to change the state of a contract before executing the call. This means you | ||||||||||
can modify the values of variables stored in the contract, such as balances and approvals for that call without | ||||||||||
actually modifying the contract on the blockchain. | ||||||||||
This behavior is equivalent to the one defined for the `eth_call` RPC method. | ||||||||||
|
||||||||||
Example: | ||||||||||
```json | ||||||||||
{ | ||||||||||
"id": 1, | ||||||||||
"jsonrpc": "2.0", | ||||||||||
"method": "eth_estimateGasAA", | ||||||||||
"params": [ | ||||||||||
{ | ||||||||||
"sender": "0x783Ca0bD27E42357D6Dbc87E9Cf9eb3a8D513843", | ||||||||||
"senderValidationData": "0xdeadbeef", | ||||||||||
"deployer": "0xD6E4aA932147A3FE5311dA1b67D9e73da06F9cEf", | ||||||||||
"deployerData": "0xdeadbeef", | ||||||||||
"paymaster": "0x8410373DF6E9b20765c9599c26d585B2cd0Ef628", | ||||||||||
"paymasterData": "0xdeadbeef", | ||||||||||
"executionData": "0xdeadbeef", | ||||||||||
"builderFee": "0x9184e72a000", | ||||||||||
"maxPriorityFeePerGas": "0x9184e72a000", | ||||||||||
"maxFeePerGas": "0x9184e72a000", | ||||||||||
"accessList": [], | ||||||||||
"authorizationList": [ | ||||||||||
{ | ||||||||||
"chainId": "0x1", | ||||||||||
"nonce": "0x15", | ||||||||||
"yParity": "0x25", | ||||||||||
"r": "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea", | ||||||||||
"s": "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c" | ||||||||||
} | ||||||||||
] | ||||||||||
}, | ||||||||||
"latest", | ||||||||||
{ | ||||||||||
"0x1111111111111111111111111111111111111111": { | ||||||||||
"balance": "0x9184e72a000", | ||||||||||
"nonce": "0x1", | ||||||||||
"code": "0xdeadbeef", | ||||||||||
"state": { | ||||||||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x1" | ||||||||||
}, | ||||||||||
"stateDiff": { | ||||||||||
"0x0000000000000000000000000000000000000000000000000000000000000001": "0x1" | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
] | ||||||||||
} | ||||||||||
``` | ||||||||||
Returns: | ||||||||||
|
||||||||||
| Name | Type | Comment | | ||||||||||
|-----------------------------|----------|------------------------------------------------------------| | ||||||||||
| validationGasLimit | QUANTITY | | | ||||||||||
| callGasLimit | QUANTITY | | | ||||||||||
| paymasterValidationGasLimit | QUANTITY | if `paymaster` is set, `null` otherwise | | ||||||||||
| paymasterPostOpGasLimit | QUANTITY | if `paymaster` is set, `0` if not needed, `null` otherwise | | ||||||||||
|
||||||||||
Example: | ||||||||||
|
||||||||||
```json | ||||||||||
{ | ||||||||||
"validationGasLimit": "0x9184e72a000", | ||||||||||
"paymasterValidationGasLimit":"0x9184e72a000", | ||||||||||
"paymasterPostOpGasLimit":"0x9184e72a000", | ||||||||||
"callGasLimit": "0x9184e72a000" | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
### eth_callAA | ||||||||||
|
||||||||||
Execute the AA transaction in memory without broadcasting it to the mempool or commiting it to the blockchain. | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
An equivalent to the legacy Ethereum `eth_call` RPC API method. | ||||||||||
|
||||||||||
Inputs are equivalent to the ones defined for the [eth_estimateGasAA](#eth_estimategasaa) method. | ||||||||||
|
||||||||||
User can also provide the gas limit for all or some of the AA transaction frames.\ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
If gas limits are not provided, the RPC node provider is free to choose a default value.\ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
The RPC node provider should also choose a maximum acceptable gas limit value taking into consideration the | ||||||||||
block gas limit and other rules of the underlying L2 protocol. | ||||||||||
Comment on lines
+156
to
+157
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Does not require the transaction to be properly signed, meaning it continues execution after either an account | ||||||||||
or a paymaster contract make a `sigFailAccount` or `sigFailPaymaster` call. | ||||||||||
Comment on lines
+159
to
+160
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Returns the data that is returned by the top-level execution frame, gas usage info, | ||||||||||
as well as the full array of event logs emitted during the execution of the RIP-7560 transaction | ||||||||||
Comment on lines
+162
to
+163
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
If any of the validation or execution frames reverts, returns an error object containing the revert message. | ||||||||||
|
||||||||||
Inputs: | ||||||||||
|
||||||||||
1. OBJECT - The RIP-7560 transaction object. | ||||||||||
The `authorizationData` field is optional. | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
2. QUANTITY | TAG - integer block number, or the string "latest", "earliest", "pending", "safe" or "finalized" | ||||||||||
|
||||||||||
Returns: | ||||||||||
|
||||||||||
- **returnData** - DATA - the return value of the `sender` execution frame | ||||||||||
- **gasUsed** - QUANTITY - the total amount of gas used by the transaction | ||||||||||
- **sigFail** - QUANTITY - whether account called "sigFailAccount" or not | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
- **sigFailPaymaster** - QUANTITY - whether paymaster called "sigFailPaymaster" or not | ||||||||||
- **status** - QUANTITY - whether the execution phase including the `paymasterPostOp` completed successfully | ||||||||||
- **logs** - ARRAY - an array of the event logs emitted during the transaction | ||||||||||
|
||||||||||
Example: | ||||||||||
```json | ||||||||||
{ | ||||||||||
"returnData": "0xdeadbeef", | ||||||||||
"gasUsed": "0x1a14b", | ||||||||||
"status": "0x1", | ||||||||||
"sigFail": "0x1", | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
"sigFailPaymaster": "0x1", | ||||||||||
"logs": [ | ||||||||||
{ | ||||||||||
"transactionHash": "0x8fc90a6c3ee3001cdcbbb685b4fbe67b1fa2bec575b15b0395fea5540d0901ae", | ||||||||||
"address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", | ||||||||||
"blockHash": "0x58a945e1558810523df00490ff28cbe111b37851c44679ce5be1eeaebb4b4907", | ||||||||||
"blockNumber": "0xeb8822", | ||||||||||
"data": "0x000000000000000000000000000000000000000000000000000000001debea42", | ||||||||||
"logIndex": "0x6c", | ||||||||||
"removed": false, | ||||||||||
"topics": [ | ||||||||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", | ||||||||||
"0x0000000000000000000000005067c042e35881843f2b31dfc2db1f4f272ef48c", | ||||||||||
"0x0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585" | ||||||||||
], | ||||||||||
"transactionIndex": "0x4e" | ||||||||||
} | ||||||||||
] | ||||||||||
} | ||||||||||
``` | ||||||||||
|
||||||||||
### wallet_prepareTransactionAA | ||||||||||
|
||||||||||
The `wallet_prepareTransactionAA` API is meant to be implemented by the wallet application, | ||||||||||
and not the L2 client RPC node provider. | ||||||||||
Comment on lines
+213
to
+214
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
The method accepts a partially filled RIP-7560 transaction object and fills in all the missing fields.\ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
These may include gas prices, gas limits, nonces etc.\ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
All parameters are optional. If the parameter is provided with a value, calling this method will never override it. | ||||||||||
|
||||||||||
The method returns the full transaction object that can be serialized and sent to the `eth_sendRawTransaction` API. | ||||||||||
|
||||||||||
In case the wallet was not able to fill the transaction object for any reason, | ||||||||||
the `wallet_prepareTransactionAA` method returns an appropriate error code. | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+222
to
+223
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
### Suggested paymaster flow | ||||||||||
|
||||||||||
The Paymaster service should expose the `pm_getPaymasterStubData` and `pm_getPaymasterData` APIs as defined by the | ||||||||||
[ERC-7677](https://eips.ethereum.org/EIPS/eip-7677). | ||||||||||
Comment on lines
+227
to
+228
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
The API defined there should be implemented by a third party paymaster service, | ||||||||||
and not by the L2 client RPC node provider. | ||||||||||
Comment on lines
+230
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
This API is expected to be used internally by the wallet application as part of the `wallet_prepareTransactionAA` flow. | ||||||||||
|
||||||||||
### Add Account Abstraction support for all transaction-level RPC APIs | ||||||||||
|
||||||||||
This includes the following APIs: | ||||||||||
`eth_sendTransaction`, | ||||||||||
`eth_sendRawTransaction`, | ||||||||||
`eth_getTransactionByHash`, | ||||||||||
`eth_getTransactionReceipt`, | ||||||||||
`eth_getTransactionByBlockHashAndIndex`, | ||||||||||
`eth_getTransactionByBlockNumberAndIndex`. | ||||||||||
|
||||||||||
These methods have a very similar purpose and should support returning the new transaction type object. | ||||||||||
|
||||||||||
Note that the "transaction index position" is determined by the position of the transaction's **validation frame**. | ||||||||||
|
||||||||||
### Errors | ||||||||||
|
||||||||||
Error format: | ||||||||||
|
||||||||||
- DATA - The revert data of the first reverted frame. | ||||||||||
- CODE - The error code indicating the type of error, which may include the entity that caused the revert on-chain. | ||||||||||
- MESSAGE - The human-readable error that may include a decoding of the `DATA` field if possible. | ||||||||||
|
||||||||||
Error codes: | ||||||||||
|
||||||||||
* code: -32500 - transaction validation failed by `sender`. | ||||||||||
The message field SHOULD be set to the revert message and data from the `sender`. | ||||||||||
|
||||||||||
* code: -32501 - transaction validation failed by `paymaster`. | ||||||||||
The message field SHOULD be set to the revert message and data from the `paymaster`. | ||||||||||
|
||||||||||
* code: -32502 - transaction validation failed by `deployer` | ||||||||||
The message field SHOULD be set to the revert message and data from the `deployer`. | ||||||||||
|
||||||||||
* code: -32503 - Transaction out of time range. | ||||||||||
The message field SHOULD include the requested time range and the current block timestamp. | ||||||||||
|
||||||||||
|
||||||||||
## Rationale | ||||||||||
|
||||||||||
### New methods instead of modification for existing ones | ||||||||||
|
||||||||||
While the `eth_esimateGasAA` and `eth_callAA` methods are simply RIP-7560 extensions of the existing | ||||||||||
`eth_esimateGasAA` and `eth_call` methods accordingly, | ||||||||||
forshtat marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
the type of the return values for the existing methods does not match the needs of the Native Account Abstraction API. | ||||||||||
|
||||||||||
The approach of creating a new set of methods is cleaner in the long term as these methods may be used later in the | ||||||||||
EOF-based Native Account Abstraction as described in [EIP-7701](https://eips.ethereum.org/EIPS/eip-7701). | ||||||||||
Comment on lines
+280
to
+281
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
### The `eth_callAA` returned data format | ||||||||||
|
||||||||||
The legacy `eth_call` method returns a single DATA array representing the return data of the single top-level | ||||||||||
call frame of a legacy transaction. | ||||||||||
Comment on lines
+285
to
+286
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
However, this approach would not be sufficiently useful for the Native Account Abstraction solution.\ | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
The top-level frame of the execution phase is a call to the `sender` smart account that may consist of multiple | ||||||||||
inner batched calls. | ||||||||||
Comment on lines
+289
to
+290
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
Furthermore, the code in the `sender` address is frequently just | ||||||||||
a proxy contract that does not propagate the implementation's returned data. | ||||||||||
Comment on lines
+292
to
+293
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
In order to allow smart contract wallets to reliably expose any data to the off-chain execution we have little choice | ||||||||||
but to rely on the existing mechanism of emitting the event logs. | ||||||||||
Comment on lines
+295
to
+296
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
## Backwards Compatibility | ||||||||||
|
||||||||||
The added methods are not interfering with any existing code and should pose no backwards compatibility issues. | ||||||||||
|
||||||||||
## Security Considerations | ||||||||||
|
||||||||||
### Estimate Gas flow in smart contracts | ||||||||||
|
||||||||||
As mentioned in the [eth_estimateGasAA](#eth_estimategasaa) section, the `sender` and `paymaster` smart contracts | ||||||||||
are expected to explicitly implement a special code flow to support the gas estimation. | ||||||||||
Comment on lines
+306
to
+307
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
This code is supposed to only be executable during the `eth_estimateGasAA` and MUST never result in a valid transaction. | ||||||||||
In case this constraint is violated the contract will be vulnerable to various potential threats. | ||||||||||
Comment on lines
+309
to
+310
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
## Copyright | ||||||||||
|
||||||||||
Copyright and related rights waived via [CC0](../LICENSE.md). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.