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: Configure Fast Withdrawals using Safe #177

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ You need to set the following environment variables in an .env file:
```bash
yarn dev
```

57 changes: 57 additions & 0 deletions examples/setup-fast-withdrawal-multisig/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#step 1
#signer of the Safe multisignature contract that can propose a transaction
OWNER_1_ADDRESS_PRIVATE_KEY=
#parent chain id - 1 etheruem, 42161 arbitrum one
PARENT_CHAIN_ID=
#Safe multisignature contract address
SAFE_ADDRESS=
#stakers and validators that are whitelisted on the Rollup contract
FC_VALIDATORS='["0x1234567890123456789012345678901234567890"]'

#step 2
#Rollup contract address
ROLLUP_ADDRESS=
#Safe address that was created on step 1
FC_VALIDATORS_SAFE_ADDRESS=
#RPC url
RPC=
#signer of the Safe multisignature contract that can propose a transaction
OWNER_1_ADDRESS_PRIVATE_KEY=
#parent chain id - 1 etheruem, 42161 arbitrum one
PARENT_CHAIN_ID=
#Safe multisignature contract address
SAFE_ADDRESS=
#stakers and validators that are whitelisted on the Rollup contract
FC_VALIDATORS='["0x1234567890123456789012345678901234567890"]'

#step 3
#Rollup contract address
ROLLUP_ADDRESS=
#add Safe address that was created on step 1 as fast-confirmer (setAnyTrustFastConfirmerPrepareTransactionRequest - fastConfirmer: safeAddress)
FC_VALIDATORS_SAFE_ADDRESS=
#RPC url
RPC=
#signer of the Safe multisignature contract that can propose a transaction
OWNER_1_ADDRESS_PRIVATE_KEY=
#parent chain id - 1 etheruem, 42161 arbitrum one
PARENT_CHAIN_ID=
#Safe multisignature contract address
SAFE_ADDRESS=
#stakers and validators that are whitelisted on the Rollup contract
FC_VALIDATORS='["0x1234567890123456789012345678901234567890"]'

#step 4
#Rollup contract address
ROLLUP_ADDRESS=
#Safe address that was created on step 1
FC_VALIDATORS_SAFE_ADDRESS=
#RPC url
RPC=
#signer of the Safe multisignature contract that can propose a transaction
OWNER_1_ADDRESS_PRIVATE_KEY=
#parent chain id - 1 etheruem, 42161 arbitrum one
PARENT_CHAIN_ID=
#Safe multisignature contract address
SAFE_ADDRESS=
#default is 75 (15minutes)
MINIMUM_ASSERTION_PERIOD=1
79 changes: 79 additions & 0 deletions examples/setup-fast-withdrawal-multisig/1-create_multisig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { createPublicClient, http, isAddress } from 'viem';
import {
createSafePrepareTransactionRequest,
} from '@arbitrum/orbit-sdk';
import { getParentChainFromId } from '@arbitrum/orbit-sdk/utils';
import { config } from 'dotenv';
import { propose } from './common.js';

config();

//check environment variables
if (typeof process.env.OWNER_1_ADDRESS_PRIVATE_KEY === 'undefined') {
throw new Error(`Please provide the "OWNER_1_ADDRESS_PRIVATE_KEY" environment variable`);
}

if (typeof process.env.PARENT_CHAIN_ID === 'undefined') {
throw new Error(`Please provide the "PARENT_CHAIN_ID" environment variable`);
}

if (typeof process.env.SAFE_ADDRESS === 'undefined') {
throw new Error(`Please provide the "SAFE_ADDRESS" environment variable`);
}

if (typeof process.env.FC_VALIDATORS === 'undefined') {
throw new Error(`Please provide the "FC_VALIDATORS" environment variable`);
}

const rollupOwnerSafeAddress = process.env.SAFE_ADDRESS as `0x${string}`;
// // set the parent chain and create a public client for it
const parentChainId = Number(process.env.PARENT_CHAIN_ID);
const parentChain = getParentChainFromId(parentChainId);
const parentChainPublicClient = createPublicClient({
chain: parentChain,
transport: http(process.env.RPC),
});
// sanitize validator addresses
const fcValidators = JSON.parse(process.env.FC_VALIDATORS);
const safeWalletThreshold = fcValidators.length;
if (!fcValidators) {
throw new Error(`The "FC_VALIDATORS" environment variable must be a valid array`);
}

const sanitizedFcValidators = [
...new Set(
fcValidators.filter((validator: `0x${string}`) =>
isAddress(validator) ? validator : undefined,
),
),
];
if (sanitizedFcValidators.length !== safeWalletThreshold) {
throw new Error(
`Some of the addresses in the "FC_VALIDATORS" environment variable appear to not be valid or duplicated.`,
);
}

