IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 区块链 -> 使用Brownie开发Fund Me智能合约 -> 正文阅读

[区块链]使用Brownie开发Fund Me智能合约

9b1cc4f6eb34c93c458add33a0fb104f.png

在「Solidity入门-开发众筹智能合约」一文中,实现了名为Fund Me的众筹智能合约,但开发过程还是比较粗糙的,本文使用Brownie框架将其完善一下,主要涉及内容如下:

  • 1.Brownie基本使用

  • 2.实现单元测试

  • 3.Brownie添加区块链网络

  • 4.使用Mock功能

  • 5.使用Fork功能

因为智能合约发布后,便不可以更改,所以在发布前,通常需要进行大量的测试,测试代码一般比业务代码多,所以如何优雅的实现单元测试就很重要。

此外,相比于传统的开发,智能合约在开发时也会更加麻烦一些,比如我们需要使用外部的智能合约,而外部的智能合约却在主网上,此时就需要使用Mock或Fork的方式来解决,从而实现本地的调试。

项目代码:https://github.com/ayuLiao/brownie-fund-me

玩得开心😁

Brownie基本使用

创建名为【brownie-fund-me】的文件夹,然后brownie init初始化brownie项目,将Github中FundMe.sol中代码复制到contracts目录中(这里就不再提FundMe.sol中代码,相关细节可看Solidity入门-开发众筹智能合约)。

通过brownie compile编译一下,此时会出现第一个问题:因为FundMe合约中使用了外部合约,brownie无法获取。

12e1d2ea71776d51ac46437e75de1993.png

与Remix在线IDE不同,Remix看到代码中有@符号的import会自动从npm中下载,但brownie不能,但brownie可以从github中下载相应的内容,通常外部的智能合约除了上传到npm还会上传到github中,我们可以新增brownie-config.yaml配置文件,新增如下内容:

dependencies:
??#?-?<organization/repo>@<version>
??-?smartcontractkit/chainlink-brownie-contracts@1.1.1
compiler:
??solc:
????remappings:
??????-?'@chainlink=smartcontractkit/chainlink-brownie-contracts@1.1.1'

配置中,通过remappings关联@chainlike与对于的github项目。

后续brownie遇到@chainlink便会将其替换成smartcontractkit/chainlink-brownie-contracts@1.1.1。

随后,我们在对合约进行编译,便可以正常编译。

40eb4afdc1817c9580030368e132a530.png

使用brownie部署合约时,brownie默认会部署到本地的区块链网络中(由ganache-cli提供),但FundMe合约使用了外部的智能合约,这些外部的智能合约必然不会在本地区块链网络中,所以这里直接部署到rinkeby。

跟之前一样,我们使用infer作为我们的节点提供商,从而快速实现将合约部署到不同网络的操作,infer使用的细节可以看「Brownie 开发智能合约(入门使用)」一文。

简单而言,注册infer,在infer上新建项目,从而获得相应的project_id和对应的私钥。

将需要使用私钥,我们已经放在.env文件中,在brownie-config.yaml中使用一下:

dependencies:
??#?-?<organization/repo>@<version>
??-?smartcontractkit/chainlink-brownie-contracts@1.1.1
compiler:
??solc:
????remappings:
??????-?'@chainlink=smartcontractkit/chainlink-brownie-contracts@1.1.1'
dotenv:?.env
wallets:
??from_key:?${PRIVATE_KEY}

yaml配置文件通常是要被git托管的,而私钥等信息,我们不希望被传到github中,所以我们创建.env然后将配置写到.env中,而yarm配置文件中也相应的填写了「dotenv:.env」

.env如下:

PRIVATE_KEY=<在Rinkeby测试网络中你钱包账号的私钥>
WEB3_INFURA_PROJECT_ID=<infer?project?id>

在scripts目录中,创建deploy.py文件,写入如下代码,进行合约的部署。

部署代码:

from?brownie?import?FundMe
from?scripts.utils?import?get_account


def?deploy_fund_me():
????account?=?get_account()
????#?部署是交易行为
????fund_me?=?FundMe.deploy({'from':?account})
????print(f'Contract?deployed?to?{fund_me.address}')


def?main():
????deploy_fund_me()

