《深入掌握以太坊核心技术》--14-Dapp示例

简单Dadpp投票

任务

在这里插入图片描述

开发环境–Linux准备

下面是基于 Linux的安装指南。这要求预先安装 nodeis 和 npm,再用 npm安装 ganache-cli、web3 和 solc0.4.25(当时版本)

mkdir simple_voting.dapp
cd simple_voting.dapp
npm init
npm install ganache-cli web3@0.20.1 solc
node node_modules/.bin/ganache-cli

输出以下内容则成功
在这里插入图片描述

Ganache-cli是一个用于以太坊开发和测试的命令行工具。它提供了一个本地的以太坊区块链环境,可以用于开发智能合约、部署合约和进行交易等操作。使用ganache-cli,开发者可以在本地快速搭建一个私有的以太坊网络,而无需连接到真实的以太坊网络。它还提供了一些方便的功能,如创建模拟账户、生成测试数据、调整区块链状态等,有助于简化以太坊应用程序的开发和测试过程。

Solidity 合约

Voting 的合约,这个合约有以下内容:

  • 一个构造函数,用来初始化一些候选者。
  • 一个用来投票的方法(对投票数加 1)
  • 一个返回候选者所获得的总票数的方法
pragma solidity ^0.4.22;

// 选举合约
contract Voting {
    // 候选人列表
    bytes32[] public candidateList;
    
    // 候选人得票数映射
    mapping(bytes32 => uint8) public votesReceived;
    
    // 构造函数,初始化候选人列表
    constructor(bytes32[] candiListNames) public {
        candidateList = candiListNames;
    } 
    
    // 检查候选人是否有效
    function validcandidate(bytes32 candidateName) internal view returns(bool) {
        for (uint8 i = 0; i < candidateList.length; i++) {
            if (candidateName == candidateList[i])
                return true;
        }
        return false;
    }
    
    // 投票函数
    function vote(bytes32 candidateName) public {
        require(validcandidate(candidateName)); // 确保候选人有效
        votesReceived[candidateName] += 1; // 增加候选人得票数
    }
    
    // 查询某候选人的总得票数
    function totalVotesFor(bytes32 candidateName) view public returns(uint8) {
        require(validcandidate(candidateName)); // 确保候选人有效
        return votesReceived[candidateName];
    }
}

编译合约

在 node console输入以下命令

// 引入Web3库
var Web3 = require('web3')

// 创建一个Web3实例,连接到本地的以太坊节点
var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))

// 引入Solidity编译器
var solc = require('solc')

// 读取Voting.sol文件中的源代码
var SourceCode = fs.readFileSync('Voting.sol').toString()

// 编译Solidity源代码
var compiledCode = solc.compile(SourceCode)

// 从编译结果中提取合约的ABI(应用程序二进制接口)
var abi = JSON.parse(compiledCode.contracts[':Voting'].interface)

// 获取编译后的字节码
var bytecode = compiledCode.contracts[':Voting'].bytecode

// 使用ABI和字节码创建Voting合约对象
var Votingcontract = web3.eth.contract(abi)

// 构造部署智能合约的交易对象
var deployTxObj = {
    data: bytecode,                 // 合约的字节码
    from: web3.eth.accounts[0],     // 从哪个账户发送部署交易
    gas: 3000000                    // 设定gas限制
}

// 部署智能合约并创建合约实例,传入候选人名单['Alice','Bob','Cary']
var contractInstance = Votingcontract.new(['Alice','Bob','cary'], deployTxObj)


// 给Alice投票
contractInstance.vote("Alice", {from: web3.eth.accounts[0]});
// 查询Alice的总得票数
contractInstance.totalVotesFor("Alice");
contractInstance.totalVotesFor("Alice").toString();
// 查询Bob的总得票数
contractInstance.totalVotesFor.call("Bob").toString();
// 给Cary投票
contractInstance.vote("Cary", {from: web3.eth.accounts[0]});
// 查询Cary的总得票数
contractInstance.totalVotesFor.call("Cary").toString();

在这里插入图片描述

网页交互

Html

<!DOCTYPE html>
<html>
    <head>
        <title>Voting DApp</title>
        <link
href='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css' rel='stylesheet' type='text/css'>
    </head>
    <body class="container">
        <h1>Simple Voting DApp</h1>
        <div>
            <table class="table table-bordered">
                <thead>
                    <tr>
                        <th>Candidate</th>
                        <th>Vote Count</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>Alice</td>
                        <td id="candidate-1"></td>
                    </tr>
                    <tr>
                        <td>Bod</td>
                        <td id="candidate-2"></td>
                    </tr>
                    <tr>
                        <td>Cary</td>
                        <td id="candidate-3"></td>
                    </tr>
                </tbody>
            </table>
            <input type="text" id="candidate" />
            <a href="#" onclick="voteForCandidate" class="btn btn-primary">Vote</a>
        </div>
    </body>
    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/web3@4.8.0/dist/web3.min.js"></script>
    <script src="./index.js"></script>
</html>

index.js

var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"))

var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidate","type":"string"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"candidate","type":"string"}],"name":"votesReceived","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"string"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"candidateList","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]');
var contractAddr = "0x69c93bc7ef2cc9b0be438387204cee27b43281f6";
var VotingContract = web3.eth.contract(abi);
var contractInstance = VotingContract.at(contractAddr);

var candidates = { "Alice": "candidate-1", "Bod": "candidate-2", "Cary": "candidate-3" };

function voteForCandidate() {
    let candidateName = $("#candidate").val();
    try {
        contractInstance.vote(candidateName, { from: web3.eth.accounts[0] },(err, res)=> {
            if(err) {
                console.log("Error:", err);
            }
            else {
                let id =candidates[candidateName];
                let count =contractInstance.totalVotesFor(candidateName).toString();
                $("#"+ id).html(count);
    }
    })
    }catch (err){}
}
$(document).ready(function () {
    var candidateList = Object.keys(candidates);
    for (let i = 0; i < candidateList.length; i++){
        let name = candidateList[i];
        let count = contractInstance.totalVotesFor(name).toString();
        $("#" + candidates[name]).html(count);
    }
});

在这里插入图片描述

编译脚本

// 引入必要的模块
const fs = require('fs-extra'); // 用于文件操作的模块
const solc = require('solc'); // Solidity 编译器
const path = require('path'); // 用于处理文件路径的模块

// 定义合约编译结果存储的路径
const compiledPath = path.resolve(__dirname, '../compiled'); // 绝对路径
fs.readFileSync(compiledPath); // 读取编译路径的内容
fs.ensureDirSync(compiledPath); // 确保编译路径存在,如果不存在则创建

// 定义智能合约文件路径和读取合约源代码
const contractPath = path.resolve(__direname, '../contracts', 'Car.sol'); // 合约文件路径
const contractSource = fs.readFileSync(contractPath, 'utf-8'); // 读取合约源代码

// 使用 Solidity 编译器编译合约源代码
let compileResult = solc.compile(contractSource, 1); // 对合约源代码进行编译

// 输出编译结果
//consile.log(compileResult);

// 检查是否有编译错误,如果有则抛出异常
if (Array.isArray(compileResult.errors) && compileResult.errors.length) {
    throw new Error(compileResult.errors[0]);
}

// 遍历编译结果中的合约对象
Object.keys(compileResult.contracts).forEach(name => {
    let contractName = name.replace(/^:/, ''); // 获取合约名称
    let filePath = path.resolve(compiledPath, '../compiled', '$(contractName).json'); // 确定 JSON 文件的路径
    fs.outputJsonSync(filePath, compileResult.contracts[name]); // 将合约编译结果写入 JSON 文件
    console.log("Saying json file to", filePath); // 输出 JSON 文件的保存路径
})

部署脚本

// 引入web3库
const Web3 = require('web3');
// 创建web3实例并连接到本地节点
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
// 引入文件系统模块
const fs = require('fs-extra');
// 引入路径模块
const path = require('path');
// 引入readline模块的Interface类
const { Interface } = require('readline');

// 指定合约编译后的JSON文件路径
const filePath = path.resolve(__dirname, '../compiled.json');
// 从指定路径读取合约编译后的JSON文件,并获取其中的Interface和bytecode字段
const { Interface, bytecode } = require(filePath);

// 使用异步函数自执行
(async () => {
    // 获取以太坊节点上的账户列表
    let accounts = await web3.eth.getAccounts();
    // 计时开始
    console.time("deploy time");
    // 部署合约并返回部署结果
    let result = await new web3.eth.Contract(JSON.parse(Interface))
        .deploy({ data: bytecode, arguments: ["BMW"] })
        .send({ from: accounts[0], gas: 500000 });
    // 计时结束
    console.timeEnd("deploy time");
    // 打印合约地址
    console.log("contract address", result.options.address);
})();

测试脚本

const assert = require('assert')
const path = require("path")
const Web3 = require('web3')
const ganache = require('ganache-cli');
const { describe, before, beforeEach } = require('node:test');

const web3 = new Web3(ganache.provider());

const contractPath = path.resolve(__dirname, './compiled/Car.json');
const { interface, bytecode } = require(contractPath);

let contract;
let accounts; 
const initialBrand = 'BMN';

describe('#contract', () => {
    beforeEach(async () => {
        accounts = await web3.eth.getAccounts();
        contract = await web3.eth.Contract(JOSN.parse(interface))
            .deploy({ data: bytecode, arguments: [initialBrand] })
            .send({ from: accounts[0], gas: 300000 });
        console.log('合约已部署!!');
    })
    it("deployed contract successfully", () => {
        assert.ok(contract.options.address);
    })
    it ('should has ainitial brand', async () => {
        const brand = await contract.methods.brand().call();
        assert.equal(brand, initialBrand);
    });
    it ('should set a new brand', async () => {
        const newBrand = 'Audi';
        await contract.methods.setBrand(newBrand)
            .send({ from: accounts[0] });
        const brand = await contract.methods.brand().call();
        assert.equal(brand, newBrand);
        
    })
})

  • 26
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值