Hi everyone,
Yesterday during the Ledger&Tokenization Working Group we discussed ERC-20 the approve/transferFrom
flow and its issues. Only 16 people participated in the meeting so I thought it could be useful to have a conversation about the topic here on the forum.
For context, on Friday we asked people whether ERC-20 like approve/transferFrom
should be added to the ICRC-1 Token Standard and half of the people voted against it. If we had to make a decision right now we would not include approve/transferFrom
in the standard. I actually would like to give it a second go to be sure this is what we want and that’s why I’m writing this post.
From what I understand, the main reasons for not wanting ERC-20 like approve/transferFrom
are security concerns and issues with cycle draining.
Security concerns
ERC-20’s approve/transferFrom
have been highly criticized for their issues. There is a famous attack vector using them. Another issue is with phishing. Many phishing attacks on Ethereum involve tricking users into signing approvals. Phishing attacks exist everywhere but approve
can make them trivial to do. On one hand, users get used to approve
third parties without fully understanding the implications and lower their defenses regarding this approach. This is exacerbated by the fact that there is no immediate effect in approving somebody. On the other hand, third-parties often abuse approve
and the trust of their users by getting approval for an unlimited amount of tokens. The combination of these two aspects together with simple phishing techniques is highly damaging.
Note that the ERC-20 standard implicitly relies on wallet UIs to help users understand what they are signing. To date wallets are poor at fulfilling this task.
I would like to know what you guys think about those security concerns.
We don’t have to have exactly the same approve/transferFrom
ERC-20 provides. We could improve over Ethereum’s standard now that we know it has issues. For instance, a way to avoid the attack vector on ERC-20’s approve/transferFrom
is to change the way approve/transferFrom
works to mimic how cheques work in real life. In essence, approve/transferFrom
would be a 2 steps transfer. To better reflect this, let’s rename approve
into approveTransfer
and transferFrom
into commitTransfer
. approveTransfer
would be equivalent to signing a check with an amount of tokens. The cheque can be destroyed by the issuer or can be cashed by the beneficiary via commitTransfer
. Multiple approveTransfers
would sign multiple cheques meaning that the attack vector would not exist anymore. The interface would look like this
approveTransfer(
from_subaccount: opt Subaccount,
to_principal: Principal,
to_subaccount: opt Subaccount,
amount: Tokens,
) -> ApprovalId;
commitTransfer(
from_principal: Principal,
from_subaccount: opt Subaccount,
to_principal: Principal,
to_subaccount: opt Subaccount,
approvalId: ApprovalId,
) -> CommitTransferResult;
revokeApproval(approvalId: ApprovalId) -> ();
allowance(approvalId: ApprovalId) -> Tokens;
Now this solves only partially the issue with ERC-20’s approve/transferFrom
but it’s a significant improvement over the original standard in terms of security.
Other solutions could include having a max allowance and having expiration for the allowance and forcing the user to renew the allowance. Now the problem with these two solutions is what is the right max amount and expiration time. This can be quite tricky to decide but better than allowing unlimited values or infinite time.
Cycles draining
Another issue with approve/transferFrom
that was raised is the cycle draining issue for service canisters. Approvals don’t really give any guarantee to a service that transferFrom
will succeed. A user can approve a service and then notify the service about the approval. The service will then use some cycles to attempt a transferFrom
. The user can control how many tokens it has in its own account so it can make transferFrom
fail. At that point the service can only retry, ask the user to retry or black list the user. None of them really take care of the problem. Retries means using more cycles by doing an operation that only the user knows if it will fail or not. Blacklisting users work but only if you require users to register to your service first.
Another solution is that approvals can be done against a “service invoice” that will be issued by the service only after a creation fee has been paid by the user.
Conclusions
I hope this gives food for thought to everybody involved with the discussion. Looking forward to the answers and the next working group next Tuesday!
Best,
Mario
References:
EDIT:
- 14 Jul 22 11:01 add
revokeApproval