diff --git a/packages/contracts/scripts/deploy/dollar/solidityScripting/01_Diamond.s.sol b/packages/contracts/scripts/deploy/dollar/solidityScripting/01_Diamond.s.sol index 62cd6a970..ad90cd859 100644 --- a/packages/contracts/scripts/deploy/dollar/solidityScripting/01_Diamond.s.sol +++ b/packages/contracts/scripts/deploy/dollar/solidityScripting/01_Diamond.s.sol @@ -15,6 +15,7 @@ import {CollectableDustFacet} from "../../../../src/dollar/facets/CollectableDus import {ChefFacet} from "../../../../src/dollar/facets/ChefFacet.sol"; import {StakingFacet} from "../../../../src/dollar/facets/StakingFacet.sol"; import {StakingFormulasFacet} from "../../../../src/dollar/facets/StakingFormulasFacet.sol"; +import {CreditClockFacet} from "../../../../src/dollar/facets/CreditClockFacet.sol"; import {CreditNftManagerFacet} from "../../../../src/dollar/facets/CreditNftManagerFacet.sol"; import {CreditNftRedemptionCalculatorFacet} from "../../../../src/dollar/facets/CreditNftRedemptionCalculatorFacet.sol"; import {CreditRedemptionCalculatorFacet} from "../../../../src/dollar/facets/CreditRedemptionCalculatorFacet.sol"; @@ -48,6 +49,7 @@ contract DiamondScript is Constants { bytes4[] selectorsOfDollarMintCalculatorFacet; bytes4[] selectorsOfDollarMintExcessFacet; + bytes4[] selectorsOfCreditClockFacet; // contract types of facets to be deployed Diamond diamond; DiamondCutFacet dCutFacet; @@ -72,6 +74,7 @@ contract DiamondScript is Constants { DollarMintCalculatorFacet dollarMintCalculatorFacet; DollarMintExcessFacet dollarMintExcessFacet; + CreditClockFacet creditClockFacet; string[] facetNames; @@ -96,6 +99,7 @@ contract DiamondScript is Constants { dollarMintCalculatorFacet = new DollarMintCalculatorFacet(); dollarMintExcessFacet = new DollarMintExcessFacet(); + creditClockFacet = new CreditClockFacet(); dInit = new DiamondInit(); facetNames = [ @@ -113,7 +117,8 @@ contract DiamondScript is Constants { "CreditNftRedemptionCalculatorFacet", "CreditRedemptionCalculatorFacet", "DollarMintCalculatorFacet", - "DollarMintExcessFacet" + "DollarMintExcessFacet", + "CreditClockFacet" ]; DiamondInit.Args memory initArgs = DiamondInit.Args({ @@ -133,7 +138,7 @@ contract DiamondScript is Constants { initArgs ) }); - IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](15); + IDiamondCut.FacetCut[] memory cuts = new IDiamondCut.FacetCut[](16); setFacet(cuts); // deploy diamond @@ -256,6 +261,13 @@ contract DiamondScript is Constants { functionSelectors: selectorsOfDollarMintExcessFacet }) ); + cuts[15] = ( + IDiamondCut.FacetCut({ + facetAddress: address(creditClockFacet), + action: IDiamondCut.FacetCutAction.Add, + functionSelectors: selectorsOfCreditClockFacet + }) + ); } function getSelectors() internal { @@ -521,5 +533,17 @@ contract DiamondScript is Constants { selectorsOfDollarMintExcessFacet.push( (dollarMintExcessFacet.distributeDollars.selector) ); + + // Credit Clock Facet + selectorsOfCreditClockFacet.push((creditClockFacet.getRate.selector)); + selectorsOfCreditClockFacet.push( + creditClockFacet.setRatePerBlock.selector + ); + selectorsOfCreditClockFacet.push( + (creditClockFacet.setManager.selector) + ); + selectorsOfCreditClockFacet.push( + (creditClockFacet.getManager.selector) + ); } } diff --git a/packages/contracts/src/dollar/core/CreditClock.sol b/packages/contracts/src/dollar/core/CreditClock.sol deleted file mode 100644 index 461801b3b..000000000 --- a/packages/contracts/src/dollar/core/CreditClock.sol +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.19; - -import "abdk/ABDKMathQuad.sol"; -import {IAccessControl} from "../interfaces/IAccessControl.sol"; -import "../libraries/Constants.sol"; - -/** - * @notice CreditClock contract - */ -contract CreditClock { - using ABDKMathQuad for uint256; - using ABDKMathQuad for bytes16; - - /// @notice Access control contract - IAccessControl public accessControl; - - /// @notice ABDKMathQuad with value of 1. - bytes16 private immutable one = uint256(1).fromUInt(); - - /// @notice The block height from where we start applying the rate. - uint256 public rateStartBlock; - - /// @notice This is the exchange rate of Credits for the start block. - bytes16 public rateStartValue; - - /// @notice Deprecation rate. How many Dollars are deprecated on each block. - bytes16 public ratePerBlock; - - /// @notice Emitted when depreciation rate per block is updated - event SetRatePerBlock( - uint256 rateStartBlock, - bytes16 rateStartValue, - bytes16 ratePerBlock - ); - - /// @notice Modifier checks that the method is called by a user with the "Incentive manager" role - modifier onlyAdmin() { - require( - accessControl.hasRole(INCENTIVE_MANAGER_ROLE, msg.sender), - "CreditClock: not admin" - ); - _; - } - - /** - * @notice Contract constructor - * @param _manager The address of the `_manager` contract for access control - * @param _rateStartValue ABDKMathQuad Initial rate - * @param _ratePerBlock ABDKMathQuad Initial rate change per block - */ - constructor( - address _manager, - bytes16 _rateStartValue, - bytes16 _ratePerBlock - ) { - accessControl = IAccessControl(_manager); - rateStartBlock = block.number; - rateStartValue = _rateStartValue; - ratePerBlock = _ratePerBlock; - - emit SetRatePerBlock(rateStartBlock, rateStartValue, ratePerBlock); - } - - /** - * @notice Updates the manager address - * @param _manager New manager address - */ - function setManager(address _manager) external onlyAdmin { - accessControl = IAccessControl(_manager); - } - - /** - * @notice Returns the manager address - * @return Manager address - */ - function getManager() external view returns (address) { - return address(accessControl); - } - - /** - * @notice Sets rate to apply from this block onward - * @param _ratePerBlock ABDKMathQuad new rate per block to apply from this block onward - */ - function setRatePerBlock(bytes16 _ratePerBlock) external onlyAdmin { - rateStartValue = getRate(block.number); - rateStartBlock = block.number; - ratePerBlock = _ratePerBlock; - - emit SetRatePerBlock(rateStartBlock, rateStartValue, ratePerBlock); - } - - /** - * @notice Calculates `rateStartValue * (1 / ((1 + ratePerBlock)^blockNumber - rateStartBlock)))` - * @param blockNumber Block number to get the rate for. 0 for current block. - * @return rate ABDKMathQuad rate calculated for the block number - */ - function getRate(uint256 blockNumber) public view returns (bytes16 rate) { - if (blockNumber == 0) { - blockNumber = block.number; - } else { - if (blockNumber < block.number) { - revert("CreditClock: block number must not be in the past."); - } - } - // slither-disable-next-line divide-before-multiply - rate = rateStartValue.mul( - one.div( - // b ^ n == 2^(n*log²(b)) - (blockNumber - rateStartBlock) - .fromUInt() - .mul(one.add(ratePerBlock).log_2()) - .pow_2() - ) - ); - } -} diff --git a/packages/contracts/src/dollar/facets/CreditClockFacet.sol b/packages/contracts/src/dollar/facets/CreditClockFacet.sol new file mode 100644 index 000000000..fbc2d3181 --- /dev/null +++ b/packages/contracts/src/dollar/facets/CreditClockFacet.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "abdk/ABDKMathQuad.sol"; +import "../libraries/Constants.sol"; +import {Modifiers} from "../libraries/LibAppStorage.sol"; +import {LibCreditClock} from "../libraries/LibCreditClock.sol"; + +/** + * @notice CreditClock Facet + */ +contract CreditClockFacet is Modifiers { + /** + * @notice Updates the manager address + * @param _manager New manager address + */ + function setManager(address _manager) external onlyAdmin { + LibCreditClock.setManager(_manager); + } + + /** + * @notice Returns the manager address + * @return Manager address + */ + function getManager() external view returns (address) { + return LibCreditClock.getManager(); + } + + /** + * @notice Sets rate to apply from this block onward + * @param _ratePerBlock ABDKMathQuad new rate per block to apply from this block onward + */ + function setRatePerBlock(bytes16 _ratePerBlock) external onlyAdmin { + LibCreditClock.setRatePerBlock(_ratePerBlock); + } + + /** + * @param blockNumber Block number to get the rate for. 0 for current block. + */ + function getRate(uint256 blockNumber) external view { + LibCreditClock.getRate(blockNumber); + } +} diff --git a/packages/contracts/src/dollar/libraries/LibCreditClock.sol b/packages/contracts/src/dollar/libraries/LibCreditClock.sol new file mode 100644 index 000000000..bd79d8d9c --- /dev/null +++ b/packages/contracts/src/dollar/libraries/LibCreditClock.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import "abdk/ABDKMathQuad.sol"; +import {LibAccessControl} from "./LibAccessControl.sol"; +import {IAccessControl} from "../interfaces/IAccessControl.sol"; +import "../libraries/Constants.sol"; + +/// @notice Library for Credit Clock Facet +library LibCreditClock { + using ABDKMathQuad for uint256; + using ABDKMathQuad for bytes16; + + /// @notice Emitted when depreciation rate per block is updated + event SetRatePerBlock( + uint256 rateStartBlock, + bytes16 rateStartValue, + bytes16 ratePerBlock + ); + + /// @notice Storage slot used to store data for this library + bytes32 constant CREDIT_CLOCK_STORAGE_POSITION = + bytes32( + uint256(keccak256("ubiquity.contracts.credit.clock.storage")) - 1 + ); + + /// @notice Struct used as a storage for the current library + struct CreditClockData { + IAccessControl accessControl; + uint256 rateStartBlock; + bytes16 rateStartValue; + bytes16 ratePerBlock; + bytes16 one; + } + + /** + * @notice Returns struct used as a storage for this library + * @return data Struct used as a storage + */ + function creditClockStorage() + internal + pure + returns (CreditClockData storage data) + { + bytes32 position = CREDIT_CLOCK_STORAGE_POSITION; + assembly { + data.slot := position + } + } + + /** + * @notice Updates the manager address + * @param _manager New manager address + */ + function setManager(address _manager) internal { + creditClockStorage().accessControl = IAccessControl(_manager); + } + + /** + * @notice Returns the manager address + * @return Manager address + */ + function getManager() internal view returns (address) { + return address(creditClockStorage().accessControl); + } + + /** + * @notice Sets rate to apply from this block onward + * @param _ratePerBlock ABDKMathQuad new rate per block to apply from this block onward + */ + function setRatePerBlock(bytes16 _ratePerBlock) internal { + CreditClockData storage data = creditClockStorage(); + data.rateStartValue = getRate(block.number); + data.rateStartBlock = block.number; + data.ratePerBlock = _ratePerBlock; + + emit SetRatePerBlock( + data.rateStartBlock, + data.rateStartValue, + data.ratePerBlock + ); + } + + /** + * @notice Calculates `rateStartValue * (1 / ((1 + ratePerBlock)^blockNumber - rateStartBlock)))` + * @param blockNumber Block number to get the rate for. 0 for current block. + * @return rate ABDKMathQuad rate calculated for the block number + */ + function getRate(uint256 blockNumber) internal view returns (bytes16 rate) { + CreditClockData storage data = creditClockStorage(); + if (blockNumber == 0) { + blockNumber = block.number; + } else { + if (blockNumber < block.number) { + revert("CreditClock: block number must not be in the past."); + } + } + // slither-disable-next-line divide-before-multiply + rate = data.rateStartValue.mul( + data.one.div( + // b ^ n == 2^(n*log²(b)) + (blockNumber - data.rateStartBlock) + .fromUInt() + .mul(data.one.add(data.ratePerBlock).log_2()) + .pow_2() + ) + ); + } +} diff --git a/packages/contracts/test/diamond/DiamondTest.t.sol b/packages/contracts/test/diamond/DiamondTest.t.sol index c63fb52b3..7507b9209 100644 --- a/packages/contracts/test/diamond/DiamondTest.t.sol +++ b/packages/contracts/test/diamond/DiamondTest.t.sol @@ -12,7 +12,7 @@ contract TestDiamond is DiamondTestSetup { } function testHasMultipleFacets() public { - assertEq(facetAddressList.length, 18); + assertEq(facetAddressList.length, 19); } function testFacetsHaveCorrectSelectors() public { diff --git a/packages/contracts/test/diamond/DiamondTestSetup.sol b/packages/contracts/test/diamond/DiamondTestSetup.sol index 5761c5c59..4a5b0170f 100644 --- a/packages/contracts/test/diamond/DiamondTestSetup.sol +++ b/packages/contracts/test/diamond/DiamondTestSetup.sol @@ -14,6 +14,7 @@ import {CollectableDustFacet} from "../../src/dollar/facets/CollectableDustFacet import {CreditNftManagerFacet} from "../../src/dollar/facets/CreditNftManagerFacet.sol"; import {CreditNftRedemptionCalculatorFacet} from "../../src/dollar/facets/CreditNftRedemptionCalculatorFacet.sol"; import {CreditRedemptionCalculatorFacet} from "../../src/dollar/facets/CreditRedemptionCalculatorFacet.sol"; +import {CreditClockFacet} from "../../src/dollar/facets/CreditClockFacet.sol"; import {CurveDollarIncentiveFacet} from "../../src/dollar/facets/CurveDollarIncentiveFacet.sol"; import {DiamondCutFacet} from "../../src/dollar/facets/DiamondCutFacet.sol"; import {DiamondLoupeFacet} from "../../src/dollar/facets/DiamondLoupeFacet.sol"; @@ -56,6 +57,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { StakingFormulasFacet stakingFormulasFacet; TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacet; UbiquityPoolFacet ubiquityPoolFacet; + CreditClockFacet creditClockFacet; // diamond facet implementation instances (should not be used in tests, use only on upgrades) AccessControlFacet accessControlFacetImplementation; @@ -76,6 +78,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { StakingFormulasFacet stakingFormulasFacetImplementation; TWAPOracleDollar3poolFacet twapOracleDollar3PoolFacetImplementation; UbiquityPoolFacet ubiquityPoolFacetImplementation; + CreditClockFacet creditClockFacetImplementation; // facet names with addresses string[] facetNames; @@ -107,6 +110,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { bytes4[] selectorsOfStakingFormulasFacet; bytes4[] selectorsOfTWAPOracleDollar3poolFacet; bytes4[] selectorsOfUbiquityPoolFacet; + bytes4[] selectorsOfCreditClockFacet; /// @notice Deploys diamond and connects facets function setUp() public virtual { @@ -563,6 +567,20 @@ abstract contract DiamondTestSetup is DiamondTestHelper { (dollarMintExcessFacetImplementation.distributeDollars.selector) ); + // Credit Clock Facet + selectorsOfCreditClockFacet.push( + (creditClockFacetImplementation.getRate.selector) + ); + selectorsOfCreditClockFacet.push( + creditClockFacetImplementation.setRatePerBlock.selector + ); + selectorsOfCreditClockFacet.push( + (creditClockFacetImplementation.setManager.selector) + ); + selectorsOfCreditClockFacet.push( + (creditClockFacetImplementation.getManager.selector) + ); + // deploy facet implementation instances accessControlFacetImplementation = new AccessControlFacet(); bondingCurveFacetImplementation = new BondingCurveFacet(); @@ -582,6 +600,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { stakingFormulasFacetImplementation = new StakingFormulasFacet(); twapOracleDollar3PoolFacetImplementation = new TWAPOracleDollar3poolFacet(); ubiquityPoolFacetImplementation = new UbiquityPoolFacet(); + creditClockFacetImplementation = new CreditClockFacet(); // prepare diamond init args diamondInit = new DiamondInit(); @@ -603,7 +622,8 @@ abstract contract DiamondTestSetup is DiamondTestHelper { "StakingFacet", "StakingFormulasFacet", "TWAPOracleDollar3poolFacet", - "UbiquityPoolFacet" + "UbiquityPoolFacet", + "CreditClockFacet" ]; DiamondInit.Args memory initArgs = DiamondInit.Args({ admin: admin, @@ -623,7 +643,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { ) }); - FacetCut[] memory cuts = new FacetCut[](18); + FacetCut[] memory cuts = new FacetCut[](19); cuts[0] = ( FacetCut({ @@ -755,6 +775,13 @@ abstract contract DiamondTestSetup is DiamondTestHelper { functionSelectors: selectorsOfUbiquityPoolFacet }) ); + cuts[18] = ( + FacetCut({ + facetAddress: address(creditClockFacetImplementation), + action: FacetCutAction.Add, + functionSelectors: selectorsOfCreditClockFacet + }) + ); // deploy diamond vm.prank(owner); @@ -785,6 +812,7 @@ abstract contract DiamondTestSetup is DiamondTestHelper { address(diamond) ); ubiquityPoolFacet = UbiquityPoolFacet(address(diamond)); + creditClockFacet = CreditClockFacet(address(diamond)); // get all addresses facetAddressList = diamondLoupeFacet.facetAddresses(); diff --git a/packages/contracts/test/diamond/facets/CreditClockFacet.t.sol b/packages/contracts/test/diamond/facets/CreditClockFacet.t.sol new file mode 100644 index 000000000..0b5b50cdc --- /dev/null +++ b/packages/contracts/test/diamond/facets/CreditClockFacet.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {CreditClockFacet} from "../../../src/dollar/facets/CreditClockFacet.sol"; +import "abdk/ABDKMathQuad.sol"; +import "../DiamondTestSetup.sol"; + +contract CreditClockFacetTest is DiamondTestSetup { + using ABDKMathQuad for uint256; + using ABDKMathQuad for bytes16; + + function setUp() public override { + super.setUp(); + } + + function testSetManager_ShouldRevert_WhenNotAdmin() public { + vm.prank(address(0x123abc)); + vm.expectRevert("Manager: Caller is not admin"); + creditClockFacet.setManager(address(0x123abc)); + } + + function testSetManager_ShouldSetDiamond() public { + address newDiamond = address(0x123abc); + vm.prank(admin); + creditClockFacet.setManager(newDiamond); + require(creditClockFacet.getManager() == newDiamond); + } + + function testGetManager_ShouldGet() public view { + creditClockFacet.getManager(); + } + + function testSetRatePerBlock_ShouldRevert_WhenNotAdmin() public { + vm.prank(address(0x123abc)); + vm.expectRevert("Manager: Caller is not admin"); + creditClockFacet.setRatePerBlock(uint256(1).fromUInt()); + } + + function testSetRatePerBlock_Default() public { + vm.prank(admin); + creditClockFacet.setRatePerBlock(uint256(0).fromUInt()); + } + + function testGetRate_ShouldRevert_WhenBlockIsInThePast() public { + vm.roll(block.number + 10); + vm.expectRevert("CreditClock: block number must not be in the past."); + creditClockFacet.getRate(block.number - 1); + } +} diff --git a/packages/contracts/test/dollar/core/CreditClock.t.sol b/packages/contracts/test/dollar/core/CreditClock.t.sol deleted file mode 100644 index da99ac847..000000000 --- a/packages/contracts/test/dollar/core/CreditClock.t.sol +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {CreditClock} from "../../../src/dollar/core/CreditClock.sol"; -import "abdk/ABDKMathQuad.sol"; -import "../../helpers/LocalTestHelper.sol"; - -contract CreditClockTest is LocalTestHelper { - using ABDKMathQuad for uint256; - using ABDKMathQuad for bytes16; - - CreditClock creditClock; - - bytes16 private immutable one = uint256(1).fromUInt(); - - event SetRatePerBlock( - uint256 rateStartBlock, - bytes16 rateStartValue, - bytes16 ratePerBlock - ); - - function setUp() public override { - super.setUp(); - creditClock = new CreditClock( - address(diamond), - uint256(1000000).fromUInt(), - uint256(1).fromUInt().div(uint256(100).fromUInt()) - ); - } - - function testSetManager_ShouldRevert_WhenNotAdmin() public { - vm.prank(address(0x123abc)); - vm.expectRevert("CreditClock: not admin"); - creditClock.setManager(address(0x123abc)); - } - - function testSetManager_ShouldSetDiamond() public { - address newDiamond = address(0x123abc); - vm.prank(admin); - creditClock.setManager(newDiamond); - require(creditClock.getManager() == newDiamond); - } - - function testSetRatePerBlock_ShouldRevert_WhenNotAdmin() public { - vm.prank(address(0x123abc)); - vm.expectRevert("CreditClock: not admin"); - creditClock.setRatePerBlock(uint256(1).fromUInt()); - } - - function testGetRate_ShouldRevert_WhenBlockIsInThePast() public { - vm.roll(block.number + 10); - vm.expectRevert("CreditClock: block number must not be in the past."); - creditClock.getRate(block.number - 1); - } - - /// @dev Calculates b raised to the power of n. - /// @param b ABDKMathQuad - /// @param n ABDKMathQuad - /// @return ABDKMathQuad b ^ n - function pow(bytes16 b, bytes16 n) private pure returns (bytes16) { - // b ^ n == 2^(n*log²(b)) - return n.mul(b.log_2()).pow_2(); - } - - /// @dev Calculate rateStartValue * ( 1 / ( (1 + ratePerBlock) ^ blockDelta) ) ) - /// @param _rateStartValue ABDKMathQuad The initial value of the rate. - /// @param _ratePerBlock ABDKMathQuad The rate per block. - /// @param blockDelta How many blocks after the rate was set. - /// @return rate ABDKMathQuad The rate calculated. - function calculateRate( - bytes16 _rateStartValue, - bytes16 _ratePerBlock, - uint256 blockDelta - ) public view returns (bytes16 rate) { - rate = _rateStartValue.mul( - one.div(pow(one.add(_ratePerBlock), (blockDelta).fromUInt())) - ); - } - - function test() public { - uint256 rateStartBlock = block.number; - bytes16 rateStartValue = uint256(1000000).fromUInt(); - bytes16 ratePerBlock = uint256(1).fromUInt().div( - uint256(100).fromUInt() - ); - - require(creditClock.rateStartBlock() == rateStartBlock); - require(creditClock.rateStartValue() == rateStartValue); - require(creditClock.ratePerBlock() == ratePerBlock); - - require(creditClock.getRate(0) == rateStartValue); - - for (uint256 i = 0; i < 1000; i++) { - require( - creditClock.getRate(rateStartBlock + i) == - calculateRate(rateStartValue, ratePerBlock, i) - ); - } - - rateStartBlock += 3578; - rateStartValue = calculateRate(rateStartValue, ratePerBlock, 3578); - ratePerBlock = uint256(2).fromUInt().div(uint256(100).fromUInt()); - - vm.expectEmit(false, false, false, true); - emit SetRatePerBlock(rateStartBlock, rateStartValue, ratePerBlock); - vm.roll(rateStartBlock); - vm.prank(admin); - creditClock.setRatePerBlock(ratePerBlock); - - require(creditClock.rateStartBlock() == rateStartBlock); - require(creditClock.rateStartValue() == rateStartValue); - require(creditClock.ratePerBlock() == ratePerBlock); - - require(creditClock.getRate(0) == rateStartValue); - - for (uint256 i = 0; i < 1000; i++) { - require( - creditClock.getRate(rateStartBlock + i) == - calculateRate(rateStartValue, ratePerBlock, i) - ); - } - - rateStartBlock += 8447483; - rateStartValue = calculateRate(rateStartValue, ratePerBlock, 8447483); - ratePerBlock = uint256(3).fromUInt().div(uint256(100).fromUInt()); - - vm.expectEmit(false, false, false, true); - emit SetRatePerBlock(rateStartBlock, rateStartValue, ratePerBlock); - vm.roll(rateStartBlock); - vm.prank(admin); - creditClock.setRatePerBlock(ratePerBlock); - - require(creditClock.rateStartBlock() == rateStartBlock); - require(creditClock.rateStartValue() == rateStartValue); - require(creditClock.ratePerBlock() == ratePerBlock); - - require(creditClock.getRate(0) == rateStartValue); - - for (uint256 i = 0; i < 1000; i++) { - require( - creditClock.getRate(rateStartBlock + i) == - calculateRate(rateStartValue, ratePerBlock, i) - ); - } - - rateStartBlock += 1345; - rateStartValue = calculateRate(rateStartValue, ratePerBlock, 1345); - ratePerBlock = uint256(17).fromUInt().div(uint256(100).fromUInt()); - - vm.expectEmit(false, false, false, true); - emit SetRatePerBlock(rateStartBlock, rateStartValue, ratePerBlock); - vm.roll(rateStartBlock); - vm.prank(admin); - creditClock.setRatePerBlock(ratePerBlock); - - require(creditClock.rateStartBlock() == rateStartBlock); - require(creditClock.rateStartValue() == rateStartValue); - require(creditClock.ratePerBlock() == ratePerBlock); - - require(creditClock.getRate(0) == rateStartValue); - - for (uint256 i = 0; i < 1000; i++) { - require( - creditClock.getRate(rateStartBlock + i) == - calculateRate(rateStartValue, ratePerBlock, i) - ); - } - } -}