Skip to content

Commit 524a68f

Browse files
authored
Merge pull request #3 from NFTX-project/fail-safe-add-guardians
FailSafe: add guardians
2 parents 7c66adf + 3f4d199 commit 524a68f

File tree

8 files changed

+267
-36
lines changed

8 files changed

+267
-36
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Contract | mainnet | sepolia | goerli
2121
--- | --- | --- | ---
2222
[CreateVaultZap](./src/zaps/CreateVaultZap.sol) | [0x56dab32697B4A313f353DA0CE42B5113eD8E6f74](https://etherscan.io/address/0x56dab32697B4A313f353DA0CE42B5113eD8E6f74#code) | [0xD80b916470F8e79FD8d09874cb159CbB8D13d8da](https://sepolia.etherscan.io/address/0xD80b916470F8e79FD8d09874cb159CbB8D13d8da#code) | [0xc6464CC63bC20b64e1633A0293C2C9b202F4f1b6](https://goerli.etherscan.io/address/0xc6464CC63bC20b64e1633A0293C2C9b202F4f1b6#code)
2323
DefaultProxyAdmin | [0xf8Cab5e4912e08c475033776d9472b81c1325e58](https://etherscan.io/address/0xf8Cab5e4912e08c475033776d9472b81c1325e58#code) | [0x36cBBb16F2FA71d4B773E9F4A11cF7FC53B13EfD](https://sepolia.etherscan.io/address/0x36cBBb16F2FA71d4B773E9F4A11cF7FC53B13EfD#code) | [0xa0d26F02D5e94C8E9ED524875D9fce36ab4838a2](https://goerli.etherscan.io/address/0xa0d26F02D5e94C8E9ED524875D9fce36ab4838a2#code)
24-
FailSafe | [0x8665664CD32B6263A9b716371814Dc8CecFb8F2B](https://etherscan.io/address/0x8665664CD32B6263A9b716371814Dc8CecFb8F2B#code) | N/A | N/A
24+
[FailSafe](./src/FailSafe.sol) | [0x6bB724F11a8D7254800Cf34922E0D54407B0e698](https://etherscan.io/address/0x6bB724F11a8D7254800Cf34922E0D54407B0e698#code) | N/A | N/A
2525
[MarketplaceUniversalRouterZap](./src/zaps/MarketplaceUniversalRouterZap.sol) | [0x293A0c49c85F1D8851C665Ac3cE1f1DC2a79bE3d](https://etherscan.io/address/0x293A0c49c85F1D8851C665Ac3cE1f1DC2a79bE3d#code) | [0xd88a3B9D0Fb2d39ec8394CfFD983aFBB2D4a6410](https://sepolia.etherscan.io/address/0xd88a3B9D0Fb2d39ec8394CfFD983aFBB2D4a6410#code) | [0x0be2D766Eef4b6a72F1fAe2e49619F013d647B8A](https://goerli.etherscan.io/address/0x0be2D766Eef4b6a72F1fAe2e49619F013d647B8A#code)
2626
[MigratorZap](./src/zaps/MigratorZap.sol) | [0x089610Fb04c34C014B4B391f4eCEFAef94E98CEc](https://etherscan.io/address/0x089610Fb04c34C014B4B391f4eCEFAef94E98CEc#code) | [0x19762e505aF085284E287c8DAb931fb28545461f](https://sepolia.etherscan.io/address/0x19762e505aF085284E287c8DAb931fb28545461f#code) | [0xD4B67Fe6a1258fd5e1C4dF84f3De01F62e7ac127](https://goerli.etherscan.io/address/0xD4B67Fe6a1258fd5e1C4dF84f3De01F62e7ac127#code)
2727
[NFTXEligibilityManager](./src/v2/NFTXEligibilityManager.sol) | [0x4086e98Cce041d286112d021612fD894cFed94D5](https://etherscan.io/address/0x4086e98Cce041d286112d021612fD894cFed94D5#code) | [0xa1ad09f8Fd789E3A940Ba9Dc5aE4D17021eF290D](https://sepolia.etherscan.io/address/0xa1ad09f8Fd789E3A940Ba9Dc5aE4D17021eF290D#code) | [0xA4e9e286CE7A34d19f774c36844225468290C3A8](https://goerli.etherscan.io/address/0xA4e9e286CE7A34d19f774c36844225468290C3A8#code)

addresses.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"mainnet": {
33
"CreateVaultZap": "0x56dab32697B4A313f353DA0CE42B5113eD8E6f74",
44
"DefaultProxyAdmin": "0xf8Cab5e4912e08c475033776d9472b81c1325e58",
5-
"FailSafe": "0x8665664CD32B6263A9b716371814Dc8CecFb8F2B",
5+
"FailSafe": "0x6bB724F11a8D7254800Cf34922E0D54407B0e698",
66
"MarketplaceUniversalRouterZap": "0x293A0c49c85F1D8851C665Ac3cE1f1DC2a79bE3d",
77
"MigratorZap": "0x089610Fb04c34C014B4B391f4eCEFAef94E98CEc",
88
"NFTXEligibilityManager": "0x4086e98Cce041d286112d021612fD894cFed94D5",

deploy/scripts/TransferOwnership.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
1515
"DefaultProxyAdmin",
1616
"MarketplaceUniversalRouterZap",
1717
"MigratorZap",
18-
"FailSafe",
1918
"NFTXFeeDistributorV3",
2019
"NFTXInventoryStakingV3Upgradeable",
2120
"NFTXRouter",

deployments/mainnet/FailSafe.json

Lines changed: 110 additions & 29 deletions
Large diffs are not rendered by default.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"language": "Solidity",
3+
"sources": {
4+
"lib/openzeppelin-contracts/contracts/access/Ownable.sol": {
5+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n _;\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n"
6+
},
7+
"lib/openzeppelin-contracts/contracts/utils/Context.sol": {
8+
"content": "// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n"
9+
},
10+
"src/FailSafe.sol": {
11+
"content": "// SPDX-License-Identifier: MIT\npragma solidity =0.8.15;\n\n// inheriting\nimport {Ownable} from \"@openzeppelin/contracts/access/Ownable.sol\";\n\ninterface IPausable {\n function pause(uint256 lockId) external;\n}\n\n/**\n * @title Fail Safe\n * @author @apoorvlathey\n *\n * @notice Pause all operations at once. This contract must be set as guardian.\n */\ncontract FailSafe is Ownable {\n // types\n struct Contract {\n address addr;\n uint256 lastLockId;\n }\n\n // storage\n Contract[] public contracts;\n mapping(address => bool) public isGuardian;\n\n // events\n event SetIsGuardian(address addr, bool isGuardian);\n\n // errors\n error NotGuardian();\n\n constructor(Contract[] memory _contracts) {\n setContracts(_contracts);\n isGuardian[msg.sender] = true;\n }\n\n // modifiers\n modifier onlyGuardian() {\n if (!isGuardian[msg.sender]) revert NotGuardian();\n _;\n }\n\n // external functions\n // onlyGuardian\n function pauseAll() external onlyGuardian {\n uint256 len = contracts.length;\n for (uint256 i; i < len; ) {\n Contract storage c = contracts[i];\n\n for (uint256 j; j <= c.lastLockId; ) {\n IPausable(c.addr).pause(j);\n\n unchecked {\n ++j;\n }\n }\n\n unchecked {\n ++i;\n }\n }\n }\n\n // onlyOwner\n function setContracts(Contract[] memory _contracts) public onlyOwner {\n delete contracts;\n\n uint256 len = _contracts.length;\n for (uint256 i; i < len; ) {\n contracts.push(_contracts[i]);\n\n unchecked {\n ++i;\n }\n }\n }\n\n function setIsGuardian(address addr, bool _isGuardian) external onlyOwner {\n isGuardian[addr] = _isGuardian;\n emit SetIsGuardian(addr, _isGuardian);\n }\n}\n"
12+
}
13+
},
14+
"settings": {
15+
"optimizer": {
16+
"enabled": true,
17+
"runs": 800
18+
},
19+
"metadata": {
20+
"bytecodeHash": "none",
21+
"useLiteralContent": true
22+
},
23+
"outputSelection": {
24+
"*": {
25+
"*": [
26+
"abi",
27+
"evm.bytecode",
28+
"evm.deployedBytecode",
29+
"evm.methodIdentifiers",
30+
"metadata",
31+
"devdoc",
32+
"userdoc",
33+
"storageLayout",
34+
"evm.gasEstimates"
35+
],
36+
"": [
37+
"ast"
38+
]
39+
}
40+
},
41+
"remappings": [
42+
"@openzeppelin/=lib/openzeppelin-contracts/",
43+
"@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
44+
"@uni-core/=src/uniswap/v3-core/",
45+
"@uni-periphery/=src/uniswap/v3-periphery/",
46+
"@permit2/=lib/permit2/src/",
47+
"@mocks/=src/mocks/",
48+
"@uniswap/v3-core/contracts/=src/uniswap/v3-core/",
49+
"@uniswap/lib/=lib/solidity-lib/",
50+
"@uniswap/v2-core/=lib/v2-core/",
51+
"base64-sol/=src/uniswap/v3-periphery/libraries/",
52+
"forge-std/=lib/forge-std/src/",
53+
"@src/=src/",
54+
"@test/=test/",
55+
"ds-test/=lib/forge-std/lib/ds-test/src/",
56+
"forge-gas-snapshot/=lib/permit2/lib/forge-gas-snapshot/src/",
57+
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
58+
"openzeppelin-contracts/=lib/openzeppelin-contracts/",
59+
"permit2/=lib/permit2/",
60+
"solidity-lib/=lib/solidity-lib/contracts/",
61+
"solmate/=lib/permit2/lib/solmate/",
62+
"v2-core/=lib/v2-core/contracts/"
63+
]
64+
}
65+
}

script/genREADME.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const baseExplorerURLs = {
1414
// URLs for each contract
1515
const contractURLs = {
1616
CreateVaultZap: "./src/zaps/CreateVaultZap.sol",
17+
FailSafe: "./src/FailSafe.sol",
1718
MarketplaceUniversalRouterZap: "./src/zaps/MarketplaceUniversalRouterZap.sol",
1819
MigratorZap: "./src/zaps/MigratorZap.sol",
1920
NFTXEligibilityManager: "./src/v2/NFTXEligibilityManager.sol",

src/FailSafe.sol

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,36 @@ interface IPausable {
1515
* @notice Pause all operations at once. This contract must be set as guardian.
1616
*/
1717
contract FailSafe is Ownable {
18+
// types
1819
struct Contract {
1920
address addr;
2021
uint256 lastLockId;
2122
}
2223

24+
// storage
2325
Contract[] public contracts;
26+
mapping(address => bool) public isGuardian;
27+
28+
// events
29+
event SetIsGuardian(address addr, bool isGuardian);
30+
31+
// errors
32+
error NotGuardian();
2433

2534
constructor(Contract[] memory _contracts) {
2635
setContracts(_contracts);
36+
isGuardian[msg.sender] = true;
37+
}
38+
39+
// modifiers
40+
modifier onlyGuardian() {
41+
if (!isGuardian[msg.sender]) revert NotGuardian();
42+
_;
2743
}
2844

29-
function pauseAll() external onlyOwner {
45+
// external functions
46+
// onlyGuardian
47+
function pauseAll() external onlyGuardian {
3048
uint256 len = contracts.length;
3149
for (uint256 i; i < len; ) {
3250
Contract storage c = contracts[i];
@@ -45,6 +63,7 @@ contract FailSafe is Ownable {
4563
}
4664
}
4765

66+
// onlyOwner
4867
function setContracts(Contract[] memory _contracts) public onlyOwner {
4968
delete contracts;
5069

@@ -57,4 +76,9 @@ contract FailSafe is Ownable {
5776
}
5877
}
5978
}
79+
80+
function setIsGuardian(address addr, bool _isGuardian) external onlyOwner {
81+
isGuardian[addr] = _isGuardian;
82+
emit SetIsGuardian(addr, _isGuardian);
83+
}
6084
}

test/FailSafe.t.sol

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,19 @@ contract FailSafeTests is TestBase {
5858
}
5959
}
6060

61-
function test_FailSafe_pauseAll_RevertsForNonOwner() external {
62-
hoax(makeAddr("nonOwner"));
63-
vm.expectRevert("Ownable: caller is not the owner");
61+
// FailSafe#pauseAll
62+
63+
function test_FailSafe_pauseAll_RevertsForNonGuardian() external {
64+
hoax(makeAddr("nonGuardian"));
65+
vm.expectRevert(FailSafe.NotGuardian.selector);
6466
failsafe.pauseAll();
6567
}
6668

6769
function test_FailSafe_pauseAll_Success() external {
70+
address newGuardian = makeAddr("newGuardian");
71+
failsafe.setIsGuardian(newGuardian, true);
72+
startHoax(newGuardian);
73+
6874
failsafe.pauseAll();
6975

7076
// check if all operations are paused
@@ -93,4 +99,59 @@ contract FailSafeTests is TestBase {
9399
assertTrue(nftxRouter.isPaused(NFTX_ROUTER_LOCK_ID_SELLNFTS));
94100
assertTrue(nftxRouter.isPaused(NFTX_ROUTER_LOCK_ID_BUYNFTS));
95101
}
102+
103+
// FailSafe#setContracts
104+
105+
function test_FailSafe_setContracts_RevertsForNonOwner() external {
106+
hoax(makeAddr("nonOwner"));
107+
vm.expectRevert(OWNABLE_NOT_OWNER_ERROR);
108+
failsafe.setContracts(new FailSafe.Contract[](0));
109+
}
110+
111+
function test_FailSafe_setContracts_Success() external {
112+
FailSafe.Contract[] memory contracts = new FailSafe.Contract[](5);
113+
contracts[0] = FailSafe.Contract({
114+
addr: address(inventoryStaking),
115+
lastLockId: 4
116+
});
117+
contracts[1] = FailSafe.Contract({
118+
addr: address(vaultFactory),
119+
lastLockId: 0
120+
});
121+
contracts[2] = FailSafe.Contract({
122+
addr: address(feeDistributor),
123+
lastLockId: 1
124+
});
125+
contracts[3] = FailSafe.Contract({
126+
addr: address(nftxRouter),
127+
lastLockId: 4
128+
});
129+
contracts[4] = FailSafe.Contract({
130+
addr: makeAddr("newContract"),
131+
lastLockId: 5
132+
});
133+
134+
failsafe.setContracts(contracts);
135+
136+
// check if the contracts are set
137+
for (uint256 i; i < contracts.length; i++) {
138+
(address addr, uint256 lastLockId) = failsafe.contracts(i);
139+
assertEq(addr, contracts[i].addr);
140+
assertEq(lastLockId, contracts[i].lastLockId);
141+
}
142+
}
143+
144+
// FailSafe#setIsGuardian
145+
function test_FailSafe_setIsGuardian_RevertsForNonOwner() external {
146+
hoax(makeAddr("nonOwner"));
147+
vm.expectRevert(OWNABLE_NOT_OWNER_ERROR);
148+
failsafe.setIsGuardian(makeAddr("addr"), true);
149+
}
150+
151+
function test_FailSafe_setIsGuardian_Success() external {
152+
address newGuardian = makeAddr("newGuardian");
153+
failsafe.setIsGuardian(newGuardian, true);
154+
155+
assertTrue(failsafe.isGuardian(newGuardian));
156+
}
96157
}

0 commit comments

Comments
 (0)