FPS enables the simulation of proposals within integration tests. This capability is essential for verifying the functionality of your proposals and ensuring they don't break existing features. Additionally, it allows testing of the entire proposal lifecycle, including governance proposals and deployment scripts. This guide illustrates writing integration tests with the Multisig example from our Multisig Proposal Guide. These integration tests have already been implemented in the fps-example repo.
Setting Up PostProposalCheck.sol
The first step is to create a PostProposalCheck.sol contract, which serves as a base for your integration test contracts. This contract is responsible for deploying proposal contracts, executing them, and updating the addresses object. This allows integration tests to run against the newly updated state after all changes from the governance proposal go into effect.
pragmasolidity ^0.8.0;import"@forge-std/Test.sol";import { MultisigProposal } from"@forge-proposal-simulator/src/proposals/MultisigProposal.sol";import { Addresses } from"@forge-proposal-simulator/addresses/Addresses.sol";// @notice this is a helper contract to execute proposals before running integration tests.// @dev should be inherited by integration test contracts.contractMultisigPostProposalCheckisTest { Addresses public addresses;functionsetUp() publicvirtual {string[] memory inputs =newstring[](2); inputs[0] ="./get-latest-proposal.sh"; inputs[1] ="MultisigProposal";stringmemory output =string(vm.ffi(inputs)); MultisigProposal multisigProposal =MultisigProposal(deployCode(output) ); vm.makePersistent(address(multisigProposal));// Execute proposals multisigProposal.run(); addresses = multisigProposal.addresses(); }}
Creating a script that returns the latest proposal based on the type of proposal.
#!/bin/bashBASE_DIR="out"PROPOSAL_TYPE="$1"# Find proposal directories and get the latest oneLATEST_PROPOSAL_DIR=$(ls-1v ${BASE_DIR}/|grep"^$PROPOSAL_TYPE"|tail-n1)LATEST_FILE="${LATEST_PROPOSAL_DIR%.sol}"# Print the path to the latest proposal artifact json fileecho"${BASE_DIR}/${LATEST_PROPOSAL_DIR}/${LATEST_FILE}.json"
Creating Integration Test Contracts
Next, the creation of the MultisigProposalIntegrationTest contract is required, which will inherit from MultisigPostProposalCheck. Tests should be added to this contract. Utilize the addresses object within this contract to access the addresses of the contracts that have been deployed by the proposals.
pragmasolidity ^0.8.0;import { Vault } from"src/mocks/Vault.sol";import { Token } from"src/mocks/Token.sol";import { MultisigPostProposalCheck } from"./MultisigPostProposalCheck.sol";// @dev This test contract inherits MultisigPostProposalCheck, granting it// the ability to interact with state modifications effected by proposals// and to work with newly deployed contracts, if applicable.contractMultisigProposalIntegrationTestisMultisigPostProposalCheck {// Tests adding a token to the whitelist in the Vault contractfunctiontest_addTokenToWhitelist() public {// Retrieves the Vault instance using its address from the Addresses contract Vault multisigVault =Vault(addresses.getAddress("MULTISIG_VAULT"));// Retrieves the address of the multisig walletaddress multisig = addresses.getAddress("DEV_MULTISIG");// Creates a new instance of Token Token token =newToken();// Sets the next caller of the function to be the multisig address vm.prank(multisig);// Whitelists the newly created token in the Vault multisigVault.whitelistToken(address(token),true);// Asserts that the token is successfully whitelistedassertTrue( multisigVault.tokenWhitelist(address(token)),"Token should be whitelisted" ); }// Tests deposit functionality in the Vault contractfunctiontest_depositToVault() public {// Retrieves the Vault instance using its address from the Addresses contract Vault multisigVault =Vault(addresses.getAddress("MULTISIG_VAULT"));// Retrieves the address of the multisig walletaddress multisig = addresses.getAddress("DEV_MULTISIG");// Retrieves the address of the token to be depositedaddress token = addresses.getAddress("MULTISIG_TOKEN"); (uint256 prevDeposits, ) = multisigVault.deposits(address(token), multisig );uint256 depositAmount =100;// Starts a prank session with the multisig address as the caller vm.startPrank(multisig);// Mints 100 tokens to the multisig contract's addressToken(token).mint(multisig, depositAmount);// Approves the Vault to spend depositAmount tokensToken(token).approve(address(multisigVault), depositAmount);// Deposits depositAmount tokens into the Vault multisigVault.deposit(address(token), depositAmount);// Retrieves the deposit amount of the token in the Vault for the multisig address (uint256 amount, ) = multisigVault.deposits(address(token), multisig);// Asserts that the deposit amount is equal to previous deposit + depositAmountassertTrue( amount == prevDeposits + depositAmount,"Token should be deposited" ); }}
Running Integration Tests
Executing the integration tests triggers the setUp() function before each test, ensuring the tests are always executed on a fresh state after the proposals execution.