async function main() {
//
// Step 1. Create Safe multisig
//
console.log(
`Step 1: Create a new ${safeWalletThreshold}/${safeWalletThreshold} Safe wallet with the following addresses as signers:`,
fcValidators,
);
console.log('---');
const txRequest = await createSafePrepareTransactionRequest({
publicClient: parentChainPublicClient,
account: rollupOwnerSafeAddress,
owners: fcValidators,
threshold: safeWalletThreshold,
saltNonce: BigInt(Date.now())
});
propose(txRequest.to as string, txRequest.data as string, rollupOwnerSafeAddress);
//execute the transaction
//https://help.safe.global/en/articles/40834-verify-safe-creation
//in the executed transaction find `ProxyCreation` event
//Data proxy : <address> is what you're looing for
}

main();
117 changes: 117 additions & 0 deletions examples/setup-fast-withdrawal-multisig/2-add_validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { createPublicClient, http, isAddress, Address } from 'viem';
import {
createRollupFetchTransactionHash,
createRollupPrepareTransactionReceipt,
rollupAdminLogicPublicActions,
} from '@arbitrum/orbit-sdk';
import { getParentChainFromId } from '@arbitrum/orbit-sdk/utils';
import { config } from 'dotenv';
import { propose } from './common.js';


config();

//check environment variables
if (typeof process.env.OWNER_1_ADDRESS_PRIVATE_KEY === 'undefined') {
throw new Error(`Please provide the "OWNER_1_ADDRESS_PRIVATE_KEY" environment variable`);
}

if (typeof process.env.PARENT_CHAIN_ID === 'undefined') {
throw new Error(`Please provide the "PARENT_CHAIN_ID" environment variable`);
}

if (typeof process.env.SAFE_ADDRESS === 'undefined') {
throw new Error(`Please provide the "SAFE_ADDRESS" environment variable`);
}

if (typeof process.env.FC_VALIDATORS_SAFE_ADDRESS === 'undefined') {
throw new Error(`Please provide the "FC_VALIDATORS_SAFE_ADDRESS" environment variable (run step 1)`);
}

if (typeof process.env.FC_VALIDATORS === 'undefined') {
throw new Error(`Please provide the "FC_VALIDATORS" environment variable`);
}

if (typeof process.env.ROLLUP_ADDRESS === 'undefined') {
throw new Error(`Please provide the "ROLLUP_ADDRESS" environment variable`);
}

if (typeof process.env.RPC === 'undefined') {
throw new Error(`Please provide an "RPC" endpoint with unlimited eth_getLogs range`);
}

const rollupOwnerSafeAddress = process.env.SAFE_ADDRESS as `0x${string}`;
const safeAddress = process.env.FC_VALIDATORS_SAFE_ADDRESS as `0x${string}`;
const rollupAddress = process.env.ROLLUP_ADDRESS as Address;
// // set the parent chain and create a public client for it
const parentChainId = Number(process.env.PARENT_CHAIN_ID);
const parentChain = getParentChainFromId(parentChainId);
const parentChainPublicClient = createPublicClient({
chain: parentChain,
transport: http(process.env.RPC),
}).extend(
rollupAdminLogicPublicActions({
rollup: rollupAddress,
}),
);

// sanitize validator addresses
const fcValidators = JSON.parse(process.env.FC_VALIDATORS);
const safeWalletThreshold = fcValidators.length;
if (!fcValidators) {
throw new Error(`The "FC_VALIDATORS" environment variable must be a valid array`);
}

const sanitizedFcValidators = [
...new Set(
fcValidators.filter((validator: `0x${string}`) =>
isAddress(validator) ? validator : undefined,
),
),
];
if (sanitizedFcValidators.length !== safeWalletThreshold) {
throw new Error(
`Some of the addresses in the "FC_VALIDATORS" environment variable appear to not be valid or duplicated.`,
);
}

