vlambda博客
学习文章列表

用Vyper实现智能合约 - 一种 Python 方法

本文分享如何用 Vyper 而不是 Solidity 编写智能合约

                  - 图片来源 — Jan Kopřiva[2] -

很多学习智能合约的程序员都会学 Solidity 语言,网上有大量关于 Solidity 的在线教程和书籍。而 Solidity 与 Truffle 框架的结合,简直就是开发智能合约的杀手级组合。几乎以太坊区块链上所有的的智能合约都是用 Solidity 编写的。

然而本文将探讨如何用 Vyper 编程语言编写智能合约。

介绍

什么是 Vyper

Vyper 是一种面向合约的 Python 编程语言,对标[以太坊虚拟机 (EVM)](https://learnblockchain.cn/article/1229 "以太坊虚拟机 (EVM "以太坊虚拟机 (EVM)")")。Vyper 的语法非常简单易懂;Vyper 的原则之一是让开发人员几乎不可能编写引起错误的程序。

为什么选择 Vyper?

  • 安全: 用 Vyper 构建安全的智能合约是可能且自然的
  • 语言和编译器简单: 语言和编译器的实现力求简单
  • 可审计: Vyper 代码最大限度地具有人类可读性。此外,编写错误代码非常困难。对读者的简单性比对作者的简单性更重要,而对于 Vyper 经验不足(以及一般编程经验不足)的读者来说尤其重要。

因此,Vyper 提供了以下功能:

  • 边界和溢出检查: 在数组访问和算术运算时
  • 支持有符号整数和十进制定点数
  • 可判定: 可以精确计算任何 Vyper 函数调用的 gas 消耗上限。
  • 强类型
  • 小而易懂的编译器代码
  • 对纯函数的有限支持: 所有常量不允许改变状态。

设置环境

Vyper 需要 Python 3.6 版本。如果你没有安装可以点击这里[3]安装,然后按照以下步骤操作:

$ python3 -m pip install --user virtualenv
$ pip install vyper
$ virtualenv -p python3.6 vyper-venv
source vyper-venv/bin/activate
(vyper-venv) $ pip install vyper
(vyper-venv) $ vyper --version

用 Vyper 创建智能合约

现在我们用 Vyper 创建一个智能合约。首先,我们需要创建一个后缀为 .vy 的文件并将其命名为hello.vy,如下所示:

name: public(String[24])

@external
def __init__():
    self.name = "Satoshi Nakamoto"

@external
def change_name(new_name: String[24]):
    self.name = new_name

@external
def say_hello() -> String[32]:
    return concat("Hello, ", self.name)

编译:

(vyper-venv) $ vyper hello.vy

输出结果:

用Vyper实现智能合约 - 一种 Python 方法

                                 - 字节码 -

这是智能合约的字节码。请记住,部署智能合约需要字节码,访问智能合约需要 abi。怎么得到 abi?运行以下命令:

(vyper-venv) $ vyper -f json hello.vy

输出结果:

用Vyper实现智能合约 - 一种 Python 方法

                                   - ABI -

如果你想一次编译同时获得 abi 和字节码,可以这样:

(vyper-venv) $ vyper -f json,bytecode hello.vy

输出结果:

用Vyper实现智能合约 - 一种 Python 方法

                             - ABI 和字节码-

将智能合约部署到 Ganache

怎么将这个智能合约部署到以太坊区块链上呢?有几种方法,但我们用熟悉的 Truffle :

  1. 创建一个目录 hello_project并用 truffle init初始化,如下:
$ mkdir hello_project
cd hello_project
$ truffle init
  1. 如下设置 truffle-config.js :
module.exports = {
  networks: {
    "development": {
       network_id: "*",
       host: "127.0.0.1",
       port: 8545 // port at Ganache running
      },
    }
 };
  1. 创建一个目录 build/contracts:
$ mkdir -p build/contracts
cd build/contracts
  1. 在目录下创建一个 Hello.json 文件:
{
  “abi”:
  ”bytecode”:
}

                             - Hello.json -

  1. 然后将前面编译时输出的 abi 填入 abi 字段,将字节码填入字节码字段。字节码需要用双引号。不要忘记在 abi 和字节码之间加一个逗号。如下:
1  {
 2    "abi": [
 3      {
 4        "stateMutability""nonpayable",
 5        "type""constructor",
 6        "inputs": [],
 7        "outputs": []
 8      },
 9      {
10        "stateMutability""nonpayable",
11        "type""function",
12        "name""change_name",
13        "inputs": [
14          {
15            "name""new_name",
16            "type""string"
17          }
18        ],
19        "outputs": [],
20        "gas": 74063
21      },
22      {
23        "stateMutability""nonpayable",
24        "type""function",
25        "name""say_hello",
26        "inputs": [],
27        "outputs": [
28          {
29            "name""",
30            "type""string"
31          }
32        ],
33        "gas": 14923
34      },
35      {
36        "stateMutability""view",
37        "type""function",
38        "name""name",
39        "inputs": [],
40        "outputs": [
41          {
42            "name""",
43            "type""string"
44          }
45        ],
46        "gas": 10538
47      }
48    ],
49    "bytecode""0x601060e0527f5361746f736869204e616b616d6f746f000000000000000000000000000000006101005260e0806000602082510160c060006002818352015b8260c05160200211156100505761006f565b60c05160200285015160c051850155815160010180835281141561003e575b5050505050506102d756600436101561000d57610252565b60046000601c37600051346102585763e349cb12811861009257600435600401601881351161025857808035602001808260e03750505060e0806000602082510160c060006002818352015b8260c051602002111561006b5761008a565b60c05160200285015160c0518501558151600101808352811415610059575b505050505050005b63b459850381186101ad576101608060208082526000600760e0527f48656c6c6f2c20000000000000000000000000000000000000000000000000006101005260e06007806020846101200101826020850160045afa5050805182019150506000600181016020836101200101825460c060006001818352015b8260c051602002111561011e5761013d565b60c05185015460c051602002850152815160010180835281141561010c575b50505050508054820191505080610120526101209050818401808280516020018083828460045afa905050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f82010390509050905090508101905090509050610160f35b6306fdde0381186102505760e08060208082528083018060008082602082540160c060006002818352015b8260c05160200211156101ea57610209565b60c05185015460c05160200285015281516001018083528114156101d8575b5050505050508051806020830101818260206001820306601f8201039050033682375050805160200160206001820306601f820103905090509050810190509050905060e0f35b505b60006000fd5b600080fd5b61007a6102d70361007a60003961007a6102d7036000f3"
50  }

                              - Hello.json -

  1. 然后创建一个新文件 migrations/2_deploy_hello.js来部署智能合约,如下:
var Hello = artifacts.require("Hello");
module.exports = function(deployer){
   deployer.deploy(Hello);
};

一切就绪,启动 Ganache!

  1. hello_project 目录中,可以只运行迁移过程,如下:
$ truffle migrate

你会看到下面的输出:

用Vyper实现智能合约 - 一种 Python 方法

                              - truffle 输出 -

0x4AB3935Df0E224771663984f3617F1a78beA4E8D

与智能合约交互

如前所示,可以使用 Truffle 控制台与智能合约进行交互,如下:

$ truffle console

用下面的语句访问智能合约:

truffle(development)> Contract.at("0x4AB3935Df0E224771663984f3617F1a78beA4E8D")

会有一个很长的输出,包含 abi,字节码等,如下:

用Vyper实现智能合约 - 一种 Python 方法

                             - truffle 输出 -

查看合约name变量的值:

truffle(development)> Contract.at(“0x4AB3935Df0E224771663984f3617F1a78beA4E8D”).then(function(instance){return instance.name.call(); });

'Satoshi Nakamoto'

修改name:

truffle(development)> Contract.at(“0x4AB3935Df0E224771663984f3617F1a78beA4E8D”).then(function(instance) { return instance.change_name(“Vitalik Buterin”), { from: “0xb28Fc17791bf66719FBFCc65846B01Fe2726e9E2” } });

from字段中的值取自 Ganache 中的一个帐户。您只需查看 Ganache 窗口选择您喜欢的任何帐户。

输出:

{ from: ‘0xb28Fc17791bf66719FBFCc65846B01Fe2726e9E2’ }

修改成功了吗?运行下面的命令来确认一下:

truffle(development)> Contract.at(“0x4AB3935Df0E224771663984f3617F1a78beA4E8D”).then(function(instance){return instance.name.call(); });

‘Vitalik Buterin’

与其他智能合约交互

你的智能合约也可以与区块链上的其他合约进行交互。

重新启动 Ganache 。还记得你的hello.vy文件吗?我们想用一个自定义名称来部署 Hello 合约。

迁移文件migrations/2_deploy_hello.js不变,如下:

var Hello = artifacts.require("Hello");
module.exports = function(deployer){
   deployer.deploy(Hello);
};

再次编译hello.vy文件以获取接口和字节码。打开合约 JSON 文件build/contracts/Hello.json。删除全部内容并替换为下面的代码:

{
     "contractName""Hello",
     "abi": <your Hello smart contract's interface>,
     "bytecode": "<your Hello smart contract'
s bytecode>"
}

这次必须为智能合约命名,因为需要部署两个智能合约。如果没有命名,会给它一个默认名称——Contract。如果只部署一个智能合约,这样没问题。

然后,编辑donation.vy,并添加以下代码:

1  struct DonaturDetail:
 2      sum: uint256
 3      name: String[100]
 4      time: uint256
 5
 6  interface Hello():
 7      def say_hello() -> Bytes[32]:view
 8
 9  donatur_details: public(HashMap[(address, DonaturDetail)])
10
11  donaturs: public(address[10])
12
13  donatee: public(address)
14
15  index: int128
16
17  @external
18  def __init__():
19      self.donatee = msg.sender
20
21  @payable
22  @external
23  def donate(name: String[100]):
24      assert msg.value >= as_wei_value(1, "ether")
25      assert self.index < 10
26
27      self.donatur_details[msg.sender] = DonaturDetail({
28                                           sum: msg.value,
29                                           name: name,
30                                           time: block.timestamp
31                                         })
32
33      self.donaturs[self.index] = msg.sender
34      self.index += 1
35
36  @external
37  def withdraw_donation():
38      assert msg.sender == self.donatee
39
40      send(self.donatee, self.balance)
41
42  @external
43  def donation_smart_contract_call_hello_smart_contract_method(smart_contract_address: address) -> Bytes[32]:
44      return Hello(smart_contract_address).say_hello()

                          - donation.vy -

用下面的代码,为捐赠合约创建另一个迁移文件migrations/3_deploy_donation.js

var Donation = artifacts.require("Donation");
module.exports = function(deployer) {
  deployer.deploy(Donation);
};

编译donation.vy获取智能合约的接口和字节码。然后,用以下代码,为捐赠合约创建另一个合约 JSON 文件build/contracts/Donation.json

{
     "contractName""Donation",
     "abi": <your Donation smart contract's interface>,
     "bytecode": "<your Donation smart contract'
s bytecode>"
}

运行迁移。需要用到 reset,如下:

$ truffle migrate --reset

输出:

                             - truffle 输出 -

运行 Truffle 控制台:

$ truffle console

现在,我们的智能合约不再孤单了,如下:

truffle(development)> Donation.at(“0x25aFF89B8a72aB4Ba6F4C831f5B1375f9BCe76A9”).then(function(instance) { return instance.donation_smart_contract_call_hello_smart_contract_method.call(“0x772138489eD34095FBA6a0Af70d3C9115813CFcA”); } );

输出:

‘0x48656c6c6f2c205361746f736869204e616b616d6f746f’

以编程方式编译代码

你可以创建一个脚本来编译 Vyper 代码,而不是使用命令行。确保同一目录中包含Hello.vydonation.vy。创建一个名为compiler.vy 的脚本,如下:

import vyper
import os, json

# You need a Vyper file, the name that you want to give to your smart contract, and the output JSON file. The following code will do this task:
filename = 'hello.vy'
contract_name = 'Hello'
contract_json_file = open('Hello.json''w')

# Use the following lines of code to get the content of the Vyper file:
with open(filename, 'r') as f:
    content = f.read()

# Then you create a dictionary object where the key is a path to your Vyper file and the value is the content of the Vyper file, as follows:
current_directory = os.curdir
smart_contract = {}
smart_contract[current_directory] = content

# To compile the Vyper code, all you need to do is use the compile_codes method from the vyper module, as follows:
format = ['abi''bytecode']
compiled_code = vyper.compile_codes(smart_contract, format, 'dict')

smart_contract_json = {
    'contractName': contract_name,
    'abi': compiled_code[current_directory]['abi'],
    'bytecode': compiled_code[current_directory]['bytecode']
}

# The last code is used to write the result to an output JSON file:
json.dump(smart_contract_json, contract_json_file)
contract_json_file.close()

                         - compiler.vy -

如果用以下命令执行这个脚本,您将获得一个Hello.json文件,它可以与 Truffle 一起用,如下:

(vyper-venv) $ python compiler.vy

总结

参考

[1]: Vyper 文档[4]

原文链接:https://betterprogramming.pub/implementing-smart-contracts-using-vyper-a-python-approach-95f9299e64d8

参考资料

[1]

一个程序猿: https://learnblockchain.cn/people/9

[2]

Jan Kopřiva: https://www.pexels.com/@koprivakart

[3]

这里: https://www.python.org/downloads/release/python-366/

[4]

1]: [Vyper 文档: https://vyper.readthedocs.io/en/stable/index.html