pragmasolidity ^0.8.18;contract Wallet {addresspayablepublic owner;constructor() payable { owner =payable(msg.sender); }receive() externalpayable {}functionwithdraw(uint256_amount) external {require(msg.sender == owner,"caller is not owner");payable(msg.sender).transfer(_amount); }functionsetOwner(address_owner) external {require(msg.sender == owner,"caller is not owner"); owner =payable(_owner); }}
Create a test file Auth.t.sol:
// SPDX-License-Identifier: UNLICENSEDpragmasolidity ^0.8.18;import"forge-std/Test.sol";import {Wallet} from"../src/Wallet.sol";contractAuthTestisTest { Wallet public wallet;functionsetUp() public { wallet =newWallet(); }functiontestSetOwner() public { wallet.setOwner(address(1));assertEq(wallet.owner(),address(1)); }}
This AuthTest contract can call wallet.setOwner() because it was set as the owner in the constructor.
Write a fail test case:
functiontestFailNotOwner() public { vm.prank(address(1)); wallet.setOwner(address(1));assertEq(wallet.owner(),address(1));}
vm.prank(address(1)) means "for the next call, pretend we are address(1)". This test case would fail because address(1) is not the owner so that it cannot call wallet.setOwner().
An extension of vm.prank() is the vm.startPrank() and vm.endPrank() pair. The impersonation will start on vm.startPrank() until vm.endPrank() is executed.
functiontestFailSetOwnerAgain() public {// msg.sender = address(this) wallet.setOwner(address(1)); vm.startPrank(address(1));// msg.sender = address(1) wallet.setOwner(address(1)); wallet.setOwner(address(1)); wallet.setOwner(address(1)); vm.stopPrank();// msg.sender = address(this) -> revert because address(this) is not the owner anymore wallet.setOwner(address(1));}