async function main() {
console.log('Add the new Safe address (created in step 1) as a validator');
fcValidators.push(safeAddress);

console.log('Gather necessary data (UpgradeExecutor address)');
const transactionHash = await createRollupFetchTransactionHash({
rollup: rollupAddress,
publicClient: parentChainPublicClient,
});
const transactionReceipt = createRollupPrepareTransactionReceipt(
await parentChainPublicClient.getTransactionReceipt({ hash: transactionHash }),
);
const coreContracts = transactionReceipt.getCoreContracts();
const upgradeExecutorAddress = coreContracts.upgradeExecutor;

//
// Step 2. Add validators to the Orbit chain rollup validator whitelist
//
console.log(
`Step 2: Adding the following validators to the Rollup validator whitelist:`,
fcValidators,
);
console.log('---');

// prepare set validator transaction request
const fcValidatorsStatus = Array(fcValidators.length).fill(true);
const setValidatorTransactionRequest =
await parentChainPublicClient.rollupAdminLogicPrepareTransactionRequest({
functionName: 'setValidator',
args: [
fcValidators, // validator address list
fcValidatorsStatus, // validator status list
],
upgradeExecutor: upgradeExecutorAddress,
account: rollupOwnerSafeAddress,
});
propose(setValidatorTransactionRequest.to as string, setValidatorTransactionRequest.data as string, rollupOwnerSafeAddress);
}

main();
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { createPublicClient, http, Address, parseAbi } from 'viem';
import {
createRollupFetchTransactionHash,
createRollupPrepareTransactionReceipt,
rollupAdminLogicPublicActions,
setAnyTrustFastConfirmerPrepareTransactionRequest,
} from '@arbitrum/orbit-sdk';
import { getParentChainFromId } from '@arbitrum/orbit-sdk/utils';
import { config } from 'dotenv';
import { propose } from './common.js';


config();

//check environment variables
if (typeof process.env.OWNER_1_ADDRESS_PRIVATE_KEY === 'undefined') {
throw new Error(`Please provide the "OWNER_1_ADDRESS_PRIVATE_KEY" environment variable`);
}

if (typeof process.env.PARENT_CHAIN_ID === 'undefined') {
throw new Error(`Please provide the "PARENT_CHAIN_ID" environment variable`);
}

if (typeof process.env.SAFE_ADDRESS === 'undefined') {
throw new Error(`Please provide the "SAFE_ADDRESS" environment variable`);
}

if (typeof process.env.FC_VALIDATORS_SAFE_ADDRESS === 'undefined') {
throw new Error(`Please provide the "FC_VALIDATORS_SAFE_ADDRESS" environment variable (run step 1)`);
}

if (typeof process.env.ROLLUP_ADDRESS === 'undefined') {
throw new Error(`Please provide the "ROLLUP_ADDRESS" environment variable`);
}

if (typeof process.env.RPC === 'undefined') {
throw new Error(`Please provide an "RPC" endpoint with unlimited eth_getLogs range`);
}

const rollupOwnerSafeAddress = process.env.SAFE_ADDRESS as `0x${string}`;
const safeAddress = process.env.FC_VALIDATORS_SAFE_ADDRESS as `0x${string}`;
const rollupAddress = process.env.ROLLUP_ADDRESS as Address;
// // set the parent chain and create a public client for it
const parentChainId = Number(process.env.PARENT_CHAIN_ID);
const parentChain = getParentChainFromId(parentChainId);
const parentChainPublicClient = createPublicClient({
chain: parentChain,
transport: http(process.env.RPC),
}).extend(
rollupAdminLogicPublicActions({
rollup: rollupAddress,
}),
);

async function main() {
const currentAnyTrustFastConfirmer = await parentChainPublicClient.readContract({
address: rollupAddress,
abi: parseAbi(['function anyTrustFastConfirmer() view returns (address)']),
functionName: 'anyTrustFastConfirmer',
});

if (currentAnyTrustFastConfirmer.toLowerCase() !== safeAddress.toLowerCase()) {
console.log('Gather necessary data (UpgradeExecutor address)');
const transactionHash = await createRollupFetchTransactionHash({
rollup: rollupAddress,
publicClient: parentChainPublicClient,
});
const transactionReceipt = createRollupPrepareTransactionReceipt(
await parentChainPublicClient.getTransactionReceipt({ hash: transactionHash }),
);
const coreContracts = transactionReceipt.getCoreContracts();
const upgradeExecutorAddress = coreContracts.upgradeExecutor;
const setAnyTrustFastConfirmerTransactionRequest =
await setAnyTrustFastConfirmerPrepareTransactionRequest({
publicClient: parentChainPublicClient,
account: rollupOwnerSafeAddress,
rollup: rollupAddress,
upgradeExecutor: upgradeExecutorAddress,
fastConfirmer: safeAddress,
});
propose(setAnyTrustFastConfirmerTransactionRequest.to as string, setAnyTrustFastConfirmerTransactionRequest.data as string, rollupOwnerSafeAddress);

} else {
console.log(
`AnyTrust fast confirmer is already configured to ${currentAnyTrustFastConfirmer}. Skipping.`,
);
}
}

main();
Loading