diff --git a/.openzeppelin/base-sepolia.json b/.openzeppelin/base-sepolia.json
index e433349..c65d360 100644
--- a/.openzeppelin/base-sepolia.json
+++ b/.openzeppelin/base-sepolia.json
@@ -1372,6 +1372,482 @@
"contract": "DLCManager",
"src": "contracts/DLCManager.sol:47"
},
+ {
+ "label": "dlcs",
+ "offset": 0,
+ "slot": "252",
+ "type": "t_mapping(t_uint256,t_struct(DLC)9268_storage)",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:48"
+ },
+ {
+ "label": "dlcIDsByUUID",
+ "offset": 0,
+ "slot": "253",
+ "type": "t_mapping(t_bytes32,t_uint256)",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:49"
+ },
+ {
+ "label": "_minimumThreshold",
+ "offset": 0,
+ "slot": "254",
+ "type": "t_uint16",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:51"
+ },
+ {
+ "label": "_threshold",
+ "offset": 2,
+ "slot": "254",
+ "type": "t_uint16",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:52"
+ },
+ {
+ "label": "_signerCount",
+ "offset": 4,
+ "slot": "254",
+ "type": "t_uint16",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:53"
+ },
+ {
+ "label": "tssCommitment",
+ "offset": 0,
+ "slot": "255",
+ "type": "t_bytes32",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:54"
+ },
+ {
+ "label": "attestorGroupPubKey",
+ "offset": 0,
+ "slot": "256",
+ "type": "t_string_storage",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:55"
+ },
+ {
+ "label": "dlcBTC",
+ "offset": 0,
+ "slot": "257",
+ "type": "t_contract(DLCBTC)9226",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:57"
+ },
+ {
+ "label": "btcFeeRecipient",
+ "offset": 0,
+ "slot": "258",
+ "type": "t_string_storage",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:58"
+ },
+ {
+ "label": "minimumDeposit",
+ "offset": 0,
+ "slot": "259",
+ "type": "t_uint256",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:59"
+ },
+ {
+ "label": "maximumDeposit",
+ "offset": 0,
+ "slot": "260",
+ "type": "t_uint256",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:60"
+ },
+ {
+ "label": "btcMintFeeRate",
+ "offset": 0,
+ "slot": "261",
+ "type": "t_uint256",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:61"
+ },
+ {
+ "label": "btcRedeemFeeRate",
+ "offset": 0,
+ "slot": "262",
+ "type": "t_uint256",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:62"
+ },
+ {
+ "label": "whitelistingEnabled",
+ "offset": 0,
+ "slot": "263",
+ "type": "t_bool",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:63"
+ },
+ {
+ "label": "userVaults",
+ "offset": 0,
+ "slot": "264",
+ "type": "t_mapping(t_address,t_array(t_bytes32)dyn_storage)",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:65"
+ },
+ {
+ "label": "_whitelistedAddresses",
+ "offset": 0,
+ "slot": "265",
+ "type": "t_mapping(t_address,t_bool)",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:66"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "266",
+ "type": "t_array(t_uint256)41_storage",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:67"
+ }
+ ],
+ "types": {
+ "t_address": {
+ "label": "address",
+ "numberOfBytes": "20"
+ },
+ "t_array(t_bytes32)dyn_storage": {
+ "label": "bytes32[]",
+ "numberOfBytes": "32"
+ },
+ "t_array(t_uint256)41_storage": {
+ "label": "uint256[41]",
+ "numberOfBytes": "1312"
+ },
+ "t_array(t_uint256)48_storage": {
+ "label": "uint256[48]",
+ "numberOfBytes": "1536"
+ },
+ "t_array(t_uint256)49_storage": {
+ "label": "uint256[49]",
+ "numberOfBytes": "1568"
+ },
+ "t_array(t_uint256)50_storage": {
+ "label": "uint256[50]",
+ "numberOfBytes": "1600"
+ },
+ "t_bool": {
+ "label": "bool",
+ "numberOfBytes": "1"
+ },
+ "t_bytes32": {
+ "label": "bytes32",
+ "numberOfBytes": "32"
+ },
+ "t_contract(DLCBTC)9226": {
+ "label": "contract DLCBTC",
+ "numberOfBytes": "20"
+ },
+ "t_enum(DLCStatus)9238": {
+ "label": "enum DLCLink.DLCStatus",
+ "members": [
+ "READY",
+ "FUNDED",
+ "CLOSING",
+ "CLOSED",
+ "AUX_STATE_1",
+ "AUX_STATE_2",
+ "AUX_STATE_3",
+ "AUX_STATE_4",
+ "AUX_STATE_5"
+ ],
+ "numberOfBytes": "1"
+ },
+ "t_mapping(t_address,t_array(t_bytes32)dyn_storage)": {
+ "label": "mapping(address => bytes32[])",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_address,t_bool)": {
+ "label": "mapping(address => bool)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_struct(RoleData)747_storage)": {
+ "label": "mapping(bytes32 => struct AccessControlUpgradeable.RoleData)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_bytes32,t_uint256)": {
+ "label": "mapping(bytes32 => uint256)",
+ "numberOfBytes": "32"
+ },
+ "t_mapping(t_uint256,t_struct(DLC)9268_storage)": {
+ "label": "mapping(uint256 => struct DLCLink.DLC)",
+ "numberOfBytes": "32"
+ },
+ "t_string_storage": {
+ "label": "string",
+ "numberOfBytes": "32"
+ },
+ "t_struct(DLC)9268_storage": {
+ "label": "struct DLCLink.DLC",
+ "members": [
+ {
+ "label": "uuid",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "protocolContract",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "1"
+ },
+ {
+ "label": "timestamp",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "2"
+ },
+ {
+ "label": "valueLocked",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "3"
+ },
+ {
+ "label": "creator",
+ "type": "t_address",
+ "offset": 0,
+ "slot": "4"
+ },
+ {
+ "label": "status",
+ "type": "t_enum(DLCStatus)9238",
+ "offset": 20,
+ "slot": "4"
+ },
+ {
+ "label": "fundingTxId",
+ "type": "t_string_storage",
+ "offset": 0,
+ "slot": "5"
+ },
+ {
+ "label": "closingTxId",
+ "type": "t_string_storage",
+ "offset": 0,
+ "slot": "6"
+ },
+ {
+ "label": "btcFeeRecipient",
+ "type": "t_string_storage",
+ "offset": 0,
+ "slot": "7"
+ },
+ {
+ "label": "btcMintFeeBasisPoints",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "8"
+ },
+ {
+ "label": "btcRedeemFeeBasisPoints",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "9"
+ },
+ {
+ "label": "taprootPubKey",
+ "type": "t_string_storage",
+ "offset": 0,
+ "slot": "10"
+ },
+ {
+ "label": "valueMinted",
+ "type": "t_uint256",
+ "offset": 0,
+ "slot": "11"
+ },
+ {
+ "label": "wdTxId",
+ "type": "t_string_storage",
+ "offset": 0,
+ "slot": "12"
+ }
+ ],
+ "numberOfBytes": "416"
+ },
+ "t_struct(RoleData)747_storage": {
+ "label": "struct AccessControlUpgradeable.RoleData",
+ "members": [
+ {
+ "label": "members",
+ "type": "t_mapping(t_address,t_bool)",
+ "offset": 0,
+ "slot": "0"
+ },
+ {
+ "label": "adminRole",
+ "type": "t_bytes32",
+ "offset": 0,
+ "slot": "1"
+ }
+ ],
+ "numberOfBytes": "64"
+ },
+ "t_uint16": {
+ "label": "uint16",
+ "numberOfBytes": "2"
+ },
+ "t_uint256": {
+ "label": "uint256",
+ "numberOfBytes": "32"
+ },
+ "t_uint48": {
+ "label": "uint48",
+ "numberOfBytes": "6"
+ },
+ "t_uint8": {
+ "label": "uint8",
+ "numberOfBytes": "1"
+ }
+ },
+ "namespaces": {}
+ }
+ },
+ "1204facb32434b600d4c54f0616b1f8174db5a43aa86b417a97c7e2737cca230": {
+ "address": "0x8f8625c2a9bC9770BcfE06AaE1Fcdd479fA60b83",
+ "txHash": "0xb24c63922f2f42d059477faabb5c3149178e6b2c56a222eedfa990a6dca1da27",
+ "layout": {
+ "solcVersion": "0.8.18",
+ "storage": [
+ {
+ "label": "_initialized",
+ "offset": 0,
+ "slot": "0",
+ "type": "t_uint8",
+ "contract": "Initializable",
+ "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63",
+ "retypedFrom": "bool"
+ },
+ {
+ "label": "_initializing",
+ "offset": 1,
+ "slot": "0",
+ "type": "t_bool",
+ "contract": "Initializable",
+ "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "1",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ContextUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "51",
+ "type": "t_array(t_uint256)50_storage",
+ "contract": "ERC165Upgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol:41"
+ },
+ {
+ "label": "_roles",
+ "offset": 0,
+ "slot": "101",
+ "type": "t_mapping(t_bytes32,t_struct(RoleData)747_storage)",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:62"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "102",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "AccessControlUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol:260"
+ },
+ {
+ "label": "_pendingDefaultAdmin",
+ "offset": 0,
+ "slot": "151",
+ "type": "t_address",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:43"
+ },
+ {
+ "label": "_pendingDefaultAdminSchedule",
+ "offset": 20,
+ "slot": "151",
+ "type": "t_uint48",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:44"
+ },
+ {
+ "label": "_currentDelay",
+ "offset": 26,
+ "slot": "151",
+ "type": "t_uint48",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:46"
+ },
+ {
+ "label": "_currentDefaultAdmin",
+ "offset": 0,
+ "slot": "152",
+ "type": "t_address",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:47"
+ },
+ {
+ "label": "_pendingDelay",
+ "offset": 20,
+ "slot": "152",
+ "type": "t_uint48",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:50"
+ },
+ {
+ "label": "_pendingDelaySchedule",
+ "offset": 26,
+ "slot": "152",
+ "type": "t_uint48",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:51"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "153",
+ "type": "t_array(t_uint256)48_storage",
+ "contract": "AccessControlDefaultAdminRulesUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/access/AccessControlDefaultAdminRulesUpgradeable.sol:394"
+ },
+ {
+ "label": "_paused",
+ "offset": 0,
+ "slot": "201",
+ "type": "t_bool",
+ "contract": "PausableUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:29"
+ },
+ {
+ "label": "__gap",
+ "offset": 0,
+ "slot": "202",
+ "type": "t_array(t_uint256)49_storage",
+ "contract": "PausableUpgradeable",
+ "src": "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol:116"
+ },
+ {
+ "label": "_index",
+ "offset": 0,
+ "slot": "251",
+ "type": "t_uint256",
+ "contract": "DLCManager",
+ "src": "contracts/DLCManager.sol:47"
+ },
{
"label": "dlcs",
"offset": 0,
diff --git a/README.md b/README.md
index 447c1d0..5570a31 100644
--- a/README.md
+++ b/README.md
@@ -15,7 +15,7 @@ This repo contains the solidity smart contracts for the DLC.Link infrastructure
Learn more about [DLCs](https://github.com/DLC-link/dlc-solidity#What-Are-DLCs) and [dlcBTC](https://dlcbtc.com).
-## Overview
+# Overview
A DLC is a contract on Bitcoin that enables users to move/lock Bitcoin conditionally. The possible outcomes of a DLC are predefined and stored by the DLC.Link Attestor Layer. Bitcoin locked in such a way can be represented then on any EVM chain using these smart contracts. The outcome - that is, the value that will be 'attested' to - is supplied by smart contracts too.
@@ -26,6 +26,12 @@ _dlcBTC_ is a token that represents Bitcoin locked in DLCs. It is minted when Bi
Learn more about the whole architecture on the documentation site here:
https://docs.dlc.link/architecture/tech-stack
+## Timelocked Upgrades
+
+Some of the contracts in this repository are upgradeable. They can be changed by a multisig of dlcBTC team and external members. The upgrade process is timelocked, meaning that the upgrade will only happen after a certain time has passed (minimum 7 days). This is to ensure that the community has time to review the changes and react if necessary.
+
+
+
# Dev notes
## Testing
diff --git a/assets/timelockArchitecture.svg b/assets/timelockArchitecture.svg
new file mode 100644
index 0000000..074b5d5
--- /dev/null
+++ b/assets/timelockArchitecture.svg
@@ -0,0 +1,29 @@
+
\ No newline at end of file
diff --git a/contracts/TimelockController.sol b/contracts/TimelockController.sol
new file mode 100644
index 0000000..a82a6f1
--- /dev/null
+++ b/contracts/TimelockController.sol
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: MIT
+// OpenZeppelin Contracts (last updated v4.9.0) (governance/TimelockController.sol)
+
+pragma solidity 0.8.18;
+
+import "@openzeppelin/contracts/access/AccessControl.sol";
+import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
+import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
+
+/**
+ * @dev Contract module which acts as a timelocked controller. When set as the
+ * owner of an `Ownable` smart contract, it enforces a timelock on all
+ * `onlyOwner` maintenance operations. This gives time for users of the
+ * controlled contract to exit before a potentially dangerous maintenance
+ * operation is applied.
+ *
+ * By default, this contract is self administered, meaning administration tasks
+ * have to go through the timelock process. The proposer (resp executor) role
+ * is in charge of proposing (resp executing) operations. A common use case is
+ * to position this {TimelockController} as the owner of a smart contract, with
+ * a multisig or a DAO as the sole proposer.
+ *
+ * _Available since v3.3._
+ */
+contract TimelockController is
+ AccessControl,
+ IERC721Receiver,
+ IERC1155Receiver
+{
+ bytes32 public constant TIMELOCK_ADMIN_ROLE =
+ keccak256("TIMELOCK_ADMIN_ROLE");
+ bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");
+ bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");
+ bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");
+ uint256 internal constant _DONE_TIMESTAMP = uint256(1);
+
+ mapping(bytes32 => uint256) private _timestamps;
+ uint256 private _minDelay;
+
+ /**
+ * @dev Emitted when a call is scheduled as part of operation `id`.
+ */
+ event CallScheduled(
+ bytes32 indexed id,
+ uint256 indexed index,
+ address target,
+ uint256 value,
+ bytes data,
+ bytes32 predecessor,
+ uint256 delay
+ );
+
+ /**
+ * @dev Emitted when a call is performed as part of operation `id`.
+ */
+ event CallExecuted(
+ bytes32 indexed id,
+ uint256 indexed index,
+ address target,
+ uint256 value,
+ bytes data
+ );
+
+ /**
+ * @dev Emitted when new proposal is scheduled with non-zero salt.
+ */
+ event CallSalt(bytes32 indexed id, bytes32 salt);
+
+ /**
+ * @dev Emitted when operation `id` is cancelled.
+ */
+ event Cancelled(bytes32 indexed id);
+
+ /**
+ * @dev Emitted when the minimum delay for future operations is modified.
+ */
+ event MinDelayChange(uint256 oldDuration, uint256 newDuration);
+
+ /**
+ * @dev Initializes the contract with the following parameters:
+ *
+ * - `minDelay`: initial minimum delay for operations
+ * - `proposers`: accounts to be granted proposer and canceller roles
+ * - `executors`: accounts to be granted executor role
+ * - `admin`: optional account to be granted admin role; disable with zero address
+ *
+ * IMPORTANT: The optional admin can aid with initial configuration of roles after deployment
+ * without being subject to delay, but this role should be subsequently renounced in favor of
+ * administration through timelocked proposals. Previous versions of this contract would assign
+ * this admin to the deployer automatically and should be renounced as well.
+ */
+ constructor(
+ uint256 minDelay,
+ address[] memory proposers,
+ address[] memory executors,
+ address admin
+ ) {
+ _setRoleAdmin(TIMELOCK_ADMIN_ROLE, TIMELOCK_ADMIN_ROLE);
+ _setRoleAdmin(PROPOSER_ROLE, TIMELOCK_ADMIN_ROLE);
+ _setRoleAdmin(EXECUTOR_ROLE, TIMELOCK_ADMIN_ROLE);
+ _setRoleAdmin(CANCELLER_ROLE, TIMELOCK_ADMIN_ROLE);
+
+ // self administration
+ _setupRole(TIMELOCK_ADMIN_ROLE, address(this));
+
+ // optional admin
+ if (admin != address(0)) {
+ _setupRole(TIMELOCK_ADMIN_ROLE, admin);
+ }
+
+ // register proposers and cancellers
+ for (uint256 i = 0; i < proposers.length; ++i) {
+ _setupRole(PROPOSER_ROLE, proposers[i]);
+ _setupRole(CANCELLER_ROLE, proposers[i]);
+ }
+
+ // register executors
+ for (uint256 i = 0; i < executors.length; ++i) {
+ _setupRole(EXECUTOR_ROLE, executors[i]);
+ }
+
+ _minDelay = minDelay;
+ emit MinDelayChange(0, minDelay);
+ }
+
+ /**
+ * @dev Modifier to make a function callable only by a certain role. In
+ * addition to checking the sender's role, `address(0)` 's role is also
+ * considered. Granting a role to `address(0)` is equivalent to enabling
+ * this role for everyone.
+ */
+ modifier onlyRoleOrOpenRole(bytes32 role) {
+ if (!hasRole(role, address(0))) {
+ _checkRole(role, _msgSender());
+ }
+ _;
+ }
+
+ /**
+ * @dev Contract might receive/hold ETH as part of the maintenance process.
+ */
+ receive() external payable {}
+
+ /**
+ * @dev See {IERC165-supportsInterface}.
+ */
+ function supportsInterface(
+ bytes4 interfaceId
+ ) public view virtual override(IERC165, AccessControl) returns (bool) {
+ return
+ interfaceId == type(IERC1155Receiver).interfaceId ||
+ super.supportsInterface(interfaceId);
+ }
+
+ /**
+ * @dev Returns whether an id correspond to a registered operation. This
+ * includes both Pending, Ready and Done operations.
+ */
+ function isOperation(bytes32 id) public view virtual returns (bool) {
+ return getTimestamp(id) > 0;
+ }
+
+ /**
+ * @dev Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready".
+ */
+ function isOperationPending(bytes32 id) public view virtual returns (bool) {
+ return getTimestamp(id) > _DONE_TIMESTAMP;
+ }
+
+ /**
+ * @dev Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending".
+ */
+ function isOperationReady(bytes32 id) public view virtual returns (bool) {
+ uint256 timestamp = getTimestamp(id);
+ return timestamp > _DONE_TIMESTAMP && timestamp <= block.timestamp;
+ }
+
+ /**
+ * @dev Returns whether an operation is done or not.
+ */
+ function isOperationDone(bytes32 id) public view virtual returns (bool) {
+ return getTimestamp(id) == _DONE_TIMESTAMP;
+ }
+
+ /**
+ * @dev Returns the timestamp at which an operation becomes ready (0 for
+ * unset operations, 1 for done operations).
+ */
+ function getTimestamp(bytes32 id) public view virtual returns (uint256) {
+ return _timestamps[id];
+ }
+
+ /**
+ * @dev Returns the minimum delay for an operation to become valid.
+ *
+ * This value can be changed by executing an operation that calls `updateDelay`.
+ */
+ function getMinDelay() public view virtual returns (uint256) {
+ return _minDelay;
+ }
+
+ /**
+ * @dev Returns the identifier of an operation containing a single
+ * transaction.
+ */
+ function hashOperation(
+ address target,
+ uint256 value,
+ bytes calldata data,
+ bytes32 predecessor,
+ bytes32 salt
+ ) public pure virtual returns (bytes32) {
+ return keccak256(abi.encode(target, value, data, predecessor, salt));
+ }
+
+ /**
+ * @dev Returns the identifier of an operation containing a batch of
+ * transactions.
+ */
+ function hashOperationBatch(
+ address[] calldata targets,
+ uint256[] calldata values,
+ bytes[] calldata payloads,
+ bytes32 predecessor,
+ bytes32 salt
+ ) public pure virtual returns (bytes32) {
+ return
+ keccak256(abi.encode(targets, values, payloads, predecessor, salt));
+ }
+
+ /**
+ * @dev Schedule an operation containing a single transaction.
+ *
+ * Emits {CallSalt} if salt is nonzero, and {CallScheduled}.
+ *
+ * Requirements:
+ *
+ * - the caller must have the 'proposer' role.
+ */
+ function schedule(
+ address target,
+ uint256 value,
+ bytes calldata data,
+ bytes32 predecessor,
+ bytes32 salt,
+ uint256 delay
+ ) public virtual onlyRole(PROPOSER_ROLE) {
+ bytes32 id = hashOperation(target, value, data, predecessor, salt);
+ _schedule(id, delay);
+ emit CallScheduled(id, 0, target, value, data, predecessor, delay);
+ if (salt != bytes32(0)) {
+ emit CallSalt(id, salt);
+ }
+ }
+
+ /**
+ * @dev Schedule an operation containing a batch of transactions.
+ *
+ * Emits {CallSalt} if salt is nonzero, and one {CallScheduled} event per transaction in the batch.
+ *
+ * Requirements:
+ *
+ * - the caller must have the 'proposer' role.
+ */
+ function scheduleBatch(
+ address[] calldata targets,
+ uint256[] calldata values,
+ bytes[] calldata payloads,
+ bytes32 predecessor,
+ bytes32 salt,
+ uint256 delay
+ ) public virtual onlyRole(PROPOSER_ROLE) {
+ require(
+ targets.length == values.length,
+ "TimelockController: length mismatch"
+ );
+ require(
+ targets.length == payloads.length,
+ "TimelockController: length mismatch"
+ );
+
+ bytes32 id = hashOperationBatch(
+ targets,
+ values,
+ payloads,
+ predecessor,
+ salt
+ );
+ _schedule(id, delay);
+ for (uint256 i = 0; i < targets.length; ++i) {
+ emit CallScheduled(
+ id,
+ i,
+ targets[i],
+ values[i],
+ payloads[i],
+ predecessor,
+ delay
+ );
+ }
+ if (salt != bytes32(0)) {
+ emit CallSalt(id, salt);
+ }
+ }
+
+ /**
+ * @dev Schedule an operation that is to become valid after a given delay.
+ */
+ function _schedule(bytes32 id, uint256 delay) private {
+ require(
+ !isOperation(id),
+ "TimelockController: operation already scheduled"
+ );
+ require(
+ delay >= getMinDelay(),
+ "TimelockController: insufficient delay"
+ );
+ _timestamps[id] = block.timestamp + delay;
+ }
+
+ /**
+ * @dev Cancel an operation.
+ *
+ * Requirements:
+ *
+ * - the caller must have the 'canceller' role.
+ */
+ function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
+ require(
+ isOperationPending(id),
+ "TimelockController: operation cannot be cancelled"
+ );
+ delete _timestamps[id];
+
+ emit Cancelled(id);
+ }
+
+ /**
+ * @dev Execute an (ready) operation containing a single transaction.
+ *
+ * Emits a {CallExecuted} event.
+ *
+ * Requirements:
+ *
+ * - the caller must have the 'executor' role.
+ */
+ // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
+ // thus any modifications to the operation during reentrancy should be caught.
+ // slither-disable-next-line reentrancy-eth
+ function execute(
+ address target,
+ uint256 value,
+ bytes calldata payload,
+ bytes32 predecessor,
+ bytes32 salt
+ ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
+ bytes32 id = hashOperation(target, value, payload, predecessor, salt);
+
+ _beforeCall(id, predecessor);
+ _execute(target, value, payload);
+ emit CallExecuted(id, 0, target, value, payload);
+ _afterCall(id);
+ }
+
+ /**
+ * @dev Execute an (ready) operation containing a batch of transactions.
+ *
+ * Emits one {CallExecuted} event per transaction in the batch.
+ *
+ * Requirements:
+ *
+ * - the caller must have the 'executor' role.
+ */
+ // This function can reenter, but it doesn't pose a risk because _afterCall checks that the proposal is pending,
+ // thus any modifications to the operation during reentrancy should be caught.
+ // slither-disable-next-line reentrancy-eth
+ function executeBatch(
+ address[] calldata targets,
+ uint256[] calldata values,
+ bytes[] calldata payloads,
+ bytes32 predecessor,
+ bytes32 salt
+ ) public payable virtual onlyRoleOrOpenRole(EXECUTOR_ROLE) {
+ require(
+ targets.length == values.length,
+ "TimelockController: length mismatch"
+ );
+ require(
+ targets.length == payloads.length,
+ "TimelockController: length mismatch"
+ );
+
+ bytes32 id = hashOperationBatch(
+ targets,
+ values,
+ payloads,
+ predecessor,
+ salt
+ );
+
+ _beforeCall(id, predecessor);
+ for (uint256 i = 0; i < targets.length; ++i) {
+ address target = targets[i];
+ uint256 value = values[i];
+ bytes calldata payload = payloads[i];
+ _execute(target, value, payload);
+ emit CallExecuted(id, i, target, value, payload);
+ }
+ _afterCall(id);
+ }
+
+ /**
+ * @dev Execute an operation's call.
+ */
+ function _execute(
+ address target,
+ uint256 value,
+ bytes calldata data
+ ) internal virtual {
+ (bool success, ) = target.call{value: value}(data);
+ require(success, "TimelockController: underlying transaction reverted");
+ }
+
+ /**
+ * @dev Checks before execution of an operation's calls.
+ */
+ function _beforeCall(bytes32 id, bytes32 predecessor) private view {
+ require(
+ isOperationReady(id),
+ "TimelockController: operation is not ready"
+ );
+ require(
+ predecessor == bytes32(0) || isOperationDone(predecessor),
+ "TimelockController: missing dependency"
+ );
+ }
+
+ /**
+ * @dev Checks after execution of an operation's calls.
+ */
+ function _afterCall(bytes32 id) private {
+ require(
+ isOperationReady(id),
+ "TimelockController: operation is not ready"
+ );
+ _timestamps[id] = _DONE_TIMESTAMP;
+ }
+
+ /**
+ * @dev Changes the minimum timelock duration for future operations.
+ *
+ * Emits a {MinDelayChange} event.
+ *
+ * Requirements:
+ *
+ * - the caller must be the timelock itself. This can only be achieved by scheduling and later executing
+ * an operation where the timelock is the target and the data is the ABI-encoded call to this function.
+ */
+ function updateDelay(uint256 newDelay) external virtual {
+ require(
+ msg.sender == address(this),
+ "TimelockController: caller must be timelock"
+ );
+ emit MinDelayChange(_minDelay, newDelay);
+ _minDelay = newDelay;
+ }
+
+ /**
+ * @dev See {IERC721Receiver-onERC721Received}.
+ */
+ function onERC721Received(
+ address,
+ address,
+ uint256,
+ bytes memory
+ ) public virtual override returns (bytes4) {
+ return this.onERC721Received.selector;
+ }
+
+ /**
+ * @dev See {IERC1155Receiver-onERC1155Received}.
+ */
+ function onERC1155Received(
+ address,
+ address,
+ uint256,
+ uint256,
+ bytes memory
+ ) public virtual override returns (bytes4) {
+ return this.onERC1155Received.selector;
+ }
+
+ /**
+ * @dev See {IERC1155Receiver-onERC1155BatchReceived}.
+ */
+ function onERC1155BatchReceived(
+ address,
+ address,
+ uint256[] memory,
+ uint256[] memory,
+ bytes memory
+ ) public virtual override returns (bytes4) {
+ return this.onERC1155BatchReceived.selector;
+ }
+}
diff --git a/deploymentFiles/arbitrum/TimelockController.json b/deploymentFiles/arbitrum/TimelockController.json
new file mode 100644
index 0000000..6834c8e
--- /dev/null
+++ b/deploymentFiles/arbitrum/TimelockController.json
@@ -0,0 +1,49 @@
+{
+ "network": "arbitrum",
+ "updatedAt": "2024-10-09T13:59:16.146Z",
+ "gitSHA": "20e6515",
+ "contract": {
+ "name": "TimelockController",
+ "address": "0x70ca59DC7599a8Bae1e9ABd50039DD73b2598714",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)",
+ "event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)",
+ "event CallSalt(bytes32 indexed id, bytes32 salt)",
+ "event CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)",
+ "event Cancelled(bytes32 indexed id)",
+ "event MinDelayChange(uint256 oldDuration, uint256 newDuration)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "function CANCELLER_ROLE() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function EXECUTOR_ROLE() view returns (bytes32)",
+ "function PROPOSER_ROLE() view returns (bytes32)",
+ "function TIMELOCK_ADMIN_ROLE() view returns (bytes32)",
+ "function cancel(bytes32 id)",
+ "function execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt) payable",
+ "function executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) payable",
+ "function getMinDelay() view returns (uint256)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getTimestamp(bytes32 id) view returns (uint256)",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function isOperation(bytes32 id) view returns (bool)",
+ "function isOperationDone(bytes32 id) view returns (bool)",
+ "function isOperationPending(bytes32 id) view returns (bool)",
+ "function isOperationReady(bytes32 id) view returns (bool)",
+ "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)",
+ "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)",
+ "function onERC721Received(address, address, uint256, bytes) returns (bytes4)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function updateDelay(uint256 newDelay)"
+ ]
+ }
+}
diff --git a/deploymentFiles/base/TimelockController.json b/deploymentFiles/base/TimelockController.json
new file mode 100644
index 0000000..16a35e1
--- /dev/null
+++ b/deploymentFiles/base/TimelockController.json
@@ -0,0 +1,49 @@
+{
+ "network": "base",
+ "updatedAt": "2024-10-09T13:58:46.061Z",
+ "gitSHA": "20e6515",
+ "contract": {
+ "name": "TimelockController",
+ "address": "0x391faD3FeE4f37EcF35e5ee3c38717E7D2635226",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)",
+ "event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)",
+ "event CallSalt(bytes32 indexed id, bytes32 salt)",
+ "event CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)",
+ "event Cancelled(bytes32 indexed id)",
+ "event MinDelayChange(uint256 oldDuration, uint256 newDuration)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "function CANCELLER_ROLE() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function EXECUTOR_ROLE() view returns (bytes32)",
+ "function PROPOSER_ROLE() view returns (bytes32)",
+ "function TIMELOCK_ADMIN_ROLE() view returns (bytes32)",
+ "function cancel(bytes32 id)",
+ "function execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt) payable",
+ "function executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) payable",
+ "function getMinDelay() view returns (uint256)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getTimestamp(bytes32 id) view returns (uint256)",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function isOperation(bytes32 id) view returns (bool)",
+ "function isOperationDone(bytes32 id) view returns (bool)",
+ "function isOperationPending(bytes32 id) view returns (bool)",
+ "function isOperationReady(bytes32 id) view returns (bool)",
+ "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)",
+ "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)",
+ "function onERC721Received(address, address, uint256, bytes) returns (bytes4)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function updateDelay(uint256 newDelay)"
+ ]
+ }
+}
diff --git a/deploymentFiles/basesepolia/DLCManager.json b/deploymentFiles/basesepolia/DLCManager.json
index 418f368..b6b0411 100644
--- a/deploymentFiles/basesepolia/DLCManager.json
+++ b/deploymentFiles/basesepolia/DLCManager.json
@@ -1,7 +1,7 @@
{
"network": "basesepolia",
- "updatedAt": "2024-09-26T14:51:22.898Z",
- "gitSHA": "b4622fe",
+ "updatedAt": "2024-10-09T11:39:15.234Z",
+ "gitSHA": "5a0ee77",
"contract": {
"name": "DLCManager",
"address": "0x050C24dBf1eEc17babE5fc585F06116A259CC77A",
diff --git a/deploymentFiles/basesepolia/DLCManager.proposed.json b/deploymentFiles/basesepolia/DLCManager.proposed.json
new file mode 100644
index 0000000..b6b0411
--- /dev/null
+++ b/deploymentFiles/basesepolia/DLCManager.proposed.json
@@ -0,0 +1,132 @@
+{
+ "network": "basesepolia",
+ "updatedAt": "2024-10-09T11:39:15.234Z",
+ "gitSHA": "5a0ee77",
+ "contract": {
+ "name": "DLCManager",
+ "address": "0x050C24dBf1eEc17babE5fc585F06116A259CC77A",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor()",
+ "error ClosingFundedVault()",
+ "error ContractNotWhitelisted()",
+ "error DLCNotFound()",
+ "error DLCNotFunded()",
+ "error DLCNotPending()",
+ "error DLCNotReadyOrFunded()",
+ "error DepositTooLarge(uint256 deposit, uint256 maximumDeposit)",
+ "error DepositTooSmall(uint256 deposit, uint256 minimumDeposit)",
+ "error DuplicateSignature()",
+ "error DuplicateSigner(address signer)",
+ "error FeeRateOutOfBounds(uint256 feeRate)",
+ "error IncompatibleRoles()",
+ "error InsufficientMintedBalance(uint256 minted, uint256 amount)",
+ "error InsufficientTokenBalance(uint256 balance, uint256 amount)",
+ "error InvalidRange()",
+ "error InvalidSigner()",
+ "error NoSignerRenouncement()",
+ "error NotCreatorContract()",
+ "error NotDLCAdmin()",
+ "error NotEnoughSignatures()",
+ "error NotOwner()",
+ "error NotWhitelisted()",
+ "error SignerNotApproved(address signer)",
+ "error ThresholdMinimumReached(uint16 _minimumThreshold)",
+ "error ThresholdTooLow(uint16 _minimumThreshold)",
+ "error Unauthorized()",
+ "error UnderCollateralized(uint256 newValueLocked, uint256 valueMinted)",
+ "event Burn(address from, uint256 amount)",
+ "event CreateDLC(bytes32 uuid, address creator, uint256 timestamp)",
+ "event DefaultAdminDelayChangeCanceled()",
+ "event DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule)",
+ "event DefaultAdminTransferCanceled()",
+ "event DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule)",
+ "event Initialized(uint8 version)",
+ "event Mint(address to, uint256 amount)",
+ "event Paused(address account)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event SetBtcFeeRecipient(string btcFeeRecipient)",
+ "event SetBtcMintFeeRate(uint256 newBtcMintFeeRate)",
+ "event SetBtcRedeemFeeRate(uint256 newBtcRedeemFeeRate)",
+ "event SetMaximumDeposit(uint256 newMaximumDeposit)",
+ "event SetMinimumDeposit(uint256 newMinimumDeposit)",
+ "event SetStatusFunded(bytes32 uuid, string btcTxId, address creator, uint256 newValueLocked, uint256 amountToMint)",
+ "event SetStatusPending(bytes32 uuid, string btcTxId, address creator, string taprootPubKey, uint256 newValueLocked)",
+ "event SetThreshold(uint16 newThreshold)",
+ "event SetWhitelistingEnabled(bool isWhitelistingEnabled)",
+ "event TransferTokenContractOwnership(address newOwner)",
+ "event Unpaused(address account)",
+ "event UnwhitelistAddress(address addressToUnWhitelist)",
+ "event WhitelistAddress(address addressToWhitelist)",
+ "event Withdraw(bytes32 uuid, uint256 amount, address sender)",
+ "function APPROVED_SIGNER() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function DLC_ADMIN_ROLE() view returns (bytes32)",
+ "function WHITELISTED_CONTRACT() view returns (bytes32)",
+ "function acceptDefaultAdminTransfer()",
+ "function attestorGroupPubKey() view returns (string)",
+ "function beginDefaultAdminTransfer(address newAdmin)",
+ "function btcFeeRecipient() view returns (string)",
+ "function btcMintFeeRate() view returns (uint256)",
+ "function btcRedeemFeeRate() view returns (uint256)",
+ "function cancelDefaultAdminTransfer()",
+ "function changeDefaultAdminDelay(uint48 newDelay)",
+ "function defaultAdmin() view returns (address)",
+ "function defaultAdminDelay() view returns (uint48)",
+ "function defaultAdminDelayIncreaseWait() view returns (uint48)",
+ "function dlcBTC() view returns (address)",
+ "function dlcIDsByUUID(bytes32) view returns (uint256)",
+ "function dlcs(uint256) view returns (bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)",
+ "function getAllDLCs(uint256 startIndex, uint256 endIndex) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)[])",
+ "function getAllVaultUUIDsForAddress(address owner) view returns (bytes32[])",
+ "function getAllVaultsForAddress(address owner) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId)[])",
+ "function getDLC(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))",
+ "function getDLCByIndex(uint256 index) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))",
+ "function getMinimumThreshold() view returns (uint16)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getSignerCount() view returns (uint16)",
+ "function getThreshold() view returns (uint16)",
+ "function getVault(bytes32 uuid) view returns (tuple(bytes32 uuid, address protocolContract, uint256 timestamp, uint256 valueLocked, address creator, uint8 status, string fundingTxId, string closingTxId, string btcFeeRecipient, uint256 btcMintFeeBasisPoints, uint256 btcRedeemFeeBasisPoints, string taprootPubKey, uint256 valueMinted, string wdTxId))",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function initialize(address defaultAdmin, address dlcAdminRole, uint16 threshold, address tokenContract, string btcFeeRecipientToSet)",
+ "function isWhitelisted(address account) view returns (bool)",
+ "function maximumDeposit() view returns (uint256)",
+ "function minimumDeposit() view returns (uint256)",
+ "function owner() view returns (address)",
+ "function pauseContract()",
+ "function paused() view returns (bool)",
+ "function pendingDefaultAdmin() view returns (address newAdmin, uint48 schedule)",
+ "function pendingDefaultAdminDelay() view returns (uint48 newDelay, uint48 schedule)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function rollbackDefaultAdminDelay()",
+ "function setAttestorGroupPubKey(string pubKey)",
+ "function setBtcFeeRecipient(string btcFeeRecipientToSet)",
+ "function setBtcFeeRecipientForVault(bytes32 uuid, string btcFeeRecipientToSet)",
+ "function setBtcMintFeeRate(uint256 newBtcMintFeeRate)",
+ "function setBtcRedeemFeeRate(uint256 newBtcRedeemFeeRate)",
+ "function setBurnerOnTokenContract(address burner)",
+ "function setMaximumDeposit(uint256 newMaximumDeposit)",
+ "function setMinimumDeposit(uint256 newMinimumDeposit)",
+ "function setMinterOnTokenContract(address minter)",
+ "function setStatusFunded(bytes32 uuid, string btcTxId, bytes[] signatures, uint256 newValueLocked)",
+ "function setStatusPending(bytes32 uuid, string wdTxId, bytes[] signatures, string taprootPubKey, uint256 newValueLocked)",
+ "function setTSSCommitment(bytes32 commitment)",
+ "function setThreshold(uint16 newThreshold)",
+ "function setWhitelistingEnabled(bool isWhitelistingEnabled)",
+ "function setupVault() returns (bytes32)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function transferTokenContractOwnership(address newOwner)",
+ "function tssCommitment() view returns (bytes32)",
+ "function unpauseContract()",
+ "function unwhitelistAddress(address addressToUnWhitelist)",
+ "function userVaults(address, uint256) view returns (bytes32)",
+ "function whitelistAddress(address addressToWhitelist)",
+ "function whitelistingEnabled() view returns (bool)",
+ "function withdraw(bytes32 uuid, uint256 amount)"
+ ]
+ }
+}
diff --git a/deploymentFiles/basesepolia/TimelockController.json b/deploymentFiles/basesepolia/TimelockController.json
new file mode 100644
index 0000000..514c572
--- /dev/null
+++ b/deploymentFiles/basesepolia/TimelockController.json
@@ -0,0 +1,49 @@
+{
+ "network": "basesepolia",
+ "updatedAt": "2024-10-08T16:47:01.355Z",
+ "gitSHA": "9c7dcb7",
+ "contract": {
+ "name": "TimelockController",
+ "address": "0x34E7e62bA1B1dD5c491d4e2e36f643CD415f72D5",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)",
+ "event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)",
+ "event CallSalt(bytes32 indexed id, bytes32 salt)",
+ "event CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)",
+ "event Cancelled(bytes32 indexed id)",
+ "event MinDelayChange(uint256 oldDuration, uint256 newDuration)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "function CANCELLER_ROLE() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function EXECUTOR_ROLE() view returns (bytes32)",
+ "function PROPOSER_ROLE() view returns (bytes32)",
+ "function TIMELOCK_ADMIN_ROLE() view returns (bytes32)",
+ "function cancel(bytes32 id)",
+ "function execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt) payable",
+ "function executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) payable",
+ "function getMinDelay() view returns (uint256)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getTimestamp(bytes32 id) view returns (uint256)",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function isOperation(bytes32 id) view returns (bool)",
+ "function isOperationDone(bytes32 id) view returns (bool)",
+ "function isOperationPending(bytes32 id) view returns (bool)",
+ "function isOperationReady(bytes32 id) view returns (bool)",
+ "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)",
+ "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)",
+ "function onERC721Received(address, address, uint256, bytes) returns (bytes4)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function updateDelay(uint256 newDelay)"
+ ]
+ }
+}
diff --git a/deploymentFiles/mainnet/TimelockController.json b/deploymentFiles/mainnet/TimelockController.json
new file mode 100644
index 0000000..70b62e6
--- /dev/null
+++ b/deploymentFiles/mainnet/TimelockController.json
@@ -0,0 +1,49 @@
+{
+ "network": "mainnet",
+ "updatedAt": "2024-10-09T13:59:55.117Z",
+ "gitSHA": "20e6515",
+ "contract": {
+ "name": "TimelockController",
+ "address": "0x07234079573a5C1e2c0B3f1DA3Bd5aD675b5a2A7",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)",
+ "event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)",
+ "event CallSalt(bytes32 indexed id, bytes32 salt)",
+ "event CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)",
+ "event Cancelled(bytes32 indexed id)",
+ "event MinDelayChange(uint256 oldDuration, uint256 newDuration)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "function CANCELLER_ROLE() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function EXECUTOR_ROLE() view returns (bytes32)",
+ "function PROPOSER_ROLE() view returns (bytes32)",
+ "function TIMELOCK_ADMIN_ROLE() view returns (bytes32)",
+ "function cancel(bytes32 id)",
+ "function execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt) payable",
+ "function executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) payable",
+ "function getMinDelay() view returns (uint256)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getTimestamp(bytes32 id) view returns (uint256)",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function isOperation(bytes32 id) view returns (bool)",
+ "function isOperationDone(bytes32 id) view returns (bool)",
+ "function isOperationPending(bytes32 id) view returns (bool)",
+ "function isOperationReady(bytes32 id) view returns (bool)",
+ "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)",
+ "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)",
+ "function onERC721Received(address, address, uint256, bytes) returns (bytes4)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function updateDelay(uint256 newDelay)"
+ ]
+ }
+}
diff --git a/deploymentFiles/optimism/TimelockController.json b/deploymentFiles/optimism/TimelockController.json
new file mode 100644
index 0000000..82ce1f4
--- /dev/null
+++ b/deploymentFiles/optimism/TimelockController.json
@@ -0,0 +1,49 @@
+{
+ "network": "optimism",
+ "updatedAt": "2024-10-09T14:00:16.413Z",
+ "gitSHA": "20e6515",
+ "contract": {
+ "name": "TimelockController",
+ "address": "0x67a4Ed5E3E80ad4f1b84646E45bcf9216059A37e",
+ "signerAddress": "0x9506Ea24038609679732855F757041a3C1C06623",
+ "abi": [
+ "constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)",
+ "event CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)",
+ "event CallSalt(bytes32 indexed id, bytes32 salt)",
+ "event CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)",
+ "event Cancelled(bytes32 indexed id)",
+ "event MinDelayChange(uint256 oldDuration, uint256 newDuration)",
+ "event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)",
+ "event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)",
+ "event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)",
+ "function CANCELLER_ROLE() view returns (bytes32)",
+ "function DEFAULT_ADMIN_ROLE() view returns (bytes32)",
+ "function EXECUTOR_ROLE() view returns (bytes32)",
+ "function PROPOSER_ROLE() view returns (bytes32)",
+ "function TIMELOCK_ADMIN_ROLE() view returns (bytes32)",
+ "function cancel(bytes32 id)",
+ "function execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt) payable",
+ "function executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) payable",
+ "function getMinDelay() view returns (uint256)",
+ "function getRoleAdmin(bytes32 role) view returns (bytes32)",
+ "function getTimestamp(bytes32 id) view returns (uint256)",
+ "function grantRole(bytes32 role, address account)",
+ "function hasRole(bytes32 role, address account) view returns (bool)",
+ "function hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) pure returns (bytes32)",
+ "function isOperation(bytes32 id) view returns (bool)",
+ "function isOperationDone(bytes32 id) view returns (bool)",
+ "function isOperationPending(bytes32 id) view returns (bool)",
+ "function isOperationReady(bytes32 id) view returns (bool)",
+ "function onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) returns (bytes4)",
+ "function onERC1155Received(address, address, uint256, uint256, bytes) returns (bytes4)",
+ "function onERC721Received(address, address, uint256, bytes) returns (bytes4)",
+ "function renounceRole(bytes32 role, address account)",
+ "function revokeRole(bytes32 role, address account)",
+ "function schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)",
+ "function supportsInterface(bytes4 interfaceId) view returns (bool)",
+ "function updateDelay(uint256 newDelay)"
+ ]
+ }
+}
diff --git a/hardhat.config.js b/hardhat.config.js
index c69737c..fcdb883 100644
--- a/hardhat.config.js
+++ b/hardhat.config.js
@@ -1,6 +1,7 @@
/** @type import('hardhat/config').HardhatUserConfig */
require('@nomicfoundation/hardhat-toolbox');
+require('@nomiclabs/hardhat-ethers');
require('@openzeppelin/hardhat-upgrades');
require('@nomiclabs/hardhat-solhint');
require('dotenv').config();
diff --git a/scripts/50_contract-admin.js b/scripts/50_contract-admin.js
index fa55259..ed43e0b 100644
--- a/scripts/50_contract-admin.js
+++ b/scripts/50_contract-admin.js
@@ -1,11 +1,16 @@
require('dotenv').config();
const hardhat = require('hardhat');
+const fs = require('fs/promises');
const prompts = require('prompts');
const chalk = require('chalk');
const dlcAdminSafesConfigs = require('./helpers/dlc-admin-safes');
const getContractConfigs = require('./99_contract-configs');
-const { promptUser, loadContractAddress } = require('./helpers/utils');
+const {
+ promptUser,
+ loadContractAddress,
+ getMinimumDelay,
+} = require('./helpers/utils');
const {
saveDeploymentInfo,
deploymentInfo,
@@ -45,8 +50,23 @@ module.exports = async function contractAdmin() {
value: 'deploy',
},
{ title: 'Verify Contract On Etherscan', value: 'verify' },
- { title: 'Validate an Upgrade', value: 'validate-upgrade' },
- { title: 'Upgrade Proxy Implementation', value: 'upgrade' },
+ {
+ title: 'Validate an Upgrade',
+ value: 'validate-upgrade',
+ description:
+ 'Validate a potential contract upgrade without performing any deployments.',
+ },
+ {
+ title: 'Upgrade Proxy',
+ value: 'upgrade',
+ description:
+ 'Upgrade a contract. Either directly, or through a SAFE proposal through a TimelockController',
+ },
+ {
+ title: 'Execute Upgrade',
+ value: 'execute-upgrade',
+ description: 'Execute an upgrade after the delay period',
+ },
{
title: 'Transfer DLCBTC Ownership',
value: 'transfer-dlcbtc',
@@ -55,10 +75,13 @@ module.exports = async function contractAdmin() {
{
title: 'Begin Transfer DEFAULT_ADMIN_ROLE',
value: 'transfer-admin',
+ description:
+ 'Begin the transfer of DEFAULT_ADMIN_ROLE on the DLCManager contract',
},
{
title: 'Transfer ProxyAdmin Ownership',
value: 'transfer-proxyadmin',
+ description: 'Transfer ownership of the ProxyAdmin contract',
},
],
initial: 0,
@@ -137,19 +160,15 @@ module.exports = async function contractAdmin() {
}
const contractSelectPrompt = await prompts({
type: 'select',
- name: 'contracts',
+ name: 'contract',
message: `Select contract to verify on ${network}`,
choices: contractConfigs.map((config) => ({
title: `${config.name}`,
- value: config.name,
+ value: config,
})),
});
- const contractName = contractSelectPrompt.contracts;
- const contractConfig = contractConfigs.find(
- (config) => config.name === contractName
- );
try {
- await contractConfig.verify();
+ await contractSelectPrompt.contract.verify();
} catch (error) {
console.error(error);
}
@@ -176,15 +195,16 @@ module.exports = async function contractAdmin() {
);
const newImplementation =
await hardhat.ethers.getContractFactory(contractName);
- const validation = await hardhat.upgrades.validateUpgrade(
- proxyAddress,
- newImplementation
- );
- if (!validation) {
- console.log('Upgrade is valid');
+ try {
+ await hardhat.upgrades.validateUpgrade(
+ proxyAddress,
+ newImplementation
+ );
+ } catch (error) {
+ console.error(error);
return;
}
- console.log(validation);
+ console.log('Upgrade is valid');
break;
}
case 'upgrade': {
@@ -205,14 +225,8 @@ module.exports = async function contractAdmin() {
contractName,
network
);
- const proxyAdminAddress =
- await hardhat.upgrades.erc1967.getAdminAddress(proxyAddress);
- const proxyAdmin = new hardhat.ethers.Contract(
- proxyAdminAddress,
- ['function owner() view returns (address)'],
- deployer
- );
+ const proxyAdmin = await hardhat.upgrades.admin.getInstance();
const proxyAdminOwner = await proxyAdmin.owner();
console.log('ProxyAdmin owner:', proxyAdminOwner);
@@ -220,11 +234,13 @@ module.exports = async function contractAdmin() {
await hardhat.ethers.getContractFactory(contractName);
if (proxyAdminOwner == deployer) {
+ // Deployer can perform the whole upgrade flow
console.log('deployer is ProxyAdmin owner, continuing...');
await hardhat.upgrades.upgradeProxy(
proxyAddress,
newImplementation,
{
+ // @ts-ignore
txOverrides: {
maxFeePerGas: 1000000000,
maxPriorityFeePerGas: 1000000000,
@@ -241,10 +257,16 @@ module.exports = async function contractAdmin() {
await saveDeploymentInfo(
deploymentInfo(network, contractObject, contractName)
);
+ const contractConfig = contractConfigs.find(
+ (config) => config.name === contractName
+ );
+ // @ts-ignore
+ await contractConfig.verify();
} catch (error) {
console.error(error);
}
} else {
+ // We need to propose the upgrade through the SAFE & timelock
const newImplementationAddress =
await hardhat.upgrades.prepareUpgrade(
proxyAddress,
@@ -256,49 +278,141 @@ module.exports = async function contractAdmin() {
newImplementationAddress
);
- try {
- await hardhat.upgrades.upgradeProxy(
- proxyAddress,
- newImplementation
- );
- console.log('Upgraded contract', contractName);
- console.log('Updating DeploymentInfo...');
- try {
- const contractObject =
- await hardhat.ethers.getContractAt(
- contractName,
- proxyAddress
- );
- await saveDeploymentInfo(
- deploymentInfo(
- network,
- contractObject,
- contractName
- )
- );
- } catch (error) {
- console.error(error);
- }
- } catch (error) {
- console.error(error);
- console.log(chalk.bgYellow('Upgrade through the SAFE!'));
- const implObject = await hardhat.ethers.getContractAt(
- contractName,
- newImplementationAddress
- );
- const deploymentInfoToSave = deploymentInfo(
- network,
- { ...implObject, address: proxyAddress },
- contractName
- );
- await saveDeploymentInfo(deploymentInfoToSave);
- console.log(deploymentInfoToSave);
- console.log(chalk.bgRed('Upgrade through the SAFE!'));
- }
+ console.log('Verifying new implementation...');
+ await hardhat.run('verify:verify', {
+ address: newImplementationAddress,
+ });
+ console.log('New implementation verified.');
+
+ // we prepare the tx to the ProxyAdmin to upgrade the contract
+ // NOTE: we have to store this data for the actual execution
+ const upgradeTx = await proxyAdmin.populateTransaction.upgrade(
+ proxyAddress,
+ newImplementationAddress
+ );
+ console.log('proxyAddress', proxyAddress);
+ console.log(
+ 'newImplementationAddress',
+ newImplementationAddress
+ );
+ console.log('proxyAdmin.address', proxyAdmin.address);
+ console.log(
+ chalk.bgYellowBright('upgradeTx: (Store this!)'),
+ upgradeTx
+ );
+
+ // Fetching the TimelockController contract
+ const timeLockContractDeployInfo = await loadDeploymentInfo(
+ network,
+ 'TimelockController'
+ );
+ const timelockContract = new hardhat.ethers.Contract(
+ timeLockContractDeployInfo.contract.address,
+ timeLockContractDeployInfo.contract.abi,
+ deployer
+ );
+
+ // Preparing the Multisig request to the TimelockController
+ const minimumDelay = getMinimumDelay(network);
+ const tlRequestParams = [
+ proxyAdmin.address,
+ 0,
+ upgradeTx.data,
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ minimumDelay,
+ ];
+ const timelockContractTxRequest = await timelockContract
+ .connect(deployer)
+ .populateTransaction['schedule'](...tlRequestParams);
+ console.log(
+ 'timelockContractTxRequest',
+ timelockContractTxRequest
+ );
+
+ // Proposing the upgrade through the SAFE
+ await safeContractProposal(
+ timelockContractTxRequest,
+ deployer,
+ dlcAdminSafes.critical
+ );
+
+ const implObject = await hardhat.ethers.getContractAt(
+ contractName,
+ // @ts-ignore
+ newImplementationAddress
+ );
+ const deploymentInfoToSave = deploymentInfo(
+ network,
+ { ...implObject, address: proxyAddress },
+ contractName,
+ upgradeTx.data
+ );
+ await saveDeploymentInfo(
+ deploymentInfoToSave,
+ `deploymentFiles/${network}/${contractName}.proposed.json`
+ );
}
break;
}
+ case 'execute-upgrade': {
+ const contractSelectPrompt = await prompts({
+ type: 'select',
+ name: 'contracts',
+ message: `Select contract to upgrade on ${network}`,
+ choices: contractConfigs
+ .filter((config) => config.upgradeable)
+ .map((config) => ({
+ title: `${config.name}`,
+ value: config.name,
+ })),
+ });
+ const contractName = contractSelectPrompt.contracts;
+
+ const contractDeployInfo = await loadDeploymentInfo(
+ network,
+ contractName,
+ true
+ );
+
+ // Fetching the TimelockController contract
+ const timeLockContractDeployInfo = await loadDeploymentInfo(
+ network,
+ 'TimelockController'
+ );
+ const timelockContract = new hardhat.ethers.Contract(
+ timeLockContractDeployInfo.contract.address,
+ timeLockContractDeployInfo.contract.abi,
+ deployer
+ );
+ const proxyAdmin = await hardhat.upgrades.admin.getInstance();
+ const tlRequestParams = [
+ proxyAdmin.address,
+ 0,
+ contractDeployInfo.upgradeData,
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ '0x0000000000000000000000000000000000000000000000000000000000000000',
+ ];
+
+ console.log('Executing upgrade...');
+ await timelockContract
+ .connect(deployer)
+ .execute(...tlRequestParams);
+
+ console.log('Updating DeploymentInfo...');
+ await fs.copyFile(
+ `deploymentFiles/${network}/${contractName}.proposed.json`,
+ `deploymentFiles/${network}/${contractName}.json`
+ );
+ await fs.rename(
+ `deploymentFiles/${network}/${contractName}.proposed.json`,
+ `deploymentFiles/${network}/${contractName}.${new Date().toISOString()}.json`
+ );
+ console.log('DeploymentInfo updated.');
+
+ break;
+ }
// NOTE: TODO: This will be useful for one time, while we transfer from the old TokenManager
// After that this will change to transfering from the DLCManager, if ever
case 'transfer-dlcbtc': {
@@ -393,7 +507,18 @@ module.exports = async function contractAdmin() {
console.log(
chalk.bgYellow('Current ProxyAdmin owner:', currentAdmin)
);
- if (currentAdmin == dlcAdminSafes.critical) return;
+ if (currentAdmin == dlcAdminSafes.critical) {
+ console.log(
+ chalk.bgRed(
+ 'Current ProxyAdmin owner is the Critical Multisig Already!'
+ )
+ );
+ if (
+ (await promptUser('Are you sure you want to continue?')) ===
+ false
+ )
+ return;
+ }
const newAdmin = await prompts({
type: 'text',
diff --git a/scripts/99_contract-configs.js b/scripts/99_contract-configs.js
index fe0e0ab..29716c8 100644
--- a/scripts/99_contract-configs.js
+++ b/scripts/99_contract-configs.js
@@ -4,7 +4,11 @@ const {
saveDeploymentInfo,
deploymentInfo,
} = require('./helpers/deployment-handlers_versioned');
-const { promptUser, loadContractAddress } = require('./helpers/utils');
+const {
+ promptUser,
+ loadContractAddress,
+ getMinimumDelay,
+} = require('./helpers/utils');
// This is a pure function that just logs
async function beforeDeployment(contractName, constructorArguments, network) {
@@ -37,6 +41,15 @@ module.exports = function getContractConfigs(networkConfig, _btcFeeRecipient) {
const btcFeeRecipient =
_btcFeeRecipient ?? '0014e60f61fa2f2941217934d5f9976bf27381b3b036';
const threshold = 2;
+ const minimumDelay = getMinimumDelay(networkName);
+ const proposers = [dlcAdminSafes.critical];
+ const executors = [dlcAdminSafes.critical, deployer.address];
+ const timelockConstructorArgs = [
+ minimumDelay,
+ proposers,
+ executors,
+ hardhat.ethers.constants.AddressZero, // no admin
+ ];
return [
{
@@ -66,6 +79,46 @@ module.exports = function getContractConfigs(networkConfig, _btcFeeRecipient) {
});
},
},
+ {
+ name: 'TimelockController',
+ deployer: deployer.address,
+ upgradeable: false,
+ requirements: [],
+ deploy: async (requirementAddresses) => {
+ await beforeDeployment(
+ 'TimelockController',
+ timelockConstructorArgs,
+ networkName
+ );
+
+ const TimelockController =
+ await hardhat.ethers.getContractFactory(
+ 'TimelockController'
+ );
+ const timelockController = await TimelockController.deploy(
+ ...timelockConstructorArgs
+ );
+ await timelockController.deployed();
+
+ await afterDeployment(
+ 'TimelockController',
+ timelockController,
+ networkName
+ );
+
+ return timelockController.address;
+ },
+ verify: async () => {
+ const address = await loadContractAddress(
+ 'TimelockController',
+ networkName
+ );
+ await hardhat.run('verify:verify', {
+ address: address,
+ constructorArguments: timelockConstructorArgs,
+ });
+ },
+ },
{
name: 'DLCManager',
deployer: deployer.address,
diff --git a/scripts/helpers/deployment-handlers_versioned.js b/scripts/helpers/deployment-handlers_versioned.js
index bb45f42..ad1eddd 100644
--- a/scripts/helpers/deployment-handlers_versioned.js
+++ b/scripts/helpers/deployment-handlers_versioned.js
@@ -1,7 +1,7 @@
const fs = require('fs/promises');
const { execSync } = require('child_process');
-function deploymentInfo(networkName, contract, contractName) {
+function deploymentInfo(networkName, contract, contractName, upgradeData) {
let _gitSHA;
try {
_gitSHA = execSync('git rev-parse --short HEAD').toString().trim();
@@ -19,12 +19,13 @@ function deploymentInfo(networkName, contract, contractName) {
signerAddress: contract.signer.address,
abi: contract.interface.format(),
},
+ upgradeData: upgradeData || '',
};
console.log(deployInfo);
return deployInfo;
}
-async function saveDeploymentInfo(info, filename = undefined) {
+async function saveDeploymentInfo(info, filename) {
if (!filename) {
filename = `deploymentFiles/${info.network}/${info.contract.name}.json`;
}
@@ -56,8 +57,10 @@ function validateDeploymentInfo(deployInfo) {
required('abi');
}
-async function loadDeploymentInfo(networkName, contractName) {
- const deploymentConfigFile = `deploymentFiles/${networkName}/${contractName}.json`;
+async function loadDeploymentInfo(networkName, contractName, proposed) {
+ const deploymentConfigFile = `deploymentFiles/${networkName}/${contractName}${
+ proposed ? '.proposed' : ''
+ }.json`;
const content = await fs.readFile(deploymentConfigFile, {
encoding: 'utf8',
});
diff --git a/scripts/helpers/dlc-admin-safes.js b/scripts/helpers/dlc-admin-safes.js
index 1672b0f..a9ab7ee 100644
--- a/scripts/helpers/dlc-admin-safes.js
+++ b/scripts/helpers/dlc-admin-safes.js
@@ -29,6 +29,6 @@ module.exports = {
},
basesepolia: {
medium: '0x9506Ea24038609679732855F757041a3C1C06623',
- critical: '0x9506Ea24038609679732855F757041a3C1C06623',
+ critical: '0xebDC2027D3ee493B49553Befc1200e1cce9e2E08',
},
};
diff --git a/scripts/helpers/mainnet-call-all.sh b/scripts/helpers/mainnet-call-all.sh
index 431b6e9..045a352 100755
--- a/scripts/helpers/mainnet-call-all.sh
+++ b/scripts/helpers/mainnet-call-all.sh
@@ -13,3 +13,4 @@ extra_args="${@:2}"
HARDHAT_NETWORK=base dlc-link-eth $command $extra_args
HARDHAT_NETWORK=arbitrum dlc-link-eth $command $extra_args
HARDHAT_NETWORK=mainnet dlc-link-eth $command $extra_args
+HARDHAT_NETWORK=optimism dlc-link-eth $command $extra_args
diff --git a/scripts/helpers/safe-api-service.js b/scripts/helpers/safe-api-service.js
index d1dd074..00cea1a 100644
--- a/scripts/helpers/safe-api-service.js
+++ b/scripts/helpers/safe-api-service.js
@@ -12,7 +12,10 @@ module.exports = async function safeContractProposal(
signer,
safeAddress
) {
- const network = hardhat.network.name;
+ let network = hardhat.network.name;
+
+ if (network === 'basesepolia') network = 'base-sepolia';
+
console.log('Network', network);
const ethAdapter = new EthersAdapter({
diff --git a/scripts/helpers/utils.js b/scripts/helpers/utils.js
index 5947327..3cb1b1f 100644
--- a/scripts/helpers/utils.js
+++ b/scripts/helpers/utils.js
@@ -29,7 +29,20 @@ async function loadContractAddress(requirement, network) {
return deployment.contract.address;
}
+function getMinimumDelay(networkName) {
+ switch (networkName) {
+ case 'localhost':
+ case 'arbsepolia':
+ case 'sepolia':
+ case 'basesepolia':
+ return 60 * 2; // 2 minutes
+ default:
+ return 60 * 60 * 24 * 7; // 1 week
+ }
+}
+
module.exports = {
promptUser,
loadContractAddress,
+ getMinimumDelay,
};