代码逻辑非常简单,通过get_account()方法获得部署合约时需要使用的账户,然后调用FundMe的deploy方法,部署合约是改变区块的行为,属于交易行为,需要通过account进行签名。

get_account()方法代码如下:

def?get_account():
????if?network.show_active()?==?'development':
????????return?accounts[0]
????else:
????????return?accounts.add(config["wallets"]["from_key"])

get_account()方法逻辑简单,如果当前区块链网络环境为development(brownie默认使用的本地环境)则直接返回accounts中的账号。brownie默认会通过ganache-cli在本地构建区块链网络,并默认初始化10个账号供于测试使用。

通过brownie run scripts/deploy.py --network rinkeby将合约部署到rinkeby中。

411a830ae4003d19acdc1f2b5c1e5fdf.png

部署完后,可以看到合约的地址

通过https://rinkeby.etherscan.io/可查看,点击contract部分,发现都是字节码,目前很多项目所谓的智能合约开源只是开源字节码,通过字节码,可以获得操作码(Opcodes),但Opcodes与源码在阅读理解上,还是有很大的差异的,所以研究区块链安全,逆向solidity是必不可少的(后面会写文章分享,已经在学了)。

ebc2ba5444c7b6a336c9a8af9261b4e6.png

这里,我们将FundMe.sol的合约源码直接放到etherscan中,方便他人浏览。

放源码最简单的方式,就是在etherscan中直接操作,点击 Verify and Publish,将合约代码以可阅读的方式开源到Etherscan上。

8bd3e1789a6fda4e0a3ac2473c103896.png

通过上图的形式,我们可以手动将合约的明文代码直接复制到etherscan中(当然,这个过程需要账户授权,所以通常只有合约创建者才有权限修改)。

因为solidity中,import操作其实是将相应的solidity代码复制到当前文件的过程,所以在复制代码到etherscan时,我们也需要将import对于的solidity文件复制进去。

通过brownie可以自动实现上述操作。

在etherscan.io上注册账号,然后进入账号设置页,点击API-KEYs,然后创建一个api-keys, brownie可以通过这个keys1与etherscan交互,从而实现将合约代码明文部署上去的操作。

8ff4cd20c448490db54809dd83bd4769.png

配置一下.env文件,其中新增的ETHERSCAN_TOKEN,其实就是我们在Etherscan中创建的Api-Key。

PRIVATE_KEY=<在Rinkeby测试网络中你钱包账号的私钥>
WEB3_INFURA_PROJECT_ID=<infer?project?id>
ETHERSCAN_TOKEN=<Etherscan中的Api-Key>

然后我们修改一个deploy.py中代码,如下:

from?brownie?import?FundMe
from?scripts.utils?import?get_account


def?deploy_fund_me():
????account?=?get_account()
????#?新增了publish_source=True
????fund_me?=?FundMe.deploy({'from':?account},?publish_source=True)
????print(f'Contract?deployed?to?{fund_me.address}')


def?main():
????deploy_fund_me()

在使用FundMe.deploy方法时,新增publish_source参数,便可以实现将合约源码提交到Etherscan中,再次部署,如下图:

431554630d8a166e5b7c57f2ee5309ee.png

从上图可以看出,brownie与api-rinkeby.etherscan.io交互的过程。在rinkeby.etherscan可看到源码

56d684278fbef202f8baca746c10acc6.png

此外,还给出了读合约和写合约的方法。

f591592d5c24c50c6e2fe222006d5fb3.png

Mocks

相比于将智能合约发布到rinkeby进行调试的方式,本地调试更快也更方便,但FundMe.sol中的问题便是,外部智能合约无法在本地区块链网络中使用。

我们可以通过Mock来解决。

chainlink无法在本地区块链网络中使用,可以通过mock功能解决。

Mock在传统的前后端开发中也很常见,在后端没有提供完整功能的接口前,前端可以通过Mock的方式模拟后端完整接口返回的数据,从而实现前端功能的开发。这里也是类似的,我们可以在本地区块链网络中实现与外部智能合约返回相同结果的方法,然后在本地使用时,直接替换成Mock的合约。

需要注意,Mock智能合约并不是要你完全实现一个具有相同功能的合约,而是确保同名函数可以返回与真实函数具有相同格式的值则可。

回到FundMe.sol,我们需要在本地Mock出AggregatorV3Interface.sol中的功能,从而提供假数据

