From 985380e2a0e47a941ac472c7efc05ec8f86658a9 Mon Sep 17 00:00:00 2001 From: Victoria Zotova Date: Fri, 29 Sep 2023 10:20:54 -0400 Subject: [PATCH 1/2] Adds auto-increase flag and handling this flag in `topUp` --- contracts/staking/IStaking.sol | 12 ++ contracts/staking/TokenStaking.sol | 65 ++++++++ docs/rfc-1-staking-contract.adoc | 18 ++- test/staking/TokenStaking.test.js | 236 ++++++++++++++++++++++++++++- 4 files changed, 326 insertions(+), 5 deletions(-) diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index 1d7019b8..274d20e3 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -158,10 +158,16 @@ interface IStaking { // /// @notice Increases the amount of the stake for the given staking provider. + /// If `autoIncrease` flag is true then the amount will be added for + /// all authorized applications. /// @dev The sender of this transaction needs to have the amount approved to /// transfer to the staking contract. function topUp(address stakingProvider, uint96 amount) external; + /// @notice Toggle auto authorization increase flag. If true then all amount + /// in top-up will be added to already authorized applications. + function toggleAutoAuthorizationIncrease(address stakingProvider) external; + // // // Undelegating a stake (unstaking) @@ -278,6 +284,12 @@ interface IStaking { view returns (uint256); + /// @notice Returns auto-increase flag. + function getAutoIncreaseFlag(address stakingProvider) + external + view + returns (bool); + /// @notice Returns staked amount of NU for the specified staking provider. function stakedNu(address stakingProvider) external view returns (uint256); diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index f8f17110..57db7cee 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -60,6 +60,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { mapping(address => AppAuthorization) authorizations; address[] authorizedApplications; uint256 startStakingTimestamp; + bool autoIncrease; } struct AppAuthorization { @@ -150,6 +151,10 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { ); event AuthorizationCeilingSet(uint256 ceiling); event ToppedUp(address indexed stakingProvider, uint96 amount); + event AutoIncreaseToggled( + address indexed stakingProvider, + bool autoIncrease + ); event Unstaked(address indexed stakingProvider, uint96 amount); event TokensSeized( address indexed stakingProvider, @@ -575,6 +580,8 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { // /// @notice Increases the amount of the stake for the given staking provider. + /// If `autoIncrease` flag is true then the amount will be added for + /// all authorized applications. /// @dev The sender of this transaction needs to have the amount approved to /// transfer to the staking contract. function topUp(address stakingProvider, uint96 amount) external override { @@ -590,6 +597,54 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { emit ToppedUp(stakingProvider, amount); increaseStakeCheckpoint(stakingProvider, amount); token.safeTransferFrom(msg.sender, address(this), amount); + + if (!stakingProviderStruct.autoIncrease) { + return; + } + + // increase authorization for all authorized app + for ( + uint256 i = 0; + i < stakingProviderStruct.authorizedApplications.length; + i++ + ) { + address application = stakingProviderStruct.authorizedApplications[ + i + ]; + AppAuthorization storage authorization = stakingProviderStruct + .authorizations[application]; + uint96 fromAmount = authorization.authorized; + authorization.authorized += amount; + emit AuthorizationIncreased( + stakingProvider, + application, + fromAmount, + authorization.authorized + ); + IApplication(application).authorizationIncreased( + stakingProvider, + fromAmount, + authorization.authorized + ); + } + } + + /// @notice Toggle auto authorization increase flag. If true then all amount + /// in top-up will be added to already authorized applications. + function toggleAutoAuthorizationIncrease(address stakingProvider) + external + override + onlyAuthorizerOf(stakingProvider) + { + StakingProviderInfo storage stakingProviderStruct = stakingProviders[ + stakingProvider + ]; + stakingProviderStruct.autoIncrease = !stakingProviderStruct + .autoIncrease; + emit AutoIncreaseToggled( + stakingProvider, + stakingProviderStruct.autoIncrease + ); } // @@ -918,6 +973,16 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { return stakingProviders[stakingProvider].startStakingTimestamp; } + /// @notice Returns auto-increase flag. + function getAutoIncreaseFlag(address stakingProvider) + external + view + override + returns (bool) + { + return stakingProviders[stakingProvider].autoIncrease; + } + /// @notice Returns staked amount of NU for the specified staking provider. function stakedNu(address stakingProvider) external diff --git a/docs/rfc-1-staking-contract.adoc b/docs/rfc-1-staking-contract.adoc index d3f24dea..6a244fa9 100644 --- a/docs/rfc-1-staking-contract.adoc +++ b/docs/rfc-1-staking-contract.adoc @@ -282,9 +282,15 @@ protect against DoSing slashing queue. Can only be called by the governance. ==== `topUp(address stakingProvider, uint96 amount) external` -Increases the amount of the stake for the given staking provider. The sender of this -transaction needs to have the amount approved to transfer to the staking -contract. +Increases the amount of the stake for the given staking provider. If `autoIncrease` +flag is true then the amount will be added for all authorized applications. +The sender of this transaction needs to have the amount approved to transfer +to the staking contract. + +==== `toggleAutoAuthorizationIncrease(address stakingProvider) external` + +Toggle auto authorization increase flag. If true then all amount in top-up +will be added to already authorized applications. === Undelegating a stake (unstaking) @@ -374,6 +380,12 @@ Returns start staking timestamp for T/NU stake. This value is set at most once, and only when a stake is created with T or NU tokens. If a stake is created from a legacy KEEP stake, this value will remain as zero. + +==== `getAutoIncreaseFlag(address stakingProvider) external view returns (bool)` + +Returns auto-increase flag. If flag is true then any topped up amount will be added to +existing authorizations. + ==== `stakedNu(address stakingProvider) external view returns (uint256)` Returns staked amount of NU for the specified staking provider diff --git a/test/staking/TokenStaking.test.js b/test/staking/TokenStaking.test.js index 4c4d2ac9..080907b6 100644 --- a/test/staking/TokenStaking.test.js +++ b/test/staking/TokenStaking.test.js @@ -2140,6 +2140,16 @@ describe("TokenStaking", () => { amount ) blockTimestamp = await lastBlockTime() + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(staker) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + amount + ) await tokenStaking .connect(staker) @@ -2183,13 +2193,13 @@ describe("TokenStaking", () => { stakingProvider.address, application1Mock.address ) - ).to.equal(expectedAmount) + ).to.equal(topUpAmount) }) it("should not increase min staked amount", async () => { expect( await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) - ).to.equal(0) + ).to.equal(amount) expect( await tokenStaking.getMinStaked( stakingProvider.address, @@ -2242,6 +2252,9 @@ describe("TokenStaking", () => { amount ) blockTimestamp = await lastBlockTime() + await tokenStaking + .connect(staker) + .toggleAutoAuthorizationIncrease(stakingProvider.address) await increaseTime(86400) // +24h @@ -2457,6 +2470,225 @@ describe("TokenStaking", () => { ) }) }) + + context("when auto increase flag is enabled", () => { + const amount = initialStakerBalance.div(2) + const topUpAmount = initialStakerBalance + const expectedAmount = amount.add(topUpAmount) + const authorized1 = amount + const authorized2 = amount.div(2) + let tx + + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + await tokenStaking + .connect(deployer) + .approveApplication(application1Mock.address) + await tokenStaking + .connect(deployer) + .approveApplication(application2Mock.address) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application1Mock.address, + authorized1 + ) + await tokenStaking + .connect(authorizer) + .increaseAuthorization( + stakingProvider.address, + application2Mock.address, + authorized2 + ) + await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + + await tToken + .connect(deployer) + .transfer(stakingProvider.address, topUpAmount) + await tToken + .connect(stakingProvider) + .approve(tokenStaking.address, topUpAmount) + tx = await tokenStaking + .connect(stakingProvider) + .topUp(stakingProvider.address, topUpAmount) + }) + + it("should update T staked amount", async () => { + await assertStakes(stakingProvider.address, expectedAmount, Zero, Zero) + }) + + it("should not increase available amount to authorize", async () => { + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(0) + expect( + await tokenStaking.getAvailableToAuthorize( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(amount.sub(authorized2)) + }) + + it("should increase min staked amount", async () => { + expect( + await tokenStaking.getMinStaked(stakingProvider.address, StakeTypes.T) + ).to.equal(expectedAmount) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.NU + ) + ).to.equal(0) + expect( + await tokenStaking.getMinStaked( + stakingProvider.address, + StakeTypes.KEEP + ) + ).to.equal(0) + }) + + it("should emit ToppedUp event", async () => { + await expect(tx) + .to.emit(tokenStaking, "ToppedUp") + .withArgs(stakingProvider.address, topUpAmount) + }) + + it("should increase authorized amounts", async () => { + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application1Mock.address + ) + ).to.equal(expectedAmount) + expect( + await tokenStaking.authorizedStake( + stakingProvider.address, + application2Mock.address + ) + ).to.equal(authorized2.add(topUpAmount)) + }) + + it("should inform application", async () => { + await assertApplicationStakingProviders( + application1Mock, + stakingProvider.address, + expectedAmount, + Zero + ) + await assertApplicationStakingProviders( + application2Mock, + stakingProvider.address, + authorized2.add(topUpAmount), + Zero + ) + }) + + it("should emit AuthorizationIncreased", async () => { + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application1Mock.address, + authorized1, + expectedAmount + ) + await expect(tx) + .to.emit(tokenStaking, "AuthorizationIncreased") + .withArgs( + stakingProvider.address, + application2Mock.address, + authorized2, + authorized2.add(topUpAmount) + ) + }) + }) + }) + + describe("toggleAutoAuthorizationIncrease", () => { + const amount = initialStakerBalance + + beforeEach(async () => { + await tToken.connect(staker).approve(tokenStaking.address, amount) + await tokenStaking + .connect(staker) + .stake( + stakingProvider.address, + beneficiary.address, + authorizer.address, + amount + ) + }) + + context("when caller is not authorizer", () => { + it("should revert", async () => { + await expect( + tokenStaking + .connect(stakingProvider) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + ).to.be.revertedWith("Not authorizer") + }) + }) + + context("when method called first time", () => { + let tx + + beforeEach(async () => { + tx = await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + }) + + it("should enable auto increase flag", async () => { + expect( + await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) + ).to.equal(true) + }) + + it("should emit AutoIncreaseToggled", async () => { + await expect(tx) + .to.emit(tokenStaking, "AutoIncreaseToggled") + .withArgs(stakingProvider.address, true) + }) + }) + + context("when method called second time", () => { + let tx + + beforeEach(async () => { + await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + tx = await tokenStaking + .connect(authorizer) + .toggleAutoAuthorizationIncrease(stakingProvider.address) + }) + + it("should enable auto increase flag", async () => { + expect( + await tokenStaking.getAutoIncreaseFlag(stakingProvider.address) + ).to.equal(false) + }) + + it("should emit AutoIncreaseToggled", async () => { + await expect(tx) + .to.emit(tokenStaking, "AutoIncreaseToggled") + .withArgs(stakingProvider.address, false) + }) + }) }) describe("unstakeT", () => { From 624ead6da19bf5890b049af91462801e63a4b3d2 Mon Sep 17 00:00:00 2001 From: Victoria Date: Tue, 14 Nov 2023 16:15:39 +0200 Subject: [PATCH 2/2] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: David Núñez --- contracts/staking/IStaking.sol | 2 +- contracts/staking/TokenStaking.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/staking/IStaking.sol b/contracts/staking/IStaking.sol index 274d20e3..fa5d60da 100644 --- a/contracts/staking/IStaking.sol +++ b/contracts/staking/IStaking.sol @@ -164,7 +164,7 @@ interface IStaking { /// transfer to the staking contract. function topUp(address stakingProvider, uint96 amount) external; - /// @notice Toggle auto authorization increase flag. If true then all amount + /// @notice Toggle `autoIncrease` flag. If true then the complete amount /// in top-up will be added to already authorized applications. function toggleAutoAuthorizationIncrease(address stakingProvider) external; diff --git a/contracts/staking/TokenStaking.sol b/contracts/staking/TokenStaking.sol index 57db7cee..29f50cd3 100644 --- a/contracts/staking/TokenStaking.sol +++ b/contracts/staking/TokenStaking.sol @@ -629,7 +629,7 @@ contract TokenStaking is Initializable, IStaking, Checkpoints { } } - /// @notice Toggle auto authorization increase flag. If true then all amount + /// @notice Toggle `autoIncrease` flag. If true then the complete amount /// in top-up will be added to already authorized applications. function toggleAutoAuthorizationIncrease(address stakingProvider) external