From 0daed77ea6537e00d3f9cf353dfe0b2647e1a197 Mon Sep 17 00:00:00 2001 From: Jasmine-liang Date: Tue, 12 Jul 2022 19:57:44 +0800 Subject: [PATCH] feat: add timelock contract and deploy script --- .gas-snapshot | 30 ++++---- Makefile | 4 +- README.md | 4 + src/TheSpace/TimeLock.sol | 153 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 16 deletions(-) create mode 100644 src/TheSpace/TimeLock.sol diff --git a/.gas-snapshot b/.gas-snapshot index 36339d9..a64c0e5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,12 +1,12 @@ LogbookTest:testClaim() (gas: 134551) -LogbookTest:testDonate(uint96) (runs: 256, μ: 146160, ~: 156782) -LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 142810, ~: 140238) -LogbookTest:testFork(uint96,string) (runs: 256, μ: 426671, ~: 451048) -LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 2238066, ~: 268025) -LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 425319, ~: 257305) +LogbookTest:testDonate(uint96) (runs: 256, μ: 148188, ~: 156782) +LogbookTest:testDonateWithCommission(uint96,uint96) (runs: 256, μ: 144791, ~: 140238) +LogbookTest:testFork(uint96,string) (runs: 256, μ: 424298, ~: 451048) +LogbookTest:testForkRecursively(uint8,uint96) (runs: 256, μ: 1618051, ~: 268025) +LogbookTest:testForkWithCommission(uint96,string,uint256) (runs: 256, μ: 430473, ~: 257305) LogbookTest:testMulticall() (gas: 286758) LogbookTest:testPublicSale() (gas: 202464) -LogbookTest:testPublish(string) (runs: 256, μ: 264899, ~: 264312) +LogbookTest:testPublish(string) (runs: 256, μ: 264883, ~: 264344) LogbookTest:testPublishEn1000() (gas: 243675) LogbookTest:testPublishEn140() (gas: 221330) LogbookTest:testPublishEn200() (gas: 223090) @@ -25,17 +25,17 @@ LogbookTest:testPublishZh5000() (gas: 607910) LogbookTest:testSetDescription() (gas: 141188) LogbookTest:testSetForkPrice() (gas: 154277) LogbookTest:testSetTitle() (gas: 167036) -LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 1204465, ~: 310758) +LogbookTest:testSplitRoyalty(uint8,uint8,uint96) (runs: 256, μ: 854688, ~: 176469) LogbookTest:testWithdraw() (gas: 7464895) -LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2186202, ~: 1336263) +LogbookNFTSVGTest:testTokenURI(uint8,uint8,uint16) (runs: 256, μ: 2315936, ~: 1339654) SnapperTest:testCannotInitRegionByNotOwner() (gas: 11456) SnapperTest:testCannotReInitRegion() (gas: 14399) SnapperTest:testCannotTakeSnapshotBeforeInit() (gas: 15780) SnapperTest:testCannotTakeSnapshotByNotOwner() (gas: 12507) -SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49275) +SnapperTest:testCannotTakeSnapshotWrongLastBlock() (gas: 49438) SnapperTest:testCannotTakeSnapshotWrongSnapshotBlock() (gas: 24002) -SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114657, ~: 114657) -SnapperTest:testTakeSnapshot() (gas: 48060) +SnapperTest:testInitRegion(uint256) (runs: 256, μ: 114660, ~: 114660) +SnapperTest:testTakeSnapshot() (gas: 48223) ACLManagerTest:testCannotGrantACLManagerRole() (gas: 12276) ACLManagerTest:testCannotGrantRoleByNonACLManager() (gas: 17404) ACLManagerTest:testCannotGrantRoleToZeroAddress() (gas: 12243) @@ -48,7 +48,7 @@ ACLManagerTest:testRenounceRole() (gas: 27973) ACLManagerTest:testRoles() (gas: 15436) ACLManagerTest:testTransferRole() (gas: 21660) TheSpaceTest:testBatchBid() (gas: 692298) -TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 363366, ~: 367792) +TheSpaceTest:testBatchSetPixels(uint16,uint8) (runs: 256, μ: 363493, ~: 367792) TheSpaceTest:testBidDefaultedToken() (gas: 410646) TheSpaceTest:testBidExistingToken() (gas: 356958) TheSpaceTest:testBidNewToken() (gas: 301097) @@ -80,7 +80,7 @@ TheSpaceTest:testGetPixelsPageByOwnerWithPixels() (gas: 586023) TheSpaceTest:testGetPrice() (gas: 297936) TheSpaceTest:testGetTax() (gas: 377402) TheSpaceTest:testGetTokenImageURI() (gas: 14461) -TheSpaceTest:testGetTokenURI() (gas: 331478) +TheSpaceTest:testGetTokenURI() (gas: 331496) TheSpaceTest:testSetColor() (gas: 328913) TheSpaceTest:testSetMintTax() (gas: 268980) TheSpaceTest:testSetPixel(uint256) (runs: 256, μ: 400887, ~: 400887) @@ -88,13 +88,13 @@ TheSpaceTest:testSetPrice(uint256) (runs: 256, μ: 302087, ~: 302087) TheSpaceTest:testSetPriceByOperator(uint256) (runs: 256, μ: 349876, ~: 349876) TheSpaceTest:testSetPriceTooHigh() (gas: 312185) TheSpaceTest:testSetTaxRate() (gas: 345290) -TheSpaceTest:testSetTokenImageURI() (gas: 354173) +TheSpaceTest:testSetTokenImageURI() (gas: 354371) TheSpaceTest:testSetTotalSupply(uint256) (runs: 256, μ: 349455, ~: 349457) TheSpaceTest:testSetTreasuryShare() (gas: 381549) TheSpaceTest:testSettleTax() (gas: 336739) TheSpaceTest:testTaxCalculation() (gas: 401824) TheSpaceTest:testTokenShouldBeDefaulted() (gas: 322889) TheSpaceTest:testTotalSupply() (gas: 7568) -TheSpaceTest:testUpgradeTo() (gas: 3197912) +TheSpaceTest:testUpgradeTo() (gas: 3209811) TheSpaceTest:testWithdrawTreasury() (gas: 352481) TheSpaceTest:testWithdrawUBI() (gas: 375552) diff --git a/Makefile b/Makefile index 1e3f189..7a578db 100644 --- a/Makefile +++ b/Makefile @@ -24,9 +24,11 @@ seeds-logbook:; @./bin/seed-logbook.sh ## The Space deploy-the-space-currency: clean @forge create SpaceToken --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${THESPACE_TREASURY_ADDRESS} --constructor-args ${THESPACE_TREASURY_TOKENS} --constructor-args ${THESPACE_TEAM_ADDRESS} --constructor-args ${THESPACE_TEAM_TOKENS} --constructor-args ${THESPACE_INCENTIVES_ADDRESS} --constructor-args ${THESPACE_INCENTIVES_TOKENS} --constructor-args ${THESPACE_LP_ADDRESS} --constructor-args ${THESPACE_LP_TOKENS} --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} - deploy-the-space: clean @forge create TheSpace --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${THESPACE_CURRENCY_ADDRESS} --constructor-args ${THESPACE_REGISTRY_ADDRESS} --constructor-args ${THESPACE_TOKEN_IMAGE_URI} --constructor-args ${THESPACE_ACL_MANAGER_ADDRESS} --constructor-args ${THESPACE_MARKET_ADMIN_ADDRESS} --constructor-args ${THESPACE_TREASURY_ADMIN_ADDRESS} --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} +deploy-the-space-timelock:clean + @forge create Timelock --rpc-url ${ETH_RPC_URL} --private-key ${DEPLOYER_PRIVATE_KEY} --constructor-args ${THESPACE_ACL_MANAGER_ADDRESS} --constructor-args ${TIMELOCK_DELAY} --legacy --verify --etherscan-api-key ${ETHERSCAN_API_KEY} + ## snapper deploy-snapper: clean diff --git a/README.md b/README.md index 08b4e41..31099ba 100644 --- a/README.md +++ b/README.md @@ -102,9 +102,13 @@ make deploy-logbook # Deploy currency first, then add the contract address to THESPACE_CURRENCY_ADDRESS env variable make deploy-the-space-currency + # Deploy the space contract make deploy-the-space +# Deploy the space contract +make deploy-the-space-timelock + # Deploy the snapper contract make deploy-snapper ``` diff --git a/src/TheSpace/TimeLock.sol b/src/TheSpace/TimeLock.sol new file mode 100644 index 0000000..8ce6be8 --- /dev/null +++ b/src/TheSpace/TimeLock.sol @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.10; + +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; + +contract Timelock { + using SafeMath for uint256; + + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint256 indexed newDelay); + event CancelTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event ExecuteTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + event QueueTransaction( + bytes32 indexed txHash, + address indexed target, + uint256 value, + string signature, + bytes data, + uint256 eta + ); + + uint256 public constant GRACE_PERIOD = 15 days; + uint256 public constant MINIMUM_DELAY = 1; + uint256 public constant MAXIMUM_DELAY = 1 hours; + + address public admin; + address public pendingAdmin; + uint256 public delay; + + mapping(bytes32 => bool) public queuedTransactions; + + constructor(address admin_, uint256 delay_) public { + require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + + admin = admin_; + delay = delay_; + } + + fallback() external payable {} + + function setDelay(uint256 delay_) public { + require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); + require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + delay = delay_; + + emit NewDelay(delay); + } + + function acceptAdmin() public { + require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); + admin = msg.sender; + pendingAdmin = address(0); + + emit NewAdmin(admin); + } + + function setPendingAdmin(address pendingAdmin_) public { + require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); + pendingAdmin = pendingAdmin_; + + emit NewPendingAdmin(pendingAdmin); + } + + function queueTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public returns (bytes32) { + require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); + require( + eta >= getBlockTimestamp().add(delay), + "Timelock::queueTransaction: Estimated execution block must satisfy delay." + ); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, eta); + return txHash; + } + + function cancelTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public { + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, eta); + } + + function executeTransaction( + address target, + uint256 value, + string memory signature, + bytes memory data, + uint256 eta + ) public payable returns (bytes memory) { + require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + require(getBlockTimestamp() <= eta.add(GRACE_PERIOD), "Timelock::executeTransaction: Transaction is stale."); + + queuedTransactions[txHash] = false; + + bytes memory callData; + + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + + // solium-disable-next-line security/no-call-value + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, eta); + + return returnData; + } + + function getBlockTimestamp() internal view returns (uint256) { + // solium-disable-next-line security/no-block-members + return block.timestamp; + } +}