From ede54f440907766bd8c01b1d8ea1cbbb6368045f Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Sat, 23 Mar 2024 15:50:25 -0400 Subject: [PATCH] Adds tracking of overall authorized amount for each application --- contracts/staking/TokenStaking.sol | 44 +++++- test/staking/TokenStaking.test.js | 225 ++++++++++++++++++++++++++--- 2 files changed, 251 insertions(+), 18 deletions(-) diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index 3121202..defc8a9 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -74,6 +74,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { struct ApplicationInfo { ApplicationStatus status; address panicButton; + uint96 authorizedOverall; } struct SlashingEvent { @@ -328,6 +329,36 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { emit ApplicationStatusChanged(application, ApplicationStatus.APPROVED); } + /// @notice Update `authorizedOverall` field for each application. + /// Can only be called by Governance. `authorizedOVerall` should be + /// equal to the sum of all authorization for particular application. + // TODO remove after use + function updateAuthorizedOverall(int96[] memory authorizedOverallValues) + external + onlyGovernance + { + require( + authorizedOverallValues.length == applications.length, + "Wrong input parameters" + ); + for (uint256 i = 0; i < applications.length; i++) { + address application = applications[i]; + ApplicationInfo storage applicationStruct = applicationInfo[ + application + ]; + int96 authorizedOverall = authorizedOverallValues[i]; + if (authorizedOverall >= 0) { + applicationStruct.authorizedOverall += uint96( + authorizedOverall + ); + } else { + applicationStruct.authorizedOverall -= uint96( + -authorizedOverall + ); + } + } + } + /// @notice Increases the authorization of the given staking provider for /// the given application by the given amount. Can only be called by /// the given staking provider’s authorizer. @@ -370,6 +401,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { ); require(availableTValue >= amount, "Not enough stake to authorize"); authorization.authorized += amount; + applicationStruct.authorizedOverall += amount; emit AuthorizationIncreased( stakingProvider, application, @@ -446,6 +478,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { uint96 fromAmount = authorization.authorized; authorization.authorized -= authorization.deauthorizing; + applicationStruct.authorizedOverall -= authorization.deauthorizing; authorization.deauthorizing = 0; emit AuthorizationDecreaseApproved( stakingProvider, @@ -469,8 +502,11 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { address stakingProvider, address application ) external override { + ApplicationInfo storage applicationStruct = applicationInfo[ + application + ]; require( - applicationInfo[application].status == ApplicationStatus.DISABLED, + applicationStruct.status == ApplicationStatus.DISABLED, "Application is not disabled" ); @@ -483,6 +519,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { require(fromAmount > 0, "Application is not authorized"); authorization.authorized = 0; authorization.deauthorizing = 0; + applicationStruct.authorizedOverall -= fromAmount; emit AuthorizationDecreaseApproved( stakingProvider, @@ -612,6 +649,8 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { .authorizations[application]; uint96 fromAmount = authorization.authorized; authorization.authorized += amount; + //slither-disable-next-line reentrancy-benign + applicationInfo[application].authorizedOverall += amount; emit AuthorizationIncreased( stakingProvider, application, @@ -1116,6 +1155,9 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { if (authorization.authorized > totalStake) { authorization.authorized = totalStake; } + applicationInfo[authorizedApplication].authorizedOverall -= + fromAmount - + authorization.authorized; bool successful = true; //slither-disable-next-line calls-loop diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index bf91e4e..a844ca7 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -407,9 +407,12 @@ describe("TokenStaking", () => { }) it("should approve application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, AddressZero]) + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.status).to.equal(ApplicationStatus.APPROVED) + expect(info.panicButton).to.equal(AddressZero) + expect(info.authorizedOverall).to.equal(0) }) it("should add application to the list of all applications", async () => { @@ -445,9 +448,12 @@ describe("TokenStaking", () => { }) it("should enable application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.status).to.equal(ApplicationStatus.APPROVED) + expect(info.panicButton).to.equal(panicButton.address) + expect(info.authorizedOverall).to.equal(0) }) it("should keep list of all applications unchanged", async () => { @@ -465,6 +471,77 @@ describe("TokenStaking", () => { }) }) + describe("updateAuthorizedOverall", () => { + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + }) + + context("when caller is not the governance", () => { + it("should revert", async () => { + await expect( + tokenStaking.connect(staker).updateAuthorizedOverall([]) + ).to.be.revertedWith("Caller is not the governance") + }) + }) + + context("when caller did not provide proper array", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(deployer) + .updateAuthorizedOverall([initialStakerBalance]) + ).to.be.revertedWith("Wrong input parameters") + }) + }) + + context("when setting new values", () => { + const amount1 = initialStakerBalance + const amount2 = amount1.div(3) + + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .updateAuthorizedOverall([amount1, amount2]) + }) + + it("should set new overall authorized for all applications", async () => { + let info = await tokenStaking.applicationInfo(application1Mock.address) + expect(info.authorizedOverall).to.equal(amount1) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(amount2) + }) + }) + + context("when updating old values", () => { + const amount1 = initialStakerBalance + const amount2 = amount1.div(3) + const addition1 = amount1.div(4) + const subtraction2 = amount2.div(4) + + beforeEach(async () => { + await tokenStaking + .connect(deployer) + .updateAuthorizedOverall([amount1, amount2]) + await tokenStaking.connect(deployer).updateAuthorizedOverall([0, 0]) + await tokenStaking + .connect(deployer) + .updateAuthorizedOverall([addition1, Zero.sub(subtraction2)]) + }) + + it("should set new overall authorized for all applications", async () => { + let info = await tokenStaking.applicationInfo(application1Mock.address) + expect(info.authorizedOverall).to.equal(amount1.add(addition1)) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(amount2.sub(subtraction2)) + }) + }) + }) + describe("increaseAuthorization", () => { context("when caller is not authorizer", () => { it("should revert", async () => { @@ -621,6 +698,17 @@ describe("TokenStaking", () => { ).to.equal(authorizedAmount) }) + it("should increase overall authorization for one application", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(authorizedAmount) + info = await tokenStaking.applicationInfo( + application2Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + }) + it("should decrease available amount to authorize for one application", async () => { expect( await tokenStaking.getAvailableToAuthorize( @@ -716,6 +804,17 @@ describe("TokenStaking", () => { ).to.equal(amount) }) + it("should increase overall authorization for one application", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(amount) + info = await tokenStaking.applicationInfo( + application2Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + }) + it("should decrease available amount to authorize for one application", async () => { expect( await tokenStaking.getAvailableToAuthorize( @@ -804,6 +903,17 @@ describe("TokenStaking", () => { ) ).to.equal(amount) }) + + it("should increase overall authorization for one application", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + info = await tokenStaking.applicationInfo( + application2Mock.address + ) + expect(info.authorizedOverall).to.equal(amount) + }) }) }) } @@ -1223,6 +1333,13 @@ describe("TokenStaking", () => { ).to.equal(expectedToAmount) }) + it("should decrease overall authorization", async () => { + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(expectedToAmount) + }) + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) .to.emit(tokenStaking, "AuthorizationDecreaseApproved") @@ -1282,6 +1399,15 @@ describe("TokenStaking", () => { ).to.equal(otherAmount) }) + it("should decrease overall authorization for one app", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(otherAmount) + }) + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) .to.emit(tokenStaking, "AuthorizationDecreaseApproved") @@ -1337,6 +1463,15 @@ describe("TokenStaking", () => { ).to.equal(0) }) + it("should decrease overall authorization", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(0) + }) + it("should emit AuthorizationDecreaseApproved", async () => { await expect(tx) .to.emit(tokenStaking, "AuthorizationDecreaseApproved") @@ -1472,6 +1607,13 @@ describe("TokenStaking", () => { ).to.equal(0) }) + it("should decrease overall authorization", async () => { + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal(0) + }) + it("should allow to authorize more applications", async () => { await tokenStaking .connect(deployer) @@ -1554,9 +1696,12 @@ describe("TokenStaking", () => { }) it("should pause application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.PAUSED, panicButton.address]) + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.status).to.equal(ApplicationStatus.PAUSED) + expect(info.panicButton).to.equal(panicButton.address) + expect(info.authorizedOverall).to.equal(0) }) it("should keep list of all applications unchanged", async () => { @@ -1629,9 +1774,12 @@ describe("TokenStaking", () => { }) it("should disable application", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.DISABLED, panicButton.address]) + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.status).to.equal(ApplicationStatus.DISABLED) + expect(info.panicButton).to.equal(panicButton.address) + expect(info.authorizedOverall).to.equal(0) }) it("should keep list of all applications unchanged", async () => { @@ -1711,9 +1859,12 @@ describe("TokenStaking", () => { }) it("should set address of panic button", async () => { - expect( - await tokenStaking.applicationInfo(application1Mock.address) - ).to.deep.equal([ApplicationStatus.APPROVED, panicButton.address]) + const info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.status).to.equal(ApplicationStatus.APPROVED) + expect(info.panicButton).to.equal(panicButton.address) + expect(info.authorizedOverall).to.equal(0) }) it("should emit PanicButtonSet", async () => { @@ -1937,11 +2088,11 @@ describe("TokenStaking", () => { }) context("when auto increase flag is enabled", () => { - const amount = initialStakerBalance.div(2) + const amount = initialStakerBalance.div(3) const topUpAmount = initialStakerBalance const expectedAmount = amount.add(topUpAmount) const authorized1 = amount - const authorized2 = amount.div(2) + const authorized2 = amount.div(3) let tx beforeEach(async () => { @@ -2035,6 +2186,13 @@ describe("TokenStaking", () => { ).to.equal(authorized2.add(topUpAmount)) }) + it("should increase overall authorization", async () => { + let info = await tokenStaking.applicationInfo(application1Mock.address) + expect(info.authorizedOverall).to.equal(expectedAmount) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(authorized2.add(topUpAmount)) + }) + it("should inform application", async () => { await assertApplicationStakingProviders( application1Mock, @@ -3085,6 +3243,19 @@ describe("TokenStaking", () => { ).to.equal(provider2Authorized2) }) + it("should decrease overall authorization", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal( + provider1Authorized1.add(provider2Authorized1).sub(amountToSlash) + ) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal( + provider1Authorized2.add(provider2Authorized2).sub(amountToSlash) + ) + }) + it("should not allow to authorize more applications", async () => { await tokenStaking .connect(deployer) @@ -3202,6 +3373,19 @@ describe("TokenStaking", () => { ) }) + it("should decrease overall authorization", async () => { + let info = await tokenStaking.applicationInfo( + application1Mock.address + ) + expect(info.authorizedOverall).to.equal( + provider1Authorized1.sub(amountToSlash) + ) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal( + provider1Authorized2.sub(amountToSlash) + ) + }) + it("should allow to authorize more applications", async () => { await tokenStaking.connect(deployer).setAuthorizationCeiling(2) await tToken.connect(deployer).transfer(otherStaker.address, tAmount) @@ -3368,6 +3552,13 @@ describe("TokenStaking", () => { ).to.equal(0) }) + it("should decrease overall authorization", async () => { + let info = await tokenStaking.applicationInfo(application1Mock.address) + expect(info.authorizedOverall).to.equal(0) + info = await tokenStaking.applicationInfo(application2Mock.address) + expect(info.authorizedOverall).to.equal(0) + }) + it("should allow to authorize one more application", async () => { await tokenStaking .connect(staker)