ApproveAndCall

#はじめに

Ethereumの支払い処理は、payableを使えば良いだけだが、意外と困るのが、
自作のtokenを支払い手段にする場合に、コントラクトが受け取ってから、アイテムなどの排出までの一連の流れを制御する時。
社内の勉強会用に自作tokenを自作サービスの通貨として使うような話をしたので覚書。

#ETHの場合は、payable。自作tokenの場合はどうする？

ETHをやり取りする場合、payableという修飾子が用意されていて、
これを使うと下記のような形で、やり取りできる。
重要なのは、購入した時に、payloadをひっつけて、商品IDなどの情報を指定できることが必要条件。
(つまりただ送金するだけでは不十分)

```javascript
function storeData(uint256 payload) public payable {
  require(msg.value == 1 ether);
  info[msg.sender] = payload;
}
```

自作tokenの場合は、下記のような方法で書きたくなるが、不可能。

```javascript
function storeData(uint256 payload) public payable {
  require(msg.value == 1 HOGECOIN);
  info[msg.sender] = payload;
}
```

どのようにするかというと、

approve + trasactionFrom または  transactionのどちらかを使う方法。


例えば購入処理などで使う場合は、サービス側のコントラクトから、
coinを呼び出して


```javascript
service.coin.approve()
service.coin.transferFrom()
```

とやりたくなるが、セキュリティ上これはできない。


そのため、coin側からapproveAndCallという形で

```
coin.approve()
coin.service.transerfer()
```

という形にcallする必要がある。


```
pragma solidity ^ 0.4 .24;
//version:0.4.24+commit.e67f0147.Emscripten.clang
import "./ERC20.sol";
import "./ERC20Detailed.sol";
import "./ERC20Mintable.sol";
import "./Strings.sol";

/**
 * @title PlanetCoin
 */
interface tokenRecipient {
    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public;
}

contract PlanetCoin is ERC20, ERC20Mintable, ERC20Detailed {
    string private _name = "PlanetCoin";
    string private _symbol = "PTC";
    uint8 private _decimals = 0;
    address account = msg.sender;
    uint value = 1;
    constructor() ERC20Detailed(_name, _symbol, _decimals) public {
        _mint(account, value);
    }

    function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns(bool success) {
        tokenRecipient spender = tokenRecipient(_spender);
        if (approve(_spender, _value)) {
            spender.receiveApproval(msg.sender, _value, this, _extraData);
            return true;
        }
    }
}

contract ApproveAndCall {
    function receiveApproval(address _from, uint256 _amount, address _tokenContract, bytes _data) public returns(bool);
}

contract CoinTransferDemo {
    address public owner;
    PlanetCoin public planetCoinContract;
    mapping(address => uint) public addressToPlanetCoinDeposit;
    constructor() CoinTransferDemo() payable public {
        owner = msg.sender;
    }
    mapping(address => uint256) public addressToCoinamount;

    function sendMint() public {
        planetCoinContract.mint(msg.sender, 100);
        addressToPlanetCoinDeposit[msg.sender] += 100;
    }

    function setPlanetCoinContract(address _contractAddress) public {
        planetCoinContract = PlanetCoin(_contractAddress);
    }

    function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public {
        //Token t = Token(_token);
        //require(t.transferFrom(_from,  this, _value));
        addressToCoinamount[_from] = _value;
        //receivedTokens(_from, _value, _token, _extraData);
    }
    event receivedTokens(address _from, uint256 _value, address _token, bytes _extraData);
}
```


(参考)

Ethereum smart service payment with tokens
https://medium.com/@jgm.orinoco/ethereum-smart-service-payment-with-tokens-60894a79f75c

Paying for Services with ERC20 Tokens
https://medium.com/official-amulet/paying-for-services-with-erc20-tokens-6c4313114128

function approveAndCall error
https://ethereum.stackexchange.com/questions/52712/function-approveandcall-error