Securing a Smart Contract with a Multisig Gnosis Safe
This tutorial demonstrates how to secure a smart contract by transfering ownership of it to a Multisig Gnosis Safe, and then interacting with that contract using Hardhat and Ethers.js.
Why does this matter? “Multisig” quite literally means that multiple signatures are required before certain actions can occur. For example, we can use this to secure particular functions of a smart contract, so that one wallet does not have complete control over ETH that it holds.
In this tutorial, we will write code..
- To transfer ownership of a smart contract to a multisig Gnosis Safe
- To call functions of the contract once it is owned by the Safe
This process is a key element of Spectra.art’s secure web3 codebase, which has been tested and refined over many successful NFT launches.
This article is Part 2 of an introductory series on deploying a smart contract to sell NFTs!
- Part 1 is here, walking through contract development, testing and deployment, and creating an OpenSea collection
- Part 3, which will cover a basic React app that mints NFTs, is still being written
All code for the series can be found in this repo: https://github.com/Barefoot-Dev/solidity-nft-boilerplate. I will be assuming you have created a hardhat project and deployed a contract before continuing with the next steps.
Transfering contract ownership to a Gnosis Safe
To do this we’ll need a couple of things:
- A smart contract that uses OpenZeppelin’s Ownable library
- A Gnosis Safe
- Some javascript
If you followed Part 1 of this series, you’ll already have inherited Ownable in your smart contract.
It should look something like this:
Next, create a new Gnosis Safe on the Rinkeby test network: https://gnosis-safe.io/app/open.
It’s fine to just add one owner for now if you want to check out the code, just make sure it’s the same wallet that your hardhat project is configured with. Any other owners you add will be able to provide signatures by interacting the Safe’s UI, but we’ll get to that later!
Once created, grab the Safe’s deployment address and put it in a config.json
file like so (indicating which network you created it on):
{
"gnosisSafeAddress": {
"rinkeby": "<DEPLOYED_SAFE_ADDRESS>"
}
}
Store this file in the top level of your hardhat project.
Now it’s time for some trusty Javascript!
First, we’ll write a helper function to get an instance of our deployed contract, and store it atscripts/utils.ts
.
Now we have the contract, we can write a script to call the contract’s transferOwnership
function. Let’s call this script transferOwnershipToGnosis.ts
(as we’ll do the opposite, transferOwnershipFromGnosis, in the next section).
We’ll also save this underscripts/
in the hardhat project. We can then call it like so:
npx hardhat run scripts/transferOwnershipToGnosis.ts --network rinkeby
After executing this, we can run the contract’s owner
function on etherscan to see that the new owner is in-fact our newly created Gnosis Safe. Note that this will only work if you have verified the contract.
https://rinkeby.etherscan.io/address/<CONTRACT_ADDRESS>#readContract
Interacting with your contract as a Safe Owner
Smart contracts developers typically restrict who can call specific smart contract functions. For example, we wouldn’t want anyone to be able to call withdrawEth
after launching our NFT collection!
OpenZeppelin’s ownable.sol
library helps us here by providing an onlyOwner
modify, which we can use to ensure a particular function can only be called by the contract owner!
Now that the owner is now a Gnosis Safe, we need a new script that calls our contract on behalf of the Safe in order to continue running such functions (e.g. to withdrawEth
directly to the Safe).
In the following script, we are focused on the proposeTransaction
function, which instructs the gnosis safe to call a specific smart contract function. Note that this must be called by one of the safe’s owners (this code assumes that the wallet Hardhat is configured for, and which we originally deployed the contract from, has been made an owner of the safe).
By proposing a transaction, you (one of the owners) will provide a signature that approves the safe to call the contract. If your safe has multiple owners, the script will sleep and periodically check if the other owners have provided their signatures before proceeding to submit the transaction to the network. (Other owners will need to login to the Safe’s UI and sign manually).
Now for the final piece!
We’ll need a modified version oftransferOwnershipToGnosis.ts
, which gets the method signature to transfer ownership from the safe and back to the deployer wallet, and calls our new proposeTransaction
function from above.
To keep things simple, we’ll call this one transferOwnershipFromGnosis.ts
.
Run it like so:
npx hardhat run scripts/transferOwnershipFromGnosis.ts --network rinkeby
This should log a number of things to your console while it runs. It will await all confirmations required (ie signatures provided by other Safe owners, if there are more than 1), then submit the transaction to the network, wait for it to execute, and finally log the transaction hash.
You can of course verify that ownership has been transfered back to the original deploying wallet:
https://rinkeby.etherscan.io/address/<CONTRACT_ADDRESS>#readContract
If you want to add more owners, simply do so in the Safe’s settings. At anytime, you can also modify the number of signatures required to approve a transaction, just don’t forget that all current signatures are required to do so!
Aaaand we’re done!!
You’ve successfully secured a smart contract by giving ownership of it to a multisig Gnosis Safe, and then run functions of the contract by requesting signatures from the Safe’s owners!
Before using a Safe in production, please read up on Gnosis and always keep up to date with security best practices. Don’t forget that if your Safe has 3 owners, and requires 3 signatures to do anything, losing one of those owner wallets mean your contract (and any ETH in it!) are lost forever.