初学以太坊智能合约
直接打开 http://trufflesuite.com/tutorial/index.html 有新手指导区,英语好的老板可以直接参考原文,下面是根据教程加上我的体验进行的翻译和实践记录。
这个教程可以让你体验做一个dapp宠物商店(pet shop),通过这个教程可以完整的体验一次合约的开发。
这个教程主要会让我们体验这么几个过程
设置开发环境
需要安装
上述两个安装完成之后,npm安装 truffle
# 安装truffle
npm install -g truffle
# 确认是否安装完成
truffle version
使用 Truffle Box 创建 Truffle 项目
# 新建文件夹
mkdir pet-shop-tutorial
# 进入文件夹
cd pet-shop-tutorial
# 使用truffle 创建项目框架
truffle unbox pet-shop
ps:介于国内的网络原因,这一步会经常失败,老板可以考虑科学上网,或者失败之后多运行几次,总有成功的。等不了的可以直接去clone他的远嘛到该目录 https://github.com/trufflesuite/pet-shop-tutorial
成功之后就有如下的目录结构
挑选主要文件讲解一下
├── LICENSE
├── contracts #包含我们智能合约的 Solidity 源文件。这里有一个重要的契约叫做Migrations.sol,后续教程会讲到
├── migrations #Truffle 使用迁移系统来处理智能合约部署。迁移是一个额外的特殊智能合约,用于跟踪更改。
├── src
├── test # 包含我们智能合约的 JavaScript 和 Solidity 测试
└── truffle-config.js # 配置文件
开始写第一个智能合约
1.在 contracts文件夹下面创建 Adoption.sol
2.添加如下内容到新建的文件
# 注明了 solidity的合约最低版本,^ 表示需要更高的版本,即向上兼容
pragma solidity ^0.5.0;
contract Adoption {
}
设置变量
Solidity 是一种静态类型语言,这意味着必须定义字符串、整数和数组等数据类型。 Solidity 有一种独特的类型,称为地址。地址是以太坊地址,存储为 20 字节值。以太坊区块链上的每个账户和智能合约都有一个地址,可以从该地址发送和接收以太币。
pragma solidity ^0.5.0;
contract Adoption {
address[16] public adopters;
}
开发第一个方法:收养一只宠物
// Adopting a pet
function adopt(uint petId) public returns (uint) {
require(petId >= 0 && petId <= 15);
adopters[petId] = msg.sender;
return petId;
}
开发第二个方法:返回收养数据
// Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
return adopters;
}
编译和迁移智能合约
Solidity是一种编译语言,意味着我们需要将我们的Solidity编译成字节码,以便Ethereum虚拟机(EVM)执行。把它看作是把我们的人类可读的Solidity翻译成EVM能理解的东西。
在终端中,确保你在包含dapp的目录的根部,并输入。
truffle compile
迁移到区块链
现在,我们已经成功地编译了我们的合约,是时候将它们迁移到区块链上了!这就是我们的工作。
你会看到在migrations/目录下已经有一个JavaScript文件。1_initial_migration.js。这是处理部署Migrations.sol合约以观察后续的智能合约迁移,并确保我们在未来不会重复迁移未改变的合约。
现在我们准备创建我们自己的迁移脚本。
1.在migrations/目录下创建一个名为2_deploy_contracts.js的新文件。
2.在2_deploy_contracts.js文件中添加以下内容。
var Adoption = artifacts.require("Adoption");module.exports = function(deployer) { deployer.deploy(Adoption);};
安装Ganache
下载地址:http://trufflesuite.com/ganache
Ganache是一个快速启动个人以太坊区块链,您可以使用它来运行测试、执行命令和检查状态,同时控制链的运行方式。如果不用他,直接在真实的网络部署测试的话,是需要花费eth的。安装这个在本地就不需要了。
这个很简单,下载安装就好,然后双击运行,就可以看到本地跑起来了。
本地的端口跑在 HTTP://127.0.0.1:7545
使用 lsof -i:7544 确认已经成功运行
运行迁移
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: 6721975 (0x6691b7)1_initial_migration.js====================== Replacing 'Migrations' ---------------------- > transaction hash: 0xf848aad72fb6ab403f201ff2fa1113d959d1ecb7fe331260752c263925ca403c > Blocks: 0 Seconds: 0 > contract address: 0xC0112c7071c9Fab2f42D438B738D51b367fca38a > block number: 1 > block timestamp: 1638962361 > account: 0x4035a5D4709fD2Cb87160Bd29ca5eD88C9E5765F > balance: 99.99616114 > gas used: 191943 (0x2edc7) > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00383886 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00383886 ETH2_deploy_contracts.js===================== Replacing 'Adoption' -------------------- > transaction hash: 0xabfd61f1d738ab26c5c0f65ee9cc37787d3cc46656cf9ce59c559affeabbb71b > Blocks: 0 Seconds: 0 > contract address: 0x705b3e834149c2eF5bbE00440474f0aC3327451A > block number: 3 > block timestamp: 1638962361 > account: 0x4035a5D4709fD2Cb87160Bd29ca5eD88C9E5765F > balance: 99.99123784 > gas used: 203827 (0x31c33) > gas price: 20 gwei > value sent: 0 ETH > total cost: 0.00407654 ETH > Saving migration to chain. > Saving artifacts ------------------------------------- > Total cost: 0.00407654 ETHSummary=======> Total deployments: 2> Final cost: 0.0079154 ETH
最终花费了0.0079154的eth,如果直接在线上的话就需要花钱了。。
创建用户界面以与智能合约交互
实例化web3
在文本编辑器中打开/src/js/app.js
检查该文件。注意,有一个全局的App对象来管理我们的应用程序,在init()中加载宠物数据,然后调用函数initWeb3()。web3的JavaScript库与以太坊区块链进行互动。它可以检索用户账户,发送交易,与智能合约互动,等等。
web3的api可以参考 https://github.com/ethereum/web3.js/
这段代码本质上就是先获得一个web3的对象,然后通过该web3api与合约进行交互,大部分都是封装好了的api。
最开始我们写了2个合约的方法function adopt function getAdopters() 定义了一个类型为address的变量adopters
1.初始化web3
2.初始化合约。初始化合约的时候载入Adoption.json这个文件,该文件是我们执行truffle compile生成的。这个文件包含了合约的工程文件(artifact file),Artifacts是关于我们合同的信息,比如它的部署地址和应用二进制接口(ABI)。ABI是一个JavaScript对象,定义了如何与合同互动,包括其变量、函数及其参数。( Artifacts are information about our contract such as its deployed address and Application Binary Interface (ABI). The ABI is a JavaScript object defining how to interact with the contract including its variables, functions and their parameters.)
3.markAdopted判断是否已经被买了,通过adoptionInstance.getAdopters.call();调用所有记录的adopters地址,如果不是初始化的地址,就标记为success,并且disable。返回的合约信息如下图:
4.bindEvents绑定事件,如果可以购买,则触发领养 App.contracts.Adoption.deployed(),然后刷新UI即可。
// app.jsApp = { web3Provider: null, contracts: {}, init: async function () { // Load pets. $.getJSON("../pets.json", function (data) { var petsRow = $("#petsRow"); var petTemplate = $("#petTemplate"); for (i = 0; i < data.length; i++) { petTemplate.find(".panel-title").text(data[i].name); petTemplate.find("img").attr("src", data[i].picture); petTemplate.find(".pet-breed").text(data[i].breed); petTemplate.find(".pet-age").text(data[i].age); petTemplate.find(".pet-location").text(data[i].location); petTemplate.find(".btn-adopt").attr("data-id", data[i].id); petsRow.append(petTemplate.html()); } }); return await App.initWeb3(); }, initWeb3: async function () { // Modern dapp browsers... if (window.ethereum) { App.web3Provider = window.ethereum; try { // Request account access await window.ethereum.request({ method: "eth_requestAccounts" }); } catch (error) { // User denied account access... console.error("User denied account access"); } } // Legacy dapp browsers... else if (window.web3) { App.web3Provider = window.web3.currentProvider; } // If no injected web3 instance is detected, fall back to Ganache else { App.web3Provider = new Web3.providers.HttpProvider( "http://localhost:7545" ); } web3 = new Web3(App.web3Provider); return App.initContract(); }, initContract: function () { $.getJSON("Adoption.json", function (data) { // Get the necessary contract artifact file and instantiate it with @truffle/contract var AdoptionArtifact = data; App.contracts.Adoption = TruffleContract(AdoptionArtifact); // Set the provider for our contract App.contracts.Adoption.setProvider(App.web3Provider); // Use our contract to retrieve and mark the adopted pets return App.markAdopted(); }); return App.bindEvents(); }, bindEvents: function () { $(document).on("click", ".btn-adopt", App.handleAdopt); }, markAdopted: function () { var adoptionInstance; App.contracts.Adoption.deployed() .then(function (instance) { adoptionInstance = instance; return adoptionInstance.getAdopters.call(); }) .then(function (adopters) { for (i = 0; i < adopters.length; i++) { if (adopters[i] !== "0x0000000000000000000000000000000000000000") { $(".panel-pet") .eq(i) .find("button") .text("Success") .attr("disabled", true); } } }) .catch(function (err) { console.log(err.message); }); }, handleAdopt: function (event) { event.preventDefault(); var petId = parseInt($(event.target).data("id")); var adoptionInstance; web3.eth.getAccounts(function (error, accounts) { if (error) { console.log(error); } var account = accounts[0]; App.contracts.Adoption.deployed() .then(function (instance) { adoptionInstance = instance; // Execute adopt as a transaction by sending account return adoptionInstance.adopt(petId, { from: account }); }) .then(function (result) { return App.markAdopted(); }) .catch(function (err) { console.log(err.message); }); }); },};$(function () { $(window).load(function () { App.init(); });});
安装metamask进行测试
先安装metamask插件,可以在chrome插件商城安装。
如果有了直接点击添加网络
按照如下资料进行填写
这里的http://127.0.0.1:7545就是你本地启动Gnanche的时候启动的
填写完成之后,选择网络,把网络切换到本地测试网络
点击小狐狸插件头像,切换网络即可
切换到本地网络之后,可以直接倒入本地的账号
点击钥匙图标,查看本地的私钥,然后选择小狐狸直接倒入。
倒入成功之后,你账号就有了99.99eth,其他账号没有消耗的话应该都是100ETH
运行你的首个DAPP
回到开发目录下
npm run dev
首个DAPP就跑起来了。点击adopt就可以唤起小狐狸进行领养和gas支付了。
那么至此,基本上就了解了一个DAPP的运行逻辑了。
回顾
梳理一下,就是我们编写了一个solidity的合约,需要先编译成机器码,然后再部署到ETH的网络,部署的时候需要消耗GAS。
部署完成之后,我们就可以通过用户UI去进行交互,调用metamask去与底层web3API进行交流。比如这个demo就是领养宠物,在游戏打金领域就是氪金挖矿了。当然我们可以直接跳过这些,直接用web3去跟合约交流了。
更深入的了解需要学习一下solidity的语法,然后根据游戏去挖掘相应合约的方法调用即可。
可以继续参考老板的未公开合约的游戏怎么制作脚本 思路和方法已经讲明白了,剩下的就靠自己多领悟和学习了
举一反三:多多实践
批量创建账号
既然本地已经有了eth的网络环境了,那么我是不是可以通过web3 api直接批量创建钱包地址呢?
之前一直想看老板批量创建metamask账号的的代码,可惜权限不够,这回理解了应该可以自己动手了~
查看API,有一个account的接口。通过以下代码即可创建一个账号,后续再通过循环就可以创建和存储了。
//通过web3 api 创建账号const accepts = require('accepts');var Web3 = require('web3');web3 = new Web3(new Web3.providers.HttpProvider("http://127.0.0.1:7545"));let account = web3.eth.accounts.create('test');let keystore = account.encrypt('test');console.log(account, keystore);/***{ address: '0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1', privateKey: '0x4291e1585205b5a0217ecea837f2070ea2100a401f753806c15a93dfb84c3661', signTransaction: [Function: signTransaction], sign: [Function: sign], encrypt: [Function: encrypt]} { version: 3, id: '8a2ded2a-8469-43d4-88a0-3e4169012d54', address: '3e12932ef648be841e03db1ce46faec4afc9aae1', crypto: { ciphertext: 'e7492608eb06ee703909b6348054f8b98bc30a79a5d4628fad72e012c4a50075', cipherparams: { iv: 'a2b68822d9b03e788ad8df3a57c95cec' }, cipher: 'aes-128-ctr', kdf: 'scrypt', kdfparams: { dklen: 32, salt: 'f513ee04af1662d1adab37e8962423d9d4ff65218aadce7b88063eb6e6d1ece0', n: 8192, r: 8, p: 1 }, mac: 'eadc2bb27d5fe6c8889caee2f897eabde4cd9254005af9bdb11f5c7989fe6837' }}***/
尝试转账
假设我已经创建了一个 0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1的新账号,
然后我本地导入metamask的账号已经有了99.99eth,我想给新账号转账。
直接通过metamask转账即可。
转账成功,可以用代码查询新建账号的余额,查询成功~
var Web3 = require('web3');//创建 rpc 连接字符串var rpcstring = 'http://127.0.0.1:7545'//创建ws连接字符串var wstring = 'wss://bsc-ws-node.nariox.org:443';// var wscweb3 = new Web3(new Web3.providers.WebsocketProvider(wstring ));var rpcweb3 = new Web3(new Web3.providers.HttpProvider(rpcstring ));//设置web3 使用rpcweb3模式web3 = rpcweb3;const getBNBBalance = async (address) =>{ let result = await web3.eth.getBalance(address) //由于使用的是大数模式,小数点有18位,所以获得的balance 要除以10^18次方才是正确的数据 //或者使用自带的转换工具 let balance = web3.utils.fromWei(result.toString(10), getweiname()); //打印结果 console.log("地址:" + address +"有" + balance +"个ETH"); return balance;}//通过小数点多少位,转换对应的数据function getweiname(tokendecimals = 18) { weiname = 'ether'; switch (tokendecimals) { case 3: weiname = "Kwei"; break; case 6: weiname = 'mwei'; break; case 9: weiname = 'gwei'; break; case 12: weiname = 'microether '; break; case 15: weiname = 'milliether'; break; case 18: weiname = 'ether'; break; } return weiname;}let banlance = getBNBBalance('0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1');console.log('banlance:' + banlance);//banlance:[object Promise]//地址:0x3E12932eF648bE841e03db1CE46Faec4aFc9AAe1有88个ETH
后续
可以再试试通过web3api实现批量转账等测试,等有时间再试试。作为小白,通过这个教程大致梳理了一下整个DAPP的开发流程,如果有纰漏也请各位老板指正。感谢感谢~