【区块链学习笔记-01】DAPP智能合约开发应用

初学以太坊智能合约

直接打开 http://trufflesuite.com/tutorial/index.html 有新手指导区,英语好的老板可以直接参考原文,下面是根据教程加上我的体验进行的翻译和实践记录。

这个教程可以让你体验做一个dapp宠物商店(pet shop),通过这个教程可以完整的体验一次合约的开发。

这个教程主要会让我们体验这么几个过程

  • 设置开发环境
  • 使用 Truffle Box 创建 Truffle 项目
  • 编写智能合约
  • 编译和迁移智能合约
  • 测试智能合约
  • 创建用户界面以与智能合约交互
  • 在浏览器中与 dapp 交互

设置开发环境

需要安装

  • Nodejs
  • Git

上述两个安装完成之后,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;
}
  • 我们定义了一个单一的变量:adopters。这是一个以太坊地址的数组。数组包含一种类型,可以有一个固定或可变的长度。在这种情况下,类型是地址,长度是16。
  • 你还会注意到adopters是公开的。公共变量有自动的getter方法,但在数组的情况下,需要一个key,并且只会返回一个值。稍后,我们将写一个函数来返回整个数组,以便在我们的用户界面中使用。

开发第一个方法:收养一只宠物

// Adopting a pet
function adopt(uint petId) public returns (uint) {
  require(petId >= 0 && petId <= 15);

  adopters[petId] = msg.sender;

  return petId;
}
  • 在Solidity中,函数参数和输出的类型都必须被指定。在这种情况下,我们将接收一个petId(整数)并返回一个整数。
  • 我们正在检查以确保petId是在我们的收养者数组的范围内。Solidity中的数组是从0开始索引的,所以ID值需要在0和15之间。我们使用require()语句来确保ID是在范围内。
  • 如果ID在范围内,我们就把进行呼叫的地址添加到我们的收养者数组中。调用此函数的人或智能合约的地址用msg.sender表示。
  • 最后,我们返回提供的宠物ID作为确认。

开发第二个方法:返回收养数据

// Retrieving the adopters
function getAdopters() public view returns (address[16] memory) {
  return adopters;
}
  • 由于adopters已经被声明,我们可以简单地返回它。确保指定返回类型(在本例中,adopters的类型)为address[16] memory。 memory给出了变量的数据位置。
  • 函数声明中的view关键字意味着该函数将不会修改合同的状态。关于视图所施加的确切限制的进一步信息,可在这里找到。

编译和迁移智能合约

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 ETH


2_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 ETH


Summary
=======
> 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.js
App = {
  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账号的的代码,可惜权限不够,这回理解了应该可以自己动手了~

https://web3js.readthedocs.io/en/v1.2.11/web3-eth-accounts.html#example

查看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的开发流程,如果有纰漏也请各位老板指正。感谢感谢~

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