i still don't get the idea behind the "if you send 100 ERC20 to a contract, they're stuck there and these 100 ERC20 are lost forever".. i really don't get.. like, a contract has an address and a wallet like any normal acccount. if we send ERC20 to a contract, why don't we use the transfer method for example to take the token stored in the smart contract to some other account address. Can anyone example this point to me ? thnx in advance.
1 Answer
It isn't especially obvious at first.
A contract can easily accept and transact with ERC20 tokens. However, a contract cannot do anything it wasn't designed to do in advance of deployment. While it's easy to say that contract is an address and it can theoretically forward tokens, in practice, it may not know how. Contracts do not perform arbitrary actions that were not coded in advance.
Open Zeppelin has the CanReclaimTokens
solution (https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.12.0/contracts/ownership/CanReclaimToken.sol) that accepts an abritrary ERC20 contract address (because random tokens flowing in could be anything) and allows withdrawal by the receiving contract's owner
. This is a rescue possibility, but only if the function was included in the receiving contract:
- Alice sends
XYZ
tokens tocontract FOO
.FOO
is not aware of this but it's address has a balance in XYZ. - Alice reports the error to Bob (headache for Bob). Only Bob, as the
owner
ofFOO
has the authority to withdrawXYZ
fromFOO
. - Bob uses
reclaimToken(address XYZ)
to recover the stranded tokens. - Bob forwards the
XYZ
back to Alice. - Alice rejoices.
You may notice that this is not ideal for Bob in any case. If the contract is incapable of returning errant tokens, then Bob has to explain that to Alice. If the contract is decentralized and does not have a priveleged owner, then this workaround may not be practical. If the contract does have a recovery function, then Bob takes on a service responsibility that may not be desirable. This all assumes Alice can find Bob.
I think it's reasonable to say that many (all?) cases of marooned tokens are operator error (or dev) in one form or another because it doesn't make a lot of sense to send something to a contract if the contract doesn't want or expect it. By default, it's an unrecoverable mistake. ERC223 is an attempt to remedy this hazardous situation preventatively.
ERC223 is ERC20 with a little extra logic. It's backward compatible and addresses this by:
- Check if the receiver is a contract. Contracts have code, so (pseudo),
if(bytecode.length > 0)
, then it's a contract. - If the receiver is a contract, invoke the receiver's
tokenFallback()
function (if one exists), or else the regular fallback function. This gives the receiver a chance to react. Instead of being unaware of the receipt, now the receiving contract is invoked.
What to do ... hmmm ...
What about rejecting (with revert()
) tokens of unexpected types? Rejection (by revert()
) would return the tokens to the sender, which is probably what we want.
One can imagine a contract that doesn't expect payment of any kind and has no means to deal with it. The default is already in place regarding unexpected ether, because functions have to be marked payable
if ether is attached. And, a default fallback function (not payable
) created by solc and run by the EVM rejects everything else if ether was sent.
The tokenFallback()
logic extends this idea to token contracts with a proposed standardized way of handling it.
Consider:
function tokenFallback(args ...) public {
revert("Don't send tokens here. This contract has no idea what to do with them.");
}
It's fleshed out a little more, here: https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.12.0/contracts/ownership/HasNoTokens.sol.
That protects senders of ERC223 tokens. ERC20 tokens will just arrive because receivers have no way to reject them.
Hope it helps.