本文中直接用到的工具:Remix, truffle(v5.1.0), ganache(v2.1.2), MetaMask等
Chrome插件。可选择连接不同的节点;导入用户私钥后还可查看用户的以太币数量;关联账户后部分交易产生时可弹窗让用户确认交易。
Remix: http://remix.ethereum.org
在File explorers
里可进行新建合约,编辑合约等操作。
在Solidity compiler
里选择Solidity编译器版本,先编译验证语法是否有问题,再在Deploy & run transactions
中选择环境:Environment
选择Injected Web3
会弹出netmask插件进行账号绑定(Environment也可选Web3 Provider,会提示连接到以太坊节点,可先连接localhost:8545进行测试。)
Deploy & run transactions
面板中合约选择下拉框的下方还有Deploy和At Address,前一个可通过构造函数创建合约,后一个通过输入一个合约的地址获取(连接?)合约,创建(/连接)成功后,下方会显示出合约中可调用的方法,可在这个面板上操作调用合约的函数,(通过输入地址连接上的合约,需要配置账户等信息才会执行交易)。
合约编写窗口下方为控制台,可在其中通过web3的API执行命令,如web3.eth.getBalance(“0x04555018d100bd9ad75544de623ec8e3692e423a”);
# truffle unbox react
✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
......
✔ Setting up box
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
Test dapp: cd client && npm test
Run dev server: cd client && npm run start
Build for production: cd client && npm run build
(直接下载比较慢,考虑使用代理下载) 执行命令 truffle unbox react
下载一个模板项目。其中client目录下是一个React项目,当执行到Setting up box
这一步时,耗时较长,其实是在client/node_modules目录下下载所需依赖(100+MB)。
也可在TRUFFLE BOXES搜索box的名字下载,如react在https://www.trufflesuite.com/boxes/react,可在其中点击下载按钮下载,但这样下载client目录中没有node_modules,需要自己解决依赖问题。
直接在truffle网站上下载的项目目录结构如下:
.
├── box-img-lg.png
├── box-img-sm.png
├── client
│ ├── package.json
│ ├── package-lock.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── README.md
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── getWeb3.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ └── serviceWorker.js
│ └── yarn.lock
├── contracts
│ ├── Migrations.sol
│ └── SimpleStorage.sol
├── LICENSE
├── migrations
│ ├── 1_initial_migration.js
│ └── 2_deploy_contracts.js
├── README.md
├── test
│ ├── simplestorage.js
│ └── TestSimpleStorage.sol
├── truffle-box.json
└── truffle-config.js
root@kali:/BlockChain/react-box-master# truffle compile
Compiling your contracts...
===========================
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/SimpleStorage.sol
> Artifacts written to /BlockChain/react-box-master/client/src/contracts
> Compiled successfully using:
- solc: 0.5.12+commit.7709ece9.Emscripten.clang
项目目录下执行truffle compile
进行编译,编译后生成了client/src/contracts目录,该目录下有两个文件:Migrations.json SimpleStorage.json
,
配置节点信息,修改后的文件内容如下:
const path = require("path");
module.exports = {
// See
// to customize your Truffle configuration!
contracts_build_directory: path.join(__dirname, "client/src/contracts"),
networks: {
development: {
host: "localhost",
port: 8545,
network_id: "*"
}
}
};
使用truffle migrate
进行部署:
root@kali:/BlockChain/react-box-master# truffle migrate
Compiling your contracts...
===========================
> Everything is up to date, there is nothing to compile.
Starting migrations...
======================
> Network name: 'development'
> Network id: 5777
> Block gas limit: 0x6691b7
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x872c9be81fc25005dc3535821e9fe52375b0ccb409cbdf4b85e2483d76bbcfe0
> Blocks: 0 Seconds: 0
> contract address: 0xe7536a28473dFF0875b81bDdb7dCe10CA1e95cDe
> block number: 1
> block timestamp: 1574910360
> account: 0xA5D2948e8fd43D6d0C8eD7bCe0fEba3039A8D009
> balance: 99.99472518
> gas used: 263741
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00527482 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00527482 ETH
2_deploy_contracts.js
=====================
Deploying 'SimpleStorage'
-------------------------
> transaction hash: 0xba46904f988290f25d36918c7f731f9d4bdb3d2b6b3c2520f2b79f90f0df5173
> Blocks: 0 Seconds: 0
> contract address: 0x0D7C49CFfD2e82B5C124F455fCEaDC27d1cdC41D
> block number: 3
> block timestamp: 1574910361
> account: 0xA5D2948e8fd43D6d0C8eD7bCe0fEba3039A8D009
> balance: 99.99173734
> gas used: 107369
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00214738 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00214738 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.0074222 ETH
在在线IDE中测试合约,先将MetaMask连接到ganache启动的节点,导入其中的一个测试账号。将SimpleStorage.sol拷贝到Remix Solidity IDE中,编译,然后在Deploy & run transactions
窗口中选中合约,在At Address
按钮后的输入框中输入上面部署得到的合约地址0x0D7C49CFfD2e82B5C124F455fCEaDC27d1cdC41D
,再点击按钮,获取到合约实例。
在下方显示出的合约方法列表中,输入参数并点击显示set方法名的按钮调用方法,弹出MetaMask窗口,确认交易,完成后在ganache中可看到新增了一个块和一个类型为CONTRACT CALL
的交易。再在在线IDE的页面上点击get方法按,可看到返回的就是刚才存入的值。
client/src/App.js中定义了一个React组件,当生命周期方法componentDidMount被调用时,获取web3、账号、合约实例,更新状态,并调用runExample方法。runExample方法中先调用合约的set方法写了一个值5,再通过合约的get方法获取刚写入的值,再将获取的值更新到组件的状态中,然后组件的render方法被调用,该值以及一段HTML被渲染到DOM中。App.js代码如下:
import React, { Component } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
class App extends Component {
state = { storageValue: 0, 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 = SimpleStorageContract.networks[networkId];
const instance = new web3.eth.Contract(
SimpleStorageContract.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;
// Stores a given value, 5 by default.
await contract.methods.set(5).send({ from: accounts[0] });
// Get the value from the contract to prove it worked.
const response = await contract.methods.get().call();
// Update state with the result.
this.setState({ storageValue: response });
};
render() {
if (!this.state.web3) {
return Loading Web3, accounts, and contract...;
}
return (
Good to Go!
Your Truffle Box is installed and ready.
Smart Contract Example
If your contracts compiled and migrated successfully, below will show
a stored value of 5 (by default).
Try changing the value stored on line 40 of App.js.
The stored value is: {this.state.storageValue}
);
}
}
export default App;
可将await contract.methods.set(5).send({ from: accounts[0] });
这行代码注释,然后在client目录下运行npm start
启动服务,浏览器访问localhost:3000
,页面上可看到上一步从在线IDE中写入的值。
就在模板项目上进行修改,先在项目目录下contracts目录下新建合约Voting.sol(从某教程拷贝而来,一个简单的投票合约,根据solidity版本稍作修改):
pragma solidity ^0.5.0;
contract Voting {
bytes32[] candidates;
mapping(bytes32 => uint) candidatesVotingCount;
// https://stackoverflow.com/questions/53460851/typeerror-data-location-must-be-memory-for-parameter-in-function-but-none-wa
// google浏览器下remix智能合约中 bytes32[] 类型的输入:https://blog.csdn.net/github_38575699/article/details/101269929
// 新版remix还是solidity本身的问题?Deploy的输入框中不能直接输入字符串数组,要转成十六进制形式,且bytes32类型要写成32个字节的十六进制形式
// ["A", "B"]应写成下面的形式:
/* ["0x4100000000000000000000000000000000000000000000000000000000000000",
"0x4200000000000000000000000000000000000000000000000000000000000000"]
*/
constructor(bytes32[] memory _candidates) public {
candidates = _candidates;
}
function votingToPerson(bytes32 person) public {
assert(isValidToPerson(person));
candidatesVotingCount[person] += 1;
}
function votingTotalToPerson(bytes32 person) view public returns(uint) {
require(isValidToPerson(person));
return candidatesVotingCount[person];
}
function isValidToPerson(bytes32 person) view public returns(bool) {
for (uint i = 0; i < candidates.length; i++) {
if (candidates[i] == person) {
return true;
}
}
return false;
}
}
然后执行truffle compile
编译合约,编译完成后client/src/contracts比之前多了一个Voting.json
修改migrations目录下的2_deploy_contracts.js
,将其中的模板项目合约名SimpleStorage
全部换成Voting
,再执行truffle migrate
进部署,报错:
......
Error: Error: Error: *** Deployment Failed ***
"Voting" -- Invalid number of parameters for "undefined". Got 0 expected 1!.
at Object.run (/node-v12.13.0-linux-x64/lib/node_modules/truffle/build/webpack:/packages/migrate/index.js:92:1)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Truffle v5.1.0 (core: 5.1.0)
Node v12.13.0
根据一些解决方法(如https://stackoverflow.com/questions/54304214/truffle-smart-contract-error-invalid-number-of-parameter),可知2_deploy_contracts.js
中的deployer.deploy可以传第2个参数,试了几次之后发现第2个参数是合约的构造函数的参数,于是修改2_deploy_contracts.js
内容如下:
var Voting = artifacts.require("./Voting.sol");
module.exports = function(deployer) {
deployer.deploy(Voting, ["0x4100000000000000000000000000000000000000000000000000000000000000", "0x4200000000000000000000000000000000000000000000000000000000000000"]);
};
然后使用truffle migrate
进行部署,部署后已经可以根据输出的合约地址在在线IDE中进行测试。
先另存App.js作为备份,然后修改App.js的内容如下:
import React, { Component } from "react";
import Voting from "./contracts/Voting.json";
import getWeb3 from "./getWeb3";
import "./App.css";
class App extends Component {
state = {voting: [], 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 = Voting.networks[networkId];
const instance = new web3.eth.Contract(
Voting.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.getVoting);
} 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);
}
};
getVoting = async () => {
const { contract } = this.state;
let candidates = ["0x4100000000000000000000000000000000000000000000000000000000000000", "0x4200000000000000000000000000000000000000000000000000000000000000"];
let voting = new Array();
await Promise.all(candidates.map(async (elem, index) => {
voting.push({
name: elem,
// 获取票数
count: await contract.methods.votingTotalToPerson(elem).call(),
key: index
})
}));
this.setState({ voting});
};
render() {
if (!this.state.web3) {
return Loading Web3, accounts, and contract...;
}
return (
Voting
{
this.state.voting.map((candidate) => {
return ({candidate.name}获得{candidate.count}票
)
})
}
);
}
}
export default App;
在client目录下用命令npm start
启动服务,再从浏览器访问localhost:3000
可看到列出的投票数,也可通过页面上的输入框和按钮进行投票。