• Stars
    star
    137
  • Rank 266,121 (Top 6 %)
  • Language
    Solidity
  • License
    MIT License
  • Created almost 2 years ago
  • Updated about 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Foundry tools for testing with Gnosis Safes

Gnosis Safe Tools for Foundry

SafeTestTools is a friendly wrapper for deploying safes, executing transactions, performing EIP1271 signatures, and enabling/disabling modules. It manages Safe deployments, private keys, and transaction signing so you can simply call _setupSafe() and ensure your code works with Safe's as well as EOAs.

Before -> After

Before after

Quick Start

Pre-Deployed Safe (Spoof Safe Tx's in a Fork Test)

In a fork test, you can "attach" to a deployed safe and spoof transactions.

This is great for running forge script POCs, where you need to simulate transaction execution from a Safe

import "safe-tools/SafeTestTools.sol";
import "forge-std/Test.sol";

contract Test is Test, SafeTestTools {
    using SafeTestLib for SafeInstance;

    setUp() public {
        vm.createSelectFork("RPC_HERE");

        address frax_safe = 0xB1748C79709f4Ba2Dd82834B8c82D4a505003f27;
        SafeInstance memory instance = _attachToSafe(frax_safe);

        safeInstance.execTransaction({
            to: alice,
            value: 0.5 ether,
            data: ""
        }); // send .5 eth to alice

        assertEq(alice.balance, 0.5 ether); // passes โœ…
    }
}

NOTE: Attached safes are stored with instance.instanceType == InstanceType.Live

New Safe (Deploy a Safe w/ Default Signers via SafeProxyFactory)

import "safe-tools/SafeTestTools.sol";
import "forge-std/Test.sol";

contract Test is Test, SafeTestTools {
    using SafeTestLib for SafeInstance;

    setUp() public {
        SafeInstance memory safeInstance = _setupSafe();
        address alice = address(0xA11c3);

        safeInstance.execTransaction({
            to: alice,
            value: 0.5 ether,
            data: ""
        }); // send .5 eth to alice

        assertEq(alice.balance, 0.5 ether); // passes โœ…
    }
}

Basic Setup

Use the _setupSafe(); method to setup a SafeInstance with the default initialization parameters.

SafeInstance memory safeInstance = _setupSafe();

Default Parameters:

  1. Threshold: 2/3
  2. Signers: The owners are the first 3 signers from the standard test test test test test test test test test test test junk derived accounts. These accounts are vm.label'd as SAFETEST: Signer 0-2: for Forge's call tracing functionality.
  3. Initial Balance: 10000 ether
  4. Salt Nonce: 0xbff0e1d6be3df3bedf05c892f554fbea3c6ca2bb9d224bc3f3d3fbc3ec267d1c

This will create a SafeInstance with the address of 0x584a697DC2b125117d232Fca046f6cDe5Edd0ba7

(See Custom Setup for more setup options)

The Safe Instance Struct:

struct SafeInstance {
    InstanceType instanceType, // either InstanceType.Test | InstanceType.Live
    uint256 instanceId;
    uint256[] ownerPKs;
    address[] owners;
    uint256 threshold;
    DeployedSafe safe;
}

A safe instance stores:

  1. instanceId: a unique id
  2. ownerPKs: an array of owner private keys (NOTE! these PKs will be sorted by computed address for signing purposes)
  3. owners: an array of owner addresses (sorted to match the private keys)
  4. threshold: the signing threshold of the safe
  5. safe: the address of the deployed safe wrapped in a custom interface DeployedSafe that includes all:
    • GnosisSafe.sol methods
    • CompatibilityFallbackHandler.sol methods (for EIP1271 signature validation, messaging hashing, token callbacks, etc)

SafeInstance Methods

Wrap the SafeInstance with SafeTestLib methods to add access wrappers for signing methods for common Safe methods.

using SafeTestLib for SafeInstance;

API

// EXEC FUNCTION VARIATIONS
function execTransaction(
    address to,
    uint256 value,
    bytes data
) public returns (bool);

function execTransaction(
    address to,
    uint256 value,
    bytes data,
    Enum.Operation operation
) public returns (bool);

function execTransaction(
    address to,
    uint256 value,
    bytes data,
    Enum.Operation operation,
    uint256 safeTxGas,
    uint256 baseGas,
    uint256 gasPrice,
    address gasToken,
    address refundReceiver,
    bytes memory signatures
) public returns (bool);

// MODULE FUNCTIONS

function enableModule(address module);

function disableModule(address module);

// MISC

function EIP1271Sign(bytes data);

function EIP1271Sign(bytes32 digest);

function incrementNonce() public returns (uint256 newNonce);

function signTransaction(
    uint256 privateKey,
    address to,
    uint256 value,
    bytes memory data,
    Enum.Operation operation,
    uint256 safeTxGas,
    uint256 baseGas,
    uint256 gasPrice,
    address gasToken,
    address refundReceiver
) public view returns (uint8 v, bytes32 r, bytes32 s)

Custom Setup

Setup options:

Then there are a few overrides of _setupSafe() at your disposal for custom Safe setup:

// pass an array of uint256 private keys
function _setupSafe(
    uint256[] memory ownerPKs,
    uint256 threshold
) public returns (SafeInstance memory);

// you could also specify the initial balance of the Safe
function _setupSafe(
    uint256[] memory ownerPKs,
    uint256 threshold,
    uint256 initialBalance
) public returns (SafeInstance memory);

// or if you need to fully tweak the Safe setup parameters, you can pass an `AdvancedSafeInitParams` struct
function _setupSafe(
    uint256[] memory ownerPKs,
    uint256 threshold,
    uint256 initialBalance,
    AdvancedSafeInitParams memory advancedParams
) public returns (SafeInstance memory)

AdvancedSafeInitParams

Passing the AdvancedSafeInitParams struct allows you to fully customize the Safe setup call parameters. The struct is defined as follows:

struct AdvancedSafeInitParams {
    bool includeFallbackHandler;
    uint256 saltNonce;
    address setupModulesCall_to;
    bytes setupModulesCall_data;
    uint256 refundAmount;
    address refundToken;
    address payable refundReceiver;
    bytes initData;
}
Param Type Description
includeFallbackHandler bool Whether or not to include the CompatibilityFallbackHandler contract in the Safe setup. The fallbackHandler receives calls to the Safe with unrecognized signatures. This contains EIP1271 signature validation, allows the Safe to receive EIP712, 1155, and 777 tokens, and includes fallbacks for previous Safe versions.
saltNonce uint256 The salt nonce to use when deploying the Safe. Passing saltNonce > 0 will call createProxyWithNonce() method on the SafeFactory. createProxy() will be called otherwise.
setupModulesCall_to address An address that receives a delegateCall with setupModulesCall_data as part of the setupModules() call during Safe setup. This is useful for setting up modules during initialization.
setupModulesCall_data bytes The delegateCall data for the setupModulesCall_to call. See above.
refundAmount uint256 The amount of refundToken to send to refundReceiver after Safe setup.
refundToken address The address of the token to refund. NOTE: address(0) indicates native token. If refundAmount > 0, a deployment refund will initiate.
refundReceiver address payable The address to receive the refundAmount of refundToken. NOTE: address(0) indicates tx.origin and will doesn't make senes for Foundry.
initData bytes When creating a safe from Safe UI, the data param in the Factory call includes the setup() transaction. A setup transaction is just the abi.encoded call to setup on the Safe contract after the factory deploys the SafeProxy (see how I do this behind the scenes). If you wish to implement a custom Safe setup() call, you can override advancedInitParams.initData with your own bytes string. NOTE: overriding the initData will override the following above params by default setupModulesCall_to, setupModulesCall_data, includeFallbackHandler, refundToken, refundAmount, refundReceiver

License

License MIT ยฉ Colin Nielsen

More Repositories

1

ecrecover-noir

A Noir circuit that mimics Solidity's ecrecover
Roff
43
star
2

dark-safe

under construction ๐Ÿšง
Solidity
30
star
3

DanielWolbergPortfolio

๐Ÿ“ฮป This is my Galvanize capstone project. It was made in ThreeJS and Vue. It's a 3D portfolio for an architectural student to display his 3D models. It's also a beautiful 3D experience!
Vue
24
star
4

noir-u2b

u(ints) -> b(yte arrays)
Roff
8
star
5

proxy-deep-dive

Repo with useful resources for the Solidity Deep Dives event
Yul
6
star
6

noir-array-helpers

A lib of array manipulation functions
Roff
5
star
7

BurberryIndigo

Burberry Indigo ๐Ÿ‡ฌ๐Ÿ‡งโ˜•๏ธ - a creative product showcase made in reactjs
JavaScript
4
star
8

SNARK-hash-benchmark

๐Ÿƒ๐Ÿฝโ€โ™‚๏ธ๐Ÿ’จ Benchmarks for popular hashes in the Circom/ZK-SNARK ecosystem
Shell
3
star
9

GeoSaver

๐ŸŒ GeoSaver is a photo cataloguing app that displays your photos based on where you took them. Save all those secret photography spots ๐Ÿ‘€
JavaScript
3
star
10

votrbackend

๐Ÿฅž backend server made by @djmeans and myself. Has auth functionality and crud functionality for user + userdata tables.
JavaScript
3
star
11

noir-verify-ecdsa_secp256k1

A playground for verifying ecdsa sigs in noir
TypeScript
3
star
12

ZK-Merkle-tree

๐ŸŒฒ๐Ÿคซ๐ŸŒฒ Store a Merkle root on-chain and verify a Merkle proof's authenticity without revealing the proof inputs ๐ŸŒฒ๐Ÿ•ต๐Ÿผโ€โ™€๏ธ๐ŸŒฒ
Solidity
2
star
13

Contractooor

TypeScript
1
star
14

animate-it-

๐Ÿ
HTML
1
star
15

YourStyleRehabist

๐Ÿ‘—๐Ÿ‘  landing page for YourStyleRehabist.com. Built with Gatsby + styledcomponents. Deployed on Netlify.
JavaScript
1
star
16

safe-talk

Solidity
1
star
17

GSAPGlobalAnimationController

๐Ÿธ๐Ÿ’ƒ๐ŸฝA conceptual project attempting to use GSAP and Gatsby with hooks and context as a global animation controller that actions and controls the animations of components from a global state instead of individually within the component.
JavaScript
1
star
18

spotify-clone

JavaScript
1
star