diff --git a/contracts/BackedAutoFeeTokenImplementation.sol b/contracts/BackedAutoFeeTokenImplementation.sol index 25b5738..abbf317 100644 --- a/contracts/BackedAutoFeeTokenImplementation.sol +++ b/contracts/BackedAutoFeeTokenImplementation.sol @@ -77,6 +77,8 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { uint256 internal _totalShares; + uint256 public multiplierNonce; + // Events: /** @@ -104,13 +106,37 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { // Modifiers: modifier updateMultiplier() { - (uint256 newMultiplier, uint256 periodsPassed) = getCurrentMultiplier(); + (uint256 newMultiplier, uint256 periodsPassed, uint256 newMultiplierNonce) = getCurrentMultiplier(); lastTimeFeeApplied = lastTimeFeeApplied + periodLength * periodsPassed; if (multiplier != newMultiplier) { - _updateMultiplier(newMultiplier); + _updateMultiplier(newMultiplier, newMultiplierNonce); } _; } + + modifier onlyMultiplierUpdater() { + require( + _msgSender() == multiplierUpdater, + "BackedToken: Only multiplier updater" + ); + _; + } + + modifier onlyUpdatedMultiplier(uint256 oldMultiplier) { + require( + multiplier == oldMultiplier, + "BackedToken: Multiplier changed in the meantime" + ); + _; + } + + modifier onlyNewerMultiplierNonce(uint256 newMultiplierNonce) { + require( + multiplierNonce < newMultiplierNonce, + "BackedToken: Multiplier nonce is outdated." + ); + _; + } // constructor, set lastTimeFeeApplied to lock the implementation instance. constructor () { @@ -155,6 +181,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { require(_lastTimeFeeApplied != 0, "Invalid last time fee applied"); multiplier = 1e18; + multiplierNonce = 0; periodLength = _periodLength; lastTimeFeeApplied = _lastTimeFeeApplied; feePerPeriod = _feePerPeriod; @@ -164,7 +191,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { * @dev See {IERC20-totalSupply}. */ function totalSupply() public view virtual override returns (uint256) { - (uint256 newMultiplier, ) = getCurrentMultiplier(); + (uint256 newMultiplier, ,) = getCurrentMultiplier(); return _getUnderlyingAmountByShares(_totalShares, newMultiplier); } @@ -174,7 +201,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { function balanceOf( address account ) public view virtual override returns (uint256) { - (uint256 newMultiplier, ) = getCurrentMultiplier(); + (uint256 newMultiplier, ,) = getCurrentMultiplier(); return _getUnderlyingAmountByShares(sharesOf(account), newMultiplier); } @@ -186,14 +213,16 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { public view virtual - returns (uint256 newMultiplier, uint256 periodsPassed) + returns (uint256 newMultiplier, uint256 periodsPassed, uint256 newMultiplierNonce) { periodsPassed = (block.timestamp - lastTimeFeeApplied) / periodLength; newMultiplier = multiplier; + newMultiplierNonce = multiplierNonce; if (feePerPeriod > 0) { for (uint256 index = 0; index < periodsPassed; index++) { newMultiplier = (newMultiplier * (1e18 - feePerPeriod)) / 1e18; } + newMultiplierNonce += periodsPassed; } } @@ -210,7 +239,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { function getSharesByUnderlyingAmount( uint256 _underlyingAmount ) external view returns (uint256) { - (uint256 newMultiplier, ) = getCurrentMultiplier(); + (uint256 newMultiplier, ,) = getCurrentMultiplier(); return _getSharesByUnderlyingAmount(_underlyingAmount, newMultiplier); } @@ -220,7 +249,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { function getUnderlyingAmountByShares( uint256 _sharesAmount ) external view returns (uint256) { - (uint256 newMultiplier, ) = getCurrentMultiplier(); + (uint256 newMultiplier, ,) = getCurrentMultiplier(); return _getUnderlyingAmountByShares(_sharesAmount, newMultiplier); } @@ -319,7 +348,7 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { } /** - * @dev Function to change the contract multiplier, only if oldMultiplier did not change in the meantime. Allowed only for owner + * @dev Function to change the contract multiplier, only if oldMultiplier did not change in the meantime. Allowed only for multiplierUpdater * * Emits a { MultiplierChanged } event * @@ -328,16 +357,24 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { function updateMultiplierValue( uint256 newMultiplier, uint256 oldMultiplier - ) external updateMultiplier { - require( - _msgSender() == multiplierUpdater, - "BackedToken: Only multiplier updater" - ); - require( - multiplier == oldMultiplier, - "BackedToken: Multiplier changed in the meantime" - ); - _updateMultiplier(newMultiplier); + ) public onlyMultiplierUpdater updateMultiplier onlyUpdatedMultiplier(oldMultiplier) { + _updateMultiplier(newMultiplier, multiplierNonce + 1); + } + + /** + * @dev Function to change the contract multiplier with nonce, only if oldMultiplier did not change in the meantime. Allowed only for multiplierUpdater + * + * Emits a { MultiplierChanged } event + * + * @param newMultiplier New multiplier value + * @param newMultiplierNonce New multplier nonce + */ + function updateMultiplierWithNonce( + uint256 newMultiplier, + uint256 oldMultiplier, + uint256 newMultiplierNonce + ) external onlyMultiplierUpdater updateMultiplier onlyUpdatedMultiplier(oldMultiplier) onlyNewerMultiplierNonce(newMultiplierNonce){ + _updateMultiplier(newMultiplier, newMultiplierNonce); } /** @@ -469,8 +506,9 @@ contract BackedAutoFeeTokenImplementation is BackedTokenImplementation { * * Emit an {MultiplierUpdated} event. */ - function _updateMultiplier(uint256 newMultiplier) internal virtual { + function _updateMultiplier(uint256 newMultiplier, uint256 newMultiplierNonce) internal virtual { multiplier = newMultiplier; + multiplierNonce = newMultiplierNonce; emit MultiplierUpdated(newMultiplier); } diff --git a/test/BackedAutoFeeTokenImplementation.ts b/test/BackedAutoFeeTokenImplementation.ts index fd07ce1..b20af2f 100644 --- a/test/BackedAutoFeeTokenImplementation.ts +++ b/test/BackedAutoFeeTokenImplementation.ts @@ -1,3 +1,4 @@ +/* eslint-disable prettier/prettier */ import { ProxyAdmin__factory } from '../typechain/factories/ProxyAdmin__factory'; import { ProxyAdmin } from '../typechain/ProxyAdmin'; import { BackedAutoFeeTokenImplementation__factory } from '../typechain/factories/BackedAutoFeeTokenImplementation__factory'; @@ -184,15 +185,19 @@ describe("BackedAutoFeeTokenImplementation", function () { describe('when time moved by 365 days forward', () => { const periodsPassed = 365; let preMultiplier: BigNumber; + let preMultiplierNonce: BigNumber; describe('and fee is set to non-zero value', () => { cacheBeforeEach(async () => { preMultiplier = await token.multiplier(); + preMultiplierNonce = await token.multiplierNonce(); await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); await helpers.mine() }) it('should change current multiplier', async () => { - expect((await token.getCurrentMultiplier()).newMultiplier).to.be.not.equal(preMultiplier) + const currentMultiplier = await token.getCurrentMultiplier(); + expect(currentMultiplier.newMultiplier).to.be.not.equal(preMultiplier) + expect(currentMultiplier.newMultiplierNonce).to.be.not.equal(preMultiplierNonce) }) it('should not update stored multiplier', async () => { expect(await token.multiplier()).to.be.equal(preMultiplier) @@ -202,6 +207,7 @@ describe("BackedAutoFeeTokenImplementation", function () { cacheBeforeEach(async () => { await token.updateFeePerPeriod('0'); preMultiplier = await token.multiplier(); + preMultiplierNonce = await token.multiplierNonce(); await helpers.time.setNextBlockTimestamp(baseTime + periodsPassed * accrualPeriodLength); await helpers.mine() }) @@ -209,6 +215,10 @@ describe("BackedAutoFeeTokenImplementation", function () { it('should not change current multiplier', async () => { expect((await token.getCurrentMultiplier()).newMultiplier).to.be.equal(preMultiplier) }) + + it('should change current multiplier nonce', async () => { + expect((await token.getCurrentMultiplier()).newMultiplierNonce).to.be.equal(preMultiplierNonce) + }) }) }) }) @@ -341,11 +351,12 @@ describe("BackedAutoFeeTokenImplementation", function () { }) describe('#updateMultiplierValue', () => { - it('Should update stored multiplier value', async () => { - const { newMultiplier: currentMultiplier } = await token.getCurrentMultiplier(); + it('Should update stored multiplier value and nonce', async () => { + const { newMultiplier: currentMultiplier, newMultiplierNonce: currentMultiplierNonce } = await token.getCurrentMultiplier(); const newMultiplierValue = currentMultiplier.div(2); await token.updateMultiplierValue(newMultiplierValue, currentMultiplier) expect(await token.multiplier()).to.be.equal(newMultiplierValue); + expect(await token.multiplierNonce()).to.be.equal(currentMultiplierNonce.add(1)); expect(await token.lastTimeFeeApplied()).to.be.equal(baseTime + periodsPassed * accrualPeriodLength); }); it('Should reject update, if wrong past value was passed', async () => { @@ -357,6 +368,29 @@ describe("BackedAutoFeeTokenImplementation", function () { }); }); + describe('#updateMultiplierWithNonce', () => { + it('Should update stored multiplier value and nonce', async () => { + const { newMultiplier: currentMultiplier, newMultiplierNonce: currentMultiplierNonce } = await token.getCurrentMultiplier(); + const newMultiplierValue = currentMultiplier.div(2); + const newMultiplierNonce = currentMultiplierNonce.add(100); + await token.updateMultiplierWithNonce(newMultiplierValue, currentMultiplier, newMultiplierNonce) + expect(await token.multiplier()).to.be.equal(newMultiplierValue); + expect(await token.multiplierNonce()).to.be.equal(newMultiplierNonce); + expect(await token.lastTimeFeeApplied()).to.be.equal(baseTime + periodsPassed * accrualPeriodLength); + }); + it('Should reject update, if wrong past value was passed', async () => { + await expect(token.updateMultiplierWithNonce(0, 1, 1)).to.be.reverted; + }); + it('Should reject update, if wrong account is used', async () => { + const { newMultiplier: currentMultiplier, newMultiplierNonce: currentMultiplierNonce } = await token.getCurrentMultiplier(); + await expect(token.connect(actor.signer).updateMultiplierWithNonce(1, currentMultiplier, currentMultiplierNonce.add(1))).to.be.reverted + }); + it('Should reject update, if wrong nonce is used', async () => { + const { newMultiplier: currentMultiplier, newMultiplierNonce: currentMultiplierNonce } = await token.getCurrentMultiplier(); + await expect(token.connect(actor.signer).updateMultiplierValue(1, currentMultiplier, currentMultiplierNonce)).to.be.reverted + }); + }); + describe('#balanceOf', () => { it('Should decrease balance of the user by fee accrued in 365 days', async () => { expect((await token.balanceOf(owner.address)).sub(baseMintedAmount.mul(annualFee * 100).div(100)).abs()).to.lte(