[Ethereum] web3.jsによりgethを操作

– web3.jsとは、Ethereumネットワーク上でデータを扱えるようにするライブラリ
– HTTPまたはIPC接続を利用して、ローカルまたはリモートのEthereumブロックチェーンのnodeとやり取りできる

$ npm install web3@0.20.0

### geth起動
$ geth –networkid “15” –nodiscover –port 30304 –datadir ./ console 2>> ./geth_err.log
> admin.startRPC(‘0.0.0.0’, 8545, ”, ‘eth,net,web3,personal’)
true

### web3によるアカウント作成

1
2
3
4
5
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://192.168.33.10:8545'));
var _account = web3.personal.newAccount("test");
console.log(_account)

$ node create_account.js
0x41a4a74bfd4659b742d45d4cafe2bbbd0c6b5d65

### 残高確認

1
2
3
4
5
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://192.168.33.10:8545'));
var _balance = web3.eth.getBalance("0x6752a913de4f4530ed3f071371db8d70acc42fce");
console.log(_balance)

$ node getBalance.js
BigNumber { s: 1, e: 22, c: [ 322200000, 21000000000000 ] }

### 送金

1
2
3
4
5
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://192.168.33.10:8545'));
var _sendTransaction = web3.eth.sendTransaction({from: "0x6752a913de4f4530ed3f071371db8d70acc42fce", to:"0x87b2d715dafdfa88da7d76b25069162153967e23", value:200});
console.log(_sendTransaction)

実行する前に、アカウントをアンロックする
> personal.unlockAccount(eth.accounts[0],”pass1″)
true

$ node sendTransaction.js
0x61a0c52aa933c7d5448928d322b7c545726037c65a982d83f8c1669c1fd837ac

### balanceの確認

1
2
3
4
5
6
7
8
9
10
11
12
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://192.168.33.10:8545'));
web3.eth.defaultAccount=web3.eth.accounts[0]
 
