Skip to content
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

EIP1035: Transaction execution batching and delegation #1035

Closed
k06a opened this issue Apr 25, 2018 · 15 comments
Closed

EIP1035: Transaction execution batching and delegation #1035

k06a opened this issue Apr 25, 2018 · 15 comments
Labels

Comments

@k06a
Copy link
Contributor

k06a commented Apr 25, 2018


eip: EIP1035
title: Transaction execution batching and delegation
author: Anton Bukov (@k06a) k06aaa@gmail.com
discussions-to: k06aaa@gmail.com
status: Draft
type: Core
category: Core
created: 2018-04-25
updated: 2018-04-26
relates: #901

Simple Summary

It will be great to allow trustless delegated execution of the transactions inside EVM. I mean some account can sign target, call data, account nonce, network chain_id and pass them to any other, who will be able to perform this call and pay Ethereum fees like this:

target.authorizedcall(data, account , nonce, chain_id, signature)

This also will allow batching/chaining account transactions and many other features. One more use case: sender of any ERC20 tokens can signed necessary call and pass it to receiver, and receiver (exchange?) will be able to pay fees for this transaction.

Abstract

Abstract implementation of target.authorizedcall(data, account, nonce, chain_id, signature) method should perform:

  1. Check nonce of the account to be valid
  2. Check chain_id to be valid for this network
  3. Check signature of target|data|nonce|chain_id and extract signer
  4. Check signer is the same as account
  5. Perform data call on target with msg.sender == account
  6. Increment account nonce

Motivation

  1. Ethereum fees is a real problem for some new users, who had some (bounty?) tokens and can't even spend this tokens without buying some ether.
  2. Look at ERC827 Token Standard (ERC20 Extension) #827, ERC1003 Token Standard (ERC20 Extension) #1003 – these protocols are trying to solve the problem of batching/chaining user transactions.

Specification

From solidity side this should looks like function on every smart contract:

function authorizedcall(bytes data, address account, uint256 nonce, uint256 chain_id, bytes signature);

Test Cases

Test cases for an implementation are mandatory for EIPs that are affecting consensus changes. Other EIPs can choose to include links to test cases if applicable.

Implementation

This is working example: https://github.com/bitclave/Feeless

Complete code:

import { ECRecovery } from "zeppelin-solidity/contracts/ECRecovery.sol";


contract Feeless {
    
    address internal msgSender;
    mapping(address => uint256) public nonces;
    
    modifier feeless {
        if (msgSender == address(0)) {
            msgSender = msg.sender;
            _;
            msgSender = address(0);
        } else {
            _;
        }
    }

    function performFeelessTransaction(address sender, bytes data, uint256 nonce, bytes sig) public payable {
        bytes memory prefix = "\x19Ethereum Signed Message:\n32";
        bytes32 hash = keccak256(prefix, keccak256(data, nonce));
        msgSender = ECRecovery.recover(hash, sig);
        require(msgSender == sender);

        require(nonces[msgSender]++ == nonce);
        require(address(this).call.value(msg.value)(data));
        msgSender = address(0);
    }
    
}

Usage:

contract MyToken is StandardToken, Feeless {

    string public constant symbol = "XXX";
    string public constant name = "MyToken";
    uint8 public constant decimals = 18;
    string public constant version = "1.0";

    function transfer(address _to, uint256 _value) public feeless returns (bool) {
        balances[msgSender] = balances[msgSender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        Transfer(msgSender, _to, _value);
        return true;
    }

}

Signing call data:

const target = myToken.options.address;
const nonce = await myToken.methods.nonces(wallet1).call();
const data = await myToken.methods.transfer(wallet2, 5 * 10**18).encodeABI();
const hash = web3.utils.sha3(target + data.substr(2) + web3.utils.toBN(nonce).toString(16,64));
const sig = await web3.eth.accounts.sign(hash, wallet1PrivateKey);

Delegated execution by any other account:

await myToken.performFeelessTransaction(wallet1, target, data, nonce, sig).send({ from: wallet2 });
@k06a k06a changed the title Transaction execution delegation EIP1035: Transaction execution delegation Apr 25, 2018
@MicahZoltu
Copy link
Contributor

I would personally prefer this to be an EVM layer change, rather than contract layer. It feels silly that the person authorizing the transaction has to be the same person paying fees for the transaction. Miners care whether they get fees, they don't care about who they get fees from. Users care about other people not being able to use/spend assets under the control of their private keys, they don't care about how the transaction ends up in a block.

I would like to see transactions be two layers, the outer layer is signed by the fee payer and contains feePayerNonce, gasPrice, gasLimit, feePayerSignature, transactorNonce, to, value, data, transactorSignature. It would be even cooler if transactorNonce, to, value, data, transactorSignature was actually an array, for batching.

The above scheme would allow users to pay for "transaction submission services" off-chain via other mechanisms (e.g., monthly subscription, pre-paid, paid with fiat, add-on benefit to other services, etc.). It would also indirectly enable gas payments in non-eth if batching was supported, as the transactor executing the transaction could provide two signed transactions to the feePayer, one that transfers them some tokens and another that does the thing desired, with the nonce of the token transfer being one more than the nonce of the desired action. The feePayer can batch these two transactions together to effectively be paid in tokens, while miners are still paid in ETH. Alternatively, if the transactor is willing to enter into a trusted relationship with the feePayer then they could pre-pay for submissions via something like DAI.

@k06a
Copy link
Contributor Author

k06a commented Apr 26, 2018

@MicahZoltu the more I think about this proposal the more I see this is the solution for tx batching to replace ERC827 (and ERC1003). Ethereum also can provide default smart contract (0x05?) for batching transactions with the methods like this:

function batchWithRequire(
    address account,
    address[] targets,
    bytes[] datas,
    uint256 firstNonce,
    uint256 chain_id,
    bytes[] sigs) public
{
    for (uint i = 0; i < targets.length; i++) {
        require(targets[i].authorizedcall(datas[i], account, firstNonce + i, chain_id, sigs[i]));
    }
}   

@k06a k06a changed the title EIP1035: Transaction execution delegation EIP1035: Transaction execution batching and delegation Apr 26, 2018
@k06a
Copy link
Contributor Author

k06a commented Jun 24, 2018

This EIP will help a lot in inter-chain development.

@ukstv
Copy link

ukstv commented Jun 25, 2018

Yay! That would greatly help with off-chain scaling schemes like state channels.

@PhABC
Copy link
Contributor

PhABC commented Jul 2, 2018

Strongly related to #1077 by @alexvandesande. Have you considered collaborating on this one @k06a?

@k06a
Copy link
Contributor Author

k06a commented Jul 2, 2018

@PhABC thanks, I'll dig into it!

@MohamedLEGH
Copy link

I agree with @MicahZoltu , I think we should allow any Ethereum transaction to have gas payment delegated, with no need of in contract function. I think this proposal is related to #1228 and #865 .

@rstormsf
Copy link

rstormsf commented Sep 1, 2018

If someone is looking for ETH, ERC20 batch sender - feel free to use:
https://multisender.app

@csajedi
Copy link

csajedi commented Jun 11, 2019

I would personally prefer this to be an EVM layer change, rather than contract layer. It feels silly that the person authorizing the transaction has to be the same person paying fees for the transaction. Miners care whether they get fees, they don't care about who they get fees from. Users care about other people not being able to use/spend assets under the control of their private keys, they don't care about how the transaction ends up in a block.

I would like to see transactions be two layers, the outer layer is signed by the fee payer and contains feePayerNonce, gasPrice, gasLimit, feePayerSignature, transactorNonce, to, value, data, transactorSignature. It would be even cooler if transactorNonce, to, value, data, transactorSignature was actually an array, for batching.

The above scheme would allow users to pay for "transaction submission services" off-chain via other mechanisms (e.g., monthly subscription, pre-paid, paid with fiat, add-on benefit to other services, etc.). It would also indirectly enable gas payments in non-eth if batching was supported, as the transactor executing the transaction could provide two signed transactions to the feePayer, one that transfers them some tokens and another that does the thing desired, with the nonce of the token transfer being one more than the nonce of the desired action. The feePayer can batch these two transactions together to effectively be paid in tokens, while miners are still paid in ETH. Alternatively, if the transactor is willing to enter into a trusted relationship with the feePayer then they could pre-pay for submissions via something like DAI.

Is this EIP continuing? I am especially interested in details pertaining to the EVM change described above. It would simplify the creation of a retroactive metatransactions upgrade to all existing tokens and contracts. relevant to EIPs: 1776, 965, 985.

@MicahZoltu
Copy link
Contributor

@AlexeyAkhunov has been pushing recently for changing the transaction protocol so it can evolve over time. It may be worth considering #1035 (comment) in that design.

@csajedi
Copy link

csajedi commented Jun 16, 2019

@MicahZoltu I stumbled upon this EIP while having similar discussions regarding enabling meta transactions on the Zilliqa blockchain. My overall goal is driving towards subscription-based recurring payments that would drastically benefit from transaction batching. I've linked your comment to justify work breaking out the messaging standard like so. In that regard, I'd like to stay in the loop and contribute as I can to the Ethereum effort. Kudos to you ETH guys for discussing and innovating in the open... Zilliqa really needs an EIP repository.

@longduer
Copy link

longduer commented Aug 1, 2019

what if the ERC20 token already existed,Could implement the feeless contract? if can ,how

@ARJUN-R34
Copy link

import { ECRecovery } from "zeppelin-solidity/contracts/ECRecovery.sol";

contract Feeless {

address internal msgSender;
mapping(address => uint256) public nonces;

modifier feeless {
    if (msgSender == address(0)) {
        msgSender = msg.sender;
        _;
        msgSender = address(0);
    } else {
        _;
    }
}

function performFeelessTransaction(address sender, bytes data, uint256 nonce, bytes sig) public payable {
    bytes memory prefix = "\x19Ethereum Signed Message:\n32";
    bytes32 hash = keccak256(prefix, keccak256(data, nonce));
    msgSender = ECRecovery.recover(hash, sig);
    require(msgSender == sender);

    require(nonces[msgSender]++ == nonce);
    require(address(this).call.value(msg.value)(data));
    msgSender = address(0);
}

}


Usage:

contract MyToken is StandardToken, Feeless {

string public constant symbol = "XXX";
string public constant name = "MyToken";
uint8 public constant decimals = 18;
string public constant version = "1.0";

function transfer(address _to, uint256 _value) public feeless returns (bool) {
    balances[msgSender] = balances[msgSender].sub(_value);
    balances[_to] = balances[_to].add(_value);
    Transfer(msgSender, _to, _value);
    return true;
}

}


Signing call data:

const target = myToken.options.address;
const nonce = await myToken.methods.nonces(wallet1).call();
const data = await myToken.methods.transfer(wallet2, 5 * 10**18).encodeABI();
const hash = web3.utils.sha3(target + data.substr(2) + web3.utils.toBN(nonce).toString(16,64));
const sig = await web3.eth.accounts.sign(hash, wallet1PrivateKey);


Delegated execution by any other account:

await myToken.performFeelessTransaction(wallet1, target, data, nonce, sig).send({ from: wallet2 });

If all it takes is just one public address to delegate the transaction, how is the security maintained ?
What is stopping someone from delegating the transaction to random accounts ?

@github-actions
Copy link

github-actions bot commented Dec 7, 2021

There has been no activity on this issue for two months. It will be closed in a week if no further activity occurs. If you would like to move this EIP forward, please respond to any outstanding feedback or add a comment indicating that you have addressed all required feedback and are ready for a review.

@github-actions github-actions bot added the stale label Dec 7, 2021
@github-actions
Copy link

This issue was closed due to inactivity. If you are still pursuing it, feel free to reopen it and respond to any feedback or request a review in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants