ERC-721におけるsetApprovalForAllを理解する

# はじめに
[ERC-721](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md)とは、Ethereum上でNon-Fungible Token（NFT）を実現するための現実的な標準規格です。

以下のEventとFunctionのインターフェースを実装することで、様々な発行者のNFTを共通に取り扱うことができるようになります。

```javascript
interface ERC721 /* is ERC165 */ {
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
    event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
    event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

    function balanceOf(address _owner) external view returns (uint256);
    function ownerOf(uint256 _tokenId) external view returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    function approve(address _approved, uint256 _tokenId) external payable;
    function setApprovalForAll(address _operator, bool _approved) external;
    function getApproved(uint256 _tokenId) external view returns (address);
    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}
```

この規格があることにより、OpenSeaといったNFT取引所はいくつものNFTを同じように取り扱うことができます。

# transfer
ERC-721にてトークンの所有情報を譲渡する場合には、ERC-721コントラクトへ`transferFrom`関数を実行することで実現されます。
通常、そのトークンの所有者であれば、相手がEOA（秘密鍵によるアカウント）であっても、コントラクトアカウントであっても、譲渡が可能です。

下図は、EOAであるAliceからEOAのBobもしくは、コントラクトCarolにトークンの譲渡を行うイメージです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/49e4981e-645b-24ff-479a-bc769b0c55c2.png)

ここで、トランザクションの発行対象は、ERC-721のコントラクトになります。

# approve

Charlieコントラクトという預け入れコントラクトがあったとします。
ここで、ERC-721への`transferFrom`ではなく、Charlieコントラクトの`deposit`機能を使って預けたい場合があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/2d381f1f-4786-18ef-16de-7506f767c602.png)

このとき、Charlieコントラクトでは、`deposit`機能内で、ERC-721コントラクトに向けて`transferFrom`の内部トランザクションを発行しますが、当然トークンの所有者でないため失敗します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/e61f7fc9-65a6-e93a-2104-5146262dc8f5.png)

そこで、「転送の許可」である`approve`を実行し前もってCharlieへ譲渡を許可します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/37cf1b83-13a9-1d79-6d4b-c70660cc84a8.png)

具体的なユースケースとしては、トークンのマーケットプレイスがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/474ff211-4dab-5dd7-b229-2ef9f554bbde.png)

マーケットコントラクトに対して事前にトークンの`approve`を実行し、出品`exhibit`をします。

トークンの買い手Bobが現れた際には、指定の金額を送付し１つのトランザクションでトークンの売買が成立します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/9a0a246e-ade4-fad7-268f-99e86c93995d.png)

# setApprovalForAll

前述の`approve`によって、トークンの譲渡を自動化することができ、条件によって転送させることができるようになりました。
NFTにおいてトークン一つ一つは代替不可能なものであり、トークン１つごとに`approve`の必要があります。

そこで、ERC-721では、`setApprovalForAll`が規定されています。
これは、特定のアカウントに対して、**所有する全てのトークンの転送を許可する**ことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/05b90d7f-24f2-d528-cfea-984eead66e37.png)

つまり、マーケットコントラクト上で煩わしい`approve`は不要になります。

また、Ethereum上では、全てのトランザクションにGasというコストがかかります。
すでに、`setApprovalForAll`により譲渡の許可がされているのであれば、
信頼できるマーケットの管理者が、代理で出品を行うことで販売者はコストを０にし、利便性が上がるでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/b06e16dd-043c-60df-4fdc-378e288e07de.png)

# 悪意のあるsetApprovalForAll
さて、煩わしい`approve`と、それを一気に解決してくれる`setApprovalForAll`ですが、
悪意のあるコントラクトへ許可してしまった場合はどうでしょう？

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/44ec9d2f-c429-ac28-1fe4-21d5561fb9af.png)

Evilコントラクトは、Aliceの保有トークンを好きに動かせるようになります。
つまり、悪意のあるアカウントであるMalloryは、Evilコントラクトを経由して、Aliceのトークンを盗み出すことができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155246/1bdf1805-3229-0823-5309-fd3631c3d159.png)


# さいごに
Ethereumは、非中央集権である部分がもてはやされていますが、現実的なサービスは管理者が存在しユーザーの利便性のため`setApprovalForAll`を利用しているケースがあります。
`setApprovalForAll`は全権の移譲です。全てを自己責任とすることもできます。
しかしながら、第三者によるソースコードの保証があり悪意の働けないコントラクトになっていることが確認できる場合や、信頼できるサービス提供者であることを確認した上で`setApprovalForAll`を実行するほうが良いでしょう。

また、サービス提供者も、最低限のソースコード公開は必須だと思います。