幸运的是,我们可以直接使用chainlink-mix项目为我们写好的mock

项目代码:https://github.com/smartcontractkit/chainlink-mix/blob/master/contracts/test/MockV3Aggregator.sol

f3bdbe3e5aa9bc5bedc62c7c6cfe45c7.png

在contracts目录中创建test命令,然后将MockV3Aggregator.sol复制过去。

deploy.py修改后的代码为:

from?brownie?import?FundMe,?MockV3Aggregator,?network,?config
from?scripts.utils?import?*


def?deploy_fund_me():
????account?=?get_account()
????if?network.show_active()?not?in?LOCAL_BLOCKCHAIN_ENVIRONMENTS:
????????price_feed_address?=?config["networks"][network.show_active()]["eth_usd_price_feed"]
????else:
????????deploy_mocks()
????????#?部署后,最新的MockV3Aggregator
????????price_feed_address?=?MockV3Aggregator[-1].address
????fund_me?=?FundMe.deploy(
????????price_feed_address,
????????{'from':?account},
????????publish_source=config["networks"][network.show_active()].get('verify'))
????print(f'Contract?deployed?to?{fund_me.address}')
????return?fund_me


def?main():
????deploy_fund_me()

deploy_fund_me()方法会判断当前的网络情况,如果不是本地区块网络,则通过deploy_mocks()方法部署MockV3Aggregator.sol。

部署完后,直接通过MockV3Aggregator[-1].address取出部署的合约地址,然后在部署时传入。

这里有点奇怪的是,在本文前面的内容中,FundMe.deploy方法是不需要传入外部合约地址的,这因为我们在FundMe.sol中已经硬编码写死了,而此时因为我们Mock了外部的合约,所以不能使用硬编码的形式,而是需要我们使用外部传参的形式,通过修改FundMe.sol合约构造函数的方式做到这点:

23205247924b7111799432b07079fb90.png

接着,看一下utils.py中deploy_mocks方法的代码:

from?brownie?import?network,?config,?accounts,?MockV3Aggregator

def?deploy_mocks():
????print(f'The?active?network?is?{network.show_active()}')
????print('Deploying?Mocks...')
????#?先判断此前有没有部署过,没有再部署
????if?len(MockV3Aggregator)?<=?0:
????????MockV3Aggregator.deploy(DECIMALS,?STARTING_PRICE,?{"from":?get_account()})
????print('Mocks?Deployed')

因为Mock后的外部合约,其内容是不会改变的,所以只需要部署一次,部署过就不再需要部署了。

实现Mock后,通过brownie run scripts/deploy.py部署合约,从下图可知,合约部署成功:

33096a3f0f4862d2670705e7bfc681f1.png

添加新网络

brownie network list 可以查看当前所有的network,我们可以新增任何EVM的网络到brownie的network中。

新增区块链网络的原因:每次运行brownie,brownie都会通过ganache-cli在本地构建一个新的区块链网络,这样,之前的操作在brownie重新运行时,就会消失。可以通过新增本地网络的形式,通过ganache-cli构建本地网络,然后新增的网络会去链接它,这样就避免brownie每次运行都去新建。

首先,通过ganache-cli启动一个本地区块链网络。

ganache-cli?--port?8545?--gasLimit?12000000?--accounts?10?--hardfork?istanbul?--mnemonic?brownie

效果如图:

c73436037ce55d195e4b3f4375eb15c0.png

然后将这个网络添加到Ethereum中,新的网络名为ganache-local,命令如下(注意这是个错误命令,我在这里爬了一段时间坑):

brownie?networks?add?Ethereum?ganache-local?host=127.0.0.1:8545?chainid=1337

命令效果如下:

a5baa255bf25cc7bd1fb3dcc24d9cac8.png

然后看networks,可以发现Ethereum中多了一个ganache-local,似乎一切都很正常。

cc7f6e06f81d83d31a38563c14f3f9d3.png

然后我们让brownie使用ganache-local时,命令如下:

brownie?run?scripts/deploy.py?--network?ganache-local

运行命令后,会报RPC或HTTP无法连接的错误,这是因为ganache-local的host不是一个完整的连接,我们需要修改一下:

brownie?networks?modify?ganache-local?host=http://127.0.0.1:8545

注意host等于http://127.0.0.1:8545,不能缺少http前缀。

428383c96ea0016a021888aad2acce22.png

再次让brownie使用ganache-local运行区块链网络,效果就正常了。

3598f5a0980fa44c1d3a68c8a2a82465.png

ganache-cli不要关闭,不然上述命令会报错,我们看到ganache-cli中也会有相应的日志输出。

79e31534ad9ba8fcb11f404d1f7cee3c.png

部署后,build/deployments会有1337目录,存放着abi。

4eff3f896f08b64d508d4eba2dbaac7f.png

使用合约

在FundMe.sol中新增getEntranceFee方法

function getEntranceFee() public view returns (uint256) {
        // mimimumUSD
        uint256 mimimumUSD = 50 * 10 ** 18;
        uint256 price = getPrice();
        uint256 precision = 1 * 10 ** 18;
        return (mimimumUSD * precision) / price;
    }

4b51fec024e01bd88d96506a6f7e6f40.png

智能合约改变后需要再次编译和部署,不然我们使用的依旧是旧合约,而旧合约中是没有getEntranceFee方法的:

brownie?compile
brownie?run?scripts/deploy.py?--network?ganache-local

在scripts中创建fund_and_withdraw.py,在其中写入与FundMe.sol合约进行交互的逻辑,代码如下:

from?brownie?import?FundMe
from?scripts.utils?import?get_account


def?fund():
????fund_me?=?FundMe[-1]
????account?=?get_account()
????entrance_fee?=?fund_me.getEntranceFee()
????print(entrance_fee)
????print(f"The?current?entry?fee?is?{entrance_fee}")
????print("Funding")
????fund_me.fund({"from":?account,?"value":?entrance_fee})


def?withdraw():
????fund_me?=?FundMe[-1]
????account?=?get_account()
????fund_me.withdraw({"from":?account})


def?main():
????fund()
????withdraw()

通过brownie运行

brownie?run?scripts/fund_and_withdraw.py?--network?ganache-local

b35eff3fce0c95f38b75a5f4a4fc960f.png

单元测试

智能合约部署后,便无法修改,出现bug了,通常也是切换到新的智能合约上,而旧的合约会一直留在相应的区块中(这有利于我们学习恶意bug,因为旧合约一直都在,我们可以基于这些有问题的旧合约进行学习和恶意bug的利用复现)

为了尽量避免合约有bug,被恶意利用,我们需要写大量的单元测试代码,通常单元测试代码会多于合约的逻辑代码。

在test命令下创建新的py文件,然后写下如下测试代码:

import?pytest
from?brownie?import?network,?accounts,?exceptions
from?scripts.utils?import?*
from?scripts.deploy?import?deploy_fund_me


def?test_can_fund_and_withdraw():
????"""测试"""
????account?=?get_account()
????fund_me?=?deploy_fund_me()
????#?entrace?fee?入场费
????entrance_fee?=?fund_me.getEntranceFee()?+?100
????tx?=?fund_me.fund({"from":?account,?"value":?entrance_fee})
????tx.wait(1)
????assert?fund_me.addressToAmountFunded(account.address)?==?entrance_fee
????tx2?=?fund_me.withdraw({"from":?account})
????tx2.wait(1)
????assert?fund_me.addressToAmountFunded(account.address)?==?0


def?test_only_owner_can_withdraw():
????"""测试是否只有合约创建则才能转账"""
????if?network.show_active()?not?in?LOCAL_BLOCKCHAIN_ENVIRONMENTS:
????????#?不是本地网络,则跳过测试
????????pytest.skip("only?for?local?testing")
????fund_me?=?deploy_fund_me()
????bad_actor?=?accounts.add()
????#?非合约创建者,转账会失败,而失败是我们期望的结果,所以使用pytest.raises将其包裹
????with?pytest.raises(exceptions.VirtualMachineError):
????????fund_me.withdraw({"from":?bad_actor})

智能合约的单元测试与常规软件开发的单元测试没有太大差别。

上述代码中的test_only_owner_can_withdraw方法需要注意,我们希望测试时出现异常,所以使用pytest.raises捕获了相应的异常。

进行单元测试时,将本地运行着的ganache-cli停掉,使用brownie test时,会默认使用development网络进行测试,而运行中的ganache-cli可能会一些有数据,在测试时,可能会因数据不符而失败。

关停ganache-cli,然后再测试,如下图,图中使用了-k参数,指定只测试其中的一个方法:

2163496340656db2a5b0c807b3922b3b.png

测试所有单元测试:

36116569644b73ce5690bb8147af4010.png

在rinkeby测试链上测试合约:

f93d537f11387f6cc39a17f74d50d857.png

brownie默认运行的网络是development,你可以通过修改brownie-config.yaml来修改brownie默认运行的网络,这里还是选择development。

926f86294f76848ffafebe8d51af5fbe.png

fork

因为DeFi的兴起,现在很多智能合约都需要调用外部的合约,这就给本地开发智能合带来了一点麻烦,因为你本地的区块链是没有这些外部的智能合约的。在前面,我们使用Mock的方式解决过这个问题,但如果你调用的外部智能合约比较多,Mock起来也会有点麻烦。

解决这个问题的最佳方式是使用ganache-cli的fork功能,brownie也兼容了ganache-cli的fork功能,让我们在开发时,可以无感的使用。

fork的另外一个好处是,我们调用的外部合约与主网的情况是一致的,因为很多智能合约在测试网络中的情况与主网是有差异的,此时如果你直接上线,结果就很坑。

通过fork,我们可以构建一条与主网相同的区块链网络,它具有主网中的数据,这也就意味着我们fork的区块链网络已经自带了这个我们需要的外部智能合约了,而且与真实的合约是一致的。

与我们熟知的fork不同,比如我们在Github fork一个项目,这个项目的数据会完全同步一份到自己的github账号,而区块链主网数据其实挺大的,如果fork时将数据完全同步到本地,也挺麻烦的。

infer之类的服务帮我们简化了这步,即通过这类服务,它知道我们需要fork一份主网,但它会帮我们做完数据的fork操作,我们只需要通过http或rpc的方式与之交互便可以实现与主网交互同样的效果,而且这类交互还不需要花费真实的金钱。

这样,我们操作本地区块链中数据时,与操作真实的区块链没有啥区别。

这里我们使用https://www.alchemy.com/,alchemy与infer类似,都是节点提供商,两者拥有相似的功能。我们在alchemy中创建账号,然后创建一个app,主要创建的app其network选择以太坊的主网。

89d1ba154016029deaf8fae5a31478d6.png

创建完后,我们就会获得HTTP的链接地址。

631d9bc96bb5734951de59fb7b77af7d.png

我们通过brownie将这个地址以fork形式添加到networks中,具体命令如下:

brownie?networks?add?development?mainnet-fork-dev?cmd=ganache-cli?host=http://127.0.0.1?port=8545?accounts=10?mnemonic=brownie?fork=https://eth-mainnet.alchemyapi.io/v2/<在alchemy中获得>

上述命令添加名为mainnet-fork-dev的区块链网络到development中,我们使用ganache-cli作为创建本地区块链的后端,其原理就是利用了ganache-cli的fork功能。

0db1be0fc5b0bac34af9011ce1575c23.png

在mainnet-fork-dev网络中进行部署。

brownie?run?scripts/deploy.py?--network?mainnet-fork-dev

357ad90383fac37afd690d58df67c385.png

在mainnet-fork-dev网络中进行单元测试

brownie?test?--network?mainnet-fork-dev

64d55ccf56e70898c9c6d55996c782c6.png

在brownie中使用fork功能获得一个与主网相同的开发环境还是很简单的。

结尾

后续我会将业余时间花在智能合约安全以及dApp开发上,我们下篇文章见。

  区块链 最新文章
盘点具备盈利潜力的几大加密板块,以及潜在
阅读笔记|让区块空间成为商品,打造Web3云
区块链1.0-比特币的数据结构
Team Finance被黑分析|黑客自建Token“瞒天
区块链≠绿色?波卡或成 Web3“生态环保”标
期货从入门到高深之手动交易系列D1课
以太坊基础---区块验证
进入以太坊合并的五个数字
经典同态加密算法Paillier解读 - 原理、实现
IPFS/Filecoin学习知识科普(四)
上一篇文章      下一篇文章      查看所有文章
加:2021-11-20 18:26:43  更:2021-11-20 18:27:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 23:27:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码