diff --git a/contracts/gov/ERC721/powers/AbstractERC721Power.sol b/contracts/gov/ERC721/powers/AbstractERC721Power.sol index 14d1eb1c..13fc0047 100644 --- a/contracts/gov/ERC721/powers/AbstractERC721Power.sol +++ b/contracts/gov/ERC721/powers/AbstractERC721Power.sol @@ -115,12 +115,15 @@ abstract contract AbstractERC721Power is function getNftMaxPower(uint256 tokenId) public view virtual returns (uint256); + function getNftMinPower(uint256 tokenId) public view virtual returns (uint256); + function getNftPower(uint256 tokenId) public view virtual returns (uint256); function getNftInfo(uint256 tokenId) external view virtual returns (NftInfoView memory info) { info.rawInfo = _nftInfos[tokenId]; info.maxPower = getNftMaxPower(tokenId); + info.minPower = getNftMinPower(tokenId); info.currentPower = getNftPower(tokenId); } @@ -200,7 +203,7 @@ abstract contract AbstractERC721Power is } function _recalculateRawNftPower(uint256 tokenId) internal { - if (!_exists(tokenId) || block.timestamp < powerCalcStartTimestamp) { + if (!_isActiveNft(tokenId)) { return; } @@ -218,16 +221,13 @@ abstract contract AbstractERC721Power is } function _getRawNftPower(uint256 tokenId) internal view returns (uint256) { - if (!_exists(tokenId) || block.timestamp < powerCalcStartTimestamp) { + if (!_isActiveNft(tokenId)) { return 0; } - uint256 collateral = _nftInfos[tokenId].currentCollateral; - // Calculate the minimum possible power based on the collateral of the nft uint256 maxNftPower = _getRawNftMaxPower(tokenId); - uint256 minNftPower = maxNftPower.ratio(collateral, _getNftRequiredCollateral(tokenId)); - minNftPower = maxNftPower.min(minNftPower); + uint256 minNftPower = _getRawNftMinPower(tokenId); // Get last update and current power. Or set them to default if it is first iteration uint64 lastUpdate = _nftInfos[tokenId].lastUpdate; @@ -260,12 +260,29 @@ abstract contract AbstractERC721Power is return localRawPower == 0 ? nftMaxRawPower : localRawPower; } + function _getRawNftMinPower(uint256 tokenId) internal view returns (uint256) { + if (!_isActiveNft(tokenId)) { + return 0; + } + + uint256 maxNftPower = _getRawNftMaxPower(tokenId); + + return + maxNftPower + .ratio(_nftInfos[tokenId].currentCollateral, _getNftRequiredCollateral(tokenId)) + .min(maxNftPower); + } + function _getNftRequiredCollateral(uint256 tokenId) internal view returns (uint256) { uint256 requiredCollateralForNft = _nftInfos[tokenId].requiredCollateral; return requiredCollateralForNft == 0 ? nftRequiredCollateral : requiredCollateralForNft; } + function _isActiveNft(uint256 tokenId) internal view returns (bool) { + return _exists(tokenId) && block.timestamp >= powerCalcStartTimestamp; + } + function _onlyBeforePowerCalc() internal view { require( block.timestamp < powerCalcStartTimestamp, diff --git a/contracts/gov/ERC721/powers/ERC721EquivalentPower.sol b/contracts/gov/ERC721/powers/ERC721EquivalentPower.sol index 69f0edbb..91d43633 100644 --- a/contracts/gov/ERC721/powers/ERC721EquivalentPower.sol +++ b/contracts/gov/ERC721/powers/ERC721EquivalentPower.sol @@ -58,6 +58,10 @@ contract ERC721EquivalentPower is AbstractERC721Power { return _getEquivalentPower(_getRawNftMaxPower(tokenId)); } + function getNftMinPower(uint256 tokenId) public view override returns (uint256) { + return _getEquivalentPower(_getRawNftMinPower(tokenId)); + } + function getNftPower(uint256 tokenId) public view override returns (uint256) { return _getEquivalentPower(_getRawNftPower(tokenId)); } diff --git a/contracts/gov/ERC721/powers/ERC721RawPower.sol b/contracts/gov/ERC721/powers/ERC721RawPower.sol index b3a8fd87..f45ed7e6 100644 --- a/contracts/gov/ERC721/powers/ERC721RawPower.sol +++ b/contracts/gov/ERC721/powers/ERC721RawPower.sol @@ -50,6 +50,10 @@ contract ERC721RawPower is AbstractERC721Power { return _getRawNftMaxPower(tokenId).min(totalRawPower); } + function getNftMinPower(uint256 tokenId) public view override returns (uint256) { + return _getRawNftMinPower(tokenId).min(totalRawPower); + } + function getNftPower(uint256 tokenId) public view override returns (uint256) { return _getRawNftPower(tokenId).min(totalRawPower); } diff --git a/contracts/gov/GovPool.sol b/contracts/gov/GovPool.sol index 6cc638ac..4bd3f4f9 100644 --- a/contracts/gov/GovPool.sol +++ b/contracts/gov/GovPool.sol @@ -244,6 +244,8 @@ contract GovPool is _unlock(msg.sender); _unlock(delegatee); + _updateNftPowers(nftIds); + _govUserKeeper.delegateTokens.exec(delegatee, amount); _govUserKeeper.delegateNfts.exec(delegatee, nftIds); @@ -281,6 +283,8 @@ contract GovPool is nft.safeTransferFrom(address(this), address(_govUserKeeper), nftIds[i]); } + _updateNftPowers(nftIds); + _govUserKeeper.delegateNftsTreasury(delegatee, nftIds); } @@ -300,6 +304,8 @@ contract GovPool is _unlock(delegatee); + _updateNftPowers(nftIds); + _govUserKeeper.undelegateTokens.exec(delegatee, amount); _govUserKeeper.undelegateNfts.exec(delegatee, nftIds); @@ -321,6 +327,8 @@ contract GovPool is _unlock(msg.sender); + _updateNftPowers(nftIds); + _govUserKeeper.undelegateTokensTreasury.exec(delegatee, amount); _govUserKeeper.undelegateNftsTreasury.exec(delegatee, nftIds); @@ -555,6 +563,8 @@ contract GovPool is uint256[] calldata voteNftIds, bool isVoteFor ) internal { + _updateNftPowers(voteNftIds); + _proposals.vote(_userInfos, proposalId, voteAmount, voteNftIds, isVoteFor); } @@ -572,6 +582,10 @@ contract GovPool is } } + function _updateNftPowers(uint256[] calldata nftIds) internal { + _govUserKeeper.updateNftPowers(nftIds); + } + function _unlock(address user) internal { _userInfos.unlockInProposals(user); } diff --git a/contracts/gov/user-keeper/GovUserKeeper.sol b/contracts/gov/user-keeper/GovUserKeeper.sol index d600eb07..5d4cc437 100644 --- a/contracts/gov/user-keeper/GovUserKeeper.sol +++ b/contracts/gov/user-keeper/GovUserKeeper.sol @@ -16,12 +16,11 @@ import "@solarity/solidity-lib/libs/data-structures/memory/Vector.sol"; import "../../interfaces/gov/user-keeper/IGovUserKeeper.sol"; import "../../interfaces/gov/IGovPool.sol"; +import "../../interfaces/gov/ERC721/powers/IERC721Power.sol"; import "../../libs/math/MathHelper.sol"; import "../../libs/gov/gov-user-keeper/GovUserKeeperView.sol"; -import "../ERC721/powers/AbstractERC721Power.sol"; - contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgradeable { using SafeERC20 for IERC20; using Math for uint256; @@ -35,13 +34,9 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad using Vector for Vector.UintVector; address public tokenAddress; - address public nftAddress; - NFTInfo internal _nftInfo; mapping(address => UserInfo) internal _usersInfo; // user => info - mapping(address => BalanceInfo) internal _micropoolsInfo; // user = micropool address => balance info - mapping(address => BalanceInfo) internal _treasuryPoolsInfo; // user => balance info mapping(uint256 => uint256) internal _nftLockedNums; // tokenId => locked num @@ -87,7 +82,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad IERC20(token).safeTransferFrom(payer, address(this), amount.from18(token.decimals())); - _usersInfo[receiver].balanceInfo.tokenBalance += amount; + _usersInfo[receiver].balances[IGovPool.VoteType.PersonalVote].tokens += amount; } function withdrawTokens( @@ -96,10 +91,10 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256 amount ) external override onlyOwner withSupportedToken { UserInfo storage payerInfo = _usersInfo[payer]; - BalanceInfo storage payerBalanceInfo = payerInfo.balanceInfo; + BalanceInfo storage payerBalanceInfo = payerInfo.balances[IGovPool.VoteType.PersonalVote]; address token = tokenAddress; - uint256 balance = payerBalanceInfo.tokenBalance; + uint256 balance = payerBalanceInfo.tokens; uint256 maxTokensLocked = payerInfo.maxTokensLocked; require( @@ -107,7 +102,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad "GovUK: can't withdraw this" ); - payerBalanceInfo.tokenBalance = balance - amount; + payerBalanceInfo.tokens = balance - amount; IERC20(token).safeTransfer(receiver, amount.from18(token.decimals())); } @@ -118,18 +113,20 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256 amount ) external override onlyOwner withSupportedToken { UserInfo storage delegatorInfo = _usersInfo[delegator]; - BalanceInfo storage delegatorBalanceInfo = delegatorInfo.balanceInfo; + BalanceInfo storage delegatorBalanceInfo = delegatorInfo.balances[ + IGovPool.VoteType.PersonalVote + ]; - uint256 balance = delegatorBalanceInfo.tokenBalance; + uint256 balance = delegatorBalanceInfo.tokens; uint256 maxTokensLocked = delegatorInfo.maxTokensLocked; require(amount <= balance.max(maxTokensLocked) - maxTokensLocked, "GovUK: overdelegation"); - delegatorInfo.delegatedTokens[delegatee] += amount; - delegatorInfo.allDelegatedTokens += amount; - delegatorBalanceInfo.tokenBalance = balance - amount; + delegatorInfo.delegatedBalances[delegatee].tokens += amount; + delegatorInfo.allDelegatedBalance.tokens += amount; + delegatorBalanceInfo.tokens = balance - amount; - _micropoolsInfo[delegatee].tokenBalance += amount; + _usersInfo[delegatee].balances[IGovPool.VoteType.MicropoolVote].tokens += amount; delegatorInfo.delegatees.add(delegatee); } @@ -138,7 +135,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address delegatee, uint256 amount ) external override onlyOwner withSupportedToken { - _treasuryPoolsInfo[delegatee].tokenBalance += amount; + _usersInfo[delegatee].balances[IGovPool.VoteType.TreasuryVote].tokens += amount; } function undelegateTokens( @@ -149,15 +146,15 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad UserInfo storage delegatorInfo = _usersInfo[delegator]; require( - amount <= delegatorInfo.delegatedTokens[delegatee], + amount <= delegatorInfo.delegatedBalances[delegatee].tokens, "GovUK: amount exceeds delegation" ); - _micropoolsInfo[delegatee].tokenBalance -= amount; + _usersInfo[delegatee].balances[IGovPool.VoteType.MicropoolVote].tokens -= amount; - delegatorInfo.balanceInfo.tokenBalance += amount; - delegatorInfo.delegatedTokens[delegatee] -= amount; - delegatorInfo.allDelegatedTokens -= amount; + delegatorInfo.balances[IGovPool.VoteType.PersonalVote].tokens += amount; + delegatorInfo.delegatedBalances[delegatee].tokens -= amount; + delegatorInfo.allDelegatedBalance.tokens -= amount; _cleanDelegatee(delegatorInfo, delegatee); } @@ -166,13 +163,15 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address delegatee, uint256 amount ) external override onlyOwner withSupportedToken { - BalanceInfo storage delegateeBalanceInfo = _treasuryPoolsInfo[delegatee]; + BalanceInfo storage delegateeBalanceInfo = _usersInfo[delegatee].balances[ + IGovPool.VoteType.TreasuryVote + ]; - uint256 balance = delegateeBalanceInfo.tokenBalance; + uint256 balance = delegateeBalanceInfo.tokens; require(amount <= balance, "GovUK: can't withdraw this"); - delegateeBalanceInfo.tokenBalance = balance - amount; + delegateeBalanceInfo.tokens = balance - amount; address token = tokenAddress; @@ -185,10 +184,10 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { EnumerableSet.UintSet storage receiverNftBalance = _usersInfo[receiver] - .balanceInfo - .nftBalance; + .balances[IGovPool.VoteType.PersonalVote] + .nfts; - IERC721 nft = IERC721(nftAddress); + IERC721Power nft = IERC721Power(_nftInfo.nftAddress); for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; @@ -204,9 +203,11 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address receiver, uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { - EnumerableSet.UintSet storage payerNftBalance = _usersInfo[payer].balanceInfo.nftBalance; + EnumerableSet.UintSet storage payerNftBalance = _usersInfo[payer] + .balances[IGovPool.VoteType.PersonalVote] + .nfts; - IERC721 nft = IERC721(nftAddress); + IERC721 nft = IERC721(_nftInfo.nftAddress); for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; @@ -228,12 +229,23 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { UserInfo storage delegatorInfo = _usersInfo[delegator]; - - EnumerableSet.UintSet storage delegatorNftBalance = delegatorInfo.balanceInfo.nftBalance; - EnumerableSet.UintSet storage delegatedNfts = delegatorInfo.delegatedNfts[delegatee]; - EnumerableSet.UintSet storage allDelegatedNfts = delegatorInfo.allDelegatedNfts; - - EnumerableSet.UintSet storage delegateeNftBalance = _micropoolsInfo[delegatee].nftBalance; + UserInfo storage delegateeInfo = _usersInfo[delegatee]; + + EnumerableSet.UintSet storage delegatorNftBalance = delegatorInfo + .balances[IGovPool.VoteType.PersonalVote] + .nfts; + EnumerableSet.UintSet storage delegatedNfts = delegatorInfo + .delegatedBalances[delegatee] + .nfts; + EnumerableSet.UintSet storage allDelegatedNfts = delegatorInfo.allDelegatedBalance.nfts; + + EnumerableSet.UintSet storage delegateeNftBalance = delegateeInfo + .balances[IGovPool.VoteType.MicropoolVote] + .nfts; + + IERC721Power nft = IERC721Power(_nftInfo.nftAddress); + bool isSupportPower = _nftInfo.isSupportPower; + uint256 nftPower; for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; @@ -249,20 +261,47 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad allDelegatedNfts.add(nftId); delegateeNftBalance.add(nftId); + + if (isSupportPower) { + _nftInfo.nftMinPower[nftId] = nft.getNftMinPower(nftId); + nftPower += _nftInfo.nftMinPower[nftId]; + } } delegatorInfo.delegatees.add(delegatee); + + if (isSupportPower) { + delegatorInfo.delegatedNftPowers[delegatee] += nftPower; + delegateeInfo.nftsPowers[IGovPool.VoteType.MicropoolVote] += nftPower; + } } function delegateNftsTreasury( address delegatee, uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { - EnumerableSet.UintSet storage delegateeNftBalance = _treasuryPoolsInfo[delegatee] - .nftBalance; + UserInfo storage delegateeInfo = _usersInfo[delegatee]; + EnumerableSet.UintSet storage delegateeNftBalance = delegateeInfo + .balances[IGovPool.VoteType.TreasuryVote] + .nfts; + + IERC721Power nft = IERC721Power(_nftInfo.nftAddress); + bool isSupportPower = _nftInfo.isSupportPower; + uint256 nftPower; for (uint256 i; i < nftIds.length; i++) { - delegateeNftBalance.add(nftIds[i]); + uint256 nftId = nftIds[i]; + + delegateeNftBalance.add(nftId); + + if (isSupportPower) { + _nftInfo.nftMinPower[nftId] = nft.getNftMinPower(nftId); + nftPower += _nftInfo.nftMinPower[nftId]; + } + } + + if (isSupportPower) { + delegateeInfo.nftsPowers[IGovPool.VoteType.TreasuryVote] += nftPower; } } @@ -272,12 +311,22 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { UserInfo storage delegatorInfo = _usersInfo[delegator]; + UserInfo storage delegateeInfo = _usersInfo[delegatee]; - EnumerableSet.UintSet storage delegatorNftBalance = delegatorInfo.balanceInfo.nftBalance; - EnumerableSet.UintSet storage delegatedNfts = delegatorInfo.delegatedNfts[delegatee]; - EnumerableSet.UintSet storage allDelegatedNfts = delegatorInfo.allDelegatedNfts; + EnumerableSet.UintSet storage delegatorNftBalance = delegatorInfo + .balances[IGovPool.VoteType.PersonalVote] + .nfts; + EnumerableSet.UintSet storage delegatedNfts = delegatorInfo + .delegatedBalances[delegatee] + .nfts; + EnumerableSet.UintSet storage allDelegatedNfts = delegatorInfo.allDelegatedBalance.nfts; - EnumerableSet.UintSet storage delegateeNftBalance = _micropoolsInfo[delegatee].nftBalance; + EnumerableSet.UintSet storage delegateeNftBalance = delegateeInfo + .balances[IGovPool.VoteType.MicropoolVote] + .nfts; + + bool isSupportPower = _nftInfo.isSupportPower; + uint256 nftPower; for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; @@ -290,6 +339,16 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad allDelegatedNfts.remove(nftId); delegatorNftBalance.add(nftId); + + if (isSupportPower) { + nftPower += _nftInfo.nftMinPower[nftId]; + delete _nftInfo.nftMinPower[nftId]; + } + } + + if (isSupportPower) { + delegatorInfo.delegatedNftPowers[delegatee] -= nftPower; + delegateeInfo.nftsPowers[IGovPool.VoteType.MicropoolVote] -= nftPower; } _cleanDelegatee(delegatorInfo, delegatee); @@ -299,10 +358,14 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address delegatee, uint256[] calldata nftIds ) external override onlyOwner withSupportedNft { - EnumerableSet.UintSet storage delegateeNftBalance = _treasuryPoolsInfo[delegatee] - .nftBalance; + UserInfo storage delegateeInfo = _usersInfo[delegatee]; + EnumerableSet.UintSet storage delegateeNftBalance = delegateeInfo + .balances[IGovPool.VoteType.TreasuryVote] + .nfts; - IERC721 nft = IERC721(nftAddress); + IERC721 nft = IERC721(_nftInfo.nftAddress); + bool isSupportPower = _nftInfo.isSupportPower; + uint256 nftPower; for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; @@ -310,6 +373,15 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad require(delegateeNftBalance.remove(nftId), "GovUK: NFT is not owned"); nft.safeTransferFrom(address(this), msg.sender, nftId); + + if (isSupportPower) { + nftPower += _nftInfo.nftMinPower[nftId]; + delete _nftInfo.nftMinPower[nftId]; + } + } + + if (isSupportPower) { + delegateeInfo.nftsPowers[IGovPool.VoteType.TreasuryVote] -= nftPower; } } @@ -356,14 +428,17 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256[] calldata nftIds ) external override onlyOwner { UserInfo storage voterInfo = _usersInfo[voter]; + EnumerableSet.UintSet storage voteNftBalance = voterInfo + .balances[IGovPool.VoteType.PersonalVote] + .nfts; for (uint256 i; i < nftIds.length; i++) { uint256 nftId = nftIds[i]; - bool hasNft = voterInfo.balanceInfo.nftBalance.contains(nftId); + bool hasNft = voteNftBalance.contains(nftId); if (voteType == IGovPool.VoteType.DelegatedVote) { - hasNft = hasNft || voterInfo.allDelegatedNfts.contains(nftId); + hasNft = hasNft || voterInfo.allDelegatedBalance.nfts.contains(nftId); } require(hasNft, "GovUK: NFT is not owned"); @@ -387,7 +462,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad return; } - AbstractERC721Power(nftAddress).recalculateNftPowers(nftIds); + IERC721Power(_nftInfo.nftAddress).recalculateNftPowers(nftIds); } function setERC20Address(address _tokenAddress) external override onlyOwner { @@ -402,8 +477,17 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad _setERC721Address(_nftAddress, individualPower, nftsTotalSupply); } - function getNftInfo() external view override returns (NFTInfo memory) { - return _nftInfo; + function nftAddress() external view override returns (address) { + return _nftInfo.nftAddress; + } + + function getNftInfo() + external + view + override + returns (bool isSupportPower, uint256 individualPower, uint256 totalSupply) + { + return (_nftInfo.isSupportPower, _nftInfo.individualPower, _nftInfo.totalSupply); } function maxLockedAmount(address voter) external view override returns (uint256) { @@ -418,7 +502,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad return (0, 0); } - totalBalance = _getBalanceInfoStorage(voter, voteType).tokenBalance; + totalBalance = _getBalanceInfoStorage(voter, voteType).tokens; if ( voteType != IGovPool.VoteType.PersonalVote && @@ -428,7 +512,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } if (voteType == IGovPool.VoteType.DelegatedVote) { - totalBalance += _usersInfo[voter].allDelegatedTokens; + totalBalance += _usersInfo[voter].allDelegatedBalance.tokens; } ownedBalance = ERC20(tokenAddress).balanceOf(voter).to18(tokenAddress.decimals()); @@ -439,11 +523,13 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address voter, IGovPool.VoteType voteType ) external view override returns (uint256 totalBalance, uint256 ownedBalance) { - if (nftAddress == address(0)) { + address nftAddress_ = _nftInfo.nftAddress; + + if (nftAddress_ == address(0)) { return (0, 0); } - totalBalance = _getBalanceInfoStorage(voter, voteType).nftBalance.length(); + totalBalance = _getBalanceInfoStorage(voter, voteType).nfts.length(); if ( voteType != IGovPool.VoteType.PersonalVote && @@ -453,10 +539,10 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } if (voteType == IGovPool.VoteType.DelegatedVote) { - totalBalance += _usersInfo[voter].allDelegatedNfts.length(); + totalBalance += _usersInfo[voter].allDelegatedBalance.nfts.length(); } - ownedBalance = ERC721Upgradeable(nftAddress).balanceOf(voter); + ownedBalance = IERC721Upgradeable(nftAddress_).balanceOf(voter); totalBalance += ownedBalance; } @@ -464,12 +550,14 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address voter, IGovPool.VoteType voteType ) public view override returns (uint256[] memory nfts, uint256 ownedLength) { - if (nftAddress == address(0)) { + address nftAddress_ = _nftInfo.nftAddress; + + if (nftAddress_ == address(0)) { return (nfts, 0); } Vector.UintVector memory nftsVector = Vector.newUint( - _getBalanceInfoStorage(voter, voteType).nftBalance.values() + _getBalanceInfoStorage(voter, voteType).nfts.values() ); if ( @@ -480,10 +568,10 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } if (voteType == IGovPool.VoteType.DelegatedVote) { - nftsVector.push(_usersInfo[voter].allDelegatedNfts.values()); + nftsVector.push(_usersInfo[voter].allDelegatedBalance.nfts.values()); } - ownedLength = ERC721Upgradeable(nftAddress).balanceOf(voter); + ownedLength = IERC721Upgradeable(nftAddress_).balanceOf(voter); if (_nftInfo.totalSupply != 0) { nftsVector.push(new uint256[](ownedLength)); @@ -491,7 +579,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad return (nftsVector.toArray(), ownedLength); } - AbstractERC721Power nftContract = AbstractERC721Power(nftAddress); + IERC721Power nftContract = IERC721Power(nftAddress_); for (uint256 i; i < ownedLength; i++) { nftsVector.push(nftContract.tokenOfOwnerByIndex(voter, i)); @@ -500,6 +588,15 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad return (nftsVector.toArray(), ownedLength); } + function getTotalNftsPower( + uint256[] memory nftIds, + IGovPool.VoteType voteType, + address voter, + bool perNftPowerArray + ) public view override returns (uint256 nftPower, uint256[] memory perNftPower) { + return _usersInfo.getTotalNftsPower(_nftInfo, nftIds, voteType, voter, perNftPowerArray); + } + function getTotalPower() external view override returns (uint256 power) { address token = tokenAddress; @@ -507,7 +604,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad power = IERC20(token).totalSupply().to18(token.decimals()); } - token = nftAddress; + token = _nftInfo.nftAddress; if (token != address(0)) { if (!_nftInfo.isSupportPower) { @@ -540,30 +637,29 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } (uint256[] memory nftIds, uint256 owned) = nftExactBalance(voter, voteType); - (uint256[] memory nftIdsMicropool, ) = nftExactBalance( + + nftIds.crop(nftIds.length - owned); + + (uint256 personalNftPower, ) = getTotalNftsPower( + nftIds, + IGovPool.VoteType.PersonalVote, + address(0), + false + ); + (uint256 micropoolNftPower, ) = getTotalNftsPower( + new uint256[](0), + IGovPool.VoteType.MicropoolVote, voter, - IGovPool.VoteType.MicropoolVote + false ); - (uint256[] memory nftIdsTreasury, ) = nftExactBalance( + (uint256 treasuryNftPower, ) = getTotalNftsPower( + new uint256[](0), + IGovPool.VoteType.TreasuryVote, voter, - IGovPool.VoteType.TreasuryVote + false ); - nftIds.crop(nftIds.length - owned); - - uint256 nftPower; - uint256 tmpPower; - - (tmpPower, ) = nftVotingPower(nftIds, false); - nftPower += tmpPower; - - (tmpPower, ) = nftVotingPower(nftIdsMicropool, false); - nftPower += tmpPower; - - (tmpPower, ) = nftVotingPower(nftIdsTreasury, false); - nftPower += tmpPower; - - return tokens + nftPower >= requiredVotes; + return tokens + personalNftPower + micropoolNftPower + treasuryNftPower >= requiredVotes; } function votingPower( @@ -571,7 +667,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad IGovPool.VoteType[] calldata voteTypes, bool perNftPowerArray ) external view override returns (VotingPowerView[] memory votingPowers) { - return users.votingPower(voteTypes, perNftPowerArray); + return _usersInfo.votingPower(_nftInfo, tokenAddress, users, voteTypes, perNftPowerArray); } function transformedVotingPower( @@ -579,21 +675,14 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256 amount, uint256[] calldata nftIds ) external view override returns (uint256 personalPower, uint256 fullPower) { - return voter.transformedVotingPower(amount, nftIds); - } - - function nftVotingPower( - uint256[] memory nftIds, - bool perNftPowerArray - ) public view override returns (uint256 nftPower, uint256[] memory perNftPower) { - return nftIds.nftVotingPower(perNftPowerArray); + return _usersInfo.transformedVotingPower(_nftInfo, tokenAddress, voter, amount, nftIds); } function delegations( address user, bool perNftPowerArray ) external view override returns (uint256 power, DelegationInfoView[] memory delegationsInfo) { - return _usersInfo[user].delegations(perNftPowerArray); + return _usersInfo.delegations(_nftInfo, user, perNftPowerArray); } function getWithdrawableAssets( @@ -610,23 +699,26 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad lockedProposals.getWithdrawableAssets(unlockedNfts, _usersInfo[voter], _nftLockedNums); } - function getDelegatedAssets( + function getDelegatedAssetsPower( address delegator, address delegatee - ) external view override returns (uint256 tokenAmount, uint256[] memory nftIds) { + ) external view override returns (uint256 delegatedPower) { UserInfo storage delegatorInfo = _usersInfo[delegator]; + BalanceInfo storage delegatedBalance = delegatorInfo.delegatedBalances[delegatee]; - return ( - delegatorInfo.delegatedTokens[delegatee], - delegatorInfo.delegatedNfts[delegatee].values() - ); + return + delegatedBalance.tokens + + ( + _nftInfo.isSupportPower + ? delegatorInfo.delegatedNftPowers[delegatee] + : delegatedBalance.nfts.length() * _nftInfo.individualPower + ); } function _cleanDelegatee(UserInfo storage delegatorInfo, address delegatee) internal { - if ( - delegatorInfo.delegatedTokens[delegatee] == 0 && - delegatorInfo.delegatedNfts[delegatee].length() == 0 - ) { + BalanceInfo storage delegatedBalance = delegatorInfo.delegatedBalances[delegatee]; + + if (delegatedBalance.tokens == 0 && delegatedBalance.nfts.length() == 0) { delegatorInfo.delegatees.remove(delegatee); } } @@ -645,7 +737,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad uint256 individualPower, uint256 nftsTotalSupply ) internal { - require(nftAddress == address(0), "GovUK: current token address isn't zero"); + require(_nftInfo.nftAddress == address(0), "GovUK: current token address isn't zero"); require(_nftAddress != address(0), "GovUK: new token address is zero"); if (IERC165(_nftAddress).supportsInterface(type(IERC721Power).interfaceId)) { @@ -666,7 +758,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } } - nftAddress = _nftAddress; + _nftInfo.nftAddress = _nftAddress; emit SetERC721(_nftAddress); } @@ -675,15 +767,10 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address voter, IGovPool.VoteType voteType ) internal view returns (BalanceInfo storage) { - if (voteType == IGovPool.VoteType.MicropoolVote) { - return _micropoolsInfo[voter]; - } - - if (voteType == IGovPool.VoteType.TreasuryVote) { - return _treasuryPoolsInfo[voter]; - } - - return _usersInfo[voter].balanceInfo; + return + voteType == IGovPool.VoteType.DelegatedVote + ? _usersInfo[voter].balances[IGovPool.VoteType.PersonalVote] + : _usersInfo[voter].balances[voteType]; } function _withSupportedToken() internal view { @@ -691,6 +778,6 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } function _withSupportedNft() internal view { - require(nftAddress != address(0), "GovUK: nft is not supported"); + require(_nftInfo.nftAddress != address(0), "GovUK: nft is not supported"); } } diff --git a/contracts/interfaces/gov/ERC721/powers/IERC721Power.sol b/contracts/interfaces/gov/ERC721/powers/IERC721Power.sol index e4959f1d..93b7810c 100644 --- a/contracts/interfaces/gov/ERC721/powers/IERC721Power.sol +++ b/contracts/interfaces/gov/ERC721/powers/IERC721Power.sol @@ -24,10 +24,12 @@ interface IERC721Power is IERC721EnumerableUpgradeable { /// @notice The struct to get info about the NFT /// @param rawInfo the raw NFT info /// @param maxPower real max nft power + /// @param minPower real min nft power /// @param currentPower real nft power struct NftInfoView { NftInfo rawInfo; uint256 maxPower; + uint256 minPower; uint256 currentPower; } @@ -54,6 +56,11 @@ interface IERC721Power is IERC721EnumerableUpgradeable { /// @return max power for Nft function getNftMaxPower(uint256 tokenId) external view returns (uint256); + /// @notice Return min possible power (coefficient) for nft + /// @param tokenId Nft number + /// @return min power for Nft + function getNftMinPower(uint256 tokenId) external view returns (uint256); + /// @notice The function to get current NFT power /// @param tokenId the Nft number /// @return current power of the Nft diff --git a/contracts/interfaces/gov/IGovPool.sol b/contracts/interfaces/gov/IGovPool.sol index 88100446..a6ad119c 100644 --- a/contracts/interfaces/gov/IGovPool.sol +++ b/contracts/interfaces/gov/IGovPool.sol @@ -138,10 +138,12 @@ interface IGovPool { /// @notice The struct that holds information about the typed vote (only for internal needs) /// @param tokensVoted the total erc20 amount voted from one user for the proposal before the formula /// @param totalVoted the total power of typed votes from one user for the proposal before the formula + /// @param nftsAmount the amount of nfts participating in the vote /// @param nftsVoted the set of ids of nfts voted from one user for the proposal struct RawVote { uint256 tokensVoted; uint256 totalVoted; + uint256 nftsAmount; EnumerableSet.UintSet nftsVoted; } @@ -185,13 +187,11 @@ interface IGovPool { /// @notice The struct that holds information about the delegator (only for internal needs) /// @param delegationTimes the list of timestamps when delegated amount was changed - /// @param nftIds lists of delegated nfts in corresponding timestamps - /// @param tokenAmounts the list of delegated token amounts in corresponding timestamps + /// @param delegationPowers the list of delegated assets powers /// @param isClaimed matching proposals ids with flags indicating whether rewards have been claimed struct DelegatorInfo { uint256[] delegationTimes; - uint256[][] nftIds; - uint256[] tokenAmounts; + uint256[] delegationPowers; mapping(uint256 => bool) isClaimed; } diff --git a/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol b/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol index 1c587b9c..ece791c7 100644 --- a/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol +++ b/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol @@ -11,41 +11,45 @@ import "../../../interfaces/gov/IGovPool.sol"; */ interface IGovUserKeeper { /// @notice The struct holds information about user deposited tokens - /// @param tokenBalance the amount of deposited tokens - /// @param nftBalance the array of deposited nfts + /// @param tokens the amount of deposited tokens + /// @param nfts the array of deposited nfts struct BalanceInfo { - uint256 tokenBalance; - EnumerableSet.UintSet nftBalance; // array of NFTs + uint256 tokens; + EnumerableSet.UintSet nfts; } /// @notice The struct holds information about user balances - /// @param balanceInfo the BalanceInfo struct - /// @param delegatedTokens the mapping of delegated tokens (delegatee address => delegated amount) - /// @param allDelegatedTokens the total amount of delegated tokens - /// @param delegatedNfts the mapping of delegated nfts (delegatee address => array of delegated nft ids) - /// @param allDelegatedNfts the list of all delegated nfts + /// @param balances matching vote types with balance infos + /// @param nftsPowers matching vote types with cached nfts powers + /// @param delegatedBalances matching delegatees with balances infos + /// @param delegatedNftPowers matching delegatees with delegated nft powers + /// @param allDelegatedBalance the balance info of all delegated assets /// @param delegatees the array of delegatees /// @param maxTokensLocked the upper bound of currently locked tokens /// @param lockedInProposals the amount of deposited tokens locked in proposals struct UserInfo { - BalanceInfo balanceInfo; - mapping(address => uint256) delegatedTokens; - uint256 allDelegatedTokens; - mapping(address => EnumerableSet.UintSet) delegatedNfts; - EnumerableSet.UintSet allDelegatedNfts; + mapping(IGovPool.VoteType => BalanceInfo) balances; + mapping(IGovPool.VoteType => uint256) nftsPowers; + mapping(address => BalanceInfo) delegatedBalances; + mapping(address => uint256) delegatedNftPowers; + BalanceInfo allDelegatedBalance; EnumerableSet.AddressSet delegatees; uint256 maxTokensLocked; mapping(uint256 => uint256) lockedInProposals; } /// @notice The struct holds information about nft contract + /// @param nftAddress the address of the nft /// @param isSupportPower boolean flag, if true then nft contract supports power /// @param individualPower the voting power an nft /// @param totalSupply the total supply of nfts that are not enumerable + /// @param nftMinPower matching nft ids to their minimal powers struct NFTInfo { + address nftAddress; bool isSupportPower; uint256 individualPower; uint256 totalSupply; + mapping(uint256 => uint256) nftMinPower; } /// @notice The struct that is used in view functions of contract as a return argument @@ -217,9 +221,14 @@ interface IGovUserKeeper { /// @return `nftAddress` the erc721 address function nftAddress() external view returns (address); - /// @notice The function for getting information about nft contract - /// @return `NFTInfo` struct - function getNftInfo() external view returns (NFTInfo memory); + /// @notice The function for getting nft info + /// @return isSupportPower boolean flag, if true then nft contract supports power + /// @return individualPower the voting power an nft + /// @return totalSupply the total supply of nfts that are not enumerable + function getNftInfo() + external + view + returns (bool isSupportPower, uint256 individualPower, uint256 totalSupply); /// @notice The function for getting max locked amount of a user /// @param voter the address of voter @@ -256,9 +265,23 @@ interface IGovUserKeeper { IGovPool.VoteType voteType ) external view returns (uint256[] memory nfts, uint256 ownedLength); + /// @notice The function for getting total power of nfts by ids + /// @param nftIds the array of nft ids + /// @param voteType the type of vote + /// @param voter the address of user + /// @param perNftPowerArray should the nft raw powers array be returned + /// @return nftPower the total total power of nfts + /// @return perNftPower the array of nft powers, bounded with nftIds by index + function getTotalNftsPower( + uint256[] memory nftIds, + IGovPool.VoteType voteType, + address voter, + bool perNftPowerArray + ) external view returns (uint256 nftPower, uint256[] memory perNftPower); + /// @notice The function for getting total voting power of the contract - /// @return `total` power - function getTotalPower() external view returns (uint256); + /// @return power total power + function getTotalPower() external view returns (uint256 power); /// @notice The function to define if voter is able to create a proposal. Includes micropool balance /// @param voter the address of voter @@ -294,16 +317,6 @@ interface IGovUserKeeper { uint256[] calldata nftIds ) external view returns (uint256 personalPower, uint256 fullPower); - /// @notice The function for getting power of nfts by ids - /// @param nftIds the array of nft ids - /// @param perNftPowerArray should the nft powers array be calculated - /// @return nftPower the total power of nfts - /// @return perNftPower the array of nft powers, bounded with nftIds by index - function nftVotingPower( - uint256[] memory nftIds, - bool perNftPowerArray - ) external view returns (uint256 nftPower, uint256[] memory perNftPower); - /// @notice The function for getting information about user's delegations /// @param user the address of user /// @param perNftPowerArray should the nft powers array be calculated @@ -326,13 +339,12 @@ interface IGovUserKeeper { uint256[] calldata unlockedNfts ) external view returns (uint256 withdrawableTokens, uint256[] memory withdrawableNfts); - /// @notice The function for getting the total delegated amount by the delegator and the delegatee + /// @notice The function for getting the total delegated power by the delegator and the delegatee /// @param delegator the address of the delegator /// @param delegatee the address of the delegatee - /// @return tokenAmount the amount of delegated tokens - /// @return nftIds the list of delegated nft ids - function getDelegatedAssets( + /// @return delegatedPower the total delegated power + function getDelegatedAssetsPower( address delegator, address delegatee - ) external view returns (uint256 tokenAmount, uint256[] memory nftIds); + ) external view returns (uint256 delegatedPower); } diff --git a/contracts/libs/gov/gov-pool/GovPoolCreate.sol b/contracts/libs/gov/gov-pool/GovPoolCreate.sol index e167022e..e9b264b7 100644 --- a/contracts/libs/gov/gov-pool/GovPoolCreate.sol +++ b/contracts/libs/gov/gov-pool/GovPoolCreate.sol @@ -17,7 +17,6 @@ import "../../../interfaces/gov/ERC721/experts/IERC721Expert.sol"; import "../../utils/DataHelper.sol"; -import "../../../gov/user-keeper/GovUserKeeper.sol"; import "../../../gov/GovPool.sol"; import "../../../libs/utils/TypeHelper.sol"; @@ -411,8 +410,8 @@ library GovPoolCreate { IGovPool.ProposalAction calldata actionAgainst, address metaUserKeeper ) internal view { - address metaToken = GovUserKeeper(metaUserKeeper).tokenAddress(); - address metaNft = GovUserKeeper(metaUserKeeper).nftAddress(); + address metaToken = IGovUserKeeper(metaUserKeeper).tokenAddress(); + address metaNft = IGovUserKeeper(metaUserKeeper).nftAddress(); require( actionFor.executor == metaToken || actionFor.executor == metaNft, @@ -434,7 +433,7 @@ library GovPoolCreate { IGovPool.ProposalAction calldata actionAgainst, address metaUserKeeper ) internal view { - address metaNft = GovUserKeeper(metaUserKeeper).nftAddress(); + address metaNft = IGovUserKeeper(metaUserKeeper).nftAddress(); require(actionFor.executor == metaNft, "Gov: invalid executor"); diff --git a/contracts/libs/gov/gov-pool/GovPoolMicropool.sol b/contracts/libs/gov/gov-pool/GovPoolMicropool.sol index 49021dcf..ae88dcd3 100644 --- a/contracts/libs/gov/gov-pool/GovPoolMicropool.sol +++ b/contracts/libs/gov/gov-pool/GovPoolMicropool.sol @@ -34,28 +34,24 @@ library GovPoolMicropool { ) external { (, address userKeeper, , , ) = IGovPool(address(this)).getHelperContracts(); - (uint256 currentTokenAmount, uint256[] memory currentNftIds) = IGovUserKeeper(userKeeper) - .getDelegatedAssets(msg.sender, delegatee); - IGovPool.DelegatorInfo storage delegatorInfo = userInfos[delegatee].delegatorInfos[ msg.sender ]; uint256[] storage delegationTimes = delegatorInfo.delegationTimes; - uint256[] storage tokenAmounts = delegatorInfo.tokenAmounts; - uint256[][] storage nftIds = delegatorInfo.nftIds; + uint256[] storage delegationPowers = delegatorInfo.delegationPowers; uint256 length = delegationTimes.length; if (length > 0 && delegationTimes[length - 1] == block.timestamp) { delegationTimes.pop(); - tokenAmounts.pop(); - nftIds.pop(); + delegationPowers.pop(); } delegationTimes.push(block.timestamp); - tokenAmounts.push(currentTokenAmount); - nftIds.push(currentNftIds); + delegationPowers.push( + IGovUserKeeper(userKeeper).getDelegatedAssetsPower(msg.sender, delegatee) + ); } function claim( @@ -127,8 +123,6 @@ library GovPoolMicropool { address delegator, address delegatee ) private view returns (uint256) { - (, address userKeeper, , , ) = IGovPool(address(this)).getHelperContracts(); - IGovPool.ProposalCore storage core = proposals[proposalId].core; IGovPool.UserInfo storage userInfo = userInfos[delegatee]; IGovPool.RawVote storage micropoolRawVote = userInfo.voteInfos[proposalId].rawVotes[ @@ -155,15 +149,11 @@ library GovPoolMicropool { --index; } - (uint256 nftPower, ) = IGovUserKeeper(userKeeper).nftVotingPower( - delegatorInfo.nftIds[index], - false - ); + uint256 totalVoted = micropoolRawVote.totalVoted; return - delegatorsRewards.ratio( - delegatorInfo.tokenAmounts[index] + nftPower, - micropoolRawVote.totalVoted + delegatorsRewards.ratio(delegatorInfo.delegationPowers[index], totalVoted).min( + totalVoted ); } } diff --git a/contracts/libs/gov/gov-pool/GovPoolVote.sol b/contracts/libs/gov/gov-pool/GovPoolVote.sol index 8537fdeb..9b04bc2a 100644 --- a/contracts/libs/gov/gov-pool/GovPoolVote.sol +++ b/contracts/libs/gov/gov-pool/GovPoolVote.sol @@ -58,7 +58,7 @@ library GovPoolVote { userKeeper.lockNfts(msg.sender, voteType, nftIds); } - _vote(rawVotes[IGovPool.VoteType.PersonalVote], amount, nftIds); + _vote(rawVotes[IGovPool.VoteType.PersonalVote], amount, nftIds, address(0), voteType); } if (voteType != IGovPool.VoteType.DelegatedVote) { @@ -167,43 +167,48 @@ library GovPoolVote { return; } - (, address userKeeperAddress, , , ) = IGovPool(address(this)).getHelperContracts(); - IGovUserKeeper userKeeper = IGovUserKeeper(userKeeperAddress); + (, address userKeeper, , , ) = IGovPool(address(this)).getHelperContracts(); - (uint256 amount, ) = userKeeper.tokenBalance(voter, voteType); - (uint256[] memory nftIds, ) = userKeeper.nftExactBalance(voter, voteType); + (uint256 amount, ) = IGovUserKeeper(userKeeper).tokenBalance(voter, voteType); - _vote(rawVote, amount, nftIds); + _vote(rawVote, amount, new uint256[](0), voter, voteType); } function _vote( IGovPool.RawVote storage rawVote, uint256 amount, - uint256[] memory nftIds + uint256[] memory nftIds, + address voter, + IGovPool.VoteType voteType ) internal { - rawVote.tokensVoted = amount; - - if (nftIds.length == 0) { - rawVote.totalVoted = amount; - return; - } - EnumerableSet.UintSet storage nftsVoted = rawVote.nftsVoted; for (uint256 i; i < nftIds.length; i++) { require(nftsVoted.add(nftIds[i]), "Gov: NFT already voted"); } - (, address userKeeper, , , ) = IGovPool(address(this)).getHelperContracts(); + (, address userKeeperAddress, , , ) = IGovPool(address(this)).getHelperContracts(); + IGovUserKeeper userKeeper = IGovUserKeeper(userKeeperAddress); - (uint256 nftPower, ) = IGovUserKeeper(userKeeper).nftVotingPower(nftIds, false); + (uint256 nftsPower, ) = userKeeper.getTotalNftsPower(nftIds, voteType, voter, false); + + rawVote.tokensVoted = amount; + rawVote.totalVoted = amount + nftsPower; - rawVote.totalVoted = amount + nftPower; + if ( + voteType == IGovPool.VoteType.PersonalVote || + voteType == IGovPool.VoteType.DelegatedVote + ) { + rawVote.nftsAmount = nftIds.length; + } else { + (rawVote.nftsAmount, ) = userKeeper.nftBalance(voter, voteType); + } } function _cancel(IGovPool.RawVote storage rawVote) internal { rawVote.tokensVoted = 0; rawVote.totalVoted = 0; + rawVote.nftsAmount = 0; EnumerableSet.UintSet storage nftsVoted = rawVote.nftsVoted; @@ -351,9 +356,9 @@ library GovPoolVote { personalRawVote.totalVoted != 0 || micropoolRawVote.totalVoted != 0 || treasuryRawVote.totalVoted != 0 || - personalRawVote.nftsVoted.length() != 0 || - micropoolRawVote.nftsVoted.length() != 0 || - treasuryRawVote.nftsVoted.length() != 0; + personalRawVote.nftsAmount != 0 || + micropoolRawVote.nftsAmount != 0 || + treasuryRawVote.nftsAmount != 0; } function _quorumReached(IGovPool.ProposalCore storage core) internal view returns (bool) { diff --git a/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol b/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol index e2339522..1cc1fa7c 100644 --- a/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol +++ b/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol @@ -9,11 +9,11 @@ import "@solarity/solidity-lib/libs/utils/TypeCaster.sol"; import "../../../interfaces/gov/user-keeper/IGovUserKeeper.sol"; import "../../../interfaces/gov/voting/IVotePower.sol"; +import "../../../interfaces/gov/ERC721/powers/IERC721Power.sol"; import "../../math/MathHelper.sol"; import "../../utils/TypeHelper.sol"; -import "../../../gov/ERC721/powers/AbstractERC721Power.sol"; import "../../../gov/user-keeper/GovUserKeeper.sol"; library GovUserKeeperView { @@ -25,7 +25,46 @@ library GovUserKeeperView { using TypeCaster for *; using TypeHelper for *; + function transformedVotingPower( + mapping(address => IGovUserKeeper.UserInfo) storage usersInfo, + IGovUserKeeper.NFTInfo storage nftInfo, + address tokenAddress, + address voter, + uint256 amount, + uint256[] calldata nftIds + ) external view returns (uint256 personalPower, uint256 fullPower) { + IGovUserKeeper.VotingPowerView[] memory votingPowers = votingPower( + usersInfo, + nftInfo, + tokenAddress, + [voter, voter].asDynamic(), + [IGovPool.VoteType.MicropoolVote, IGovPool.VoteType.TreasuryVote].asDynamic(), + false + ); + + (uint256 nftPower, ) = getTotalNftsPower( + usersInfo, + nftInfo, + nftIds, + IGovPool.VoteType.PersonalVote, + voter, + false + ); + + (, , , , address votePower) = IGovPool(GovUserKeeper(address(this)).owner()) + .getHelperContracts(); + + personalPower = amount + nftPower; + fullPower = personalPower + votingPowers[0].rawPower + votingPowers[1].rawPower; + + personalPower = IVotePower(votePower).transformVotes(voter, personalPower); + fullPower = IVotePower(votePower).transformVotes(voter, fullPower); + } + function votingPower( + mapping(address => IGovUserKeeper.UserInfo) storage usersInfo, + IGovUserKeeper.NFTInfo storage nftInfo, + address tokenAddress, address[] memory users, IGovPool.VoteType[] memory voteTypes, bool perNftPowerArray @@ -33,13 +72,10 @@ library GovUserKeeperView { GovUserKeeper userKeeper = GovUserKeeper(address(this)); votingPowers = new IGovUserKeeper.VotingPowerView[](users.length); - bool tokenAddressExists = userKeeper.tokenAddress() != address(0); - bool nftAddressExists = userKeeper.nftAddress() != address(0); - for (uint256 i = 0; i < users.length; i++) { IGovUserKeeper.VotingPowerView memory power = votingPowers[i]; - if (tokenAddressExists) { + if (tokenAddress != address(0)) { (power.power, power.ownedBalance) = userKeeper.tokenBalance( users[i], voteTypes[i] @@ -48,21 +84,44 @@ library GovUserKeeperView { power.rawPower = power.power - power.ownedBalance; } - if (nftAddressExists) { - (uint256[] memory nftIds, uint256 length) = userKeeper.nftExactBalance( + if (nftInfo.nftAddress != address(0)) { + uint256[] memory nftIds; + uint256 length; + + if (!perNftPowerArray) { + (, length) = userKeeper.nftBalance(users[i], voteTypes[i]); + } else { + (nftIds, length) = userKeeper.nftExactBalance(users[i], voteTypes[i]); + } + + (power.nftPower, power.perNftPower) = getTotalNftsPower( + usersInfo, + nftInfo, + nftIds, + voteTypes[i], users[i], - voteTypes[i] + perNftPowerArray ); - (power.nftPower, power.perNftPower) = nftVotingPower(nftIds, perNftPowerArray); - assembly { - mstore(nftIds, sub(mload(nftIds), length)) + if (perNftPowerArray) { + assembly { + mstore(nftIds, sub(mload(nftIds), length)) + } } - (power.rawNftPower, ) = nftVotingPower(nftIds, perNftPowerArray); + (power.rawNftPower, ) = getTotalNftsPower( + usersInfo, + nftInfo, + nftIds, + voteTypes[i], + users[i], + perNftPowerArray + ); - assembly { - mstore(nftIds, add(mload(nftIds), length)) + if (perNftPowerArray) { + assembly { + mstore(nftIds, add(mload(nftIds), length)) + } } power.nftIds = nftIds; @@ -74,91 +133,104 @@ library GovUserKeeperView { } } - function transformedVotingPower( - address voter, - uint256 amount, - uint256[] calldata nftIds - ) external view returns (uint256 personalPower, uint256 fullPower) { - address govPool = GovUserKeeper(address(this)).owner(); - - (, , , , address votePowerAddress) = IGovPool(govPool).getHelperContracts(); - IVotePower votePower = IVotePower(votePowerAddress); - - (uint256 nftPower, ) = nftVotingPower(nftIds, false); - - IGovUserKeeper.VotingPowerView[] memory votingPowers = votingPower( - [voter, voter].asDynamic(), - [IGovPool.VoteType.MicropoolVote, IGovPool.VoteType.TreasuryVote].asDynamic(), - false - ); - - personalPower = amount + nftPower; - fullPower = personalPower + votingPowers[0].rawPower + votingPowers[1].rawPower; - - personalPower = votePower.transformVotes(voter, personalPower); - fullPower = votePower.transformVotes(voter, fullPower); - } - - function nftVotingPower( + function getTotalNftsPower( + mapping(address => IGovUserKeeper.UserInfo) storage usersInfo, + IGovUserKeeper.NFTInfo storage nftInfo, uint256[] memory nftIds, + IGovPool.VoteType voteType, + address user, bool perNftPowerArray ) public view returns (uint256 nftPower, uint256[] memory perNftPower) { - GovUserKeeper userKeeper = GovUserKeeper(address(this)); - AbstractERC721Power nftContract = AbstractERC721Power(userKeeper.nftAddress()); - - if (address(nftContract) == address(0)) { + if (nftInfo.nftAddress == address(0)) { return (nftPower, perNftPower); } - IGovUserKeeper.NFTInfo memory nftInfo = userKeeper.getNftInfo(); + if (user != address(0)) { + if ( + voteType == IGovPool.VoteType.MicropoolVote || + voteType == IGovPool.VoteType.TreasuryVote + ) { + if (perNftPowerArray) { + perNftPower = new uint256[](nftIds.length); - if (perNftPowerArray) { - perNftPower = new uint256[](nftIds.length); - } + if (!nftInfo.isSupportPower) { + for (uint256 i = 0; i < perNftPower.length; i++) { + perNftPower[i] = nftInfo.individualPower; + } - if (!nftInfo.isSupportPower) { - uint256 individualPower = nftInfo.individualPower; - - nftPower = nftIds.length * nftInfo.individualPower; + return (nftIds.length * nftInfo.individualPower, perNftPower); + } - if (perNftPowerArray) { - for (uint256 i; i < nftIds.length; i++) { - perNftPower[i] = individualPower; - } - } - } else { - for (uint256 i; i < nftIds.length; i++) { - uint256 currentNftPower = nftContract.getNftPower(nftIds[i]); + for (uint256 i = 0; i < nftIds.length; ++i) { + uint256 currentNftPower = nftInfo.nftMinPower[nftIds[i]]; - nftPower += currentNftPower; + nftPower += currentNftPower; + perNftPower[i] = currentNftPower; + } - if (perNftPowerArray) { - perNftPower[i] = currentNftPower; + return (nftPower, perNftPower); + } else { + return + nftInfo.isSupportPower + ? (usersInfo[user].nftsPowers[voteType], perNftPower) + : ( + usersInfo[user].balances[voteType].nfts.length() * + nftInfo.individualPower, + perNftPower + ); } + } else { + return + _getActualNftPowers( + IERC721Power(nftInfo.nftAddress).getNftPower, + nftInfo, + nftIds, + perNftPowerArray + ); } + } else { + return + _getActualNftPowers( + IERC721Power(nftInfo.nftAddress).getNftMinPower, + nftInfo, + nftIds, + perNftPowerArray + ); } } function delegations( - IGovUserKeeper.UserInfo storage userInfo, + mapping(address => IGovUserKeeper.UserInfo) storage usersInfo, + IGovUserKeeper.NFTInfo storage nftInfo, + address user, bool perNftPowerArray ) external view returns (uint256 power, IGovUserKeeper.DelegationInfoView[] memory delegationsInfo) { + IGovUserKeeper.UserInfo storage userInfo = usersInfo[user]; + delegationsInfo = new IGovUserKeeper.DelegationInfoView[](userInfo.delegatees.length()); for (uint256 i; i < delegationsInfo.length; i++) { IGovUserKeeper.DelegationInfoView memory delegation = delegationsInfo[i]; address delegatee = userInfo.delegatees.at(i); + IGovUserKeeper.BalanceInfo storage delegatedBalance = userInfo.delegatedBalances[ + delegatee + ]; + delegation.delegatee = delegatee; - delegation.delegatedTokens = userInfo.delegatedTokens[delegatee]; - delegation.delegatedNfts = userInfo.delegatedNfts[delegatee].values(); + delegation.delegatedTokens = delegatedBalance.tokens; + delegation.delegatedNfts = delegatedBalance.nfts.values(); - (delegation.nftPower, delegation.perNftPower) = nftVotingPower( + (delegation.nftPower, delegation.perNftPower) = getTotalNftsPower( + usersInfo, + nftInfo, delegation.delegatedNfts, + IGovPool.VoteType.MicropoolVote, + delegatee, perNftPowerArray ); @@ -172,7 +244,9 @@ library GovUserKeeperView { IGovUserKeeper.UserInfo storage userInfo, mapping(uint256 => uint256) storage nftLockedNums ) external view returns (uint256 withdrawableTokens, uint256[] memory withdrawableNfts) { - IGovUserKeeper.BalanceInfo storage balanceInfo = userInfo.balanceInfo; + IGovUserKeeper.BalanceInfo storage balanceInfo = userInfo.balances[ + IGovPool.VoteType.PersonalVote + ]; uint256 newLockedAmount; @@ -180,13 +254,13 @@ library GovUserKeeperView { newLockedAmount = newLockedAmount.max(userInfo.lockedInProposals[lockedProposals[i]]); } - withdrawableTokens = balanceInfo.tokenBalance.max(newLockedAmount) - newLockedAmount; + withdrawableTokens = balanceInfo.tokens.max(newLockedAmount) - newLockedAmount; Vector.UintVector memory nfts = Vector.newUint(); - uint256 nftsLength = balanceInfo.nftBalance.length(); + uint256 nftsLength = balanceInfo.nfts.length(); for (uint256 i; i < nftsLength; i++) { - uint256 nftId = balanceInfo.nftBalance.at(i); + uint256 nftId = balanceInfo.nfts.at(i); uint256 nftLockAmount = nftLockedNums[nftId]; if (nftLockAmount != 0) { @@ -204,4 +278,35 @@ library GovUserKeeperView { withdrawableNfts = nfts.toArray(); } + + function _getActualNftPowers( + function(uint256) external view returns (uint256) powerFunc, + IGovUserKeeper.NFTInfo storage nftInfo, + uint256[] memory nftIds, + bool perNftPowerArray + ) internal view returns (uint256 nftPower, uint256[] memory perNftPower) { + if (!nftInfo.isSupportPower) { + nftPower = nftIds.length * nftInfo.individualPower; + } else { + for (uint256 i = 0; i < nftIds.length; ++i) { + nftPower += powerFunc(nftIds[i]); + } + } + + if (perNftPowerArray) { + perNftPower = new uint256[](nftIds.length); + + if (!nftInfo.isSupportPower) { + for (uint256 i = 0; i < perNftPower.length; i++) { + perNftPower[i] = nftInfo.individualPower; + } + } else { + for (uint256 i = 0; i < nftIds.length; ++i) { + uint256 currentNftPower = powerFunc(nftIds[i]); + + perNftPower[i] = currentNftPower; + } + } + } + } } diff --git a/contracts/mock/spherex/SphereXEngineMock.sol b/contracts/mock/spherex/SphereXEngineMock.sol index 8a4f074c..97a32394 100644 --- a/contracts/mock/spherex/SphereXEngineMock.sol +++ b/contracts/mock/spherex/SphereXEngineMock.sol @@ -11,44 +11,48 @@ contract SphereXEngineMock is ISphereXEngine { } function sphereXValidatePre( - int256 num, - address sender, - bytes calldata data - ) external override returns (bytes32[] memory returnData) { + int256, + address, + bytes calldata + ) external view override returns (bytes32[] memory returnData) { _revert(); + + return returnData; } function sphereXValidatePost( - int256 num, - uint256 gas, - bytes32[] calldata valuesBefore, - bytes32[] calldata valuesAfter - ) external override { + int256, + uint256, + bytes32[] calldata, + bytes32[] calldata + ) external view override { _revert(); } function sphereXValidateInternalPre( - int256 num - ) external returns (bytes32[] memory returnData) { + int256 + ) external view returns (bytes32[] memory returnData) { _revert(); + + return returnData; } function sphereXValidateInternalPost( - int256 num, - uint256 gas, - bytes32[] calldata valuesBefore, - bytes32[] calldata valuesAfter - ) external { + int256, + uint256, + bytes32[] calldata, + bytes32[] calldata + ) external view { _revert(); } function addAllowedSenderOnChain(address sender) external override {} - function supportsInterface(bytes4 interfaceId) external view returns (bool) { + function supportsInterface(bytes4) external pure returns (bool) { return true; } - function _revert() internal { + function _revert() internal view { require(!shouldRevert, "SphereXEngineMock: malicious tx"); } } diff --git a/docs/gov/ERC721/powers/IERC721Power.md b/docs/gov/ERC721/powers/IERC721Power.md index 68093515..787a89b8 100644 --- a/docs/gov/ERC721/powers/IERC721Power.md +++ b/docs/gov/ERC721/powers/IERC721Power.md @@ -45,6 +45,7 @@ Parameters: struct NftInfoView { IERC721Power.NftInfo rawInfo; uint256 maxPower; + uint256 minPower; uint256 currentPower; } ``` @@ -58,6 +59,7 @@ Parameters: | :----------- | :-------------------------- | :------------------ | | rawInfo | struct IERC721Power.NftInfo | the raw NFT info | | maxPower | uint256 | real max nft power | +| minPower | uint256 | real min nft power | | currentPower | uint256 | real nft power | ## Functions info @@ -146,6 +148,28 @@ Return values: | :--- | :------ | :---------------- | | [0] | uint256 | max power for Nft | +### getNftMinPower (0x7c24b33a) + +```solidity +function getNftMinPower(uint256 tokenId) external view returns (uint256) +``` + +Return min possible power (coefficient) for nft + + +Parameters: + +| Name | Type | Description | +| :------ | :------ | :---------- | +| tokenId | uint256 | Nft number | + + +Return values: + +| Name | Type | Description | +| :--- | :------ | :---------------- | +| [0] | uint256 | min power for Nft | + ### getNftPower (0x412e8a29) ```solidity diff --git a/docs/gov/IGovPool.md b/docs/gov/IGovPool.md index c33f399b..f2418a41 100644 --- a/docs/gov/IGovPool.md +++ b/docs/gov/IGovPool.md @@ -233,6 +233,7 @@ Parameters: struct RawVote { uint256 tokensVoted; uint256 totalVoted; + uint256 nftsAmount; EnumerableSet.UintSet nftsVoted; } ``` @@ -246,6 +247,7 @@ Parameters: | :---------- | :--------------------------- | :-------------------------------------------------------------------------------- | | tokensVoted | uint256 | the total erc20 amount voted from one user for the proposal before the formula | | totalVoted | uint256 | the total power of typed votes from one user for the proposal before the formula | +| nftsAmount | uint256 | the amount of nfts participating in the vote | | nftsVoted | struct EnumerableSet.UintSet | the set of ids of nfts voted from one user for the proposal | ### VoteInfo @@ -324,8 +326,7 @@ Parameters: ```solidity struct DelegatorInfo { uint256[] delegationTimes; - uint256[][] nftIds; - uint256[] tokenAmounts; + uint256[] delegationPowers; mapping(uint256 => bool) isClaimed; } ``` @@ -335,12 +336,11 @@ The struct that holds information about the delegator (only for internal needs) Parameters: -| Name | Type | Description | -| :-------------- | :----------------------- | :----------------------------------------------------------------------------- | -| delegationTimes | uint256[] | the list of timestamps when delegated amount was changed | -| nftIds | uint256[][] | lists of delegated nfts in corresponding timestamps | -| tokenAmounts | uint256[] | the list of delegated token amounts in corresponding timestamps | -| isClaimed | mapping(uint256 => bool) | matching proposals ids with flags indicating whether rewards have been claimed | +| Name | Type | Description | +| :--------------- | :----------------------- | :----------------------------------------------------------------------------- | +| delegationTimes | uint256[] | the list of timestamps when delegated amount was changed | +| delegationPowers | uint256[] | the list of delegated assets powers | +| isClaimed | mapping(uint256 => bool) | matching proposals ids with flags indicating whether rewards have been claimed | ### PendingRewards diff --git a/docs/gov/user-keeper/IGovUserKeeper.md b/docs/gov/user-keeper/IGovUserKeeper.md index 3372b1ae..268273b1 100644 --- a/docs/gov/user-keeper/IGovUserKeeper.md +++ b/docs/gov/user-keeper/IGovUserKeeper.md @@ -19,8 +19,8 @@ ERC20 tokens or NFTs ```solidity struct BalanceInfo { - uint256 tokenBalance; - EnumerableSet.UintSet nftBalance; + uint256 tokens; + EnumerableSet.UintSet nfts; } ``` @@ -29,20 +29,20 @@ The struct holds information about user deposited tokens Parameters: -| Name | Type | Description | -| :----------- | :--------------------------- | :------------------------------ | -| tokenBalance | uint256 | the amount of deposited tokens | -| nftBalance | struct EnumerableSet.UintSet | the array of deposited nfts | +| Name | Type | Description | +| :----- | :--------------------------- | :------------------------------ | +| tokens | uint256 | the amount of deposited tokens | +| nfts | struct EnumerableSet.UintSet | the array of deposited nfts | ### UserInfo ```solidity struct UserInfo { - IGovUserKeeper.BalanceInfo balanceInfo; - mapping(address => uint256) delegatedTokens; - uint256 allDelegatedTokens; - mapping(address => EnumerableSet.UintSet) delegatedNfts; - EnumerableSet.UintSet allDelegatedNfts; + mapping(IGovPool.VoteType => struct IGovUserKeeper.BalanceInfo) balances; + mapping(IGovPool.VoteType => uint256) nftsPowers; + mapping(address => IGovUserKeeper.BalanceInfo) delegatedBalances; + mapping(address => uint256) delegatedNftPowers; + IGovUserKeeper.BalanceInfo allDelegatedBalance; EnumerableSet.AddressSet delegatees; uint256 maxTokensLocked; mapping(uint256 => uint256) lockedInProposals; @@ -54,24 +54,26 @@ The struct holds information about user balances Parameters: -| Name | Type | Description | -| :----------------- | :----------------------------------------------- | :------------------------------------------------------------------------------- | -| balanceInfo | struct IGovUserKeeper.BalanceInfo | the BalanceInfo struct | -| delegatedTokens | mapping(address => uint256) | the mapping of delegated tokens (delegatee address => delegated amount) | -| allDelegatedTokens | uint256 | the total amount of delegated tokens | -| delegatedNfts | mapping(address => struct EnumerableSet.UintSet) | the mapping of delegated nfts (delegatee address => array of delegated nft ids) | -| allDelegatedNfts | struct EnumerableSet.UintSet | the list of all delegated nfts | -| delegatees | struct EnumerableSet.AddressSet | the array of delegatees | -| maxTokensLocked | uint256 | the upper bound of currently locked tokens | -| lockedInProposals | mapping(uint256 => uint256) | the amount of deposited tokens locked in proposals | +| Name | Type | Description | +| :------------------ | :------------------------------------------------------------------- | :------------------------------------------------- | +| balances | mapping(enum IGovPool.VoteType => struct IGovUserKeeper.BalanceInfo) | matching vote types with balance infos | +| nftsPowers | mapping(enum IGovPool.VoteType => uint256) | matching vote types with cached nfts powers | +| delegatedBalances | mapping(address => struct IGovUserKeeper.BalanceInfo) | matching delegatees with balances infos | +| delegatedNftPowers | mapping(address => uint256) | matching delegatees with delegated nft powers | +| allDelegatedBalance | struct IGovUserKeeper.BalanceInfo | the balance info of all delegated assets | +| delegatees | struct EnumerableSet.AddressSet | the array of delegatees | +| maxTokensLocked | uint256 | the upper bound of currently locked tokens | +| lockedInProposals | mapping(uint256 => uint256) | the amount of deposited tokens locked in proposals | ### NFTInfo ```solidity struct NFTInfo { + address nftAddress; bool isSupportPower; uint256 individualPower; uint256 totalSupply; + mapping(uint256 => uint256) nftMinPower; } ``` @@ -80,11 +82,13 @@ The struct holds information about nft contract Parameters: -| Name | Type | Description | -| :-------------- | :------ | :------------------------------------------------------ | -| isSupportPower | bool | boolean flag, if true then nft contract supports power | -| individualPower | uint256 | the voting power an nft | -| totalSupply | uint256 | the total supply of nfts that are not enumerable | +| Name | Type | Description | +| :-------------- | :-------------------------- | :------------------------------------------------------ | +| nftAddress | address | the address of the nft | +| isSupportPower | bool | boolean flag, if true then nft contract supports power | +| individualPower | uint256 | the voting power an nft | +| totalSupply | uint256 | the total supply of nfts that are not enumerable | +| nftMinPower | mapping(uint256 => uint256) | matching nft ids to their minimal powers | ### VotingPowerView @@ -554,17 +558,22 @@ Return values: ### getNftInfo (0x7ca5685f) ```solidity -function getNftInfo() external view returns (IGovUserKeeper.NFTInfo memory) +function getNftInfo() + external + view + returns (bool isSupportPower, uint256 individualPower, uint256 totalSupply) ``` -The function for getting information about nft contract +The function for getting nft info Return values: -| Name | Type | Description | -| :--- | :---------------------------- | :--------------- | -| [0] | struct IGovUserKeeper.NFTInfo | `NFTInfo` struct | +| Name | Type | Description | +| :-------------- | :------ | :------------------------------------------------------ | +| isSupportPower | bool | boolean flag, if true then nft contract supports power | +| individualPower | uint256 | the voting power an nft | +| totalSupply | uint256 | the total supply of nfts that are not enumerable | ### maxLockedAmount (0x3b3707a3) @@ -669,10 +678,41 @@ Return values: | nfts | uint256[] | the array of owned nft ids | | ownedLength | uint256 | the number of nfts that are not deposited to the contract | +### getTotalNftsPower (0x4a5f293c) + +```solidity +function getTotalNftsPower( + uint256[] memory nftIds, + IGovPool.VoteType voteType, + address voter, + bool perNftPowerArray +) external view returns (uint256 nftPower, uint256[] memory perNftPower) +``` + +The function for getting total power of nfts by ids + + +Parameters: + +| Name | Type | Description | +| :--------------- | :--------------------- | :------------------------------------------- | +| nftIds | uint256[] | the array of nft ids | +| voteType | enum IGovPool.VoteType | the type of vote | +| voter | address | the address of user | +| perNftPowerArray | bool | should the nft raw powers array be returned | + + +Return values: + +| Name | Type | Description | +| :---------- | :-------- | :---------------------------------------------------- | +| nftPower | uint256 | the total total power of nfts | +| perNftPower | uint256[] | the array of nft powers, bounded with nftIds by index | + ### getTotalPower (0x53976a26) ```solidity -function getTotalPower() external view returns (uint256) +function getTotalPower() external view returns (uint256 power) ``` The function for getting total voting power of the contract @@ -680,9 +720,9 @@ The function for getting total voting power of the contract Return values: -| Name | Type | Description | -| :--- | :------ | :------------ | -| [0] | uint256 | `total` power | +| Name | Type | Description | +| :---- | :------ | :---------- | +| power | uint256 | total power | ### canCreate (0x6f123e76) @@ -769,33 +809,6 @@ Return values: | personalPower | uint256 | the personal voting power after the formula | | fullPower | uint256 | the personal plus delegated voting power after the formula | -### nftVotingPower (0xabe04d74) - -```solidity -function nftVotingPower( - uint256[] memory nftIds, - bool perNftPowerArray -) external view returns (uint256 nftPower, uint256[] memory perNftPower) -``` - -The function for getting power of nfts by ids - - -Parameters: - -| Name | Type | Description | -| :--------------- | :-------- | :----------------------------------------- | -| nftIds | uint256[] | the array of nft ids | -| perNftPowerArray | bool | should the nft powers array be calculated | - - -Return values: - -| Name | Type | Description | -| :---------- | :-------- | :---------------------------------------------------- | -| nftPower | uint256 | the total power of nfts | -| perNftPower | uint256[] | the array of nft powers, bounded with nftIds by index | - ### delegations (0x4d123d7e) ```solidity @@ -861,16 +874,16 @@ Return values: | withdrawableTokens | uint256 | the tokens that can we withdrawn | | withdrawableNfts | uint256[] | the array of nfts that can we withdrawn | -### getDelegatedAssets (0x4d735416) +### getDelegatedAssetsPower (0x8a3ca923) ```solidity -function getDelegatedAssets( +function getDelegatedAssetsPower( address delegator, address delegatee -) external view returns (uint256 tokenAmount, uint256[] memory nftIds) +) external view returns (uint256 delegatedPower) ``` -The function for getting the total delegated amount by the delegator and the delegatee +The function for getting the total delegated power by the delegator and the delegatee Parameters: @@ -883,7 +896,6 @@ Parameters: Return values: -| Name | Type | Description | -| :---------- | :-------- | :------------------------------ | -| tokenAmount | uint256 | the amount of delegated tokens | -| nftIds | uint256[] | the list of delegated nft ids | +| Name | Type | Description | +| :------------- | :------ | :------------------------ | +| delegatedPower | uint256 | the total delegated power | diff --git a/test/gov/ERC721/ERC721EquivalentPower.test.js b/test/gov/ERC721/ERC721EquivalentPower.test.js index 7617ad81..f40c3837 100644 --- a/test/gov/ERC721/ERC721EquivalentPower.test.js +++ b/test/gov/ERC721/ERC721EquivalentPower.test.js @@ -80,6 +80,7 @@ describe("ERC721EquivalentPower", () => { await nft.recalculateNftPowers([1]); assert.equal(toBN(await nft.totalPower()).toFixed(), wei("100")); + assert.equal(toBN(await nft.getNftMinPower(1)).toFixed(), wei("100")); assert.equal(toBN(await nft.getNftMaxPower(1)).toFixed(), wei("100")); assert.equal(toBN(await nft.getNftPower(1)).toFixed(), wei("100")); @@ -90,6 +91,7 @@ describe("ERC721EquivalentPower", () => { await nft.recalculateNftPowers([1]); assert.equal(toBN(await nft.totalPower()).toFixed(), wei("100")); + assert.equal(toBN(await nft.getNftMinPower(1)).toFixed(), "0"); assert.equal(toBN(await nft.getNftMaxPower(1)).toFixed(), "0"); assert.equal(toBN(await nft.getNftPower(1)).toFixed(), "0"); }); diff --git a/test/gov/ERC721/ERC721RawPower.test.js b/test/gov/ERC721/ERC721RawPower.test.js index 9c4a4383..08bedf32 100644 --- a/test/gov/ERC721/ERC721RawPower.test.js +++ b/test/gov/ERC721/ERC721RawPower.test.js @@ -151,7 +151,7 @@ describe("ERC721RawPower", () => { }); it("should support these interfaces", async () => { - assert.isTrue(await nft.supportsInterface("0x589dde78")); + assert.isTrue(await nft.supportsInterface("0x24b96d42")); assert.isTrue(await nft.supportsInterface("0x780e9d63")); }); }); @@ -244,6 +244,23 @@ describe("ERC721RawPower", () => { }); }); + describe("getNftMinPower()", () => { + beforeEach(async () => { + await deployNft(startTime + 1000, "1", wei("100"), wei("500")); + await nft.mint(SECOND, 1, "URI"); + }); + + it("should return correct min power", async () => { + assert.equal((await nft.getNftMinPower(1)).toFixed(), "0"); + + await nft.addCollateral(wei("250"), "1", { from: SECOND }); + + await setTime((await getCurrentBlockTime()) + 1000); + + assert.equal((await nft.getNftMinPower(1)).toFixed(), wei("50")); + }); + }); + describe("setNftRequiredCollateral()", () => { beforeEach(async () => { await deployNft(startTime + 1000, "1", "1", "1"); diff --git a/test/gov/GovUserKeeper.test.js b/test/gov/GovUserKeeper.test.js index 6fbeba0a..cb34e471 100644 --- a/test/gov/GovUserKeeper.test.js +++ b/test/gov/GovUserKeeper.test.js @@ -265,14 +265,6 @@ describe("GovUserKeeper", () => { ["1", "3", "5"] ); - const nftPower = await userKeeper.nftVotingPower(power.nftIds, true); - - assert.equal(nftPower.nftPower.toFixed(), wei("3000")); - assert.deepEqual( - nftPower.perNftPower.map((e) => e.toFixed()), - [wei("1000"), wei("1000"), wei("1000")] - ); - assert.deepEqual( (await userKeeper.nftExactBalance(SECOND, VoteType.PersonalVote)).nfts.map((e) => e.toFixed()), ["1", "3", "5"] @@ -945,69 +937,21 @@ describe("GovUserKeeper", () => { }); describe("getDelegatedAssets()", () => { - it("should return delegated assets properly", async () => { + it("should return delegated power properly", async () => { await userKeeper.depositTokens(OWNER, OWNER, wei("400")); await userKeeper.depositNfts(OWNER, OWNER, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), 0); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - [] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); await userKeeper.delegateTokens(OWNER, SECOND, wei("400")); await userKeeper.delegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("9400")); await userKeeper.undelegateTokens(OWNER, SECOND, wei("400")); await userKeeper.undelegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), 0); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - [] - ); - }); - - it("should return delegated assets properly", async () => { - await userKeeper.depositTokens(OWNER, OWNER, wei("400")); - await userKeeper.depositNfts(OWNER, OWNER, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("0")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - [] - ); - - await userKeeper.delegateTokens(OWNER, SECOND, wei("400")); - - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - [] - ); - - await userKeeper.delegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 8, 9]); - - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - ["1", "2", "3", "4", "5", "6", "7", "8", "9"] - ); - - await userKeeper.undelegateTokens(OWNER, SECOND, wei("200")); - await userKeeper.undelegateNfts(OWNER, SECOND, [1, 2, 3, 4]); - - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("200")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - ["9", "8", "7", "6", "5"] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); }); }); @@ -1187,11 +1131,6 @@ describe("GovUserKeeper", () => { assert.equal(toBN(power.rawNftPower).toFixed(), "0"); assert.deepEqual(power.perNftPower, []); - const nftPower = await userKeeper.nftVotingPower([], true); - - assert.equal(nftPower.nftPower, "0"); - assert.deepEqual(nftPower.perNftPower, []); - const nftBalance = await userKeeper.nftBalance(OWNER, VoteType.PersonalVote); const nftExactBalance = await userKeeper.nftExactBalance(OWNER, VoteType.PersonalVote); @@ -1201,11 +1140,8 @@ describe("GovUserKeeper", () => { assert.equal(nftExactBalance.ownedLength, "0"); }); - it("should return zero delegated assets", async () => { - const delegatedAmounts = await userKeeper.getDelegatedAssets(OWNER, SECOND); - - assert.equal(delegatedAmounts[0], "0"); - assert.deepEqual(delegatedAmounts[1], []); + it("should return zero delegated power", async () => { + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); }); it("should set erc721", async () => { @@ -1235,6 +1171,16 @@ describe("GovUserKeeper", () => { "Ownable: caller is not the owner" ); }); + + it("should return zero total nfts power", async () => { + const totalNftsPowers = await userKeeper.getTotalNftsPower([1, 2, 3], VoteType.PersonalVote, OWNER, true); + + assert.equal(totalNftsPowers[0].toFixed(), "0"); + assert.deepEqual( + totalNftsPowers[1].map((e) => e.toFixed()), + [] + ); + }); }); describe("enumerable nft", () => { @@ -1416,12 +1362,8 @@ describe("GovUserKeeper", () => { }); describe("getDelegatedAssets()", () => { - it("should return delegated assets properly", async () => { - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("0")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - [] - ); + it("should return delegated power properly", async () => { + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); await token.mint(OWNER, wei("400")); await token.approve(userKeeper.address, wei("400")); @@ -1434,29 +1376,17 @@ describe("GovUserKeeper", () => { await userKeeper.depositTokens(OWNER, OWNER, wei("400")); await userKeeper.depositNfts(OWNER, OWNER, [1, 2, 3]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("0")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - [] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); await userKeeper.delegateTokens(OWNER, SECOND, wei("400")); await userKeeper.delegateNfts(OWNER, SECOND, [1, 2, 3]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - ["1", "2", "3"] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("3400")); await userKeeper.undelegateTokens(OWNER, SECOND, wei("400")); await userKeeper.undelegateNfts(OWNER, SECOND, [1, 2, 3]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("0")); - assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => toBN(e).toFixed()), - [] - ); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("0")); }); }); }); @@ -1504,6 +1434,18 @@ describe("GovUserKeeper", () => { ); }); + it("should update nfts correctly if all conditions are met", async () => { + await userKeeper.depositNfts(OWNER, OWNER, [9]); + + await setTime((await getCurrentBlockTime()) + 1001); + + assert.equal((await nft.getNftInfo(9)).rawInfo.lastUpdate, "0"); + + await userKeeper.updateNftPowers([9]); + + assert.notEqual((await nft.getNftInfo(9)).rawInfo.lastUpdate, "0"); + }); + it("should calculate zero NFT power", async () => { await userKeeper.depositNfts(OWNER, SECOND, [1]); @@ -1526,68 +1468,118 @@ describe("GovUserKeeper", () => { it("should get total power with power NFT", async () => { assert.equal((await userKeeper.getTotalPower()).toFixed(), wei("80900")); }); - - it("should get nft voting power", async () => { - const nftPower = await userKeeper.nftVotingPower([1, 2, 3], true); - const nftPower2 = await userKeeper.nftVotingPower([1, 2, 3], false); - - assert.equal(toBN(nftPower.nftPower).toFixed(), "0"); - assert.equal(toBN(nftPower.nftPower).toFixed(), toBN(nftPower2.nftPower).toFixed()); - assert.deepEqual( - nftPower.perNftPower.map((e) => toBN(e).toFixed()), - ["0", "0", "0"] - ); - }); }); - describe("getDelegatedAssets()", () => { - it("should return delegated amount properly", async () => { + describe("getDelegatedAssetsPower()", () => { + it("should return delegated power properly", async () => { await token.approve(userKeeper.address, wei("400")); + await setTime(startTime + 201); + await userKeeper.depositTokens(OWNER, OWNER, wei("400")); await userKeeper.depositNfts(OWNER, OWNER, [1, 2, 3, 4, 5, 6, 7, 9]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), "0"); - assert.deepEqual((await userKeeper.getDelegatedAssets(OWNER, SECOND))[1], []); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); await userKeeper.delegateTokens(OWNER, SECOND, wei("400")); await userKeeper.delegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 9]); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("10400")); + + await setTime(startTime + 1001); + + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("10400")); + + await userKeeper.undelegateNfts(OWNER, SECOND, [9]); + + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), wei("400")); + + await userKeeper.undelegateTokens(OWNER, SECOND, wei("400")); + + assert.deepEqual((await userKeeper.getDelegatedAssetsPower(OWNER, SECOND)).toFixed(), "0"); + }); + }); + + describe("getTotalNftsPower()", () => { + beforeEach(async () => { + await token.approve(nft.address, wei("250")); + await nft.addCollateral(wei("250"), "7"); + }); + + it("should return total nfts min power correctly if micropool", async () => { + let totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, true); + + assert.equal(totalNftsPowers[0].toFixed(), "0"); assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - ["1", "2", "3", "4", "5", "6", "7", "9"] + totalNftsPowers[1].map((e) => e.toFixed()), + ["0", "0"] ); - await setTime(startTime + 201); + await setTime(startTime + 1001); + + await userKeeper.depositNfts(OWNER, OWNER, [7, 9]); + + totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, false); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), wei("400")); + assert.equal(totalNftsPowers[0].toFixed(), "0"); assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - ["1", "2", "3", "4", "5", "6", "7", "9"] + totalNftsPowers[1].map((e) => e.toFixed()), + [] ); - await userKeeper.undelegateTokens(OWNER, SECOND, wei("400")); - await userKeeper.undelegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 9]); + await userKeeper.delegateNfts(OWNER, SECOND, [7, 9]); + + totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, true); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), "0"); - assert.deepEqual((await userKeeper.getDelegatedAssets(OWNER, SECOND))[1], []); + assert.equal(totalNftsPowers[0].toFixed(), wei("15000")); + assert.deepEqual( + totalNftsPowers[1].map((e) => e.toFixed()), + [wei("5000"), wei("10000")] + ); + + await userKeeper.undelegateNfts(OWNER, SECOND, [7, 9]); + + totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, true); + + assert.equal(totalNftsPowers[0].toFixed(), "0"); + assert.deepEqual( + totalNftsPowers[1].map((e) => e.toFixed()), + ["0", "0"] + ); }); - it("should return zero delegated stake amount", async () => { - await nft.removeCollateral(wei("500"), "9"); + it("should return total nfts min power correctly if treasury", async () => { + await nft.transferFrom(OWNER, userKeeper.address, "7"); + await nft.transferFrom(OWNER, userKeeper.address, "9"); - await userKeeper.depositNfts(OWNER, OWNER, [1, 2, 3, 4, 5, 6, 7, 9]); - await userKeeper.delegateNfts(OWNER, SECOND, [1, 2, 3, 4, 5, 6, 7, 9]); + let totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.TreasuryVote, SECOND, true); - await setTime(startTime + 1000000000000); + assert.equal(totalNftsPowers[0].toFixed(), "0"); + assert.deepEqual( + totalNftsPowers[1].map((e) => e.toFixed()), + ["0", "0"] + ); - await userKeeper.updateNftPowers([1, 2, 3, 4, 5, 6, 7, 9]); + await setTime(startTime + 1001); + + await userKeeper.delegateNftsTreasury(SECOND, [7, 9]); + + totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, true); + + assert.equal(totalNftsPowers[0].toFixed(), wei("15000")); + assert.deepEqual( + totalNftsPowers[1].map((e) => e.toFixed()), + [wei("5000"), wei("10000")] + ); + + await userKeeper.undelegateNftsTreasury(SECOND, [7, 9]); + + totalNftsPowers = await userKeeper.getTotalNftsPower([7, 9], VoteType.MicropoolVote, SECOND, true); - assert.equal((await userKeeper.getDelegatedAssets(OWNER, SECOND))[0].toFixed(), "0"); + assert.equal(totalNftsPowers[0].toFixed(), "0"); assert.deepEqual( - (await userKeeper.getDelegatedAssets(OWNER, SECOND))[1].map((e) => e.toFixed()), - ["1", "2", "3", "4", "5", "6", "7", "9"] + totalNftsPowers[1].map((e) => e.toFixed()), + ["0", "0"] ); }); });