[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

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

{
  "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

var GreeterContract = artifacts.require("./Greeter.sol");

module.exports = function(deployer) {
  deployer.deploy(GreeterContract);
};

client/src/App.js

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

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

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

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

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 ネットワーク設定

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がないとダメって書かれてるな..
まあ なんとなく仕組みはわかった

[Ethereum] Parityを使ったGoerliへのデプロイ

faucetでイーサを取得する
https://goerli-faucet.slock.it/

エラーになるな… 何故だろう
$ npm install truffle-hdwallet-provider –save-dev
$ export MNEMONIC=”<ハーモニック>”

truffle-config.js

const HDWalletProvider = require('@truffle/hdwallet-provider');

goerli: {
	provider: () => {
		const mnemonic = process.env["MNEMONIC"]
		return new HDWalletProvider(mnemonic, "http://192.168.34.10:8545")
	},
	network_id: "*",
}

$ parity –chain=goerli
$ truffle migrate –network goerli
$ cd client
$ npm run start

なんか かなり難しいな…