-
Notifications
You must be signed in to change notification settings - Fork 11.8k
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
ERC777 implementation and security clarifications #1749
Comments
I can answer a few of these questions as apply to the spec rather than the implementation.
|
@mcdee Thanks for your comment.
|
The item labeled 5 should have been for 6; apologies. |
Thanks for your answers,
-- Another point arose after reading https://www.wealdtech.com/articles/understanding-erc777-token-contracts/ and https://www.wealdtech.com/articles/understanding-erc777-token-operator-contracts/:
Note1: Also "token operator contract can be used with any ERC-777 token and only a single copy needs to be deployed on the blockchain" introduces even more risk in the sense that many tokens creators just copy mindlessly other contracts code which can make one vulnerability in some token operator contract affect multiple deployed ERC777 tokens which use the contract. In this perspective it is great that there is a way to revoke default operators however if an attacker will find a vulnerability in an operator then he might utilize the exploit simultaneously on all affected tokens before they have a chance to revoke him. This DOES increase the attack surface by encouraging different tokens to use common operators contracts. I guess maybe the benefits are bigger than the risk in the minds of the creators. I see the final statement in the second article confirms this: "Any weaknesses in token operator contracts can be magnified when the same token operator contract is used by multiple token contracts. Careful auditing of token operator contracts is highly encouraged.". Note2: Some of my statements are based on my security research and security code review background (not specifically for the blockchain however security attacks in different fields have common grounds). |
tokensToSend() is a hook that acts before state changes, as per its name (and as opposed to tokensReceived() which is a hook after state changes). As such it can examine state (account balances) prior to the transfer and decide if the transfer is to proceed or not. It is also important to understand that the contract in which tokensToSend() is called is specified by the token holder. As such a number of the guidelines around C-E-I can be relaxed (or alternatively you can think of tokensToSend() as part of the check phase; same end result).
As to your point 9), the short answer is "yes" but the important point is that due to the separation of concerns it is far easier to audit a few simple token operator contracts than an entire token contract as would be the case with ERC-20.
More risk in having a single, well-examined and audited contract than having everyone write their own? The purpose is to minimise risk, not to eliminate it. If people want to write their own token operator contracts they are of course welcome to, but allowing shared contracts provides an alternative that in the majority of cases will be safer for token holders. |
|
|
@mcdee Thanks for all the answers. Regarding original questions just to clear out for anybody reading this, what is left unanswered are questions number: 1, 6, 7, 8 (updated others as "answered"). |
Wow, this is a great discussion, thank you @guylando and @mcdee!
Indeed, there's no concept of 'initial supply' in our implementation of ERC777, but if you do need an initial holder, calling
Our ERC777 contract implements ERC20 compatiblity as described in the EIP, which relaxes the requirement on recipient contracts to implement the ERC777TokenReceiver, but only when using the
This is 100% correct, but it's important to keep in mind that the purpose behind this requirement is to help avoid having tokens locked in contracts. A more thorough check is as far as I know not possible, and I think the EIP authors made the right call by using extcodesize.
This is actually a great point! |
Yes, I agree, we're gonna have to change our implementation to update allowances before calling Thank you for the review @guylando! It helps a lot. Let us know if you have any more questions or comments. |
No more questions or comments, although some of my points were partially answered or I do not agree with the decisions (in point 3 deciding if publicly allowing anybody to decrease total supply is maybe worse psychologically/economically than letting users send tokens to 0xdead, in point 4 I am not convinced that hardcoding the 1820 registry address is a good idea in light that any minor fix of 1820 which will be uploaded to a new address will force ALL ERC777 TOKENS which are based on openzeppelin to redeploy which is a mass). |
Also regarding the decision on allowing what I wrote in point 8, need to put a lot of thought if this allows some exploit or not. Sounds dangerous but I have no example for an exploit (dangerous because it is the type of things that everybody can think is safe until there comes someone who thinks of some cool exploit which nobody thought about before. the exploit could utilize some existing ethereum/solidity behavior or some future behavior which will come in the future). |
I'm not sure how point 8 could lead to an exploint: there's no inherent issue with a contract that is not an implementer having tokens. Such a thing could be achieved without resorting to the constructor trick: a contract may register an implementer, receive the tokens, and then unregister it. The intent behind this requirement is to prevent contracts that are unaware of 777's existence (and therefore lack any mechanisms to transfer the tokens out of these contracts) from receiving tokens in the first place, since they would be effectively burned, but it is still possible for a contract to implement the interface and have no way of transferring them, getting us back to square one. The requirement is not a security one, but an attempt to avoid unfortunate scenarios (that cannot be 100% avoided). |
@frangio we want to use the ERC777 but waiting for the changes you wrote. Any idea when they will be merged to this repo or can you suggest on the exact changes which we should manually do so that our changes will be consistent with your changes? |
@guylando They will be available later today in a new release candidate. The final 2.3.0 release will be available later this week or early next week. |
@guylando you can install the latest release candidate, including these changes, by running
|
@nventuro Thanks!
Anyway, good job on the quick implementation and fixes! |
Yeah this has been our thought process as well. We only made sure that a future opt-out API would be feasible in terms of backwards compatibility.
This is interesting but we will not add this check. It does not belong at the smart contract level. We check for 0x0 because it's the default for uninitialized values like mapping entries.
This is a concern of spec rather than the implementation, but in any case |
I believe there is a difference between sending to irrecoverable address and burning which decreases total supply because the first is not visible to the non-technical investors while the second one will change the token total supply on coinmarketcap which might cause undesired reaction from non-technical investors who follow the token in coinmarketcap and similar tools |
Accidentally found a "proof" of the difference between burning and sending to an invalid address: |
So from legal point of view there is more claim to the owner of the tokens that he still owns legally the tokens which were sent to an invalid address than for him to claim he owns burned tokens. |
Those are merely questions and clarifications to better understand the decisions behind the ERC777 implementation. Please don't consider them as critique. We are looking to use your ERC777 implementation code and just want to be confident about it and to make sure any problems, if there are any, are discovered/discussed/understood as early as possible.
(UPDATE: answered by nventuro in the comments - requires changes)In transferFrom why is _transfer called before lowering allowance and _approve? https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC777/ERC777.sol#L123
I checked that the same happens in ERC20. Doesn't it sound safer to first adjust allowance and only then perform the transfer (external call)? (Checks-Effects-Interactions pattern)
(UPDATE: answered by mcdee in the comments)Why burn doesn't have limited access (token creator only)? why to allow anybody to burn their tokens? it influences the token economics https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC777/ERC777.sol#L134
maybe by default to let nobody (or only token creator) burn tokens and to add "Burnable" interface which adds the ability for anybody to burn tokens?
(UPDATE: answered by mcdee in the comments)Why burning tokens is done by sending them to address(0) instead of some other way (such as a "burnedTokens" counter)? The discussions in Reclaiming of ether in common classes of stuck accounts ethereum/EIPs#156 and in a few other places show that many people mistakenly sent tokens to address(0) and are looking for ways to now recover them. So knowingly to continue sending tokens to address(0) by design for burning purposes seems undesirable, no?
(UPDATE: answered by mcdee in the comments)IERC1820Registry (0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24) and TOKENS_SENDER_INTERFACE_HASH (0x29ddb589b1fb5fc7cf394961c1adf5f8c6454761adf795e67fe149f658abe895) and TOKENS_RECIPIENT_INTERFACE_HASH (0xb281fc8c12954d22544db45de3159a39272895b169a852b314f9cc762e44c53b) and "ERC777Token" and "ERC20Token" are all hardcoded into the token contract without a way to modify them. Is that a good idea? We are all aware that new critical bugs are discovered in contracts very often, some because of solidity modifications or new eips and some because of new attack ideas. Redeploying a token in a new address (redeploying in an old address using things like CREATE2 losses trust) requires a lot of explanations to the users who own the token or who are looking into buying the token. So maybe there should have been provided some alternative secure mechanism instead of hard-coding those things just in case something changes?
(UPDATE: answered by mcdee in the comments)How do the calls to _callTokensReceived and _callTokensToSend go hand in hand with the Checks-Effects-Interactions pattern? For example in the _send function _callTokensToSend is called before balances modifications. Same in _burn and _mint.
In this article by OpenZeppelin CTO: https://blog.zeppelin.solutions/onward-with-ethereum-smart-contract-security-97a827e47702
in the "Order your function code: conditions, actions, interactions" section he gives an example where the event emitting is inserted under "2. Effects" before the "3. Interactions". So why _send, _burn, _mint emit the events in the end of the code?
(UPDATE: answered by nventuro in the comments)Since _totalSupply is private and is not set/increased anywhere other than in the internal _mint, it means that initial supply is expected to be set by an inheriting contract in the constructor by calling _mint?
(UPDATE2: answered by nventuro in the comments)(UPDATE: I see the validation in _callTokensReceived does not apply to the ERC20 functions which pass "false" to the validation boolean. Can you confirm it that this makes ERC777 work with multisig wallets which support ERC20?) The implementation of _callTokensReceived forces receiver to either be an EOA or to implement ERC1820/ERC165. So what happens for multisig wallets? The most popular of them at this time is gnosis. Will it not be able to hold the ERC777 tokens? https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol I am not sure that even the newer gnosis safe implements that https://github.com/gnosis/safe-contracts/tree/development/contracts
since multisig wallets are important for holding the initial tokens funds, this seems like a critical matter for them to support the ERC777 implementation or otherwise for security reasons, it will be better for token creators to create ERC20 supported by multisig wallets than to create ERC777 without support from multisig wallets.
(UPDATE: answered by nventuro in the comments)_callTokensReceived uses extcodesize to decide if the address is a contract so if _mint is called at the constructor of the contract then the test will pass (since extcodesize returns 0 at the constructor) and the contract will be able to get tokens while additional tokens after the contract creation will not be able to be sent to it (using the ERC777 new functions) if it doesn't implement the desired interface.
https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/utils/Address.sol#L23
https://ethereum.stackexchange.com/questions/15641/how-does-a-contract-find-out-if-another-address-is-a-contract/15642#15642
The text was updated successfully, but these errors were encountered: