mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-03 10:34:24 +00:00
update to latest eip-template
This commit is contained in:
parent
925de35aac
commit
a88d6dbd6c
270
EIPS/eip-1077.md
270
EIPS/eip-1077.md
@ -1,21 +1,113 @@
|
||||
---
|
||||
eip: 1077
|
||||
title: Executable Signed Messages refunded by the contract
|
||||
title: Gas relay for contract calls
|
||||
author: Alex Van de Sande <avsa@ethereum.org>, Ricardo Guilherme Schmidt (@3esmit)
|
||||
discussions-to: https://ethereum-magicians.org/t/erc1077-and-1078-the-magic-of-executable-signed-messages-to-login-and-do-actions/351
|
||||
status: Draft
|
||||
type: Standards Track
|
||||
category: ERC
|
||||
created: 2018-05-04
|
||||
requires: 191, 1271
|
||||
requires: 20, 191, 1271, 1344
|
||||
---
|
||||
|
||||
|
||||
## Simple Summary
|
||||
|
||||
Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them is an emerging pattern being used in many projects. Standardizing a common format for them, as well as a way in which the user allows the transaction to be paid in tokens, gives app developers a lot of flexibility and can become the main way in which app users interact with the Blockchain.
|
||||
A standard interface for gas abstraction in top of smart contracts.
|
||||
|
||||
Allows users to offer [EIP-20] token for paying the gas used in a call.
|
||||
|
||||
## Abstract
|
||||
|
||||
A main barrier for adoption of DApps is the requirement of multiple tokens for executing in chain actions. Allowing users to sign messages to show intent of execution, but allowing a third party relayer to execute them can circumvent this problem, while ETH will always be required for ethereum transactions, it's possible for smart contract to take [EIP-191] signatures and forward a payment incentive to an untrusted party with ETH for executing the transaction.
|
||||
|
||||
## Motivation
|
||||
|
||||
Standardizing a common format for them, as well as a way in which the user allows the transaction to be paid in tokens, gives app developers a lot of flexibility and can become the main way in which app users interact with the Blockchain.
|
||||
|
||||
|
||||
## Specification
|
||||
|
||||
### Methods
|
||||
|
||||
#### executeGasRelay
|
||||
|
||||
Executes `_execData` with current `lastNonce()` and pays `msg.sender` the gas used in specified `_gasToken`.
|
||||
|
||||
```solidity
|
||||
function executeGasRelay(bytes calldata _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, bytes calldata _signature) external;
|
||||
```
|
||||
|
||||
### executeGasRelayMsg
|
||||
|
||||
Returns the `executeGasRelay` message used for signing messages..
|
||||
|
||||
```solidity
|
||||
function executeGasRelayMsg(uint256 _nonce, bytes memory _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, address _gasRelayer) public pure returns (bytes memory);
|
||||
```
|
||||
|
||||
#### executeGasRelayERC191Msg
|
||||
|
||||
Returns the [EIP-191] of `executeGasRelayMsg` used for signing messages and for verifying the execution.
|
||||
|
||||
```solidity
|
||||
function executeGasRelayERC191Msg(uint256 _nonce, bytes memory _execData, uint256 _gasPrice, uint256 _gasLimit, address _gasToken, address _gasRelayer) public view returns (bytes memory);
|
||||
```
|
||||
|
||||
#### lastNonce
|
||||
|
||||
Returns the current nonce for the gas relayed messages.
|
||||
|
||||
```solidity
|
||||
function lastNonce() public returns (uint nonce);
|
||||
```
|
||||
|
||||
### Signed Message
|
||||
|
||||
The signed message require the following fields:
|
||||
|
||||
* Nonce: A nonce *or* a timestamp;
|
||||
* Execute Data: the bytecode to be executed by the account contract;
|
||||
* Gas Price: The gas price (paid in the selected token);
|
||||
* Gas Limit: The gas reserved to the relayed execution;
|
||||
* Gas Token: A token in which the gas will be paid (leave 0 for ether);
|
||||
* Gas Relayer: the authorized relayer for this message.
|
||||
|
||||
#### Signing the message
|
||||
|
||||
The message **MUST** be signed as [EIP-191] standard, and the called contract m**MUST**ust also implement [EIP-1271] which must validate the signed messages.
|
||||
|
||||
Messages **MUST** be signed by the owner of the account contract executing. If the owner is a contract, it must implement [EIP-1271] interface and forward validation to it.
|
||||
|
||||
In order to be compliant, the transaction **MUST** request to sign a "messageHash" that is a concatenation of multiple fields.
|
||||
|
||||
The fields **MUST** be constructed as this method:
|
||||
|
||||
The first and second fields are to make it [EIP-191] compliant. Starting a transaction with `byte(0x19)` ensure the signed data from being a [valid ethereum transaction](https://github.com/ethereum/wiki/wiki/RLP). The second argument is a version control byte. The third being the validator address (the account contract address) accoring to version 0 of [EIP-191]. The remaining arguments being the application specific data for the gas relay: chainID as per [EIP-1344], execution nonce, execution data, agreed gas Price, gas limit of gas relayed call, gas token to pay back and gas relayer authorized to receive reward.
|
||||
|
||||
The [EIP-191] message must be constructed as following:
|
||||
```solidity
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
byte(0x19), //ERC-191 - the initial 0x19 byte
|
||||
byte(0x0), //ERC-191 - the version byte
|
||||
address(this), //ERC-191 - version data (validator address)
|
||||
chainID,
|
||||
bytes4(
|
||||
keccak256("executeGasRelay(uint256,bytes,uint256,uint256,address,address)")
|
||||
),
|
||||
_nonce,
|
||||
_execData,
|
||||
_gasPrice,
|
||||
_gasLimit,
|
||||
_gasToken,
|
||||
_gasRelayer
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
User pain points:
|
||||
|
||||
* users don't want to think about ether
|
||||
@ -32,68 +124,26 @@ App developer pain points:
|
||||
|
||||
Using signed messages, specially combined with an account contract that holds funds, and multiple disposable ether-less keys that can sign on its behalf, solves many of these pain points.
|
||||
|
||||
### Implementation
|
||||
### Multiple signatures
|
||||
|
||||
The signed messages require the following fields:
|
||||
More than one signed transaction with the same parameter can be executed by this function at the same time, by passing all signatures in the `messageSignatures` field. That field will split the signature in multiple 72 character individual signatures and evaluate each one. This is used for cases in which one action might require the approval of multiple parties, in a single transaction.
|
||||
|
||||
* execData: the bytecode to be executed by the account contract
|
||||
* Nonce: A nonce *or* a timestamp
|
||||
* GasToken: A token in which the gas will be paid (leave 0 for ether)
|
||||
* Gasprice: The gas price (paid in the selected token);
|
||||
* GasLimit: The gas reserved to the relayed execution
|
||||
* GasRelayer: the authorized relayer for this message
|
||||
If multiple signatures are required, then all signatures should then be *ordered by account* and the account contract should implement signatures checks locally (`JUMP`) on [EIP-1271] interface which might forward (`STATIC_CALL`) the [EIP-1271] signature check to owner contract.
|
||||
|
||||
#### Signing the message
|
||||
### Keep track of nonces:
|
||||
|
||||
The executor contract must implement [EIP-1271] which would validate the signed messages,
|
||||
|
||||
Messages must be signed by the owner of the account contract executing. If the owner is a contract, it must implement EIP1271 interface and forward validation to it.
|
||||
|
||||
In order to be compliant, the transaction **MUST** request to sign a messageHash that is a concatenation of multiple fields.
|
||||
|
||||
The fields **MUST** be constructed as this method:
|
||||
|
||||
The first and second fields are to make it [EIP-191] compliant. Starting a transaction with byte(0x19) ensure the signed data from being a [valid ethereum transaction](https://github.com/ethereum/wiki/wiki/RLP). The second argument is a version control byte. The third being the validator address (the account contract address) accoring to version 0 of ERC-191. The remaining arguments being the application specific data for the gas relay: execution nonce, execution data, agreed gas Price, gas limit of gas relayed call, gas token to pay back and gas relayer authorized to receive reward.
|
||||
|
||||
The ERC-191 message must be constructed as following:
|
||||
```solidity
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
byte(0x19), //ERC-191 - the initial 0x19 byte
|
||||
byte(0x0), //ERC-191 - the version byte
|
||||
address(this), //ERC-191 - version data (validator address)
|
||||
bytes4(
|
||||
keccak256("executeGasRelay(uint256,bytes,uint256,uint256,address,address)")
|
||||
),
|
||||
_nonce,
|
||||
_execData,
|
||||
_gasPrice,
|
||||
_gasLimit,
|
||||
_gasToken,
|
||||
_gasRelayer
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
#### Backwards and forwards capability
|
||||
|
||||
`_execData` contains arbitrary data which will be evaluated by the account contract. It's up to the account contract to handle properly this data, which can be another abi encoded call. Contracts can gas relay any behavior, such as its management calls, or it's meta transactions.
|
||||
|
||||
#### Multiple signatures
|
||||
|
||||
If multiple signatures are required, then all signatures should then be *ordered by account* and the account contract should implement signatures checks locally on EIP-1271 interface or forward the EIP-1271 signature check to the owner contract.
|
||||
|
||||
#### Keep track of nonces:
|
||||
Note that `executeGasRelay` function does not take a `_nonce` as parameter. The contract knows what is the current nonce, and can only execute the transactions in order, therefore there is no reason
|
||||
|
||||
Nonces work similarly to normal ethereum transactions: a transaction can only be executed if it matches the last nonce + 1, and once a transaction has occurred, the `lastNonce` will be updated to the current one. This prevents transactions to be executed out of order or more than once.
|
||||
|
||||
Contracts should accept transactions without nonce (nonce = 0). The contract then must keep the full hash of the transaction to prevent it from being replayed. This option allows contracts to have more flexibilities as you can sign a transaction that can be executed out of order or not at all, but it uses more memory for each transaction. It can be used, for instance, for transactions that the user wants to schedule in the future but cannot know its future nonce, or transactions that are made for state channel contracts that are not guaranteed to be executed or are only executed when there's some dispute.
|
||||
Contracts may accept transactions without nonce (nonce = 0). The contract then must keep the full hash of the transaction to prevent it from being replayed. This would allows contracts to have more flexibilities as you can sign a transaction that can be executed out of order or not at all, but it uses more memory for each transaction. It can be used, for instance, for transactions that the user wants to schedule in the future but cannot know its future nonce, or transactions that are made for state channel contracts that are not guaranteed to be executed or are only executed when there's some dispute.
|
||||
|
||||
### Execute transaction
|
||||
|
||||
After signature validation, the evaluation of `_execBytes` is up to the account contract implementation, it's role of the wallet to properly use the account contract and it's gas relay method.
|
||||
A common pattern is to expose an interface which can be only called by the contract itself. The `_execBytes` could enteirely forward the call in this way, as example: `address(this).call.gas(_gasLimit)(_execData);`
|
||||
A common pattern is to expose an interface which can be only called by the contract itself. The `_execBytes` could entirely forward the call in this way, as example: `address(this).call.gas(_gasLimit)(_execData);`
|
||||
Where `_execData` could call any method of the contract itself, for example:
|
||||
|
||||
- `call(address to, uint256 value, bytes data)`: allow any type of ethereum call be performed;
|
||||
- `create(uint256 value, bytes deployData)`: allows create contract
|
||||
- `create2(uint256 value, bytes32 salt, bytes deployData)`: allows create contract with deterministic address
|
||||
@ -102,8 +152,8 @@ Where `_execData` could call any method of the contract itself, for example:
|
||||
- `changeOwner(address newOwner)`: Some account contracts might allow change of owner
|
||||
- `foo(bytes bar)`: Some account contracts might have custom methods of any format.
|
||||
|
||||
The standarization of account contracts is not scope of this ERC, and is presented here only for illustration on possible implementations.
|
||||
Using a self call to eval `_execBytes` is not mandatory, depending on the account contract logic, the eval could be done locally.
|
||||
The standardization of account contracts is not scope of this ERC, and is presented here only for illustration on possible implementations.
|
||||
Using a self call to evaluate `_execBytes` is not mandatory, depending on the account contract logic, the evaluation could be done locally.
|
||||
|
||||
### Gas accounting and refund
|
||||
|
||||
@ -115,91 +165,29 @@ If the executed transaction fails internally, nonces should still be updated and
|
||||
|
||||
Contracts are not obligated to support ether or any other token they don’t want and can be implemented to only accept refunds in a few tokens of their choice.
|
||||
|
||||
**Deployers of transactions have no guarantees that the contract they are interacting with correctly implements the standard and they will be reimbursed for gas, so they should maintain their own white/blacklists of contracts to support, as well as keep track of which tokens and for which gasPrice they’re willing to deploy transactions.**
|
||||
### Usage examples
|
||||
|
||||
### Supported functions
|
||||
This scheme opens up a great deal of possibilities on interaction as well as different experiments on business models:
|
||||
|
||||
```solidity
|
||||
/**
|
||||
* @notice execute something for this account and get paid the proportional gas in specified token.
|
||||
* @param _execData execution data (anything)
|
||||
* @param _gasPrice price in `_gasToken` paid back to `msg.sender` per gas unit used
|
||||
* @param _gasLimit maximum gas of this transacton
|
||||
* @param _gasToken token being used for paying `msg.sender`, if address(0), ether is used
|
||||
* @param _signature rsv concatenated ethereum signed message signatures required
|
||||
*/
|
||||
function executeGasRelay(
|
||||
bytes calldata _execData,
|
||||
uint256 _gasPrice,
|
||||
uint256 _gasLimit,
|
||||
address _gasToken,
|
||||
bytes calldata _signature
|
||||
)
|
||||
external;
|
||||
|
||||
```
|
||||
* Apps can create individual identities contract for their users which holds the actual funds and then create a different private key for each device they log into. Other apps can use the same identity and just ask to add permissioned public keys to manage the device, so that if one individual key is lost, no ether is lost.
|
||||
* An app can create its own token and only charge their users in its internal currency for any ethereum transaction. The currency units can be rounded so it looks more similar to to actual amount of transactions: a standard transaction always costs 1 token, a very complex transaction costs exactly 2, etc. Since the app is the issuer of the transactions, they can do their own Sybil verifications and give a free amount of currency units to new users to get them started.
|
||||
* A game company creates games with a traditional monthly subscription, either by credit card or platform-specific microtransactions. Private keys never leave the device and keep no ether and only the public accounts are sent to the company. The game then signs transactions on the device with gas price 0, sends them to the game company which checks who is an active subscriber and batches all transactions and pays the ether themselves. If the company goes bankrupt, the gamers themselves can set up similar subscription systems or just increase the gas price. End result is a **ethereum based game in which gamers can play by spending apple, google or xbox credits**.
|
||||
* A standard token is created that doesn’t require its users to have ether, and instead allows tokens to be transferred by paying in tokens. A wallet is created that signs messages and send them via whisper to the network, where other nodes can compete to download the available transactions, check the current gas price, and select those who are paying enough tokens to cover the cost. **The result is a token that the end users never need to keep any ether and can pay fees in the token itself.**
|
||||
* A DAO is created with a list of accounts of their employees. Employees never need to own ether, instead they sign messages, send them to whisper to a decentralized list of relayers which then deploy the transactions. The DAO contract then checks if the transaction is valid and sends ether to the deployers. Employees have an incentive not to use too many of the companies resources because they’re identifiable. The result is that the users of the DAO don't need to keep ether, and **the contract ends up paying for it's own gas usage**.
|
||||
|
||||
Executes a signed message.
|
||||
## Backwards Compatibility
|
||||
|
||||
More than one signed transaction with the same parameter can be executed by this function at the same time, by passing all signatures in the `messageSignatures` field. That field will split the signature in multiple 72 character individual signatures and evaluate each one. This is used for cases in which one action might require the approval of multiple parties, in a single transaction.
|
||||
There is no issues with backwards compatibility, however for future upgrades, as `_execData` contains arbitrary data evaluated by the account contract, it's up to the contract to handle properly this data and therefore contracts can gas relay any behavior with the current interface.
|
||||
|
||||
## Test Cases
|
||||
|
||||
```solidity
|
||||
/**
|
||||
* @notice gets ERC191 signing Hash of execute gas relay message
|
||||
* @param _nonce current account nonce
|
||||
* @param _execData execution data (anything)
|
||||
* @param _gasPrice price in `_gasToken` paid back to `_gasRelayer` per gas unit used
|
||||
* @param _gasLimit maximum gas of this transacton
|
||||
* @param _gasToken token being used for paying `_gasRelayer`
|
||||
* @param _gasRelayer beneficiary of gas refund
|
||||
* @return executeGasRelayERC191Hash the message to be signed
|
||||
*/
|
||||
function executeGasRelayERC191Msg(
|
||||
uint256 _nonce,
|
||||
bytes memory _execData,
|
||||
uint256 _gasPrice,
|
||||
uint256 _gasLimit,
|
||||
address _gasToken,
|
||||
address _gasRelayer
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (bytes memory)
|
||||
{
|
||||
return abi.encodePacked(
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
byte(0x19), //ERC-191 - the initial 0x19 byte
|
||||
byte(0x0), //ERC-191 - the version byte
|
||||
address(this), //ERC-191 - the validator address
|
||||
bytes4(
|
||||
keccak256("executeGasRelay(uint256,bytes,uint256,uint256,address,address)")
|
||||
),
|
||||
_nonce,
|
||||
_execData,
|
||||
_gasPrice,
|
||||
_gasLimit,
|
||||
_gasToken,
|
||||
_gasRelayer
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
A read only function that checks if the transaction will be executable and how much gas it’s expected to cost.
|
||||
|
||||
`lastNonce() public returns (uint nonce)`
|
||||
|
||||
Both are simple read only functions that return the last used Nonce and last used timestamp.
|
||||
TBD
|
||||
|
||||
## Implementation
|
||||
|
||||
One initial implementation of such a contract can be found at [the Identity Gas Relay at the Status repository](https://github.com/status-im/contracts/blob/73-economic-abstraction/contracts/identity/IdentityGasRelay.sol)
|
||||
One initial implementation of such a contract can be found at [Status.im account-contracts repository](https://github.com/status-im/account-contracts/blob/develop/contracts/account/AccountGasAbstract.sol)
|
||||
|
||||
## Similar implementations
|
||||
### Similar implementations
|
||||
|
||||
The idea of using signed messages as executable intent has been around for a while and many other projects are taking similar approaches, which makes it a great candidate for a standard that guarantees interoperability:
|
||||
|
||||
@ -215,24 +203,20 @@ The idea of using signed messages as executable intent has been around for a whi
|
||||
|
||||
Swarm city uses a similar proposition for etherless transactions, called [Gas Station Service](https://github.com/swarmcity/SCLabs-gasstation-service), but it's a different approach. Instead of using signed messages, a traditional ethereum transaction is signed on an etherless account, the transaction is then sent to a service that immediately sends the exact amount of ether required and then publishes the transaction.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
## Usage examples
|
||||
Deployers of transactions (relayers) should be able to call untrusted contracts, which provides no guarantees that the contract they are interacting with correctly implements the standard and they will be reimbursed for gas. To prevent being fooled by bad implementations, relayers must **estimate the outcome of a transaction**, and only include/sign transactions which have a desired outcome.
|
||||
|
||||
This scheme opens up a great deal of possibilities on interaction as well as different experiments on business models:
|
||||
|
||||
* Apps can create individual identities contract for their users which holds the actual funds and then create a different private key for each device they log into. Other apps can use the same identity and just ask to add permissioned public keys to manage the device, so that if one individual key is lost, no ether is lost.
|
||||
* An app can create its own token and only charge their users in its internal currency for any ethereum transaction. The currency units can be rounded so it looks more similar to to actual amount of transactions: a standard transaction always costs 1 token, a very complex transaction costs exactly 2, etc. Since the app is the issuer of the transactions, they can do their own Sybil verifications and give a free amount of currency units to new users to get them started.
|
||||
* A game company creates games with a traditional monthly subscription, either by credit card or platform-specific microtransactions. Private keys never leave the device and keep no ether and only the public accounts are sent to the company. The game then signs transactions on the device with gas price 0, sends them to the game company which checks who is an active subscriber and batches all transactions and pays the ether themselves. If the company goes bankrupt, the gamers themselves can set up similar subscription systems or just increase the gas price. End result is a **ethereum based game in which gamers can play by spending apple, google or xbox credits**.
|
||||
* A standard token is created that doesn’t require its users to have ether, and instead allows tokens to be transferred by paying in tokens. A wallet is created that signs messages and send them via whisper to the network, where other nodes can compete to download the available transactions, check the current gas price, and select those who are paying enough tokens to cover the cost. **The result is a token that the end users never need to keep any ether and can pay fees in the token itself.**
|
||||
* A DAO is created with a list of accounts of their employees. Employees never need to own ether, instead they sign messages, send them to whisper to a decentralized list of relayers which then deploy the transactions. The DAO contract then checks if the transaction is valid and sends ether to the deployers. Employees have an incentive not to use too many of the companies resources because they’re identifiable. The result is that the users of the DAO don't need to keep ether, and **the contract ends up paying for it's own gas usage**.
|
||||
|
||||
### References
|
||||
|
||||
* [Universal Logins talk at UX Unconf, Toronto](https://www.youtube.com/watch?v=qF2lhJzngto)
|
||||
Is also interest of relayers to maintaining a private reputation of contracts they interact with, as well as keep track of which tokens and for which `gasPrice` they’re willing to deploy transactions.
|
||||
|
||||
## Copyright
|
||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
||||
## References
|
||||
|
||||
* [Universal Logins talk at UX Unconf, Toronto](https://www.youtube.com/watch?v=qF2lhJzngto)
|
||||
|
||||
[EIP-20]: https://eips.ethereum.org/EIPS/eip-20
|
||||
[EIP-191]: https://eips.ethereum.org/EIPS/eip-191
|
||||
[EIP-1271]: https://eips.ethereum.org/EIPS/eip-1271
|
||||
[EIP-1271]: https://eips.ethereum.org/EIPS/eip-1271
|
||||
[EIP-1344]: https://eips.ethereum.org/EIPS/eip-1344
|
||||
|
Loading…
x
Reference in New Issue
Block a user