From 2b1189531feed405fa1a80e4ef00d8c10ba2b52f Mon Sep 17 00:00:00 2001 From: Sergey <2901744+evercoinx@users.noreply.github.com.> Date: Sun, 29 Sep 2024 16:54:15 +0200 Subject: [PATCH] Update public interfaces of external contracts --- contracts/ValidatorRegistry.sol | 4 +- contracts/interfaces/IStakeManager.sol | 73 +-- contracts/interfaces/IValidatorRegistry.sol | 4 +- contracts/interfaces/IValidatorShare.sol | 35 +- contracts/mocks/StakeManagerMock.sol | 68 +- contracts/mocks/ValidatorShareMock.sol | 58 +- test/MaticX.old.spec.ts | 676 -------------------- test/MaticX.spec.ts | 271 ++++++-- 8 files changed, 307 insertions(+), 882 deletions(-) delete mode 100644 test/MaticX.old.spec.ts diff --git a/contracts/ValidatorRegistry.sol b/contracts/ValidatorRegistry.sol index 6b247df2..67e4880a 100644 --- a/contracts/ValidatorRegistry.sol +++ b/contracts/ValidatorRegistry.sol @@ -285,8 +285,8 @@ contract ValidatorRegistry is return validators[_idx]; } - /// @notice Returns a list of registered validators. - /// @return List of registered validators + /// @notice Returns an array of registered validator ids. + /// @return Array of registered validator ids function getValidators() external view override returns (uint256[] memory) { return validators; } diff --git a/contracts/interfaces/IStakeManager.sol b/contracts/interfaces/IStakeManager.sol index 68c18af0..7a4f9a04 100644 --- a/contracts/interfaces/IStakeManager.sol +++ b/contracts/interfaces/IStakeManager.sol @@ -1,63 +1,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; -/// @title polygon stake manager interface. -/// @notice User to interact with the polygon stake manager. interface IStakeManager { - /// @notice Request unstake a validator. - /// @param validatorId validator id. - function unstake(uint256 validatorId) external; - - /// @notice Get the validator id using the user address. - /// @param user user that own the validator in our case the validator contract. - /// @return return the validator id - function getValidatorId(address user) external view returns (uint256); - - /// @notice get the validator contract used for delegation. - /// @param validatorId validator id. - /// @return return the address of the validator contract. - function getValidatorContract( - uint256 validatorId - ) external view returns (address); - - /// @notice Withdraw accumulated rewards - /// @param validatorId validator id. - function withdrawRewards(uint256 validatorId) external; - - /// @notice Get validator total staked. - /// @param validatorId validator id. - function validatorStake( - uint256 validatorId - ) external view returns (uint256); - - /// @notice Allows to unstake the staked tokens on the stakeManager. - /// @param validatorId validator id. - function unstakeClaim(uint256 validatorId) external; - - /// @notice Allows to migrate the staked tokens to another validator. - /// @param fromValidatorId From validator id. - /// @param toValidatorId To validator id. - /// @param amount amount in Matic. - function migrateDelegation( - uint256 fromValidatorId, - uint256 toValidatorId, - uint256 amount - ) external; - - /// @notice Returns a withdrawal delay. - function withdrawalDelay() external view returns (uint256); - - /// @notice Transfers amount from delegator - function delegationDeposit( - uint256 validatorId, - uint256 amount, - address delegator - ) external returns (bool); - - function setCurrentEpoch(uint256 _currentEpoch) external; - - function epoch() external view returns (uint256); - enum Status { Inactive, Active, @@ -81,10 +25,23 @@ interface IStakeManager { uint256 initialRewardPerStake; } + function migrateDelegation( + uint256 _fromValidatorId, + uint256 _toValidatorId, + uint256 _amount + ) external; + + function setCurrentEpoch(uint256 _currentEpoch) external; + + function getValidatorContract( + uint256 _validatorId + ) external view returns (address); + function validators( uint256 _index ) external view returns (Validator memory); - // TODO: Remove it and use stakeFor instead - function createValidator(uint256 _validatorId) external; + function epoch() external view returns (uint256); + + function withdrawalDelay() external view returns (uint256); } diff --git a/contracts/interfaces/IValidatorRegistry.sol b/contracts/interfaces/IValidatorRegistry.sol index a08adc0f..cc1efe25 100644 --- a/contracts/interfaces/IValidatorRegistry.sol +++ b/contracts/interfaces/IValidatorRegistry.sol @@ -93,7 +93,7 @@ interface IValidatorRegistry { /// @return Validator id function getValidatorId(uint256 _idx) external view returns (uint256); - /// @notice Returns all the validator addresses joined the MaticX protocol. - /// @return List of validator addresses + /// @notice Returns an array of registered validator ids. + /// @return Array of registered validator ids function getValidators() external view returns (uint256[] memory); } diff --git a/contracts/interfaces/IValidatorShare.sol b/contracts/interfaces/IValidatorShare.sol index 6a709a2d..87f7bb5b 100644 --- a/contracts/interfaces/IValidatorShare.sol +++ b/contracts/interfaces/IValidatorShare.sol @@ -1,58 +1,43 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; -interface IValidatorShare { +import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +interface IValidatorShare is IERC20Upgradeable { struct DelegatorUnbond { uint256 shares; uint256 withdrawEpoch; } - function stakingLogger() external view returns (address); - - function minAmount() external view returns (uint256); - - function unbondNonces(address _address) external view returns (uint256); - - function validatorId() external view returns (uint256); - - function delegation() external view returns (bool); - function buyVoucher( uint256 _amount, uint256 _minSharesToMint - ) external returns (uint256); + ) external returns (uint256 amountToDeposit); function buyVoucherPOL( uint256 _amount, uint256 _minSharesToMint - ) external returns (uint256); - - function sellVoucher_new( - uint256 _claimAmount, - uint256 _maximumSharesToBurn - ) external; + ) external returns (uint256 amountToDeposit); function sellVoucher_newPOL( uint256 _claimAmount, uint256 _maximumSharesToBurn ) external; - function unstakeClaimTokens_new(uint256 _unbondNonce) external; - function unstakeClaimTokens_newPOL(uint256 _unbondNonce) external; - function restake() external returns (uint256, uint256); - - function withdrawRewards() external; - function withdrawRewardsPOL() external; function getTotalStake( address _user ) external view returns (uint256, uint256); + function unbondNonces(address _user) external view returns (uint256); + function unbonds_new( - address _address, + address _user, uint256 _unbondNonce ) external view returns (DelegatorUnbond memory); + + function stakingLogger() external view returns (address); } diff --git a/contracts/mocks/StakeManagerMock.sol b/contracts/mocks/StakeManagerMock.sol index e92b9790..cccbf1c6 100644 --- a/contracts/mocks/StakeManagerMock.sol +++ b/contracts/mocks/StakeManagerMock.sol @@ -1,15 +1,37 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; -import "../interfaces/IStakeManager.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../mocks/ValidatorShareMock.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { ValidatorShareMock } from "./ValidatorShareMock.sol"; + +contract StakeManagerMock { + enum Status { + Inactive, + Active, + Locked, + Unstaked + } + + struct Validator { + uint256 amount; + uint256 reward; + uint256 activationEpoch; + uint256 deactivationEpoch; + uint256 jailTime; + address signer; + address contractAddress; + Status status; + uint256 commissionRate; + uint256 lastCommissionUpdate; + uint256 delegatorsReward; + uint256 delegatedAmount; + uint256 initialRewardPerStake; + } -contract StakeManagerMock is IStakeManager { event UpdateSigner(uint256 validatorId, bytes signerPubkey); event UpdateCommissionRate(uint256 validatorId, uint256 newCommissionRate); - mapping(uint256 => IStakeManager.Validator) smValidators; + mapping(uint256 => Validator) smValidators; struct State { address token; address stakeNFT; @@ -30,12 +52,12 @@ contract StakeManagerMock is IStakeManager { state.stakeNFT = _stakeNFT; } - function unstake(uint256 _validatorId) external override { + function unstake(uint256 _validatorId) external { smValidators[_validatorId].deactivationEpoch = block.timestamp; } - function createValidator(uint256 _validatorId) external override { - smValidators[_validatorId] = IStakeManager.Validator({ + function createValidator(uint256 _validatorId) external { + smValidators[_validatorId] = Validator({ amount: 0, reward: 0, activationEpoch: block.timestamp, @@ -45,7 +67,7 @@ contract StakeManagerMock is IStakeManager { contractAddress: address( new ValidatorShareMock(state.token, address(this), _validatorId) ), - status: IStakeManager.Status.Active, + status: Status.Active, commissionRate: 0, lastCommissionUpdate: 0, delegatorsReward: 0, @@ -57,38 +79,36 @@ contract StakeManagerMock is IStakeManager { ); } - function getValidatorId( - address _user - ) external view override returns (uint256) { + function getValidatorId(address _user) external view returns (uint256) { return state.validators[_user]; } function getValidatorContract( uint256 _validatorId - ) external view override returns (address) { + ) external view returns (address) { return state.validatorShares[_validatorId]; } - function withdrawRewards(uint256) external override { + function withdrawRewards(uint256) external { IERC20(state.token).transfer(msg.sender, 1000); } - function unstakeClaim(uint256 _validatorId) external override { + function unstakeClaim(uint256 _validatorId) external { IERC20(state.token).transfer( msg.sender, IERC20(state.token).balanceOf(address(this)) ); state.delegator2Amount[msg.sender] = 0; - smValidators[_validatorId].status = IStakeManager.Status.Unstaked; + smValidators[_validatorId].status = Status.Unstaked; } function validatorStake( uint256 _validatorId - ) external view override returns (uint256) { + ) external view returns (uint256) { return state.stakedAmount[_validatorId]; } - function withdrawalDelay() external pure override returns (uint256) { + function withdrawalDelay() external pure returns (uint256) { return (2 ** 13); } @@ -96,29 +116,29 @@ contract StakeManagerMock is IStakeManager { uint256 _validatorId, uint256 _amount, address _delegator - ) external override returns (bool) { + ) external returns (bool) { state.delegator2Amount[msg.sender] += _amount; state.stakedAmount[_validatorId] += _amount; IERC20(state.token).transferFrom(_delegator, address(this), _amount); return IERC20(state.token).transfer(msg.sender, _amount); } - function setCurrentEpoch(uint256 _currentEpoch) external override { + function setCurrentEpoch(uint256 _currentEpoch) external { state.epoch = _currentEpoch; } - function epoch() external view override returns (uint256) { + function epoch() external view returns (uint256) { return state.epoch; } function slash(uint256 _validatorId) external { - smValidators[_validatorId].status = IStakeManager.Status.Locked; + smValidators[_validatorId].status = Status.Locked; state.stakedAmount[_validatorId] -= 100; } function validators( uint256 _validatorId - ) external view override returns (Validator memory) { + ) external view returns (Validator memory) { return smValidators[_validatorId]; } @@ -130,7 +150,7 @@ contract StakeManagerMock is IStakeManager { uint256 _fromValidatorId, uint256 _toValidatorId, uint256 _amount - ) public override { + ) public { state.stakedAmount[_fromValidatorId] -= _amount; state.stakedAmount[_toValidatorId] += _amount; } diff --git a/contracts/mocks/ValidatorShareMock.sol b/contracts/mocks/ValidatorShareMock.sol index 773790c3..fb72b5ee 100644 --- a/contracts/mocks/ValidatorShareMock.sol +++ b/contracts/mocks/ValidatorShareMock.sol @@ -1,17 +1,19 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "../interfaces/IValidatorShare.sol"; -import "../interfaces/IStakeManager.sol"; +contract ValidatorShareMock { + struct DelegatorUnbond { + uint256 shares; + uint256 withdrawEpoch; + } -contract ValidatorShareMock is IValidatorShare { uint256 constant REWARD_PRECISION = 10 ** 25; address public token; - bool public override delegation; + bool public delegation; uint256 mAmount; uint256 public rewardPerShare; @@ -19,57 +21,51 @@ contract ValidatorShareMock is IValidatorShare { uint256 public withdrawPool; uint256 public totalStaked; uint256 public totalWithdrawPoolShares; - uint256 public override validatorId; + uint256 public validatorId; mapping(address => mapping(uint256 => uint256)) public user2WithdrawPoolShare; - mapping(address => uint256) public override unbondNonces; + mapping(address => uint256) public unbondNonces; mapping(address => uint256) public initalRewardPerShare; - IStakeManager stakeManager; - address public override stakingLogger; + address stakeManager; + address public stakingLogger; constructor(address _token, address _stakeManager, uint256 _id) { token = _token; - stakeManager = IStakeManager(_stakeManager); + stakeManager = _stakeManager; validatorId = _id; delegation = true; } - function buyVoucher( - uint256 _amount, - uint256 - ) external override returns (uint256) { + function buyVoucher(uint256 _amount, uint256) external returns (uint256) { return _buyVoucher(_amount); } function buyVoucherPOL( uint256 _amount, uint256 - ) external override returns (uint256) { + ) external returns (uint256) { return _buyVoucher(_amount); } - function sellVoucher_new(uint256 _claimAmount, uint256) external override { + function sellVoucher_new(uint256 _claimAmount, uint256) external { _sellVoucher_new(_claimAmount); } - function sellVoucher_newPOL( - uint256 _claimAmount, - uint256 - ) external override { + function sellVoucher_newPOL(uint256 _claimAmount, uint256) external { _sellVoucher_new(_claimAmount); } - function unstakeClaimTokens_new(uint256 _unbondNonce) external override { + function unstakeClaimTokens_new(uint256 _unbondNonce) external { _unstakeClaimTokens_new(_unbondNonce); } - function unstakeClaimTokens_newPOL(uint256 _unbondNonce) external override { + function unstakeClaimTokens_newPOL(uint256 _unbondNonce) external { _unstakeClaimTokens_new(_unbondNonce); } - function restake() external override returns (uint256, uint256) { + function restake() external returns (uint256, uint256) { uint256 liquidRewards = _withdrawReward(msg.sender); // uint256 amountRestaked = buyVoucher(liquidRewards, 0); uint256 amountRestaked = 0; @@ -82,17 +78,15 @@ contract ValidatorShareMock is IValidatorShare { return thisBalance - (totalStaked + withdrawPool); } - function withdrawRewards() external override { + function withdrawRewards() external { _withdrawReward(msg.sender); } - function withdrawRewardsPOL() external override { + function withdrawRewardsPOL() external { _withdrawReward(msg.sender); } - function getTotalStake( - address - ) external view override returns (uint256, uint256) { + function getTotalStake(address) external view returns (uint256, uint256) { //getTotalStake returns totalStake of msg.sender but we need withdrawPool return (totalStaked, 1); } @@ -101,7 +95,7 @@ contract ValidatorShareMock is IValidatorShare { mAmount = _minAmount; } - function minAmount() public view override returns (uint256) { + function minAmount() public view returns (uint256) { return mAmount; } @@ -116,7 +110,7 @@ contract ValidatorShareMock is IValidatorShare { function unbonds_new( address _address, uint256 _unbondNonce - ) external view override returns (DelegatorUnbond memory) { + ) external view returns (DelegatorUnbond memory) { DelegatorUnbond memory unbond = DelegatorUnbond( user2WithdrawPoolShare[_address][_unbondNonce], 2 @@ -133,10 +127,6 @@ contract ValidatorShareMock is IValidatorShare { totalShares += shares; totalStaked += _amount; - require( - stakeManager.delegationDeposit(validatorId, _amount, msg.sender), - "deposit failed" - ); return 1; } diff --git a/test/MaticX.old.spec.ts b/test/MaticX.old.spec.ts deleted file mode 100644 index 3f3afca6..00000000 --- a/test/MaticX.old.spec.ts +++ /dev/null @@ -1,676 +0,0 @@ -import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; -import { reset } from "@nomicfoundation/hardhat-network-helpers"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { expect } from "chai"; -import { Transaction } from "ethers"; -import { ethers, upgrades } from "hardhat"; -import { - MaticX, - PolygonMock, - ValidatorRegistry, - StakeManagerMock, - FxRootMock, - FxStateRootTunnel, - FxStateChildTunnel, - RateProvider, -} from "../typechain-types"; - -describe("MaticX (Old)", function () { - let deployer: SignerWithAddress; - let manager: SignerWithAddress; - let instantPoolOwner: SignerWithAddress; - let treasury: SignerWithAddress; - let users: SignerWithAddress[] = []; - let maticX: MaticX; - let maticMock: PolygonMock; - let polMock: PolygonMock; - let validatorRegistry: ValidatorRegistry; - let stakeManagerMock: StakeManagerMock; - let fxRootMock: FxRootMock; - let fxStateRootTunnel: FxStateRootTunnel; - let fxStateChildTunnel: FxStateChildTunnel; - let rateProvider: RateProvider; - - let mintMatic: ( - signer: SignerWithAddress, - amount: BigNumberish - ) => Promise; - let approveMatic: ( - signer: SignerWithAddress, - amount: BigNumberish - ) => Promise; - let submitWithoutApprove: ( - signer: SignerWithAddress, - amount: BigNumberish - ) => Promise; - let submit: ( - signer: SignerWithAddress, - amount: BigNumberish - ) => Promise; - let requestWithdraw: ( - signer: SignerWithAddress, - amount: BigNumberish - ) => Promise; - let migrateDelegation: ( - signer: SignerWithAddress, - fromValidatorId: BigNumberish, - toValidatorId: BigNumberish, - amount: BigNumberish - ) => Promise; - let stakeRewardsAndDistributeFees: ( - signer: SignerWithAddress, - validatorId: BigNumberish - ) => Promise; - let setFeePercent: ( - signer: SignerWithAddress, - feePercent: BigNumberish - ) => Promise; - let mintAndTransferMatic: ( - signer: SignerWithAddress, - amount: BigNumber, - to: string - ) => Promise; - - before(() => { - mintMatic = async (signer, amount) => { - const signerERC = maticMock.connect(signer); - await signerERC.mint(amount); - }; - - approveMatic = async (signer, amount) => { - const signerERC20 = maticMock.connect(signer); - await signerERC20.approve(maticX.address, amount); - }; - - mintAndTransferMatic = async (signer, amount, to) => { - const signerERC = maticMock.connect(signer); - await signerERC.mint(amount); - await signerERC.approve(to, amount); - return await signerERC.transfer(to, amount); - }; - - submitWithoutApprove = async (signer, amount) => { - const signerMaticX = maticX.connect(signer); - await signerMaticX.submit(amount); - }; - - submit = async (signer, amount) => { - await approveMatic(signer, amount); - - const signerMaticX = maticX.connect(signer); - return signerMaticX.submit(amount); - }; - - requestWithdraw = async (signer, amount) => { - const signerMaticX = maticX.connect(signer); - await signerMaticX.approve(maticX.address, amount); - return signerMaticX.requestWithdraw(amount); - }; - - migrateDelegation = async ( - signer, - fromValidatorId, - toValidatorId, - amount - ) => { - const signerMaticX = maticX.connect(signer); - return await signerMaticX.migrateDelegation( - fromValidatorId, - toValidatorId, - amount - ); - }; - - stakeRewardsAndDistributeFees = async (signer, validatorId) => { - const signerMaticX = maticX.connect(signer); - return signerMaticX.stakeRewardsAndDistributeFees(validatorId); - }; - - setFeePercent = async (signer, feePercent) => { - const signerMaticX = maticX.connect(signer); - return signerMaticX.setFeePercent(feePercent); - }; - }); - - beforeEach(async () => { - await reset(); - - [deployer, ...users] = await ethers.getSigners(); - manager = deployer; - treasury = users[1]; - instantPoolOwner = deployer; - - maticMock = (await ( - await ethers.getContractFactory("PolygonMock") - ).deploy()) as PolygonMock; - await maticMock.deployed(); - - polMock = (await ( - await ethers.getContractFactory("PolygonMock") - ).deploy()) as PolygonMock; - await polMock.deployed(); - - fxRootMock = (await ( - await ethers.getContractFactory("FxRootMock") - ).deploy()) as FxRootMock; - await fxRootMock.deployed(); - - fxStateChildTunnel = (await ( - await ethers.getContractFactory("FxStateChildTunnel") - ).deploy(fxRootMock.address)) as FxStateChildTunnel; - await fxStateChildTunnel.deployed(); - - fxStateRootTunnel = (await ( - await ethers.getContractFactory("FxStateRootTunnel") - ).deploy( - manager.address, - fxRootMock.address, - manager.address - )) as FxStateRootTunnel; - await fxStateRootTunnel.deployed(); - - rateProvider = (await ( - await ethers.getContractFactory("RateProvider") - ).deploy(fxStateChildTunnel.address)) as RateProvider; - await rateProvider.deployed(); - - stakeManagerMock = (await ( - await ethers.getContractFactory("StakeManagerMock") - ).deploy(maticMock.address, maticMock.address)) as StakeManagerMock; - await stakeManagerMock.deployed(); - - validatorRegistry = (await upgrades.deployProxy( - await ethers.getContractFactory("ValidatorRegistry"), - [ - stakeManagerMock.address, - maticMock.address, - ethers.constants.AddressZero, - manager.address, - ] - )) as ValidatorRegistry; - await validatorRegistry.deployed(); - - maticX = (await upgrades.deployProxy( - await ethers.getContractFactory("MaticX"), - [ - validatorRegistry.address, - stakeManagerMock.address, - maticMock.address, - manager.address, - treasury.address, - ] - )) as MaticX; - await maticX.deployed(); - - await validatorRegistry.setMaticX(maticX.address); - await stakeManagerMock.createValidator(1); - await validatorRegistry.addValidator(1); - await validatorRegistry.grantRole( - await validatorRegistry.BOT(), - manager.address - ); - await validatorRegistry.setPreferredDepositValidatorId(1); - await validatorRegistry.setPreferredWithdrawalValidatorId(1); - await stakeManagerMock.createValidator(2); - await validatorRegistry.addValidator(2); - await maticX.setFxStateRootTunnel(fxStateRootTunnel.address); - await fxStateRootTunnel.setMaticX(maticX.address); - await fxStateRootTunnel.setFxChildTunnel(fxStateChildTunnel.address); - await fxStateChildTunnel.setFxRootTunnel(fxStateRootTunnel.address); - await maticX.grantRole(await maticX.BOT(), instantPoolOwner.address); - await maticX.initializeV2(polMock.address); - }); - - it.skip("Should submit successfully", async () => { - const totalAmount = ethers.utils.parseEther("1"); - const user = users[0]; - - await mintMatic(user, totalAmount); - - const approveAmount = ethers.utils.parseEther("0.4"); - // Approve & Submit individually 0.4 - await approveMatic(user, approveAmount); - await submitWithoutApprove(user, approveAmount); - - let userBalance = await maticX.balanceOf(user.address); - expect(userBalance).to.equal(approveAmount); - - // Approve & Submit individually 0.6 - const remainingAmount = ethers.utils.parseEther("0.6"); - const submitTx = await submit(user, remainingAmount); - await expect(submitTx) - .emit(maticX, "Submit") - .withArgs(user.address, remainingAmount); - await expect(submitTx) - .emit(maticX, "Delegate") - .withArgs(1, remainingAmount); - - userBalance = await maticX.balanceOf(user.address); - expect(userBalance).to.equal(totalAmount); - }); - - it.skip("fails when submit amount is greater than signer balance", async () => { - const user = users[0]; - let userMaticXBalance = await maticX.balanceOf(user.address); - expect(userMaticXBalance).to.equal(0); - - const amount = ethers.utils.parseEther("1"); - await mintMatic(user, amount); - - await expect(submitWithoutApprove(user, amount)).to.be.revertedWith( - "ERC20: insufficient allowance" - ); - - await expect( - submit(user, ethers.utils.parseEther("2")) - ).to.be.revertedWith("ERC20: transfer amount exceeds balance"); - - userMaticXBalance = await maticX.balanceOf(user.address); - expect(userMaticXBalance).to.equal(0); - }); - - it.skip("Should request withdraw from the contract successfully", async () => { - const amount = ethers.utils.parseEther("1"); - const user = users[0]; - - await mintMatic(user, amount); - - const submitTx = await submit(user, amount); - await expect(submitTx) - .emit(maticX, "Submit") - .withArgs(user.address, amount); - await expect(submitTx).emit(maticX, "Delegate").withArgs(1, amount); - - await expect(await requestWithdraw(user, amount)) - .emit(maticX, "RequestWithdraw") - .withArgs(user.address, amount, amount); - - const userBalance = await maticX.balanceOf(user.address); - expect(userBalance).to.equal(0); - }); - - it.skip("WithdrawalRequest should have correct share amount", async () => { - const expectedAmount = ethers.utils.parseEther("1"); - const user = users[0]; - - await mintMatic(user, expectedAmount); - - const submitTx = await submit(user, expectedAmount); - await expect(submitTx) - .emit(maticX, "Submit") - .withArgs(user.address, expectedAmount); - await expect(submitTx) - .emit(maticX, "Delegate") - .withArgs(1, expectedAmount); - - await expect(await requestWithdraw(user, expectedAmount)) - .emit(maticX, "RequestWithdraw") - .withArgs(user.address, expectedAmount, expectedAmount); - - const amount = await maticX.getSharesAmountOfUserWithdrawalRequest( - user.address, - 0 - ); - expect(expectedAmount).to.equal(amount); - }); - - it("Should claim withdrawals after submitting to contract successfully", async () => { - const submitAmounts: string[] = []; - const withdrawAmounts: string[] = []; - - const [minAmount, maxAmount] = [0.005, 0.01]; - const delegatorsAmount = Math.floor(Math.random() * (10 - 1)) + 1; - - for (let i = 0; i < delegatorsAmount; i++) { - submitAmounts.push( - (Math.random() * (maxAmount - minAmount) + minAmount).toFixed(3) - ); - const submitAmountWei = ethers.utils.parseEther(submitAmounts[i]); - - await mintMatic(users[i], submitAmountWei); - - const submitTx = submit(users[i], submitAmountWei); - await expect(submitTx) - .emit(maticX, "Submit") - .withArgs(users[i].address, submitAmountWei); - await expect(submitTx) - .emit(maticX, "Delegate") - .withArgs(1, submitAmountWei); - } - - await stakeManagerMock.setEpoch(1); - - for (let i = 0; i < delegatorsAmount; i++) { - withdrawAmounts.push( - ( - Math.random() * (Number(submitAmounts[i]) - minAmount) + - minAmount - ).toFixed(3) - ); - const withdrawAmountWei = ethers.utils.parseEther( - withdrawAmounts[i] - ); - - await expect(await requestWithdraw(users[i], withdrawAmountWei)) - .emit(maticX, "RequestWithdraw") - .withArgs( - users[i].address, - withdrawAmountWei, - withdrawAmountWei - ); - } - - const withdrawalDelay = await stakeManagerMock.withdrawalDelay(); - const currentEpoch = await stakeManagerMock.epoch(); - - await stakeManagerMock.setEpoch(withdrawalDelay.add(currentEpoch)); - - // for (let i = 0; i < delegatorsAmount; i++) { - // await expect(await claimWithdrawal(users[i], 0)) - // .emit(maticX, "ClaimWithdrawal") - // .withArgs( - // users[i].address, - // 0, - // ethers.utils.parseEther(withdrawAmounts[i]) - // ); - - // const balanceAfter = await maticMock.balanceOf(users[i].address); - - // expect(balanceAfter).to.equal( - // ethers.utils.parseEther(withdrawAmounts[i]) - // ); - // } - }); - - it.skip("Should stake rewards to a validator successfully without using instant pool matic", async () => { - const submitAmounts: string[] = []; - - const [minAmount, maxAmount] = [0.005, 0.01]; - const delegatorsAmount = Math.floor(Math.random() * (10 - 1)) + 1; - - for (let i = 0; i < delegatorsAmount; i++) { - submitAmounts.push( - (Math.random() * (maxAmount - minAmount) + minAmount).toFixed(3) - ); - const submitAmountWei = ethers.utils.parseEther(submitAmounts[i]); - await mintMatic(users[i], submitAmountWei); - await submit(users[i], submitAmountWei); - } - - const instantPoolMatic = ethers.utils.parseEther("10"); - await mintMatic(deployer, instantPoolMatic); - await approveMatic(deployer, instantPoolMatic); - // await provideInstantPoolMatic(deployer, instantPoolMatic); - expect(await maticMock.balanceOf(maticX.address)).to.equal( - instantPoolMatic - ); - - await expect(await setFeePercent(manager, 10)) - .emit(maticX, "SetFeePercent") - .withArgs(10); - const rewards = 1000000; - const feePercent = await maticX.feePercent(); - const treasuryFee = (rewards * feePercent) / 100; - const stakedAmount = rewards - treasuryFee; - await maticMock.mintTo(maticX.address, rewards); - - const stakeRewardsAndDistributeFeesTx = - await stakeRewardsAndDistributeFees(manager, 1); - await expect(stakeRewardsAndDistributeFeesTx) - .emit(maticX, "StakeRewards") - .withArgs(1, stakedAmount); - await expect(stakeRewardsAndDistributeFeesTx) - .emit(maticX, "DistributeFees") - .withArgs(treasury.address, treasuryFee); - - expect(await maticMock.balanceOf(maticX.address)).to.equal( - instantPoolMatic - ); - }); - - it("Should migrate validator stake to another validator successfully", async () => { - const user = users[0]; - - await mintMatic(user, 100); - await submit(user, 100); - - await stakeManagerMock.createValidator(123); - await validatorRegistry.addValidator(123); - - await expect(await migrateDelegation(manager, 1, 123, 100)) - .emit(maticX, "MigrateDelegation") - .withArgs(1, 123, 100); - }); - - it.skip("Should send correct message from L1 to L2", async () => { - const user = users[0]; - const mintAmount = 1000000; - const withdrawAmount = 400000; - const rewardsAmount = 300000; - - await mintMatic(user, mintAmount); - // Submitting twice to skip the edge case that occurs when there is only 0 deposit at the beginning - await submit(user, 1000000 - 200000); - await submit(user, 1000000 - 800000); - - const [totalSharesAfterDeposit, totalPooledMaticAfterDeposit] = - await fxStateChildTunnel.getReserves(); - const rateAfterDeposit = await rateProvider.getRate(); - expect(totalSharesAfterDeposit).to.equal(mintAmount); - expect(totalPooledMaticAfterDeposit).to.equal(mintAmount); - const expectedRateAfterDeposit: BigNumberish = BigNumber.from( - "1000000000000000000" - ) - .mul(totalPooledMaticAfterDeposit) - .div(totalSharesAfterDeposit); - expect(rateAfterDeposit).to.equal(expectedRateAfterDeposit); - - await requestWithdraw(user, withdrawAmount); - - const [totalSharesAfterWithdraw, totalPooledMaticAfterWithdraw] = - await fxStateChildTunnel.getReserves(); - const rateAfterWithdraw = await rateProvider.getRate(); - expect(totalSharesAfterWithdraw).to.equal(mintAmount - withdrawAmount); - expect(totalPooledMaticAfterWithdraw).to.equal( - mintAmount - withdrawAmount - ); - const expectedRateAfterWithdraw: BigNumberish = BigNumber.from( - "1000000000000000000" - ) - .mul(totalPooledMaticAfterWithdraw) - .div(totalSharesAfterWithdraw); - expect(rateAfterWithdraw).to.equal(expectedRateAfterWithdraw); - - await maticMock.mintTo(maticX.address, rewardsAmount); - await stakeRewardsAndDistributeFees(manager, 1); - - const rewardsAfterFee = - (rewardsAmount * (100 - (await maticX.feePercent()))) / 100; - const [totalSharesAfterRewards, totalPooledMaticAfterRewards] = - await fxStateChildTunnel.getReserves(); - const rateAfterRewards = await rateProvider.getRate(); - expect(totalSharesAfterRewards).to.equal(mintAmount - withdrawAmount); - expect(totalPooledMaticAfterRewards).to.equal( - mintAmount - withdrawAmount + rewardsAfterFee - ); - const expectedRateAfterRewards: BigNumberish = BigNumber.from( - "1000000000000000000" - ) - .mul(totalPooledMaticAfterRewards) - .div(totalSharesAfterRewards); - expect(rateAfterRewards).to.equal(expectedRateAfterRewards); - - await requestWithdraw(user, totalSharesAfterRewards); - - const [totalSharesAfterWithdrawAll, totalPooledMaticAfterWithdrawAll] = - await fxStateChildTunnel.getReserves(); - const rateAfterWithdrawAll = await rateProvider.getRate(); - expect(totalSharesAfterWithdrawAll).to.equal(0); - expect(totalPooledMaticAfterWithdrawAll).to.equal(0); - expect(rateAfterWithdrawAll).to.equal(ethers.utils.parseEther("1")); - }); - - it("it should add and then remove a bot address", async () => { - const botRole = await maticX.BOT(); - expect(await maticX.hasRole(botRole, users[0].address)).to.eql(false); - const tx = await maticX.grantRole(botRole, users[0].address); - await expect(tx) - .to.emit(maticX, "RoleGranted") - .withArgs(botRole, users[0].address, instantPoolOwner.address); - expect(await maticX.hasRole(botRole, users[0].address)).to.eql(true); - - const tx2 = await maticX.revokeRole(botRole, users[0].address); - await expect(tx2) - .to.emit(maticX, "RoleRevoked") - .withArgs(botRole, users[0].address, instantPoolOwner.address); - expect(await maticX.hasRole(botRole, users[0].address)).to.eql(false); - }); - - it.skip("it should stakeRewards - accesscontrol check", async () => { - const botRole = await maticX.BOT(); - await maticX.grantRole(botRole, users[1].address); - - const submitAmounts: string[] = []; - const [minAmount, maxAmount] = [0.005, 0.01]; - const delegatorsAmount = Math.floor(Math.random() * (10 - 1)) + 1; - for (let i = 0; i < delegatorsAmount; i++) { - submitAmounts.push( - (Math.random() * (maxAmount - minAmount) + minAmount).toFixed(3) - ); - const submitAmountWei = ethers.utils.parseEther(submitAmounts[i]); - await mintMatic(users[i], submitAmountWei); - await submit(users[i], submitAmountWei); - } - const instantPoolMatic = ethers.utils.parseEther("10"); - await mintMatic(deployer, instantPoolMatic); - await approveMatic(deployer, instantPoolMatic); - // await provideInstantPoolMatic(deployer, instantPoolMatic); - const rewards = 1000000; - const feePercent = await maticX.feePercent(); - const treasuryFee = (rewards * feePercent) / 100; - const stakedAmount = rewards - treasuryFee; - await maticMock.mintTo(maticX.address, rewards); - - // fails for non-bot - await expect(stakeRewardsAndDistributeFees(users[0], 1)).to.be.reverted; - - // succeeds for bot - const stakeRewardsAndDistributeFeesTx = - await stakeRewardsAndDistributeFees(users[1], 1); - await expect(stakeRewardsAndDistributeFeesTx) - .emit(maticX, "StakeRewards") - .withArgs(1, stakedAmount); - await expect(stakeRewardsAndDistributeFeesTx) - .emit(maticX, "DistributeFees") - .withArgs(treasury.address, treasuryFee); - }); - - it.skip("should call the withdraw rewards on multiple validators", async () => { - const submitAmounts: string[] = []; - - const [minAmount, maxAmount] = [0.005, 0.01]; - const delegatorsAmount = Math.floor(Math.random() * (10 - 1)) + 1; - - for (let i = 0; i < delegatorsAmount; i++) { - submitAmounts.push( - (Math.random() * (maxAmount - minAmount) + minAmount).toFixed(3) - ); - const submitAmountWei = ethers.utils.parseEther(submitAmounts[i]); - await mintMatic(users[i], submitAmountWei); - await submit(users[i], submitAmountWei); - } - - const validatorsAddress = []; - validatorsAddress[0] = await stakeManagerMock.getValidatorContract( - BigNumber.from(1) - ); - validatorsAddress[1] = await stakeManagerMock.getValidatorContract( - BigNumber.from(2) - ); - await mintAndTransferMatic( - deployer, - ethers.utils.parseEther("15"), - validatorsAddress[0] - ); - await mintAndTransferMatic( - deployer, - ethers.utils.parseEther("17"), - validatorsAddress[1] - ); - /* - const iFace = new ethers.utils.Interface([ - "event WithdrawRewards(uint256 indexed _validatorId, uint256 _rewards)", - ]); - const tx1 = await maticX.withdrawRewards(BigNumber.from(1)); - const receipt = await ethers.provider.getTransactionReceipt(tx1.hash); - console.log( - iFace.decodeEventLog( - "WithdrawRewards", - receipt.logs[1].data, - receipt.logs[1].topics - ) - );*/ - - const oldBalance = await maticMock.balanceOf(maticX.address); - const tx = await maticX.withdrawValidatorsReward([ - BigNumber.from(1), - BigNumber.from(2), - ]); - - await expect(tx) - .to.emit(maticX, "WithdrawRewards") - .withArgs(BigNumber.from(1), ethers.utils.parseEther("15")); - await expect(tx) - .to.emit(maticX, "WithdrawRewards") - .withArgs(BigNumber.from(2), ethers.utils.parseEther("17")); - const newBalance = await maticMock.balanceOf(maticX.address); - expect(newBalance.sub(oldBalance)).to.eql( - ethers.utils.parseEther("32") - ); - }); - - it("should call the withdraw rewards on multiple validators - wrong validator Id", async () => { - const submitAmounts: string[] = []; - - const [minAmount, maxAmount] = [0.005, 0.01]; - const delegatorsAmount = Math.floor(Math.random() * (10 - 1)) + 1; - - for (let i = 0; i < delegatorsAmount; i++) { - submitAmounts.push( - (Math.random() * (maxAmount - minAmount) + minAmount).toFixed(3) - ); - const submitAmountWei = ethers.utils.parseEther(submitAmounts[i]); - await mintMatic(users[i], submitAmountWei); - await submit(users[i], submitAmountWei); - } - - const validatorsAddress = []; - validatorsAddress[0] = await stakeManagerMock.getValidatorContract( - BigNumber.from(1) - ); - validatorsAddress[1] = await stakeManagerMock.getValidatorContract( - BigNumber.from(2) - ); - await mintAndTransferMatic( - deployer, - ethers.utils.parseEther("15"), - validatorsAddress[0] - ); - await mintAndTransferMatic( - deployer, - ethers.utils.parseEther("17"), - validatorsAddress[1] - ); - - const oldBalance = await maticMock.balanceOf(maticX.address); - const tx = maticX.withdrawValidatorsReward([ - BigNumber.from(1), - BigNumber.from(4), - ]); - await expect(tx).to.be.revertedWith( - "function call to a non-contract account" - ); - const newBalance = await maticMock.balanceOf(maticX.address); - expect(newBalance.sub(oldBalance)).to.eql(BigNumber.from(0)); - }); -}); diff --git a/test/MaticX.spec.ts b/test/MaticX.spec.ts index 74851059..eebc60c6 100644 --- a/test/MaticX.spec.ts +++ b/test/MaticX.spec.ts @@ -38,6 +38,9 @@ describe("MaticX", function () { const stakeManagerGovernance = await impersonateAccount( "0x6e7a5820baD6cebA8Ef5ea69c0C92EbbDAc9CE48" ); + const validatorShareHolder = await impersonateAccount( + "0x9789FD6bCDD7077cF52FFDD4f2483513C557cd41" + ); const [executor, bot, treasury, stakerA, stakerB] = await ethers.getSigners(); @@ -150,6 +153,7 @@ describe("MaticX", function () { stakerA, stakerB, stakers, + validatorShareHolder, defaultAdminRole, botRole, preferredDepositValidatorId, @@ -286,6 +290,14 @@ describe("MaticX", function () { expect(hasRole).to.be.true; }); + it("Should return the default admin role set for the bot", async function () { + const { maticX, bot, botRole } = + await loadFixture(deployFixture); + + const hasRole = await maticX.hasRole(botRole, bot.address); + expect(hasRole).to.be.true; + }); + it("Should return the right paused status", async function () { const { maticX } = await loadFixture(deployFixture); @@ -1417,59 +1429,7 @@ describe("MaticX", function () { ); }); - it("Should emit the RequestWithraw event if having an extra amount of MaticX shares received", async function () { - const { maticX, pol, stakerA, stakerB, stakers } = - await loadFixture(deployFixture); - - for (const staker of stakers) { - await pol - .connect(staker) - .approve(maticX.address, stakeAmount); - await maticX.connect(staker).submitPOL(stakeAmount); - } - - await maticX - .connect(stakerB) - .transfer(stakerA.address, stakeAmount); - const doubleStakeAmount = stakeAmount.mul(2); - - const promise = maticX - .connect(stakerA) - .requestWithdraw(doubleStakeAmount); - await expect(promise) - .to.emit(maticX, "RequestWithdraw") - .withArgs( - stakerA.address, - doubleStakeAmount, - doubleStakeAmount - ); - }); - - it("Should emit the RequestWithraw event if having a preferred withdrawal validator id changed", async function () { - const { - maticX, - pol, - validatorRegistry, - stakerA, - preferredDepositValidatorId, - } = await loadFixture(deployFixture); - - await pol.connect(stakerA).approve(maticX.address, stakeAmount); - await maticX.connect(stakerA).submitPOL(stakeAmount); - - await validatorRegistry.setPreferredWithdrawalValidatorId( - preferredDepositValidatorId - ); - - const promise = maticX - .connect(stakerA) - .requestWithdraw(stakeAmount); - await expect(promise) - .to.emit(maticX, "RequestWithdraw") - .withArgs(stakerA.address, stakeAmount, stakeAmount); - }); - - it("Should return the right MaticX and POL token balances if submitting POL", async function () { + it("Should return the right MaticX and POL token balances if having POL tokens submitted", async function () { const { maticX, pol, stakerA } = await loadFixture(deployFixture); @@ -1487,8 +1447,8 @@ describe("MaticX", function () { await expect(promise).to.changeTokenBalance(pol, stakerA, 0); }); - it("Should return the right MaticX and POL token balances if submitting Matic", async function () { - const { maticX, pol, matic, stakerA } = + it("Should return the right MaticX balances if having Matic tokens submitted", async function () { + const { maticX, matic, stakerA } = await loadFixture(deployFixture); await matic @@ -1504,8 +1464,6 @@ describe("MaticX", function () { stakerA, stakeAmount.mul(-1) ); - await expect(promise).to.changeTokenBalance(pol, stakerA, 0); - await expect(promise).to.changeTokenBalance(matic, stakerA, 0); }); it("Should return the right staker's withdrawal requests", async function () { @@ -1826,10 +1784,55 @@ describe("MaticX", function () { "Too small rewards amount" ); }); + + it("Should revert with the right error if passing an non existing validator id", async function () { + const { maticX, manager } = await loadFixture(deployFixture); + + const promise = maticX.connect(manager).withdrawRewards(1_000); + await expect(promise).to.be.revertedWith( + "function call to a non-contract account" + ); + }); }); describe("Positive", function () { - // TODO Add tests + it.skip("Should emit the WithdrawRewards event", async function () { + const { + maticX, + stakeManager, + manager, + validatorShareHolder, + preferredDepositValidatorId, + } = await loadFixture(deployFixture); + + const validatorShareAddress = + await stakeManager.getValidatorContract( + preferredDepositValidatorId + ); + + const validatorShare = (await ethers.getContractAt( + "IValidatorShare", + validatorShareAddress + )) as IValidatorShare; + + const validatorShareHolderBalance = + await validatorShare.balanceOf( + validatorShareHolder.address + ); + await validatorShare + .connect(validatorShareHolder) + .transfer(maticX.address, validatorShareHolderBalance); + + const promise = maticX + .connect(manager) + .withdrawRewards(preferredDepositValidatorId); + await expect(promise) + .to.emit(maticX, "WithdrawRewards") + .withArgs( + preferredDepositValidatorId, + validatorShareHolderBalance + ); + }); }); }); @@ -1872,10 +1875,61 @@ describe("MaticX", function () { "Too small rewards amount" ); }); + + it("Should revert with the right error if passing an non existing validator id", async function () { + const { maticX, manager, preferredDepositValidatorId } = + await loadFixture(deployFixture); + + const promise = maticX + .connect(manager) + .withdrawValidatorsReward([ + 1_000, + preferredDepositValidatorId, + ]); + await expect(promise).to.be.revertedWith( + "function call to a non-contract account" + ); + }); }); describe("Positive", function () { - // TODO Add tests + it.skip("Should emit the WithdrawRewards event", async function () { + const { + maticX, + stakeManager, + manager, + validatorShareHolder, + preferredDepositValidatorId, + } = await loadFixture(deployFixture); + + const validatorShareAddress = + await stakeManager.getValidatorContract( + preferredDepositValidatorId + ); + + const validatorShare = (await ethers.getContractAt( + "IValidatorShare", + validatorShareAddress + )) as IValidatorShare; + + const validatorShareHolderBalance = + await validatorShare.balanceOf( + validatorShareHolder.address + ); + await validatorShare + .connect(validatorShareHolder) + .transfer(maticX.address, validatorShareHolderBalance); + + const promise = maticX + .connect(manager) + .withdrawValidatorsReward([preferredDepositValidatorId]); + await expect(promise) + .to.emit(maticX, "WithdrawRewards") + .withArgs( + preferredDepositValidatorId, + validatorShareHolderBalance + ); + }); }); }); @@ -2108,7 +2162,7 @@ describe("MaticX", function () { await expect(promise).to.be.revertedWith("Amount is zero"); }); - it("Should revert with the right error if migrating a too much amount", async function () { + it("Should revert with the right error if having the zero delegated amount", async function () { const { maticX, manager, @@ -2128,7 +2182,102 @@ describe("MaticX", function () { }); describe("Positive", function () { - // TODO Add tests + it("Should emit the MigrateDelegation event", async function () { + const { + maticX, + pol, + manager, + stakerA, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const promise = maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + stakeAmount + ); + await expect(promise).to.emit(maticX, "MigrateDelegation"); + }); + + it("Should return the right total stake of the from validator", async function () { + const { + maticX, + stakeManager, + pol, + manager, + stakerA, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const fromValidatorShareAddress = + await stakeManager.getValidatorContract( + preferredDepositValidatorId + ); + const [initialTotalStake] = await maticX.getTotalStake( + fromValidatorShareAddress + ); + + await maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + stakeAmount + ); + + const [currentTotalStake] = await maticX.getTotalStake( + fromValidatorShareAddress + ); + expect(currentTotalStake).not.to.equal(initialTotalStake); + expect(currentTotalStake).to.equal(0); + }); + + it("Should return the right total stake of the to validator", async function () { + const { + maticX, + stakeManager, + pol, + manager, + stakerA, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const toValidatorShareAddress = + await stakeManager.getValidatorContract( + preferredWithdrawalValidatorId + ); + const [initialTotalStake] = await maticX.getTotalStake( + toValidatorShareAddress + ); + + await maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + stakeAmount + ); + + const [currentTotalStake] = await maticX.getTotalStake( + toValidatorShareAddress + ); + expect(currentTotalStake).not.to.equal(initialTotalStake); + expect(currentTotalStake).to.equal(stakeAmount); + }); }); });