diff --git a/contracts/core/ContractsRegistry.sol b/contracts/core/ContractsRegistry.sol index 45c48e00..0db1108e 100644 --- a/contracts/core/ContractsRegistry.sol +++ b/contracts/core/ContractsRegistry.sol @@ -18,6 +18,7 @@ contract ContractsRegistry is IContractsRegistry, MultiOwnableContractsRegistry, string public constant POOL_REGISTRY_NAME = "POOL_REGISTRY"; string public constant DEXE_NAME = "DEXE"; + string public constant WETH_NAME = "WETH"; string public constant USD_NAME = "USD"; string public constant BABT_NAME = "BABT"; string public constant DEXE_EXPERT_NFT_NAME = "DEXE_EXPERT_NFT"; @@ -27,6 +28,7 @@ contract ContractsRegistry is IContractsRegistry, MultiOwnableContractsRegistry, string public constant TREASURY_NAME = "TREASURY"; string public constant CORE_PROPERTIES_NAME = "CORE_PROPERTIES"; + string public constant NETWORK_PROPERTIES_NAME = "NETWORK_PROPERTIES"; string public constant SPHEREX_ENGINE_NAME = "SPHEREX_ENGINE"; string public constant POOL_SPHEREX_ENGINE_NAME = "POOL_SPHEREX_ENGINE"; @@ -42,6 +44,22 @@ contract ContractsRegistry is IContractsRegistry, MultiOwnableContractsRegistry, _setSphereXEngine(CORE_PROPERTIES_NAME, sphereXEngine); } + function addContracts( + string[] calldata names_, + address[] calldata contractAddresses_ + ) external onlyOwner { + uint256 length = names_.length; + + require( + contractAddresses_.length == length, + "Contracts Registry: names and addresses lengths don't match" + ); + + for (uint256 i = 0; i < length; i++) { + _addContract(names_[i], contractAddresses_[i]); + } + } + function protectContractFunctions( string calldata contractName, bytes4[] calldata selectors @@ -72,6 +90,10 @@ contract ContractsRegistry is IContractsRegistry, MultiOwnableContractsRegistry, return getContract(DEXE_NAME); } + function getWETHContract() external view override returns (address) { + return getContract(WETH_NAME); + } + function getUSDContract() external view override returns (address) { return getContract(USD_NAME); } @@ -88,6 +110,10 @@ contract ContractsRegistry is IContractsRegistry, MultiOwnableContractsRegistry, return getContract(CORE_PROPERTIES_NAME); } + function getNetworkPropertiesContract() external view override returns (address) { + return getContract(NETWORK_PROPERTIES_NAME); + } + function getBABTContract() external view override returns (address) { return getContract(BABT_NAME); } diff --git a/contracts/core/network-properties/BSCProperties.sol b/contracts/core/network-properties/BSCProperties.sol new file mode 100644 index 00000000..600e935e --- /dev/null +++ b/contracts/core/network-properties/BSCProperties.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "../../interfaces/core/INetworkProperties.sol"; + +contract BSCProperties is INetworkProperties { + uint256 private constant BNB_SUPPLY = 150_000_000 * 10 ** 18; + + function getNativeSupply() external view override returns (uint256) { + return BNB_SUPPLY; + } +} diff --git a/contracts/factory/PoolFactory.sol b/contracts/factory/PoolFactory.sol index c9e823c3..440ca015 100644 --- a/contracts/factory/PoolFactory.sol +++ b/contracts/factory/PoolFactory.sol @@ -139,7 +139,7 @@ contract PoolFactory is IPoolFactory, AbstractPoolFactory { TokenSaleProposal(tokenSaleProxy).__TokenSaleProposal_init(poolProxy); GovSettings(govPoolDeps.settingsAddress).transferOwnership(poolProxy); - GovUserKeeper(govPoolDeps.userKeeperAddress).transferOwnership(poolProxy); + GovUserKeeper(payable(govPoolDeps.userKeeperAddress)).transferOwnership(poolProxy); GovValidators(govPoolDeps.validatorsAddress).transferOwnership(poolProxy); ERC721Expert(govPoolDeps.expertNftAddress).transferOwnership(poolProxy); ERC721Multiplier(govPoolDeps.nftMultiplierAddress).transferOwnership(poolProxy); @@ -214,7 +214,7 @@ contract PoolFactory is IPoolFactory, AbstractPoolFactory { parameters.validatorsParams.validators, parameters.validatorsParams.balances ); - GovUserKeeper(govPoolDeps.userKeeperAddress).__GovUserKeeper_init( + GovUserKeeper(payable(govPoolDeps.userKeeperAddress)).__GovUserKeeper_init( parameters.userKeeperParams.tokenAddress, parameters.userKeeperParams.nftAddress, parameters.userKeeperParams.individualPower, diff --git a/contracts/gov/GovPool.sol b/contracts/gov/GovPool.sol index 1ce18020..abff89da 100644 --- a/contracts/gov/GovPool.sol +++ b/contracts/gov/GovPool.sol @@ -144,6 +144,8 @@ contract GovPool is _babt = ISBT721(registry.getBABTContract()); _dexeExpertNft = IERC721Expert(registry.getDexeExpertNftContract()); _poolRegistry = registry.getPoolRegistryContract(); + + IGovUserKeeper(_govUserKeeper).setDependencies(contractsRegistry, bytes("")); } function unlock(address user) external override onlyBABTHolder { @@ -156,12 +158,19 @@ contract GovPool is _proposals.execute(proposalId); } - function deposit(uint256 amount, uint256[] calldata nftIds) external override onlyBABTHolder { + function deposit( + uint256 amount, + uint256[] calldata nftIds + ) external payable override onlyBABTHolder { require(amount > 0 || nftIds.length > 0, "Gov: empty deposit"); + _checkValue(amount); _lockBlock(DEPOSIT_WITHDRAW, msg.sender); - _govUserKeeper.depositTokens.exec(msg.sender, amount); + if (amount != 0) { + _govUserKeeper.depositTokens{value: msg.value}(msg.sender, msg.sender, amount); + } + _govUserKeeper.depositNfts.exec(msg.sender, nftIds); emit Deposited(amount, nftIds, msg.sender); @@ -262,9 +271,10 @@ contract GovPool is address delegatee, uint256 amount, uint256[] calldata nftIds - ) external override onlyThis { + ) external payable override onlyThis { require(amount > 0 || nftIds.length > 0, "Gov: empty delegation"); require(getExpertStatus(delegatee), "Gov: delegatee is not an expert"); + _checkValue(amount); _lockBlock(DELEGATE_UNDELEGATE_TREASURY, msg.sender); @@ -273,9 +283,14 @@ contract GovPool is if (amount != 0) { address token = _govUserKeeper.tokenAddress(); - IERC20(token).safeTransfer(address(_govUserKeeper), amount.from18Safe(token)); + if (amount != msg.value) { + IERC20(token).safeTransfer( + address(_govUserKeeper), + (amount - msg.value).from18Safe(token) + ); + } - _govUserKeeper.delegateTokensTreasury(delegatee, amount); + _govUserKeeper.delegateTokensTreasury{value: msg.value}(delegatee, amount); } if (nftIds.length != 0) { @@ -615,4 +630,8 @@ contract GovPool is "Gov: not BABT holder" ); } + + function _checkValue(uint256 amount) internal view { + require(msg.value <= amount, "Gov: value is greater than amount to transfer"); + } } diff --git a/contracts/gov/user-keeper/GovUserKeeper.sol b/contracts/gov/user-keeper/GovUserKeeper.sol index 5b3a4660..f1df200c 100644 --- a/contracts/gov/user-keeper/GovUserKeeper.sol +++ b/contracts/gov/user-keeper/GovUserKeeper.sol @@ -14,6 +14,10 @@ import "@solarity/solidity-lib/libs/arrays/Paginator.sol"; import "@solarity/solidity-lib/libs/arrays/ArrayHelper.sol"; import "@solarity/solidity-lib/libs/data-structures/memory/Vector.sol"; +import "@uniswap/v2-periphery/contracts/interfaces/IWETH.sol"; + +import "../../interfaces/core/IContractsRegistry.sol"; +import "../../interfaces/core/INetworkProperties.sol"; import "../../interfaces/gov/user-keeper/IGovUserKeeper.sol"; import "../../interfaces/gov/IGovPool.sol"; import "../../interfaces/gov/ERC721/powers/IERC721Power.sol"; @@ -40,6 +44,9 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad mapping(uint256 => uint256) internal _nftLockedNums; // tokenId => locked num + address public wethAddress; + address public networkPropertiesAddress; + event SetERC20(address token); event SetERC721(address token); @@ -73,16 +80,31 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } } + function setDependencies(address contractsRegistry, bytes memory) external onlyOwner { + IContractsRegistry registry = IContractsRegistry(contractsRegistry); + + wethAddress = registry.getWETHContract(); + networkPropertiesAddress = registry.getNetworkPropertiesContract(); + } + function depositTokens( address payer, address receiver, uint256 amount - ) external override onlyOwner withSupportedToken { + ) external payable override onlyOwner withSupportedToken { address token = tokenAddress; + uint256 fullAmount = amount; + + if (msg.value != 0) { + _wrapNative(msg.value); + amount -= msg.value; + } - IERC20(token).safeTransferFrom(payer, address(this), amount.from18Safe(token)); + if (amount > 0) { + IERC20(token).safeTransferFrom(payer, address(this), amount.from18Safe(token)); + } - _usersInfo[receiver].balances[IGovPool.VoteType.PersonalVote].tokens += amount; + _usersInfo[receiver].balances[IGovPool.VoteType.PersonalVote].tokens += fullAmount; } function withdrawTokens( @@ -93,7 +115,6 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad UserInfo storage payerInfo = _usersInfo[payer]; BalanceInfo storage payerBalanceInfo = payerInfo.balances[IGovPool.VoteType.PersonalVote]; - address token = tokenAddress; uint256 balance = payerBalanceInfo.tokens; uint256 maxTokensLocked = payerInfo.maxTokensLocked; @@ -104,7 +125,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad payerBalanceInfo.tokens = balance - amount; - IERC20(token).safeTransfer(receiver, amount.from18Safe(token)); + _sendNativeOrToken(receiver, amount); } function delegateTokens( @@ -134,7 +155,11 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad function delegateTokensTreasury( address delegatee, uint256 amount - ) external override onlyOwner withSupportedToken { + ) external payable override onlyOwner withSupportedToken { + if (msg.value != 0) { + _wrapNative(msg.value); + } + _usersInfo[delegatee].balances[IGovPool.VoteType.TreasuryVote].tokens += amount; } @@ -173,9 +198,7 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad delegateeBalanceInfo.tokens = balance - amount; - address token = tokenAddress; - - IERC20(token).safeTransfer(msg.sender, amount.from18Safe(token)); + _sendNativeOrToken(msg.sender, amount); } function depositNfts( @@ -477,6 +500,8 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad _setERC721Address(_nftAddress, individualPower, nftsTotalSupply); } + receive() external payable {} + function nftAddress() external view override returns (address) { return _nftInfo.nftAddress; } @@ -516,6 +541,11 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad } ownedBalance = ERC20(tokenAddress).balanceOf(voter).to18(tokenAddress); + + if (_isWrapped()) { + ownedBalance += address(voter).balance; + } + totalBalance += ownedBalance; } @@ -601,7 +631,11 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad address token = tokenAddress; if (token != address(0)) { - power = IERC20(token).totalSupply().to18(token); + if (_isWrapped()) { + power = INetworkProperties(networkPropertiesAddress).getNativeSupply(); + } else { + power = IERC20(token).totalSupply().to18(token); + } } token = _nftInfo.nftAddress; @@ -715,6 +749,19 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad ); } + function _sendNativeOrToken(address receiver, uint256 amount) internal { + address token = tokenAddress; + + if (_isWrapped()) { + _unwrapNative(amount); + + (bool ok, ) = payable(receiver).call{value: amount}(""); + require(ok, "GovUK: can't send ether"); + } else { + IERC20(token).safeTransfer(receiver, amount.from18Safe(token)); + } + } + function _cleanDelegatee(UserInfo storage delegatorInfo, address delegatee) internal { BalanceInfo storage delegatedBalance = delegatorInfo.delegatedBalances[delegatee]; @@ -780,4 +827,20 @@ contract GovUserKeeper is IGovUserKeeper, OwnableUpgradeable, ERC721HolderUpgrad function _withSupportedNft() internal view { require(_nftInfo.nftAddress != address(0), "GovUK: nft is not supported"); } + + function _wrapNative(uint256 value) internal { + require(_isWrapped(), "GovUK: not native token pool"); + + IWETH(wethAddress).deposit{value: value}(); + } + + function _unwrapNative(uint256 value) internal { + IWETH(wethAddress).withdraw(value); + } + + function _isWrapped() internal view returns (bool) { + address _wethAddress = wethAddress; + + return _wethAddress != address(0) && wethAddress == tokenAddress; + } } diff --git a/contracts/interfaces/core/IContractsRegistry.sol b/contracts/interfaces/core/IContractsRegistry.sol index 30260010..f6050931 100644 --- a/contracts/interfaces/core/IContractsRegistry.sol +++ b/contracts/interfaces/core/IContractsRegistry.sol @@ -23,6 +23,10 @@ interface IContractsRegistry { /// @return DEXE token contract address function getDEXEContract() external view returns (address); + /// @notice Used in dependency injection mechanism + /// @return Wrapped native coin contract address + function getWETHContract() external view returns (address); + /// @notice Used in dependency injection mechanism /// @return Platform's native USD token contract address. This may be USDT/BUSD/USDC/DAI/FEI function getUSDContract() external view returns (address); @@ -39,6 +43,10 @@ interface IContractsRegistry { /// @return CoreProperties contract address function getCorePropertiesContract() external view returns (address); + /// @notice Used in dependency injection mechanism + /// @return NetworkProperties contract address + function getNetworkPropertiesContract() external view returns (address); + /// @notice Used in dependency injection mechanism /// @return BABT contract address function getBABTContract() external view returns (address); diff --git a/contracts/interfaces/core/INetworkProperties.sol b/contracts/interfaces/core/INetworkProperties.sol new file mode 100644 index 00000000..5e88f060 --- /dev/null +++ b/contracts/interfaces/core/INetworkProperties.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +interface INetworkProperties { + /// @notice Used in native coin governance mechanism + /// @return The current full supply of native coin + function getNativeSupply() external view returns (uint256); +} diff --git a/contracts/interfaces/gov/IGovPool.sol b/contracts/interfaces/gov/IGovPool.sol index 5ab368f4..4683a60c 100644 --- a/contracts/interfaces/gov/IGovPool.sol +++ b/contracts/interfaces/gov/IGovPool.sol @@ -361,7 +361,7 @@ interface IGovPool { /// @notice The function for depositing tokens to the pool /// @param amount the erc20 deposit amount /// @param nftIds the array of nft ids to deposit - function deposit(uint256 amount, uint256[] calldata nftIds) external; + function deposit(uint256 amount, uint256[] calldata nftIds) external payable; /// @notice The function for withdrawing deposited tokens /// @param receiver the withdrawal receiver address @@ -383,7 +383,7 @@ interface IGovPool { address delegatee, uint256 amount, uint256[] calldata nftIds - ) external; + ) external payable; /// @notice The function for undelegating delegated tokens /// @param delegatee the undelegation target address (person who will be undelegated) diff --git a/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol b/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol index ece791c7..08917aa7 100644 --- a/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol +++ b/contracts/interfaces/gov/user-keeper/IGovUserKeeper.sol @@ -86,11 +86,15 @@ interface IGovUserKeeper { uint256[] perNftPower; } + /// @notice The function for injecting dependencies from the GovPool + /// @param contractsRegistry the address of Contracts Registry + function setDependencies(address contractsRegistry, bytes memory) external; + /// @notice The function for depositing tokens /// @param payer the address of depositor /// @param receiver the deposit receiver address /// @param amount the erc20 deposit amount - function depositTokens(address payer, address receiver, uint256 amount) external; + function depositTokens(address payer, address receiver, uint256 amount) external payable; /// @notice The function for withdrawing tokens /// @param payer the address from whom to withdraw the tokens @@ -107,7 +111,7 @@ interface IGovUserKeeper { /// @notice The function for delegating tokens from Treasury /// @param delegatee the address of delegatee /// @param amount the erc20 delegation amount - function delegateTokensTreasury(address delegatee, uint256 amount) external; + function delegateTokensTreasury(address delegatee, uint256 amount) external payable; /// @notice The function for undelegating tokens /// @param delegator the address of delegator diff --git a/contracts/libs/gov/gov-pool/GovPoolCreate.sol b/contracts/libs/gov/gov-pool/GovPoolCreate.sol index e9b264b7..5ace1a0a 100644 --- a/contracts/libs/gov/gov-pool/GovPoolCreate.sol +++ b/contracts/libs/gov/gov-pool/GovPoolCreate.sol @@ -245,24 +245,31 @@ library GovPoolCreate { bytes4 selector = actions[i].data.getSelector(); uint256 executorSettings = govSettings.executorToSettings(actions[i].executor); - require( - actions[i].value == 0 && + if (actions[i].value != 0) { + require( executorSettings == uint256(IGovSettings.ExecutorType.INTERNAL) && - (selector == IGovSettings.addSettings.selector || - selector == IGovSettings.editSettings.selector || - selector == IGovSettings.changeExecutors.selector || - selector == IGovUserKeeper.setERC20Address.selector || - selector == IGovUserKeeper.setERC721Address.selector || - selector == IGovPool.changeVotePower.selector || - selector == IGovPool.editDescriptionURL.selector || - selector == IGovPool.setNftMultiplierAddress.selector || - selector == IGovPool.changeVerifier.selector || - selector == IGovPool.delegateTreasury.selector || - selector == IGovPool.undelegateTreasury.selector || - selector == IGovPool.changeBABTRestriction.selector || - selector == IGovPool.setCreditInfo.selector), - "Gov: invalid internal data" - ); + selector == IGovPool.delegateTreasury.selector, + "Gov: invalid internal data" + ); + } else { + require( + executorSettings == uint256(IGovSettings.ExecutorType.INTERNAL) && + (selector == IGovSettings.addSettings.selector || + selector == IGovSettings.editSettings.selector || + selector == IGovSettings.changeExecutors.selector || + selector == IGovUserKeeper.setERC20Address.selector || + selector == IGovUserKeeper.setERC721Address.selector || + selector == IGovPool.changeVotePower.selector || + selector == IGovPool.editDescriptionURL.selector || + selector == IGovPool.setNftMultiplierAddress.selector || + selector == IGovPool.changeVerifier.selector || + selector == IGovPool.delegateTreasury.selector || + selector == IGovPool.undelegateTreasury.selector || + selector == IGovPool.changeBABTRestriction.selector || + selector == IGovPool.setCreditInfo.selector), + "Gov: invalid internal data" + ); + } } } @@ -461,6 +468,7 @@ library GovPoolCreate { require(actionFor.executor == metaGovPool, "Gov: invalid executor"); require(amountFor == amountAgainst, "Gov: invalid amount"); + require(actionFor.value == actionAgainst.value, "Gov: invalid value"); require(nftIdsFor.length == nftIdsAgainst.length, "Gov: invalid nfts length"); for (uint256 i = 0; i < nftIdsFor.length; i++) { diff --git a/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol b/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol index 4e815df1..7ff03484 100644 --- a/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol +++ b/contracts/libs/gov/gov-user-keeper/GovUserKeeperView.sol @@ -51,7 +51,7 @@ library GovUserKeeperView { false ); - (, , , , address votePower) = IGovPool(GovUserKeeper(address(this)).owner()) + (, , , , address votePower) = IGovPool(GovUserKeeper(payable(address(this))).owner()) .getHelperContracts(); personalPower = amount + nftPower; @@ -69,7 +69,7 @@ library GovUserKeeperView { IGovPool.VoteType[] memory voteTypes, bool perNftPowerArray ) public view returns (IGovUserKeeper.VotingPowerView[] memory votingPowers) { - GovUserKeeper userKeeper = GovUserKeeper(address(this)); + GovUserKeeper userKeeper = GovUserKeeper(payable(address(this))); votingPowers = new IGovUserKeeper.VotingPowerView[](users.length); for (uint256 i = 0; i < users.length; i++) { diff --git a/contracts/mock/tokens/ERC20Mock.sol b/contracts/mock/tokens/ERC20Mock.sol index cbf30c16..3a010b36 100644 --- a/contracts/mock/tokens/ERC20Mock.sol +++ b/contracts/mock/tokens/ERC20Mock.sol @@ -37,4 +37,14 @@ contract ERC20Mock is ERC20 { function setDecimals(uint8 decimals_) external { _decimals = decimals_; } + + function deposit() public payable { + _mint(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf(msg.sender) >= wad, ""); + _burn(msg.sender, wad); + payable(msg.sender).transfer(wad); + } } diff --git a/contracts/mock/tokens/WETHMock.sol b/contracts/mock/tokens/WETHMock.sol new file mode 100644 index 00000000..5df12217 --- /dev/null +++ b/contracts/mock/tokens/WETHMock.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract WETHMock { + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; + + event Approval(address indexed src, address indexed guy, uint256 wad); + event Transfer(address indexed src, address indexed dst, uint256 wad); + event Deposit(address indexed dst, uint256 wad); + event Withdrawal(address indexed src, uint256 wad); + + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + + receive() external payable { + deposit(); + } + + function deposit() public payable { + balanceOf[msg.sender] += msg.value; + emit Deposit(msg.sender, msg.value); + } + + function withdraw(uint256 wad) public { + require(balanceOf[msg.sender] >= wad, ""); + balanceOf[msg.sender] -= wad; + payable(msg.sender).transfer(wad); + emit Withdrawal(msg.sender, wad); + } + + function totalSupply() public view returns (uint256) { + return address(this).balance; + } + + function approve(address guy, uint256 wad) public returns (bool) { + allowance[msg.sender][guy] = wad; + emit Approval(msg.sender, guy, wad); + return true; + } + + function transfer(address dst, uint256 wad) public returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + + function transferFrom(address src, address dst, uint256 wad) public returns (bool) { + require(balanceOf[src] >= wad, ""); + + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad, ""); + allowance[src][msg.sender] -= wad; + } + + balanceOf[src] -= wad; + balanceOf[dst] += wad; + + emit Transfer(src, dst, wad); + + return true; + } +} diff --git a/deploy/1_ContractsRegistry.migration.js b/deploy/1_ContractsRegistry.migration.js index 85e78bb1..fac1c2ad 100644 --- a/deploy/1_ContractsRegistry.migration.js +++ b/deploy/1_ContractsRegistry.migration.js @@ -7,7 +7,4 @@ module.exports = async (deployer) => { const contractsRegistry = await deployer.deploy(ContractsRegistry); await deployer.deploy(ERC1967Proxy, [contractsRegistry.address, getBytesContractsRegistryInit()], { name: "proxy" }); - - // Empty transactions to account for nonces on BSC mainnet - await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce0"); }; diff --git a/deploy/2_NetworkProperties.migration.js b/deploy/2_NetworkProperties.migration.js new file mode 100644 index 00000000..d5a927a8 --- /dev/null +++ b/deploy/2_NetworkProperties.migration.js @@ -0,0 +1,12 @@ +const config = require("./config/utils.js").getConfig(); + +const ContractsRegistry = artifacts.require("ContractsRegistry"); + +module.exports = async (deployer) => { + const contractsRegistry = await deployer.deployed(ContractsRegistry, "proxy"); + + await contractsRegistry.addContracts( + [await contractsRegistry.NETWORK_PROPERTIES_NAME(), await contractsRegistry.WETH_NAME()], + [config.NETWORK_PROPERTIES, config.tokens.WBNB], + ); +}; diff --git a/deploy/2_SphereXEngines.migration.js b/deploy/3_SphereXEngines.migration.js similarity index 100% rename from deploy/2_SphereXEngines.migration.js rename to deploy/3_SphereXEngines.migration.js diff --git a/deploy/3_UserRegistry.migration.js b/deploy/4_UserRegistry.migration.js similarity index 100% rename from deploy/3_UserRegistry.migration.js rename to deploy/4_UserRegistry.migration.js diff --git a/deploy/4_CoreTokens.migration.js b/deploy/5_CoreTokens.migration.js similarity index 100% rename from deploy/4_CoreTokens.migration.js rename to deploy/5_CoreTokens.migration.js diff --git a/deploy/5_CoreProperties.migration.js b/deploy/6_CoreProperties.migration.js similarity index 100% rename from deploy/5_CoreProperties.migration.js rename to deploy/6_CoreProperties.migration.js diff --git a/deploy/6_PriceFeed.migration.js b/deploy/7_PriceFeed.migration.js similarity index 100% rename from deploy/6_PriceFeed.migration.js rename to deploy/7_PriceFeed.migration.js diff --git a/deploy/7_PoolFactory&Registry.migration.js b/deploy/8_PoolFactory&Registry.migration.js similarity index 100% rename from deploy/7_PoolFactory&Registry.migration.js rename to deploy/8_PoolFactory&Registry.migration.js diff --git a/deploy/97_DEXEDAOMultiplier.migration.js b/deploy/97_DEXEDAOMultiplier.migration.js index 0b2852e5..9a25214c 100644 --- a/deploy/97_DEXEDAOMultiplier.migration.js +++ b/deploy/97_DEXEDAOMultiplier.migration.js @@ -6,8 +6,8 @@ const DexeMultiplier = artifacts.require("DexeERC721Multiplier"); module.exports = async (deployer) => { // Empty transactions to account for nonces on BSC mainnet + await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce0"); await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce1"); - await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce2"); let dexeMultiplier = await deployer.deploy(DexeMultiplier); await deployer.deploy( @@ -18,7 +18,7 @@ module.exports = async (deployer) => { dexeMultiplier = await deployer.deployed(DexeMultiplier, "multiplierProxy"); - await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce3"); + await deployer.sendNative("0x0000000000000000000000000000000000000000", 0, "nonce2"); await dexeMultiplier.transferOwnership(deployer.dexeDaoAddress); Reporter.reportContracts(["DEXE MULTIPLIER NFT", dexeMultiplier.address]); diff --git a/deploy/config/configs/dev-mumbai.conf.js b/deploy/config/configs/dev-mumbai.conf.js index 3ca09623..cc35c5a3 100644 --- a/deploy/config/configs/dev-mumbai.conf.js +++ b/deploy/config/configs/dev-mumbai.conf.js @@ -18,6 +18,8 @@ const uniswap = { quoter: "0x61fFE014bA17989E743c5F6cB21bF9697530B21e", }; +const NETWORK_PROPERTIES = ""; + const DEXE_DAO_NAME = "DeXe Protocol"; const DOCUMENT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -180,6 +182,7 @@ module.exports = { owners, tokens, uniswap, + NETWORK_PROPERTIES, DEXE_DAO_NAME, DOCUMENT_HASH, DEFAULT_CORE_PROPERTIES, diff --git a/deploy/config/configs/dev-sepolia.conf.js b/deploy/config/configs/dev-sepolia.conf.js index 46008be6..c0213a2f 100644 --- a/deploy/config/configs/dev-sepolia.conf.js +++ b/deploy/config/configs/dev-sepolia.conf.js @@ -18,6 +18,8 @@ const uniswap = { quoter: "0xed1f6473345f45b75f8179591dd5ba1888cf2fb3", }; +const NETWORK_PROPERTIES = "0x7Dc3f3bEAeC9dAbC1Ae17508d96335E228a3c2a8"; + const DEXE_DAO_NAME = "DeXe Protocol"; const DOCUMENT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -180,6 +182,7 @@ module.exports = { owners, tokens, uniswap, + NETWORK_PROPERTIES, DEXE_DAO_NAME, DOCUMENT_HASH, DEFAULT_CORE_PROPERTIES, diff --git a/deploy/config/configs/dev.conf.js b/deploy/config/configs/dev.conf.js index 23b99690..4406f5ba 100644 --- a/deploy/config/configs/dev.conf.js +++ b/deploy/config/configs/dev.conf.js @@ -18,6 +18,8 @@ const uniswap = { quoter: "0xbC203d7f83677c7ed3F7acEc959963E7F4ECC5C2", }; +const NETWORK_PROPERTIES = "0x35b3978fa2fA3cCC754e47cbD1D9956485A83736"; + const DEXE_DAO_NAME = "DeXe Protocol"; const DOCUMENT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -181,6 +183,7 @@ module.exports = { owners, tokens, uniswap, + NETWORK_PROPERTIES, DEXE_DAO_NAME, DOCUMENT_HASH, DEFAULT_CORE_PROPERTIES, diff --git a/deploy/config/configs/prod.conf.js b/deploy/config/configs/prod.conf.js index afe7cf3c..6a4a316a 100644 --- a/deploy/config/configs/prod.conf.js +++ b/deploy/config/configs/prod.conf.js @@ -18,6 +18,8 @@ const uniswap = { quoter: "0xb048bbc1ee6b733fffcfb9e9cef7375518e25997", }; +const NETWORK_PROPERTIES = "0x9a1AF9996458FDe28dD33EDC199f707ca9Ec2bA6"; + const DEXE_DAO_NAME = "DeXe Protocol"; const DOCUMENT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -181,6 +183,7 @@ module.exports = { owners, tokens, uniswap, + NETWORK_PROPERTIES, DEXE_DAO_NAME, DOCUMENT_HASH, DEFAULT_CORE_PROPERTIES, diff --git a/deploy/config/configs/stage.conf.js b/deploy/config/configs/stage.conf.js index 4a418dec..d93fe954 100644 --- a/deploy/config/configs/stage.conf.js +++ b/deploy/config/configs/stage.conf.js @@ -18,6 +18,8 @@ const uniswap = { quoter: "0xbC203d7f83677c7ed3F7acEc959963E7F4ECC5C2", }; +const NETWORK_PROPERTIES = "0x35b3978fa2fA3cCC754e47cbD1D9956485A83736"; + const DEXE_DAO_NAME = "DeXe Protocol"; const DOCUMENT_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000"; @@ -181,6 +183,7 @@ module.exports = { owners, tokens, uniswap, + NETWORK_PROPERTIES, DEXE_DAO_NAME, DOCUMENT_HASH, DEFAULT_CORE_PROPERTIES, diff --git a/hardhat.config.js b/hardhat.config.js index 8da19633..d7c7a952 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -41,7 +41,7 @@ module.exports = { gasMultiplier: 1.2, }, mumbai: { - url: `https://polygon-mumbai.infura.io/v3/${process.env.INFURA_KEY}`, + url: `https://polygon-testnet.public.blastapi.io`, accounts: privateKey(), gasPrice: 2000000000, gasMultiplier: 1.2, diff --git a/package-lock.json b/package-lock.json index 7ed12147..66666f61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6616,6 +6616,7 @@ "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-7.2.0.tgz", "integrity": "sha512-DnhQwcFEaYsvYDnACLZhMmCWd3rkOeEvglpa4q5i/5Jlm3UIsWaxVzuXvDLFCSCWRO3yy2/+V/G7FusFgejnfQ==", "dev": true, + "optional": true, "peer": true, "dependencies": { "buffer": "^6.0.3", @@ -6648,6 +6649,7 @@ "url": "https://feross.org/support" } ], + "optional": true, "peer": true, "dependencies": { "base64-js": "^1.3.1", @@ -7899,6 +7901,7 @@ "resolved": "https://registry.npmjs.org/catering/-/catering-2.1.1.tgz", "integrity": "sha512-K7Qy8O9p76sL3/3m7/zLKbRkyOlSZAgzEaLhyj2mXS8PsCud2Eo4hAb8aLtZqHh0QGqLcb9dlJSu6lHRVENm1w==", "dev": true, + "optional": true, "peer": true, "engines": { "node": ">=6" @@ -9232,6 +9235,7 @@ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.4.1.tgz", "integrity": "sha512-r4eRSeStEGf6M5SKdrQhhLK5bOwOBxQhIE3YSTnZE3GpKiLfnnhE+tPtrJE79+eDJgm39BM6LSoI8SCx4HbwlQ==", "dev": true, + "optional": true, "peer": true, "engines": { "node": ">=6" @@ -15739,6 +15743,7 @@ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", "dev": true, + "optional": true, "peer": true }, "node_modules/immutable": { @@ -15947,6 +15952,7 @@ "url": "https://feross.org/support" } ], + "optional": true, "peer": true, "engines": { "node": ">=4" @@ -16579,6 +16585,7 @@ "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-3.1.0.tgz", "integrity": "sha512-BWRCMHBxbIqPxJ8vHOvKUsaO0v1sLYZtjN3K2iZJsRBYtp+ONsY6Jfi6hy9K3+zolgQRryhIn2NRZjZnWJ9NmQ==", "dev": true, + "optional": true, "peer": true, "dependencies": { "catering": "^2.1.0" @@ -16694,6 +16701,7 @@ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-2.1.0.tgz", "integrity": "sha512-E486g1NCjW5cF78KGPrMDRBYzPuueMZ6VBXHT6gC7A8UYWGiM14fGgp+s/L1oFfDWSPV/+SFkYCmZ0SiESkRKA==", "dev": true, + "optional": true, "peer": true, "engines": { "node": ">=10" @@ -16716,6 +16724,7 @@ "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", "dev": true, "hasInstallScript": true, + "optional": true, "peer": true, "dependencies": { "abstract-leveldown": "~6.2.1", @@ -16731,6 +16740,7 @@ "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", "dev": true, + "optional": true, "peer": true, "dependencies": { "buffer": "^5.5.0", @@ -16748,6 +16758,7 @@ "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", "dev": true, + "optional": true, "peer": true, "engines": { "node": ">=6" @@ -16758,6 +16769,7 @@ "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", "dev": true, + "optional": true, "peer": true, "dependencies": { "xtend": "^4.0.2" @@ -16771,6 +16783,7 @@ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", "dev": true, + "optional": true, "peer": true, "bin": { "node-gyp-build": "bin.js", @@ -17696,6 +17709,7 @@ "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", "dev": true, + "optional": true, "peer": true }, "node_modules/negotiator": { diff --git a/test/core/ContractsRegistry.test.js b/test/core/ContractsRegistry.test.js index 302ad23d..2550c2fe 100644 --- a/test/core/ContractsRegistry.test.js +++ b/test/core/ContractsRegistry.test.js @@ -66,6 +66,7 @@ describe("ContractsRegistry", () => { describe("contract management", () => { it("should add and remove the contract", async () => { const USD = await ERC20Mock.new("USD", "USD", 18); + const WETH = await ERC20Mock.new("WETH", "WETH", 18); await contractsRegistry.addContract(await contractsRegistry.USD_NAME(), USD.address); @@ -76,6 +77,42 @@ describe("ContractsRegistry", () => { await truffleAssert.reverts(contractsRegistry.getUSDContract(), "ContractsRegistry: this mapping doesn't exist"); assert.isFalse(await contractsRegistry.hasContract(await contractsRegistry.USD_NAME())); + + await contractsRegistry.addContracts( + [await contractsRegistry.USD_NAME(), await contractsRegistry.WETH_NAME()], + [USD.address, WETH.address], + ); + + assert.equal(await contractsRegistry.getUSDContract(), USD.address); + assert.isTrue(await contractsRegistry.hasContract(await contractsRegistry.USD_NAME())); + assert.equal(await contractsRegistry.getWETHContract(), WETH.address); + assert.isTrue(await contractsRegistry.hasContract(await contractsRegistry.WETH_NAME())); + }); + + it("should not batch add if not owner", async () => { + const USD = await ERC20Mock.new("USD", "USD", 18); + const WETH = await ERC20Mock.new("WETH", "WETH", 18); + + await truffleAssert.reverts( + contractsRegistry.addContracts( + [await contractsRegistry.USD_NAME(), await contractsRegistry.WETH_NAME()], + [USD.address, WETH.address], + { from: SECOND }, + ), + "MultiOwnable: caller is not the owner", + ); + }); + + it("should revert on names and addresses length mismatch", async () => { + const USD = await ERC20Mock.new("USD", "USD", 18); + + await truffleAssert.reverts( + contractsRegistry.addContracts( + [await contractsRegistry.USD_NAME(), await contractsRegistry.WETH_NAME()], + [USD.address], + ), + "Contracts Registry: names and addresses lengths don't match", + ); }); it("should not add proxy contract without engine", async () => { diff --git a/test/core/PriceFeed.test.js b/test/core/PriceFeed.test.js index 4d87dc6c..28b7e463 100644 --- a/test/core/PriceFeed.test.js +++ b/test/core/PriceFeed.test.js @@ -9,6 +9,8 @@ const UniswapPathFinderLib = artifacts.require("UniswapPathFinder"); const UniswapV2RouterMock = artifacts.require("UniswapV2RouterMock"); const UniswapV3QuoterMock = artifacts.require("UniswapV3QuoterMock"); const ERC20Mock = artifacts.require("ERC20Mock"); +const WethMock = artifacts.require("WETHMock"); +const BscProperties = artifacts.require("BSCProperties"); const SphereXEngineMock = artifacts.require("SphereXEngineMock"); ContractsRegistry.numberFormat = "BigNumber"; @@ -16,6 +18,8 @@ PriceFeed.numberFormat = "BigNumber"; UniswapV2RouterMock.numberFormat = "BigNumber"; UniswapV3QuoterMock.numberFormat = "BigNumber"; ERC20Mock.numberFormat = "BigNumber"; +WethMock.numberFormat = "BigNumber"; +BscProperties.numberFormat = "BigNumber"; const SWAP_UNISWAP_V2 = "0"; const SWAP_UNISWAP_V3_FEE500 = "1"; @@ -34,6 +38,7 @@ describe("PriceFeed", () => { let uniswapV3Quoter; let DEXE; let USD; + let WETH; const reverter = new Reverter(); @@ -49,6 +54,8 @@ describe("PriceFeed", () => { const _priceFeed = await PriceFeed.new(); DEXE = await ERC20Mock.new("DEXE", "DEXE", 18); USD = await ERC20Mock.new("USD", "USD", 18); + WETH = await WethMock.new(); + networkProperties = await BscProperties.new(); uniswapV2Router = await UniswapV2RouterMock.new(); uniswapV3Quoter = await UniswapV3QuoterMock.new(); const _sphereXEngine = await SphereXEngineMock.new(); @@ -57,6 +64,8 @@ describe("PriceFeed", () => { await contractsRegistry.addContract(await contractsRegistry.DEXE_NAME(), DEXE.address); await contractsRegistry.addContract(await contractsRegistry.USD_NAME(), USD.address); + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), WETH.address); + await contractsRegistry.addContract(await contractsRegistry.NETWORK_PROPERTIES_NAME(), networkProperties.address); await contractsRegistry.addContract(await contractsRegistry.SPHEREX_ENGINE_NAME(), _sphereXEngine.address); await contractsRegistry.addProxyContract(await contractsRegistry.PRICE_FEED_NAME(), _priceFeed.address); diff --git a/test/factory/PoolFactory.test.js b/test/factory/PoolFactory.test.js index c3d055fa..e3ec9c0e 100644 --- a/test/factory/PoolFactory.test.js +++ b/test/factory/PoolFactory.test.js @@ -10,6 +10,8 @@ const ContractsRegistry = artifacts.require("ContractsRegistry"); const ERC20Mock = artifacts.require("ERC20Mock"); const ERC721Mock = artifacts.require("ERC721Mock"); const BABTMock = artifacts.require("BABTMock"); +const WethMock = artifacts.require("WETHMock"); +const BscProperties = artifacts.require("BSCProperties"); const ERC721Expert = artifacts.require("ERC721Expert"); const ERC721Multiplier = artifacts.require("ERC721Multiplier"); const LinearPower = artifacts.require("LinearPower"); @@ -47,6 +49,8 @@ const SphereXEngine = artifacts.require("SphereXEngine"); ContractsRegistry.numberFormat = "BigNumber"; ERC20Mock.numberFormat = "BigNumber"; +WethMock.numberFormat = "BigNumber"; +BscProperties.numberFormat = "BigNumber"; ERC721Mock.numberFormat = "BigNumber"; BABTMock.numberFormat = "BigNumber"; CoreProperties.numberFormat = "BigNumber"; @@ -71,6 +75,7 @@ describe("PoolFactory", () => { let testERC20; let testERC721; let babt; + let WETH; let sphereXEngine; const reverter = new Reverter(); @@ -136,6 +141,8 @@ describe("PoolFactory", () => { const contractsRegistry = await ContractsRegistry.new(); const DEXE = await ERC20Mock.new("DEXE", "DEXE", 18); const USD = await ERC20Mock.new("USD", "USD", 6); + WETH = await WethMock.new(); + networkProperties = await BscProperties.new(); babt = await BABTMock.new(); const _dexeExpertNft = await ERC721Expert.new(); const _coreProperties = await CoreProperties.new(); @@ -156,6 +163,8 @@ describe("PoolFactory", () => { await contractsRegistry.addContract(await contractsRegistry.DEXE_NAME(), DEXE.address); await contractsRegistry.addContract(await contractsRegistry.USD_NAME(), USD.address); + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), WETH.address); + await contractsRegistry.addContract(await contractsRegistry.NETWORK_PROPERTIES_NAME(), networkProperties.address); await contractsRegistry.addContract(await contractsRegistry.BABT_NAME(), babt.address); await contractsRegistry.addContract(await contractsRegistry.DEXE_EXPERT_NFT_NAME(), _dexeExpertNft.address); @@ -382,6 +391,8 @@ describe("PoolFactory", () => { assert.equal(await tokenSale.latestTierId(), "0"); assert.equal(await govUserKeeper.tokenAddress(), token.address); + assert.equal(await govUserKeeper.wethAddress(), WETH.address); + assert.equal(await govUserKeeper.networkPropertiesAddress(), networkProperties.address); const votePower = await PolynomialPower.at(helperContracts[4]); diff --git a/test/gov/GovPool.test.js b/test/gov/GovPool.test.js index da67109e..135ca6fe 100644 --- a/test/gov/GovPool.test.js +++ b/test/gov/GovPool.test.js @@ -49,6 +49,7 @@ const { getCurrentBlockTime, setTime } = require("../helpers/block-helper"); const { impersonate } = require("../helpers/impersonator"); const { assert } = require("chai"); const ethSigUtil = require("@metamask/eth-sig-util"); +const { default: BigNumber } = require("bignumber.js"); const ContractsRegistry = artifacts.require("ContractsRegistry"); const PoolRegistry = artifacts.require("PoolRegistry"); @@ -65,6 +66,8 @@ const VotePowerMock = artifacts.require("VotePowerMock"); const ERC721RawPower = artifacts.require("ERC721RawPower"); const ERC721Expert = artifacts.require("ERC721Expert"); const ERC20Mock = artifacts.require("ERC20Mock"); +const WethMock = artifacts.require("WETHMock"); +const BscProperties = artifacts.require("BSCProperties"); const ERC20 = artifacts.require("ERC20"); const BABTMock = artifacts.require("BABTMock"); const ExecutorTransferMock = artifacts.require("ExecutorTransferMock"); @@ -98,6 +101,8 @@ ERC721Expert.numberFormat = "BigNumber"; ERC20Mock.numberFormat = "BigNumber"; ERC20.numberFormat = "BigNumber"; BABTMock.numberFormat = "BigNumber"; +WethMock.numberFormat = "BigNumber"; +BscProperties.numberFormat = "BigNumber"; ExecutorTransferMock.numberFormat = "BigNumber"; describe("GovPool", () => { @@ -119,6 +124,7 @@ describe("GovPool", () => { let rewardToken; let nftMultiplier; let babt; + let weth; let settings; let expertNft; @@ -137,6 +143,16 @@ describe("GovPool", () => { const getProposalByIndex = async (index) => (await govPool.getProposals(index - 1, 1))[0].proposal; + async function switchWeth(poolOffset, newWethAddress) { + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), newWethAddress); + + await poolRegistry.injectDependenciesToExistingPools(await poolRegistry.GOV_POOL_NAME(), poolOffset, 1); + } + + function calculateUsedGas(tx) { + return toBN(tx.receipt.gasUsed).times(tx.receipt.effectiveGasPrice); + } + async function depositAndVote( proposalId, depositAmount, @@ -228,6 +244,8 @@ describe("GovPool", () => { const _poolRegistry = await PoolRegistry.new(); dexeExpertNft = await ERC721Expert.new(); babt = await BABTMock.new(); + weth = await WethMock.new(); + networkProperties = await BscProperties.new(); token = await ERC20Mock.new("Mock", "Mock", 18); nft = await ERC721EnumMock.new("Mock", "Mock"); attacker = await GovPoolAttackerMock.new(); @@ -249,6 +267,8 @@ describe("GovPool", () => { await contractsRegistry.addContract(await contractsRegistry.DEXE_EXPERT_NFT_NAME(), dexeExpertNft.address); await contractsRegistry.addContract(await contractsRegistry.BABT_NAME(), babt.address); + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), weth.address); + await contractsRegistry.addContract(await contractsRegistry.NETWORK_PROPERTIES_NAME(), networkProperties.address); coreProperties = await CoreProperties.at(await contractsRegistry.getCorePropertiesContract()); poolRegistry = await PoolRegistry.at(await contractsRegistry.getPoolRegistryContract()); @@ -481,18 +501,26 @@ describe("GovPool", () => { await executeValidatorProposal([[govPool.address, 0, getBytesSetNftMultiplierAddress(nftMultiplierAddress)]]); } - async function delegateTreasury(delegatee, amount, nftIds) { + async function delegateTreasury(delegatee, amount, nftIds, value = 0) { if (!(await govPool.getExpertStatus(delegatee))) { await executeValidatorProposal([[expertNft.address, 0, getBytesMintExpertNft(delegatee, "URI")]]); } - await token.mint(govPool.address, amount); + if (value != 0) { + web3.eth.sendTransaction({ + from: OWNER, + to: govPool.address, + value: value, + }); + } + + await token.mint(govPool.address, BigNumber.maximum(toBN(amount), toBN(value)).minus(value)); for (let i of nftIds) { await nft.mint(govPool.address, i); } - await executeValidatorProposal([[govPool.address, 0, getBytesDelegateTreasury(delegatee, amount, nftIds)]]); + await executeValidatorProposal([[govPool.address, value, getBytesDelegateTreasury(delegatee, amount, nftIds)]]); } async function undelegateTreasury(delegatee, amount, nftIds) { @@ -663,6 +691,96 @@ describe("GovPool", () => { assert.equal((await userKeeper.nftBalance(OWNER, VoteType.PersonalVote)).totalBalance.toFixed(), "10"); assert.equal((await userKeeper.nftBalance(OWNER, VoteType.PersonalVote)).ownedBalance.toFixed(), "7"); }); + + it("cant deposit ether if gov token is not native", async () => { + await truffleAssert.reverts(govPool.deposit(wei("100"), [], { value: 100 }), "GovUK: not native token pool"); + }); + + it("cant overdeposit ether", async () => { + await truffleAssert.reverts( + govPool.deposit(1, [1, 2, 3], { value: 2 }), + "Gov: value is greater than amount to transfer", + ); + }); + + it("could deposit ether", async () => { + await switchWeth(0, token.address); + + await token.mint(SECOND, wei("1000")); + + const INITIAL_ETHER_BALANCE = toBN(await web3.eth.getBalance(SECOND)); + const INITIAL_WRAPPED_BALANCE = await token.balanceOf(SECOND); + const INITIAL_OVERALL_BALANCE = INITIAL_ETHER_BALANCE.plus(INITIAL_WRAPPED_BALANCE); + + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).totalBalance.toFixed(), + INITIAL_OVERALL_BALANCE.toFixed(), + ); + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).ownedBalance.toFixed(), + INITIAL_OVERALL_BALANCE.toFixed(), + ); + + const tx = await govPool.deposit(wei("100"), [], { value: wei("100"), from: SECOND }); + const USED_GAS = calculateUsedGas(tx); + + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).totalBalance.toFixed(), + INITIAL_OVERALL_BALANCE.minus(USED_GAS).toFixed(), + ); + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).ownedBalance.toFixed(), + INITIAL_OVERALL_BALANCE.minus(wei("100")).minus(USED_GAS).toFixed(), + ); + + await switchWeth(0, weth.address); + }); + + it("could make mixed deposit", async () => { + await switchWeth(0, token.address); + + await token.mint(SECOND, wei("1000")); + await token.approve(userKeeper.address, wei("500"), { from: SECOND }); + + await truffleAssert.reverts( + govPool.deposit(wei("1001"), [], { value: wei("500"), from: SECOND }), + "ERC20: insufficient allowance", + ); + + const INITIAL_ETHER_BALANCE = toBN(await web3.eth.getBalance(SECOND)); + const INITIAL_WRAPPED_BALANCE = await token.balanceOf(SECOND); + const INITIAL_OVERALL_BALANCE = INITIAL_ETHER_BALANCE.plus(INITIAL_WRAPPED_BALANCE); + + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).totalBalance.toFixed(), + INITIAL_OVERALL_BALANCE.toFixed(), + ); + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).ownedBalance.toFixed(), + INITIAL_OVERALL_BALANCE.toFixed(), + ); + + const tx = await govPool.deposit(wei("1000"), [], { value: wei("500"), from: SECOND }); + const USED_GAS = calculateUsedGas(tx); + + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).totalBalance.toFixed(), + INITIAL_OVERALL_BALANCE.minus(USED_GAS).toFixed(), + ); + assert.equal( + (await userKeeper.tokenBalance(SECOND, VoteType.PersonalVote)).ownedBalance.toFixed(), + INITIAL_OVERALL_BALANCE.minus(wei("1000")).minus(USED_GAS).toFixed(), + ); + + assert.equal( + INITIAL_ETHER_BALANCE.minus(wei("500")).minus(USED_GAS).toFixed(), + toBN(await web3.eth.getBalance(SECOND)).toFixed(), + ); + + assert.equal(INITIAL_WRAPPED_BALANCE.minus(wei("500")).toFixed(), (await token.balanceOf(SECOND)).toFixed()); + + await switchWeth(0, weth.address); + }); }); describe("unlock()", () => { @@ -810,6 +928,13 @@ describe("GovPool", () => { await truffleAssert.reverts(govPool.createProposal("example.com", [], []), "Gov: invalid array length"); }); + it("should not create internal proposal with wrong selector", async () => { + await truffleAssert.reverts( + govPool.createProposal("example.com", [[govPool.address, 0, getBytesApprove(SECOND, 1)]], []), + "Gov: invalid internal data", + ); + }); + it("should not create proposal if insufficient deposited amount", async () => { await govPool.withdraw(OWNER, 0, [1]); @@ -1319,6 +1444,22 @@ describe("GovPool", () => { "Gov: invalid amount", ); + await truffleAssert.reverts( + govPool.createProposal( + "", + [ + [govPool.address, wei("1"), getBytesGovDeposit(wei("1"), [])], + [govPool.address, 0, getBytesGovVote(1, wei("1"), [], true)], + ], + [ + [govPool.address, 0, getBytesGovDeposit(wei("1"), [])], + [govPool.address, 0, getBytesGovVote(1, wei("1"), [], false)], + ], + { from: SECOND }, + ), + "Gov: invalid value", + ); + await truffleAssert.reverts( govPool.createProposal( "", @@ -1642,6 +1783,17 @@ describe("GovPool", () => { await govPool.deposit(wei("100000000000000000000"), [], { from: SECOND }); }); + it("should get totalPower of native coin", async () => { + await switchWeth(0, token.address); + + const individualPower = wei("1000"); + const totalNftPower = (await nft.totalSupply()).times(individualPower); + + assert.equal((await userKeeper.getTotalPower()).toFixed(), totalNftPower.plus(wei("150000000")).toFixed()); + + await switchWeth(0, weth.address); + }); + it("should not vote if vote unavailable", async () => { await truffleAssert.reverts( govPool.vote(1, true, wei("100000000000000000000"), [], { from: SECOND }), @@ -3016,6 +3168,33 @@ describe("GovPool", () => { assert.equal(await nft.ownerOf(1), OWNER); }); + it("should deposit and withdraw native tokens", async () => { + await switchWeth(0, token.address); + + await govPool.deposit(wei("1000"), [], { value: wei("1000") }); + + const ETH_BEFORE = toBN(await web3.eth.getBalance(OWNER)); + const tx = await govPool.withdraw(OWNER, wei("1000"), []); + const GAS_USED = calculateUsedGas(tx); + + assert.equal( + toBN(await web3.eth.getBalance(OWNER)).toFixed(), + ETH_BEFORE.minus(GAS_USED).plus(wei("1000")).toFixed(), + ); + + await switchWeth(0, weth.address); + }); + + it("reverts if could not withdraw eth", async () => { + await switchWeth(0, token.address); + + await govPool.deposit(wei("1000"), [], { value: wei("1000") }); + + await truffleAssert.reverts(govPool.withdraw(settings.address, wei("1000"), []), "GovUK: can't send ether"); + + await switchWeth(0, weth.address); + }); + it("should deposit, vote, unlock", async () => { await govPool.deposit(wei("1000"), [1, 2, 3, 4]); @@ -3571,6 +3750,32 @@ describe("GovPool", () => { ); }); + it("could delegate and undelegate treasury for native token", async () => { + await switchWeth(0, token.address); + + assert.equal((await userKeeper.tokenBalance(THIRD, VoteType.TreasuryVote)).totalBalance.toFixed(), "0"); + + await truffleAssert.reverts( + delegateTreasury(THIRD, wei("100"), [], wei("200")), + "Gov: value is greater than amount to transfer", + ); + + await delegateTreasury(THIRD, wei("100"), [], wei("100")); + assert.equal( + (await userKeeper.tokenBalance(THIRD, VoteType.TreasuryVote)).totalBalance.toFixed(), + wei("100"), + ); + + const INITIAL_ETHER_BALANCE = toBN(await web3.eth.getBalance(govPool.address)); + await undelegateTreasury(THIRD, wei("100"), []); + assert.equal( + toBN(await web3.eth.getBalance(govPool.address)).toFixed(), + INITIAL_ETHER_BALANCE.plus(wei("100")).toFixed(), + ); + + await switchWeth(0, weth.address); + }); + it("should revert if call is not from expert", async () => { await token.mint(govPool.address, wei("1")); diff --git a/test/gov/GovUserKeeper.test.js b/test/gov/GovUserKeeper.test.js index 3b29ae95..3648a6ab 100644 --- a/test/gov/GovUserKeeper.test.js +++ b/test/gov/GovUserKeeper.test.js @@ -118,6 +118,11 @@ describe("GovUserKeeper", () => { }); it("only owner should call these functions", async () => { + await truffleAssert.reverts( + userKeeper.setDependencies(SECOND, "0x", { from: SECOND }), + "Ownable: caller is not the owner", + ); + await truffleAssert.reverts( userKeeper.depositTokens(OWNER, SECOND, wei("100"), { from: SECOND }), "Ownable: caller is not the owner", diff --git a/test/gov/proposals/DistributionProposal.test.js b/test/gov/proposals/DistributionProposal.test.js index 39c8874d..ae8942c9 100644 --- a/test/gov/proposals/DistributionProposal.test.js +++ b/test/gov/proposals/DistributionProposal.test.js @@ -21,6 +21,8 @@ const ERC721Expert = artifacts.require("ERC721Expert"); const LinearPower = artifacts.require("LinearPower"); const ERC721Multiplier = artifacts.require("ERC721Multiplier"); const ERC20Mock = artifacts.require("ERC20Mock"); +const WethMock = artifacts.require("WETHMock"); +const BscProperties = artifacts.require("BSCProperties"); const BABTMock = artifacts.require("BABTMock"); const GovUserKeeperViewLib = artifacts.require("GovUserKeeperView"); const GovPoolCreateLib = artifacts.require("GovPoolCreate"); @@ -49,6 +51,8 @@ ERC721EnumMock.numberFormat = "BigNumber"; ERC721Expert.numberFormat = "BigNumber"; ERC20Mock.numberFormat = "BigNumber"; BABTMock.numberFormat = "BigNumber"; +WethMock.numberFormat = "BigNumber"; +BscProperties.numberFormat = "BigNumber"; describe("DistributionProposal", () => { let OWNER; @@ -115,6 +119,8 @@ describe("DistributionProposal", () => { const _poolRegistry = await PoolRegistry.new(); const _dexeExpertNft = await ERC721Expert.new(); const BABT = await BABTMock.new(); + const WETH = await WethMock.new(); + networkProperties = await BscProperties.new(); token = await ERC20Mock.new("Mock", "Mock", 18); nft = await ERC721EnumMock.new("Mock", "Mock"); const _sphereXEngine = await SphereXEngineMock.new(); @@ -133,6 +139,8 @@ describe("DistributionProposal", () => { await contractsRegistry.addContract(await contractsRegistry.DEXE_EXPERT_NFT_NAME(), _dexeExpertNft.address); await contractsRegistry.addContract(await contractsRegistry.BABT_NAME(), BABT.address); + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), WETH.address); + await contractsRegistry.addContract(await contractsRegistry.NETWORK_PROPERTIES_NAME(), networkProperties.address); coreProperties = await CoreProperties.at(await contractsRegistry.getCorePropertiesContract()); poolRegistry = await PoolRegistry.at(await contractsRegistry.getPoolRegistryContract()); diff --git a/test/gov/proposals/TokenSaleProposal.test.js b/test/gov/proposals/TokenSaleProposal.test.js index 0c54607a..25475425 100644 --- a/test/gov/proposals/TokenSaleProposal.test.js +++ b/test/gov/proposals/TokenSaleProposal.test.js @@ -32,6 +32,8 @@ const GovSettings = artifacts.require("GovSettings"); const GovValidators = artifacts.require("GovValidators"); const GovUserKeeper = artifacts.require("GovUserKeeper"); const ERC20Mock = artifacts.require("ERC20Mock"); +const WethMock = artifacts.require("WETHMock"); +const BscProperties = artifacts.require("BSCProperties"); const ERC721Mock = artifacts.require("ERC721Mock"); const ERC721Expert = artifacts.require("ERC721Expert"); const ERC721Multiplier = artifacts.require("ERC721Multiplier"); @@ -63,6 +65,8 @@ PoolRegistry.numberFormat = "BigNumber"; CoreProperties.numberFormat = "BigNumber"; GovPool.numberFormat = "BigNumber"; ERC20Mock.numberFormat = "BigNumber"; +WethMock.numberFormat = "BigNumber"; +BscProperties.numberFormat = "BigNumber"; ERC721Mock.numberFormat = "BigNumber"; ERC20Gov.numberFormat = "BigNumber"; ERC721Expert.numberFormat = "BigNumber"; @@ -105,6 +109,7 @@ describe("TokenSaleProposal", () => { let govPool; let dp; let babt; + let weth; let merkleTree; @@ -172,6 +177,8 @@ describe("TokenSaleProposal", () => { babt = await BABTMock.new(); const _dexeExpertNft = await ERC721Expert.new(); token = await ERC20Mock.new("Mock", "Mock", 18); + weth = await WethMock.new(); + networkProperties = await BscProperties.new(); participationToken = await ERC20Mock.new("PTMock", "PTMock", 18); participationNft = await ERC721Mock.new("PNFTMock", "PNFTMock"); const _sphereXEngine = await SphereXEngineMock.new(); @@ -191,6 +198,8 @@ describe("TokenSaleProposal", () => { await contractsRegistry.addContract(await contractsRegistry.DEXE_EXPERT_NFT_NAME(), _dexeExpertNft.address); await contractsRegistry.addContract(await contractsRegistry.BABT_NAME(), babt.address); + await contractsRegistry.addContract(await contractsRegistry.WETH_NAME(), weth.address); + await contractsRegistry.addContract(await contractsRegistry.NETWORK_PROPERTIES_NAME(), networkProperties.address); coreProperties = await CoreProperties.at(await contractsRegistry.getCorePropertiesContract()); poolRegistry = await PoolRegistry.at(await contractsRegistry.getPoolRegistryContract());