var abi = [{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"}],"payable":false,"type":"constructor"}];
 
var contract_address = "0x5e722e71c6bd4127eec0b4fa8c2e415755985826"
var balance_address = "0x87b2d715dafdfa88da7d76b25069162153967e23"
 
var _balanceOf = web3.eth.contract(abi).at(contract_address).balanceOf(balance_address);
console.log(_balanceOf);

$ node balanceOf.js
BigNumber { s: 1, e: 5, c: [ 200200 ] }

contractのaddressがあるのね。

### transfer

1
2
3
4
5
6
7
8
9
10
11
12
13
var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider('http://192.168.33.10:8545'));
web3.eth.defaultAccount=web3.eth.accounts[0]
 
var abi = [{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"}],"payable":false,"type":"constructor"}];
 
var contract_address = "0x5e722e71c6bd4127eec0b4fa8c2e415755985826"
var transfer_address = "0x87b2d715dafdfa88da7d76b25069162153967e23"
var value = 1000
 
var _transfer = web3.eth.contract(abi).at(contract_address).transfer.sendTransaction(transfer_address, value);
console.log(_transfer);

$ node transfer.js
0xda2cec8a827e4890920a0d28728e7cc162b807c2bf34b7017ad9e43fab19a6c9

$ node balanceOf.js
BigNumber { s: 1, e: 5, c: [ 201200 ] }

このweb3.jsはかなり便利

[Ethereum] 独自トークンの発行

### gethを起動
geth –networkid “15” –nodiscover –port 30304 –datadir ./ console 2>> ./geth_err.log

### RPCを有効にする必要がある
admin.startRPC(‘192.168.33.10’, 8545, ‘*’, ‘eth,net,web3’)
admin.stopRPC()

### broser-solidity
$ git clone https://github.com/ethereum/browser-solidity.git
$ php -S 192.168.33.10:8000
Solidity IDEを起動(index.htmlにアクセス)し、EnvironmentでWeb3Providerで192.168.33.10:8545に接続する

アカウントを見ると、private netで作成したアカウントと残高が一致している

broser/211220_MyToken.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
pragma solidity ^0.4.16;
 
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
 
contract TokenERC20 {
    // Public variables of the token
    string public name;
    string public symbol;
    uint8 public decimals = 18;
    // 18 decimals is the strongly suggested default, avoid changing it
    uint256 public totalSupply;
 
    // This creates an array with all balances
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;
 
    // This generates a public event on the blockchain that will notify clients
    event Transfer(address indexed from, address indexed to, uint256 value);
 
    // This notifies clients about the amount burnt
    event Burn(address indexed from, uint256 value);
 
    /**
     * Constrctor function
     *
     * Initializes contract with initial supply tokens to the creator of the contract
     */
    function TokenERC20(
        uint256 initialSupply,
        string tokenName,
        string tokenSymbol
    ) public {
        totalSupply = initialSupply * 10 ** uint256(decimals);  // Update total supply with the decimal amount
        balanceOf[msg.sender] = totalSupply;                // Give the creator all initial tokens
        name = tokenName;                                   // Set the name for display purposes
        symbol = tokenSymbol;                               // Set the symbol for display purposes
    }
 
    /**
     * Internal transfer, only can be called by this contract
     */
    function _transfer(address _from, address _to, uint _value) internal {
        // Prevent transfer to 0x0 address. Use burn() instead
        require(_to != 0x0);
        // Check if the sender has enough
        require(balanceOf[_from] >= _value);
        // Check for overflows
        require(balanceOf[_to] + _value > balanceOf[_to]);
        // Save this for an assertion in the future
        uint previousBalances = balanceOf[_from] + balanceOf[_to];
        // Subtract from the sender
        balanceOf[_from] -= _value;
        // Add the same to the recipient
        balanceOf[_to] += _value;
        Transfer(_from, _to, _value);
        // Asserts are used to use static analysis to find bugs in your code. They should never fail
        assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
    }
 
    /**
     * Transfer tokens
     *
     * Send `_value` tokens to `_to` from your account
     *
     * @param _to The address of the recipient
     * @param _value the amount to send
     */
    function transfer(address _to, uint256 _value) public {
        _transfer(msg.sender, _to, _value);
    }
 
    /**
     * Transfer tokens from other address
     *
     * Send `_value` tokens to `_to` on behalf of `_from`
     *
     * @param _from The address of the sender
     * @param _to The address of the recipient
     * @param _value the amount to send
     */
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        require(_value <= allowance[_from][msg.sender]);     // Check allowance
        allowance[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }
 
    /**
     * Set allowance for other address
     *
     * Allows `_spender` to spend no more than `_value` tokens on your behalf
     *
     * @param _spender The address authorized to spend
     * @param _value the max amount they can spend
     */
    function approve(address _spender, uint256 _value) public
        returns (bool success) {
        allowance[msg.sender][_spender] = _value;
        return true;
    }
 
    /**
     * Set allowance for other address and notify
     *
     * Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it
     *
     * @param _spender The address authorized to spend
     * @param _value the max amount they can spend
     * @param _extraData some extra information to send to the approved contract
     */
    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;
        }
    }
 
    /**
     * Destroy tokens
     *
     * Remove `_value` tokens from the system irreversibly
     *
     * @param _value the amount of money to burn
     */
    function burn(uint256 _value) public returns (bool success) {
        require(balanceOf[msg.sender] >= _value);   // Check if the sender has enough
        balanceOf[msg.sender] -= _value;            // Subtract from the sender
        totalSupply -= _value;                      // Updates totalSupply
        Burn(msg.sender, _value);
        return true;
    }
 
    /**
     * Destroy tokens from other account
     *
     * Remove `_value` tokens from the system irreversibly on behalf of `_from`.
     *
     * @param _from the address of the sender
     * @param _value the amount of money to burn
     */
    function burnFrom(address _from, uint256 _value) public returns (bool success) {
        require(balanceOf[_from] >= _value);                // Check if the targeted balance is enough
        require(_value <= allowance[_from][msg.sender]);    // Check allowance
        balanceOf[_from] -= _value;                         // Subtract from the targeted balance
        allowance[_from][msg.sender] -= _value;             // Subtract from the sender's allowance
        totalSupply -= _value;                              // Update totalSupply
        Burn(_from, _value);
        return true;
    }
}

SettingでSolidity versionを0.4.16に合わせる
※Compilerのバージョンが異なると、実行できない

TimeCoinを1億枚発行します
100000000, “Timecoin”, “TMCN”

transferしてみます。
transfer: “0x87b2d715dafdfa88da7d76b25069162153967e23″, 2000
personal.unlockAccount(eth.accounts[0],”pass1”)
miner.start()

なぜだろう、、、
まあ、tokenの発行方法はなんとなくわかりました。

Running 3 transaction(s) …
(0) {
“value”: “0”,
“parameters”: [
1000
],
“abi”: “0x99ff0d9125e1fc9531a11262e15aeb2c60509a078c4cc4c64cefdfb06ff68647”,
“contractName”: “Token”,
“bytecode”: “6060604052341561000f57600080fd5b6040516020806102a9833981016040528080519060200190919050505b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b505b610228806100816000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806370a082311461004957806390b98a1114610096575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100f0565b6040518082815260200191505060405180910390f35b34156100a157600080fd5b6100d6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610108565b604051808215151515815260200191505060405180910390f35b60006020528060005260406000206000915090505481565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561015957600090506101f6565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550600190505b929150505600a165627a7a72305820ce860e3f5be7186124af4f5b1b7d2f2bce526d21e1473f9ae5860d1b9e8275610029”,
“linkReferences”: {},
“name”: “”,
“type”: “constructor”,
“from”: “0x6752a913de4f4530ed3f071371db8d70acc42fce”
}
(0) data: 6060604052341561000f57600080fd5b6040516020806102a9833981016040528080519060200190919050505b806000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b505b610228806100816000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806370a082311461004957806390b98a1114610096575b600080fd5b341561005457600080fd5b610080600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506100f0565b6040518082815260200191505060405180910390f35b34156100a157600080fd5b6100d6600480803573ffffffffffffffffffffffffffffffffffffffff16906020019091908035906020019091905050610108565b604051808215151515815260200191505060405180910390f35b60006020528060005260406000206000915090505481565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054101561015957600090506101f6565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540392505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282540192505081905550600190505b929150505600a165627a7a72305820ce860e3f5be7186124af4f5b1b7d2f2bce526d21e1473f9ae5860d1b9e827561002900000000000000000000000000000000000000000000000000000000000003e8

[block:1425 txIndex:0] from:0x675…42fce, to:TokenERC20.transfer(address,uint256) 0x9dc…09b6e, value:0 wei, 1 logs, data:0xa90…007d0, hash:0x69f…e3972
Details
Debug
from 0x6752a913de4f4530ed3f071371db8d70acc42fce
to TokenERC20.transfer(address,uint256) 0x9dc215338fac216e5e04221c9aed78a31a609b6e
gas 53034 gas

transaction cost 53034 gas
hash 0x69fbfb4cbc25afc761ef1cbbe814fbf35f01c1f153cf73f97e090575675e3972
input 0xa9059cbb00000000000000000000000087b2d715dafdfa88da7d76b25069162153967e2300000000000000000000000000000000000000000000000000000000000007d0
decoded input {
“address _to”: “0x87b2d715dafdfa88da7d76b25069162153967e23”,
“uint256 _value”: “2000”
}
decoded output –
logs [
{
“topic”: “0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef”,
“event”: “Transfer”,
“args”: [
“6752a913de4f4530ed3f071371db8d70acc42fce”,
“87b2d715dafdfa88da7d76b25069162153967e23”,
“2000”
]
}
]
value 0 wei

ああああああああああ、できたー
scenario.jsonを保存してから実行しないとpendingになるのね。理解した。

[Ethereum] GethでETH送金

### ETH送金
> web3.fromWei(eth.getBalance(eth.accounts[1]), “ether”)
0
> web3.fromWei(eth.getBalance(eth.accounts[0]), “ether”)
265

– アカウントのロック解除
> personal.unlockAccount(eth.accounts[0],”pass1″)
true

– ETH送金
> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(10, “ether”)})
“0x2c0c90b18598551e678fa0eddef034c5e7759c2750927abae38cef9486814c0c”

