Skip to content

Commit 9fc31ff

Browse files
authored
EVM: Support Safe upgrades (#78)
1 parent f5966ba commit 9fc31ff

5 files changed

Lines changed: 264 additions & 23 deletions

File tree

packages/evm/.env.sample

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ SOLVER=0xE0D76433Edd9f5df370561bd0AF231E72c83Cd3a
44
VALIDATOR=0xc76B16fA2Fa75D93e08099DC16413D9a083404A1
55

66
SETTLER_PROXY=
7+
SAFE=
8+
SAFE_API_KEY=
79

810
ETHERSCAN_KEY=
911
DEPLOYER_PRIVATE_KEY=

packages/evm/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
"@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.0-next.23",
3030
"@nomicfoundation/hardhat-typechain": "^3.0.0-next.23",
3131
"@nomicfoundation/hardhat-verify": "^3.0.0-next.23",
32+
"@safe-global/api-kit": "^4.0.0",
33+
"@safe-global/protocol-kit": "^6.0.0",
3234
"@types/chai": "^4.3.20",
3335
"@types/chai-as-promised": "^8.0.2",
3436
"@types/mocha": "^10.0.10",

packages/evm/scripts/deploy-contracts.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ async function main(): Promise<void> {
1515
if (!process.env.ADMIN) throw Error('ADMIN env variable not provided')
1616
if (!process.env.SOLVER) throw Error('SOLVER env variable not provided')
1717
if (!process.env.VALIDATOR) throw Error('VALIDATOR env variable not provided')
18-
const { ADMIN, SOLVER, AXIA, VALIDATOR } = process.env
18+
if (!process.env.SAFE) throw Error('SAFE env variable not provided')
19+
const { ADMIN, SOLVER, AXIA, VALIDATOR, SAFE } = process.env
1920

2021
const controllerArgs = [ADMIN, [SOLVER], [], [AXIA], [VALIDATOR], MIN_VALIDATORS]
2122
const controller = await deployCreate3(ControllerArtifact, controllerArgs, '0x17')
@@ -25,12 +26,16 @@ async function main(): Promise<void> {
2526

2627
const initializeData = new Interface(SettlerArtifact.abi).encodeFunctionData('initialize', [
2728
controller.target,
28-
ADMIN,
29+
ADMIN, // settler owner
2930
dynamicCallEncoder.target,
3031
])
3132
const settlerProxy = await deployCreate3(
3233
ProxyArtifact,
33-
[settlerImplementation.target, ADMIN, initializeData],
34+
[
35+
settlerImplementation.target,
36+
SAFE, // proxy owner
37+
initializeData,
38+
],
3439
'0x04302603'
3540
)
3641

packages/evm/scripts/upgrade-settler.ts

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { HardhatEthers, HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/types'
2-
import { Contract, getAddress } from 'ethers'
2+
import SafeApiKit from '@safe-global/api-kit'
3+
import Safe from '@safe-global/protocol-kit'
4+
import { MetaTransactionData as Transaction } from '@safe-global/types-kit'
5+
import { Contract, Eip1193Provider, getAddress } from 'ethers'
36
import { network } from 'hardhat'
47

58
import ProxyAdminArtifact from '../artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'
@@ -9,22 +12,63 @@ import { deployCreate3 } from './deploy-create3'
912
const ERC1967_ADMIN_SLOT = '0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103'
1013

1114
async function main(): Promise<void> {
12-
const { ethers } = await network.connect()
15+
const { ethers, networkConfig, provider } = await network.connect()
1316
const [signer] = await ethers.getSigners()
1417

1518
if (!process.env.SETTLER_PROXY) throw Error('SETTLER_PROXY env variable not provided')
1619
const proxy = getAddress(process.env.SETTLER_PROXY)
1720

1821
const proxyAdmin = await getProxyAdmin(ethers, proxy, signer)
1922
const proxyAdminOwner = await proxyAdmin.owner()
20-
if (proxyAdminOwner !== signer.address) {
21-
throw Error(`Signer ${signer.address} is not the ProxyAdmin owner ${proxyAdminOwner}`)
23+
24+
const safeAddress = process.env.SAFE ? getAddress(process.env.SAFE) : undefined
25+
const expectedOwner = safeAddress ?? signer.address
26+
27+
if (proxyAdminOwner !== expectedOwner) {
28+
throw Error(`Expected owner ${expectedOwner} does not match ProxyAdmin owner ${proxyAdminOwner}`)
2229
}
2330

2431
const implementation = await deployCreate3(SettlerArtifact, [], '0x04302605', 'V1')
25-
const tx = await proxyAdmin.upgradeAndCall(proxy, implementation.target, '0x')
26-
await tx.wait()
27-
console.log(`✅ Settler ${proxy} upgraded in tx ${tx.hash}`)
32+
33+
if (safeAddress) {
34+
const { chainId } = await ethers.provider.getNetwork()
35+
if (networkConfig.type !== 'http') throw Error('Safe proposal requires an HTTP network')
36+
const to = await proxyAdmin.getAddress()
37+
const data = proxyAdmin.interface.encodeFunctionData('upgradeAndCall', [proxy, implementation.target, '0x'])
38+
const transactions = [{ to, value: '0', data }]
39+
await proposeSafeTransaction(safeAddress, transactions, chainId, signer, provider)
40+
} else {
41+
const tx = await proxyAdmin.upgradeAndCall(proxy, implementation.target, '0x')
42+
await tx.wait()
43+
console.log(`✅ Settler ${proxy} upgraded in tx ${tx.hash}`)
44+
}
45+
}
46+
47+
async function proposeSafeTransaction(
48+
safeAddress: string,
49+
transactions: Transaction[],
50+
chainId: bigint,
51+
signer: HardhatEthersSigner,
52+
provider: Eip1193Provider
53+
): Promise<void> {
54+
if (!process.env.SAFE_API_KEY) throw Error('SAFE_API_KEY env variable required for Safe proposal')
55+
const apiKit = new SafeApiKit({ chainId, apiKey: process.env.SAFE_API_KEY })
56+
57+
const safe = await Safe.init({ safeAddress, signer: signer.address, provider })
58+
const safeTx = await safe.createTransaction({ transactions })
59+
const safeTxHash = await safe.getTransactionHash(safeTx)
60+
const senderSignature = await safe.signHash(safeTxHash)
61+
62+
await apiKit.proposeTransaction({
63+
safeAddress,
64+
safeTransactionData: safeTx.data,
65+
safeTxHash,
66+
senderAddress: signer.address,
67+
senderSignature: senderSignature.data,
68+
})
69+
70+
console.log(`✅ Upgrade proposed to Safe ${safeAddress}`)
71+
console.log(` Safe TX Hash: ${safeTxHash}`)
2872
}
2973

3074
async function getProxyAdmin(ethers: HardhatEthers, proxy: string, signer: HardhatEthersSigner): Promise<Contract> {

0 commit comments

Comments
 (0)