一.合約案例簡介
- 此案例為入門案例,從 合約 -> 編譯 -> web3部署和調用
二.彩票合約源碼
pragma solidity ^0.4.25;
// 彩票合約
contract LotteryShop{
//購買彩票事件,在購買彩票方法中調用
event BuyLottery(address indexed buyer,uint money,uint16 luckNum);
//開獎事件,在開獎方法中調用
event DrawLottery(address winner,uint money,uint16 luckNum);
//購買記錄(購買者的address, 彩票號碼)
mapping(address=>uint) buyMapping;
//購買用戶的地址
address[] usrAdrList;
//管理員地址
address manageAdr;
//合約地址
address contractAdr;
constructor() public payable{
//將合約部署人的地址保存起來作為管理員地址
manageAdr=msg.sender;
//將當前合約對象的地址保存
contractAdr= address(this);
}
//0.1 顯示管理員地址
function ShowManageAdr() constant returns(address){
return manageAdr;
}
//0.2 顯示調用者的彩票數據
function ShowInvokerCaiPiao() constant returns(uint){
return buyMapping[msg.sender];
}
//0.3 顯示管理員余額
function ShowManageBalance() constant returns(uint){
return manageAdr.balance;
}
//0.4 顯示合約余額
function ShowContractMoney() constant returns(uint){
return contractAdr.balance;
}
//0.5 獲取買家地址列表
function getAllUsrAddress() view returns(address[]){
return usrAdrList;
}
//0.5 買彩票方法
function BuyCaiPiao(uint16 haoMa) payable{
//0. 判斷用戶賬戶是否有1 eth
require(msg.value == 1 ether);
//1. 判斷彩票購買列表里是否已經存在當前用戶
require(buyMapping[msg.sender]==0);
//2. 將用戶的錢轉到合約賬戶
contractAdr.send(msg.value);
//3.1 調用事件日志
emit BuyLottery(msg.sender,msg.value,haoMa);
//3.2 添加到mapping
buyMapping[msg.sender] = haoMa;
//3.3 將地址存入 買家數組
usrAdrList.push(msg.sender);
}
//1. 開獎 - 必須是管理員才能操作
function KaiJiang() adminOnly payable returns(uint){
//1.生成一個隨機的開獎號碼
uint256 luckNum = uint256(keccak256(abi.encodePacked(block.difficulty,now)));
//1.1 取模10,保證獎號在10以內
luckNum = luckNum % 3;
address tempAdr;
//2.循環用戶地址數組
for(uint32 i=0; i< usrAdrList.length;i++){
tempAdr = usrAdrList[i];
//2.1 判斷用戶地址 在 mapping中 對應的 CaiPiao.hao 的數字是否一樣
if(buyMapping[tempAdr] == luckNum){
//2.2 記錄日志
emit DrawLottery(tempAdr,contractAdr.balance,uint16(luckNum));
//2.3 將合約里所有的錢轉給 中獎賬戶地址
tempAdr.send(contractAdr.balance);
break;
}
}
//3.返回 中獎號碼
return luckNum;
}
//2. 重置數據
function resetData() adminOnly{
//2.1 循環 買家數組,刪除 購買記錄mapping中對應的記錄
for(uint16 i = 0;i<usrAdrList.length;i++){
delete buyMapping[usrAdrList[i]];
}
//2.2 刪除 買家數組
delete usrAdrList;
}
//3. 銷毀合約
function kill() adminOnly{
//3.1 調用合約自毀函數,把合約賬戶余額轉給當前調用者(管理員)
selfdestruct(msg.sender);
}
//4. 管理員修飾符,只允許管理員操作
modifier adminOnly() {
require(msg.sender == manageAdr);
//代碼修飾器所修飾函數的代碼
_;
}
}
三.nodeJs 編譯合約並保存為文件
const solc = require('solc');
const path = require('path');
const fs = require('fs');
//1.1 合約代碼文件路徑
const sourceFilePath = path.resolve(__dirname,'./LotteryShop.sol');
//1.2 合約編譯成字節碼后的保存文件路徑
const bytecodeFilePath = path.resolve(__dirname,'./LotteryShop.bytecode');
//2.讀取文件
const source = fs.readFileSync(sourceFilePath,'utf-8');
//3.編譯合約原文件
const result = solc.compile(source,1);
//console.log(result);
console.log('1.編譯完成:'+sourceFilePath);
//3.1 將編譯后的代碼寫入到文件中,這樣部署文件 就不需要每次都重新編譯,而是直接讀取編譯好的合約字節碼和abi就行了
fs.writeFileSync(bytecodeFilePath,JSON.stringify(result.contracts[':LotteryShop']),'utf-8');
console.log('2.字節文件寫入完成:'+bytecodeFilePath);
//4.暴露給外部訪問
//module.exports=result.contracts[':CaiPiaoShop'];
四.web3調用代碼
//1.1 導入 編譯好的 合約的 字節代碼 和 abi
const path = require('path');
const fs = require('fs');
//1.2 合約編譯后的文件路徑
const bytecodeFilePath = path.resolve(__dirname,'./LotteryShop.bytecode');
const bytecodeJsonStr = fs.readFileSync(bytecodeFilePath,'utf-8');
const byteCodeJsonObj = JSON.parse(bytecodeJsonStr);
//const {bytecode,interface} = require('./compilCaiPiao');
const bytecode = byteCodeJsonObj.bytecode;
const interface = byteCodeJsonObj.interface;
//2.導入 hd錢包provider
const HDWalletProvider = require("truffle-hdwallet-provider");
//3.助記詞(相當於是我們的私鑰)
const mnemonic = "jar trumpet ..... cat beef"; // 12 word mnemonic
//4.創建 provider,可以用來訪問 以太坊真實網絡節點
const provider = new HDWalletProvider(mnemonic, "https://rinkeby.infura.io/v3/3a60f2b16......",0);//最后的0 是獲取 助記詞 的第1個地址
//5.創建web對象
const Web3 = require('web3');
const web3= new Web3(provider);
// “主函數”作為啟動函數
async function main(){
console.log('開始與以太網交互......');
const usrAdr = await web3.eth.getAccounts();
web3.eth.defaultAccount = usrAdr[0];
console.log('當前調用者的地址:' + web3.eth.defaultAccount);
//6.部署合約到 以太網節點
//let contractObj =await deployContract();
//7.調用合約
//7.0 創建 遠程智能合約(傳入第6步已經部署好的合約地址)
const contractObj = await new web3.eth.Contract(JSON.parse(interface),'0x8f27Ad244cFeabcCc45B81B7406a7EDEA53c5f97');
console.log('獲取【合約】對象成功!');
// 7.1 調用 買彩票 的方法(合約對象,購買的號碼)
//await buyLottery(contractObj,2);
// 7.2 顯示合約賬戶余額
//await showContracMoney(contractObj);
// 查看調用者購買的號碼
//await showInvokerMoney(contractObj);
// 7.3 開獎
await withdrawLottery(contractObj);
// 7.4 顯示買家賬戶列表
await showUsrList(contractObj);
await showContracMoney(contractObj);
await showInvokerMoney(contractObj);
// 7.5 重置數據
//await resetContract(contractObj);
//await killContract(contractObj);
//await showUsrList(contractObj);
console.log('結束!');
}
// 啟動
main();
// 1.部署合約
async function deployContract() {
console.log('開始部署合約......');
let contractObj = await new web3.eth.Contract(JSON.parse(interface))
.deploy({
data: bytecode,
arguments: []
}).send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log('部署成功,合約地址:【'+contractObj.options.address+'】'); // 0x8f27Ad244cFeabcCc45B81B7406a7EDEA53c5f97
return contractObj;
}
// 2.買彩票
async function buyLottery(contractObj,luckNum) {
console.log('開始購買彩票,稍等15秒~~');
await contractObj.methods.BuyCaiPiao(luckNum).send(
{
from:web3.eth.defaultAccount,
value:web3.utils.toWei('1','ether'),
gas:'1000000'
}
);
console.log('購買彩票完畢~~');
}
// 3.開獎
async function withdrawLottery(contractObj) {
console.log('開獎進行中請等待15秒左右...');
let luckNum;
await contractObj.methods.KaiJiang().send({
from:web3.eth.defaultAccount,
gas:'1000000'
},function (err,result) {
//將返回的 開獎數字 賦值給 局部變量 luckNum
luckNum = result;
console.log('開獎完成, luckNum:');
console.log(result);
});
console.log('開獎完成:'+luckNum);
}
// -------------------------------------------
// 4.顯示合約余額
async function showContracMoney(contractObj) {
let msg = await contractObj.methods.ShowContractMoney().call();
console.log('合約賬號里的余額:'+msg);
}
// 5.顯示管理員地址
async function showManageAddress(contractObj) {
let msg = await contractObj.methods.ShowManageAdr().call();
console.log('合約管理員地址:'+msg);
}
// 6.查看調用者購買的號碼
async function showInvokerMoney(contractObj) {
//獲取當前調用合約的賬戶
const usrAdr = await web3.eth.getAccounts();
// 1.因為 被調用合約里的ShowInverkCaiPiao()方法 需要使用 msg.sender
// 所以在 call({from:當前調用者賬號地址})的時候要指定from為當前調用者
// let msg = await contractObj.methods.ShowInverkCaiPiao().call({
// from:usrAdr[0]
// });
// 2.或者,可以設置 web3.eth.defaultAccount = usrAdr[0],再調用 call()時,會自動設置 from
//設置給 默認賬戶
web3.eth.defaultAccount = usrAdr[0];
let msg1 = await contractObj.methods.ShowInvokerCaiPiao().call();
console.log('調用者購買的號碼:' + msg1);
}
// 7.重置合約數據
async function resetContract(contractObj) {
// 因為 合約的 resetData 方法里 修改了 全局變量,所以需要使用 send 方法
let msg = await contractObj.methods.resetData().send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log('交易記錄:');
console.log(msg);//打印從交易記錄
console.log('彩票購買記錄 和 買家地址列表 已清零~~');
}
// 8.銷毀合約
async function killContract(contractObj) {
console.log('合約銷毀開始~~');
await contractObj.methods.kill().send({
from:web3.eth.defaultAccount,
gas:'1000000'
});
console.log('合約已作廢~~');
}
// 9. 顯示買家列表
async function showUsrList(contractObj) {
let msg = await contractObj.methods.getAllUsrAddress().call();
console.log('買家地址列表:');
console.log(msg);
}