您正在查看: 2023年2月

Foundry教程|如何调试和部署Solidity智能合约

Foundry是一个Solidity框架,用于构建、测试、模糊、调试和部署Solidity智能合约。在这个Foundry教程中,我们将介绍以下内容。

  • Foundry简介 [视频]
  • Foundry入门
  • 使用Foundry的Hardhat
  • 使用 Solidity 测试
  • Foundry作弊代码
  • 部署和使用合约

Foundry简介

我制作了一个[视频],你可以在YouTube上观看:https://youtu.be/VhaP9kYvlOA
智能合约通常是用Solidity编写的,但使用Javascript框架(如Truffle或Hardhat)进行测试和部署。Foundry提供了一套在Rust中构建的工具,允许区块链开发者在Solidity中编写测试,并通过命令行部署和与合约交互。

为什么用Foundry?

  • 在solidity中编写单元测试,而不是Javascript
  • 更快的编译和测试
  • 内置的模糊测试
  • Gas优化工具
  • 支持主网分叉
  • Etherscan 代码验证
  • 硬件钱包兼容
  • Solidity脚本
  • 通过作弊代码操纵区块链状态

开始使用Foundry

安装说明

为了开始使用,我们需要安装foundry包,它需要rust。以下是linux、mac、windows和docker的命令。

Linux/Mac:

curl -L https://foundry.paradigm.xyz | bash;
foundryup

Windows: (需要 Rust,从 https://rustup.rs/ 安装)

cargo install --git https://github.com/foundry-rs/foundry --bins --locked

Docker:

docker pull ghcr.io/foundry-rs/foundry:latest

使用foundry的第一步

Foundry软件包带有两个主要的命令行功能:

  • forge - 建立编译测试本地智能合约
  • cast - 使用已部署的智能合约执行链上交易

如果想从Github上克隆一个repo,我们可以使用forge命令。

forge install jamesbachini/myVault -hh

这里我们使用的是Github用户名和版本库名称,加上-hh 参数,用于迁移Hardhat版本库。

我们也可以用 "myrepo" 初始化一个新的版本库。

forge init myrepo

然后就可以继续编译和测试智能合约了

forge build
forge test


注意测试是如何通过的,还得到了测试交易的Gas成本

如何使用 Hardhat 设置 Foundry

假设我们已经按照上面的说明安装了 Foundry,我们就可以与 Hardhat 一起使用它。
注意,我们需要为当前工作目录设置一个 repo,所以如果还没有的话,请使用 git init。
然后复制并粘贴以下内容到版本库根目录下的一个新的foundry.toml文件中:

[default]
src = 'contracts'
test = 'test'
out = 'artifacts/contracts'
libs = ['lib']

这将设置目录结构以便与 Hardhat 保持一致。Foundry 测试可以使用正常的 MyContract.t.sol 命名放在标准测试文件夹中。
从这里我们可以在Hardhat 中使用Foundry进行测试和部署。就我个人而言,我喜欢 Hardhat 的脚本环境(特别是对于复杂的部署),但也认识到使用 Foundry 进行测试和模糊处理的好处。在同一个代码库中使用这两个应用程序,可以提供两个最佳选择。

使用 Solidity 测试

在我们开始编写单元测试之前,需要安装标准库

forge install foundry-rs/forge-std

然后我们可以将其导入测试文件中,该文件的名称将与我们的合约相同,后缀是.t.sol。

pragma solidity ^0.8.13;

import "forge-std/Test.sol";

contract MyContractTest is Test {

  testWhatever(uint256 var1) public {
    uint256 var2 = 1;
    assertEq(var1,var2);
  }

}

请注意,在上面的例子中,我们给函数名加上了一个前缀test -> testWhatever(),此时出现任何revert都将失败。我们也可以把testFail -> testFailWhatever()作为前缀,这样就需要revert才能通过。

另外请注意,在上面的例子中,我们在var1中传入了一个uint256变量。这个值将被fuzzed(模糊),这意味着它将用各种不常用的值在函数上循环,试图创造一个边缘情况,导致回退(revert)。在这种情况下,任何不是1的值都会由于assertEq()函数而导致测试失败。
也可以创建自定义断言,例如:

function myAssertion(uint a, uint b) {
  if (a != b) {
    emit log_string("a != b");
    fail();
  }
}

然后,我们可以在整个合约中使用这个断言,或者建立一个自定义断言库,并类似于我们先前导入标准库的方式来导入它们。

如果代码库包含许多不同的智能合约,可以使用 --match-contract将单个合约和它的依赖关系分离出来,甚至可以使用--match-test命令行选项进行特定测试。

forge test --match-test optionalSpecificTest --match-contract optionalSpecificContract

另外,我们可以使用forge run来执行一个单一的solidity "脚本 "。

forge run src/Contract.sol              //  执行单个脚本
forge run src/Contract.sol --debug // open script in debugger
forge run src/Contract.sol --sig "foo(string)" "hi" //  执行单一函数

一旦我们发现了一个bug,我们可以使用-v命令来提高提示程度并获得更多细节。

debug with logs -vv
debug with traces for failing tests -vvv
debug with traces for all tests -vvvv

追踪(Traces)是一种非常强大的方式,可以仔细观察智能合约执行过程中发生的事情。这有点像在命令行上有Tenderly.co

还有一个交互式调试器(debugger),这让我想起了1990年的Linux调试器。我没有经常使用,但这里有更多的说明: https://book.getfoundry.sh/forge/debugger.html

forge test --debug "testSomething"

可以在本地分叉(fork)一个区块链,然后关联外部智能合约一起测试我们的合约。如果你想与其他defi协议交互,例如Uniswap,或者使用真实的市场数据执行压力测试,这很有用。

forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/abc123alchmeyApiKey

Gas优化

编译时的合约Gas报告可以通过foundry.toml配置来设置

gas_reports = ["MyContract", "MyContractFactory"]

然后用forge test -gas-report选项执行命令。

优化函数的一个方法是使用测试合约,并在修改前后进行快照对比:

forge snapshot --snap gas1.txt
// make some changes
forge snapshot --diff gas1.txt

这将提供之前的Gas报告和当前快照之间的差异。

用Slither进行安全分析

当涉及到智能合约安全时,Slither绝不是一个简单的解决方案,但它是有用的,并提供了一些自动检查,如检查重入错误。为了使用slither,我一般会切换到WSL(linux的Windows子系统),它可以用以下命令安装(注意0.8.13是目前foundry演示合约中使用的solc版本。你可以把它修改为Solidity文件中设置的任何版本)。

apt install python3-pip
pip3 install slither-analyzer
pip3 install solc-select
solc-select install 0.8.13
solc-select use 0.8.13

然后把以下复制到我们工作的主目录下一个叫slither.config.json的文件里

{
  "filter_paths": "lib",
  "solc_remaps": [
    "ds-test/=lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/"
  ]
}

然后运行一个测试,使用

slither src/Contract.sol

在官方readme中有更多关于slither的信息:https://github.com/crytic/slither

Foundry作弊代码

Foundry有一套作弊代码,它可以对区块链的状态进行修改,以方便在测试时使用。这些代码可以直接执行合约:0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 进行调用,但更多时候是通过标准库和vm对象执行。
重要的作弊代码有:

  • vm.warp(uint256) external; 设置 block.timestamp
  • vm.roll(uint256) external; 设置 block.height.
  • vm.prank(address) external; 设置地址作为下一次调用的msg.sender
  • vm.startPrank(address) external; 设置地址作为所有后续调用的msg.sender
  • vm.stopPrank() external; 重置后续调用msg.sender为address(this)。
  • vm.deal(address, uint256) external; 设置一个地址的余额,参数:(who,newBalance)。
  • vm.expectRevert(bytes calldata) external; 期待下次调用时出现错误。
  • vm.record() external; 记录所有存储的读和写。
  • vm.expectEmit(true, false, false, false); emit Transfer(address(this)); transfer(); 检查事件主题1在两个事件中是否相等
  • vm.load(address,bytes32)外部返回(bytes32); 从一个地址加载一个存储槽
  • vm.store(address,bytes32,bytes32) external; 将一个值存储到一个地址的存储槽中,参数(who, slot, value)。

这些可以用来改变测试的过程,如在这个例子中,告诉测试套件在调用时期望一个标准的算术错误。

vm.expectRevert(stdError.arithmeticError);

完整的列表在这里:https://github.com/foundry-rs/forge-std/blob/master/src/Vm.sol

部署和使用合约

Foundry也可以用来部署并与智能合约交互。

要部署一个合约,我们可以使用下面的命令:

forge create --rpc-url https://mainnet.infura.io --private-key abc123456789 src/MyContract.sol:MyContract --constructor-args "Hello Foundry" "Arg2"

注意我们在生产中部署时不应该使用硬编码的私钥。一个选择是使用-ledger或-trezor来通过硬件钱包执行。另外,也可以使用环境变量来存储私钥。

另一种环境变量来存储私钥:
Linux/Mac:
--privateKey $privateKey

export privateKey=abc123

Windows Powershell:
--privateKey $env:privateKey

$env:privateKey = "0x123abc"

私钥不应该包含0x前缀,否则你会得到一个错误 "Invalid character ‘x’ at position 1(无效字符'x'在位置1)"

我们还可以使用forge命令在etherscan上验证合约,以便我们能够使用Etherscan的UI和Metamask与之交互。

forge verify-contract --chain-id 1 --num-of-optimizations 200 --constructor-args (cast abi-encode "constructor(string)" "Hello Foundry" --compiler-version v0.8.10+commit.fc410830 0xContractAddressHere src/MyContract.sol:MyContract ABCetherscanApiKey123

也可以使用forge来扁平化合约,其中包括外部合约的依赖关系合并到一个文件中:

forge flatten --output src/MyContract.flattened.sol src/MyContract.sol

要生成ABI,可以使用以下命令:

forge inspect src/MyContract.sol abi

注意任何ABI都可以转换为接口并直接在solidity中使用: https://gnidan.github.io/abi-to-sol/

如果合约已经被验证,我们也可以使用以下命令来生成一个接口。

cast interface 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984

用 Cast 交互

我们可以call 方式调用合约请求链上数据。我们也可以提供凭证(私钥)来发送一个交易,就像我们在metamask中签署一个交易一样。

cast call 0xabc123 "totalSupply()(uint256)" --rpc-url https://eth-mainnet.alchemyapi.io

cast send 0xabc123 "mint(uint256)" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123

一旦区块被确认,也可以获取交易本身的信息:

cast tx 0xa1588a7c58a0ac632a9c7389b205f3999b7caee67ecb918d07b80f859aa605fd

也可以通过flashbots保护执行交易,cast使用--flashbots 参数发送交易。

最后,你可能想估算一个Gas成本,这也可以用cast来完成:

cast estimate 0xabc123 "mint(uint256)" 3 --rpc-url https://eth-mainnet.alchemyapi.io --private-key=abc123

Foundry为测试和审计智能合约提供了一个快速、高效的框架。我希望这个教程是有用的,如果想进一步深入了解,别忘了查看官方文档。https://book.getfoundry.sh

Foundry中文文档: https://learnblockchain.cn/docs/foundry/i18n/zh/
转载:https://learnblockchain.cn/article/4524

tx.origin、msg.sender有什么不一样

刚看了篇文章,感觉还可以,分享一下,原文在此:
tx.origin vs msg.sender. I have been recently playing ethernaut… | by David Kathoh | Medium

吃过亏的人肯定知道。

msg.sender: 指直接调用智能合约功能的帐户或智能合约的地址
tx.origin: 指调用智能合约功能的账户地址,只有账户地址可以是tx.origin

再来一张图片:

目前不少安全问题都是因为这两个概念理解差异造成的,希望你在写合约的时候用你需要的东西。

转载自:https://learnblockchain.cn/article/3568

how to get eth_signTypedDataV4 signature in web3.py

EIP712 signatures can generated using the https://eth-account.readthedocs.io/en/stable/eth_account.html#eth_account.messages.encode_structured_data function to encode the EIP712 message, and use web3 to sign it.

msg = { "types": 
{ "EIP712Domain": [], "Type":  []},
  "domain":domain,
  "primaryType": 'Type',
  "message":message }

encoded_data=encode_structured_data(msg)
web3.eth.account.sign_message(encoded_data,privateKey)

https://ethereum.stackexchange.com/questions/114190/how-to-get-eth-signtypeddatav4-signature-in-web3-py

Web3学习笔记:交易所钱包管理系统

交易所作为web3世界的核心枢纽,托管着大量加密资产,其钱包管理关系到大量用户的资产安全,因此很有必要对其钱包管理系统研究学习一番。这里我整理了一篇个人从产品设计者视角的学习笔记,希望能够帮助到大家学习。

交易所钱包系统组成

交易所钱包系统设计主要需要考虑到安全性便捷性。为了兼顾两者,交易所大部分资产用冷钱包管理,以保证资产安全;而少部分资产使用热钱包管理,以方便用户提现资产。

热钱包系统

热钱包系统由用户充值钱包、归集钱包、提现钱包、手续费钱包等组成

  • 用户充值钱包:用户需要向交易所进行代币充值 ,需要为每一名用户分配一个地址,用于充值代币,其私钥保存在服务端以供后续资金归集时签名。
  • 归集钱包:资产分散在各用户充值钱包中不方便管理,因此当充值钱包达到一定规模时需要进行资金归集,将资金统一归集至归集钱包。
    大量资金统一管理在一个钱包中是存在比较大的安全风险,所以需要对归集钱包中的资产进行分配并转移。
  • 提现钱包:为了方便用户提现和资金安全,一般会将20%资金转移到一个提现钱包(也可能是几个钱包),这个钱包专门用于客户提币使用。
  • 手续费钱包:归集资金、转移资金、用户提现这几个操作均需要交易费,当其他钱包ETH不足抵扣交易费时,由该钱包转入一定的ETH作为交易费。

冷钱包系统

冷钱包系统一般包含系统冷钱包和BOSS钱包组成。

  • 系统冷钱包:一般会将20%~30%的资金会转移至系统冷钱包,当提现钱包资金不够时才会在系统冷钱包进行划转。
  • BOSS钱包:一般50%以上资金会转移至boss钱包,由公司一个或多个老板控制。

钱包系统业务流程

交易所钱包系统主要涉及业务流程:

  • 注册— 生成充值地址
  • 用户充值处理
  • 资金归集
  • 资金分配转移
  • 提现转账

注册—— 生产充值地址

当用户注册账户,需要对应创建一个钱包,为用户分配一个单独的充值地址,私钥由保存在服务端以用于资金归集。

用户充值处理

我们需要持续监听充值钱包代币入账及区块确认数,一般12个区块确认后才认定为入账成功。入账成功后,需要判断平台是否支持该代币,若支持则增加账户对应代币的余额,至此完成用户充值处理。

资金归集

用户充值完成后,我们需要判断用户充值钱包该代币资产价值,一般当其价值达到1000U,我们需要将钱包中资金归集到归集钱包。

归集前需要判断预估gas是否过高(超过100 gas),若过高则等待降下来,再进行归集。
另外,需要判断钱包是否有足够ETH以抵扣交易费,若不足则需从手续费钱包转入0.01ETH再进行归集。

资金分配转移

归集钱包为热钱包,当大量资产都在一个热钱包是存在极大风险的,因此我们将资产转移。

筛选统计:在进行划转和分配之前,我们需要对归集钱包中资产进行筛选统计,筛选出平台支持且有余额的代币,然后统计筛出代币的余额。

资金分配:我们将80%的资产转移至冷钱包地址,20%继续转移至提现热钱包中。这样设计兼顾安全性和便捷性,冷钱包控制相对来说保证了安全性;而大部分玩家都是把大部分资金存放在交易所中,热钱包20%流动资金是足够支撑用户的提币需求,因此也一定保证了便捷性。

资金划转:我们一般为固定周期进行分配转移,一般设计为一周划转一次,具体周期和时间可以根据业务和公司情况而定。我们根据代币类型采用不同划转方式,ETH直接根据分配资金转账,ERC-20代币使用合约批量打包转账(节省交易费)。

提现转账

用户发起提现后,由于Nonce机制存在,并不直接将交易发送至网络,而使用本地队列排队,接收到上一条成功确认记录后,再获取最新Nonce,进行下一条转账发送。

发送前,判断余额是否充足,若不足则需申请从系统钱包手动转入足够的代币后再进行转账。

私钥管理方案

多签

提示:以太坊本身不支持多签,需要使用智能合约实现,合约本身又存在安全风险,因此交易所的以太坊私钥一般采用该方案保存

普通用户可以将私钥/助记词保存在软件、纸上或者通过大脑记忆保存。但对于拥有巨额资产的交易所而言,普通用户的私钥管理方式根本无法适用,否则将存在单点保存的风险。

为了避免一个私钥的丢失导致地址的资金丢失,达到风险分散的目的,一般会采用多重签名技术。所谓多重签名技术,就是把一个钱包地址的控制权交给多个密钥,这些密钥保存在不同的地点,并分别生成签名。举个简单的例子:

2-3多重签名,表示3个人拥有签名权,而两个人签名就可以支配这个账户里的资金;
1-2多重签名,表示2个人可以签名,两个人拥有私钥,谁都可以来支配这笔资金。

在热钱包方面,一般采用2-3多重签名,需要三个私钥持有人中的两个分别进行授权,才能完成签名。
在冷钱包方面,一般采用的是2-2多重签名,即每个私钥的使用需要两个人双重授权才能进行提币,以保证冷钱包安全。

备份

在采用多重签名的同时,为了进一步降低私钥丢失或损坏的风险,还对私钥进行了备份处理:
对于热钱包,备份私钥存储办公室附近的银行保险柜。
对于冷钱包,一般备份两份,一份存在办公室附近的银行保险柜,一份存在异地某一家银行的保险柜;同时,异地银行保险柜必须由两个不同的人掌握,掌握银行保险柜的两个人不得乘坐同一辆交通工具。

转载自:https://learnblockchain.cn/article/4615

EIP 1014: CREATE2 指令

EIP 1014: CREATE2 指令

作者 类型 分类 状态 创建时间
Vitalik Buterin Standards Track Core Final 2018-04-20

#规范

添加了一个新的0xf5 操作码指令CREATE2 ,它使用4个栈参数:endowment, memory_start, memory_length, salt 。 0xf5 指令的行为和CREATE相同,只不过它使用keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:] 而不是 发送者+nonce hash来计算出合约地址。

CREATE2CREATEgas 模式一样,但是有一个额外的 GSHA3WORD * ceil(len(init_code) / 32) hash 计算消耗(hashcost),在计算出地址和执行 init_code代码之前,hashcost 与内存扩展 及 CreateGas 一起被扣除。

  • 0xff 为一个字节
  • address20 字节
  • salt32 直接 (占用一个栈).

因此,最后一次哈希keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:] 的内容始终正好是85字节长, 在2018-08-10进行的核心开发者会议上决定使用上述公式。

#动机

允许使用链上尚不存在但可以依赖的地址进行交互,对于像 counterfactually 类似的状态通道中非常有用。地址由特定初始化代码及可控的salt控制。

#原理阐述

#Address 计算公式

  • 确保使用此方案创建的地址不会与使用传统的 keccak256(rlp([sender, nonce])) 公式创建的地址冲突,而0xff 只可能是很长字节的 RLP编码前缀。
  • 确保hash 的转载内容是 固定字节大小

#Gas 消耗

由于地址计算依赖于对 init_code 的hash ,因此如果反复对大块 init_code 执行 hash,可能使网络客户端容易受到DoS攻击,因为内存扩展仅支付一次。 该EIP使用与 SHA3 操作码相同的每字成本。

#澄清

init_code 是执行后会生成运行时字节码(存入链状态)的代码,通常由高级语言用来实现构造函数。合约

此EIP可能产生冲突。 冲突行为EIP 684有指出:

如果使用创建交易或CREATE(或将来的CREATE2)操作码去尝试创建合约,并且目标地址已经具有非零 nonce 或非空代码,则该创建将立即抛出,如果初始化代码中的第一个字节是无效的操作码,则同样会抛出此错误。 适用与从创世纪块开始追溯。

具体来说,如果 noncecode 非零,则创建操作将失败。

以及 EIP 161

在执行初始化代码之前,帐户创建交易和CREATE操作应随机数增加1。

这意味着,如果在交易中创建合约,则 nonce 立即为非零,其副作用是,同一交易中的冲突将始终失败-即使是通过 init_code 本身执行 。

还应注意,SELFDESTRUCTnoncecode 没有立即生效,因此不能在一次交易中销毁和重新创建合约。

#示例

示例 0

  • address 0x0000000000000000000000000000000000000000
  • salt 0x0000000000000000000000000000000000000000000000000000000000000000
  • init_code 0x00
  • gas (assuming no mem expansion): 32006
  • result: 0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38

示例 1

  • address 0xdeadbeef00000000000000000000000000000000
  • salt 0x0000000000000000000000000000000000000000000000000000000000000000
  • init_code 0x00
  • gas (assuming no mem expansion): 32006
  • result: 0xB928f69Bb1D91Cd65274e3c79d8986362984fDA3

示例 2

  • address 0xdeadbeef00000000000000000000000000000000
  • salt 0x000000000000000000000000feed000000000000000000000000000000000000
  • init_code 0x00
  • gas (assuming no mem expansion): 32006
  • result: 0xD04116cDd17beBE565EB2422F2497E06cC1C9833

示例 3

  • address 0x0000000000000000000000000000000000000000
  • salt 0x0000000000000000000000000000000000000000000000000000000000000000
  • init_code 0xdeadbeef
  • gas (assuming no mem expansion): 32006
  • result: 0x70f2b2914A2a4b783FaEFb75f459A580616Fcb5e

示例 4

  • address 0x00000000000000000000000000000000deadbeef
  • salt 0x00000000000000000000000000000000000000000000000000000000cafebabe
  • init_code 0xdeadbeef
  • gas (assuming no mem expansion): 32006
  • result: 0x60f3f640a8508fC6a86d45DF051962668E1e8AC7

示例 5

  • address 0x00000000000000000000000000000000deadbeef
  • salt 0x00000000000000000000000000000000000000000000000000000000cafebabe
  • init_code 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef
  • gas (assuming no mem expansion): 32012
  • result: 0x1d8bfDC5D46DC4f61D6b6115972536eBE6A8854C

示例 6

  • address 0x0000000000000000000000000000000000000000
  • salt 0x0000000000000000000000000000000000000000000000000000000000000000
  • init_code 0x
  • gas (assuming no mem expansion): 32000
  • result: 0xE33C0C7F7df4809055C3ebA6c09CFe4BaF1BD9e0

本翻译采用BY-NC-ND许可协议,译者:深入浅出区块链 Tiny熊。

转载自:https://learnblockchain.cn/docs/eips/eip-1014.html