diff --git a/implementation/contracts/system/TBTCSystem.sol b/implementation/contracts/system/TBTCSystem.sol index 27feea04f..44f5da039 100644 --- a/implementation/contracts/system/TBTCSystem.sol +++ b/implementation/contracts/system/TBTCSystem.sol @@ -25,6 +25,15 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog { using SafeMath for uint256; + event LotSizesUpdateStarted(uint256[] _lotSizes, uint256 _timestamp); + event SignerFeeDivisorUpdateStarted(uint256 _signerFeeDivisor, uint256 _timestamp); + event CollateralizationThresholdsUpdateStarted( + uint128 _initialCollateralizedPercent, + uint128 _undercollateralizedThresholdPercent, + uint128 _severelyUndercollateralizedThresholdPercent, + uint256 _timestamp + ); + event LotSizesUpdated(uint256[] _lotSizes); event AllowNewDepositsUpdated(bool _allowNewDeposits); event SignerFeeDivisorUpdated(uint256 _signerFeeDivisor); @@ -51,6 +60,18 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog { uint128 private severelyUndercollateralizedThresholdPercent = 110; // percent uint256[] lotSizesSatoshis = [10**5, 10**6, 10**7, 2 * 10**7, 5 * 10**7, 10**8]; // [0.001, 0.01, 0.1, 0.2, 0.5, 1.0] BTC + uint256 constant governanceTimeDelay = 6 hours; + + uint256 private signerFeeDivisorChangeInitiated; + uint256 private lotSizesChangeInitiated; + uint256 private collateralizationThresholdsChangeInitiated; + + uint256 private newSignerFeeDivisor; + uint256[] newLotSizesSatoshis; + uint128 private newInitialCollateralizedPercent; + uint128 private newUndercollateralizedThresholdPercent; + uint128 private newSeverelyUndercollateralizedThresholdPercent; + constructor(address _priceFeed, address _relay) public { priceFeed = _priceFeed; relay = _relay; @@ -128,57 +149,50 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog { pausedDuration.sub(block.timestamp.sub(pausedTimestamp)); } + /// @notice Gets the system signer fee divisor. + /// @return The signer fee divisor. + function getSignerFeeDivisor() external view returns (uint256) { return signerFeeDivisor; } + /// @notice Set the system signer fee divisor. + /// @dev This can be finalized by calling `finalizeSignerFeeDivisorUpdate` + /// Anytime after `governanceTimeDelay` has elapsed. /// @param _signerFeeDivisor The signer fee divisor. - function setSignerFeeDivisor(uint256 _signerFeeDivisor) + function beginSignerFeeDivisorUpdate(uint256 _signerFeeDivisor) external onlyOwner { require(_signerFeeDivisor > 9, "Signer fee divisor must be greater than 9, for a signer fee that is <= 10%."); - signerFeeDivisor = _signerFeeDivisor; - emit SignerFeeDivisorUpdated(_signerFeeDivisor); + newSignerFeeDivisor = _signerFeeDivisor; + signerFeeDivisorChangeInitiated = block.timestamp; + emit SignerFeeDivisorUpdateStarted(_signerFeeDivisor, block.timestamp); } - /// @notice Gets the system signer fee divisor. - /// @return The signer fee divisor. - function getSignerFeeDivisor() external view returns (uint256) { return signerFeeDivisor; } - /// @notice Set the allowed deposit lot sizes. /// @dev Lot size array should always contain 10**8 satoshis (1BTC value) + /// This can be finalized by calling `finalizeLotSizesUpdate` + /// Anytime after `governanceTimeDelay` has elapsed. /// @param _lotSizes Array of allowed lot sizes. - function setLotSizes(uint256[] calldata _lotSizes) external onlyOwner { + function beginLotSizesUpdate(uint256[] calldata _lotSizes) + external onlyOwner + { for( uint i = 0; i < _lotSizes.length; i++){ if (_lotSizes[i] == 10**8){ lotSizesSatoshis = _lotSizes; - emit LotSizesUpdated(_lotSizes); + emit LotSizesUpdateStarted(_lotSizes, block.timestamp); + newLotSizesSatoshis = _lotSizes; + lotSizesChangeInitiated = block.timestamp; return; } } revert("Lot size array must always contain 1BTC"); } - /// @notice Gets the allowed lot sizes - /// @return Uint256 array of allowed lot sizes - function getAllowedLotSizes() external view returns (uint256[] memory){ - return lotSizesSatoshis; - } - - /// @notice Check if a lot size is allowed. - /// @param _lotSizeSatoshis Lot size to check. - /// @return True if lot size is allowed, false otherwise. - function isAllowedLotSize(uint256 _lotSizeSatoshis) external view returns (bool){ - for( uint i = 0; i < lotSizesSatoshis.length; i++){ - if (lotSizesSatoshis[i] == _lotSizeSatoshis){ - return true; - } - } - return false; - } - /// @notice Set the system collateralization levels + /// @dev This can be finalized by calling `finalizeCollateralizationThresholdsUpdate` + /// Anytime after `governanceTimeDelay` has elapsed. /// @param _initialCollateralizedPercent default signing bond percent for new deposits /// @param _undercollateralizedThresholdPercent first undercollateralization trigger /// @param _severelyUndercollateralizedThresholdPercent second undercollateralization trigger - function setCollateralizationThresholds( + function beginCollateralizationThresholdsUpdate( uint128 _initialCollateralizedPercent, uint128 _undercollateralizedThresholdPercent, uint128 _severelyUndercollateralizedThresholdPercent @@ -195,14 +209,99 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog { _undercollateralizedThresholdPercent > _severelyUndercollateralizedThresholdPercent, "Severe undercollateralized threshold must be < undercollateralized threshold" ); - initialCollateralizedPercent = _initialCollateralizedPercent; - undercollateralizedThresholdPercent = _undercollateralizedThresholdPercent; - severelyUndercollateralizedThresholdPercent = _severelyUndercollateralizedThresholdPercent; - emit CollateralizationThresholdsUpdated( + + newInitialCollateralizedPercent = _initialCollateralizedPercent; + newUndercollateralizedThresholdPercent = _undercollateralizedThresholdPercent; + newSeverelyUndercollateralizedThresholdPercent = _severelyUndercollateralizedThresholdPercent; + collateralizationThresholdsChangeInitiated = block.timestamp; + emit CollateralizationThresholdsUpdateStarted( _initialCollateralizedPercent, _undercollateralizedThresholdPercent, - _severelyUndercollateralizedThresholdPercent + _severelyUndercollateralizedThresholdPercent, + block.timestamp + ); + } + + modifier onlyAfterDelay(uint256 _changeInitializedTimestamp) { + require(_changeInitializedTimestamp > 0, "Change not initiated"); + require( + block.timestamp.sub(_changeInitializedTimestamp) >= + governanceTimeDelay, + "Timer not elapsed" + ); + _; + } + + /// @notice Finish setting the system signer fee divisor. + /// @dev `beginSignerFeeDivisorUpdate` must be called first, once `governanceTimeDelay` + /// has passed, this function can be called to set the signer fee divisor to the + /// value set in `beginSignerFeeDivisorUpdate` + function finalizeSignerFeeDivisorUpdate() + external + onlyOwner + onlyAfterDelay(signerFeeDivisorChangeInitiated) + { + signerFeeDivisor = newSignerFeeDivisor; + emit SignerFeeDivisorUpdated(newSignerFeeDivisor); + newSignerFeeDivisor = 0; + signerFeeDivisorChangeInitiated = 0; + } + /// @notice Finish setting the accepted system lot sizes. + /// @dev `beginLotSizesUpdate` must be called first, once `governanceTimeDelay` + /// has passed, this function can be called to set the lot sizes to the + /// value set in `beginLotSizesUpdate` + function finalizeLotSizesUpdate() + external + onlyOwner + onlyAfterDelay(lotSizesChangeInitiated) { + + lotSizesSatoshis = newLotSizesSatoshis; + emit LotSizesUpdated(newLotSizesSatoshis); + lotSizesChangeInitiated = 0; + newLotSizesSatoshis.length = 0; + } + + /// @notice Gets the allowed lot sizes + /// @return Uint256 array of allowed lot sizes + function getAllowedLotSizes() external view returns (uint256[] memory){ + return lotSizesSatoshis; + } + + /// @notice Check if a lot size is allowed. + /// @param _lotSizeSatoshis Lot size to check. + /// @return True if lot size is allowed, false otherwise. + function isAllowedLotSize(uint256 _lotSizeSatoshis) external view returns (bool){ + for( uint i = 0; i < lotSizesSatoshis.length; i++){ + if (lotSizesSatoshis[i] == _lotSizeSatoshis){ + return true; + } + } + return false; + } + + /// @notice Finish setting the system collateralization levels + /// @dev `beginCollateralizationThresholdsUpdate` must be called first, once `governanceTimeDelay` + /// has passed, this function can be called to set the collateralization thresholds to the + /// value set in `beginCollateralizationThresholdsUpdate` + function finalizeCollateralizationThresholdsUpdate() + external + onlyOwner + onlyAfterDelay(collateralizationThresholdsChangeInitiated) { + + initialCollateralizedPercent = newInitialCollateralizedPercent; + undercollateralizedThresholdPercent = newUndercollateralizedThresholdPercent; + severelyUndercollateralizedThresholdPercent = newSeverelyUndercollateralizedThresholdPercent; + + emit CollateralizationThresholdsUpdated( + newInitialCollateralizedPercent, + newUndercollateralizedThresholdPercent, + newSeverelyUndercollateralizedThresholdPercent ); + + newInitialCollateralizedPercent = 0; + newUndercollateralizedThresholdPercent = 0; + newSeverelyUndercollateralizedThresholdPercent = 0; + collateralizationThresholdsChangeInitiated = 0; } /// @notice Get the system undercollateralization level for new deposits @@ -220,6 +319,34 @@ contract TBTCSystem is Ownable, ITBTCSystem, DepositLog { return initialCollateralizedPercent; } + /// @notice Get the time remaining until the collateralization thresholds can be updated. + function getRemainingCollateralizationUpdateTime() external view returns (uint256) { + return getRemainingChangeTime(collateralizationThresholdsChangeInitiated); + } + + /// @notice Get the time remaining until the lot sizes can be updated. + function getRemainingLotSizesUpdateTime() external view returns (uint256) { + return getRemainingChangeTime(lotSizesChangeInitiated); + } + + /// @notice Get the time remaining until the signer fee divisor can be updated. + function geRemainingSignerFeeDivisorUpdateTime() external view returns (uint256) { + return getRemainingChangeTime(signerFeeDivisorChangeInitiated); + } + + /// @notice Get the time remaining until the function parameter timer value can be updated. + function getRemainingChangeTime(uint256 _changeTimestamp) internal view returns (uint256){ + require(_changeTimestamp > 0, "Update not initiated"); + uint256 elapsed = block.timestamp.sub(_changeTimestamp); + return (elapsed >= governanceTimeDelay)? + 0: + governanceTimeDelay.sub(elapsed); + } + + function getGovernanceTimeDelay() public view returns (uint256) { + return governanceTimeDelay; + } + // Price Feed /// @notice Get the price of one satoshi in wei. diff --git a/implementation/test/TBTCSystemTest.js b/implementation/test/TBTCSystemTest.js index 1c8e5ca2b..f853ad080 100644 --- a/implementation/test/TBTCSystemTest.js +++ b/implementation/test/TBTCSystemTest.js @@ -2,7 +2,7 @@ const {deployAndLinkAll} = require("./helpers/testDeployer.js") const {increaseTime} = require("./helpers/utils.js") const {createSnapshot, restoreSnapshot} = require("./helpers/snapshot.js") const {accounts, contract, web3} = require("@openzeppelin/test-environment") -const {BN, expectRevert} = require("@openzeppelin/test-helpers") +const {BN, expectRevert, expectEvent} = require("@openzeppelin/test-helpers") const {expect} = require("chai") const TBTCSystem = contract.fromArtifact("TBTCSystem") @@ -99,56 +99,407 @@ describe("TBTCSystem", async function() { }) }) - describe("setSignerFeeDivisor", async () => { - it("sets the signer fee", async () => { - await tbtcSystem.setSignerFeeDivisor(new BN("201")) + describe("geRemainingSignerFeeDivisorUpdateTime", async () => { + let totalDelay - const signerFeeDivisor = await tbtcSystem.getSignerFeeDivisor() - expect(signerFeeDivisor).to.eq.BN(new BN("201")) + before(async () => { + totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() }) - it("reverts if msg.sender != owner", async () => { - await expectRevert.unspecified( - tbtcSystem.setSignerFeeDivisor(new BN("201"), { - from: accounts[1], - }), - "", + beforeEach(async () => { + await createSnapshot() + }) + + afterEach(async () => { + await restoreSnapshot() + }) + + it("reverts if update has not been initiated", async () => { + await expectRevert( + tbtcSystem.geRemainingSignerFeeDivisorUpdateTime.call(), + "Update not initiated", + ) + }) + + it("returns total delay if no time has passed ", async () => { + await tbtcSystem.beginSignerFeeDivisorUpdate(new BN("200")) + const remaining = await tbtcSystem.geRemainingSignerFeeDivisorUpdateTime.call() + expect(remaining).to.eq.BN(totalDelay) + }) + + it("returns the correct remaining time", async () => { + await tbtcSystem.beginSignerFeeDivisorUpdate(new BN("200")) + const expectedRemaining = 100 + await increaseTime(totalDelay.toNumber() - expectedRemaining) + + const remaining = await tbtcSystem.geRemainingSignerFeeDivisorUpdateTime.call() + expect([expectedRemaining, expectedRemaining + 1]).to.include.toString( + remaining.toNumber(), ) }) }) - describe("setLotSizes", async () => { - it("sets a different lot size array", async () => { - const blockNumber = await web3.eth.getBlock("latest").number - const lotSizes = [10 ** 8, 10 ** 6] - await tbtcSystem.setLotSizes(lotSizes) + describe("getRemainingLotSizesUpdateTime", async () => { + let totalDelay + const lotSizes = [new BN(10 ** 8), new BN(10 ** 6)] - const eventList = await tbtcSystem.getPastEvents("LotSizesUpdated", { - fromBlock: blockNumber, - toBlock: "latest", - }) - expect(eventList.length).to.equal(1) - expect(eventList[0].returnValues._lotSizes).to.eql([ - "100000000", - "1000000", - ]) // deep equality check + before(async () => { + totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() + }) + + beforeEach(async () => { + await createSnapshot() }) - it("reverts if lot size array is empty", async () => { - const lotSizes = [] + afterEach(async () => { + await restoreSnapshot() + }) + + it("reverts if update has not been initiated", async () => { await expectRevert( - tbtcSystem.setLotSizes(lotSizes), - "Lot size array must always contain 1BTC", + tbtcSystem.getRemainingLotSizesUpdateTime.call(), + "Update not initiated", ) }) - it("reverts if lot size array does not contain a 1BTC lot size", async () => { - const lotSizes = [10 ** 7] + it("returns total delay if no time has passed ", async () => { + await tbtcSystem.beginLotSizesUpdate(lotSizes) + const remaining = await tbtcSystem.getRemainingLotSizesUpdateTime.call() + expect(remaining).to.eq.BN(totalDelay) + }) + + it("returns the correct remaining time", async () => { + await tbtcSystem.beginLotSizesUpdate(lotSizes) + const expectedRemaining = 100 + await increaseTime(totalDelay.toNumber() - expectedRemaining) + + const remaining = await tbtcSystem.getRemainingLotSizesUpdateTime.call() + expect([expectedRemaining, expectedRemaining + 1]).to.include.toString( + remaining.toNumber(), + ) + }) + }) + + describe("getRemainingCollateralizationUpdateTime", async () => { + let totalDelay + + before(async () => { + totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() + }) + + beforeEach(async () => { + await createSnapshot() + }) + + afterEach(async () => { + await restoreSnapshot() + }) + + it("reverts if update has not been initiated", async () => { await expectRevert( - tbtcSystem.setLotSizes(lotSizes), - "Lot size array must always contain 1BTC", + tbtcSystem.getRemainingCollateralizationUpdateTime.call(), + "Update not initiated", ) }) + + it("returns total delay if no time has passed ", async () => { + await tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("150"), + new BN("130"), + new BN("120"), + ) + const remaining = await tbtcSystem.getRemainingCollateralizationUpdateTime.call() + expect(remaining).to.eq.BN(totalDelay) + }) + + it("returns the correct remaining time", async () => { + await tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("150"), + new BN("130"), + new BN("120"), + ) + const expectedRemaining = 100 + await increaseTime(totalDelay.toNumber() - expectedRemaining) + + const remaining = await tbtcSystem.getRemainingCollateralizationUpdateTime.call() + expect([expectedRemaining, expectedRemaining + 1]).to.include.toString( + remaining.toNumber(), + ) + }) + }) + + describe("update signer fee", async () => { + describe("beginSignerFeeDivisorUpdate", async () => { + const newFee = new BN("201") + it("executes and fires SignerFeeDivisorUpdateStarted event", async () => { + receipt = await tbtcSystem.beginSignerFeeDivisorUpdate(new BN("200")) + + expectEvent(receipt, "SignerFeeDivisorUpdateStarted", { + _signerFeeDivisor: new BN("200"), + }) + }) + + it("overrides previous update and resets timer", async () => { + receipt = await tbtcSystem.beginSignerFeeDivisorUpdate(newFee) + const remainingTime = await tbtcSystem.geRemainingSignerFeeDivisorUpdateTime.call() + const totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() + + expectEvent(receipt, "SignerFeeDivisorUpdateStarted", { + _signerFeeDivisor: newFee, + }) + expect([ + remainingTime.toString(), + remainingTime.toString() - 1, + ]).to.include(totalDelay.toString()) + }) + + it("reverts if msg.sender != owner", async () => { + await expectRevert.unspecified( + tbtcSystem.beginSignerFeeDivisorUpdate(newFee, { + from: accounts[1], + }), + "", + ) + }) + + it("reverts if fee divisor is smaller than 10", async () => { + await expectRevert( + tbtcSystem.beginSignerFeeDivisorUpdate(new BN("9")), + "Signer fee divisor must be greater than 9, for a signer fee that is <= 10%.", + ) + }) + }) + + describe("finalizeSignerFeeDivisorUpdate", async () => { + it("reverts if the governance timer has not elapsed", async () => { + await expectRevert( + tbtcSystem.finalizeSignerFeeDivisorUpdate(), + "Timer not elapsed", + ) + }) + + it("updates signer fee and fires SignerFeeDivisorUpdated event", async () => { + const remainingTime = await tbtcSystem.geRemainingSignerFeeDivisorUpdateTime() + + await increaseTime(remainingTime.toNumber() + 1) + + receipt = await tbtcSystem.finalizeSignerFeeDivisorUpdate() + + const signerFeeDivisor = await tbtcSystem.getSignerFeeDivisor.call() + + expectEvent(receipt, "SignerFeeDivisorUpdated", { + _signerFeeDivisor: new BN("201"), + }) + expect(signerFeeDivisor).to.eq.BN(new BN("201")) + }) + + it("reverts if a change has not been initiated", async () => { + await expectRevert( + tbtcSystem.finalizeSignerFeeDivisorUpdate(), + "Change not initiated", + ) + }) + }) + }) + + describe("update lot sizes", async () => { + const lotSizes = [new BN(10 ** 8), new BN(10 ** 6)] + describe("beginLotSizesUpdate", async () => { + it("executes and emits a LotSizesUpdateStarted event", async () => { + const testSizes = [new BN(10 ** 8), new BN(10 ** 6)] + const block = await web3.eth.getBlock("latest") + const receipt = await tbtcSystem.beginLotSizesUpdate(testSizes) + expectEvent(receipt, "LotSizesUpdateStarted", {}) + expect(receipt.logs[0].args[0][0]).to.eq.BN(testSizes[0]) + expect(receipt.logs[0].args[0][1]).to.eq.BN(testSizes[1]) + expect([ + receipt.logs[0].args[1].toString(), + receipt.logs[0].args[1].toString() - 1, + ]).to.include(block.timestamp.toString()) + }) + + it("overrides previous update and resets timer", async () => { + const block = await web3.eth.getBlock("latest") + const receipt = await tbtcSystem.beginLotSizesUpdate(lotSizes) + const remainingTime = await tbtcSystem.getRemainingLotSizesUpdateTime.call() + const totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() + + expectEvent(receipt, "LotSizesUpdateStarted", {}) + expect(receipt.logs[0].args[0][0]).to.eq.BN(lotSizes[0]) + expect(receipt.logs[0].args[0][1]).to.eq.BN(lotSizes[1]) + expect([ + receipt.logs[0].args[1].toString(), + receipt.logs[0].args[1].toString() - 1, + ]).to.include(block.timestamp.toString()) + expect([ + remainingTime.toString(), + remainingTime.toString() - 1, + ]).to.include(totalDelay.toString()) + }) + + it("reverts if lot size array is empty", async () => { + const lotSizes = [] + await expectRevert( + tbtcSystem.beginLotSizesUpdate(lotSizes), + "Lot size array must always contain 1BTC", + ) + }) + + it("reverts if lot size array does not contain a 1BTC lot size", async () => { + const lotSizes = [10 ** 7] + await expectRevert( + tbtcSystem.beginLotSizesUpdate(lotSizes), + "Lot size array must always contain 1BTC", + ) + }) + }) + + describe("finalizeLotSizesUpdate", async () => { + it("reverts if the governance timer has not elapsed", async () => { + await expectRevert( + tbtcSystem.finalizeLotSizesUpdate(), + "Timer not elapsed", + ) + }) + + it("updates lot sizes and fires LotSizesUpdated event", async () => { + const remainingTime = await tbtcSystem.getRemainingLotSizesUpdateTime() + + await increaseTime(remainingTime.toNumber() + 1) + + receipt = await tbtcSystem.finalizeLotSizesUpdate() + + const currentLotSizes = await tbtcSystem.getAllowedLotSizes.call() + + expectEvent(receipt, "LotSizesUpdated", {}) + expect(receipt.logs[0].args._lotSizes[0]).to.eq.BN(lotSizes[0]) + expect(receipt.logs[0].args._lotSizes[1]).to.eq.BN(lotSizes[1]) + expect(currentLotSizes[0]).to.eq.BN(lotSizes[0]) + expect(currentLotSizes[1]).to.eq.BN(lotSizes[1]) + }) + + it("reverts if a change has not been initiated", async () => { + await expectRevert( + tbtcSystem.finalizeLotSizesUpdate(), + "Change not initiated", + ) + }) + }) + }) + + describe("update collateralization thresholds", async () => { + const initialPercent = new BN("150") + const undercollateralizedPercent = new BN("130") + const severelyUndercollateralizedPercent = new BN("120") + describe("beginCollateralizationThresholdsUpdate", async () => { + it("executes and fires CollateralizationThresholdsUpdateStarted event", async () => { + receipt = await tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("213"), + new BN("156"), + new BN("128"), + ) + + expectEvent(receipt, "CollateralizationThresholdsUpdateStarted", { + _initialCollateralizedPercent: new BN("213"), + _undercollateralizedThresholdPercent: new BN("156"), + _severelyUndercollateralizedThresholdPercent: new BN("128"), + }) + }) + + it("overrides previous update and resets timer", async () => { + receipt = await tbtcSystem.beginCollateralizationThresholdsUpdate( + initialPercent, + undercollateralizedPercent, + severelyUndercollateralizedPercent, + ) + + const remainingTime = await tbtcSystem.getRemainingCollateralizationUpdateTime.call() + const totalDelay = await tbtcSystem.getGovernanceTimeDelay.call() + + expectEvent(receipt, "CollateralizationThresholdsUpdateStarted", { + _initialCollateralizedPercent: initialPercent, + _undercollateralizedThresholdPercent: undercollateralizedPercent, + _severelyUndercollateralizedThresholdPercent: severelyUndercollateralizedPercent, + }) + expect([ + remainingTime.toString(), + remainingTime.toString() - 1, + ]).to.include(totalDelay.toString()) + }) + + it("reverts if Initial collateralized percent > 300", async () => { + await expectRevert( + tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("301"), + new BN("130"), + new BN("120"), + ), + "Initial collateralized percent must be <= 300%", + ) + }) + + it("reverts if Undercollateralized threshold > initial collateralize percent", async () => { + await expectRevert( + tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("150"), + new BN("160"), + new BN("120"), + ), + "Undercollateralized threshold must be < initial collateralized percent", + ) + }) + + it("reverts if Severe undercollateralized threshold > undercollateralized threshold", async () => { + await expectRevert( + tbtcSystem.beginCollateralizationThresholdsUpdate( + new BN("150"), + new BN("130"), + new BN("131"), + ), + "Severe undercollateralized threshold must be < undercollateralized threshold", + ) + }) + }) + + describe("finalizeCollateralizationThresholdsUpdate", async () => { + it("reverts if the governance timer has not elapsed", async () => { + await expectRevert( + tbtcSystem.finalizeCollateralizationThresholdsUpdate(), + "Timer not elapsed", + ) + }) + + it("updates collateralization thresholds and fires CollateralizationThresholdsUpdated event", async () => { + const remainingTime = await tbtcSystem.getRemainingCollateralizationUpdateTime() + + await increaseTime(remainingTime.toNumber() + 1) + + receipt = await tbtcSystem.finalizeCollateralizationThresholdsUpdate() + + const initial = await tbtcSystem.getInitialCollateralizedPercent.call() + const undercollateralized = await tbtcSystem.getUndercollateralizedThresholdPercent.call() + const severelyUndercollateralized = await tbtcSystem.getSeverelyUndercollateralizedThresholdPercent.call() + + expectEvent(receipt, "CollateralizationThresholdsUpdated", { + _initialCollateralizedPercent: initialPercent, + _undercollateralizedThresholdPercent: undercollateralizedPercent, + _severelyUndercollateralizedThresholdPercent: severelyUndercollateralizedPercent, + }) + + expect(initialPercent).to.eq.BN(initial) + expect(undercollateralizedPercent).to.eq.BN(undercollateralized) + expect(severelyUndercollateralizedPercent).to.eq.BN( + severelyUndercollateralized, + ) + }) + + it("reverts if a change has not been initiated", async () => { + await expectRevert( + tbtcSystem.finalizeCollateralizationThresholdsUpdate(), + "Change not initiated", + ) + }) + }) }) describe("emergencyPauseNewDeposits", async () => {