diff --git a/README.md b/README.md index 019985c..2756ca0 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,15 @@ Deployment of periphery contracts such as the [Apr Oracle](https://github.com/ye This can be done permissionlessly if the most recent contract has not yet been deployed on a chain you would like to use it on. -1. Add your deployers Private key under PRIVATE_KEY in your .env file. - - NOTE: make sure to add `0x` to the beginning of the key. +1. If you have not added a keystore private key to foundry before add your address to use + +```shell +$ cast wallet import --interactive +``` + 2. Run the deployment script for the contract you want to deploy. ```sh - forge script script/DeployContractName.s.sol:DeployContractName --broadcast --rpc-url YOUR_RPC_URL + forge script script/DeployContractName.s.sol:DeployContractName --broadcast --rpc-url YOUR_RPC_URL --account ACCOUNT_NAME ``` - You can do a dry run before officially deploying by removing the `--broadcast` flag. - For chains that don't support 1559 tx's you may need to add a `--legacy` flag. diff --git a/script/BaseScript.s.sol b/script/BaseScript.s.sol new file mode 100644 index 0000000..045eebd --- /dev/null +++ b/script/BaseScript.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import "forge-std/Script.sol"; + +interface Deployer { + event ContractCreation(address indexed newContract, bytes32 indexed salt); + + function deployCreate3( + bytes32 salt, + bytes memory initCode + ) external payable returns (address newContract); + + function deployCreate2( + bytes32 salt, + bytes memory initCode + ) external payable returns (address newContract); +} + +// Deploy a contract to a deterministic address with create2 +abstract contract BaseScript is Script { + + Deployer public deployer = Deployer(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); + + address public v3Safe = 0x33333333D5eFb92f19a5F94a43456b3cec2797AE; + + address public initGov; +} diff --git a/script/DeployAprOracle.s.sol b/script/DeployAprOracle.s.sol index de44198..b6bd530 100644 --- a/script/DeployAprOracle.s.sol +++ b/script/DeployAprOracle.s.sol @@ -1,19 +1,16 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; -import "forge-std/Script.sol"; +import "./BaseScript.s.sol"; // Deploy a contract to a deterministic address with create2 -contract DeployAprOracle is Script { - - Deployer public deployer = Deployer(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); +contract DeployAprOracle is BaseScript { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); // Encode constructor arguments - bytes memory construct = abi.encode(0x33333333D5eFb92f19a5F94a43456b3cec2797AE); + bytes memory construct = abi.encode(v3Safe); // Get the bytecode bytes memory bytecode = abi.encodePacked(vm.getCode("AprOracle.sol:AprOracle"), construct); @@ -27,13 +24,4 @@ contract DeployAprOracle is Script { vm.stopBroadcast(); } -} - -contract Deployer { - event ContractCreation(address indexed newContract, bytes32 indexed salt); - - function deployCreate2( - bytes32 salt, - bytes memory initCode - ) public payable returns (address newContract) {} } \ No newline at end of file diff --git a/script/DeployAuctionFactory.sol b/script/DeployAuctionFactory.sol index a2b95b3..2000f91 100644 --- a/script/DeployAuctionFactory.sol +++ b/script/DeployAuctionFactory.sol @@ -1,16 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.18; -import "forge-std/Script.sol"; +import "./BaseScript.s.sol"; // Deploy a contract to a deterministic address with create2 -contract DeployAuctionFactory is Script { - - Deployer public deployer = Deployer(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed); +contract DeployAuctionFactory is BaseScript { function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); // Get the bytecode bytes memory bytecode = abi.encodePacked(vm.getCode("AuctionFactory.sol:AuctionFactory")); @@ -24,13 +21,4 @@ contract DeployAuctionFactory is Script { vm.stopBroadcast(); } -} - -interface Deployer { - event ContractCreation(address indexed newContract, bytes32 indexed salt); - - function deployCreate3( - bytes32 salt, - bytes memory initCode - ) external payable returns (address newContract); } \ No newline at end of file diff --git a/script/DeployCommonTrigger.s.sol b/script/DeployCommonTrigger.s.sol index 6b8281d..3bfcf24 100644 --- a/script/DeployCommonTrigger.s.sol +++ b/script/DeployCommonTrigger.s.sol @@ -9,8 +9,7 @@ contract DeployCommonTrigger is Script { Deployer public deployer = Deployer(0x8D85e7c9A4e369E53Acc8d5426aE1568198b0112); function run() external { - uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); - vm.startBroadcast(deployerPrivateKey); + vm.startBroadcast(); // Encode constructor arguments bytes memory construct = abi.encode(0x33333333D5eFb92f19a5F94a43456b3cec2797AE); diff --git a/script/DeployInitGov.s.sol b/script/DeployInitGov.s.sol new file mode 100644 index 0000000..3300ec5 --- /dev/null +++ b/script/DeployInitGov.s.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import "./BaseScript.s.sol"; + +// Deploy a contract to a deterministic address with create2 +contract DeployInitGov is BaseScript { + + function run() external { + vm.startBroadcast(); + + // Get the bytecode + bytes memory bytecode = abi.encodePacked(vm.getCode("InitGov.sol:InitGov")); + + // Pick an unique salt + bytes32 salt = keccak256("Init Gov"); + + address contractAddress = deployer.deployCreate2(salt, bytecode); + + console.log("Address is ", contractAddress); + + vm.stopBroadcast(); + } +} diff --git a/src/test/InitGov.t.sol b/src/test/InitGov.t.sol new file mode 100644 index 0000000..fc95cbc --- /dev/null +++ b/src/test/InitGov.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.18; + +import "forge-std/console.sol"; +import {Setup, IStrategy, SafeERC20, ERC20} from "./utils/Setup.sol"; + +import {AprOracle} from "../AprOracle/AprOracle.sol"; +import {InitGov} from "../utils/InitGov.sol"; + +contract InitGovTest is Setup { + address safe = 0x33333333D5eFb92f19a5F94a43456b3cec2797AE; + + address public constant SIGNER_ONE = + 0x6d2b80BA79871281Be7F70b079996a052B8D62F4; + address public constant SIGNER_TWO = + 0x305af52AC31d3F9Daa1EC6231bA7b36Bb40f42f4; + address public constant SIGNER_THREE = + 0xa05c4256ff0dd38697e63D48dF146e6e2FE7fe4A; + address public constant SIGNER_FOUR = + 0x623d4A04e19328244924D1dee48252987C02fC0a; + address public constant SIGNER_FIVE = + 0x5C166A5919cC07d785837d8Cc1261c67229d271D; + address public constant SIGNER_SIX = + 0x80f751EdcB3012d5AF5530AFE97d5dC6EE176Bc0; + + InitGov public initGov; + + function setUp() public override { + super.setUp(); + + initGov = new InitGov(); + + assertTrue(initGov.isSigner(SIGNER_ONE)); + assertTrue(initGov.isSigner(SIGNER_TWO)); + assertTrue(initGov.isSigner(SIGNER_THREE)); + assertTrue(initGov.isSigner(SIGNER_FOUR)); + assertTrue(initGov.isSigner(SIGNER_FIVE)); + assertTrue(initGov.isSigner(SIGNER_SIX)); + } + + function test_transferGov_withSafe() public { + AprOracle oracle = new AprOracle(address(initGov)); + + assertEq(oracle.governance(), address(initGov)); + + vm.expectRevert("!safe"); + initGov.transferGovernance(address(oracle), user); + + assertEq(oracle.governance(), address(initGov)); + + vm.expectRevert("!safe"); + vm.prank(SIGNER_ONE); + initGov.transferGovernance(address(oracle), user); + + assertEq(oracle.governance(), address(initGov)); + + vm.prank(safe); + initGov.transferGovernance(address(oracle), user); + + assertEq(oracle.governance(), user); + } + + function test_transferGov_signers() public { + AprOracle oracle = new AprOracle(address(initGov)); + + assertEq(oracle.governance(), address(initGov)); + + bytes32 id = initGov.getTxnId(address(oracle), user); + + assertEq(initGov.numberSigned(id), 0); + assertFalse(initGov.signed(SIGNER_ONE, id)); + assertFalse(initGov.signed(SIGNER_TWO, id)); + assertFalse(initGov.signed(SIGNER_THREE, id)); + assertFalse(initGov.signed(SIGNER_FOUR, id)); + assertFalse(initGov.signed(SIGNER_FIVE, id)); + assertFalse(initGov.signed(SIGNER_SIX, id)); + + vm.expectRevert("!signer"); + initGov.signTxn(address(oracle), user); + + vm.prank(SIGNER_ONE); + initGov.signTxn(address(oracle), user); + + assertEq(oracle.governance(), address(initGov)); + assertEq(initGov.numberSigned(id), 1); + assertTrue(initGov.signed(SIGNER_ONE, id)); + assertFalse(initGov.signed(SIGNER_TWO, id)); + assertFalse(initGov.signed(SIGNER_THREE, id)); + assertFalse(initGov.signed(SIGNER_FOUR, id)); + assertFalse(initGov.signed(SIGNER_FIVE, id)); + assertFalse(initGov.signed(SIGNER_SIX, id)); + + vm.expectRevert("already signed"); + vm.prank(SIGNER_ONE); + initGov.signTxn(address(oracle), user); + + assertEq(oracle.governance(), address(initGov)); + assertEq(initGov.numberSigned(id), 1); + assertTrue(initGov.signed(SIGNER_ONE, id)); + assertFalse(initGov.signed(SIGNER_TWO, id)); + assertFalse(initGov.signed(SIGNER_THREE, id)); + assertFalse(initGov.signed(SIGNER_FOUR, id)); + assertFalse(initGov.signed(SIGNER_FIVE, id)); + assertFalse(initGov.signed(SIGNER_SIX, id)); + + vm.prank(SIGNER_FOUR); + initGov.signTxn(address(oracle), user); + + assertEq(oracle.governance(), address(initGov)); + assertEq(initGov.numberSigned(id), 2); + assertTrue(initGov.signed(SIGNER_ONE, id)); + assertFalse(initGov.signed(SIGNER_TWO, id)); + assertFalse(initGov.signed(SIGNER_THREE, id)); + assertTrue(initGov.signed(SIGNER_FOUR, id)); + assertFalse(initGov.signed(SIGNER_FIVE, id)); + assertFalse(initGov.signed(SIGNER_SIX, id)); + + vm.prank(SIGNER_TWO); + initGov.signTxn(address(oracle), user); + + assertEq(oracle.governance(), user); + assertEq(initGov.numberSigned(id), 3); + assertTrue(initGov.signed(SIGNER_ONE, id)); + assertTrue(initGov.signed(SIGNER_TWO, id)); + assertFalse(initGov.signed(SIGNER_THREE, id)); + assertTrue(initGov.signed(SIGNER_FOUR, id)); + assertFalse(initGov.signed(SIGNER_FIVE, id)); + assertFalse(initGov.signed(SIGNER_SIX, id)); + } +} diff --git a/src/utils/InitGov.sol b/src/utils/InitGov.sol new file mode 100644 index 0000000..7883edc --- /dev/null +++ b/src/utils/InitGov.sol @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity ^0.8.18; + +import {Governance} from "./Governance.sol"; + +/// @notice Multi chain contract to be the initial governance for contracts on deployment. +contract InitGov { + address public constant SAFE = 0x33333333D5eFb92f19a5F94a43456b3cec2797AE; + + address public constant SIGNER_ONE = + 0x6d2b80BA79871281Be7F70b079996a052B8D62F4; + address public constant SIGNER_TWO = + 0x305af52AC31d3F9Daa1EC6231bA7b36Bb40f42f4; + address public constant SIGNER_THREE = + 0xa05c4256ff0dd38697e63D48dF146e6e2FE7fe4A; + address public constant SIGNER_FOUR = + 0x623d4A04e19328244924D1dee48252987C02fC0a; + address public constant SIGNER_FIVE = + 0x5C166A5919cC07d785837d8Cc1261c67229d271D; + address public constant SIGNER_SIX = + 0x80f751EdcB3012d5AF5530AFE97d5dC6EE176Bc0; + + uint256 public constant THRESHOLD = 3; + + mapping(address => bool) public isSigner; + + mapping(bytes32 => uint256) public numberSigned; + + mapping(address => mapping(bytes32 => bool)) public signed; + + constructor() { + isSigner[SIGNER_ONE] = true; + isSigner[SIGNER_TWO] = true; + isSigner[SIGNER_THREE] = true; + isSigner[SIGNER_FOUR] = true; + isSigner[SIGNER_FIVE] = true; + isSigner[SIGNER_SIX] = true; + } + + /// @notice To sign a txn from an eoa. + function signTxn(address _contract, address _newGovernance) external { + require(isSigner[msg.sender], "!signer"); + bytes32 id = getTxnId(_contract, _newGovernance); + require(!signed[msg.sender][id], "already signed"); + + signed[msg.sender][id] = true; + numberSigned[id] += 1; + + // Execute if we have reached the threshold. + if (numberSigned[id] == THRESHOLD) + _transferGovernance(_contract, _newGovernance); + } + + /// @notice Can only be called by safe + function transferGovernance( + address _contract, + address _newGovernance + ) external { + require(msg.sender == SAFE, "!safe"); + _transferGovernance(_contract, _newGovernance); + } + + function _transferGovernance( + address _contract, + address _newGovernance + ) internal { + Governance(_contract).transferGovernance(_newGovernance); + } + + function getTxnId( + address _contract, + address _newGovernance + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_contract, _newGovernance)); + } +}