[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型の二つのアドレスがある
少しずつ理解してきました

[NEM/XEM] 独自トークンの発行

NEMのサイトに飛びます
https://discord.com/invite/xymcity

これはdiscordのチャットですね

Symbol
https://jp.symbolplatform.com/

Nemplatform wallet

Wallets


これか

import してログインする

namespace : 独自トークンを機能させるための場所
mosaic : トークの詳細設定(名称、初期供給量、小数点)

早速NEM Walletにnemを送ってみます。120NEMが必要ということなので、124NEM送る
NEMはBlock chainに比べて、blockが作られる時間が早い
https://explorer.nemtool.com/

すると、アカウントに124NEMが入っている!

### namespace
create namespaceでcapitalcoinとします
すると、100.15NEM引かれて、Dashboardでnamespaceが作られます

### Mosaics
namespaceが作られると、mosaicsの作成ができるようになります。
Mosaic name: hpscript
Description: Hpscript
Initial Supply: 100000000
Total Supply: 100000000

### Mosaicの送信
もにゃに送信できるらしいが、もにゃ側でエラーになる…

発行は簡単にできるが、使い方がイマイチやな

[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

なんか かなり難しいな…

[Ethereum] Ganacheにデプロイ

コントラクトをデプロイする3種類の方法
– Ganacheにデプロイ(ローカルブロックチェーン)
– Parityを使ってGoerliテストネットワークにコントラクをデプロイ
– Infuraを使ってRinkebyテストネットワークにデプロイ

デプロイはtruffle migrateを使う

$ truffle compile
build/contractsの下にファイルが作成される
アプリケーションバイナリインターフェイスがABI
bytecodeはコンパイルした結果

$ cp -r hoscdev/chapter-5/greeter/client greeter/client

truffle-config.js

module.exports = {
   contracts_build_directory: "./client/src/contracts",
}

$ truffle compile
$ cd client
$ npm install
$ npm run start
http://192.168.33.10:3000/

### Ganacha-CLI
どうやらローカルからGanachaは使えないみたいなので
$ curl -sL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add –
$ echo “deb https://dl.yarnpkg.com/debian/ stable main” | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt update && sudo apt install yarn
$ yarn –version
1.22.15
$ yarn add ganache-cli

error jsdom@19.0.0: The engine “node” is incompatible with this module. Expected version “>=12”. Got “10.24.1”

truffle-config.jsをローカルに落としてもう一度やってみる

ganacheのMNEMONICをコピーする
Metamaskを一旦Chrome extensionから解除、再設定して、mnemonicを入力する

$ truffle migrate –network development

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

Starting migrations…
======================
> Network name: ‘development’
> Network id: 5777
> Block gas limit: 6721975 (0x6691b7)

1_initial_migration.js
======================

Replacing ‘Migrations’
———————-
> transaction hash: 0x569e2101661c8336ea93eabe22e32ce7edd755f2ec231db1df269c699141fa9e
> Blocks: 0 Seconds: 0
> contract address: 0x9d1cB4d7659Ff924A9F929AD0abD072CA0ABC103
> block number: 1
> block timestamp: 1639442039
> account: 0xf6C3c58654B7005C200587f552980CaE09b0B7d1
> balance: 99.99615706
> gas used: 192147 (0x2ee93)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00384294 ETH

> Saving migration to chain.
> Saving artifacts
————————————-
> Total cost: 0.00384294 ETH

2_deploy_greeter.js
===================

Replacing ‘Greeter’
——————-
> transaction hash: 0x7903ecd10332145b40bf013d79b5e00846f4c77a1c5bdf480624e36200c41957
> Blocks: 0 Seconds: 0
> contract address: 0xC9A75Fc25626AB7907783F0440BD4e6fed30A54D
> block number: 3
> block timestamp: 1639442040
> account: 0xf6C3c58654B7005C200587f552980CaE09b0B7d1
> balance: 99.9888452
> gas used: 323255 (0x4eeb7)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.0064651 ETH

> Saving migration to chain.
> Saving artifacts
————————————-
> Total cost: 0.0064651 ETH

Summary
=======
> Total deployments: 2
> Final cost: 0.01030804 ETH

うーん、概念がよくわからん
MetaMaskでインポートphraseでログインしても入ってないんだよな..

[Ethereum] Hello World!

Solidityでは、入出力などはアクセスできない 関数のみ

const GreeterContract = artifacts.require("Greeter");

contract("Greeter", () => {
	it("has been deployed successfully", async() => {
		const greeter = await GreeterContract.deployed();
		assert(greeter, "contract was not deployed");
	});

	describe("greet()", () => {
		it("returns 'Hello World!'", async() => {
			const greeter = await GreeterContract.deployed();
			const expected = "Hello World!"
			const actual = await greeter.greet();
			assert.equal(actual, expected, "Greeted with 'Hello World!'")
		})
	})
});

コントラクトに関数を追加

contract Greeter {
	
	function greet() external pure returns(string memory){
		return "Hello World!";
	}
}

$ truffle test
Contract: Greeter
✓ has been deployed successfully
greet()
✓ returns ‘Hello World!’

2 passing (78ms)

状態を変更しない関数は pure または view

### Contractを動的にする
greeter_test.js

contract("Greeter: update greeting", ()=> {
	describe("setGreeting(string)", ()=> {
		it("sets greeting to passed in string", async() => {
			const greeter = await GreeterContract.deployed();
			const expected = "Hi there!";

			await greeter.setGreeting(expected);
			const actual = await greeter.greet();

			assert.equal(actual, expected, "greeting was not updated");
		});
	});
});

Greeter.sol

contract Greeter {
	string private _greeting = "Hello World!";
	
	function greet() external view returns(string memory){
		return _greeting;
	}

	function setGreeting(string calldata greeting) external {
		_greeting = greeting;
	}
}

テストにパスする

### GreeterをOwnableにする
geter関数ownerを呼び出すことで誰が所有者か確認する

contract("Greeter", () => {
	it("has been deployed successfully", async() => {
		const greeter = await GreeterContract.deployed();
		assert(greeter, "contract was not deployed");
	});

	describe("greet()", () => {
		it("returns 'Hello World!'", async() => {
			const greeter = await GreeterContract.deployed();
			const expected = "Hello World!"
			const actual = await greeter.greet();
			assert.equal(actual, expected, "Greeted with 'Hello World!'")
		})
	})

	describe("owner()", () => {
		it("returns the address of the owner", async()=> {
			const greeter = await GreeterContract.deployed();
			const owner = await greeter.owner();
			assert(owner, "the current owner");
		})
	})
});
contract Greeter {
	string private _greeting = "Hello World!";
	address private _owner;

	function greet() external view returns(string memory){
		return _greeting;
	}

	function setGreeting(string calldata greeting) external {
		_greeting = greeting;
	}

	function owner() public view returns(address){
		return _owner;
	}
}

所有者のアドレスがデプロイ元のアドレスと同じであることをテストしたい
truffleではaccounts変数を使ってアカウントにアクセスできる

contract("Greeter", (accounts) => {
	it("has been deployed successfully", async() => {
		const greeter = await GreeterContract.deployed();
		assert(greeter, "contract was not deployed");
	});

	describe("greet()", () => {
		it("returns 'Hello World!'", async() => {
			const greeter = await GreeterContract.deployed();
			const expected = "Hello World!"
			const actual = await greeter.greet();
			assert.equal(actual, expected, "Greeted with 'Hello World!'")
		})
	})

	describe("owner()", () => {
		it("returns the address of the owner", async()=> {
			const greeter = await GreeterContract.deployed();
			const owner = await greeter.owner();
			assert(owner, "the current owner");
		});

		it("matches the address that originally deployed the contract", async() => {
			const greeter = await GreeterContract.deployed();
			const owner = await greeter.owner();
			const expected = accounts[0];
			assert.equal(owner, expected, "matches address used to deploy contract");
		})
	})
});
contract Greeter {
	string private _greeting = "Hello World!";
	address private _owner;

	constructor() public {
		_owner = msg.sender;
	}

	function greet() external view returns(string memory){
		return _greeting;
	}

	function setGreeting(string calldata greeting) external {
		_greeting = greeting;
	}

	function owner() public view returns(address){
		return _owner;
	}
}

$ npm install openzeppelin-solidity

変更前

pragma solidity >= 0.4.0 < 0.7.0;

contract Greeter {
	string private _greeting = "Hello World!";
	address private _owner;

	constructor() public {
		_owner = msg.sender;
	}

	modifier onlyOwner(){
		require(
			msg.sender == _owner,
			"Ownable: caller is not the owner"
		);
		_;
	}

	function greet() external view returns(string memory){
		return _greeting;
	}

	function setGreeting(string calldata greeting) external onlyOwner{
		_greeting = greeting;
	}

	function owner() public view returns(address){
		return _owner;
	}


}

import "openzeppelin-solidity/contracts/access/Ownable.sol";

contract Greeter is Ownable {
	string private _greeting = "Hello World!";



	function greet() external view returns(string memory){
		return _greeting;
	}

	function setGreeting(string calldata greeting) external onlyOwner{
		_greeting = greeting;
	}

}

tuffle-config.js

  compilers: {
    solc: {
      version: "0.6.0",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    }
  }

うーむ、普通にやっとるが、レベル高いな…

[Ethereum] DDTによる初めてのスマートコントラクト

$ truffle init
$ tree
.
├── contracts
│   └── Migrations.sol
├── migrations
│   └── 1_initial_migration.js
├── test
└── truffle-config.js

Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test

test/greeter_test.js

const GreeterContract = artifacts.require("Greeter");

contract("Greeter", () => {
	it("has been deployed successfully", async() => {
		const greeter = await GreeterContract.deployed();
		assert(greeter, "contract was not deployed");
	});
});

$ sudo truffle test

Compiling your contracts…
===========================
> Compiling ./contracts/Migrations.sol
> Artifacts written to /tmp/test-20211113-60133-1nsib01.f2gyk
> Compiled successfully using:
– solc: 0.5.16+commit.9c3226ce.Emscripten.clang

Error: Could not find artifacts for Greeter from any sources

contracts/Greeter.sol

pragma solidity >= 0.4.0 < 0.7.0;

contract Greeter {
	
}

$ truffle test

Compiling your contracts…
===========================
> Compiling ./contracts/Greeter.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /tmp/test-20211113-60435-1cj02xv.58fvg
> Compiled successfully using:
– solc: 0.5.16+commit.9c3226ce.Emscripten.clang

Contract: Greeter
1) has been deployed successfully
> No events were emitted

truffle testを実行するたびにコントラクトをコンパイルしてテストネットワークにデプロイする
ネットワークに追加するにはマイグレーションを作成する必要がある

migrations/2_deploy_greeter.js

const GreeterContract = artifacts.require("Greeter");

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

$ truffle test

Compiling your contracts…
===========================
> Compiling ./contracts/Greeter.sol
> Compiling ./contracts/Migrations.sol
> Artifacts written to /tmp/test-20211113-63614-1lnqpgc.prxq
> Compiled successfully using:
– solc: 0.5.16+commit.9c3226ce.Emscripten.clang

Contract: Greeter
✓ has been deployed successfully

1 passing (42ms)

contactをデプロイすると、テストをパスする

[Ethereum] 開発環境の準備

イーサリアムクライアントとNode.jsを使う
Node.jsはTuffle用のJS環境
ブロックチェーンとのやりとりはJSON-RPCを使う
今回はParityを使う

### Parityのインストール(ubuntu)

$ bash <(curl https://get.parity.io -L)
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (6) Could not resolve host: get.parity.io

あれ…

### Parityのインストール(ubuntu)
$ sudo apt update
$ sudo apt install snapd
$ sudo snap install parity
parity v2.7.2 from Parity DevOps (devops-parity) installed

動作テスト
$ parity –chain=goerli
2021-12-13 02:44:58 UTC Starting Parity-Ethereum/v2.7.2-stable-2662d19-20200206/x86_64-unknown-linux-gnu/rustc1.41.0
2021-12-13 02:44:58 UTC Keys path /home/vagrant/snap/parity/16707/.local/share/io.parity.ethereum/keys/goerli
2021-12-13 02:44:58 UTC DB path /home/vagrant/snap/parity/16707/.local/share/io.parity.ethereum/chains/goerli/db/b1d518969eab529d
2021-12-13 02:44:58 UTC State DB configuration: fast
2021-12-13 02:44:58 UTC Operating mode: active
2021-12-13 02:44:59 UTC Configured for Görli Testnet using Clique engine

### MetaMask
JSON-RPCを通じてブロックチェーンのやり取りをする
12単語のハーモニックを格納できる

### node.js
$ curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
$ sudo bash nodesource_setup.sh
$ sudo apt install nodejs
$ node -v
v14.18.2

### Truffle Suite
$ npm install -g truffle@5.1.31
// npm install -g truffle

### Ganache
自分専用のブロックチェーンであり、JSON-RPCサーバを実行する

なんだこれえええええええええええええ