– マイニングする(マイニングをしないと送金されない ブロックが生成されてないからか)
> miner.start()
null
> eth.mining
true
> eth.hashrate
16082
> web3.fromWei(eth.getBalance(eth.accounts[1]),”ether”)
10
> miner.stop()
null

### トランザクション、ブロック
> eth.getTransaction(“0x2c0c90b18598551e678fa0eddef034c5e7759c2750927abae38cef9486814c0c”)
{
blockHash: “0xc0f7e2bffbeece31a4a890f6d4b554a7b79f323543a3d146463ad78b9d9a6198”,
blockNumber: 54,
from: “0x6752a913de4f4530ed3f071371db8d70acc42fce”,
gas: 21000,
gasPrice: 1000000000,
hash: “0x2c0c90b18598551e678fa0eddef034c5e7759c2750927abae38cef9486814c0c”,
input: “0x”,
nonce: 0,
r: “0x32d8d13541808080dabefbc39df215e01516ead9115be54de16133d60e9104d8”,
s: “0x3d1e49ba781eaa5ccae9683f8c2e2225e06aed1bdb736514bc29cf551923be33”,
to: “0x87b2d715dafdfa88da7d76b25069162153967e23”,
transactionIndex: 0,
type: “0x0”,
v: “0x38”,
value: 10000000000000000000
}

> eth.getBlock(“54”)
{
difficulty: 131072,
extraData: “0xd883010a0d846765746888676f312e31372e32856c696e7578”,
gasLimit: 3311539,
gasUsed: 21000,
hash: “0xc0f7e2bffbeece31a4a890f6d4b554a7b79f323543a3d146463ad78b9d9a6198”,
logsBloom: “0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000”,
miner: “0x6752a913de4f4530ed3f071371db8d70acc42fce”,
mixHash: “0x08bb6af9df543835877260cd890a8e234383269fcefaa0b03f6cec0aea87ae18”,
nonce: “0x77ca71556eca292f”,
number: 54,
parentHash: “0x14be3c7269afab1eb01e221dfe05283d21a574cabb707571a464f5d3e106210a”,
receiptsRoot: “0xaa48dd6dc5cf21faf1da3ed77d1b91e087e446ad7485ac4d8b737c53e6faf2c1”,
sha3Uncles: “0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347”,
size: 648,
stateRoot: “0xc76aaf77c4d6a10159b8dd505eef0e2c3c3b00afcf3ef826db794d895e8b3c84”,
timestamp: 1639974714,
totalDifficulty: 7294014,
transactions: [“0x2c0c90b18598551e678fa0eddef034c5e7759c2750927abae38cef9486814c0c”],
transactionsRoot: “0xce5ed8b87e501a3061bf6520df5c76ee0847426e8ad05e647f847fca0e5b4acf”,
uncles: []
}

### 手数料の確認
> personal.newAccount(“pass3”)
“0x3c0fdb1594e99dc9c0cdd65ea8f6c2631b838553″
> eth.getBalance(eth.accounts[1])
10000000000000000000
> eth.getBalance(eth.accounts[2])
0

> personal.unlockAccount(eth.accounts[1],”pass2″)
true
> eth.sendTransaction({from: eth.accounts[1], to: eth.accounts[2], value: web3.toWei(6,”ether”)})
“0xb1a53e635f9c6cec886b23d31a8184cb3490c0bed8b2348267d49d663b2b5bd1”
> miner.start()
> eth.getBalance(eth.accounts[2])
6000000000000000000
> miner.stop()
null
> eth.getBalance(eth.accounts[1])
3999979000000000000

手数料がかかるアカウントと、かからないアカウントがあるのね

[Ethereum] Gethでマイニング

// geth起動
$ geth –networkid “15” –nodiscover –port 30304 –datadir ./ console 2>> ./geth_err.log

### マイニング
– アカウントの作成(送金をする為に2つ作る)
> personal.newAccount(“pass1”)
“0x6752a913de4f4530ed3f071371db8d70acc42fce”
> personal.newAccount(“pass2”)
“0x87b2d715dafdfa88da7d76b25069162153967e23”

マイニングを受け取るアカウント
> eth.coinbase
“0x6752a913de4f4530ed3f071371db8d70acc42fce”

#### アカウントの変更
> miner.setEtherbase(eth.accounts[1])
true
> eth.coinbase
“0x87b2d715dafdfa88da7d76b25069162153967e23”

#### ブロックとアカウント残高を確認
> eth.blockNumber
0
> eth.getBalance(eth.accounts[0])
0
> eth.getBalance(eth.accounts[1])
0

#### マイニングスタート
> miner.start()
null
> eth.mining
true
> eth.hashrate
24455
> eth.blockNumber
19
> eth.blockNumber
46

#### マイニングストップ
> miner.stop()
null

> eth.getBalance(eth.accounts[0])
265000000000000000000
> web3.fromWei(eth.getBalance(eth.accounts[0]), “ether”)
265

なんかできてるっぽい
コマンドラインで操作するのね
Goで書いていくのかと思ってたわ

[Ethereum] Ubuntu20.04にGethをインストールして起動

GethはGo Ethereumの略, GNU LGPL v3ライセンス
Ethereumクライアントで、マイニングや送金を実行することができる。
送金の際はトランザクションを生成し、任意のコードも載せることもできる。

### Ubuntu20.04にGethをインストールする
$ sudo apt-get update
$ sudo apt-get upgrade

$ sudo add-apt-repository -y ppa:ethereum/ethereum
$ sudo apt-get update
$ sudo apt-get install ethereum
$ geth –help
$ geth –help
NAME:
geth – the go-ethereum command line interface

Copyright 2013-2021 The go-ethereum Authors

USAGE:
geth [options] [command] [command options] [arguments…]

VERSION:
1.10.13-stable-7a0c19f8

### Gethの起動
1. データディレクトリ: 送受信するブロックやアカウント情報を保存
2. genesis.json: 設定ファイル、ローカルプライベートテストネットを使う場合に必要

$ vi genesis.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "config": {
        "chainId": 10,
        "homesteadBlock": 0,
        "eip150Block": 0,
        "eip155Block": 0,
        "eip158Block": 0
    },
  "alloc"      : {},
  "coinbase"   : "0x0000000000000000000000000000000000000000",
  "difficulty" : "0x20000",
  "extraData"  : "",
  "gasLimit"   : "0x2fefd8",
  "nonce"      : "0x0000000000000042",
  "mixhash"    : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
  "timestamp"  : "0x00"
}

$ tree
.
├── genesis.json
├── geth
│   ├── LOCK
│   ├── chaindata
│   │   ├── 000001.log
│   │   ├── CURRENT
│   │   ├── LOCK
│   │   ├── LOG
│   │   └── MANIFEST-000000
│   ├── lightchaindata
│   │   ├── 000001.log
│   │   ├── CURRENT
│   │   ├── LOCK
│   │   ├── LOG
│   │   └── MANIFEST-000000
│   └── nodekey
└── keystore

$ geth –networkid “15” –nodiscover –port 30304 –datadir ./ console 2>> ./geth_err.log
Welcome to the Geth JavaScript console!

instance: Geth/v1.10.13-stable-7a0c19f8/linux-amd64/go1.17.2
at block: 0 (Thu Jan 01 1970 00:00:00 GMT+0000 (UTC))
datadir: /home/vagrant/dev/ethereum/eth_testnet
modules: admin:1.0 debug:1.0 eth:1.0 ethash:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0

To exit, press ctrl-d or type exit

OK

[Ethereum] UIをコントラクトに接続

$ mkdir greeter-dapp
$ truffle unbox react
$ truffle develop

truffle(develop)> compile
truffle(develop)> migrate

$ cp ../greeter/contracts/Greeter.sol contracts/

2_deploy_greeter.js

1
2
3
4
5
var GreeterContract = artifacts.require("./Greeter.sol");
 
module.exports = function(deployer) {
  deployer.deploy(GreeterContract);
};

client/src/App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import React, { Component } from "react";
import GreeterContract from "./contracts/Greeter.json";
import getWeb3 from "./getWeb3";
 
import "./App.css";
 
class App extends Component {
  state = { greeting: '', web3: null, accounts: null, contract: null };
 
  componentDidMount = async () => {
    try {
      // Get network provider and web3 instance.
      const web3 = await getWeb3();
 
      // Use web3 to get the user's accounts.
      const accounts = await web3.eth.getAccounts();
 
      // Get the contract instance.
      const networkId = await web3.eth.net.getId();
      const deployedNetwork = GreeterContract.networks[networkId];
      const instance = new web3.eth.Contract(
        GreeterContract.abi,
        deployedNetwork && deployedNetwork.address,
      );
 
      // Set web3, accounts, and contract to the state, and then proceed with an
      // example of interacting with the contract's methods.
      this.setState({ web3, accounts, contract: instance }, this.runExample);
    } catch (error) {
      // Catch any errors for any of the above operations.
      alert(
        `Failed to load web3, accounts, or contract. Check console for details.`,
      );
      console.error(error);
    }
  };
 
  runExample = async () => {
    const { accounts, contract } = this.state;
 
    const response = await contract.methods.greet().call();
    this.setState({greeting: response})
  };
 
  render() {
    if (!this.state.web3) {
      return <div>Loading Web3, accounts, and contract...</div>;
    }
    return (
      <div className="App">
        <h1>Greeter</h1>
 
        {this.state.greeting}
 
        <form>
          <label>
            New Greeting:
            <input type="text" value={this.state.greeting} onChange={this.handleGreetingChange}>
          </label>
        </form>
        <button onClick={this.formSubmitHandler}> Submit </button>
      </div>
    );
  }
}
 
export default App;

うーむ、ようわからん

[Ethereum] Web3を使ったスマートコントラクト

Web3はイーサリアムも操作できるようにするJSライブラリ
HTTPまたはIPC接続を使ってイサーリアムに接続するためのJSON-RPCラッパー
フロントエンド <-> Web3 <-> ブロックチェーン

### Web3のメソッド
getAccounts()
getBlockNumber()
getBalance()
sendTransaction()

web3.setProvider(provider) とする
web3ではPromiseを頻繁に使用する

metamaskはwe3を使っているサイトと簡単にやり取りができるブラウザ拡張
send
call(view/pure関数)

[Ethereum] テストプロジェクト FundraiserFactory

fundraiser_factory_test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
const FundraiserFactoryContract = artifacts.require("FundraiserFactory");
const FundraiserContract = artifacts.require("Fundraiser");
 
contract("FundraiserFactory: deployment", ()=> {
    it("has been deployed", async() => {
        const fundraiserFactory = FundraiserFactoryContract.deployed();
        assert(fundraiserFactory, "fundraiser factory was not deployed");
    })
});
 
contract("FundraiserFactory: createFundraiser", (accounts) => {
    let fundraiserFactory;
    const name = "Beneficiary Name";
    const url = "beneficiaryname.org";
    const imageURL = "https://placekitten.com/600/350"
    const description = "Beneficiary Description"
    const beneficiary = accounts[1];
 
    it("increments the fundraisersCount", async() => {
        fundraiserFactory = await FundraiserFactoryContract.deployed();
        const currentFundraisersCount = fundraiserFactory.fundraisersCount();
        await fundraiserFactory.createFundraiser(
            name,
            url,
            imageURL,
            description,
            beneficiary
        );
        const newFundraisersCount = await fundraiserFactory.fundraisersCount();
 
        // assert.equal(
        //  newFundraisersCount - currentFundraisersCount,
        //  1,
        //  "should increment by 1"
        // );
    });
 
    it("emits the FundraiserCreated event", async() => {
        fundraiserFactory = await FundraiserFactoryContract.deployed();
        const tx = await fundraiserFactory.createFundraiser(
            name,
            url,
            imageURL,
            description,
            beneficiary
        );
        const expectedEvent = "FundraiserCreated";
        const actualEvent = tx.logs[0].event;
 
        assert.equal(actualEvent, expectedEvent, "event should match");
    })
 
});
 
contract("FundraiserFactory: fundraisers", (accounts) => {
    async function createFundraiserFactory(fundraiserCount, accounts){
        const factory = await FundraiserFactoryContract.new();
        await addFundraisers(factory, fundraiserCount, accounts);
        return factory;
    }
 
    async function addFundraisers(factory, count, accounts){
        const name = "Beneficiary";
        const lowerCaseName = name.toLowerCase();
        const beneficiary = accounts[1];
 
        for (let i = 0; i < count; i++){
            await factory.createFundraiser(
                `${name} ${i}`,
                `${lowerCaseName}${i}.com`,
                `${lowerCaseName}${i}.png`,
                `Description for ${name} ${i}`,
                beneficiary
            );
        }
    }
 
    describe("when fundraisers collection is empty", () => {
        it("returns an empty collection", async() => {
            const factory = await createFundraiserFactory(0, accounts);
            const fundraisers = await factory.fundraisers(10, 0);
            assert.equal(
                fundraisers.length,
                0,
                "collection should be empty"
            );
        });
    });
 
    describe("varying limits", async() => {
        let factory;
        beforeEach(async() => {
            factory = await createFundraiserFactory(30, accounts);
        });
 
        it("returns 10 results when limit requested is 10", async()=> {
            const fundraisers = await factory.fundraisers(10, 0);
            assert.equal(
                fundraisers.length,
                10,
                "results size should be 10"
            );
        });
        xit("returns 20 results when limit requested is 20", async() => {
            const fundraisers = await factory.fundraisers(20, 0);
            assert.equal(
                fundraisers.length,
                20,
                "results size should be 20"
            );
        });
        xit("returns 20 results when limit requested is 30", async() => {
            const fundraisers = await factory.fundraisers(30, 0);
            assert.equal(
                fundraisers.length,
                20,
                "results size should be 20"
            );
        });
 
    });
 
    describe("varying offset", () => {
        let factory;
        beforeEach(async() => {
            factory = await createFundraiserFactory(10, accounts);
        });
 
        it("contains the fundraiser with the appropriate offset", async() => {
            const fundraisers = await factory.fundraisers(1, 0);
            const fundraiser = await FundraiserContract.at(fundraisers[0]);
            const name = await fundraiser.name();
            assert.ok(await name.includes(0), `${name} did not include the offset`);
        });
 
        it("contains the fundraiser with the appropriate offset", async() => {
            const fundraisers = await factory.fundraisers(1, 7);
            const fundraiser = await FundraiserContract.at(fundraisers[0]);
            const name = await fundraiser.name();
            assert.ok(await name.includes(7), `${name} did not include the offset`);
        })
    });
 
    describe("boundary conditions", ()=> {
        let factory;
        beforeEach(async() => {
            factory = await createFundraiserFactory(10, accounts);
        });
 
        it("raises out of bounds error", async () => {
            try {
                await factory.fundraisers(1, 11);
                assert.fail("error was not raised")
            } catch(err) {
                const expected = "offset out of bounds";
                assert.ok(err.message.includes(expected), `${err.message}`);
            }
        });
 
        it("adjusts return size to prevent out of bounds error", async() => {
            try {
                const fundraisers = await factory.fundraisers(10, 5);
                assert.equal(
                    fundraisers.length,
                    5,
                    "collection adjusted"
                );
            } catch(err) {
                assert.fail("limit and offset exceeded bounds");
            }
        })
    })
})

contracts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
pragma solidity >0.4.23 <=0.8.0;
 
import "./Fundraiser.sol";
 
contract FundraiserFactory {
    uint256 constant maxLimit = 20;
    Fundraiser[] private _fundraisers;
 
    event FundraiserCreated(Fundraiser indexed fundraiser, address indexed owner);
 
    function createFundraiser(
        string memory name,
        string memory url,
        string memory imageURL,
        string memory description,
        address payable beneficiary
    )
 
    public {
        Fundraiser fundraiser = new Fundraiser(
            name,
            url,
            imageURL,
            description,
            beneficiary,
            msg.sender
        );
        _fundraisers.push(fundraiser);
        emit FundraiserCreated(fundraiser, fundraiser.owner());
    }
 
    function fundraisersCount() public view returns(uint256) {
        return _fundraisers.length;
    }
 
    function fundraisers(uint256 limit, uint256 offset) public view returns(Fundraiser[] memory coll){
 
        require(offset <= fundraisersCount(), "offset out of bounds");
 
        uint256 size = fundraisersCount() - offset;
        size = size < limit ? size : limit;
        size = size < maxLimit ? size : maxLimit;
        coll = new Fundraiser[](size);
 
        for(uint256 i = 0; i < size; i++){
            coll[i] = _fundraisers[offset + i];
        }
 
        return coll;
    }
}

関数とその機能を覚えていけば、行けそうな気がして来た

[Ethereum] テストプロジェクト fundraiser

$ mkdir fundraiser
$ cd fundraiser
$ truffle unbox react
$ tree
$ rm contracts/SimpleStorage.sol \
migrations/2_deploy_contracts.js \
test/*
$ touch contracts/Fundraiser.sol test/fundraiser_test.js

受取人の名前、Webサイト、受取人のアドレス、トップページのカードに使う画像URL、受取人に関する説明、管理人またはownerのアドレス

fundraiser_test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
const FundraiserContract = artifacts.require("Fundraiser");
 
contract("Fundraiser", accounts => {
    let fundraiser;
    const name = "Beneficiary Name";
    const url = "beneficiaryname.org";
    const imageURL = "https://placekitten.com/600/350";
    const description = "Beneficiary description";
    const beneficiary = accounts[1];
    const owner = accounts[0];
 
    beforeEach(async() => {
        fundraiser = await FundraiserContract.new(
            name,
            url,
            imageURL,
            description,
            beneficiary,
            owner
        );
    });
 
    describe("initialization", () => {
         
 
        it("gets the beneficiary name", async() => {
            const actual = await fundraiser.name();
            assert.equal(actual, name, "names should match");
        });
 
        it("gets the beneficiary url", async() => {
            const actual = await fundraiser.url();
            assert.equal(actual, url, "url should match");
        });
 
        it("gets the beneficiary image url", async() => {
            const actual = await fundraiser.imageURL();
            assert.equal(actual, imageURL, "imageURL should match");
        });
 
        it("gets the beneficiary description", async() => {
            const actual = await fundraiser.description();
            assert.equal(actual, description, "description should match");
        });
 
        it("gets the beneficiary", async() => {
            const actual = await fundraiser.beneficiary();
            assert.equal(actual, beneficiary, "beneficiary addresses should match");
        });
 
        it("gets the owner", async() => {
            const actual = await fundraiser.owner();
            assert.equal(actual, owner, "bios should match");
        })
 
    });
 
    describe("setBeneficiary", () => {
        const newBeneficiary = accounts[2];
 
        it("updated beneficiary when called by owner account", async() => {
            await fundraiser.setBeneficiary(newBeneficiary, {from: owner});
            const actualBeneficiary = await fundraiser.beneficiary();
            assert.equal(actualBeneficiary, newBeneficiary,
                "beneficiaries should match");
        })
 
        it("throws an error when called from a non-owner account", async() => {
            try {
                await fundraiser.setBeneficiary(newBeneficiary, {from: accounts[3]});
                assert.fail("withdraw was not restricted to owners")
            } catch(err) {
                const expectedError = "Ownable: caller is not the owner"
                const actualError = err.reason;
                assert.equal(actualError, expectedError, "should not be permitted")
            }
        })
    });
 
    describe("making donations", () => {
        const value = web3.utils.toWei('0.0289');
        const donor = accounts[2];
 
        it("increases myDonationsCount", async() => {
            const currentDonationsCount = await fundraiser.myDonationsCount(
                {from: donor}
            );
 
            await fundraiser.donate({from: donor, value});
 
            const newDonationsCount = await fundraiser.myDonationsCount(
                {from: donor}
            );
 
            assert.equal(
                1,
                newDonationsCount - currentDonationsCount,
                "myDonationsCount should increment by 1"
            );
        });
        it("includes donation in myDonations", async() => {
            await fundraiser.donate({from: donor, value});
            const {values, dates} = await fundraiser.myDonations(
                    {from: donor}
                );
            assert.equal(
                    value,
                    values[0],
                    "values should match"
                );
            assert(dates[0], "date should be present");
        });
        it("increases the totalDonations amaount", async() => {
            const currentTotalDonations = await fundraiser.totalDonations();
            await fundraiser.donate({from: donor, value});
            const newTotalDonations = await fundraiser.totalDonations();
 
            const diff = newTotalDonations - currentTotalDonations;
 
            assert.equal(
                diff,
                value,
                "difference should match the donation value"
            );
        });
        it("increases donationsCount", async() => {
            const currentDonationsCount = await fundraiser.donationsCount();
            await fundraiser.donate({from: donor, value});
            const newDonationsCount = await fundraiser.donationsCount();
 
            assert.equal(
                1,
                newDonationsCount - currentDonationsCount,
                "donationsCount should increment by 1"
            )
        })
        it("emits the DonationReceived event", async() => {
            const tx = await fundraiser.donate({from: donor, value});
            const expectedEvent = "DonationReceived";
            const actualEvent = tx.logs[0].event;
 
            assert.equal(actualEvent, expectedEvent, "events should match");
        })
    });
    describe("withdrawing funds", ()=> {
        beforeEach(async() => {
            await fundraiser.donate(
                {from: accounts[2], value: web3.utils.toWei('0.1')}
            );
        });
 
        describe("access controls", () => {
            it("throws an error when called from a non-owner account", async() => {
                try {
                    await fundraiser.withdraw({from: accounts[3]});
                    assert.fail("withdraw was not restricted to owners");
                } catch(err) {
                    const expectedError = "Ownable: caller is not the owner";
                    const actualError = err.reason;
                    assert.equal(actualError, expectedError, "should not be permitted");
                }
            });
 
            it("permits the owner to call the function", async() => {
                try {
                    await fundraiser.withdraw({from: owner});
                    assert(true, "no errors were thrown");
                } catch(err) {
                    assert.fail("should not have thrown an error");
                }
            })
        });
 
        it("transfers balance to beneficiary", async() => {
            const currentContractBalance = await web3.eth.getBalance(fundraiser.address);
            const currentBeneficiaryBalance = await web3.eth.getBalance(beneficiary);
 
            await fundraiser.withdraw({from: owner});
 
            const newContractBalance = await web3.eth.getBalance(fundraiser.address);
            const newBeneficiaryBalance = await web3.eth.getBalance(beneficiary);
            const beneficiaryDifference = newBeneficiaryBalance - currentBeneficiaryBalance;
 
            assert.equal(
                newContractBalance,
                0,
                "contract should have a 0 balance"
            );
            assert.equal(
                beneficiaryDifference,
                currentContractBalance,
                "beneficiary should receive all the funds"
            );
        });
 
        it("emit Withdraw event", async()=>{
            const tx = await fundraiser.withdraw({from: owner});
            const expectedEvent = "Withdraw";
            const actualEvent = tx.logs[0].event;
 
            assert.equal(
                actualEvent,
                expectedEvent,
                "events should match"
            );
        });
    });
    describe("fallback function", () => {
        const value = web3.utils.toWei('0.0289');
 
        it("increases the totalDonation amount", async() => {
            const currentTotalDonations = await fundraiser.totalDonations();
            await web3.eth.sendTransaction(
                {to: fundraiser.address, from: accounts[9], value}
            );
            const newTotalDonations = await fundraiser.totalDonations();
 
            const diff = newTotalDonations - currentTotalDonations;
 
            assert.equal(
                diff,
                value,
                "difference should match the donation value"
            );
        });
 
        it("increase donationsCount", async() => {
            const currentDonationsCount = await fundraiser.donationsCount();
            await web3.eth.sendTransaction(
                {to: fundraiser.address, from: accounts[9], value}
            );
            const newDonationsCount = await fundraiser.donationsCount();
 
            assert.equal(
                1,
                newDonationsCount - currentDonationsCount,
                "donationsCount should increment by 1"
            );
        });
    });
});

Fundraiser.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
pragma solidity >0.4.23 <=0.8.0;
 
import "openzeppelin-solidity/contracts/access/Ownable.sol";
import "openzeppelin-solidity/contracts/utils/math/SafeMath.sol";
 
contract Fundraiser is Ownable{
    using SafeMath for uint256;
 
    uint256 public totalDonations;
    uint256 public donationsCount;
    event DonationReceived(address indexed donor, uint256 value);
    event Withdraw(uint256 amount);
 
    struct Donation {
        uint256 value;
        uint256 date;
    }
    mapping(address => Donation[]) private _donations;
 
    string public name;
    string public url;
    string public imageURL;
    string public description;
 
    address payable public beneficiary;
 
    constructor(
        string memory _name,
        string memory _url,
        string memory _imageURL,
        string memory _description,
        address payable _beneficiary,
        address _custodian
    )
 
    public {
        name = _name;
        url = _url;
        imageURL = _imageURL;
        description = _description;
        beneficiary = _beneficiary;
        transferOwnership(_custodian);
    }
 
    function setBeneficiary(address payable _beneficiary) public onlyOwner {
        beneficiary = _beneficiary;
    }
 
    function myDonationsCount() public view returns(uint256){
        return _donations[msg.sender].length;
    }
 
    function donate() public payable {
        Donation memory donation = Donation({
            value: msg.value,
            date: block.timestamp
        });
        _donations[msg.sender].push(donation);
        totalDonations = totalDonations.add(msg.value);
        donationsCount++;
 
        emit DonationReceived(msg.sender, msg.value);
    }
 
    function myDonations() public view returns(
        uint256[] memory values,
        uint256[] memory dates
    ){
        uint256 count = myDonationsCount();
        values = new uint256[](count);
        dates = new uint256[](count);
 
        for (uint256 i = 0; i < count; i++){
            Donation storage donation = _donations[msg.sender][i];
            values[i] = donation.value;
            dates[i] = donation.date;
        }
 
        return (values, dates);
    }
 
    function withdraw() public onlyOwner {
        uint256 balance = address(this).balance;
        beneficiary.transfer(balance);
        emit Withdraw(balance);
    }
 
    fallback () external payable {
        totalDonations = totalDonations.add(msg.value);
        donationsCount++;
    }
}

address型とaddress payable型の二つのアドレスがある
少しずつ理解してきました

[Ethereum] Infuraを使ったRinkebyへのデプロイ

infuraに登録する
https://infura.io/

プロジェクトのエンドポイントをコピー

セキュリティとプライバーシーでMETAMASKのMNEMONICを取得する

$ export MNEMONIC=””
$ export INFURA_PROJECT_ID=””
$ npm install truffle-hdwallet-provider –save-dev

tuffle-config.js
L ネットワーク設定

1
2
3
4
5
6
7
8
9
10
11
12
13
const HDWalletProvider = require('truffle-hdwallet-provider');
 
    rinkeby: {
      provider: () => {
        const mnemonic = process.env["MNEMONIC"]
        const project_id = process.env["INFURA_PROJECT_ID"]
        return new HDWalletProvider(
            mnemonic,
            `https://rinkeby.infura.io/v3/${project_id}`
          );
      },
      network_id: "*"
    }

$ truffle migrate –network rinkeby

Compiling your contracts…
===========================
> Everything is up to date, there is nothing to compile.

Network up to date.
Error: err: insufficient funds for gas * price + value: address 0x71c9f1D5bE00173ae7B774bDBE2112cdD03C5C92 have 0 want 134439500000000000 (supplied gas 6721975)

gasがないとダメって書かれてるな..
まあ なんとなく仕組みはわかった