diff --git a/contracts/scripts/deploy.sh b/contracts/scripts/deploy.sh index 0886ffe52..abf2c289c 100755 --- a/contracts/scripts/deploy.sh +++ b/contracts/scripts/deploy.sh @@ -1,13 +1,16 @@ #/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 & -P1=$! while ! lsof -i :$PORT do @@ -18,6 +21,6 @@ echo "started anvil" forge script ./foundry/DeployL2AdminContracts.s.sol:DeployL2AdminContracts --rpc-url http://localhost:1234 --legacy --broadcast -vvvv -./encode.sh +npx ts-node ./encode.ts echo "deployment success" \ No newline at end of file diff --git a/contracts/scripts/encode.sh b/contracts/scripts/encode.sh index f0912721e..59187b3d3 100755 --- a/contracts/scripts/encode.sh +++ b/contracts/scripts/encode.sh @@ -1,6 +1,9 @@ #/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 @@ -22,6 +25,9 @@ $L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_SCHEDULE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO 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 @@ -53,23 +59,6 @@ $L2_SCROLL_TIMELOCK_ADDR 0 $TIMELOCK_SCHEDULE_CALLDATA 0 0 0 0 $ZERO_BYTES $ZERO exit 0 - - - - - - - - - - - - - - - - - # /////////////// 2nd tx /////////////// # sign tx hash for execute call diff --git a/contracts/scripts/encode.ts b/contracts/scripts/encode.ts new file mode 100644 index 000000000..7e8012931 --- /dev/null +++ b/contracts/scripts/encode.ts @@ -0,0 +1,36 @@ +import { ethers } from "ethers"; +import { SafeAbi__factory, SafeAbi } from "../safeAbi"; + +/* +to get safe abi +* forge build +* cat artifacts/src/Safe.sol/Safe.json| jq .abi >> safeabi.json +* mkdir safeAbi +* npx typechain --target=ethers-v5 artifacts/src/Safe.sol/Safe.json --out-dir safeAbi + +*/ + +async function main() { + const provider = new ethers.providers.JsonRpcProvider("http://localhost:1234"); + const safeAddress = "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853"; + const safe = SafeAbi__factory.connect(safeAddress, provider); + + const wallet = new ethers.Wallet("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + const message = "Hello, world!"; + const dataHash = ethers.utils.hashMessage(message); + console.log(dataHash); + const sigRaw = await wallet.signMessage(ethers.utils.arrayify(dataHash)); + const sig = editSig(sigRaw); + + await safe.checkNSignatures(dataHash, ethers.utils.arrayify("0x00"), sig, 1); +} + +// 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(); diff --git a/contracts/src/test/Temp.t.sol b/contracts/src/test/Temp.t.sol new file mode 100644 index 000000000..448981eb9 --- /dev/null +++ b/contracts/src/test/Temp.t.sol @@ -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); + } +}