Closed AugustoL closed 2 years ago
@vu3mmg yes it will fail.
@AugustoL agree about both types of addresses, but why do you think target can't be ERC827 contract itself?
I think ERC 827 may break ERC20 , since ERC 20 does not differentiate between accounts or contracts and for practical purpose ERC 827 needs "contract address" . Is this understanding correct .
ERC827 do not needs "contract address". Think of ERC87 as a way to combine in single transaction calls of approve
and transferFrom
.
@k06a but execution of approve() and transferFrom() will not happen properly if the caller is not a contract , with given ABI?. But in case of ERC 20 , it will happen , irrespective of the address type , since the result is just a value adjustment in a mapping ? nothing goes out . I dont doubt the utility of ERC827 , we are implementing some use cases using the same. Hence hit on the issues
@vu3mmg if you want to execute a function in an account it will fail, if you want to only transfer token value you can execute the common ERC20 methods without the _data
argument and the call wont be executed.
A PR to fix the issues Vyper is having on Open Zeppelin: https://github.com/OpenZeppelin/zeppelin-solidity/pull/871
@AugustoL . Thank you . That resolves the issue. It will be great that if you can explicitly add couple of lines in the standard pointing to the overloading of methods . Otherwise a new reader might get confused. Also could you please point out that ERC 827 methods will fail if it is invoked on an account . I understand that one of the main purpose of ERC 827 is to manage transfers between contracts efficiently and add the notion of purpose .
@elie222 maybe to keep both method names?
According to https://github.com/OpenZeppelin/zeppelin-solidity/pull/887, I suggest to add the abstract methods increaseApproval
and decreaseApproval
to ERC827 interface so that we enforce the need for an ERC827 token to provide an implementation of these methods.
From this:
To this:
Is there any list of tokens already supporting ERC827?
@k06a I am planning on using a variation of ERC827. I need to be 100% sure (without using tx.origin) that all my functions in the receiver contract will have the actual sender as the last parameter. I have come up with this :
contract AuthERC827Token is ERC827Token {
event AuthEnabled(address indexed sender);
event AuthDisabled(address indexed sender);
mapping (address => bool) public usesAuth;
modifier checkAuth(address _receiver, bytes _data) {
if (usesAuth[_receiver]) {
require(_authSender(_data, msg.sender));
}
_;
}
function enableAuthentication() external returns (bool) {
require(!usesAuth[msg.sender]);
usesAuth[msg.sender] = true;
emit AuthEnabled(msg.sender);
return true;
}
function disableAuthentication() external returns (bool) {
require(usesAuth[msg.sender]);
usesAuth[msg.sender] = false;
emit AuthDisabled(msg.sender);
return true;
}
function approve(address _spender, uint256 _value, bytes _data)
public
checkAuth(_spender, _data)
returns (bool)
{
return super.approve(_spender, _value, _data);
}
// do the same for transfer, transferFrom, increaseApproval, decreaseApproval
// ...
/**
*@dev Checks if the last 20 bytes in _data match msg.sender
*@param _data bytes ABI-encoded contract call that needs to be checked
*@return true if the last 20 bytes in _data match msg.sender
*/
function _authSender(bytes _data, address _actualSender) internal pure returns (bool) {
require(_data.length >= 36);
bytes memory senderAddress = new bytes(20);
uint256 dataLength = _data.length;
dataLength = dataLength - 20;
for(uint256 i=0; i<=19; i++) {
senderAddress[i] = _data[dataLength + i];
}
return (keccak256(senderAddress) == keccak256(_actualSender));
}
}
Receiver contracts register themselves to use the auth check. When that happens, all the functions in the receiving contract that need to work with tokens, should have an address as the last parameter. That address will always be the initiator of the transaction (can be a contract too ... so better than using tx.origin checks, or adding signatures to the _data). Example of receiving function :
function useTokens(uint256 _tokenAmount, address _sender) public returns (bool) {
if (msg.sender != address(token)) {
_sender = msg.sender;
}
require(token.allowance(_sender, address(this)) >= _tokenAmount);
// transferFrom + more logic
// ....
}
I want to reduce gas gost for _authSender
(it's at 5600 right now) but I'm not that good at assembly. Let me know what you guys think.
@mad2sm0key why do you need this check at all, why not to use tx.origin
? I think you can also avoid require(token.allowance...)
because transferFrom
call will check this case.
@k06a tx.origin
will not work for contracts that own tokens. tx.origin
will be the address that initiated the transaction, not the address of the contract owning the tokens. You are right about the allowance check, thanks.
@mad2sm0key what do you think about this idea? https://github.com/ethereum/EIPs/issues/1003
@ProphetDaniel The increase and decrease approval funcitons are not taken in count in this standard because It tries to be extend only ERC20 methods functionality, in zeppelin we have the the ERC827 file that only extend the three methods of ERC20. The ERC827 token standard implementation is an extension of the ERC20 token standard that zeppelin use.
I just modified the ERC827 standard based on the discussions we had about function overloading and use of payable modifiers, the PRs introducing those changes are:
https://github.com/OpenZeppelin/zeppelin-solidity/pull/871 https://github.com/OpenZeppelin/zeppelin-solidity/pull/838
The standard is still a draft and I think that with these changes and after months of being reviewed and being used by the community in open source libraries we can start thinking about making it final, but I really dont know how to measure the maturity and decide weather if ready or not.
Hey, I have an idea how to avoid tx.origin
touching by transferring tokens over token smart contract itself. This scheme is also proposed in https://github.com/ethereum/EIPs/issues/1003 – where this several calls moved from web3 into token smart contract.
ERC827 public token;
// Example 1
function payForTask(uint256 taskId, uint256 tokenAmount) public {
token.transferFrom(msg.sender, this, tokenAmount);
tasks[taskId].reward += tokenAmount;
}
// Example 2
function creditAccount(address beneficiary, uint256 tokenAmount) public {
token.transferFrom(msg.sender, this, tokenAmount);
balances[beneficiary] += tokenAmount;
}
You should call:
const payForTaskData = contract.methods.payForTask(taskId, amount).encodeABI();
const approveAndCallData = token.methods.approveAndCall(contract.options.address, amount, payForTaskData).encodeABI();
token.methods.transferAndCall(token.options.address, amount, approveAndCallData).send();
Would you consider to harmonize function names and conventions with ERC721?
@fulldecent yes of course. what do you have in mind?
Here are the changes I would make.
constant
which is deprecated)safeTransferFrom
safeTransferFrom
call onERC827Received
on recipient and throw unless getting the magic value or the recipient is a plain accountBreak backwards compatibility with ERC-20, be bold:
transfer
function, only use transferFrom
and safeTransferFrom
-- RATIONALE: the wallets should hide this extra complexity from the use. Less functions is good. By dropping transfer
we reduce the risk that somebody will send money on their own behalf when they meant to send money from another account they have approve
access to.transfer
fails, do not return a boolI still not understand what it means, but can I use wallet erc20 to recieve erc827?
Yes
On Tue, 22 May 2018, 04:01 hayyudin, notifications@github.com wrote:
I still not understand what it means, but can I use wallet erc20 to recieve erc827?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ethereum/EIPs/issues/827#issuecomment-390829982, or mute the thread https://github.com/notifications/unsubscribe-auth/AC8oX419x-R2uAknYbQQ42yVGYfAbPOTks5t02N0gaJpZM4Ra7LV .
You fail to implement #223 while implementing #667 transferFrom and approve should be abolished Search before posting and stop posting dupes.
We wrote a blog post, how replacing ERC20 with ERC827, can lead to unexpected reentrancy attacks: https://medium.com/chainsecurity/why-erc827-can-make-you-vulnerable-to-reentrancy-attacks-and-how-to-prevent-them-61aeb4beb6bf
The example is an OpenZeppelin Crowdsale. Feel free to give feedback.
@ritzdorf it seems you successfully shooted in your own leg.
@k06a Why?
@ritzdorf because using transferAndCall
absolutely not needed in _deliverTokens
. Really I don’t know where transferAndCall
should be used – the main gem of ERC827 is approveAndCall
which allows to chain ‘approveand
transferFromcalls from
account address` in a single transaction. To deposit smart contracts with tokens in a single tax.
@k06a Thanks for the clarification.
transferAndCall
are unclear, I see the value of our article in clarifying how it should not be used.approveAndCall
, however, given that this functionality does not exist in ERC20 (as you said), we would have had to construct an example with vulnerable code to use it. If you have concrete cases, where approveAndCall
is called from a smart contract we would be happy to check.Overall, our intention was just to notify people that special care is needed when using these functions inside a smart contract.
It also seems like it could have unintended consequences to allow a token to call any arbitrary data. For example, if tokenA is accidentally transferred to an ERC827 token contract, anyone would be able to claim these tokens by simply calling tokenA.transfer(self, value) from the ERC827 token. https://github.com/ethereum/EIPs/issues/827#issuecomment-359119507
anybody care explaining why this would happen?
don't get why tokenA transferred into a ERC827 can be claimed by anybody
@DiveInto because tokenA
smart contract itself can contain any amount of token*
(incl tokenA
). And smart contract looks like any other account when trying to contact another token smart contract methods. He will be the msg.sender
of any call he is going to perform with data
.
This seems unsafe for any external contract using #677 and defining two ways users could interact with it.
Imagine I put #827 into DEX by normal means (deposit
function, which calls transferFrom(me)
) and this DEX also supported #677 tokenFallback
. Now a bad actor could transferAndCall
or approveAndCall
with parameters to: 0xDEX, value: 0, data: [ func: 0xtokenFallbackHashBytes4, from: 0xThem, value: 0xALot, data: [ 0xSthSth ] ]
.
Now if the DEX implementation doesn't store the information on how much of every ERC20 it should own and compare that with erc20.balanceOf(this)
when tokenFallback
is called, a bad actor could just withdraw
0xALot of this token.
@mg6maciej The token contract fills in the amount and form fields in the callback. The user doesn't get to set them.
@MicahZoltu Many do (#223, #667, #721, #777), but not this one. This one warns:
Important Note Do not use this method with fallback functions that receive the value transfered as parameter, there is not way to verify how much value was transfered on the fallback function.
This is exactly what a bad actor will not not do. Instead they will try to find contracts where they can abuse it.
Same post as https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1044. Proposal discussions should be continued here.
It is a really bad practice to allow the abuse of CUSTOM_CALL in token standard with anythingAndCall
.
<address>.call.value(msg.value)(_data)
Users are allowed to pass arbitrary data, leading to call any function with any data on any contract address.
Attackers could call any contract in the name of vulnerable contract with CUSTOM_CALL.
This vulnerability will make these attacking scenarios possible:
Attackers could steal almost each kind of tokens belong to the vulnerable contract [1] [2] as also mentioned by @k06a
Attackers could steal almost each kind of tokens approved
to the vulnerable contract
Attackers could bypass the auth check in vulnerable contract by proxy of contract itself in special situation [3] (edit: current openzeppelin implementation is not affected with the help of require(_to != address(this));
)
Attackers could pass fake values as parameter to cheat with receiver contract [4] as mentioned by @mg6maciej
We (SECBIT) think that the ERC827 proposal should be discussed further in community before OpenZeppelin putting the implementation in the repo. Many developers could use this code without knowledge of hidden danger.
@p0n1, first three issues can be solved by denying _to
to be equal to this
. Also approveAndCall
should be used for avoiding wrong arguments issues, other methods are really not reliable, and I think they are not needed at all.
@p0n1 what k06a says has been already implemented in the zeppelin implementation
@p0n1, first three issues can be solved by denying _to to be equal to this. @p0n1 what k06a says has been already implemented in the zeppelin implementation
@k06a @AugustoL haha. I don't think so.
Adding require(_to != address(this));
will only protect contract token itself.
In this case for example, only TE-FOOD Token
will be protected but ERC20 (MOAT Token)
will not be.
That's why I said "could steal almost each kind of tokens".
Third issue could be solved actually.
@p0n1 Thanks for speaking up. I'm currently discussing reasons to abandon proposals like #827 and #1003 with @AugustoL in a private channel. If we don't find consensus or there will be no more replies, I'll seek support from the community at large.
@p0n1 maybe also check first 4 bytes of _data
and deny signatures of transfer
and transferFrom
ERC20 methods?
@mg6maciej @p0n1 I think the token shouldnt care about other tokens, it is not like every contract will take in count how to recover tokens when you arent supposed to send it there. ERC20 smart contracts that received tokens they are lost. At least here they are at least still in circulation. In case that we decide to add a recovery method for tokens. How will you implement it? We can add it to the issue. If you want contact me privately and we can discuss it together. Im open for everything that will improve the standard without adding to much complexity.
@mg6maciej both #827 and #1003 will be not needed in case #1035 will be implemented in EVM. This will allow batching account transactions without chaining them.
@AugustoL I think allowing token creator to reclaim any tokens from smart contract is the best possible solution to resolve this kind of issue: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/CanReclaimToken.sol
@AugustoL It appears to be not really a critical economic problem in most case when considering the tokens stuck at the contract balance got stolen by anyone else. But we still need to be careful with other unknown scenarios. People could combine ERC827 with other codebase.
We found many deployed contracts using ERC827 or ERC223 _custom_fallback
allowed version.
https://github.com/sec-bit/awesome-buggy-erc20-tokens/blob/master/csv/custom-call-abuse.o.csv
Most of them in the list are just code learned from the recommend code templates by now.
@p0n1 I agree this is not too much of a problem with another tokens accidentally sent to ERC827 contract. I'm concerned mostly with something like this: https://gist.github.com/mg6maciej/8af66a0cfb6ac318fa8cedeb4fd246d3 This code is safe for ERC20, ERC223, ERC667 and ERC777, but anyone putting ERC827 will lose them to an attacker. If you look at this code as a base for DEX like EtherDelta, but with support for ERC677 and ERC777, you may immediately notice the problem. Attacker can grab all the ERC827 tokens, fill orders that want to buy these tokens for different tokens, do it multiple times if they want to fill more orders, after that withdraw all these ERC827 tokens, transfer to another exchange and sell them one more time.
I created a gitter channel to discuss about ERC827 standard, implementations and governance of the standard.
https://gitter.im/ERC827/Lobby
I also created a public calendar on google calendar for ERC827 community calls, maybe to happen once every two weeks, where anyone in the community can join. The first call will be 5 of june at 5pm GMT +2.
Link to the ERC827 calendar: https://calendar.google.com/calendar?cid=bjg3bDdvcXVmMTQybmY4MGxlMGtoM3J2cThAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ
Is this bug actually a feature if a token is only meant to be used within a particular ecosystem, targeting specific contracts that don't have a tokenFallback function, and not freely traded or sent to arbitrary contracts? This issue pretty much guarantees that ERC827 tokens couldn't be listed on decentralized exchanges, and would therefore be less vulnerable to securitization if the token is meant to be a marker within a limited system, and not a tradeable store of value subject to market forces and speculation.
I just added a PR with a proposal with changes over current standard that adds more control to the receiver contract allowing it to specify which functions can be executed over it from the token contract.
Let's just add ERC827Caller
contract:
contract ERC827Caller {
function makeCall(address _target, bytes _data) external payable returns (bool) {
// solium-disable-next-line security/no-call-value
return _target.call.value(msg.value)(_data);
}
}
And use it this way:
contract ERC827Token {
ERC827Caller internal caller_;
constructor() public {
caller_ = new ERC827Caller();
}
function approveAndCall(
address _spender,
uint256 _value,
bytes _data
)
public
payable
returns (bool)
{
require(_spender != address(this));
super.approve(_spender, _value);
require(caller_.makeCall.value(msg.value)(_spender, _data));
return true;
}
}
@k06a I dont see how forwarding the calls trough another contract will fix the current issue of allowing arbitrary calls executed by default to any contract from the token contract.
I think the solution in the PR https://github.com/windingtree/erc827/pull/2 might be better because it removes the allowance of arbitrary calls from the token contract by default, but any contract can allow the execution of specific functions on it.
Here is the summary of the PR:
The receiver contract where the callback function will be executed now needs to allow the execution of that callback itself, if the sender attempt to execute a callback that is not allowed on a receiver contract it will fail. The callbacks can be allowed to certain address or they can be public, which means that anyone can execute it. There is also the option for a receiver contract to allow the execution of any function from any address.
I invite you to create a proposal with the changes you suggest on the windingtree/erc827 repository where we can test it against another proposals.
This standard is still a draft and is proven to be unsafe to be used
Simple Summary
A extension of the standard interface ERC20 for tokens with methods that allows the execution of calls inside transfer and approvals.
Abstract
This standard provides basic functionality to transfer tokens, as well as allow tokens to be approved so they can be spent by another on-chain third party. Also it allows to execute calls on transfers and approvals.
Motivation
This extension of the ERC20 interface allows the token to execute a function in the receiver contract contract after the approval or transfer happens. The function is executed by the token proxy, a simple proxy which goal is to mask the msg.sender to prevent the token contract to execute the function calls itself. The ERC20 token standard is widely accepted but it only allows the transfer of value, ethereum users are available to transfer value and data on transactions, with these extension of the ERC20 token standard they will be able to do the same with ERC20 tokens.
I saw a lot of new standards being proposed in the community and I think the way to improve the current ERC20 standard is with an extension that is fully compatible with the original standard and also add new methods, but keeping it simple at the same time, the code to be added to the ERC20 standard is near 150 lines of code.
When to use each function
approveAndCall: Probably the one that you will need, maybe the only one since it only allows the receiver contract to use approved balance. The best practice is to check the allowance of the sender and then do your stuff using the transferFromAndCall method.
transferAndCall: There is no way to check that the balance that will be transferred is the correct one, this function is useful when a function dont need to check any transfer of value.
transferFromAndCall: Same as transferAndCall, only useful when there is no need to check the transfered amount of tokens and want to spend approved balance.
Specification
Token
Methods
NOTE: Callers MUST handle
false
fromreturns (bool success)
. Callers MUST NOT assume thatfalse
is never returned!name - ERC20
Returns the name of the token - e.g.
"MyToken"
.OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.
symbol - ERC20
Returns the symbol of the token. E.g. "HIX".
OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.
decimals - ERC20
Returns the number of decimals the token uses - e.g.
8
, means to divide the token amount by100000000
to get its user representation.OPTIONAL - This method can be used to improve usability, but interfaces and other contracts MUST NOT expect these values to be present.
totalSupply - ERC20
Returns the total token supply.
balanceOf - ERC20
Returns the account balance of another account with address
_owner
.transfer - ERC20
Transfers
_value
amount of tokens to address_to
, and MUST fire theTransfer
event. The function SHOULDrevert
if the_from
account balance does not have enough tokens to spend.A token contract which creates new tokens SHOULD trigger a Transfer event with the
_from
address set to0x0
when tokens are created.Note Transfers of 0 values MUST be treated as normal transfers and fire the
Transfer
event.transferFrom - ERC20
Transfers
_value
amount of tokens from address_from
to address_to
, and MUST fire theTransfer
event.The
transferFrom
method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf. This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies. The function SHOULDrevert
unless the_from
account has deliberately authorized the sender of the message via some mechanism.Note Transfers of 0 values MUST be treated as normal transfers and fire the
Transfer
event.approve - ERC20
Allows
_spender
to withdraw from your account multiple times, up to the_value
amount. If this function is called again it overwrites the current allowance with_value
.Users SHOULD make sure to create user interfaces in such a way that they set the allowance first to
0
before setting it to another value for the same spender. THOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed beforeallowance - ERC20
Returns the amount which
_spender
is still allowed to withdraw from_owner
.ERC827 Proxy
A very simple proxy contract used to forward the calls form the token contract.
There is a public variable called proxy in the ERC827 token, this can be used to check if the call is coming from the ERC827 token since the proxy can only forward calls from the token contract.
ERC827 methods
transferAndCall - ERC827
Execute a function on
_to
with the_data
parameter, if the function ends successfully execute the transfer of_value
amount of tokens to address_to
, and MUST fire theTransfer
event.This method is
payable
, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.The function SHOULD
revert
if the call to_to
address fails or if_from
account balance does not have enough tokens to spend. The ERC20transfer
method is called before the_call(_to, _data)
.Note Transfers of 0 values MUST be treated as normal transfers and fire the
Transfer
event.Important Note Do not use this method with fallback functions that receive the value transferred as parameter, there is not way to verify how much value was transferred on the fallback function.
transferFromAndCall - ERC827
Execute a function on
_to
with the_data
parameter, if the function ends successfully execute the transfer of_value
amount of tokens from address_from
to address_to
, and MUST fire theTransfer
event.This method is
payable
, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.The
transferFromAndCall
method is used for a withdraw workflow, allowing contracts to transfer tokens on your behalf before executing a function. The ERC20transferFrom
method is called before the_call(_to, _data)
. This can be used for example to allow a contract to transfer tokens on your behalf and/or to charge fees in sub-currencies. The function SHOULDrevert
if the call to_to
address fails or if the_from
approved balance by_from
tomsg.sender
is not enough to execute the transfer.Note Transfers of 0 values MUST be treated as normal transfers and fire the
Transfer
event.Important Note Do not use this method with fallback functions that receive the value transferred as parameter, there is not way to verify how much value was transferred on the fallback function.
approveAndCall - ERC827
Execute a function on
_spender
with the_data
parameter, if the function ends successfully allows_spender
to withdraw from your account multiple times, up to the_value
amount. If this function is called again it overwrites the current allowance with_value
.This method is
payable
, which means that ethers can be sent when calling it, but the transfer of ether needs to be handled in the call is executed after transfer since the one who receives the ether is the token contract and not the token receiver.Clients SHOULD make sure to create user interfaces in such a way that they set the allowance first to
0
before setting it to another value for the same spender. The ERC20approve
method is called before the_call(_spender, _data)
. The function SHOULDrevert
if the call to_spender
address fails. THOUGH The contract itself shouldn't enforce it, to allow backwards compatibility with contracts deployed beforeEvents
Transfer - ERC20
MUST trigger when tokens are transferred, including zero value transfers.
Approval - ERC20
MUST trigger on any successful call to
approve(address _spender, uint256 _value)
.Past Issues
The main issue that has been recognized by the community is that the standard does not follow the assumption about executing calls in behalf of a token contract, every smart contract that handle token balances assume the token contract will execute only the common methods and maybe a callback that is implemented by the token itself. This standard break that rule and allow the execution of arbitrary calls making it hard to integrate in current solutions. UPDATE This was solved by adding a simple proxy to the token and forwarding the calls coming from the token contract, the proxy ensure that the calls come only from the token contract and allows this to be verified on chain, this prevents the token address to be used as
msg.sender
allowing the integration with current solutions.Discussion channel
https://gitter.im/ERC827
Revisions
Implementation
ERC827 Interface in Winding Tree
[ERC827 Standard Token implementation in Winding Tree](https://github.com/windingtree/erc827/blob/master/contracts/ERC827/ERC827.sol
Copyright
Copyright and related rights waived via CC0