-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
ERC865: Pay transfers in tokens instead of gas, in one transaction #865
Comments
I'm working on something simmilar for about a week now, but I've come with a different implementation. |
Nice work @3esmit. Do you have any comments on the pros / cons of this implementation vs yours? |
3esmit pretty cool! I really like your approach on having a separate method for generating the keccak, that can be re-used with web3 for simplifying the keccak generation off-chain. const fee = 10;
const amount = 100;
const token = this.token.address;
const from = alice;
const to = bob;
const delegate = charlie;
const nonce = 32;
const bufferedAddress = (address) => {
return Buffer.from(ethUtil.stripHexPrefix(address), 'hex');
};
const bufferedInt = (int) => {
return ethUtil.setLengthLeft(int, 32);
};
const formattedByte32 = (bytes) => {
return ethUtil.addHexPrefix(bytes.toString('hex'));
};
const components = [
bufferedAddress(delegate),
bufferedAddress(token),
bufferedInt(nonce),
bufferedAddress(from),
bufferedAddress(to),
bufferedInt(amount),
bufferedInt(fee)
];
const tightPack = Buffer.concat(components);
const hashedTightPack = ethUtil.sha3(tightPack);
const alicePrivateKey = Buffer.from('c88b703fb08cbea894b6aeff5a544fb92e78a18e19814cd85da83b71f772aa6c', 'hex');
const sig = ethUtil.ecsign(hashedTightPack, alicePrivateKey)
const pubkey = ethUtil.ecrecover(hashedTightPack, sig.v, sig.r, sig.s)
const address = ethUtil.publicToAddress(pubkey)
const tx = await this.token.delegatedTransfer(
nonce,
from,
to,
amount,
fee,
sig.v,
formattedByte32(sig.r),
formattedByte32(sig.s), {from: charlie}); Pretty verbose and error prone. function getTransferHash(
address _to,
uint256 _value,
uint256 _gasPrice,
uint256 _nonce
)
constant
public
returns(bytes32 txHash)
{
//"edde766e": "transferPreSigned(uint8,bytes32,bytes32,address,uint256,uint256,uint256)",
txHash = keccak256(address(this), bytes4(0xedde766e), _to, _value, _gasPrice, _nonce);
} to function getTransferHash(
address _contract
address _to,
uint256 _value,
uint256 _gasPrice,
uint256 _nonce
)
constant
public
returns(bytes32 txHash)
{
//"12345678": "getTransferHash(address,address,uint256,uint256,uint256)",
txHash = keccak256(bytes4(0x12345678), _contract, _to, _value, _gasPrice, _nonce);
} We now have a reliable way to build the exact same keccak on chain and off chain, by simply using the ABI of the contract and directly hashing the data coming from: token.getTransferHash.request(_contract, _to, _value, _gasPrice, _nonce) What do you think? |
@ptrwtts My approch support arbitrary contract executions in SNT network, so we can even create a new contract and pay the gas relayed by SNT "gas". Aswell don't need quotes from delegates, the system works simillar way ETH gasPrice market works, but instead of being backed by USD value is by ETH value. @lgalabru I'm not sure why supporting any contract address for building the txHash, initially I did so and used @bokkypoobah live in which network? Mainnet? |
@3esmit BTTSTokenFactory on mainnet - https://etherscan.io/address/0x14aabc5ade82240330e5be05d8ef350661aebb8a#code and a token - https://etherscan.io/address/0x4ac00f287f36a6aad655281fe1ca6798c9cb727b#code . And while developing it, I was getting an out of stack space for variables problem in Solidity when using r,s and v, so switched to using sig. I also added *Check functions so the service provider is able to confirm that the transaction has not already been executed. And to get the exact error before executing the transaction, if the transaction is expected to fail. |
@3esmit the idea was to use the same method (indeed, pure), on-chain and off chain. bytes32 hash = getTransferHash(
address(this)
_to,
_value,
_gasPrice,
_nonce
) And off-chain, you could have use: const payload = await token.getTransferHash.request(token.address, _to, _value, _gasPrice, _nonce);
const data = payload.params[0].data;
const hash = ethUtil.sha3(data); After testing this approach, the problem is that off chain, when building data, all the arguments are padded vs being tightly-packed on chain.
you're hashing:
|
Fundamentally to process the contract call you need to be synced with state, the only difference is that you can call that function |
Wouldn't a way simpler solution be to have contracts set gasprice & startgas so miners are allowed to take gas from the contracts balance? Then nodes/miners would use limited gas per transaction to determine if these transactions are mineable at all (gas price is high enough) and warrant to be re-broadcast over the network. In other words, nodes/miners are executing the code of transactions (where the sender doesn't have enough funds themselves), and which contain 'gasprice & startgas'-opcodes. Contracts can then convert tokens to gas themselves. Basically wallets would allow users to set gasprice & startgas in the used token, and the contract converts that to ETH. Or if the contract is rather predictable in gas usage make it even easier for users by allowing users to only set gasprice. |
Very cool. This would be extremely useful from a UX perspective (users don't have to hold multiple tokens). |
@seweso I think the change you're describing needs to happen on the EVM level (and is on the roadmap if my understanding is correct). |
It would be good to standardize this to cover delegated ETH transfers as well when token = 0x0, for things like withdrawing from ring signature mixers. As otherwise, you would have to "pre-fund" the withdrawing account, thereby linking your deposit and withdraw addresses. |
@lgalabru Yes agreed. And it is a direction Ethereum needs to go into. Do you know how this is called on the roadmap? I couldn't find it. |
I think this is a great idea and an answer to my problem I’ve been struggling with: https://forum.ethereum.org/discussion/16990/transaction-fees-for-an-erc20-currency I only have 1 concern though: Wouldn’t this make Ether itself redundant? If miners would be able to directly accept tokens as payment, the utility of Ether would become less relevant except for staking and securing the network (which is still extremely valuable of course). |
@bokkypoobah, i'm curious to ask. What happens if the tokens are worthless? How will BTTS pay for the gas in ether when the token has no value? |
@Philipinho I plan to provide a smart contract that the token contract owner has to top up with ETH, and this smart contract will buy tokens from the BTTS service provider at a specified rate. |
I think that defining a gasPrice instead a fee is more safe and dynamic, there is no reason for not doing this. This is really important for approveAndCallPreSigned, because this function can call arbritary execution in other contract that can be hard to be estimated, so we place the responsability of being safe to signer. Please review my work in MiniMeTokenPreSigned.sol and derive from it, it's the same GNU license followed by MiniMeToken.sol (I guess GPLv3, @jbaylina?). The interface I suggest is the following: pragma solidity ^0.4.17;
/**
@notice Implements PreSigned ERC20Token operations (and approveAndCall(address,uint256,bytes);
*/
contract ERC865 {
/**
* @notice Include a presigned `"a9059cbb": "transfer(address,uint256)"`
* @param _signature Signed transfer
* @param _to The address of the recipient
* @param _value The value of tokens to be transferred
* @param _gasPrice How much tokens willing to pay per gas
* @param _nonce Presigned transaction number.
*/
function transferPreSigned(bytes _signature, address _to, uint256 _value, uint256 _gasPrice, uint256 _nonce) public;
/**
* @notice Include a presigned `""095ea7b3": "approve(address,uint256)"`
* @param _signature Signed transfer
* @param _to The address of the recipient
* @param _value The value of tokens to be transferred
* @param _gasPrice How much tokens willing to pay per gas
* @param _nonce Presigned transaction number.
*/
function approvePreSigned(bytes _signature, address _to, uint256 _value, uint256 _gasPrice, uint256 _nonce) public;
/**
* @notice Include a presigned `"cae9ca51": "approveAndCall(address,uint256,bytes)"`
* @param _signature Signed transfer
* @param _to The address of the recipient
* @param _value The value of tokens to be transferred
* @param _extraData option data to send to contract
* @param _gasPrice How much tokens willing to pay per gas
* @param _nonce Presigned transaction number.
*/
function approveAndCallPreSigned(bytes _signature, address _to, uint256 _value, uint256 _extraData, uint256 _gasPrice, uint256 _nonce) public;
} I'm not absolutely sure about the usage of Supporting additional
Including a fixed fee method in side of this 3 signatures would be an option, I'm not totally against it and don't see an actual problem in having this more option, or could be included as a parameter, however I think users will prefer including gasPriced transaction instead of fixedFee because its easier to calculate cost/risk (?). PreSigned contract calls (intended to relay native gas) should also be standarized, so we can have other types of tokens which can use different call methods and wallets would recognize it. //"edde766e": "transfer(address,uint256)",
txHash = keccak256(address(this), bytes4(0xedde766e), keccak256(_to, _value), _gasPrice, _nonce);
txHash = keccak256(address(this), keccak256(bytes4(0xedde766e), _to, _value), _gasPrice, _nonce);
txHash = keccak256(address(this), bytes4(0xedde766e), _to, _value, _gasPrice, _nonce); //seems to be better because cheaper and no reason to tie separatedly the elements. This way will be easier to understand what is being signed, specially for wallets that want give more details about where that hash came from. Also the signatures should be directed towards #191 to motivate wallet developers in agreeing in the common signing method defined there. |
Excellent idea! Why explicitly encode the sender when it's provided by ecrecover, though? Also, why encode the delegate, instead of letting the first person to get a transaction in harvest the fee? |
Great. @lgalabru Could use |
@jdkanani @Arachnid
Great idea! @3esmit |
I have been working on this idea for a side-project, the pay-protocol. This feature is something that can be achieved in a layer 2 protocol rather than adding this functionality to every token contract. Which also has the ability that every token currently deployed could use it, just by having people that want to use this create a ERC20 allowance to the PayProtocol contract or setting it a ERC777 operator. In any case, I think it would be super interesting to standardize the offchain part of this, which would allow people to run nodes looking for profitable token transfers and settle them. |
@lgalabru "Delegated" means that you transfered temporarily trust to other, is not the case here, so I think the naming transferPreSigned() or signedTransfer() are more correct, but I don't think is bad to use @izqui I think your project is a little bit different because is not the token itself that is handeling the moving, seems like is a TokenController? but should work like the same. |
How do you handle the ERC-20 approve() requirement? No one can withdraw funds from your token account without you first approving them, and that approve() step costs ETH. |
@axcrest you are right that in pay protocol if you already have tokens you need to create an approval to the contract. But if you receive the tokens directly in your pay protocol account, then you never need ether to operate. @3esmit you are right the token isn't handling the moving and that's why it is very efficient, token transfers are only settled in the token contracts when tokens need to move in or out of the protocol. This allows to settle MiniMe transfers for ~15k gas (if using bulk transfers, ~36k otherwise) instead of +100k that takes if operating directly on the token. The signed message conforms to the latest #712 spec. It allows for signing providers to show exactly what is going in the hash they are going to be signing. |
@izqui Thanks for the information about signed message spec, I think the community should consensus in a default signed standard ASAP. The main issue this EIP resolves is the need of an account to hold Ether in their balance to move their token. Even being more expansive, it might be cheaper then the process of transferring ether to that account and moving it. MiniMeTokenPresigned behaves differnt when gasPrice is zero, this can be used for an user which have 2 balances, 1 with ether and other with the token, so they can control this account using gas from other account. I can upgrade Raiden contracts to be able of accepting |
A first implementation of this EIP is available here: https://github.com/PROPSProject/props-token-distribution/blob/master/contracts/token/ERC865Token.sol. @3esmit I'm not convinced by this idea of computing the gas cost on the fly because you're adding more complexity to the method, when you want to be really accurate. I skipped the approveAndCall, since it's not part of the ERC20 protocol + there is no clear settlement at this point (ERC223 vs ERC827). |
As far as I can see, there are currently 2 proposals for tokens that allow gasless token transfers:
Are there more proposals? |
Hey everyone, I'm new to this, but this feature is very important to a use case for my project. What is involved in getting this proposal or similar proposals for gas delegation moving and implemented, and how long should I expect this to take? I'm most likely willing to put significant effort into helping this along come summer, I just need some guidance. |
Hello everyone! Total n00b here. I’d like to add a use case that may be of interest: IoT devices. I haven't seen such a use case mentioned here (nor in EIPs #965 and #1776). Currently, IoT devices that want to interact with the network constantly need "refilling" of gas by their respective owners. The device may request that the owner give it more gas, or the burden can be put on the owner to keep track of gas usage. However, if a third party were to pay for the gas, that would make future network interactions much less cumbersome. On behalf of many future IoT devices, ERC #865 seems like it would be real useful! |
Actually, from my point of view, IoT devices should use 2nd layer platforms. |
Interesting! Thank you for introducing me to 2nd layer platforms. I found this definition:
I completely agree that we don't want a bunch of tiny devices dumping data on the main blockchain, i.e. base-layer. I suppose that's been shown to be unscalable, a lá CryptoKitties. I wonder if there are use cases for any IoT devices to post data to the main chain? I did some more research into other 2nd layer platforms and found this helpful article, which describes the scaling problem while also suggesting to use 2nd layer platforms as @chompomonim had mentioned. Apparently Plasma was going to be a 2nd layer platform, but was halted. Pardon my not being current on the news. As of March 2019, here is the "State of the Ethereum Layer 2 Protocol Development", which states:
Ok, great! Thank you for the introduction to these 2nd layer platforms. I'll do more research into them. |
@embedovator ERC #777 has delegates which allows the issuer to pay for gas. So there is topping up but only by the company (or whoever sends eth to the gas address), which should be done sparingly based on the use case of course so that it isn't prohibitively expensive, but it removes the burden from the client and makes it seem like a gas-less token transfer. Just food for thought. |
can i use this implementation already on an existing ERC20? |
Just a question, why not allowing buying gas with token instead of Ether ? That would be much simpler, isn’t it ? |
@ytrezq that is very hard because miners would need to know how to deterministically price tokens. Also even if price problem would be solved, that anyway would mean hardfork and a lot of changes in ethereum node implementations. Such pricing of gas with tokens instead of Ether most probably will never happen. |
@chompomonim : I was pointing that it’s still simpler than paying transactions/computations directly in tokens, isn’t it ? |
@ytrezq what do you mean by "this"? |
@chompomonim : wanted to mean |
I mean any such changes on protocol level are very hard and almost impossible. So only workaround such as this and similar are possible. |
Hello guys, has this proposal been approved? |
@SirPhemmiey No. And there is very little chance that it will be ever approved. |
Oh oh. If I may ask, why? |
Think about it. It requires miners to collectively agree that native Ether has no use case anymore. |
This is very much untrue. Ether is used for staking and deploying contracts
regardless. This is just removing friction and making user experience
better.
…On Wed, Dec 11, 2019 at 1:34 PM Eric Lamison-White ***@***.***> wrote:
Oh oh. If I may ask, why?
Think about it.
It requires miners to collectively agree that native Ether has no use case
anymore.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#865?email_source=notifications&email_token=ABPVWL3JWM7KLAX4L53HQYTQYEXDTA5CNFSM4EOXWFLKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEGUEG3Q#issuecomment-564675438>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABPVWL6P6AVUN2B7UV5PBRLQYEXDTANCNFSM4EOXWFLA>
.
|
saying "no use case" was hyperbole, obviously everyone in this thread knows it will reduce friction, you have to convince the miners with their predictable rebuttals.
|
@1blockologist No, it dont requires miners to agree on anything. This is an application level interface. |
then what is preventing it from moving forward? the author's neglect? |
The way it implemented seems not attractive to developers because it is limited to transfers, cannot do general executions on Ethereum Smart Contracts. There are other proposals, like the draft in https://eips.ethereum.org/EIPS/eip-1077 that work on top of account contracts, as in this example here https://github.com/status-im/account-contracts I am implementing this EIP1077 (alongside my custom gasRelay interface that I plan to use as an update in 1077). ERC865 works on updating and ERC20 token, so it would only work on new tokens that implement this interface, while 1077 proposes a method that any (valued) fungible token is supported, and can do anything (transfer token, transfer other token, create a contract, execute anything that a smart contract allows), the limitation being that account contracts are supported by the DApp (some might not be, like the ones using ethereum signed messages and not supporting ERC1271 interface) The Ether remains part of it, unless miners/validators soft-fork to they become relayers themselves. Ether would still be more valuable as a native token payment is cheaper then an ERC20 token, and also allows Direct execution, while ERC20 relays on the assumption that someone will relay that for you, i.e. $ETH is guaranteed to never loose its intrinsic value of execution on the Ethereum platform, while ERC20 can only do that if they have social value, which is decided by whoever is relaying. |
@alita-moore Do you happen to know why the stale bot seems to be blind to this issue? I'm closing this issue as it hasn't had any action for quite some time. If someone would like to pursue this idea further, please create a thread over at Ethereum Magicians forum to discuss further, or create an EIP by following the process laid out in EIP-1. |
Preamble
Simple Summary
Ability for token holders to pay transfer transactions in tokens instead of gas, in one transaction.
Abstract
The following describes one standard function a token contract can implement to allow a user to delegate transfer of tokens to a third party. The third party pays for the gas, and takes a fee in tokens.
Motivation
When it comes to using tokens as utility tokens, we need to strive for a good UX. Introducing wallets and transactions to end users is a challenge, and having to explain that token holders needs ETH to send tokens is adding some friction to the process. The goal of this EIP is to abstract the gas for the end user, by introducing a fee paid in tokens. A third party can then bring the transaction on-chain, pay for the gas of that given transaction and get tokens from that user.
Specification
Process
The user A gets a quote from the delegate D for the value of the fee Y for 1 transaction (depending on gas price + value of token in ETH).
With their private key, the user generates {V,R,S} for the sha3 of the payload P {N,A,B,D,X,Y,T}.
The user sends {V,R,S} and P (unhashed, unsigned) to the delegate.
The delegate verifies that Y and D have not been altered.
The delegate proceeds to submit the transaction from his account D:
The delegatedTransfer method reconstructs the sha3 H of the payload P (where T is the address of the current contract and D is the msg.sender).
We can then call ecrecover(H, V, R, S), make sure that the result matches A, and if that’s the case, safely move X tokens from A to B and Y tokens from A to D.
The challenge mainly resides in imitating the Non-standard Packed Mode on the client side, and obtaining the exact same sha3 as the one generated on-chain.
Methods
delegatedTransfer
Is called by the delegate, and performs the transfer.
Events
TransferPreSigned
Is triggered whenever delegatedTransfer is successfully called.
Implementation proposal
Assuming a StandardToken and SafeMath available, one could come up with the following implementation.
On-chain operation (solidity)
Off-chain usage (js)
Full implementation available
OpenZeppelin/openzeppelin-contracts#741
Additional documentation
Transfer Ethereum tokens without Ether — An ERC20 Improvement to Seriously Consider
Copyright
Copyright and related rights waived via CC0.
The text was updated successfully, but these errors were encountered: