validationIntersection
vm.startPrank
在智能合约测试中,vm.startPrank 是 Foundry 测试框架中的一个功能,它用于模拟区块链上的特定地址的行为。当你调用 vm.startPrank 并传递一个地址时,你告诉测试框架模拟该地址的行为,直到调用 vm.stopPrank。
这意味着在 vm.startPrank 和 vm.stopPrank 之间的所有操作都会被执行,就好像它们是由 vm.startPrank 传递的地址发起的一样。这对于测试需要特定权限或角色的函数非常有用,例如,仅允许合约的所有者调用的函数。
以下是 vm.startPrank 的一些典型用途:
-
模拟交易发送者: 你可以模拟一个特定用户或合约地址发送交易,以测试权限控制逻辑。
-
状态隔离: 在模拟的地址上执行的所有状态更改(例如,以太币余额的变化、合约状态变量的变化等)在
vm.stopPrank调用之后不会影响后续的测试。 -
行为连贯性: 一系列连续的调用可以在同一个模拟的地址上下文中执行,而不需要每次调用都指定发送者。
在你提供的代码中,vm.startPrank(address(owner1)); 表示开始模拟地址 owner1 的行为,直到 vm.stopPrank() 被调用。这在安装插件时被使用,因为只有账户的所有者才能安装插件到他们自己的账户上。
为什么其他单元测试没有使用 vm.startPrank?
在单元测试中,是否使用 vm.startPrank 取决于测试的特定需求。vm.startPrank 主要用于模拟一个特定地址的行为,这在需要测试基于地址的权限控制时非常有用。如果某个功能需要特定的权限或角色(例如,只有所有者可以执行的操作),则使用 vm.startPrank 可以确保测试从该特定地址的角度进行。
在其他单元测试中可能没有使用 vm.startPrank 的原因可能有:
-
不需要权限控制:如果测试的函数不涉及权限控制,或者不需要模拟特定地址的行为,那么就不需要使用
vm.startPrank。 -
全局影响:如果测试的函数对所有用户都有相同的效果,或者不需要模拟特定的用户交互,那么使用
vm.startPrank就不是必需的。 -
单次交易:如果只需要发送一次交易,并且不需要维持状态或连续性,可以直接使用
vm.prank,它允许发送单个交易作为指定地址,而不需要使用vm.startPrank和vm.stopPrank包围一系列操作。 -
默认发送者:在某些测试框架中,默认的发送者可能已经是合约的所有者或具有必要权限的地址,因此不需要额外的模拟。
-
测试框架的功能:不同的测试框架可能提供不同的方法来处理权限和模拟地址。有些框架可能有替代
vm.startPrank的方法来模拟地址或设置权限。
因此,在你看到的其他单元测试中,如果没有使用 vm.startPrank,那么这些测试可能不需要模拟特定地址的状态或权限,或者可能使用了其他方法来处理这些需求。
测试 eth 发送
function test_postDeploy_ethSend() public {
UserOperation memory userOp = UserOperation({
sender: address(account2),
nonce: 0,
initCode: "",
callData: abi.encodeCall(UpgradeableModularAccount.execute, (ethRecipient, 1 wei, "")),
callGasLimit: CALL_GAS_LIMIT,
verificationGasLimit: VERIFICATION_GAS_LIMIT,
preVerificationGas: 0,
maxFeePerGas: 1,
maxPriorityFeePerGas: 1,
paymasterAndData: "",
signature: ""
});
// Generate signature
bytes32 userOpHash = entryPoint.getUserOpHash(userOp);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(owner2Key, userOpHash.toEthSignedMessageHash());
userOp.signature = abi.encodePacked(r, s, v);
UserOperation[] memory userOps = new UserOperation[](1);
userOps[0] = userOp;
entryPoint.handleOps(userOps, beneficiary);
assertEq(ethRecipient.balance, 2 wei);
}
这个是干嘛的
这段代码是一个智能合约的单元测试函数,它的目的是验证一个用户操作(UserOperation)是否能够成功地将以太币发送到指定的接收者(ethRecipient)。这个测试是在一个基于以太坊的区块链环境中进行的,其中使用了一些专门的测试工具和方法。
下面是代码的逐行解释:
-
定义一个名为
test_postDeploy_ethSend的函数,这是一个公共函数,意味着它可以在合约外部调用。 -
创建一个
UserOperation结构体实例userOp,它包含了执行一个用户操作所需的所有信息:sender是操作的发起者,这里设置为account2的地址。nonce是操作的序号,用于防止重放攻击,这里设置为 0。initCode是创建合约时执行的代码,这里为空字符串,表示不创建新合约。callData包含了调用UpgradeableModularAccount.execute函数的编码数据,它会向ethRecipient地址发送 1 wei。callGasLimit、verificationGasLimit、preVerificationGas是与操作执行和验证相关的气体限制和成本。maxFeePerGas和maxPriorityFeePerGas是用户愿意为每单位气体支付的最大费用。paymasterAndData是支付操作费用的合约(如果有)及其数据,这里为空字符串。signature是操作的签名,初始为空。
-
生成操作的哈希值
userOpHash,这个哈希是用于签名的消息。 -
使用
vm.sign函数和私钥owner2Key对userOpHash进行签名,以证明account2授权了这个操作。签名由三部分组成:v、r和s。 -
将签名的组成部分编码并设置到
userOp.signature中。 -
创建一个包含单个
UserOperation的数组userOps。 -
通过调用
entryPoint.handleOps函数来处理userOps,其中beneficiary是在操作成功后接收手续费的地址。 -
使用
assertEq函数来断言ethRecipient的余额是否为 2 wei,以验证操作是否成功将 1 wei 发送给了ethRecipient。
总之,这个测试函数模拟了一个用户操作,该操作通过 UpgradeableModularAccount.execute 函数发送一定量的以太币给接收者,并验证了操作的执行是否符合预期。这是智能合约开发中确保合约行为正确性的标准做法。