当前位置:首页>区块链文章>区块链推广>Brownie 开发智能合约(入门使用)

Brownie 开发智能合约(入门使用)

简介上篇文章,使用了 Remix 在线 IDE,个人感觉 Remix 在入门智能合约开发时,是很好的上手工具,因为 Remix 帮我们处理好了编译、部署的过程,并且还通过 JavaScri...

Brownie 开发智能合约(入门使用)

简介

上篇文章,使用了 Remix 在线 IDE,个人感觉 Remix 在入门智能合约开发时,是很好的上手工具,因为 Remix 帮我们处理好了编译、部署的过程,并且还通过 JavaScript VM 准备好了本地区块链方便我们测试,可谓开箱即用,但毕竟是线上 IDE,功能还是有限。

这里我们使用 Brownie 框架来开发智能合约,Brownie 框架是基于 Python 编写的智能合约开发框架,它可以帮我们快速完成编译、部署、测试等智能合约开发的全流程。

文档:https://eth-brownie.readthedocs.io/en/stable/

Web3.py 基础

因为 Brownie 主要基于 Web3.py 这个库开发而来,在从 Python 角度了解以太坊

编写简单的智能合约

首先,通过 Solidity 编写一个简单智能合约,没错,我们并不能通过 Python 来编写智能合约,利用 Python,只是为了让这个过程更加自动化与工程化,智能合约代码如下:

// SPDX-License-Identifier: MITpragma solidity ^0.6.0;contract Storage {    struct People {        string name;        uint256 age;    }    People[] public people;    function addPerson(string memory _name, uint256 _age) public {        people.push(People(_name, _age));    }}

上述代码中,通过 struct 关键字定义了一个名为 People 的对象,该对象中有 name 与 age 两个属性,然后基于 People 对象,实例化了 people 数组,然后定义了 addPerson 函数,该方法会接收_name 与_age 参数,然后实例化 People 对象,最后将 People 对象添加到数组中。

这里有个细节,就是参数_name 是字符串,所以需要使用 memory 关键字标注一下。Solidity 中,存储变量的方式有 storage 与 memory 两种。

  • storage 变量:永久存储在区块链中的变量

  • memory 变量:临时的,当外部函数对某合约调用完成时,内存型变量即被移除

Solidity 中的 string 的本质是字符数组(Char Array),如果你不通过 memory 声明,就算_name 是函数参数,Solidity 也会通过storage持续存储它。

编译智能合约与连接本地区块链网络

创建名为【web3py_storage】的文件夹,然后在其中创建 Storage.sol 文件并将智能合约代码复制到文件中。

通过 vscode 打开 webpy_simple_storage 文件夹,创建 base.py,在 base.py 实现对智能合约的编译以及连接上区块链网络的操作。

阅读 web3.py 智能合约相关的文档:https://web3py.readthedocs.io/en/stable/contracts.html

通过文档可知,web3.py 不支持 solidity 的编译,文档中建议我们安装 py-solc-x 库来实现 solidity 的编译,简单安装一下,然后通过 install_solc 方法来下载对应版本的 solidity 编译器。

因为我们的智能合约使用了 Solidity ^0.6.0,所以下载 0.6.0 版本的 solidity 编译器则可,然后按文档的方式设置编译 Solidity 时的配置则可,相关代码如下:

import osimport jsonfrom web3 import Web3# 编译 solidity# https://github.com/iamdefinitelyahuman/py-solc-xfrom solcx import compile_standard, install_solcwith open(\'./Storage.sol\', \'r\', encoding=\'utf-8\') as f:    storage_file = f.read()# 下载0.6.0版本的Solidity编译器install_solc(\'0.6.0\')# 编译Soliditycompiled_sol = compile_standard(    {        \"language\": \"Solidity\",        # Solidity文件        \"sources\": {\"Storage.sol\": {\"content\": storage_file}},        \"settings\": {            \"outputSelection\": {                \"*\": {                    # 编译后产生的内容                    \"*\": [\"abi\", \"metadata\", \"evm.bytecode\", \"evm.bytecode.sourceMap\"]                }            }        },    },    # 版本,与编写智能合约时Solidity使用的版本对应    solc_version=\"0.6.0\",)# 编译后的结果写入文件with open(\'compiled_code.json\', \'w\') as f:    json.dump(compiled_sol, f)

