//tips
//smart contract
早速Lottery.solのコントラクトの内容を作成していく。
この中でarrayについての記載がされるがnested dynamic arrayはsolidity単体では機能するがjs,web3,abiとの連動では、arrayの中にarrayを組み込む形式は受け渡しできないので、考慮する必要がある。
これはsolidityでのarray,stringの内容をweb3,abi,jsへ持っていけないことに通じる。
pragma solidity ^0.4.17;
contract Lottery{
address public manager;
address[] public players;//address[10] とすると限定10となるので無制限の参加は[]のまま
function Lottery()public
{
manager=msg.sender;//msg.senderはトランザクション実行者
//msgオブジェクトはどのメソッドからも呼び出せるので便利
}
function enter()public payable{
require(msg.value> .01 ether);//etherを使うことで膨大な0をつけなくても良くなる
//最低投下金額を定めている
players.push(msg.sender);
}
}
Pickwinnerメソッドも実装したいが、solidityには乱数発生機能はないので疑似乱数装置を活用していくことになる。疑似なので脆弱性を含み、チート行為を行われる可能性があることは考慮する。
function random()private view returns(uint) {
//開発用のみ使用
return uint(keccak256(block.difficulty,now,players));//ブロック難易度、現在時刻、プレイヤーアドレスをもとに疑似乱数生成
}
ここで大きな数を返すので、参加者の人数分で割り、その余り-1をindex番号として使用する。
random() % players.length
メソッドにすると下記になる。
function pickWinner()public{
uint index=random() % players.length;
players[index].transfer(this.balance);//0x10234...。これは数字や文字ではなくオブジェクトとして返される。
//thisはこのコンタクトのインスタンス。this.balanceで残高を取得
//勝者が決定したのでこのpoolは解散させる
players=new address[](0);
}
ただ、まだmanager以外もこのpickwinnerにアクセスできてしまうので制約を加える必要がある。
Requireを用いることになり、条件に当てはまらない場合は実行させないようにする。この確認作業をバリデートというよう。
こちらを追加。
require(msg.sender==manager);
また上記は便利で他のメソッドにも使用したいのでmodifierで定義。最後にプレイヤー一覧取得機能もつけた。
function pickWinner()public restricted{
//require(msg.sender==manager);
uint index=random() % players.length;
players[index].transfer(this.balance);
players=new address[](0);
}
//require(msg.sender==manager);は他のメソッドでも使用できるのでmodifierに記載
modifier restricted(){
require(msg.sender==manager);
_;
}
function getPlayers()public view returns(address[]){
//参加者一覧表示機能
return players;
}
ここまではremixでやってきたので、今度はネットワークに繋いで実行していく。これは先のinbox同様にlotteryフォルダを作成し、npm installを実行。
Contractsに先のremixの内容を移したら、compile.js,deployed.jsの中身をlotteryのもの微修正した後、testフォルダの中にlottery.test.jsを作成していく。
内容自体は先のinboxのものと大きく変わらない。
const assert=require('assert');//簡易テストをできるassert.equal()などを使用するため
const ganache=require('ganache-cli');
const Web3=require('web3');
const web3=new Web3(ganache.provider());
const {interface,bytecode}=require('../compile');
let lottery;
let accounts;
beforeEach(async()=>{
accounts = await web3.eth.getAccounts();
lottery= await new web3.eth.Contract(JSON.parse(interface))
.deploy({data:bytecode})
.send({from:accounts[0],gas:'1000000'});
});
describe('Lottery Contract',()=>{
it('deploys a contract',()=>{
assert.ok(lottery.options.address);//値が入っていれば成功
});
})
Enter処理を行ったアカウントの内容確認と実施数確認も追加している。
it('allows multiple accounts to enter',async()=>{
//enter処理を実行した複数のアカウントに対する確認
//
await lottery.methods.enter().send({
from:accounts[0],
value:web3.utils.toWei('0.02','ether')
});
await lottery.methods.enter().send({
from:accounts[1],
value:web3.utils.toWei('0.02','ether')
});
await lottery.methods.enter().send({
from:accounts[2],
value:web3.utils.toWei('0.02','ether')
});
const players=await lottery.methods.getPlayers().call({
from:accounts[0]
});
//from:accounts[0]でその他のものも取得できるよう
assert.equal(accounts[0],players[0]);//実行アカウント自体が格納されているかの確認
assert.equal(accounts[1],players[1]);
assert.equal(accounts[2],players[2]);
assert.equal(3,players.length);//実行されたplayer数が正しいかの確認
});