diff --git a/.gitmodules b/.gitmodules index bd03c845..f27e450e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,7 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable \ No newline at end of file + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades diff --git a/foundry.toml b/foundry.toml index 8847712d..55084799 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,12 +12,19 @@ remappings = [ 'ds-test/=lib/forge-std/lib/ds-test/src/', 'erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/', 'forge-std/=lib/forge-std/src/', - '@openzeppelin/=lib/openzeppelin-contracts/', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', 'openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/', 'openzeppelin-contracts/=lib/openzeppelin-contracts/', + '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/', + 'openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/', + 'solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/', ] +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] + [fmt] line_length = 120 multiline_func_header = 'all' diff --git a/lib/forge-std b/lib/forge-std index ae570fec..978ac6fa 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 00000000..4cd15fc5 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit 4cd15fc50b141c77d8cc9ff8efb44d00e841a299 diff --git a/script/contracts/L2/paused/L2GovernorPaused.s.sol b/script/contracts/L2/paused/L2GovernorPaused.s.sol new file mode 100644 index 00000000..831fd01e --- /dev/null +++ b/script/contracts/L2/paused/L2GovernorPaused.s.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import { Options } from "openzeppelin-foundry-upgrades/Options.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2GovernorPaused } from "src/L2/paused/L2GovernorPaused.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2GovernorPausedScript - L2GovernorPaused contract deployment script +/// @notice This contract is used to deploy L2GovernorPaused contract and write its address to JSON file. +contract L2GovernorPausedScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L1 and L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2GovernorPaused contract and writes its address to JSON file. + function run() public { + // Deployer's private key. This key is used to deploy the contract. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + // Validate L2GovernorPaused contract if it is implemented correctly so that it may be used as new + // implementation for the proxy contract. + Options memory opts; + opts.referenceContract = "L2Governor.sol"; + opts.unsafeAllow = "constructor"; + Upgrades.validateUpgrade("L2GovernorPaused.sol", opts); + + console2.log("Deploying L2GovernorPaused contract..."); + + // deploy L2GovernorPaused contract + vm.startBroadcast(deployerPrivateKey); + L2GovernorPaused l2GovernorPaused = new L2GovernorPaused(); + vm.stopBroadcast(); + + assert(address(l2GovernorPaused) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert(l2GovernorPaused.proxiableUUID() == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)); + + console2.log("L2GovernorPaused contract successfully deployed!"); + console2.log("L2GovernorPaused (Implementation) address: %s", address(l2GovernorPaused)); + + // write L2GovernorPaused address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(); + l2AddressesConfig.L2GovernorPaused = address(l2GovernorPaused); + utils.writeL2AddressesFile(l2AddressesConfig); + } +} diff --git a/script/contracts/L2/paused/L2VotingPowerPaused.s.sol b/script/contracts/L2/paused/L2VotingPowerPaused.s.sol new file mode 100644 index 00000000..bc721c5b --- /dev/null +++ b/script/contracts/L2/paused/L2VotingPowerPaused.s.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import { Options } from "openzeppelin-foundry-upgrades/Options.sol"; +import { Script, console2 } from "forge-std/Script.sol"; +import { L2VotingPowerPaused } from "src/L2/paused/L2VotingPowerPaused.sol"; +import "script/contracts/Utils.sol"; + +/// @title L2VotingPowerPausedScript - L2VotingPowerPaused contract deployment script +/// @notice This contract is used to deploy L2VotingPowerPaused contract and write its address to JSON file. +contract L2VotingPowerPausedScript is Script { + /// @notice Utils contract which provides functions to read and write JSON files containing L1 and L2 addresses. + Utils utils; + + function setUp() public { + utils = new Utils(); + } + + /// @notice This function deploys L2VotingPowerPaused contract and writes its address to JSON file. + function run() public { + // Deployer's private key. This key is used to deploy the contract. + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + + // Validate L2VotingPowerPaused contract if it is implemented correctly so that it may be used as new + // implementation for the proxy contract. + Options memory opts; + opts.referenceContract = "L2VotingPower.sol"; + opts.unsafeAllow = "constructor"; + Upgrades.validateUpgrade("L2VotingPowerPaused.sol", opts); + + console2.log("Deploying L2VotingPowerPaused contract..."); + + // deploy L2VotingPowerPaused contract + vm.startBroadcast(deployerPrivateKey); + L2VotingPowerPaused l2VotingPowerPausedImplementation = new L2VotingPowerPaused(); + vm.stopBroadcast(); + + assert(address(l2VotingPowerPausedImplementation) != address(0)); + + // ERC1967Utils: keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1. + assert( + l2VotingPowerPausedImplementation.proxiableUUID() + == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) + ); + + console2.log("L2VotingPowerPaused contract successfully deployed!"); + console2.log("L2VotingPowerPaused (Implementation) address: %s", address(l2VotingPowerPausedImplementation)); + + // write L2VotingPowerPaused address to l2addresses.json + Utils.L2AddressesConfig memory l2AddressesConfig = utils.readL2AddressesFile(); + l2AddressesConfig.L2VotingPowerPaused = address(l2VotingPowerPausedImplementation); + utils.writeL2AddressesFile(l2AddressesConfig); + } +} diff --git a/script/contracts/Utils.sol b/script/contracts/Utils.sol index 3402ebf8..334670f6 100644 --- a/script/contracts/Utils.sol +++ b/script/contracts/Utils.sol @@ -29,6 +29,8 @@ contract Utils is Script { address L2Governor; /// @notice The Current implementation of L2 Governor Contract. address L2GovernorImplementation; + /// @notice The Current implementation of L2GovernorPaused Contract. + address L2GovernorPaused; /// @notice L2 Lisk token address. address L2LiskToken; /// @notice L2 Locking Position contract (in Proxy), which users interact with. @@ -51,6 +53,8 @@ contract Utils is Script { address L2VotingPower; /// @notice The Current implementation of L2 Voting Power Contract. address L2VotingPowerImplementation; + /// @notice The Current implementation of L2VotingPowerPaused Contract. + address L2VotingPowerPaused; } /// @notice This struct is used to read MerkleRoot from JSON file. @@ -174,6 +178,10 @@ contract Utils is Script { l2AddressesConfig.L2GovernorImplementation = l2GovernorImplementation; } catch { } + try vm.parseJsonAddress(addressJson, ".L2GovernorPaused") returns (address l2GovernorPaused) { + l2AddressesConfig.L2GovernorPaused = l2GovernorPaused; + } catch { } + try vm.parseJsonAddress(addressJson, ".L2LiskToken") returns (address l2LiskToken) { l2AddressesConfig.L2LiskToken = l2LiskToken; } catch { } @@ -223,6 +231,9 @@ contract Utils is Script { ) { l2AddressesConfig.L2VotingPowerImplementation = l2VotingPowerImplementation; } catch { } + try vm.parseJsonAddress(addressJson, ".L2VotingPowerPaused") returns (address l2VotingPowerPaused) { + l2AddressesConfig.L2VotingPowerPaused = l2VotingPowerPaused; + } catch { } return l2AddressesConfig; } @@ -237,6 +248,7 @@ contract Utils is Script { vm.serializeAddress(json, "L2ClaimImplementation", cfg.L2ClaimImplementation); vm.serializeAddress(json, "L2Governor", cfg.L2Governor); vm.serializeAddress(json, "L2GovernorImplementation", cfg.L2GovernorImplementation); + vm.serializeAddress(json, "L2GovernorPaused", cfg.L2GovernorPaused); vm.serializeAddress(json, "L2LiskToken", cfg.L2LiskToken); vm.serializeAddress(json, "L2LockingPosition", cfg.L2LockingPosition); vm.serializeAddress(json, "L2LockingPositionImplementation", cfg.L2LockingPositionImplementation); @@ -247,8 +259,8 @@ contract Utils is Script { vm.serializeAddress(json, "L2TimelockController", cfg.L2TimelockController); vm.serializeAddress(json, "L2VestingWalletImplementation", cfg.L2VestingWalletImplementation); vm.serializeAddress(json, "L2VotingPower", cfg.L2VotingPower); - string memory finalJson = - vm.serializeAddress(json, "L2VotingPowerImplementation", cfg.L2VotingPowerImplementation); + vm.serializeAddress(json, "L2VotingPowerImplementation", cfg.L2VotingPowerImplementation); + string memory finalJson = vm.serializeAddress(json, "L2VotingPowerPaused", cfg.L2VotingPowerPaused); finalJson.write(string.concat("deployment/", network, "/l2addresses.json")); } diff --git a/script/paused/deployPausedDAO.sh b/script/paused/deployPausedDAO.sh new file mode 100755 index 00000000..513c31a8 --- /dev/null +++ b/script/paused/deployPausedDAO.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env bash + +echo "Instructing the shell to exit immediately if any command returns a non-zero exit status..." +set -e +echo "Done." + +echo "Navigating to the root directory of the project..." +cd ../../ +echo "Done." + +echo "Setting environment variables..." +source .env +echo "Done." + +echo "Creating $NETWORK directory inside deployment directory..." +if [ -z "$NETWORK" ] +then + echo "NETWORK variable inside .env file is not set. Please set NETWORK environment variable." + exit 1 +else + if [ -d "deployment/$NETWORK" ] + then + echo "Directory deployment/$NETWORK already exists." + else + mkdir deployment/$NETWORK + fi +fi +echo "Done." + +echo "Cleaning up the build artifacts to be able to deploy the contract..." +forge clean +echo "Done." + +echo "Deploying and if enabled verifying L2GovernorPaused smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/paused/L2GovernorPaused.s.sol:L2GovernorPausedScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/paused/L2GovernorPaused.s.sol:L2GovernorPausedScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/paused/L2GovernorPaused.s.sol:L2GovernorPausedScript + fi +fi +echo "Done." + +echo "Cleaning up the build artifacts to be able to deploy the contract..." +forge clean +echo "Done." + +echo "Deploying and if enabled verifying L2VotingPowerPaused smart contract..." +if [ -z "$CONTRACT_VERIFIER" ] +then + forge script --rpc-url="$L2_RPC_URL" --broadcast -vvvv script/contracts/L2/paused/L2VotingPowerPaused.s.sol:L2VotingPowerPausedScript +else + if [ $CONTRACT_VERIFIER = "blockscout" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier blockscout --verifier-url $L2_VERIFIER_URL -vvvv script/contracts/L2/paused/L2VotingPowerPaused.s.sol:L2VotingPowerPausedScript + fi + if [ $CONTRACT_VERIFIER = "etherscan" ] + then + forge script --rpc-url="$L2_RPC_URL" --broadcast --verify --verifier etherscan --etherscan-api-key="$L2_ETHERSCAN_API_KEY" -vvvv script/contracts/L2/paused/L2VotingPowerPaused.s.sol:L2VotingPowerPausedScript + fi +fi +echo "Done." \ No newline at end of file diff --git a/src/L2/paused/L2GovernorPaused.sol b/src/L2/paused/L2GovernorPaused.sol new file mode 100644 index 00000000..b617a922 --- /dev/null +++ b/src/L2/paused/L2GovernorPaused.sol @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { TimelockControllerUpgradeable } from + "@openzeppelin-upgradeable/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import { L2Governor } from "../L2Governor.sol"; + +contract L2GovernorPaused is L2Governor { + error GovernorIsPaused(); + + /// @notice Setting global params. + function initializePaused() public reinitializer(2) { } + + /// @notice Marking version as paused. + function version() public pure virtual override returns (string memory) { + return "1.0.0-paused"; + } + + /// @notice Override the cancel function to pause Governor interactions. + function cancel( + address[] memory, + uint256[] memory, + bytes[] memory, + bytes32 + ) + public + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the castVote function to pause Governor interactions. + function castVote(uint256, uint8) public virtual override returns (uint256) { + revert GovernorIsPaused(); + } + + /// @notice Override the castVoteBySig function to pause Governor interactions. + function castVoteBySig(uint256, uint8, address, bytes memory) public virtual override returns (uint256) { + revert GovernorIsPaused(); + } + + /// @notice Override the castVoteWithReason function to pause Governor interactions. + function castVoteWithReason(uint256, uint8, string calldata) public virtual override returns (uint256) { + revert GovernorIsPaused(); + } + + /// @notice Override the castVoteWithReasonAndParams function to pause Governor interactions. + function castVoteWithReasonAndParams( + uint256, + uint8, + string calldata, + bytes memory + ) + public + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the castVoteWithReasonAndParamsBySig function to pause Governor interactions. + function castVoteWithReasonAndParamsBySig( + uint256, + uint8, + address, + string calldata, + bytes memory, + bytes memory + ) + public + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the execute function to pause Governor interactions. + function execute( + address[] memory, + uint256[] memory, + bytes[] memory, + bytes32 + ) + public + payable + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the onERC1155BatchReceived function to pause Governor interactions. + function onERC1155BatchReceived( + address, + address, + uint256[] memory, + uint256[] memory, + bytes memory + ) + public + virtual + override + returns (bytes4) + { + revert GovernorIsPaused(); + } + + /// @notice Override the onERC1155Received function to pause Governor interactions. + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes memory + ) + public + virtual + override + returns (bytes4) + { + revert GovernorIsPaused(); + } + + /// @notice Override the onERC721Received function to pause Governor interactions. + function onERC721Received(address, address, uint256, bytes memory) public virtual override returns (bytes4) { + revert GovernorIsPaused(); + } + + /// @notice Override the propose function to pause Governor interactions. + function propose( + address[] memory, + uint256[] memory, + bytes[] memory, + string memory + ) + public + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the queue function to pause Governor interactions. + function queue( + address[] memory, + uint256[] memory, + bytes[] memory, + bytes32 + ) + public + virtual + override + returns (uint256) + { + revert GovernorIsPaused(); + } + + /// @notice Override the relay function to pause Governor interactions. + function relay(address, uint256, bytes memory) public payable virtual override { + revert GovernorIsPaused(); + } + + /// @notice Override the setProposalThreshold function to pause Governor interactions. + function setProposalThreshold(uint256) public virtual override { + revert GovernorIsPaused(); + } + + /// @notice Override the setVotingDelay function to pause Governor interactions. + function setVotingDelay(uint48) public virtual override { + revert GovernorIsPaused(); + } + + /// @notice Override the setVotingPeriod function to pause Governor interactions. + function setVotingPeriod(uint32) public virtual override { + revert GovernorIsPaused(); + } + + /// @notice Override the updateTimelock function to pause Governor interactions. + function updateTimelock(TimelockControllerUpgradeable) public virtual override { + revert GovernorIsPaused(); + } +} diff --git a/src/L2/paused/L2VotingPowerPaused.sol b/src/L2/paused/L2VotingPowerPaused.sol new file mode 100644 index 00000000..249e1a89 --- /dev/null +++ b/src/L2/paused/L2VotingPowerPaused.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { Ownable2StepUpgradeable } from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { L2VotingPower } from "../L2VotingPower.sol"; + +contract L2VotingPowerPaused is L2VotingPower { + error VotingPowerIsPaused(); + + /// @notice Setting global params. + function initializePaused() public reinitializer(2) { + version = "1.0.0-paused"; + } + + /// @notice Override the modifyLockingPosition function to pause VotingPower interactions. + function delegate(address) public virtual override { + revert VotingPowerIsPaused(); + } + + /// @notice Override the modifyLockingPosition function to pause VotingPower interactions. + function delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32) public virtual override { + revert VotingPowerIsPaused(); + } +} diff --git a/test/L2/paused/L2GovernorPaused.t.sol b/test/L2/paused/L2GovernorPaused.t.sol new file mode 100644 index 00000000..41cd0720 --- /dev/null +++ b/test/L2/paused/L2GovernorPaused.t.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { IVotes } from "@openzeppelin-upgradeable/contracts/governance/extensions/GovernorVotesUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { TimelockControllerUpgradeable } from + "@openzeppelin-upgradeable/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import { Test, console } from "forge-std/Test.sol"; +import { L2Governor } from "src/L2/L2Governor.sol"; +import { L2GovernorPaused } from "src/L2/paused/L2GovernorPaused.sol"; +import { Utils } from "script/contracts/Utils.sol"; +import { MockERC721 } from "test/mock/MockERC721.sol"; +import { MockERC1155 } from "test/mock/MockERC1155.sol"; + +contract MockL2GovernorV2 is L2Governor { + function version() public pure virtual override returns (string memory) { + return "2.0.0"; + } +} + +contract L2GovernorPausedTest is Test, ERC1155Holder, ERC721Holder { + Utils public utils; + L2Governor public l2GovernorImplementation; + L2Governor public l2Governor; + + IVotes votingPower; + address[] executors; + TimelockController timelock; + address initialOwner; + + function assertInitParamsEq() internal { + assertEq(l2Governor.name(), "Lisk Governor"); + assertEq(l2Governor.votingDelay(), 0); + assertEq(l2Governor.votingPeriod(), 604800); + assertEq(l2Governor.proposalThreshold(), 300_000 * 10 ** 18); + assertEq(l2Governor.timelock(), address(timelock)); + assertEq(l2Governor.quorum(0), 24_000_000 * 10 ** 18); + assertEq(address(l2Governor.token()), address(votingPower)); + assertEq(l2Governor.owner(), initialOwner); + } + + function setUp() public { + utils = new Utils(); + + // set initial values + votingPower = IVotes(address(0x1)); + executors.push(address(0)); // executor array contains address(0) such that anyone can execute proposals + timelock = new TimelockController(0, new address[](0), executors, address(this)); + initialOwner = address(this); + + console.log("L2GovernorTest address is: %s", address(this)); + + // deploy L2Governor Implementation contract + l2GovernorImplementation = new L2Governor(); + + // deploy L2Governor contract via proxy and initialize it at the same time + l2Governor = L2Governor( + payable( + address( + new ERC1967Proxy( + address(l2GovernorImplementation), + abi.encodeWithSelector(l2Governor.initialize.selector, votingPower, timelock, initialOwner) + ) + ) + ) + ); + + assertInitParamsEq(); + assertEq(l2Governor.version(), "1.0.0"); + + // assure that address(0) is in executors role + assertEq(timelock.hasRole(timelock.EXECUTOR_ROLE(), address(0)), true); + + // Upgrade from L2Governor to L2GovernorPaused, and call initializePaused + L2GovernorPaused l2GovernorPausedImplementation = new L2GovernorPaused(); + l2Governor.upgradeToAndCall( + address(l2GovernorPausedImplementation), + abi.encodeWithSelector(l2GovernorPausedImplementation.initializePaused.selector) + ); + assertEq(l2Governor.version(), "1.0.0-paused"); + + // Ensure all other params are unchanged after paused contract update + assertInitParamsEq(); + } + + function test_Cancel_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.cancel(new address[](1), new uint256[](1), new bytes[](1), 0); + } + + function test_CastVote_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.castVote(0, 0); + } + + function test_CastVoteBySig_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.castVoteBySig(0, 0, address(0), ""); + } + + function test_CastVoteWithReason_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.castVoteWithReason(0, 0, ""); + } + + function test_CastVoteWithReasonAndParams_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.castVoteWithReasonAndParams(0, 0, "", ""); + } + + function test_CastVoteWithReasonAndParamsBySig_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.castVoteWithReasonAndParamsBySig(0, 0, address(0), "", "", ""); + } + + function test_Execute_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.execute(new address[](1), new uint256[](1), new bytes[](1), 0); + } + + function test_OnERC1155BatchReceived_Paused() public { + MockERC1155 mockERC1155 = new MockERC1155(); + mockERC1155.mint(address(this), 0, 10, ""); + mockERC1155.mint(address(this), 1, 20, ""); + + uint256[] memory ids = new uint256[](2); + ids[0] = 0; + ids[1] = 1; + + uint256[] memory values = new uint256[](2); + values[0] = 10; + values[1] = 20; + + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + mockERC1155.safeBatchTransferFrom(address(this), address(l2Governor), ids, values, ""); + } + + function test_OnERC1155Received_Paused() public { + MockERC1155 mockERC1155 = new MockERC1155(); + mockERC1155.mint(address(this), 0, 10, ""); + + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + mockERC1155.safeTransferFrom(address(this), address(l2Governor), 0, 10, ""); + } + + function test_OnERC721Received_Paused() public { + MockERC721 mockERC721 = new MockERC721(); + mockERC721.mint(address(this), 0); + + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + mockERC721.safeTransferFrom(address(this), address(l2Governor), 0); + } + + function test_Propose_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.propose(new address[](1), new uint256[](1), new bytes[](1), ""); + } + + function test_Queue_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.queue(new address[](1), new uint256[](1), new bytes[](1), ""); + } + + function test_Relay_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.relay(address(0), 0, ""); + } + + function test_SetProposalThreshold_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.setProposalThreshold(0); + } + + function test_SetVotingDelay_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.setVotingDelay(0); + } + + function test_SetVotingPeriod_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.setVotingPeriod(0); + } + + function test_UpdateTimelock_Paused() public { + vm.expectRevert(L2GovernorPaused.GovernorIsPaused.selector); + l2Governor.updateTimelock(TimelockControllerUpgradeable(payable(address(0)))); + } + + function test_UpgradeToAndCall_CanUpgradeFromPausedContractToNewContract() public { + MockL2GovernorV2 mockL2GovernorV2Implementation = new MockL2GovernorV2(); + + // upgrade contract + l2Governor.upgradeToAndCall(address(mockL2GovernorV2Implementation), ""); + + // new version updated + assertEq(l2Governor.version(), "2.0.0"); + + // Ensure all other params are unchanged after non-paused contract update + assertInitParamsEq(); + } +} diff --git a/test/L2/paused/L2VotingPowerPaused.t.sol b/test/L2/paused/L2VotingPowerPaused.t.sol new file mode 100644 index 00000000..0fdccb88 --- /dev/null +++ b/test/L2/paused/L2VotingPowerPaused.t.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { IVotes } from "@openzeppelin/contracts/governance/utils/IVotes.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; +import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol"; +import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { Test, console } from "forge-std/Test.sol"; +import { L2VotingPower } from "src/L2/L2VotingPower.sol"; +import { L2VotingPowerPaused } from "src/L2/paused/L2VotingPowerPaused.sol"; +import { IL2LockingPosition } from "src/interfaces/L2/IL2LockingPosition.sol"; +import { Utils } from "script/contracts/Utils.sol"; + +contract MockL2VotingPowerV2 is L2VotingPower { + uint256 public testNumber; + + function initializeV2(uint256 _testNumber) public reinitializer(3) { + version = "2.0.0"; + testNumber = _testNumber; + } + + function onlyV2() public pure returns (string memory) { + return "Only L2VotingPowerV2 have this function"; + } +} + +contract L2VotingPowerPausedTest is Test { + Utils public utils; + L2VotingPower public l2VotingPowerImplementation; + L2VotingPower public l2VotingPower; + + address lockingPositionContractAddress; + + function assertInitParamsEq() internal { + assertEq(l2VotingPower.lockingPositionAddress(), lockingPositionContractAddress); + assertEq(l2VotingPower.name(), "Lisk Voting Power"); + assertEq(l2VotingPower.symbol(), "vpLSK"); + } + + function setUp() public { + utils = new Utils(); + + // set initial values + lockingPositionContractAddress = address(0xdeadbeefdeadbeefdeadbeef); + + console.log("L2VotingPowerTest address is: %s", address(this)); + + // deploy L2VotingPower Implementation contract + l2VotingPowerImplementation = new L2VotingPower(); + + // deploy L2VotingPower contract via proxy and initialize it at the same time + l2VotingPower = L2VotingPower( + address( + new ERC1967Proxy( + address(l2VotingPowerImplementation), + abi.encodeWithSelector(l2VotingPower.initialize.selector, lockingPositionContractAddress) + ) + ) + ); + + assertInitParamsEq(); + assertEq(l2VotingPower.version(), "1.0.0"); + + // Upgrade from L2VotingPower to L2VotingPowerPaused, and call initializePaused + L2VotingPowerPaused l2VotingPowerPausedImplementation = new L2VotingPowerPaused(); + l2VotingPower.upgradeToAndCall( + address(l2VotingPowerPausedImplementation), + abi.encodeWithSelector(l2VotingPowerPausedImplementation.initializePaused.selector) + ); + + // l2VotingPower pointing to paused contract + assertEq(l2VotingPower.version(), "1.0.0-paused"); + + // Ensure all other params are unchanged after paused contract update + assertInitParamsEq(); + } + + function test_AdjustVotingPower_NotPaused() public { + IL2LockingPosition.LockingPosition memory positionBefore = + IL2LockingPosition.LockingPosition(address(this), 50, 0, 0); + IL2LockingPosition.LockingPosition memory positionAfter = + IL2LockingPosition.LockingPosition(address(this), 100, 0, 0); + + // call it as LockingPosition contract + vm.prank(lockingPositionContractAddress); + + // Throws the require check on the first line, proving this function is not paused + vm.expectRevert("L2VotingPower: owner address cannot be 0"); + l2VotingPower.adjustVotingPower(address(0), positionBefore, positionAfter); + } + + function test_Delegate_Paused() public { + vm.expectRevert(L2VotingPowerPaused.VotingPowerIsPaused.selector); + l2VotingPower.delegate(address(0)); + } + + function test_DelegateBySig_Paused() public { + vm.expectRevert(L2VotingPowerPaused.VotingPowerIsPaused.selector); + l2VotingPower.delegateBySig(address(0), 0, 0, 0, "", ""); + } + + function test_UpgradeToAndCall_CanUpgradeFromPausedContractToNewContract() public { + MockL2VotingPowerV2 mockL2VotingPowerV2Implementation = new MockL2VotingPowerV2(); + + uint256 testNumber = 123; + + // upgrade contract, and also change some variables by reinitialize + l2VotingPower.upgradeToAndCall( + address(mockL2VotingPowerV2Implementation), + abi.encodeWithSelector(mockL2VotingPowerV2Implementation.initializeV2.selector, testNumber) + ); + + // new testNumber variable introduced + assertEq(MockL2VotingPowerV2(address(l2VotingPower)).testNumber(), testNumber); + + // new version updated + assertEq(l2VotingPower.version(), "2.0.0"); + } +} diff --git a/test/mock/MockERC1155.sol b/test/mock/MockERC1155.sol new file mode 100644 index 00000000..897371bb --- /dev/null +++ b/test/mock/MockERC1155.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +/// @title MockERC1155 +/// @notice MockERC1155 is a mock implementation of ERC1155 token. +/// IT SHOULD NEVER BE USED IN PRODUCTION. +contract MockERC1155 is ERC1155 { + constructor() ERC1155("") { } + + function mint(address _to, uint256 _id, uint256 _value, bytes memory _data) public { + _mint(_to, _id, _value, _data); + } +} diff --git a/test/mock/MockERC721.sol b/test/mock/MockERC721.sol new file mode 100644 index 00000000..18ea7af8 --- /dev/null +++ b/test/mock/MockERC721.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.23; + +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +/// @title MockERC721 +/// @notice MockERC721 is a mock implementation of ERC721 token. +/// IT SHOULD NEVER BE USED IN PRODUCTION. +contract MockERC721 is ERC721 { + constructor() ERC721("MockERC721", "ERC721") { } + + function mint(address _to, uint256 _id) public { + _mint(_to, _id); + } +}