diff --git a/contracts/MaticX.sol b/contracts/MaticX.sol index 9e83b174..de400596 100644 --- a/contracts/MaticX.sol +++ b/contracts/MaticX.sol @@ -412,6 +412,7 @@ contract MaticX is uint256 _toValidatorId, uint256 _amount ) external override whenNotPaused onlyRole(DEFAULT_ADMIN_ROLE) { + require(_amount > 0, "Amount is zero"); require( IValidatorRegistry(validatorRegistry).validatorIdExists( _fromValidatorId @@ -590,9 +591,11 @@ contract MaticX is override returns (uint256) { - uint256 totalStake; uint256[] memory validators = IValidatorRegistry(validatorRegistry) .getValidators(); + + uint256 totalStake; + for (uint256 i = 0; i < validators.length; ++i) { address validatorShare = IStakeManager(stakeManager) .getValidatorContract(validators[i]); diff --git a/test/MaticX.forking.spec.ts b/test/MaticX.forking.spec.ts index 1070beab..189474d9 100644 --- a/test/MaticX.forking.spec.ts +++ b/test/MaticX.forking.spec.ts @@ -37,8 +37,8 @@ describe("MaticX (Forking)", function () { const stakeManagerGovernance = await impersonateAccount( "0x6e7a5820baD6cebA8Ef5ea69c0C92EbbDAc9CE48" ); - const treasury = manager; - const [executor, bot, stakerA, stakerB, polHolder] = + + const [executor, bot, treasury, stakerA, stakerB, polHolder] = await ethers.getSigners(); const stakers = [stakerA, stakerB]; @@ -91,7 +91,7 @@ describe("MaticX (Forking)", function () { preferredDepositValidatorId ); - const preferredWithdrawalValidatorId = validatorIds[0]; + const preferredWithdrawalValidatorId = validatorIds[1]; validatorRegistry.setPreferredWithdrawalValidatorId( preferredWithdrawalValidatorId ); @@ -864,7 +864,7 @@ describe("MaticX (Forking)", function () { ); }); - it("Should the right Matic and POL token balances", async function () { + it("Should return the right Matic and POL token balances", async function () { const { maticX, stakeManager, matic, pol, stakerA } = await loadFixture(deployFixture); @@ -885,6 +885,90 @@ describe("MaticX (Forking)", function () { ); }); + it("Should return the right MaticX to POL conversion", async function () { + const { maticX, matic, stakerA } = + await loadFixture(deployFixture); + + await matic + .connect(stakerA) + .approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticXToPOL(stakeAmount); + + await maticX.connect(stakerA).submit(stakeAmount); + + const currentConversion = + await maticX.convertMaticXToPOL(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right MaticX to POL conversion in a backward compatible manner", async function () { + const { maticX, matic, stakerA } = + await loadFixture(deployFixture); + + await matic + .connect(stakerA) + .approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticXToMatic(stakeAmount); + + await maticX.connect(stakerA).submit(stakeAmount); + + const currentConversion = + await maticX.convertMaticXToMatic(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right POL to MaticX conversion", async function () { + const { maticX, matic, stakerA } = + await loadFixture(deployFixture); + + await matic + .connect(stakerA) + .approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertPOLToMaticX(stakeAmount); + + await maticX.connect(stakerA).submit(stakeAmount); + + const currentConversion = + await maticX.convertPOLToMaticX(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right POL to MaticX conversion in a backward compatible manner", async function () { + const { maticX, matic, stakerA } = + await loadFixture(deployFixture); + + await matic + .connect(stakerA) + .approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticToMaticX(stakeAmount); + + await maticX.connect(stakerA).submit(stakeAmount); + + const currentConversion = + await maticX.convertMaticToMaticX(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + it("Should return the right total pooled stake tokens", async function () { const { maticX, matic, stakers } = await loadFixture(deployFixture); @@ -906,6 +990,27 @@ describe("MaticX (Forking)", function () { ); }); + it("Should return the right total pooled stake tokens in a backward compatible manner", async function () { + const { maticX, matic, stakers } = + await loadFixture(deployFixture); + + for (const staker of stakers) { + await matic + .connect(staker) + .approve(maticX.address, tripleStakeAmount); + + for (let i = 0; i < 3; i++) { + await maticX.connect(staker).submit(stakeAmount); + } + } + + const totalPooledStakeTokens = + await maticX.getTotalPooledMatic(); + expect(totalPooledStakeTokens).to.equal( + tripleStakeAmount.mul(2) + ); + }); + it("Should return the right total stake from a validator share", async function () { const { maticX, @@ -1059,7 +1164,7 @@ describe("MaticX (Forking)", function () { ); }); - it("Should the right POL token balances", async function () { + it("Should return the right POL token balances", async function () { const { maticX, stakeManager, pol, stakerA } = await loadFixture(deployFixture); @@ -1073,6 +1178,82 @@ describe("MaticX (Forking)", function () { ); }); + it("Should return the right MaticX to POL conversion", async function () { + const { maticX, pol, stakerA } = + await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticXToPOL(stakeAmount); + + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const currentConversion = + await maticX.convertMaticXToPOL(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right MaticX to POL conversion in a backward compatible manner", async function () { + const { maticX, pol, stakerA } = + await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticXToMatic(stakeAmount); + + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const currentConversion = + await maticX.convertMaticXToMatic(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right POL to MaticX conversion", async function () { + const { maticX, pol, stakerA } = + await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertPOLToMaticX(stakeAmount); + + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const currentConversion = + await maticX.convertPOLToMaticX(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + + it("Should return the right POL to MaticX conversion in a backward compatible manner", async function () { + const { maticX, pol, stakerA } = + await loadFixture(deployFixture); + + await pol.connect(stakerA).approve(maticX.address, stakeAmount); + + const initialConversion = + await maticX.convertMaticToMaticX(stakeAmount); + + await maticX.connect(stakerA).submitPOL(stakeAmount); + + const currentConversion = + await maticX.convertMaticToMaticX(stakeAmount); + expect(initialConversion).not.to.equal(currentConversion); + expect(currentConversion[0]).to.equal(stakeAmount); + expect(currentConversion[1]).to.equal(stakeAmount); + expect(currentConversion[2]).to.equal(stakeAmount); + }); + it("Should return the right total pooled stake tokens", async function () { const { maticX, pol, stakers } = await loadFixture(deployFixture); @@ -1094,6 +1275,27 @@ describe("MaticX (Forking)", function () { ); }); + it("Should return the right total pooled stake tokens in a backward compatible manner", async function () { + const { maticX, pol, stakers } = + await loadFixture(deployFixture); + + for (const staker of stakers) { + await pol + .connect(staker) + .approve(maticX.address, tripleStakeAmount); + + for (let i = 0; i < 3; i++) { + await maticX.connect(staker).submitPOL(stakeAmount); + } + } + + const totalPooledStakeTokens = + await maticX.getTotalPooledMatic(); + expect(totalPooledStakeTokens).to.equal( + tripleStakeAmount.mul(2) + ); + }); + it("Should return the right total stake from a validator share", async function () { const { maticX, @@ -1796,6 +1998,123 @@ describe("MaticX (Forking)", function () { }); }); + describe("Migrate a delegation", function () { + describe("Negative", function () { + it("Should revert with the right error if paused", async function () { + const { + maticX, + manager, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + await maticX.connect(manager).togglePause(); + + const promise = maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + stakeAmount + ); + await expect(promise).to.be.revertedWith("Pausable: paused"); + }); + + it("Should revert with the right error if called by a non admin", async function () { + const { + maticX, + executor, + defaultAdminRole, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + const promise = maticX + .connect(executor) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + stakeAmount + ); + await expect(promise).to.be.revertedWith( + `AccessControl: account ${executor.address.toLowerCase()} is missing role ${defaultAdminRole}` + ); + }); + + it("Should revert with the right error if passing an unregistered source validator id", async function () { + const { maticX, manager, preferredWithdrawalValidatorId } = + await loadFixture(deployFixture); + + const promise = maticX + .connect(manager) + .migrateDelegation( + 0, + preferredWithdrawalValidatorId, + stakeAmount + ); + await expect(promise).to.be.revertedWith( + "From validator id does not exist in our registry" + ); + }); + + it("Should revert with the right error if passing an unregistered destination validator id", async function () { + const { maticX, manager, preferredDepositValidatorId } = + await loadFixture(deployFixture); + + const promise = maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + 0, + stakeAmount + ); + await expect(promise).to.be.revertedWith( + "To validator id does not exist in our registry" + ); + }); + + it("Should revert with the right error if passing zero amount", async function () { + const { + maticX, + manager, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + const promise = maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + 0 + ); + await expect(promise).to.be.revertedWith("Amount is zero"); + }); + + it("Should revert with the right error if migrating a too much amount", async function () { + const { + maticX, + manager, + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + } = await loadFixture(deployFixture); + + const promise = maticX + .connect(manager) + .migrateDelegation( + preferredDepositValidatorId, + preferredWithdrawalValidatorId, + 1 + ); + await expect(promise).to.be.revertedWith("Migrating too much"); + }); + }); + + describe("Positive", function () { + // TODO Add tests + }); + }); + describe("Set a fee percent", function () { describe("Negative", function () { it("Should revert with the right error if called by a non admin", async function () {