From 668cab43c22f42d1378ead59f949852c7e930405 Mon Sep 17 00:00:00 2001 From: echo Date: Fri, 7 Jun 2024 16:38:21 +0800 Subject: [PATCH] fix #11 --- script/Deploy.s.sol | 19 +++++++----- src/collator/CollatorSet.sol | 30 +++++++++---------- src/collator/CollatorStakingHub.sol | 27 +++++++++-------- src/collator/CollatorStakingHubStorage.sol | 4 +++ src/collator/CollatorStakingPool.sol | 4 ++- src/collator/interfaces/IGRING.sol | 9 ++++++ src/governance/GovernanceRing.sol | 35 ++++++++++------------ 7 files changed, 73 insertions(+), 55 deletions(-) create mode 100644 src/collator/interfaces/IGRING.sol diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 8d27f91..5407ea5 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -24,12 +24,6 @@ contract DeployScript is Script { "Deposit.sol:Deposit", multisig, abi.encodeCall(Deposit.initialize, ("RING Deposit NFT", "RDPS")) ); - address hub = Upgrades.deployTransparentProxy( - "CollatorStakingHub.sol:CollatorStakingHub", - multisig, - abi.encodeCall(CollatorStakingHub.initialize, (deposit, "RING")) - ); - uint256 minDelay = 3 days; address timelock = Upgrades.deployTransparentProxy( "RingTimelockController.sol:RingTimelockController", @@ -40,7 +34,7 @@ contract DeployScript is Script { address gRING = Upgrades.deployTransparentProxy( "GovernanceRing.sol:GovernanceRing", timelock, - abi.encodeCall(GovernanceRing.initialize, (deposit, hub, "Governance RING", "gRING")) + abi.encodeCall(GovernanceRing.initialize, (multisig, deposit, "Governance RING", "gRING")) ); address ringDAO = Upgrades.deployTransparentProxy( @@ -50,7 +44,16 @@ contract DeployScript is Script { RingDAO.initialize, (IVotes(gRING), TimelockControllerUpgradeable(payable(timelock)), "RingDAO") ) ); - // RingTimelockController(timelock).granRole(RingTimelockController(timelock).PROPOSER_ROLE, ringDAO); + + address hub = Upgrades.deployTransparentProxy( + "CollatorStakingHub.sol:CollatorStakingHub", + timelock, + abi.encodeCall(CollatorStakingHub.initialize, (gRING, deposit, "RING")) + ); + + // RingTimelockController(timelock).grantRole(RingTimelockController(timelock).PROPOSER_ROLE(), ringDAO); + // RingTimelockController(gRING).grantRole(GovernanceRing(gRING).MINTER_ROLE(), hub); + // RingTimelockController(gRING).grantRole(GovernanceRing(gRING).BURNER_ROLE(), hub); vm.stopBroadcast(); } diff --git a/src/collator/CollatorSet.sol b/src/collator/CollatorSet.sol index 8d5ec67..ac8d006 100644 --- a/src/collator/CollatorSet.sol +++ b/src/collator/CollatorSet.sol @@ -8,9 +8,9 @@ abstract contract CollatorSet is Initializable, CollatorStakingHubStorage { address private constant HEAD = address(0x1); address private constant TAIL = address(0x2); - event AddCollator(address indexed cur, uint256 score, address prev); + event AddCollator(address indexed cur, uint256 votes, address prev); event RemoveCollator(address indexed cur, address prev); - event UpdateCollator(address indexed cur, uint256 score, address oldPrev, address newPrev); + event UpdateCollator(address indexed cur, uint256 votes, address oldPrev, address newPrev); function __CollatorSet_init() internal onlyInitializing { collators[HEAD] = collators[TAIL]; @@ -33,19 +33,19 @@ abstract contract CollatorSet is Initializable, CollatorStakingHubStorage { return c != address(0) && c != HEAD && c != TAIL; } - function _addCollator(address cur, uint256 score, address prev) internal { + function _addCollator(address cur, uint256 votes, address prev) internal { require(_isValid(cur), "!valid"); address next = collators[prev]; // No duplicate collator allowed. require(collators[cur] == address(0), "!cur"); // Next collaotr must in the list. require(next != address(0), "!prev"); - require(_verifyIndex(prev, score, next), "!score"); + require(_verifyIndex(prev, votes, next), "!votes"); collators[cur] = next; collators[prev] = cur; - votesOf[cur] = score; + votesOf[cur] = votes; count++; - emit AddCollator(cur, score, prev); + emit AddCollator(cur, votes, prev); } function _removeCollator(address cur, address prev) internal { @@ -59,28 +59,28 @@ abstract contract CollatorSet is Initializable, CollatorStakingHubStorage { emit RemoveCollator(cur, prev); } - function _increaseScore(address cur, uint256 score, address oldPrev, address newPrev) internal { - _updateScore(cur, votesOf[cur] + score, oldPrev, newPrev); + function _increaseVotes(address cur, uint256 votes, address oldPrev, address newPrev) internal { + _updateVotes(cur, votesOf[cur] + votes, oldPrev, newPrev); } - function _reduceScore(address cur, uint256 score, address oldPrev, address newPrev) internal { - _updateScore(cur, votesOf[cur] - score, oldPrev, newPrev); + function _reduceVotes(address cur, uint256 votes, address oldPrev, address newPrev) internal { + _updateVotes(cur, votesOf[cur] - votes, oldPrev, newPrev); } - function _updateScore(address cur, uint256 newScore, address oldPrev, address newPrev) internal { + function _updateVotes(address cur, uint256 newVotes, address oldPrev, address newPrev) internal { require(_isValid(cur), "!valid"); require(collators[cur] != address(0), "!cur"); require(collators[oldPrev] != address(0), "!oldPrev"); require(collators[newPrev] != address(0), "!newPrev"); if (oldPrev == newPrev) { require(_isPrevCollator(cur, oldPrev), "!oldPrev"); - require(_verifyIndex(newPrev, newScore, collators[cur]), "!score"); - votesOf[cur] = newScore; + require(_verifyIndex(newPrev, newVotes, collators[cur]), "!votes"); + votesOf[cur] = newVotes; } else { _removeCollator(cur, oldPrev); - _addCollator(cur, newScore, newPrev); + _addCollator(cur, newVotes, newPrev); } - emit UpdateCollator(cur, newScore, oldPrev, newPrev); + emit UpdateCollator(cur, newVotes, oldPrev, newPrev); } // prev >= cur >= next diff --git a/src/collator/CollatorStakingHub.sol b/src/collator/CollatorStakingHub.sol index 784d85d..5e01141 100644 --- a/src/collator/CollatorStakingHub.sol +++ b/src/collator/CollatorStakingHub.sol @@ -9,6 +9,7 @@ import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "./interfaces/ICollatorStakingPool.sol"; +import "./interfaces/IGRING.sol"; import "../deposit/interfaces/IDeposit.sol"; import "./CollatorStakingPool.sol"; import "./CollatorSet.sol"; @@ -18,7 +19,9 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat using Address for address payable; using EnumerableSet for EnumerableSet.UintSet; - // Staking Pallet Account + // The lock-up period starts with the stake or inscrease stake. + uint256 public constant LOCK_PERIOD = 1 days; + // Staking Pallet Account. address public constant STAKING_PALLET = 0x6D6F646C64612f7374616B690000000000000000; // 0 ~ 100 uint256 private constant COMMISSION_BASE = 100; @@ -33,7 +36,8 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat _; } - function initialize(address dps, string memory symbol) public initializer { + function initialize(address gring, address dps, string memory symbol) public initializer { + gRING = gring; DEPOSIT = dps; SYMBOL = symbol; __ReentrancyGuard_init(); @@ -54,13 +58,8 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat require(poolOf[collator] == address(0), "created"); uint256 index = count; - string memory indexStr = index.toString(); - string memory tmp = string.concat(string.concat(SYMBOL, "-"), indexStr); - string memory name = string.concat("Collator Staking ", tmp); - string memory symbol = string.concat("st", tmp); - bytes memory bytecode = type(CollatorStakingPool).creationCode; - bytes memory initCode = bytes.concat(bytecode, abi.encode(collator, name, symbol)); + bytes memory initCode = bytes.concat(bytecode, abi.encode(collator, index)); assembly { pool := create2(0, add(initCode, 32), mload(initCode), 0) } @@ -75,15 +74,19 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat } function _stake(address collator, address account, uint256 assets) internal { + stakingLocks[collator][account] = LOCK_PERIOD + block.timestamp; address pool = poolOf[collator]; require(pool != address(0), "!collator"); ICollatorStakingPool(pool).stake(account, assets); + IGRING(gRING).mint(account, assets); emit Staked(pool, collator, account, assets); } function _unstake(address collator, address account, uint256 assets) internal { + require(stakingLocks[collator][account] < block.timestamp, "!locked"); address pool = poolOf[collator]; require(pool != address(0), "!collator"); + IGRING(gRING).burn(account, assets); ICollatorStakingPool(pool).withdraw(account, assets); emit Unstaked(pool, collator, account, assets); } @@ -96,14 +99,14 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat function stakeRING(address collator, address oldPrev, address newPrev) public payable nonReentrant { _stake(collator, msg.sender, msg.value); - _increaseScore(collator, _assetsToVotes(commissionOf[collator], msg.value), oldPrev, newPrev); + _increaseVotes(collator, _assetsToVotes(commissionOf[collator], msg.value), oldPrev, newPrev); stakedRINGOf[msg.sender] += msg.value; } function unstakeRING(address collator, uint256 assets, address oldPrev, address newPrev) public nonReentrant { _unstake(collator, msg.sender, assets); payable(msg.sender).sendValue(assets); - _reduceScore(collator, _assetsToVotes(commissionOf[collator], assets), oldPrev, newPrev); + _reduceVotes(collator, _assetsToVotes(commissionOf[collator], assets), oldPrev, newPrev); stakedRINGOf[msg.sender] -= assets; } @@ -114,7 +117,7 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat depositInfos[depositId] = DepositInfo(account, assets, collator); _stake(collator, account, assets); - _increaseScore(collator, _assetsToVotes(commissionOf[collator], assets), oldPrev, newPrev); + _increaseVotes(collator, _assetsToVotes(commissionOf[collator], assets), oldPrev, newPrev); require(_stakedDeposits[account].add(depositId), "!add"); } @@ -126,7 +129,7 @@ contract CollatorStakingHub is Initializable, ReentrancyGuardUpgradeable, Collat delete depositInfos[depositId]; _unstake(info.collator, info.account, info.assets); - _reduceScore(info.collator, _assetsToVotes(commissionOf[info.collator], info.assets), oldPrev, newPrev); + _reduceVotes(info.collator, _assetsToVotes(commissionOf[info.collator], info.assets), oldPrev, newPrev); require(_stakedDeposits[account].remove(depositId), "!remove"); } diff --git a/src/collator/CollatorStakingHubStorage.sol b/src/collator/CollatorStakingHubStorage.sol index 025b907..3be4e04 100644 --- a/src/collator/CollatorStakingHubStorage.sol +++ b/src/collator/CollatorStakingHubStorage.sol @@ -13,6 +13,8 @@ contract CollatorStakingHubStorage { mapping(address => uint256) public votesOf; // ---------------------- CollatorStakingHubStorage --------------------- + // Governance RING + address public gRING; // Deposit NFT. address public DEPOSIT; // CollatorStakingPool symbol subfix @@ -23,6 +25,8 @@ contract CollatorStakingHubStorage { mapping(address => address) public collatorOf; // collator => commission mapping(address => uint256) public commissionOf; + // collator => user => lockTime + mapping(address => mapping(address => uint256)) public stakingLocks; // user => staked ring mapping(address => uint256) public stakedRINGOf; // user => staked depositIds diff --git a/src/collator/CollatorStakingPool.sol b/src/collator/CollatorStakingPool.sol index b131dad..50d57b2 100644 --- a/src/collator/CollatorStakingPool.sol +++ b/src/collator/CollatorStakingPool.sol @@ -9,6 +9,7 @@ contract CollatorStakingPool { /* ========== STATE VARIABLES ========== */ + uint256 public id; address public hub; address public collator; uint256 public periodFinish = 0; @@ -30,9 +31,10 @@ contract CollatorStakingPool { /* ========== CONSTRUCTOR ========== */ - constructor(address collator_) { + constructor(address collator_, uint256 index) { hub = msg.sender; collator = collator_; + id = index; } /* ========== VIEWS ========== */ diff --git a/src/collator/interfaces/IGRING.sol b/src/collator/interfaces/IGRING.sol new file mode 100644 index 0000000..a72a0d4 --- /dev/null +++ b/src/collator/interfaces/IGRING.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.4.24; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IGRING is IERC20 { + function burn(address from, uint256 amount) external returns (bool); + function mint(address to, uint256 amount) external returns (bool); +} diff --git a/src/governance/GovernanceRing.sol b/src/governance/GovernanceRing.sol index 5ffa963..32b6395 100644 --- a/src/governance/GovernanceRing.sol +++ b/src/governance/GovernanceRing.sol @@ -4,17 +4,18 @@ pragma solidity 0.8.20; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import "../collator/interfaces/ICollatorStakingHub.sol"; import "../deposit/interfaces/IDeposit.sol"; contract GovernanceRing is Initializable, ERC20Upgradeable, + AccessControlUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ReentrancyGuardUpgradeable @@ -28,28 +29,26 @@ contract GovernanceRing is mapping(address => mapping(address => uint256)) public wrapAssets; // user => wrap depositIds mapping(address => EnumerableSet.UintSet) private _wrapDeposits; - ICollatorStakingHub public HUB; IDeposit public DEPOSIT; address public constant RING = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); event Wrap(address indexed account, address indexed token, uint256 assets); event Unwrap(address indexed account, address indexed token, uint256 assets); event WrapDeposit(address indexed account, address indexed token, uint256 depositId); event UnwrapDeposit(address indexed account, address indexed token, uint256 depositId); - modifier onlySTRING(address token) { - require(HUB.exist(token), "!stRING"); - _; - } - - function initialize(address dps, address hub, string memory name, string memory symbol) public initializer { + function initialize(address admin, address dps, string memory name, string memory symbol) public initializer { DEPOSIT = IDeposit(dps); - HUB = ICollatorStakingHub(hub); __ERC20_init(name, symbol); + __AccessControl_init(); __ERC20Permit_init(symbol); __ERC20Votes_init(); __ReentrancyGuard_init(); + + _grantRole(DEFAULT_ADMIN_ROLE, admin); } /// @custom:oz-upgrades-unsafe-allow constructor @@ -57,6 +56,14 @@ contract GovernanceRing is _disableInitializers(); } + function mint(address to, uint256 assets) public onlyRole(MINTER_ROLE) { + _mint(to, assets); + } + + function burn(address from, uint256 assets) public onlyRole(BURNER_ROLE) { + _burn(from, assets); + } + function _wrap(address account, address token, uint256 assets) internal { _mint(account, assets); wrapAssets[account][token] += assets; @@ -78,16 +85,6 @@ contract GovernanceRing is payable(msg.sender).sendValue(assets); } - function wrapCRING(address cring, uint256 assets) external onlySTRING(cring) nonReentrant { - IERC20(cring).transferFrom(msg.sender, address(this), assets); - _wrap(msg.sender, cring, assets); - } - - function unwrapCRING(address cring, uint256 assets) external onlySTRING(cring) nonReentrant { - _unwrap(msg.sender, cring, assets); - IERC20(cring).transferFrom(address(this), msg.sender, assets); - } - function wrapDeposit(uint256 depositId) public nonReentrant { address account = msg.sender; DEPOSIT.transferFrom(account, address(this), depositId);