Automate your security operations with safe-utils and tenderly-utils
Introducing safe-utils and tenderly-utils: Propose Safe transactions, simulate them with Tenderly, and run the entire flow through a reproducible and auditable Foundry script
Governance proposals done right
Join us for a deep dive into automating protocol governance on Wednesday 30 at 16:00 UTC: https://discord.com/events/1199312177727799336/1364279534425211031. In this session, we will explore how safe-utils and tenderly-utils can help streamline multisig transaction proposals and simulations. If your DAO or protocol is constantly pushing updates to production, tune in to learn more about how you can make it more secure.
When building upgradeable or configurable protocols, a common setup involves having the governance appointed to a Safe smart account, possibly behind a timelock or a Governor contract.
On the one hand, having multiple signers review configuration or protocol updates updates reduces risk by enabling more sets of eyes to go over proposed changes. On the other hand, most protocols still rely on the Safe UI to propose these transactions, which can be cumbersome for complex updates, and hard to review by trusted parties.
To mitigate these shortcomings, we’re introducing two Foundry modules: safe-utils and tenderly-utils, which can help streamline this process with minimal setup. These libraries can be used independently or together, and in this article we propose a framework to improve your protocol's governance operations.
safe-utils
The safe-utils
library enables proposing multisig transactions to the Safe API from within Foundry scripts.
Installation
forge install Recon-Fuzz/safe-utils
Usage
import {Safe} from "safe-utils/Safe.sol";
using Safe for *;
Safe.Client safe;
function setUp() public {
safe.initialize(vm.envAddress("SAFE_ADDRESS"));
}
Propose a transaction
safe.proposeTransaction(
target,
abi.encodeCall(Contract.functionName, (args)),
signer
);
The proposal is signed and posted to the Safe API backend to later be approved by the other Safe owners. By having the proposal implemented as a Foundry script, security researchers can then help review, find potential issues and mitigate findings.
tenderly-utils
The tenderly-utils
library provides an interface to the Tenderly API to automate simulations and Virtual TestNet creations. This can be used in conjunction with the safe-utils
module in order to make proposals instantly applicable on a fork network. This is useful so that frontend and backend teams can instantly test governance updates before they take effect.
Installation
forge install Recon-Fuzz/tenderly-utils
Usage
import {Tenderly} from "tenderly-utils/Tenderly.sol";
using Tenderly for *;
Tenderly.Client tenderly;
function setUp() public {
tenderly.initialize(
vm.envString("TENDERLY_ACCOUNT_NAME"),
vm.envString("TENDERLY_PROJECT_NAME"),
vm.envString("TENDERLY_ACCESS_KEY")
);
}
Simulate a transaction on a fork
Tenderly.VirtualTestnet memory vnet =
tenderly.createVirtualTestnet("vnet", block.chainid);
Tenderly.Transaction memory transaction = tenderly.sendTransaction(
vnet.id, from, weth, abi.encodeCall(IWETH.withdraw.selector, (amount))
);
The Virtual TestNet is a chain fork that you can share with your team, reflecting the simulated state. For multisig proposals, this is very useful, as you can also use cheatcodes such as setStorageAt
to change the Safe threshold to 1 and instantly make the governance proposal pass. This is exactly what happens through the Safe UI when you click “Simulate transaction”.
End to End setup
At Size Credit, a fixed-rate lending marketplace with unified liquidity, these libraries are used in the following way:
Deployments
In the past, new money markets were deployed via an admin frontend. This was hard to maintain, as it required frequent security updates to minimize user input mistakes, which were quite frequent. Now, deployments use Foundry scripts, which are easier to review and extend. It is possible to clone existing configurations by fetching the market registry and only updating a few parameters such as the collateral/debt tokens and the price feed, thus reducing the risk of human errors.
Under the hood, the proposeTransaction function signs the operation and submits an HTTP request to the Safe API Kit backend with the Safe execTransaction method parameters.
Updates
Protocol updates usually involve submitting a batch transaction to multiple addresses. Previously, this was done through the “Batch Transaction” tool from the Safe frontend. This is extremely error prone and time consuming, since it has a lot of manual steps: copying the contract address, copying the ABI, copying the update parameter, making sure they are properly formatted, then doing it over again for all necessary changes. With safe-utils
this can be constructed programmatically, preventing human errors such as forgetting to update one market, submitting updates to invalid addresses, and others, all of which have already happened in production.
Implementation-wise, the proposeTransactions (plural, as it can be used to pass multiple sub-calls) helper function will use the same MultiSendCallOnly batch aggregator contract as the Safe SDK under the hood. The benefit of using the safe-utils library over manually calling MultiSendCallOnly
is that it provides a much simpler interface, since developers do not need to manually construct the multiSend
argument, as it has a very specific encoding. One important low-level detail is that the execTransaction
operation
parameter must be made through a DelegateCall
, to ensure that the transaction context (including msg.sender
) remains the Safe account throughout all sub-calls in the transaction chain.
Simulations
Before deploying a new money market, the protocol needs to conduct multiple analyses to determine risk parameters such as the collateral ratio and liquidation bonus. Executing real liquidations with different configurations can help provide a more thorough understanding of the price impact on DEXes and of how liquidity is impacted by price changes.
Tenderly Virtual TestNets, created with the help of tenderly-utils
, can be a helpful tool to simulate a borrow, a price drop, and a liquidation event. The liquidation engine can then use flash loans to repay the borrow and assess the overall output on this fork network, and governance parameters are subsequently adjusted based on the findings.
Honorary mention: solidity-http
As you can imagine, these API triggers are glorified HTTP calls under the hood, properly encoded with Foundry's vm.serialize*
and decoded with vm.parseJson
calls. To implement these requests, a lightweight solidity-http client library was developed, inspired by axios to provide easy access to curl
through ffi
.
Conclusion
By combining safe-utils
and tenderly-utils
, we propose a framework to script the full Safe transaction lifecycle:
Encode batched or single transactions
Propose transactions to the Safe API
Simulate execution via Tenderly forks with threshold override
Share Virtual TestNets with your team
This setup is ready for DAO workflows, protocol governance, and multisig operations. You can reach out to Recon if you are interested in a trusted partner to review your governance operations.