Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add manual CI to simulate upgrades with tenderly #345

Draft
wants to merge 22 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-apples-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fuel-bridge/solidity-contracts': minor
---

add manual CI to simulate upgrades with tenderly
35 changes: 35 additions & 0 deletions .github/workflows/manual-simulate-upgrade.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Manual Simulate Upgrade

on:
workflow_dispatch:
inputs:
vnetId:
description: 'Enter the tenderly virtual testnet id'
required: true
type: string
accessKey:
description: 'Enter the tenderly account access key'
required: true
type: string
rpc:
description: 'Enter tenderly virtual network rpc'
required: true
type: string

jobs:
simulate-upgrades:
runs-on: ubuntu-latest
env:
RPC_URL: ${{ github.event.inputs.rpc }}
VNET_ID: ${{ github.event.inputs.vnetId }}
ACCESS_KEY: ${{ github.event.inputs.accessKey }}
steps:
- uses: actions/checkout@v3
- uses: FuelLabs/github-actions/setups/node@master
with:
node-version: 20.16.0
pnpm-version: 9.0.6
- name: Simulate Upgrades
run: |
npx hardhat compile && npx hardhat simulate-upgrades --network mainnet
working-directory: ./packages/solidity-contracts
1 change: 1 addition & 0 deletions packages/solidity-contracts/scripts/hardhat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export * from './withdrawalResume';
export * from './withdrawalBlacklist';
export * from './withdrawalWhitelist';
export * from './verifyMainnetDeployment';
export * from './simulateUpgrades';
export * from './eventFilter';
140 changes: 140 additions & 0 deletions packages/solidity-contracts/scripts/hardhat/simulateUpgrades.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { task } from 'hardhat/config';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { ContractFactory, ethers } from 'ethers';

const SECURITY_COUNCIL_MULTISIG = '0x32da601374b38154f05904B16F44A1911Aa6f314';
const ETH_BALANCE_TO_SET = '0xDE0B6B3A7640000'; // 1 ether
const GAS_AMOUNT = '0x7a1200';
const GAS_PRICE = '0xF4241';

task(
'simulate-upgrades',
'Mocks proxy upgrades with tenderly simulation'
).setAction(
async (taskArgs: any, hre: HardhatRuntimeEnvironment): Promise<void> => {
const network = hre.network.name;

if (network !== 'mainnet') {
return;
}

const {
upgrades: { prepareUpgrade },
ethers,
} = hre;

console.log(
`Mocking proxy upgrades on ${network}:${hre.network.config.chainId}...`
);

const signer = await ethers.provider.getSigner(0);

// fund the signer for deploying the new implementation
const setBalancePayload = {
jsonrpc: '2.0',
method: 'tenderly_setBalance',
params: [[(await signer.getAddress()).toString()], ETH_BALANCE_TO_SET],
};

const rpc: string = process.env.RPC_URL!;

const deployments = await hre.deployments.all();

const apiUrl = `https://api.tenderly.co/api/v1/account/fuel-network/project/preprod/vnets/${process.env.VNET_ID}/transactions/simulate`;
const accessKey = process.env.ACCESS_KEY;

try {
await fetch(rpc, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(setBalancePayload),
});

for (const [contractName, deployment] of Object.entries(deployments)) {
if (deployment.abi.length == 0) continue;

// mocking the deployment for the new implementation too.
// we currently assume that this script will be ran, after the `upgradeVerification` script is executed so we have access to the updated `constructorArgs`, otherwise we can allow the contructor arguments to be entered manually.
const factory = (await ethers.getContractFactory(
deployment.linkedData.factory
)) as ContractFactory; // Typing bug in `getContractFactory`

const deploymentResponse = (await prepareUpgrade(
deployment.address,
factory,
{
kind: 'uups',
constructorArgs: deployment.linkedData.constructorArgs,
getTxResponse: true,
redeployImplementation: 'always',
}
)) as ethers.TransactionResponse;

const receipt = await hre.ethers.provider.getTransactionReceipt(
deploymentResponse.hash
);

const newImplementationAddress = receipt?.contractAddress!;

const proxyContractInstance = new ethers.Contract(
deployment.address,
deployment.abi
);

// simulating upgrade enables to impersonate the security council multisig, without the need of signatures
const encodedUpgradeData =
proxyContractInstance.interface.encodeFunctionData('upgradeTo', [
newImplementationAddress,
]);

const upgradeImplementationPayload = {
callArgs: {
from: SECURITY_COUNCIL_MULTISIG,
to: deployment.address,
gas: GAS_AMOUNT,
gasPrice: GAS_PRICE,
value: '0x0',
data: encodedUpgradeData,
},
blockNumber: 'latest',
};

const response = await fetch(apiUrl, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'X-Access-Key': accessKey,
},
body: JSON.stringify(upgradeImplementationPayload),
} as any); // typing bug with the request headers

// simulations don't result in visible state changes so not checking the new implementation here, so instead we check
// the event logs that the `Upgraded` event was emitted
if (response.ok) {
const responsePayload: any = await response.json();

if (responsePayload.logs[0].name === 'Upgraded') {
console.log(
`✅ Upgrade simulation successful for ${contractName} (${deployment.address})`
);
} else {
console.log(
`❌ Upgrade simulation failed for ${contractName} (${deployment.address})`
);
throw new Error('Upgrade simulation failed');
}
} else {
console.log(
`❌ Upgrade simulation failed for ${contractName} (${deployment.address})`
);
throw new Error('Upgrade simulation failed');
}
}
} catch (error) {
console.log(`❌ Upgrade simulation failed: ${error}`);
}
}
);
Loading