mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-11 15:08:09 -05:00
Compare commits
7 Commits
feat/debug
...
feat-gov-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487f4f2af4 | ||
|
|
0a57747085 | ||
|
|
e59a1d4fba | ||
|
|
17bbb929b7 | ||
|
|
c95e0c1782 | ||
|
|
091da32936 | ||
|
|
6155612eec |
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -13,3 +13,6 @@
|
||||
[submodule "contracts/lib/solmate"]
|
||||
path = contracts/lib/solmate
|
||||
url = https://github.com/rari-capital/solmate
|
||||
[submodule "contracts/lib/safe-contracts"]
|
||||
path = contracts/lib/safe-contracts
|
||||
url = https://github.com/safe-global/safe-contracts
|
||||
|
||||
3
contracts/admin/.gitignore
vendored
Normal file
3
contracts/admin/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
dist
|
||||
|
||||
|
||||
23
contracts/admin/README.md
Normal file
23
contracts/admin/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# admin cli
|
||||
|
||||
WIP
|
||||
|
||||
provides commands to generate calldata to then paste into `cast sign` or similar tools. No cast sign raw tx exists, and want to give users ability to
|
||||
chose what method they sign with, so prefer not signing the tx in this cli tool.
|
||||
|
||||
example (hypothetical) usage:
|
||||
|
||||
- npm link
|
||||
- admin-cli approveHash --network testnet --domain L1 --targetAddress 0x0 --targetCalldata 0x0
|
||||
|
||||
{
|
||||
to: 0x1234,
|
||||
data: 0x1234,
|
||||
functionSig: "approveHash(bytes32)"
|
||||
}
|
||||
|
||||
Flow:
|
||||
|
||||
- first, approve desired transaction (schedules transaction in Timelock) in SAFE with approveHash()
|
||||
- second, someone collects all the signers and sends executeTransaction()
|
||||
- third, someone calls execute() on the Timelock. this actually sends the transaction throught the forwarder and executes the call
|
||||
11
contracts/admin/abis.sh
Executable file
11
contracts/admin/abis.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -ue
|
||||
|
||||
# This script is used to generate the typechain artifacts for the contracts
|
||||
|
||||
mkdir -p abis types
|
||||
cat ../artifacts/src/Safe.sol/Safe.json | jq .abi >> abis/safe.json
|
||||
cat ../artifacts/src/TimelockController.sol/TimelockController.json | jq .abi >> abis/timelock.json
|
||||
cat ../artifacts/src/Forwarder.sol/Forwarder.json | jq .abi >> abis/forwarder.json
|
||||
|
||||
npx typechain --target=ethers-v6 "abis/*.json"
|
||||
2
contracts/admin/bin/index.js
Executable file
2
contracts/admin/bin/index.js
Executable file
@@ -0,0 +1,2 @@
|
||||
#!/usr/bin/env node
|
||||
require("../dist/cli.js");
|
||||
57
contracts/admin/cli.ts
Normal file
57
contracts/admin/cli.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import yargs from "yargs";
|
||||
import { ethers } from "ethers";
|
||||
import { DomainDeployment, getConfig } from "./config";
|
||||
import { approveHash } from "./tx";
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
yargs
|
||||
.command(
|
||||
"approveHash",
|
||||
"approve transaction hash in SAFE",
|
||||
(yargs) =>
|
||||
yargs
|
||||
.options({
|
||||
network: {
|
||||
alias: "n",
|
||||
describe: "name of network config to use, eg: {mainnet | goerli | testnet}",
|
||||
string: true,
|
||||
},
|
||||
domain: {
|
||||
describe: "L1 or L2",
|
||||
string: true,
|
||||
coerce: (arg) => arg.toUpperCase(),
|
||||
},
|
||||
targetAddress: {
|
||||
describe: "address of contract to call",
|
||||
string: true,
|
||||
},
|
||||
targetCalldata: {
|
||||
describe: "calldata to send to contract",
|
||||
string: true,
|
||||
},
|
||||
})
|
||||
.check((argv) => {
|
||||
if (!(argv.targetAddress && argv.targetCalldata) && !(argv.network && argv.domain)) {
|
||||
throw new Error("Must provide network, domain, targetAddress and targetCalldata");
|
||||
}
|
||||
return true; // If no error was thrown, validation passed and you can return true
|
||||
}),
|
||||
async (argv) => {
|
||||
// todo: validate
|
||||
const targetAddress = ethers.getAddress(argv.targetAddress!);
|
||||
const targetCalldata = argv.targetCalldata!;
|
||||
console.log("using target value from args: ", { targetAddress, targetCalldata });
|
||||
|
||||
const conf = getConfig(argv.network!, argv.domain!);
|
||||
|
||||
const fragment = await approveHash(
|
||||
targetAddress,
|
||||
ethers.getBytes(targetCalldata),
|
||||
conf.ScrollSafeAddress,
|
||||
conf.ForwarderAddress,
|
||||
conf.ScrollTimelockAddress
|
||||
);
|
||||
console.log(fragment);
|
||||
}
|
||||
)
|
||||
.help().argv;
|
||||
49
contracts/admin/config.ts
Normal file
49
contracts/admin/config.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
export interface DomainDeployment {
|
||||
ForwarderAddress: string;
|
||||
|
||||
ScrollSafeAddress: string;
|
||||
ScrollTimelockAddress: string;
|
||||
|
||||
CouncilSafeAddress: string;
|
||||
CouncilTimelockAddress: string;
|
||||
}
|
||||
|
||||
export interface Deployment {
|
||||
L1: DomainDeployment;
|
||||
L2: DomainDeployment;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
[key: string]: Deployment;
|
||||
}
|
||||
|
||||
const config: Config = {
|
||||
testnet: {
|
||||
L1: {
|
||||
ForwarderAddress: "0x0000000000000000000000000000000000000000",
|
||||
ScrollSafeAddress: "0x0000000000000000000000000000000000000000",
|
||||
ScrollTimelockAddress: "0x0000000000000000000000000000000000000000",
|
||||
CouncilSafeAddress: "0x0000000000000000000000000000000000000000",
|
||||
CouncilTimelockAddress: "0x0000000000000000000000000000000000000000",
|
||||
},
|
||||
L2: {
|
||||
ForwarderAddress: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0",
|
||||
ScrollSafeAddress: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853",
|
||||
ScrollTimelockAddress: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318",
|
||||
CouncilSafeAddress: "0x0000000000000000000000000000000000000000",
|
||||
CouncilTimelockAddress: "0x0000000000000000000000000000000000000000",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const getConfig = (network: string, domain: string): DomainDeployment => {
|
||||
if (network in config) {
|
||||
if (domain in config[network]) {
|
||||
return config[network][domain as keyof Deployment];
|
||||
} else {
|
||||
throw new Error(`Invalid domain: ${domain}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Invalid network: ${network}`);
|
||||
}
|
||||
};
|
||||
19
contracts/admin/package.json
Normal file
19
contracts/admin/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "admin-cli",
|
||||
"bin": {
|
||||
"admin-cli": "./bin/index.js"
|
||||
},
|
||||
"main": "bin/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"prepublishOnly": "npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"ethers": "^6.6.1",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typechain/ethers-v6": "^0.4.0",
|
||||
"@types/yargs": "^17.0.24"
|
||||
}
|
||||
}
|
||||
10
contracts/admin/tsconfig.json
Normal file
10
contracts/admin/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2018",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"declaration": true
|
||||
}
|
||||
}
|
||||
113
contracts/admin/tx.ts
Normal file
113
contracts/admin/tx.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
import { ethers } from "ethers";
|
||||
import {
|
||||
Safe__factory,
|
||||
Safe,
|
||||
Forwarder__factory,
|
||||
Forwarder,
|
||||
Timelock__factory,
|
||||
Timelock,
|
||||
} from "./types/ethers-contracts";
|
||||
|
||||
export interface RawTxFragment {
|
||||
to: string;
|
||||
callData: string;
|
||||
functionSig: string;
|
||||
}
|
||||
|
||||
async function execTransaction(wallet: ethers.Wallet, safeContract: Safe, calldata: string, senders: string[]) {
|
||||
// ethers.AbiCoder.encode(
|
||||
// Safe__factory.abi
|
||||
let signatures = "0x0000000000000000000000000000000000000000";
|
||||
for (let i = 0; i < senders.length; i++) {
|
||||
signatures += encodeAddress(senders[i]);
|
||||
}
|
||||
|
||||
await safeContract
|
||||
.connect(wallet)
|
||||
.execTransaction(
|
||||
"0x0000000000000000000000000000000000000000",
|
||||
0,
|
||||
calldata,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ethers.ZeroAddress,
|
||||
ethers.ZeroAddress,
|
||||
signatures,
|
||||
{ gasLimit: 1000000 }
|
||||
);
|
||||
}
|
||||
|
||||
export async function approveHash(
|
||||
targetAddress: ethers.AddressLike,
|
||||
targetCalldata: ethers.BytesLike,
|
||||
safeAddress: ethers.AddressLike,
|
||||
forwarderAddress: ethers.AddressLike,
|
||||
timelockAddress: ethers.AddressLike
|
||||
): Promise<RawTxFragment> {
|
||||
// either implement getTransactionHash in JS or make RPC call to get hash
|
||||
const provider = new ethers.JsonRpcProvider("http://localhost:1234");
|
||||
const safeContract = Safe__factory.connect(safeAddress.toString(), provider);
|
||||
const forwarderContract = Forwarder__factory.connect(forwarderAddress.toString());
|
||||
const timelockContract = Timelock__factory.connect(timelockAddress.toString());
|
||||
// const targetCalldata = targetContract.interface.encodeFunctionData("err");
|
||||
const forwarderCalldata = forwarderContract.interface.encodeFunctionData("forward", [
|
||||
targetAddress.toString(),
|
||||
targetCalldata,
|
||||
]);
|
||||
const timelockScheduleCalldata = timelockContract.interface.encodeFunctionData("schedule", [
|
||||
forwarderAddress.toString(),
|
||||
0,
|
||||
forwarderCalldata,
|
||||
ethers.ZeroHash,
|
||||
ethers.ZeroHash,
|
||||
0,
|
||||
]);
|
||||
const txHash = await safeContract.getTransactionHash(
|
||||
timelockAddress.toString(),
|
||||
0,
|
||||
timelockScheduleCalldata,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ethers.ZeroAddress,
|
||||
ethers.ZeroAddress,
|
||||
0
|
||||
);
|
||||
|
||||
return {
|
||||
to: safeAddress.toString(),
|
||||
callData: txHash,
|
||||
functionSig: "approveHash(bytes32)",
|
||||
};
|
||||
}
|
||||
// await safeContract.checkNSignatures(scheduleSafeTxHash, ethers.arrayify("0x00"), sigSchedule, 1);
|
||||
// await timelockContract
|
||||
// .connect(wallet)
|
||||
// .execute(L2_FORWARDER_ADDR, 0, forwarderCalldata, ethers.HashZero, ethers.HashZero, {
|
||||
// gasLimit: 1000000,
|
||||
// });
|
||||
|
||||
// safe takes address as part of the signature
|
||||
function encodeAddress(address: string) {
|
||||
const r = ethers.zeroPadValue(address, 32);
|
||||
const s = ethers.zeroPadValue("0x00", 32);
|
||||
const v = "0x01";
|
||||
return ethers.toBeHex(ethers.concat([r, s, v])).slice(-2);
|
||||
}
|
||||
|
||||
// add 4 to the v byte at the end of the signature
|
||||
function editSig(sig: string) {
|
||||
const v = parseInt(sig.slice(-2), 16);
|
||||
const newV = v + 4;
|
||||
const newSig = sig.slice(0, -2) + newV.toString(16);
|
||||
return newSig;
|
||||
}
|
||||
|
||||
console.log(encodeAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"));
|
||||
|
||||
module.exports = {
|
||||
approveHash,
|
||||
};
|
||||
@@ -3,7 +3,7 @@ src = 'src' # the source directory
|
||||
test = 'src/test' # the test directory
|
||||
script = 'scripts' # the script directory
|
||||
out = 'artifacts/src' # the output directory (for artifacts)
|
||||
libs = [] # a list of library directories
|
||||
libs = ["lib"] # the library directory
|
||||
remappings = [] # a list of remappings
|
||||
libraries = [] # a list of deployed libraries to link against
|
||||
cache = true # whether to cache builds or not
|
||||
|
||||
1
contracts/lib/safe-contracts
Submodule
1
contracts/lib/safe-contracts
Submodule
Submodule contracts/lib/safe-contracts added at e870f514ad
26
contracts/scripts/deploy.sh
Executable file
26
contracts/scripts/deploy.sh
Executable file
@@ -0,0 +1,26 @@
|
||||
#/bin/sh
|
||||
set -uex
|
||||
|
||||
PID=$(lsof -t -i:1234)
|
||||
echo $PID
|
||||
kill $PID
|
||||
|
||||
export L2_DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
|
||||
PORT=1234
|
||||
|
||||
# deploys a local instance of the contracts
|
||||
anvil --port $PORT &
|
||||
|
||||
while ! lsof -i :$PORT
|
||||
do
|
||||
echo "...waiting for anvil"
|
||||
sleep 1
|
||||
done
|
||||
echo "started anvil"
|
||||
|
||||
forge script ./foundry/DeployL2AdminContracts.s.sol:DeployL2AdminContracts --rpc-url http://localhost:1234 --legacy --broadcast -vvvv
|
||||
|
||||
npx ts-node ./encode.ts
|
||||
|
||||
echo "deployment success"
|
||||
74
contracts/scripts/encode.sh
Executable file
74
contracts/scripts/encode.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#/bin/sh
|
||||
set -uex
|
||||
|
||||
|
||||
# does not work due to V recovery bit being off
|
||||
|
||||
L2_COUNCIL_SAFE_ADDR=0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
|
||||
L2_COUNCIL_TIMELOCK_ADDR=0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9
|
||||
L2_SCROLL_SAFE_ADDR=0xa513E6E4b8f2a923D98304ec87F64353C4D5C853
|
||||
L2_SCROLL_TIMELOCK_ADDR=0x8A791620dd6260079BF849Dc5567aDC3F2FdC318
|
||||
L2_FORWARDER_ADDR=0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0
|
||||
L2_TARGET_ADDR=0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82
|
||||
|
||||
# 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
||||
L2_DEPLOYER_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
||||
ZERO_BYTES=0x0000000000000000000000000000000000000000
|
||||
|
||||
# sign tx hash for timelock schedule call
|
||||
ADMIN_CALLDATA=$(cast calldata "err()")
|
||||
FORWARDER_CALLDATA=$(cast calldata "forward(address,bytes)" $L2_FORWARDER_ADDR $ADMIN_CALLDATA)
|
||||
TIMELOCK_SCHEDULE_CALLDATA=$(cast calldata "schedule(address,uint256,bytes,bytes32,bytes32,uint256)" $L2_FORWARDER_ADDR 0 $FORWARDER_CALLDATA 0x0 0x0 0x0)
|
||||
|
||||
SAFE_TX_HASH=$(cast call -r http://localhost:1234 $L2_SCROLL_SAFE_ADDR "getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)" \
|
||||
$L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_SCHEDULE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO_BYTES 0)
|
||||
|
||||
SAFE_SIG=$(cast wallet sign --private-key $L2_DEPLOYER_PRIVATE_KEY $SAFE_TX_HASH | awk '{print $2}')
|
||||
|
||||
# echo $SAFE_SIG
|
||||
# echo $SAFE_TX_HASH
|
||||
|
||||
# send safe tx to schedule the call
|
||||
cast send -c 31337 --legacy --private-key $L2_DEPLOYER_PRIVATE_KEY -r http://localhost:1234 --gas-limit 1000000 $L2_SCROLL_SAFE_ADDR "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)" \
|
||||
$L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_SCHEDULE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO_BYTES $SAFE_SIG
|
||||
|
||||
# function encodeTransactionData(
|
||||
# address to,
|
||||
# uint256 value,
|
||||
# bytes calldata data,
|
||||
# Enum.Operation operation,
|
||||
# uint256 safeTxGas,
|
||||
# uint256 baseGas,
|
||||
# uint256 gasPrice,
|
||||
# address gasToken,
|
||||
# address refundReceiver,
|
||||
# uint256 _nonce
|
||||
|
||||
# function execTransaction(
|
||||
# address to,
|
||||
# uint256 value,
|
||||
# bytes calldata data,
|
||||
# Enum.Operation operation,
|
||||
# uint256 safeTxGas,
|
||||
# uint256 baseGas,
|
||||
# uint256 gasPrice,
|
||||
# address gasToken,
|
||||
# address payable refundReceiver,
|
||||
# bytes memory signatures
|
||||
|
||||
exit 0
|
||||
|
||||
|
||||
# /////////////// 2nd tx ///////////////
|
||||
|
||||
# sign tx hash for execute call
|
||||
TIMELOCK_EXECUTE_CALLDATA=$(cast calldata "execute(address,uint256,bytes,bytes32,bytes32)" $L2_FORWARDER_ADDR 0 $FORWARDER_CALLDATA 0x0 0x0)
|
||||
SAFE_TX_HASH_=$(cast call -r http://localhost:1234 $L2_SCROLL_SAFE_ADDR "getTransactionHash(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,uint256)" \
|
||||
$L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_SCHEDULE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO_BYTES 0)
|
||||
SAFE_SIG=$(cast wallet sign --private-key $L2_DEPLOYER_PRIVATE_KEY $SAFE_TX_HASH | awk '{print $2}')
|
||||
|
||||
# send safe tx to execute the call
|
||||
cast send -c 31337 --legacy --private-key $L2_DEPLOYER_PRIVATE_KEY -r http://localhost:1234 --gas-limit 1000000 $L2_SCROLL_SAFE_ADDR "execTransaction(address,uint256,bytes,uint8,uint256,uint256,uint256,address,address,bytes)" \
|
||||
$L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_EXECUTE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO_BYTES $SAFE_SIG
|
||||
|
||||
echo "DONE"
|
||||
102
contracts/scripts/encode.ts
Normal file
102
contracts/scripts/encode.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { ethers } from "ethers";
|
||||
import { Safeabi__factory, Forwarder__factory, Target__factory, Timelock__factory } from "../safeAbi";
|
||||
|
||||
const L2_SCROLL_SAFE_ADDR = "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853";
|
||||
const L2_SCROLL_TIMELOCK_ADDR = "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318";
|
||||
const L2_FORWARDER_ADDR = "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0";
|
||||
const L2_TARGET_ADDR = "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82";
|
||||
const L2_DEPLOYER_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
||||
|
||||
/*
|
||||
TODO:
|
||||
* read from env
|
||||
* use approve hash flow
|
||||
* read nonce from safe
|
||||
* split script into schedule and execute
|
||||
* add gas limit
|
||||
* document how to use
|
||||
* how to get addresses from deployment?
|
||||
* get abis in a reasonable way
|
||||
*/
|
||||
|
||||
/*
|
||||
to get safe abi
|
||||
* forge build
|
||||
* cat artifacts/src/Safe.sol/Safe.json| jq .abi >> safeabi.json
|
||||
* mkdir safeAbi
|
||||
* npx typechain --target=ethers-v5 safeabi.json --out-dir safeAbi
|
||||
|
||||
repeat for forwarder, timelock, target
|
||||
*/
|
||||
|
||||
async function main() {
|
||||
const provider = new ethers.providers.JsonRpcProvider("http://localhost:1234");
|
||||
const wallet = new ethers.Wallet(L2_DEPLOYER_PRIVATE_KEY, provider);
|
||||
|
||||
const safeContract = Safeabi__factory.connect(L2_SCROLL_SAFE_ADDR, provider);
|
||||
const forwarderContract = Forwarder__factory.connect(L2_FORWARDER_ADDR, provider);
|
||||
const timelockContract = Timelock__factory.connect(L2_SCROLL_TIMELOCK_ADDR, provider);
|
||||
const targetContract = Target__factory.connect(L2_TARGET_ADDR, provider);
|
||||
|
||||
const targetCalldata = targetContract.interface.encodeFunctionData("err");
|
||||
const forwarderCalldata = forwarderContract.interface.encodeFunctionData("forward", [L2_TARGET_ADDR, targetCalldata]);
|
||||
const timelockScheduleCalldata = timelockContract.interface.encodeFunctionData("schedule", [
|
||||
L2_FORWARDER_ADDR,
|
||||
0,
|
||||
forwarderCalldata,
|
||||
ethers.constants.HashZero,
|
||||
ethers.constants.HashZero,
|
||||
0,
|
||||
]);
|
||||
|
||||
const scheduleSafeTxHash = await safeContract.getTransactionHash(
|
||||
L2_SCROLL_TIMELOCK_ADDR,
|
||||
0,
|
||||
timelockScheduleCalldata,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.constants.AddressZero,
|
||||
0
|
||||
);
|
||||
|
||||
const sigRawSchedule = await wallet.signMessage(ethers.utils.arrayify(scheduleSafeTxHash));
|
||||
const sigSchedule = editSig(sigRawSchedule);
|
||||
|
||||
await safeContract.checkNSignatures(scheduleSafeTxHash, ethers.utils.arrayify("0x00"), sigSchedule, 1);
|
||||
|
||||
await safeContract
|
||||
.connect(wallet)
|
||||
.execTransaction(
|
||||
L2_SCROLL_TIMELOCK_ADDR,
|
||||
0,
|
||||
timelockScheduleCalldata,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.constants.AddressZero,
|
||||
sigSchedule,
|
||||
{ gasLimit: 1000000 }
|
||||
);
|
||||
console.log("scheduled");
|
||||
|
||||
await timelockContract
|
||||
.connect(wallet)
|
||||
.execute(L2_FORWARDER_ADDR, 0, forwarderCalldata, ethers.constants.HashZero, ethers.constants.HashZero, {
|
||||
gasLimit: 1000000,
|
||||
});
|
||||
}
|
||||
|
||||
// add 4 to the v byte at the end of the signature
|
||||
function editSig(sig: string) {
|
||||
const v = parseInt(sig.slice(-2), 16);
|
||||
const newV = v + 4;
|
||||
const newSig = sig.slice(0, -2) + newV.toString(16);
|
||||
return newSig;
|
||||
}
|
||||
|
||||
main();
|
||||
76
contracts/scripts/foundry/DeployL1AdminContracts.s.sol
Normal file
76
contracts/scripts/foundry/DeployL1AdminContracts.s.sol
Normal file
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.10;
|
||||
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
|
||||
import {Safe} from "safe-contracts/Safe.sol";
|
||||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import {Forwarder} from "../../src/misc/Forwarder.sol";
|
||||
|
||||
contract DeployL1AdminContracts is Script {
|
||||
uint256 L1_DEPLOYER_PRIVATE_KEY = vm.envUint("L1_DEPLOYER_PRIVATE_KEY");
|
||||
|
||||
function run() external {
|
||||
vm.startBroadcast(L1_DEPLOYER_PRIVATE_KEY);
|
||||
|
||||
address council_safe = deploySafe();
|
||||
// deploy timelock with no delay just to have flow between council and scroll admin
|
||||
address council_timelock = deployTimelockController(council_safe, 0);
|
||||
|
||||
logAddress("L1_COUNCIL_SAFE_ADDR", address(council_safe));
|
||||
logAddress("L1_COUNCIL_TIMELOCK_ADDR", address(council_timelock));
|
||||
|
||||
address scroll_safe = deploySafe();
|
||||
// TODO: get timelock delay from env. for now just use 2 days
|
||||
address scroll_timelock = deployTimelockController(scroll_safe, 2 days);
|
||||
|
||||
logAddress("L1_SCROLL_SAFE_ADDR", address(scroll_safe));
|
||||
logAddress("L1_SCROLL_TIMELOCK_ADDR", address(scroll_timelock));
|
||||
|
||||
address forwarder = deployForwarder(address(council_safe), address(scroll_safe));
|
||||
logAddress("L1_FORWARDER_ADDR", address(forwarder));
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function deployForwarder(address admin, address superAdmin) internal returns (address) {
|
||||
Forwarder forwarder = new Forwarder(admin, superAdmin);
|
||||
return address(forwarder);
|
||||
}
|
||||
|
||||
function deploySafe() internal returns (address) {
|
||||
address owner = vm.addr(L1_DEPLOYER_PRIVATE_KEY);
|
||||
// TODO: get safe signers from env
|
||||
|
||||
Safe safe = new Safe();
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
// deployer 1/1. no gas refunds for now
|
||||
safe.setup(owners, 1, address(0), new bytes(0), address(0), address(0), 0, payable(address(0)));
|
||||
return address(safe);
|
||||
}
|
||||
|
||||
function deployTimelockController(address safe, uint256 delay) internal returns (address) {
|
||||
address deployer = vm.addr(L1_DEPLOYER_PRIVATE_KEY);
|
||||
|
||||
address[] memory proposers = new address[](1);
|
||||
proposers[0] = safe;
|
||||
// add SAFE as the only proposer, anyone can execute
|
||||
address[] memory executors = new address[](1);
|
||||
executors[0] = deployer;
|
||||
TimelockController timelock = new TimelockController(delay, proposers, executors);
|
||||
|
||||
bytes32 TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
|
||||
|
||||
// make safe admin of timelock, then revoke deployer's rights
|
||||
timelock.grantRole(TIMELOCK_ADMIN_ROLE, address(safe));
|
||||
timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer);
|
||||
|
||||
return address(timelock);
|
||||
}
|
||||
|
||||
function logAddress(string memory name, address addr) internal view {
|
||||
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ pragma solidity ^0.8.10;
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
|
||||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
|
||||
import {L1CustomERC20Gateway} from "../../src/L1/gateways/L1CustomERC20Gateway.sol";
|
||||
import {L1ERC1155Gateway} from "../../src/L1/gateways/L1ERC1155Gateway.sol";
|
||||
@@ -22,6 +22,7 @@ import {L2GasPriceOracle} from "../../src/L1/rollup/L2GasPriceOracle.sol";
|
||||
import {ScrollChain} from "../../src/L1/rollup/ScrollChain.sol";
|
||||
import {Whitelist} from "../../src/L2/predeploys/Whitelist.sol";
|
||||
|
||||
|
||||
contract DeployL1BridgeContracts is Script {
|
||||
uint256 L1_DEPLOYER_PRIVATE_KEY = vm.envUint("L1_DEPLOYER_PRIVATE_KEY");
|
||||
|
||||
@@ -30,14 +31,14 @@ contract DeployL1BridgeContracts is Script {
|
||||
address L1_WETH_ADDR = vm.envAddress("L1_WETH_ADDR");
|
||||
address L2_WETH_ADDR = vm.envAddress("L2_WETH_ADDR");
|
||||
|
||||
ProxyAdmin proxyAdmin;
|
||||
// scroll admin (timelocked) or security council
|
||||
address FORWARDER = vm.envAddress("L1_FORWARDER");
|
||||
|
||||
function run() external {
|
||||
vm.startBroadcast(L1_DEPLOYER_PRIVATE_KEY);
|
||||
|
||||
// note: the RollupVerifier library is deployed implicitly
|
||||
|
||||
deployProxyAdmin();
|
||||
deployL1Whitelist();
|
||||
deployL1MessageQueue();
|
||||
deployL2GasPriceOracle();
|
||||
@@ -55,12 +56,6 @@ contract DeployL1BridgeContracts is Script {
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function deployProxyAdmin() internal {
|
||||
proxyAdmin = new ProxyAdmin();
|
||||
|
||||
logAddress("L1_PROXY_ADMIN_ADDR", address(proxyAdmin));
|
||||
}
|
||||
|
||||
function deployL1Whitelist() internal {
|
||||
address owner = vm.addr(L1_DEPLOYER_PRIVATE_KEY);
|
||||
Whitelist whitelist = new Whitelist(owner);
|
||||
@@ -72,7 +67,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
ScrollChain impl = new ScrollChain(CHAIN_ID_L2);
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -84,7 +79,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1MessageQueue impl = new L1MessageQueue();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
logAddress("L1_MESSAGE_QUEUE_IMPLEMENTATION_ADDR", address(impl));
|
||||
@@ -95,7 +90,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L2GasPriceOracle impl = new L2GasPriceOracle();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
logAddress("L2_GAS_PRICE_ORACLE_IMPLEMENTATION_ADDR", address(impl));
|
||||
@@ -106,7 +101,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1StandardERC20Gateway impl = new L1StandardERC20Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -118,7 +113,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1ETHGateway impl = new L1ETHGateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -130,7 +125,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1WETHGateway impl = new L1WETHGateway(L1_WETH_ADDR, L2_WETH_ADDR);
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -142,7 +137,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1GatewayRouter impl = new L1GatewayRouter();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -154,7 +149,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1ScrollMessenger impl = new L1ScrollMessenger();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -166,7 +161,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
EnforcedTxGateway impl = new EnforcedTxGateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -178,7 +173,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1CustomERC20Gateway impl = new L1CustomERC20Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -190,7 +185,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1ERC721Gateway impl = new L1ERC721Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -202,7 +197,7 @@ contract DeployL1BridgeContracts is Script {
|
||||
L1ERC1155Gateway impl = new L1ERC1155Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
|
||||
114
contracts/scripts/foundry/DeployL2AdminContracts.s.sol
Normal file
114
contracts/scripts/foundry/DeployL2AdminContracts.s.sol
Normal file
@@ -0,0 +1,114 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.10;
|
||||
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
|
||||
import {Safe} from "safe-contracts/Safe.sol";
|
||||
import {SafeProxy} from "safe-contracts/proxies/SafeProxy.sol";
|
||||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import {Forwarder} from "../../src/misc/Forwarder.sol";
|
||||
import {MockTarget} from "../../src/mocks/MockTarget.sol";
|
||||
|
||||
interface ISafe {
|
||||
function setup(
|
||||
address[] calldata _owners,
|
||||
uint256 _threshold,
|
||||
address to,
|
||||
bytes calldata data,
|
||||
address fallbackHandler,
|
||||
address paymentToken,
|
||||
uint256 payment,
|
||||
address payable paymentReceiver
|
||||
) external;
|
||||
}
|
||||
|
||||
contract DeployL2AdminContracts is Script {
|
||||
uint256 L2_DEPLOYER_PRIVATE_KEY = vm.envUint("L2_DEPLOYER_PRIVATE_KEY");
|
||||
|
||||
function run() external {
|
||||
vm.startBroadcast(L2_DEPLOYER_PRIVATE_KEY);
|
||||
|
||||
address council_safe = deploySafe();
|
||||
// deploy timelock with no delay, just to keep council and scroll admin flows be parallel
|
||||
address council_timelock = deployTimelockController(council_safe, 0);
|
||||
|
||||
logAddress("L2_COUNCIL_SAFE_ADDR", address(council_safe));
|
||||
logAddress("L2_COUNCIL_TIMELOCK_ADDR", address(council_timelock));
|
||||
|
||||
address scroll_safe = deploySafe();
|
||||
// TODO: get timelock delay from env. for now just use 0
|
||||
address scroll_timelock = deployTimelockController(scroll_safe, 0);
|
||||
|
||||
logAddress("L2_SCROLL_SAFE_ADDR", address(scroll_safe));
|
||||
logAddress("L2_SCROLL_TIMELOCK_ADDR", address(scroll_timelock));
|
||||
|
||||
address forwarder = deployForwarder(address(council_timelock), address(scroll_timelock));
|
||||
logAddress("L1_FORWARDER_ADDR", address(forwarder));
|
||||
|
||||
MockTarget target = new MockTarget();
|
||||
logAddress("L2_TARGET_ADDR", address(target));
|
||||
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function deployForwarder(address admin, address superAdmin) internal returns (address) {
|
||||
Forwarder forwarder = new Forwarder(admin, superAdmin);
|
||||
return address(forwarder);
|
||||
}
|
||||
|
||||
function deploySafe() internal returns (address) {
|
||||
address owner = vm.addr(L2_DEPLOYER_PRIVATE_KEY);
|
||||
// TODO: get safe signers from env
|
||||
|
||||
Safe safe = new Safe();
|
||||
SafeProxy proxy = new SafeProxy(address(safe));
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
// deployer 1/1. no gas refunds for now
|
||||
ISafe(address(proxy)).setup(
|
||||
owners,
|
||||
1,
|
||||
address(0),
|
||||
new bytes(0),
|
||||
address(0),
|
||||
address(0),
|
||||
0,
|
||||
payable(address(0))
|
||||
);
|
||||
|
||||
return address(proxy);
|
||||
}
|
||||
|
||||
function deployTimelockController(address safe, uint256 delay) internal returns (address) {
|
||||
address deployer = vm.addr(L2_DEPLOYER_PRIVATE_KEY);
|
||||
|
||||
address[] memory proposers = new address[](1);
|
||||
proposers[0] = safe;
|
||||
|
||||
address[] memory executors = new address[](1);
|
||||
executors[0] = address(0);
|
||||
// add SAFE as the only proposer, anyone can execute
|
||||
TimelockController timelock = new TimelockController(delay, proposers, executors);
|
||||
|
||||
bytes32 TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
|
||||
|
||||
// make safe admin of timelock, then revoke deployer's rights
|
||||
timelock.grantRole(TIMELOCK_ADMIN_ROLE, address(safe));
|
||||
timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer);
|
||||
|
||||
return address(timelock);
|
||||
}
|
||||
|
||||
function logBytes32(string memory name, bytes32 value) internal view {
|
||||
console.log(string(abi.encodePacked(name, "=", vm.toString(bytes32(value)))));
|
||||
}
|
||||
|
||||
function logUint(string memory name, uint256 value) internal view {
|
||||
console.log(string(abi.encodePacked(name, "=", vm.toString(uint256(value)))));
|
||||
}
|
||||
|
||||
function logAddress(string memory name, address addr) internal view {
|
||||
console.log(string(abi.encodePacked(name, "=", vm.toString(address(addr)))));
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ pragma solidity ^0.8.10;
|
||||
import {Script} from "forge-std/Script.sol";
|
||||
import {console} from "forge-std/console.sol";
|
||||
|
||||
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
|
||||
import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
|
||||
|
||||
import {L2CustomERC20Gateway} from "../../src/L2/gateways/L2CustomERC20Gateway.sol";
|
||||
@@ -30,10 +29,12 @@ contract DeployL2BridgeContracts is Script {
|
||||
address L1_WETH_ADDR = vm.envAddress("L1_WETH_ADDR");
|
||||
address L2_WETH_ADDR = vm.envAddress("L2_WETH_ADDR");
|
||||
|
||||
// scroll admin (timelocked) or security council
|
||||
address FORWARDER = vm.envAddress("L2_FORWARDER");
|
||||
|
||||
L1GasPriceOracle oracle;
|
||||
L1BlockContainer container;
|
||||
L2MessageQueue queue;
|
||||
ProxyAdmin proxyAdmin;
|
||||
|
||||
// predeploy contracts
|
||||
address L1_BLOCK_CONTAINER_PREDEPLOY_ADDR = vm.envOr("L1_BLOCK_CONTAINER_PREDEPLOY_ADDR", address(0));
|
||||
@@ -53,7 +54,6 @@ contract DeployL2BridgeContracts is Script {
|
||||
deployL2Whitelist();
|
||||
|
||||
// upgradable
|
||||
deployProxyAdmin();
|
||||
deployL2ScrollMessenger();
|
||||
deployL2ETHGateway();
|
||||
deployL2WETHGateway();
|
||||
@@ -130,17 +130,11 @@ contract DeployL2BridgeContracts is Script {
|
||||
logAddress("L2_WHITELIST_ADDR", address(whitelist));
|
||||
}
|
||||
|
||||
function deployProxyAdmin() internal {
|
||||
proxyAdmin = new ProxyAdmin();
|
||||
|
||||
logAddress("L2_PROXY_ADMIN_ADDR", address(proxyAdmin));
|
||||
}
|
||||
|
||||
function deployL2ScrollMessenger() internal {
|
||||
L2ScrollMessenger impl = new L2ScrollMessenger(address(container), address(oracle), address(queue));
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -152,7 +146,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2StandardERC20Gateway impl = new L2StandardERC20Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -164,7 +158,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2ETHGateway impl = new L2ETHGateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -176,7 +170,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2WETHGateway impl = new L2WETHGateway(L2_WETH_ADDR, L1_WETH_ADDR);
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -188,7 +182,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2GatewayRouter impl = new L2GatewayRouter();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -208,7 +202,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2CustomERC20Gateway impl = new L2CustomERC20Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -220,7 +214,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2ERC721Gateway impl = new L2ERC721Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
@@ -232,7 +226,7 @@ contract DeployL2BridgeContracts is Script {
|
||||
L2ERC1155Gateway impl = new L2ERC1155Gateway();
|
||||
TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(
|
||||
address(impl),
|
||||
address(proxyAdmin),
|
||||
FORWARDER,
|
||||
new bytes(0)
|
||||
);
|
||||
|
||||
|
||||
45
contracts/src/misc/Forwarder.sol
Normal file
45
contracts/src/misc/Forwarder.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract Forwarder {
|
||||
|
||||
address public admin;
|
||||
address public superAdmin;
|
||||
|
||||
event Forwarded(address indexed target, uint256 value, bytes data);
|
||||
event SetAdmin(address indexed admin);
|
||||
event SetSuperAdmin(address indexed superAdmin);
|
||||
|
||||
constructor(address _admin, address _superAdmin) {
|
||||
admin = _admin;
|
||||
superAdmin = _superAdmin;
|
||||
}
|
||||
|
||||
function setAdmin(address _admin) public {
|
||||
require(msg.sender == superAdmin, "only superAdmin");
|
||||
admin = _admin;
|
||||
emit SetAdmin(_admin);
|
||||
}
|
||||
|
||||
function setSuperAdmin(address _superAdmin) public {
|
||||
require(msg.sender == superAdmin, "only superAdmin");
|
||||
superAdmin = _superAdmin;
|
||||
emit SetSuperAdmin(_superAdmin);
|
||||
}
|
||||
|
||||
function forward(address _target, bytes memory _data) public payable {
|
||||
require(msg.sender == superAdmin || msg.sender == admin, "only admin or superAdmin");
|
||||
(bool success, ) = _target.call{value: msg.value}(_data);
|
||||
// bubble up revert reason
|
||||
if (!success) {
|
||||
assembly {
|
||||
let ptr := mload(0x40)
|
||||
let size := returndatasize()
|
||||
returndatacopy(ptr, 0, size)
|
||||
revert(ptr, size)
|
||||
}
|
||||
}
|
||||
emit Forwarded(_target, msg.value, _data);
|
||||
}
|
||||
}
|
||||
14
contracts/src/mocks/MockTarget.sol
Normal file
14
contracts/src/mocks/MockTarget.sol
Normal file
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
contract MockTarget {
|
||||
event ABC(uint256);
|
||||
|
||||
function err() pure external {
|
||||
revert("test error");
|
||||
}
|
||||
function succeed() external {
|
||||
emit ABC(1);
|
||||
}
|
||||
}
|
||||
69
contracts/src/test/Forwarder.t.sol
Normal file
69
contracts/src/test/Forwarder.t.sol
Normal file
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
|
||||
import {WETH} from "solmate/tokens/WETH.sol";
|
||||
|
||||
import {Forwarder} from "../misc/Forwarder.sol";
|
||||
import {MockTarget} from "../mocks/MockTarget.sol";
|
||||
import {IL1ScrollMessenger, L1ScrollMessenger} from "../L1/L1ScrollMessenger.sol";
|
||||
|
||||
|
||||
contract ForwarderTest is DSTestPlus {
|
||||
MockTarget public target;
|
||||
Forwarder public forwarder;
|
||||
L1ScrollMessenger internal l1Messenger;
|
||||
|
||||
address public admin = address(2);
|
||||
address public superAdmin = address(3);
|
||||
|
||||
function setUp() public {
|
||||
target = new MockTarget();
|
||||
forwarder = new Forwarder(admin, superAdmin);
|
||||
|
||||
l1Messenger = new L1ScrollMessenger();
|
||||
l1Messenger.initialize(address(0), address(0), address(0), address(0));
|
||||
l1Messenger.transferOwnership(address(forwarder));
|
||||
}
|
||||
|
||||
function testAdminFail() external {
|
||||
hevm.expectRevert("only admin or superAdmin");
|
||||
forwarder.forward(address(l1Messenger),hex"00");
|
||||
|
||||
hevm.expectRevert("only superAdmin");
|
||||
forwarder.setAdmin(address(0));
|
||||
|
||||
hevm.expectRevert("only superAdmin");
|
||||
forwarder.setSuperAdmin(address(0));
|
||||
}
|
||||
|
||||
function testAdmin() external {
|
||||
// cast calldata "transferOwnership(address)" 0x0000000000000000000000000000000000000005
|
||||
// 0xf2fde38b0000000000000000000000000000000000000000000000000000000000000005
|
||||
|
||||
hevm.startPrank(admin);
|
||||
forwarder.forward(address(l1Messenger), hex"f2fde38b0000000000000000000000000000000000000000000000000000000000000006");
|
||||
assertEq(address(6), l1Messenger.owner());
|
||||
hevm.stopPrank();
|
||||
}
|
||||
|
||||
function testForwardSuperAdmin() external {
|
||||
hevm.startPrank(superAdmin);
|
||||
forwarder.forward(address(l1Messenger), hex"f2fde38b0000000000000000000000000000000000000000000000000000000000000006");
|
||||
assertEq(address(6), l1Messenger.owner());
|
||||
|
||||
forwarder.setAdmin(address(0));
|
||||
assertEq(forwarder.admin(), address(0));
|
||||
|
||||
|
||||
forwarder.setSuperAdmin(address(0));
|
||||
assertEq(forwarder.superAdmin(), address(0));
|
||||
}
|
||||
|
||||
function testNestedRevert() external {
|
||||
hevm.startPrank(superAdmin);
|
||||
hevm.expectRevert("test error");
|
||||
forwarder.forward(address(target), hex"38df7677");
|
||||
}
|
||||
}
|
||||
212
contracts/src/test/Temp.t.sol
Normal file
212
contracts/src/test/Temp.t.sol
Normal file
@@ -0,0 +1,212 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
|
||||
import "forge-std/Vm.sol";
|
||||
// import {Vm, VmSafe} from "./Vm.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console.sol";
|
||||
|
||||
import {Safe} from "safe-contracts/Safe.sol";
|
||||
import {SafeProxy} from "safe-contracts/proxies/SafeProxy.sol";
|
||||
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
import {Forwarder} from "../../src/misc/Forwarder.sol";
|
||||
import {MockTarget} from "../../src/mocks/MockTarget.sol";
|
||||
|
||||
interface ISafe {
|
||||
// enum
|
||||
enum Operation {
|
||||
Call,
|
||||
DelegateCall
|
||||
}
|
||||
|
||||
function setup(
|
||||
address[] calldata _owners,
|
||||
uint256 _threshold,
|
||||
address to,
|
||||
bytes calldata data,
|
||||
address fallbackHandler,
|
||||
address paymentToken,
|
||||
uint256 payment,
|
||||
address payable paymentReceiver
|
||||
) external;
|
||||
|
||||
function execTransaction(
|
||||
address to,
|
||||
uint256 value,
|
||||
bytes calldata data,
|
||||
Operation operation,
|
||||
uint256 safeTxGas,
|
||||
uint256 baseGas,
|
||||
uint256 gasPrice,
|
||||
address gasToken,
|
||||
address payable refundReceiver,
|
||||
bytes memory signatures
|
||||
) external returns (bool success);
|
||||
|
||||
function checkNSignatures(
|
||||
bytes32 dataHash,
|
||||
bytes memory data,
|
||||
bytes memory signatures,
|
||||
uint256 requiredSignatures
|
||||
) external;
|
||||
}
|
||||
|
||||
// scratchpad
|
||||
|
||||
contract Temp is DSTestPlus {
|
||||
address scroll_safe;
|
||||
|
||||
// function setUp() external {
|
||||
// hevm.prank(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266);
|
||||
|
||||
// address council_safe = deploySafe();
|
||||
// // deploy timelock with no delay, just to keep council and scroll admin flows be parallel
|
||||
// address council_timelock = deployTimelockController(council_safe, 0);
|
||||
|
||||
// // logAddress("L2_COUNCIL_SAFE_ADDR", address(council_safe));
|
||||
// // logAddress("L2_COUNCIL_TIMELOCK_ADDR", address(council_timelock));
|
||||
|
||||
// address scroll_safe = deploySafe();
|
||||
// // TODO: get timelock delay from env. for now just use 0
|
||||
// address scroll_timelock = deployTimelockController(scroll_safe, 0);
|
||||
|
||||
// // logAddress("L2_SCROLL_SAFE_ADDR", address(scroll_safe));
|
||||
// // logAddress("L2_SCROLL_TIMELOCK_ADDR", address(scroll_timelock));
|
||||
|
||||
// address forwarder = deployForwarder(address(council_safe), address(scroll_safe));
|
||||
// // logAddress("L1_FORWARDER_ADDR", address(forwarder));
|
||||
|
||||
// MockTarget target = new MockTarget();
|
||||
// // logAddress("L2_TARGET_ADDR", address(target));
|
||||
|
||||
// // vm.stopBroadcast();
|
||||
// }
|
||||
function testEcrecover() external {
|
||||
bytes32 dataHash = 0xb453bd4e271eed985cbab8231da609c4ce0a9cf1f763b6c1594e76315510e0f1;
|
||||
// (uint8 v, bytes32 r, bytes32 s) = signatureSplit(
|
||||
// hex"078461ca16494711508b8602c1ea3ef515e5bfe11d67fc76e45b9217d42059f57abdde7cb9bf83b094991e2b6e61fd8b1146de575fd12080d65eaedd2e0c74da1c",
|
||||
// 0
|
||||
// );
|
||||
bytes
|
||||
memory signatures = hex"078461ca16494711508b8602c1ea3ef515e5bfe11d67fc76e45b9217d42059f57abdde7cb9bf83b094991e2b6e61fd8b1146de575fd12080d65eaedd2e0c74da1c";
|
||||
uint256 requiredSignatures = 1;
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
uint256 i;
|
||||
for (i = 0; i < requiredSignatures; i++) {
|
||||
(v, r, s) = signatureSplit(signatures, i);
|
||||
emit log_uint(v);
|
||||
emit log_bytes32(r);
|
||||
emit log_bytes32(s);
|
||||
address currentOwner = ecrecover(
|
||||
keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)),
|
||||
v,
|
||||
r,
|
||||
s
|
||||
);
|
||||
assertEq(address(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf), currentOwner);
|
||||
}
|
||||
}
|
||||
|
||||
function testEcrecover1() external {
|
||||
bytes
|
||||
memory sig = hex"078461ca16494711508b8602c1ea3ef515e5bfe11d67fc76e45b9217d42059f57abdde7cb9bf83b094991e2b6e61fd8b1146de575fd12080d65eaedd2e0c74da1c";
|
||||
uint8 v;
|
||||
bytes32 r;
|
||||
bytes32 s;
|
||||
(v, r, s) = signatureSplit(sig, 0);
|
||||
emit log_uint(v);
|
||||
emit log_bytes32(r);
|
||||
emit log_bytes32(s);
|
||||
|
||||
require(r == 0x078461ca16494711508b8602c1ea3ef515e5bfe11d67fc76e45b9217d42059f5, "r");
|
||||
require(s == 0x7abdde7cb9bf83b094991e2b6e61fd8b1146de575fd12080d65eaedd2e0c74da, "s");
|
||||
require(v == 28, "v");
|
||||
}
|
||||
|
||||
function testSigVerify() external {
|
||||
address currentOwner = ecrecover(
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
"\x19Ethereum Signed Message:\n32",
|
||||
bytes32(0xb453bd4e271eed985cbab8231da609c4ce0a9cf1f763b6c1594e76315510e0f1)
|
||||
)
|
||||
),
|
||||
28,
|
||||
0x078461ca16494711508b8602c1ea3ef515e5bfe11d67fc76e45b9217d42059f5,
|
||||
0x7abdde7cb9bf83b094991e2b6e61fd8b1146de575fd12080d65eaedd2e0c74da
|
||||
);
|
||||
require(currentOwner == 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, "SIG FAIL ABC");
|
||||
}
|
||||
|
||||
function signatureSplit(bytes memory signatures, uint256 pos)
|
||||
public
|
||||
returns (
|
||||
uint8 v,
|
||||
bytes32 r,
|
||||
bytes32 s
|
||||
)
|
||||
{
|
||||
// solhint-disable-next-line no-inline-assembly
|
||||
assembly {
|
||||
let signaturePos := mul(0x41, pos)
|
||||
r := mload(add(signatures, add(signaturePos, 0x20)))
|
||||
s := mload(add(signatures, add(signaturePos, 0x40)))
|
||||
/**
|
||||
* Here we are loading the last 32 bytes, including 31 bytes
|
||||
* of 's'. There is no 'mload8' to do this.
|
||||
* 'byte' is not working due to the Solidity parser, so lets
|
||||
* use the second best option, 'and'
|
||||
*/
|
||||
v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
function deployForwarder(address admin, address superAdmin) internal returns (address) {
|
||||
Forwarder forwarder = new Forwarder(admin, superAdmin);
|
||||
return address(forwarder);
|
||||
}
|
||||
|
||||
function deploySafe() internal returns (address) {
|
||||
address owner = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
||||
// TODO: get safe signers from env
|
||||
|
||||
Safe safe = new Safe();
|
||||
SafeProxy proxy = new SafeProxy(address(safe));
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
// deployer 1/1. no gas refunds for now
|
||||
ISafe(address(proxy)).setup(
|
||||
owners,
|
||||
1,
|
||||
address(0),
|
||||
new bytes(0),
|
||||
address(0),
|
||||
address(0),
|
||||
0,
|
||||
payable(address(0))
|
||||
);
|
||||
|
||||
return address(proxy);
|
||||
}
|
||||
|
||||
function deployTimelockController(address safe, uint256 delay) internal returns (address) {
|
||||
address deployer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
||||
|
||||
address[] memory proposers = new address[](1);
|
||||
proposers[0] = safe;
|
||||
// add SAFE as the only proposer, anyone can execute
|
||||
TimelockController timelock = new TimelockController(delay, proposers, new address[](0));
|
||||
|
||||
bytes32 TIMELOCK_ADMIN_ROLE = keccak256("TIMELOCK_ADMIN_ROLE");
|
||||
|
||||
// make safe admin of timelock, then revoke deployer's rights
|
||||
timelock.grantRole(TIMELOCK_ADMIN_ROLE, address(safe));
|
||||
timelock.revokeRole(TIMELOCK_ADMIN_ROLE, deployer);
|
||||
|
||||
return address(timelock);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user