compile_standard 方法编译后的结果写入 compiled_code.json,将其格式化,如下图:

Brownie 开发智能合约(入门使用)

从上图可知,Solidity 编译后的字节码也在 compiled_code.json 中,将 json 文件中重要的数据读取出来,代码如下:

# 智能合约编译后的字节码(上链的数据)bytecode = compiled_sol[\"contracts\"][\"Storage.sol\"][\"Storage\"][\"evm\"][    \"bytecode\"][\"object\"]# ABI (Application Binary Interface),用于与智能合约中的方法进行交互的接口abi = json.loads(    compiled_sol[\"contracts\"][\"Storage.sol\"][\"Storage\"][\"metadata\"])[\"output\"][\"abi\"]
  • bytecode:智能合约编译后的字节码,智能合约上链其实就是将这部分数据存储到区块链中。

  • abi:我们的程序与智能合约交互的接口,它定义了我们的程序可以怎么与当前这个智能合约交互。

至此,智能合约的编译流程就结束了,然后我们通过 web3.py 连接到以太坊中。

与 Remix IDE 不同,web3.py 没有通过 JavaScript VM 实现的本地区块链网络,虽然有 web3 [tester],但不够完善,这里我们通过 Genache 来实现本地网络。

Genache:https://www.trufflesuite.com/ganache

下载好后,直接运行,然后点击【QUICKSTART】,选择【ETHEREUM】。

Brownie 开发智能合约(入门使用)

Ganache 会在本地快速创建区块链网络:

Brownie 开发智能合约(入门使用)

从上图中,可以看出,Ganache 会为我们创建 10 个账号,创建出的网络可以通过 http://127.0.0.1:7545 连接。

要实现连接,还需要一个信息,那就是 Ganache 创建的区块链网络,其 chain id 是多少?图中只展示了 NETWORK ID(5777),查阅文档,可知 chain id 为 1337(https://ethereum.stackexchange.com/questions/91072/setup-ganache-with-metamask-what-and-where-is-a-chain-id)。

通常,我们不会将这些常量硬编码到代码中,而是通过配置文件或环境变量的形式引入,这里使用环境变量的形式。Python 中使用环境变量比较好的方式是使用 python-dotenv 这个库,pip 安装一下,然后再项目根目录中创建名为.env 的文件,写入如下内容:

RINKEBY_RPC_URL=http://127.0.0.1:7545ACCOUNT_ADDRESS=0x4A151d2855eEFba23Eb9B7943253D29E061cFeFDPRIVATE_KEY=0xc6ba82d2e7bc2ab41f578a57b8822767b9875e339d2f93d3fe8eef25f5cb39aa

然后代码里使用一下:

from dotenv import load_dotenvload_dotenv()w3 = Web3(Web3.HTTPProvider(os.getenv(\"RINKEBY_RPC_URL\")))chain_id = 1337my_address = os.getenv(\"ACCOUNT_ADDRESS\")private_key = os.getenv(\"PRIVATE_KEY\")

Web3.py 部署智能合约

部署的流程比较简单,直接给出代码:

from base import *# 构建智能合约对象storage = w3.eth.contract(abi=abi, bytecode=bytecode)# 当前区块链中最后一个交易的noncenonce = w3.eth.get_transaction_count(my_address)# 部署智能合约 - 创建交易transaction = storage.constructor().buildTransaction(    {\"chainId\": chain_id, \"from\": my_address, \"nonce\": nonce})# 签名当前交易 - 证明是你发起的交易signed_txn = w3.eth.account.sign_transaction(transaction, private_key=private_key)print(\"Deploying Contract!\")# 开始部署 - 发送交易tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)print(\'Waiting for deploy transaction to finish...\')# 等待智能合约部署结果,部署完后,会获得合约的地址tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)print(\'Deployed Done!\')print(f\'contract address: {tx_receipt.contractAddress}\')

上述代码中,一开始通过 w3.eth.contract 方法实例化合约对象,需要传入 abi 与 bytecode(base.py 提供了)。

然后对合约进行部署,部署的过程其实也是在创建交易,这就涉及到:

  • 创建交易对象

  • 签名交易

  • 发送交易

  • 等待交易完成

上述代码刚好就是这几个步骤,需要注意的点是 nonce,每个交易都需要 nonce,这个 nonce 是顺序的,所有我们需要获取最后一个交易的 nonce,运行代码,结果如下图:

Brownie 开发智能合约(入门使用)

部署后,智能合约的地址:0x8395Fd53331cea813e3838F6bB42B9668BEBf0C2

Web3.py 调用部署的智能合约

部署完后,我们获得了合约部署后的地址,使用该地址,可以构建出合约对象,然后我们就可以调用合约里的方法了。回顾一开始我们编写的合约,其实只有 addPerson 这一个方法,该方法会将传入方法的数据存到区块链网络中,这改变了区块链的状态,所以算是一次交易操作,凡是交易操作就需要签名,从而证明这个操作是你做的。

完整代码如下:

from base import *# 调用deploy.py会获得contract_addresscontract_address = \'0x5071ad6611B322647B88ACF5CBeBCA71Bead0c6f\'nonce = w3.eth.get_transaction_count(my_address)# 实例化合约对象storage = w3.eth.contract(address=contract_address, abi=abi)# 调用addPerson方法transaction = storage.functions.addPerson(\'二两\', 28).buildTransaction({    \"chainId\": chain_id,    \"from\": my_address,    \"nonce\": nonce})# 签名signed_transaction = w3.eth.account.sign_transaction(transaction, private_key=private_key)# 发送交易tx_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)print(\'add new Person to contract...\')# 等待交易完成tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)# 获得people数组中存储的值result = storage.functions.people(0).call()print(f\'get person info: {result}\')

因为 addPerson 方法会改变区块链,即需要消耗 Gas 的交易行为,这类行为都需要使用私钥进行签名,然后才能发送交易,调用完 addPerson 函数后,再从 people 数组获取下标为 0 的数据。

Brownie 开发智能合约(入门使用)

这里提一下 ABI,让大家有更直观的理解,在上述代码中,为啥可以调用 addPerson 函数和 people 数组?因为编译后获得的智能合约的 ABI 中存在 addPerson 与 people,复制 compiled_code.json 中 abi 的内容:

\"abi\": [                    {                        \"inputs\": [                            {                                \"internalType\": \"string\",                                \"name\": \"_name\",                                \"type\": \"string\"                            },                            {                                \"internalType\": \"uint256\",                                \"name\": \"_age\",                                \"type\": \"uint256\"                            }                        ],                        \"name\": \"addPerson\",                        \"outputs\": [],                        \"stateMutability\": \"nonpayable\",                        \"type\": \"function\"                    },                    {                        \"inputs\": [                            {                                \"internalType\": \"uint256\",                                \"name\": \"\",                                \"type\": \"uint256\"                            }                        ],                        \"name\": \"people\",                        \"outputs\": [                            {                                \"internalType\": \"string\",                                \"name\": \"name\",                                \"type\": \"string\"                            },                            {                                \"internalType\": \"uint256\",                                \"name\": \"age\",                                \"type\": \"uint256\"                            }                        ],                        \"stateMutability\": \"view\",                        \"type\": \"function\"                    }                ],

以 addPerson 函数为例,其 type 为 function,name 为 addPerson,inputs 表示调用该方法需传入的参数,也给出了 type,通过 abi,程序才知道当前的智能合约提供什么功能。

部署到 Rinkeby 测试网络

通过上面的操作,我们已经可以将智能合约部署到测试网络中了,那如何部署到测试网络中?Web3.py 不像 Remix IDE 提供 Inject Web3 的功能,要部署到测试网络,我们需要借助第三方服务,与之相关的服务有:alchemy、infura 等。

简单而言,在这些服务对应的网站上,注册账号,创建应用,然后拿到开发用的 key,然后使用这个 key 与这些服务交互,我们会连接到这些服务上,然后服务会为我们将应用发布到测试网络或以太坊主网上,与我们平时使用百度、高德等 API 平台没啥差别,都是创建应用获得 key。

这里我们使用 infura 服务,infura 服务大体的工作方式如下图,简单来说,我们不需要将自己本地的计算机加入到以太坊网络中,成为其中的节点(挺麻烦的,要拉数据、足够的网速和足够的硬盘空间),而是直接通过 infura 服务连接(本质是使用 infura 的节点)。

Brownie 开发智能合约(入门使用)

从图中可知,我们通过 infura 提供的 ITX API 便可以与以太坊网络交互了,然后你创建应用,在应用的设置页,可以看到相应的信息,需要注意的是,【ENDPOINTS】处需要选择 rinkeby 测试网络,如下图:

Brownie 开发智能合约(入门使用)

有了这些设置后,我们修改一下.env 文件中的内容:

RINKEBY_RPC_URL=https://rinkeby.infura.io/v3/ACCOUNT_ADDRESS=PRIVATE_KEY=CHAIN_ID=4

RINKEBY_RPC_URL 给我 Infura 给的 http 地址,ACCOUNT_ADDRES 与 PRIVATE_KEY 可以在 MetaMask 钱包中获取(获取 Rinkeby 上的),为了方便,我将 CHAIN_ID 也放到.env 中了,不同的链具有通过的 CHAIN_ID,可以通过 https://chainlist.org/ 查询:

Brownie 开发智能合约(入门使用)

代码中连接网络的方式不需要改变,只是我们将 CHAIN_ID 抽到.env 中了,getenv 函数会返回字符串格式,需要强转一下。

w3 = Web3(Web3.HTTPProvider(os.getenv(\"RINKEBY_RPC_URL\")))chain_id = int(os.getenv(\"CHAIN_ID\"))

然后我们部署,然后调用合约中的方法,使用 play_storage.py 时,因为合约地址变了,所以你需要同步修改一下 contract_address 变量,调用后,可以通过 etherscan 查看:

Brownie 开发智能合约(入门使用)

项目代码:GitHub – ayuLiao/web3py_storage: use web3.py play ethereum contract

如果你在使用 Infura 时,发现总是 403,可以尝试删除掉原本的 project,创建一个新的 project。

Brownie 基础

上面通过 Web3.py 实现了智能合约的部署与交互,可以发现还是比较麻烦的,每次触发交易时,都需要进行签名操作等,Brownie 框架基于 Web3.py,它将很多步骤都帮我们静默完成了,如果你不了解 Web3.py,直接上 Brownie 框架,个人感觉也不好,因为会显得比较黑盒。

安装 Brownie

我们通过 pip 安装一下 Brownie,阅读文档,会发现 Brownie 建议使用 pipx 来安装,pipx 会在全局创建一个虚拟环境,然后将 Brownie 安装在虚拟环境中,研究后发现,这是因为 Brownie 依赖比较多,安装过程比较慢,如果你通过 venv 方式,每个项目都要来一次,挺费时间的,因我 Windows 的环境问题,我懒得折腾,我自己管理员开 Terminal 直接 pip 安装:

pip install eth-brownie

安装完后,根据文档,我们还需要安装一下 ganache-cli(github.com/trufflesuite/ganache),命令行版的 ganache,npm 全局安装一下则可。

npm install ganache-cli@latest --global

在 Terminal 中输入 brownieganache-cli 都可以正常使用则表示安装成功。

快速使用 Brownie

创建名为【brownie_storage】的文件夹,进入该文件夹,然后通过 brownie init 初始化项目,会获得如下结构,每个文件夹的作用也标准了:

C:\\USERS\\AYU\\WORKPLACE\\BLOCKCHAIN\\BROWNIE_STORAGE├───build                # 编译、部署等结果存放目录│   ├───contracts│   ├───deployments│   └───interfaces├───contracts            # 智能合约的目录├───interfaces           # 接口的目录├───reports              # JSON报告文件的目录(使用GUI的用户才会使用)├───scripts              # 脚本的目录└───tests                # 测试脚本目录

在使用 Brownie 编写代码前,先使用 ganache-cli 启动本地的以太坊网络,方便测试:

Brownie 开发智能合约(入门使用)

然后,我们将 Storage.sol 复制到 contracts 目录中,通过 brownie compile 命令编译智能合约,该命令会将 contracts 目录下所有的智能合约都进行编译,编译完成后,在 build/contracts 会出现同名的 json 文件,与 Web3.py 类似,这里记录着智能合约的 bytecode、abi 等信息。

完成编译后,接着进行部署,在 scripts 目录下创建 deploy.py,其代码如下:

from brownie import accounts, config, network, Storagedef deploy_storage():    account = get_account()    # Instantiate Storage contract    storage = Storage.deploy({\"from\": account})    # call addPerson function    transaction = storage.addPerson(\'二两\', 28, {\"from\": account})    # wait transaction finish    transaction.wait(1)    # call people function to get data from people array    result = storage.people(0)    print(\'result: \', result)def get_account():    if network.show_active() == \'development\':        return accounts[0]    else:        # add new account to brownie accounts        # account config data from brownie-config.yaml        return accounts.add(config[\'wallets\'][\'account_key\'])def main():    deploy_storage()

在 Windows 中,brownie 不支持 python 中有中文注释,估计是没有兼容好。

相比于 Web3.py,brownie 简单了很多,你只需导入 Storage,然后调用其 deploy 方法则可,因为 Storage 其实是动态载入的,brownie 本身并没有这个类,所以我们不可以直接通过 python 去运行 deploy.py 文件,而是需要使用 brownie run .\\scripts\\deploy.py 命令去运行:

Brownie 开发智能合约(入门使用)

上述代码中,定义了 get_account 函数,该函数会判断当前处于哪个区块链,从而使用想要的方式获得 account,brownie 默认处于 development(本地开发网络),如果不处于 development,则通过 brownie 提供的 accounts.add 函数添加账户对象,比如后面我们会部署到 Rinkeby,就需要从钱包里拿私钥(账号公钥信息可以通过私钥推导获得),这里为了方便,直接放在配置文件中。

brownie 提供的 config 模型,会自动从项目根目录的 brownie-config.yaml 中获取,在这里,该文件内容如下:

dotenv: .envwallets:  from_key: ${PRIVATE_KEY}

因为私钥比较重要,也规范一些,这里通过 ${PRIVATE_KEY} 导入项目根目录下.env 文件中的内容。

此外,我们还可以使用 brownie console,进入 brownie 提供的交互式命令环境,在该环境里,你可以使用 brownie 中的任何功能。

> brownie consoleBrownie v1.17.0 - Python development framework for EthereumBrownieStorageProject is the active project.c:\\program files\\python37\\lib\\site-packages\\brownie\\network\\main.py:46: BrownieEnvironmentWarning: Development network has a block height of 6  BrownieEnvironmentWarning,Attached to local RPC client listening at \'127.0.0.1:8545\'...Brownie environment is ready.>>> from brownie import network>>> network.show_active()\'development\'>>> from brownie import accounts>>> account = accounts[0]>>> from brownie import Storage>>> storage = Storage.deploy({\"from\": account})Transaction sent: 0xd7269730fb3ee3a642391c338234f9cb63993b7bd991316971c89ca6406cebe7  Gas price: 0.0 gwei   Gas limit: 6721975   Nonce: 6  Storage.constructor confirmed   Block: 7   Gas used: 243848 (3.63%)  Storage deployed at: 0x500F5EDceE38597164c26606E93e92D059853a46>>> transaction = storage.addPerson(\'二两\', 28, {\"from\": account})Transaction sent: 0x2aa19410ddc316413f54a6e1c25f6a5878b7a0877fa65a5bec80f380ba3c64aa  Gas price: 0.0 gwei   Gas limit: 6721975   Nonce: 7  Storage.addPerson confirmed   Block: 8   Gas used: 84259 (1.25%)>>> transaction.wait(1)  Storage.addPerson confirmed   Block: 8   Gas used: 84259 (1.25%)

进行单元测试

智能合约通常与钱相关,做好测试是非常有必要的。brownie 使用 pytest 来实现单元测试,至于 pytest,用过的都说好),在 tests 目录创建名为 test_storage.py 的文件,代码如下:

from brownie import Storage, accountsdef test_deploy():    account = accounts[0]    storage = Storage.deploy({\"from\": account})    transaction = storage.addPerson(\'二两\', 28, {\"from\": account})    transaction.wait(1)    # call people function to get data from people array    result = storage.people(0)    assert result == (\'二两\', 28)

很常规的单元测试代码,可以将智能合约部署的过程与 CI/CD 流程结合,每次部署都过一遍所有的单元测试,从而让合约更加健硕。

使用 Brownie 将合约部署到测试网络

阅读文档发现,在 Brownie 中通过 Infura 服务进行合约的部署,只需要配置一下则可,文档内容:https://eth-brownie.readthedocs.io/en/latest/network-management.html#using-infura

除了可以通过 export 的方式添加 WEB3_INFURA_PROJECT_ID 环境变量,我们还可以将 WEB3_INFURA_PROJECT_ID 直接添加到.env 中(文档里没写)。

WEB3_INFURA_PROJECT_ID 就是 Infura 为你提供的 Project ID,此外,因为要连接测试网络,所以部署时需要连接测试网络中的账号,你需要将你账号的私钥也放到.env 中。

PRIVATE_KEY= WEB3_INFURA_PROJECT_ID=

然后通过 brownie run .\\scripts\\deploy.py --network rinkeby 运行则可完成部署。

Brownie 开发智能合约(入门使用)

Brownie 开发智能合约(入门使用)

brownie 提供了多种网络,所以我们部署时不需要做额外操作,直接指定对应的网络则可。

Brownie 开发智能合约(入门使用)

当然,后续开发时,我们还可以 brownie networks add 命令添加新的网络。

项目代码:https://github.com/ayuLiao/brownie_storage

结尾

这篇文章只是简单的介绍了 Brownie 的一些操作,Brownie 还具有很多高级功能,比如 Mock、Fork 一个区块链到本地进行开发、又比如 Brownie 提供了 Debug Tools 供你进行调试开发,后续的文章会分享这些内容。

最后提一嘴,Brownie 的文档是很好的学习资料。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
区块链推广

为什么Compound选择使用Substrate进行独立链的开发

2021-11-7 12:44:24

区块链推广

元宇宙metaverse

2021-11-8 10:51:06

重要说明

本站资源大多来自网络,如有侵犯你的权益请联系管理员 区块链Bi站  或给邮箱发送邮件834379394@qq.com 我们会第一时间进行审核删除。 站内资源为网友个人学习或测试研究使用,未经原版权作者许可,禁止用于任何商业途径!请在下载24小时内删除!


如果你遇到支付完成,找不到下载链接,或者不能下载,或者解压失败,先不要忙,加客服主的QQ:834379394 (客服有可能有事情或者在睡觉不能及时的回复您,QQ留言后,请耐心等待即可!)

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索