diff --git a/package.json b/package.json index 49d7eb86..ff444704 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "dependencies": { "@defi-wonderland/solidity-utils": "0.0.0-4298c6c6", "@openzeppelin/contracts": "4.8.2", + "@openzeppelin/contracts-upgradeable": "4.8.2", "@uniswap/v3-periphery": "https://github.com/Uniswap/v3-periphery.git#0.8", "forge-std": "https://github.com/foundry-rs/forge-std.git#e8a047e3f40f13fa37af6fe14e6e06283d9a060e" }, diff --git a/remappings.txt b/remappings.txt index 649d953c..2f23ecec 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,6 @@ @defi-wonderland/solidity-utils/=node_modules/@defi-wonderland/solidity-utils/solidity/ @openzeppelin/=node_modules/@openzeppelin/contracts/ +@openzeppelin-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/ @uniswap/=node_modules/@uniswap/ @camelot/=lib/core/contracts/ @isolmate/=lib/isolmate/src/ diff --git a/script/Common.s.sol b/script/Common.s.sol index 9a69434c..fc8156bf 100644 --- a/script/Common.s.sol +++ b/script/Common.s.sol @@ -4,10 +4,15 @@ pragma solidity 0.8.19; import '@script/Contracts.s.sol'; import {Params, ParamChecker, OD, ETH_A, JOB_REWARD} from '@script/Params.s.sol'; import '@script/Registry.s.sol'; +import {Create2Factory} from '@contracts/utils/Create2Factory.sol'; abstract contract Common is Contracts, Params { uint256 internal _deployerPk = 69; // for tests - from HAI uint256 internal _governorPK; + Create2Factory internal _create2Factory; + uint256 internal salt1; + uint256 internal salt2; + uint256 internal salt3; function getChainId() public view returns (uint256) { uint256 id; @@ -164,8 +169,14 @@ abstract contract Common is Contracts, Params { function deployTokenGovernance() public updateParams { // deploy Tokens - systemCoin = new SystemCoin('Open Dollar', 'OD'); - protocolToken = new ProtocolToken('Open Dollar Governance', 'ODG'); + // systemCoin = new SystemCoin('Open Dollar', 'OD'); + // protocolToken = new ProtocolToken('Open Dollar Governance', 'ODG'); + (address systemCoinAddress, address protocolTokenAddress) = _create2Factory.deployTokens(salt1, salt2); + systemCoin = ISystemCoin(systemCoinAddress); + protocolToken = IProtocolToken(protocolTokenAddress); + + systemCoin.initialize('Open Dollar', 'OD'); + protocolToken.initialize('Open Dollar Governance', 'ODG'); address[] memory members = new address[](0); @@ -374,7 +385,11 @@ abstract contract Common is Contracts, Params { } function deployProxyContracts() public updateParams { - vault721 = new Vault721(address(timelockController)); + // vault721 = new Vault721(address(timelockController)); + address vault721Address = _create2Factory.deployVault721(salt3); + vault721 = Vault721(vault721Address); + vault721.initialize(address(timelockController)); + safeManager = new ODSafeManager(address(safeEngine), address(vault721)); nftRenderer = new NFTRenderer(address(vault721), address(oracleRelayer), address(taxCollector), address(collateralJoinFactory)); diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 8249d2ab..bb634160 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -14,6 +14,7 @@ import {Common} from '@script/Common.s.sol'; import {GoerliParams} from '@script/GoerliParams.s.sol'; import {MainnetParams} from '@script/MainnetParams.s.sol'; import {IAlgebraPool} from '@interfaces/oracles/IAlgebraPool.sol'; +import {Create2Factory} from '@contracts/utils/Create2Factory.sol'; abstract contract Deploy is Common, Script { function setupEnvironment() public virtual {} @@ -95,6 +96,10 @@ contract DeployMainnet is MainnetParams, Deploy { function setUp() public virtual { _deployerPk = uint256(vm.envBytes32('MAINNET_DEPLOYER_PK')); chainId = 42_161; + _create2Factory = Create2Factory(MAINNET_CREATE2_FACTORY); + salt1 = MAINNET_SALT_SYSTEMCOIN; + salt2 = MAINNET_SALT_PROTOCOLTOKEN; + salt3 = MAINNET_SALT_VAULT721; } function mintAirdrop() public virtual override { @@ -166,6 +171,10 @@ contract DeployGoerli is GoerliParams, Deploy { function setUp() public virtual { _deployerPk = uint256(vm.envBytes32('ARB_SEPOLIA_DEPLOYER_PK')); chainId = 421_614; + _create2Factory = Create2Factory(SEPOLIA_CREATE2_FACTORY); + salt1 = SEPOLIA_SALT_SYSTEMCOIN; + salt2 = SEPOLIA_SALT_PROTOCOLTOKEN; + salt3 = SEPOLIA_SALT_VAULT721; } function mintAirdrop() public virtual override { diff --git a/script/Registry.s.sol b/script/Registry.s.sol index 0bb1a789..d203eac8 100644 --- a/script/Registry.s.sol +++ b/script/Registry.s.sol @@ -1,30 +1,41 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -// --- Anvil --- +// --- Anvil Local Testnet --- + +// Members for governance address constant ALICE = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; // deployer address constant BOB = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; address constant CHARLOTTE = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; -// --- ARB Goerli --- +// --- ARB Sepolia Testnet --- // Deployment params uint256 constant MIN_DELAY_GOERLI = 1 minutes; uint256 constant ORACLE_INTERVAL_TEST = 1 minutes; -// Token contracts -address constant GOERLI_WETH = 0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f; -address constant GOERLI_GOV_TOKEN = 0x0Ed89D4655b2fE9f99EaDC3116b223527165452D; - // Members for governance address constant H = 0x37c5B029f9c3691B3d47cb024f84E5E257aEb0BB; address constant J = 0xcb81A76a565aC4870EDA5B0e32c5a0D2ec734174; address constant P = 0xC295763Eed507d4A0f8B77241c03dd3354781a15; +// Vanity address params - use `cast create2` to find salt +uint256 constant SEPOLIA_SALT_SYSTEMCOIN = + 112_897_861_258_990_387_098_776_944_447_239_821_066_355_482_563_138_389_422_987_534_236_459_546_050_026; +uint256 constant SEPOLIA_SALT_PROTOCOLTOKEN = + 33_909_640_905_358_342_898_143_724_624_030_133_083_920_609_190_925_545_591_963_391_866_995_498_083_907; +uint256 constant SEPOLIA_SALT_VAULT721 = + 53_108_215_892_343_944_041_352_961_460_150_983_644_958_469_624_782_417_283_725_833_369_029_472_088_009; +address constant SEPOLIA_CREATE2_FACTORY = 0x196eFA212f88C2Dd7f0a74E747168B3A37F335c0; + +// --- ARB Goerli Testnet --- + +// Token contracts +address constant GOERLI_WETH = 0xEe01c0CD76354C383B8c7B4e65EA88D00B06f36f; +address constant GOERLI_GOV_TOKEN = 0x0Ed89D4655b2fE9f99EaDC3116b223527165452D; // Chainlink feeds address constant GOERLI_CHAINLINK_ETH_USD_FEED = 0x62CAe0FA2da220f43a51F86Db2EDb36DcA9A5A08; address constant GOERLI_CHAINLINK_ARB_USD_FEED = 0x2eE9BFB2D319B31A573EA15774B755715988E99D; - // Liquidity pools address constant GOERLI_UNISWAP_V3_FACTORY = 0x4893376342d5D7b3e31d4184c08b265e5aB2A3f6; address constant GOERLI_CAMELOT_V2_FACTORY = 0x659fd9F4536f540bd051c2739Fc8b8e9355E5042; @@ -39,6 +50,12 @@ uint256 constant AIRDROP_AMOUNT = 10_000e18; // 10k tokens uint256 constant MIN_DELAY = 3 days; // timelock for governor uint256 constant ORACLE_INTERVAL_PROD = 1 hours; +// Vanity address params - use `cast create2` to find salt +uint256 constant MAINNET_SALT_SYSTEMCOIN = 0; +uint256 constant MAINNET_SALT_PROTOCOLTOKEN = 0; +uint256 constant MAINNET_SALT_VAULT721 = 0; +address constant MAINNET_CREATE2_FACTORY = address(0); + // Token contracts (all 18 decimals) address constant ARBITRUM_WSTETH = 0x5979D7b546E38E414F7E9822514be443A4800529; address constant ARBITRUM_ARB = 0x912CE59144191C1204E64559FE8253a0e49E6548; diff --git a/script/create2/DeployContracts.s.sol b/script/create2/DeployContracts.s.sol new file mode 100644 index 00000000..d7938257 --- /dev/null +++ b/script/create2/DeployContracts.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.19; + +import '@script/Registry.s.sol'; +import {Script} from 'forge-std/Script.sol'; +import {Create2Factory} from '@contracts/utils/Create2Factory.sol'; + +// BROADCAST +// source .env && forge script DeployContracts --with-gas-price 2000000000 -vvvvv --rpc-url $ARB_SEPOLIA_RPC --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY + +// SIMULATE +// source .env && forge script DeployContracts --with-gas-price 2000000000 -vvvvv --rpc-url $ARB_SEPOLIA_RPC + +contract DeployContracts is Script { + Create2Factory create2Factory = Create2Factory(SEPOLIA_CREATE2_FACTORY); + + function run() public { + vm.startBroadcast(vm.envUint('ARB_SEPOLIA_DEPLOYER_PK')); + create2Factory.deployTokens(SEPOLIA_SALT_SYSTEMCOIN, SEPOLIA_SALT_PROTOCOLTOKEN); + create2Factory.deployVault721(SEPOLIA_SALT_VAULT721); + vm.stopBroadcast(); + } +} diff --git a/script/create2/DeployCreate2Factory.s.sol b/script/create2/DeployCreate2Factory.s.sol new file mode 100644 index 00000000..2eb50495 --- /dev/null +++ b/script/create2/DeployCreate2Factory.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.19; + +import {Script} from 'forge-std/Script.sol'; +import {Create2Factory} from '@contracts/utils/Create2Factory.sol'; + +// BROADCAST +// source .env && forge script DeployCreate2Factory --with-gas-price 2000000000 -vvvvv --rpc-url $ARB_SEPOLIA_RPC --broadcast --verify --etherscan-api-key $ARB_ETHERSCAN_API_KEY + +// SIMULATE +// source .env && forge script DeployCreate2Factory --with-gas-price 2000000000 -vvvvv --rpc-url $ARB_SEPOLIA_RPC + +contract DeployCreate2Factory is Script { + function run() public { + vm.startBroadcast(vm.envUint('ARB_SEPOLIA_DEPLOYER_PK')); + new Create2Factory(); + vm.stopBroadcast(); + } +} diff --git a/src/contracts/SurplusAuctionHouse.sol b/src/contracts/SurplusAuctionHouse.sol index 1939804c..fff28f1f 100644 --- a/src/contracts/SurplusAuctionHouse.sol +++ b/src/contracts/SurplusAuctionHouse.sol @@ -9,7 +9,7 @@ import {Authorizable} from '@contracts/utils/Authorizable.sol'; import {Modifiable} from '@contracts/utils/Modifiable.sol'; import {Disableable} from '@contracts/utils/Disableable.sol'; -import {SafeERC20} from '@openzeppelin/token/ERC20/utils/SafeERC20.sol'; +import {SafeERC20Upgradeable} from '@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; import {Encoding} from '@libraries/Encoding.sol'; import {Assertions} from '@libraries/Assertions.sol'; import {Math, WAD} from '@libraries/Math.sol'; @@ -23,7 +23,7 @@ contract SurplusAuctionHouse is Authorizable, Modifiable, Disableable, ISurplusA using Math for uint256; using Encoding for bytes; using Assertions for address; - using SafeERC20 for IProtocolToken; + using SafeERC20Upgradeable for IProtocolToken; /// @inheritdoc ICommonSurplusAuctionHouse bytes32 public constant AUCTION_HOUSE_TYPE = bytes32('SURPLUS'); diff --git a/src/contracts/proxies/Vault721.sol b/src/contracts/proxies/Vault721.sol index 21c1a7a4..6377a9e4 100644 --- a/src/contracts/proxies/Vault721.sol +++ b/src/contracts/proxies/Vault721.sol @@ -2,7 +2,8 @@ pragma solidity 0.8.19; import {ERC721} from '@openzeppelin/token/ERC721/ERC721.sol'; -import {ERC721Enumerable} from '@openzeppelin/token/ERC721/extensions/ERC721Enumerable.sol'; +import {ERC721EnumerableUpgradeable} from + '@openzeppelin-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol'; import {IODSafeManager} from '@interfaces/proxies/IODSafeManager.sol'; import {ODProxy} from '@contracts/proxies/ODProxy.sol'; import {NFTRenderer} from '@contracts/proxies/NFTRenderer.sol'; @@ -10,7 +11,10 @@ import {NFTRenderer} from '@contracts/proxies/NFTRenderer.sol'; // Open Dollar // Version 1.5.8 -contract Vault721 is ERC721Enumerable { +/** + * @notice Upgradeable contract used as singleton, but is not upgradeable + */ +contract Vault721 is ERC721EnumerableUpgradeable { error NotGovernor(); error ProxyAlreadyExist(); error ZeroAddress(); @@ -27,11 +31,19 @@ contract Vault721 is ERC721Enumerable { event CreateProxy(address indexed _user, address _proxy); + /** + * @dev initializer preferred for CREATE2 deployment + */ + constructor() { + _disableInitializers(); + } + /** * @dev initializes DAO timelockController contract */ - constructor(address _timelockController) ERC721('OpenDollar Vault', 'ODV') { + function initialize(address _timelockController) external initializer { timelockController = _timelockController; + __ERC721_init('OpenDollar Vault', 'ODV'); } /** diff --git a/src/contracts/proxies/actions/CommonActions.sol b/src/contracts/proxies/actions/CommonActions.sol index 0719dad3..98bfb154 100644 --- a/src/contracts/proxies/actions/CommonActions.sol +++ b/src/contracts/proxies/actions/CommonActions.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; +import {IERC20MetadataUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol'; import {ICoinJoin} from '@interfaces/utils/ICoinJoin.sol'; import {ICollateralJoin} from '@interfaces/utils/ICollateralJoin.sol'; import {ICommonActions} from '@interfaces/proxies/actions/ICommonActions.sol'; @@ -51,7 +52,7 @@ abstract contract CommonActions is ICommonActions { if (_wad == 0) return; // NOTE: assumes systemCoin uses 18 decimals - IERC20Metadata _systemCoin = ICoinJoin(_coinJoin).systemCoin(); + IERC20MetadataUpgradeable _systemCoin = ICoinJoin(_coinJoin).systemCoin(); // Transfers coins from the user to the proxy _systemCoin.transferFrom(msg.sender, address(this), _wad); // Approves adapter to take the COIN amount diff --git a/src/contracts/proxies/actions/DebtBidActions.sol b/src/contracts/proxies/actions/DebtBidActions.sol index 013840a2..1e02f86a 100644 --- a/src/contracts/proxies/actions/DebtBidActions.sol +++ b/src/contracts/proxies/actions/DebtBidActions.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; +import {IERC20MetadataUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol'; import {IAccountingEngine} from '@interfaces/IAccountingEngine.sol'; import {IDebtAuctionHouse} from '@interfaces/IDebtAuctionHouse.sol'; import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; @@ -50,7 +50,7 @@ contract DebtBidActions is CommonActions, IDebtBidActions { if (_auction.highBidder == address(this)) { // get the amount of protocol tokens that were sold - IERC20Metadata _protocolToken = IDebtAuctionHouse(_debtAuctionHouse).protocolToken(); + IERC20MetadataUpgradeable _protocolToken = IDebtAuctionHouse(_debtAuctionHouse).protocolToken(); _protocolToken.transfer(msg.sender, _auction.amountToSell); } @@ -65,7 +65,7 @@ contract DebtBidActions is CommonActions, IDebtBidActions { /// @inheritdoc IDebtBidActions function collectProtocolTokens(address _protocolToken) external delegateCall { // get the amount of protocol tokens that the proxy has - uint256 _coinsToCollect = IERC20Metadata(_protocolToken).balanceOf(address(this)); - IERC20Metadata(_protocolToken).transfer(msg.sender, _coinsToCollect); + uint256 _coinsToCollect = IERC20MetadataUpgradeable(_protocolToken).balanceOf(address(this)); + IERC20MetadataUpgradeable(_protocolToken).transfer(msg.sender, _coinsToCollect); } } diff --git a/src/contracts/proxies/actions/SurplusBidActions.sol b/src/contracts/proxies/actions/SurplusBidActions.sol index 10ae8138..42971507 100644 --- a/src/contracts/proxies/actions/SurplusBidActions.sol +++ b/src/contracts/proxies/actions/SurplusBidActions.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; +import {IERC20MetadataUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol'; import {IAccountingEngine} from '@interfaces/IAccountingEngine.sol'; import {ISurplusAuctionHouse} from '@interfaces/ISurplusAuctionHouse.sol'; import {ISAFEEngine} from '@interfaces/ISAFEEngine.sol'; @@ -24,7 +24,7 @@ contract SurplusBidActions is ISurplusBidActions, CommonActions { uint256 _amountToSell = ISurplusAuctionHouse(_surplusAuctionHouse).auctions(_auctionId).amountToSell; // prepare protocol token spending - IERC20Metadata _protocolToken = ISurplusAuctionHouse(_surplusAuctionHouse).protocolToken(); + IERC20MetadataUpgradeable _protocolToken = ISurplusAuctionHouse(_surplusAuctionHouse).protocolToken(); _protocolToken.transferFrom(msg.sender, address(this), _bidAmount); _protocolToken.approve(address(_surplusAuctionHouse), _bidAmount); diff --git a/src/contracts/settlement/PostSettlementSurplusAuctionHouse.sol b/src/contracts/settlement/PostSettlementSurplusAuctionHouse.sol index 2490ca87..65dece32 100644 --- a/src/contracts/settlement/PostSettlementSurplusAuctionHouse.sol +++ b/src/contracts/settlement/PostSettlementSurplusAuctionHouse.sol @@ -11,7 +11,7 @@ import {IProtocolToken} from '@interfaces/tokens/IProtocolToken.sol'; import {Authorizable} from '@contracts/utils/Authorizable.sol'; import {Modifiable} from '@contracts/utils/Modifiable.sol'; -import {SafeERC20} from '@openzeppelin/token/ERC20/utils/SafeERC20.sol'; +import {SafeERC20Upgradeable} from '@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; import {Encoding} from '@libraries/Encoding.sol'; import {WAD} from '@libraries/Math.sol'; import {Assertions} from '@libraries/Assertions.sol'; @@ -23,7 +23,7 @@ import {Assertions} from '@libraries/Assertions.sol'; */ contract PostSettlementSurplusAuctionHouse is Authorizable, Modifiable, IPostSettlementSurplusAuctionHouse { using Encoding for bytes; - using SafeERC20 for IProtocolToken; + using SafeERC20Upgradeable for IProtocolToken; using Assertions for address; bytes32 public constant AUCTION_HOUSE_TYPE = bytes32('SURPLUS'); diff --git a/src/contracts/tokens/ProtocolToken.sol b/src/contracts/tokens/ProtocolToken.sol index 27acb96b..a476bb66 100644 --- a/src/contracts/tokens/ProtocolToken.sol +++ b/src/contracts/tokens/ProtocolToken.sol @@ -1,8 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {ERC20Votes, ERC20Permit, ERC20} from '@openzeppelin/token/ERC20/extensions/ERC20Votes.sol'; -import {Authorizable} from '@contracts/utils/Authorizable.sol'; +import { + ERC20VotesUpgradeable, + ERC20PermitUpgradeable, + ERC20Upgradeable +} from '@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol'; +import {AuthorizableUpgradeable} from '@contracts/utils/AuthorizableUpgradeable.sol'; import {IProtocolToken} from '@interfaces/tokens/IProtocolToken.sol'; @@ -10,17 +14,22 @@ import {IProtocolToken} from '@interfaces/tokens/IProtocolToken.sol'; * @title ProtocolToken * @notice This contract represents the protocol ERC20Votes token to be used for governance purposes */ -contract ProtocolToken is ERC20Votes, Authorizable, IProtocolToken { +contract ProtocolToken is ERC20VotesUpgradeable, AuthorizableUpgradeable, IProtocolToken { // --- Init --- + constructor() { + _disableInitializers(); + } + /** * @param _name String with the name of the token * @param _symbol String with the symbol of the token */ - constructor( - string memory _name, - string memory _symbol - ) ERC20(_name, _symbol) ERC20Permit(_name) Authorizable(msg.sender) {} + function initialize(string memory _name, string memory _symbol) external initializer { + __ERC20_init(_name, _symbol); + __ERC20Permit_init(_name); + __authorizable_init(msg.sender); + } // --- Methods --- diff --git a/src/contracts/tokens/SystemCoin.sol b/src/contracts/tokens/SystemCoin.sol index 397bacd4..3d1d27b2 100644 --- a/src/contracts/tokens/SystemCoin.sol +++ b/src/contracts/tokens/SystemCoin.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {ERC20, IERC20} from '@openzeppelin/token/ERC20/ERC20.sol'; -import {Authorizable} from '@contracts/utils/Authorizable.sol'; +import {ERC20Upgradeable, IERC20Upgradeable} from '@openzeppelin-upgradeable/token/ERC20/ERC20Upgradeable.sol'; +import {AuthorizableUpgradeable} from '@contracts/utils/AuthorizableUpgradeable.sol'; import {ISystemCoin} from '@interfaces/tokens/ISystemCoin.sol'; @@ -10,14 +10,21 @@ import {ISystemCoin} from '@interfaces/tokens/ISystemCoin.sol'; * @title SystemCoin * @notice This contract represents the system coin ERC20 token to be used outside the system */ -contract SystemCoin is ERC20, Authorizable, ISystemCoin { +contract SystemCoin is ERC20Upgradeable, AuthorizableUpgradeable, ISystemCoin { // --- Init --- + constructor() { + _disableInitializers(); + } + /** * @param _name String with the name of the token * @param _symbol String with the symbol of the token */ - constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) Authorizable(msg.sender) {} + function initialize(string memory _name, string memory _symbol) external initializer { + __ERC20_init(_name, _symbol); + __authorizable_init(msg.sender); + } // --- Methods --- diff --git a/src/contracts/tokens/TokenDistributor.sol b/src/contracts/tokens/TokenDistributor.sol index 552d68c5..abf196b0 100644 --- a/src/contracts/tokens/TokenDistributor.sol +++ b/src/contracts/tokens/TokenDistributor.sol @@ -5,17 +5,17 @@ import {ITokenDistributor} from '@interfaces/tokens/ITokenDistributor.sol'; import {Authorizable} from '@contracts/utils/Authorizable.sol'; import {Assertions} from '@libraries/Assertions.sol'; -import {ERC20Votes} from '@openzeppelin/token/ERC20/extensions/ERC20Votes.sol'; +import {ERC20VotesUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol'; import {MerkleProof} from '@openzeppelin/utils/cryptography/MerkleProof.sol'; -import {SafeERC20} from '@openzeppelin/token/ERC20/utils/SafeERC20.sol'; +import {SafeERC20Upgradeable} from '@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol'; /** * @title TokenDistributor * @notice This contract allows users to claim tokens from a merkle tree proof */ contract TokenDistributor is Authorizable, ITokenDistributor { - using SafeERC20 for ERC20Votes; + using SafeERC20Upgradeable for ERC20VotesUpgradeable; using Assertions for address; using Assertions for uint256; @@ -24,7 +24,7 @@ contract TokenDistributor is Authorizable, ITokenDistributor { /// @inheritdoc ITokenDistributor bytes32 public root; /// @inheritdoc ITokenDistributor - ERC20Votes public token; + ERC20VotesUpgradeable public token; /// @inheritdoc ITokenDistributor uint256 public totalClaimable; /// @inheritdoc ITokenDistributor @@ -44,14 +44,14 @@ contract TokenDistributor is Authorizable, ITokenDistributor { */ constructor( bytes32 _root, - ERC20Votes _token, + ERC20VotesUpgradeable _token, uint256 _totalClaimable, uint256 _claimPeriodStart, uint256 _claimPeriodEnd, address _delegateTo ) Authorizable(msg.sender) { root = _root; - token = ERC20Votes(address(_token).assertNonNull()); + token = ERC20VotesUpgradeable(address(_token).assertNonNull()); totalClaimable = _totalClaimable.assertNonNull(); claimPeriodStart = _claimPeriodStart.assertGt(block.timestamp); claimPeriodEnd = _claimPeriodEnd.assertGt(claimPeriodStart); diff --git a/src/contracts/utils/AuthorizableUpgradeable.sol b/src/contracts/utils/AuthorizableUpgradeable.sol new file mode 100644 index 00000000..336c54ce --- /dev/null +++ b/src/contracts/utils/AuthorizableUpgradeable.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.19; + +import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; + +import {EnumerableSet} from '@openzeppelin/utils/structs/EnumerableSet.sol'; + +import {Initializable} from '@openzeppelin-upgradeable/proxy/utils/Initializable.sol'; + +/** + * @title Authorizable + * @notice Implements authorization control for contracts + * @notice Upgradeable contract used as singleton, but is not upgradeable + * @dev Authorization control is boolean and handled by `onlyAuthorized` modifier + */ +abstract contract AuthorizableUpgradeable is Initializable, IAuthorizable { + using EnumerableSet for EnumerableSet.AddressSet; + + // --- Data --- + + /// @notice EnumerableSet of authorized accounts + EnumerableSet.AddressSet internal _authorizedAccounts; + + // --- Init --- + + constructor() { + _disableInitializers(); + } + + /** + * @param _account Initial account to add authorization to + */ + function __authorizable_init(address _account) internal onlyInitializing { + _addAuthorization(_account); + } + + // --- Views --- + + /** + * @notice Checks whether an account is authorized + * @return _authorized Whether the account is authorized or not + */ + function authorizedAccounts(address _account) external view returns (bool _authorized) { + return _isAuthorized(_account); + } + + /** + * @notice Getter for the authorized accounts + * @return _accounts Array of authorized accounts + */ + function authorizedAccounts() external view returns (address[] memory _accounts) { + return _authorizedAccounts.values(); + } + + // --- Methods --- + + /** + * @notice Add auth to an account + * @param _account Account to add auth to + */ + function addAuthorization(address _account) external virtual isAuthorized { + _addAuthorization(_account); + } + + /** + * @notice Remove auth from an account + * @param _account Account to remove auth from + */ + function removeAuthorization(address _account) external virtual isAuthorized { + _removeAuthorization(_account); + } + + // --- Internal methods --- + function _addAuthorization(address _account) internal { + if (_authorizedAccounts.add(_account)) { + emit AddAuthorization(_account); + } else { + revert AlreadyAuthorized(); + } + } + + function _removeAuthorization(address _account) internal { + if (_authorizedAccounts.remove(_account)) { + emit RemoveAuthorization(_account); + } else { + revert NotAuthorized(); + } + } + + function _isAuthorized(address _account) internal view virtual returns (bool _authorized) { + return _authorizedAccounts.contains(_account); + } + + // --- Modifiers --- + + /** + * @notice Checks whether msg.sender can call an authed function + * @dev Will revert with `Unauthorized` if the sender is not authorized + */ + modifier isAuthorized() { + if (!_isAuthorized(msg.sender)) revert Unauthorized(); + _; + } +} diff --git a/src/contracts/utils/Create2Factory.sol b/src/contracts/utils/Create2Factory.sol new file mode 100644 index 00000000..56551c51 --- /dev/null +++ b/src/contracts/utils/Create2Factory.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.19; + +import {Vault721} from '@contracts/proxies/Vault721.sol'; +import {SystemCoin} from '@contracts/tokens/SystemCoin.sol'; +import {ProtocolToken} from '@contracts/tokens/ProtocolToken.sol'; + +contract Create2Factory { + bytes internal _systemCoin; + bytes internal _protocolToken; + bytes internal _vault721; + + bytes32 public systemCoinHash; + bytes32 public protocolTokenHash; + bytes32 public vault721Hash; + + mapping(address _admin => bool _ok) public approved; + + event Deployed(address _addr, uint256 _salt); + + error AdminOnly(); + + constructor() { + approved[msg.sender] = true; + + _systemCoin = type(SystemCoin).creationCode; + _protocolToken = type(ProtocolToken).creationCode; + _vault721 = type(Vault721).creationCode; + + systemCoinHash = keccak256(_systemCoin); + protocolTokenHash = keccak256(_protocolToken); + vault721Hash = keccak256(_vault721); + } + + modifier onlyAdmin() { + if (approved[msg.sender] == false) revert AdminOnly(); + _; + } + + function addAdmin(address _admin) external onlyAdmin { + approved[_admin] = true; + } + + function deployTokens( + uint256 _salt1, + uint256 _salt2 + ) external onlyAdmin returns (address _deployment1, address _deployment2) { + _deployment1 = _deploy(_salt1, _systemCoin); + _deployment2 = _deploy(_salt2, _protocolToken); + } + + function deployVault721(uint256 _salt) external onlyAdmin returns (address _deployment) { + _deployment = _deploy(_salt, _vault721); + } + + function _deploy(uint256 _salt, bytes memory _bytecode) internal returns (address _deployment) { + assembly { + _deployment := create2(callvalue(), add(_bytecode, 0x20), mload(_bytecode), _salt) + if iszero(extcodesize(_deployment)) { revert(0, 0) } + } + emit Deployed(_deployment, _salt); + } +} diff --git a/src/interfaces/tokens/IProtocolToken.sol b/src/interfaces/tokens/IProtocolToken.sol index c4b879b9..8611a9f0 100644 --- a/src/interfaces/tokens/IProtocolToken.sol +++ b/src/interfaces/tokens/IProtocolToken.sol @@ -1,11 +1,16 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; -import {IVotes, IERC20Permit} from '@openzeppelin/token/ERC20/extensions/ERC20Votes.sol'; +import {IERC20MetadataUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol'; +import { + IVotesUpgradeable, + IERC20PermitUpgradeable +} from '@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol'; import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; -interface IProtocolToken is IVotes, IERC20Metadata, IERC20Permit, IAuthorizable { +interface IProtocolToken is IVotesUpgradeable, IERC20MetadataUpgradeable, IERC20PermitUpgradeable, IAuthorizable { + function initialize(string memory _name, string memory _symbol) external; + /** * @notice Mint an amount of tokens to an account * @param _account Address of the account to mint tokens to diff --git a/src/interfaces/tokens/ISystemCoin.sol b/src/interfaces/tokens/ISystemCoin.sol index 02d63fa1..dd6c8740 100644 --- a/src/interfaces/tokens/ISystemCoin.sol +++ b/src/interfaces/tokens/ISystemCoin.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.19; -import {IERC20Metadata} from '@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol'; +import {IERC20MetadataUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol'; import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; -interface ISystemCoin is IERC20Metadata, IAuthorizable { +interface ISystemCoin is IERC20MetadataUpgradeable, IAuthorizable { + function initialize(string memory _name, string memory _symbol) external; + /** * @notice Mint an amount of tokens to an account * @param _account Address of the account to mint tokens to diff --git a/src/interfaces/tokens/ITokenDistributor.sol b/src/interfaces/tokens/ITokenDistributor.sol index 27646b45..bfa55e86 100644 --- a/src/interfaces/tokens/ITokenDistributor.sol +++ b/src/interfaces/tokens/ITokenDistributor.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.19; import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; -import {ERC20Votes} from '@openzeppelin/token/ERC20/extensions/ERC20Votes.sol'; +import {ERC20VotesUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol'; interface ITokenDistributor is IAuthorizable { // --- Events --- @@ -46,7 +46,7 @@ interface ITokenDistributor is IAuthorizable { /// @notice The merkle root of the token distribution function root() external view returns (bytes32 _root); /// @notice Address of the ERC20 token to be distributed - function token() external view returns (ERC20Votes _token); + function token() external view returns (ERC20VotesUpgradeable _token); /// @notice Total amount of tokens to be distributed function totalClaimable() external view returns (uint256 _totalClaimable); /// @notice Timestamp when the claim period starts diff --git a/test/single/DebtAuctionHouse.t.sol b/test/single/DebtAuctionHouse.t.sol index 336d7a79..a2c3710f 100644 --- a/test/single/DebtAuctionHouse.t.sol +++ b/test/single/DebtAuctionHouse.t.sol @@ -87,7 +87,7 @@ contract SingleDebtAuctionHouseTest is DSTest { ISAFEEngine.SAFEEngineParams memory _safeEngineParams = ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - protocolToken = new ProtocolToken('', ''); + protocolToken = new ProtocolToken(); IDebtAuctionHouse.DebtAuctionHouseParams memory _debtAuctionHouseParams = IDebtAuctionHouse.DebtAuctionHouseParams({ bidDecrease: 1.05e18, diff --git a/test/single/StabilityFeeTreasury.t.sol b/test/single/StabilityFeeTreasury.t.sol index d1759a27..47ae80df 100644 --- a/test/single/StabilityFeeTreasury.t.sol +++ b/test/single/StabilityFeeTreasury.t.sol @@ -72,7 +72,8 @@ contract SingleStabilityFeeTreasuryTest is DSTest { ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - systemCoin = new SystemCoin('Coin', 'COIN'); + systemCoin = new SystemCoin(); + systemCoin.initialize('Coin', 'COIN'); systemCoinA = new CoinJoin(address(safeEngine), address(systemCoin)); IStabilityFeeTreasury.StabilityFeeTreasuryParams memory _stabilityFeeTreasuryParams = IStabilityFeeTreasury diff --git a/test/single/SurplusAuctionHouse.t.sol b/test/single/SurplusAuctionHouse.t.sol index cf587af5..3cc82a03 100644 --- a/test/single/SurplusAuctionHouse.t.sol +++ b/test/single/SurplusAuctionHouse.t.sol @@ -134,7 +134,7 @@ contract SingleBurningSurplusAuctionHouseTest is DSTest { ISAFEEngine.SAFEEngineParams memory _safeEngineParams = ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - protocolToken = new ProtocolToken('', ''); + protocolToken = new ProtocolToken(); ISurplusAuctionHouse.SurplusAuctionHouseParams memory _sahParams = ISurplusAuctionHouse.SurplusAuctionHouseParams({ bidIncrease: 1.05e18, @@ -262,7 +262,7 @@ contract SingleRecyclingSurplusAuctionHouseTest is DSTest { ISAFEEngine.SAFEEngineParams memory _safeEngineParams = ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - protocolToken = new ProtocolToken('', ''); + protocolToken = new ProtocolToken(); ISurplusAuctionHouse.SurplusAuctionHouseParams memory _sahParams = ISurplusAuctionHouse.SurplusAuctionHouseParams({ bidIncrease: 1.05e18, @@ -397,7 +397,7 @@ contract SingleMixedStratSurplusAuctionHouseTest is DSTest { ISAFEEngine.SAFEEngineParams memory _safeEngineParams = ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - protocolToken = new ProtocolToken('', ''); + protocolToken = new ProtocolToken(); ISurplusAuctionHouse.SurplusAuctionHouseParams memory _sahParams = ISurplusAuctionHouse.SurplusAuctionHouseParams({ bidIncrease: 1.05e18, @@ -535,7 +535,7 @@ contract SinglePostSettlementSurplusAuctionHouseTest is DSTest { ISAFEEngine.SAFEEngineParams memory _safeEngineParams = ISAFEEngine.SAFEEngineParams({safeDebtCeiling: type(uint256).max, globalDebtCeiling: 0}); safeEngine = new SAFEEngine(_safeEngineParams); - protocolToken = new ProtocolToken('', ''); + protocolToken = new ProtocolToken(); IPostSettlementSurplusAuctionHouse.PostSettlementSAHParams memory _pssahParams = IPostSettlementSurplusAuctionHouse .PostSettlementSAHParams({bidIncrease: 1.05e18, bidDuration: 3 hours, totalAuctionLength: 2 days}); diff --git a/test/unit/TokenDistributor.t.sol b/test/unit/TokenDistributor.t.sol index 499194d0..535d5d7d 100644 --- a/test/unit/TokenDistributor.t.sol +++ b/test/unit/TokenDistributor.t.sol @@ -5,7 +5,7 @@ import {HaiTest, stdStorage, StdStorage} from '@test/utils/HaiTest.t.sol'; import {MerkleTreeGenerator} from '@test/utils/MerkleTreeGenerator.sol'; import {ITokenDistributor, TokenDistributor} from '@contracts/tokens/TokenDistributor.sol'; import {IAuthorizable} from '@interfaces/utils/IAuthorizable.sol'; -import {ERC20Votes} from '@openzeppelin/token/ERC20/extensions/ERC20Votes.sol'; +import {ERC20VotesUpgradeable} from '@openzeppelin-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol'; import {Assertions} from '@libraries/Assertions.sol'; abstract contract Base is HaiTest { @@ -29,7 +29,7 @@ abstract contract Base is HaiTest { uint256 claimPeriodStart = block.timestamp + 10 days; uint256 claimPeriodEnd = block.timestamp + 20 days; - ERC20Votes token = ERC20Votes(label('ERC20Votes')); + ERC20VotesUpgradeable token = ERC20VotesUpgradeable(label('ERC20VotesUpgradeable')); address deployer = label('deployer'); address delegatee; @@ -136,7 +136,7 @@ contract Unit_TokenDistributor_Constructor is Base { function test_Call_ERC20Votes_Delegate(address _delegate) public { vm.assume(_delegate != address(0)); - vm.expectCall(address(token), abi.encodeWithSelector(ERC20Votes.delegate.selector, _delegate)); + vm.expectCall(address(token), abi.encodeWithSelector(ERC20VotesUpgradeable.delegate.selector, _delegate)); new TokenDistributor(merkleRoot, token, totalClaimable, claimPeriodStart, claimPeriodEnd, _delegate); } @@ -144,7 +144,7 @@ contract Unit_TokenDistributor_Constructor is Base { function test_Revert_Token_IsNull() public { vm.expectRevert(Assertions.NullAddress.selector); - new TokenDistributor(merkleRoot, ERC20Votes(address(0)), totalClaimable, claimPeriodStart, claimPeriodEnd, deployer); + new TokenDistributor(merkleRoot, ERC20VotesUpgradeable(address(0)), totalClaimable, claimPeriodStart, claimPeriodEnd, deployer); } function test_Revert_TotalClaimable_IsNull() public { diff --git a/yarn.lock b/yarn.lock index 277d5ce1..4a0de8b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -239,6 +239,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts-upgradeable@4.8.2": + version "4.8.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.8.2.tgz#edef522bdbc46d478481391553bababdd2199e27" + integrity sha512-zIggnBwemUmmt9IS73qxi+tumALxCY4QEs3zLCII78k0Gfse2hAOdAkuAeLUzvWUpneMUfFE5sGHzEUSTvn4Ag== + "@openzeppelin/contracts@4.6.0": version "4.6.0" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.6.0.tgz#c91cf64bc27f573836dba4122758b4743418c1b3"