Skip to content

Commit

Permalink
Merge pull request #113 from Threshold-USD/feat/update-governance-del…
Browse files Browse the repository at this point in the history
…ay-time-in-thusd

feat: add increase governance delay time function
  • Loading branch information
vzotova authored May 10, 2024
2 parents 34256fc + 4180d3c commit 3424690
Show file tree
Hide file tree
Showing 26 changed files with 706 additions and 364 deletions.
3 changes: 3 additions & 0 deletions packages/contracts/contracts/Interfaces/ITHUSDToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ interface ITHUSDToken is IERC20Metadata, IERC2612 {
event StabilityPoolAddressAdded(address _newStabilityPoolAddress);
event BorrowerOperationsAddressAdded(address _newBorrowerOperationsAddress);

event GovernanceTimeDelayIncreased(uint256 _newGovernanceTimeDelay);

event THUSDTokenBalanceUpdated(address _user, uint256 _amount);

// --- External Functions ---
Expand All @@ -30,4 +32,5 @@ interface ITHUSDToken is IERC20Metadata, IERC2612 {
// --- Governance functions ---
function startRevokeMintList(address _account) external;
function finalizeRevokeMintList() external;
function increaseGovernanceTimeDelay(uint256 _newGovernanceTimeDelay) external;
}
1 change: 1 addition & 0 deletions packages/contracts/contracts/PCV.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ contract PCV is IPCV, Ownable, CheckContract, SendCollateral {

constructor(uint256 _governanceTimeDelay) {
governanceTimeDelay = _governanceTimeDelay;
require(governanceTimeDelay <= 30 weeks, "Governance delay is too big");
}

modifier onlyAfterDebtPaid() {
Expand Down
18 changes: 17 additions & 1 deletion packages/contracts/contracts/THUSDToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ contract THUSDToken is Ownable, CheckContract, ITHUSDToken {
mapping(address => bool) public burnList;
mapping(address => bool) public mintList;

uint256 public immutable governanceTimeDelay;
uint256 public governanceTimeDelay;

address public pendingTroveManager;
address public pendingStabilityPool;
Expand Down Expand Up @@ -109,6 +109,22 @@ contract THUSDToken is Ownable, CheckContract, ITHUSDToken {
_;
}

function increaseGovernanceTimeDelay(
uint256 _newGovernanceTimeDelay
)
external
onlyOwner
{
require(
_newGovernanceTimeDelay > governanceTimeDelay,
"The governance time delay can only be increased"
);
require(_newGovernanceTimeDelay <= 30 weeks, "Governance delay is too big");

governanceTimeDelay = _newGovernanceTimeDelay;
emit GovernanceTimeDelayIncreased(_newGovernanceTimeDelay);
}

// --- Governance ---

function startRevokeMintList(address _account)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ contract EchidnaTester {
address(0),
address(0),
address(0),
90 * 24 * 60 * 60
15 * 24 * 60 * 60
);

collSurplusPool = new CollSurplusPool();
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/test/BorrowerOperationsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ contract('BorrowerOperations', async accounts => {
THUSD_GAS_COMPENSATION = await borrowerOperations.THUSD_GAS_COMPENSATION()
MIN_NET_DEBT = await borrowerOperations.MIN_NET_DEBT()
BORROWING_FEE_FLOOR = await borrowerOperations.BORROWING_FEE_FLOOR()

delay = (await contracts.thusdToken.governanceTimeDelay()).toNumber()

})
Expand Down
163 changes: 162 additions & 1 deletion packages/contracts/test/THUSDTokenTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ const {
toBN,
assertRevert,
dec,
ZERO_ADDRESS,
ZERO_ADDRESS,
ONE_HUNDRED_DAYS_IN_SECONDS,
TWO_HUNDREDS_DAYS_IN_SECONDS,
getLatestBlockTimestamp,
fastForwardTime,
getEventArgByIndex
Expand Down Expand Up @@ -393,6 +395,37 @@ contract('THUSDToken', async accounts => {
newBorrowerOperations = await BorrowerOperationsTester.new()
})

it('increaseGovernanceTimeDelay(): increases the governance time delay', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
await thusdTokenTester.increaseGovernanceTimeDelay(TWO_HUNDREDS_DAYS_IN_SECONDS, { from: owner })
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), TWO_HUNDREDS_DAYS_IN_SECONDS)
})

it('increaseGovernanceTimeDelay(): fails trying to decrease the governance time delay', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
await assertRevert(thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS - 1, { from: owner }),
'The governance time delay can only be increased')
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
})

it('increaseGovernanceTimeDelay(): fails trying to increase the governance time delay above the max cap', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
await assertRevert(thusdTokenTester.increaseGovernanceTimeDelay(20000000, { from: owner }),
'Governance delay is too big')
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
})

it('increaseGovernanceTimeDelay(): reverts when caller is not owner', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
await assertRevert(thusdTokenTester.increaseGovernanceTimeDelay(TWO_HUNDREDS_DAYS_IN_SECONDS, { from: alice }),
'Ownable: caller is not the owner')
assert.equal((await thusdTokenTester.governanceTimeDelay()).toNumber(), ONE_HUNDRED_DAYS_IN_SECONDS)
})

it('startAddContracts(): reverts when caller is not owner', async () => {
await assertRevert(
thusdTokenTester.startAddContracts(
Expand Down Expand Up @@ -535,6 +568,47 @@ contract('THUSDToken', async accounts => {
assert.equal(getEventArgByIndex(tx, "StabilityPoolAddressAdded", 0), newStabilityPool.address)
assert.equal(getEventArgByIndex(tx, "BorrowerOperationsAddressAdded", 0), newBorrowerOperations.address)
})

it('finalizeAddContracts(): reverts when the governance time was increased, but not enough time had passed', async () => {
await thusdTokenTester.startAddContracts(
newTroveManager.address, newStabilityPool.address, newBorrowerOperations.address,
{ from: owner }
)
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS - 10, web3.currentProvider)

await assertRevert(
thusdTokenTester.finalizeAddContracts(
{ from: owner }),
"Governance delay has not elapsed")
})

it('finalizeAddContracts(): enables new system contracts roles after increasing delay governance time', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await thusdTokenTester.startAddContracts(
newTroveManager.address, newStabilityPool.address, newBorrowerOperations.address,
{ from: owner }
)
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS, web3.currentProvider)

let tx = await thusdTokenTester.finalizeAddContracts({ from: owner })

assert.equal(await thusdTokenTester.pendingTroveManager(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.pendingStabilityPool(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.pendingBorrowerOperations(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.addContractsInitiated(), 0)

assert.isTrue(await thusdTokenTester.burnList(troveManager.address))
assert.isTrue(await thusdTokenTester.burnList(newTroveManager.address))
assert.isTrue(await thusdTokenTester.burnList(stabilityPool.address))
assert.isTrue(await thusdTokenTester.burnList(newStabilityPool.address))
assert.isTrue(await thusdTokenTester.burnList(newBorrowerOperations.address))
assert.isTrue(await thusdTokenTester.burnList(borrowerOperations.address))

assert.equal(getEventArgByIndex(tx, "TroveManagerAddressAdded", 0), newTroveManager.address)
assert.equal(getEventArgByIndex(tx, "StabilityPoolAddressAdded", 0), newStabilityPool.address)
assert.equal(getEventArgByIndex(tx, "BorrowerOperationsAddressAdded", 0), newBorrowerOperations.address)
})
})

it('startRevokeMintList(): reverts when caller is not owner', async () => {
Expand Down Expand Up @@ -631,6 +705,37 @@ contract('THUSDToken', async accounts => {
assert.isFalse(await thusdTokenTester.mintList(borrowerOperations.address))
})

it('finalizeRevokeMintList(): reverts when the governance time was increased, but not enough time had passed', async () => {
await thusdTokenTester.startRevokeMintList(
borrowerOperations.address,
{ from: owner }
)

await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS - 10, web3.currentProvider)

await assertRevert(
thusdTokenTester.finalizeRevokeMintList(
{ from: owner }),
"Governance delay has not elapsed")
})

it('finalizeRevokeMintList(): enables new system contracts roles after increasing delay governance time', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await thusdTokenTester.startRevokeMintList(
borrowerOperations.address,
{ from: owner }
)
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS, web3.currentProvider)

await thusdTokenTester.finalizeRevokeMintList({ from: owner })

assert.equal(await thusdTokenTester.pendingRevokedMintAddress(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.revokeMintListInitiated(), 0)

assert.isFalse(await thusdTokenTester.mintList(borrowerOperations.address))
})

it('startAddMintList(): reverts when caller is not owner', async () => {
await assertRevert(
thusdTokenTester.startAddMintList(
Expand Down Expand Up @@ -716,6 +821,31 @@ contract('THUSDToken', async accounts => {
assert.isTrue(await thusdTokenTester.mintList(alice))
})

it('finalizeAddMintList(): reverts when the governance time was increased, but not enough time had passed', async () => {
await thusdTokenTester.startAddMintList(alice, { from: owner })

await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS - 10, web3.currentProvider)

await assertRevert(
thusdTokenTester.finalizeAddMintList(
{ from: owner }),
"Governance delay has not elapsed")
})

it('finalizeAddMintList(): enables new system contracts roles after increasing delay governance time', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await thusdTokenTester.startAddMintList(alice, { from: owner })
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS, web3.currentProvider)

await thusdTokenTester.finalizeAddMintList({ from: owner })

assert.equal(await thusdTokenTester.pendingAddedMintAddress(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.addMintListInitiated(), 0)

assert.isTrue(await thusdTokenTester.mintList(alice))
})

it('startRevokeBurnList(): reverts when caller is not owner', async () => {
await assertRevert(
thusdTokenTester.startRevokeBurnList(
Expand Down Expand Up @@ -809,6 +939,37 @@ contract('THUSDToken', async accounts => {

assert.isFalse(await thusdTokenTester.burnList(borrowerOperations.address))
})

it('finalizeRevokeBurnList(): reverts when the governance time was increased, but not enough time had passed', async () => {
await thusdTokenTester.startRevokeBurnList(
borrowerOperations.address,
{ from: owner }
)

await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS - 10, web3.currentProvider)

await assertRevert(
thusdTokenTester.finalizeRevokeBurnList(
{ from: owner }),
"Governance delay has not elapsed")
})

it('finalizeRevokeBurnList(): enables new system contracts roles after increasing delay governance time', async () => {
await thusdTokenTester.increaseGovernanceTimeDelay(ONE_HUNDRED_DAYS_IN_SECONDS, { from: owner })
await thusdTokenTester.startRevokeBurnList(
borrowerOperations.address,
{ from: owner }
)
await fastForwardTime(ONE_HUNDRED_DAYS_IN_SECONDS, web3.currentProvider)

await thusdTokenTester.finalizeRevokeBurnList({ from: owner })

assert.equal(await thusdTokenTester.pendingRevokedBurnAddress(), ZERO_ADDRESS)
assert.equal(await thusdTokenTester.revokeBurnListInitiated(), 0)

assert.isFalse(await thusdTokenTester.burnList(borrowerOperations.address))
})
}
}
describe('Basic token functions, without Proxy', async () => {
Expand Down
17 changes: 9 additions & 8 deletions packages/contracts/utils/deploymentHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ const {

const ZERO_ADDRESS = '0x' + '0'.repeat(40)
const maxBytes32 = '0x' + 'f'.repeat(64)

const delay = 90 * 24 * 60 * 60 // 90 days in seconds

const thusdDelay = 15 * 24 * 60 * 60 // 15 days in seconds
const pcvDelay = 120 * 24 * 60 * 60 // 120 days in seconds

class DeploymentHelper {

Expand Down Expand Up @@ -73,9 +74,9 @@ class DeploymentHelper {
ZERO_ADDRESS,
ZERO_ADDRESS,
ZERO_ADDRESS,
delay
thusdDelay
)
const pcv = await PCV.new(delay)
const pcv = await PCV.new(pcvDelay)

PCV.setAsDeployed(pcv)
THUSDToken.setAsDeployed(thusdToken)
Expand Down Expand Up @@ -143,14 +144,14 @@ class DeploymentHelper {
testerContracts.troveManager.address,
testerContracts.stabilityPool.address,
testerContracts.borrowerOperations.address,
delay
thusdDelay
)
testerContracts.thusdOwner = await THUSDOwner.new(
governorBravo,
testerContracts.thusdToken.address,
integrationsGuild
)
testerContracts.pcv = await PCV.new(delay)
testerContracts.pcv = await PCV.new(pcvDelay)

let index = 0;
for (const account of accounts) {
Expand All @@ -172,7 +173,7 @@ class DeploymentHelper {
ZERO_ADDRESS,
ZERO_ADDRESS,
ZERO_ADDRESS,
delay
thusdDelay
)
return contracts
}
Expand All @@ -182,7 +183,7 @@ class DeploymentHelper {
contracts.troveManager.address,
contracts.stabilityPool.address,
contracts.borrowerOperations.address,
delay
thusdDelay
)
return contracts
}
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/utils/testHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -1233,6 +1233,8 @@ class TestHelper {
}

TestHelper.ZERO_ADDRESS = '0x' + '0'.repeat(40)
TestHelper.ONE_HUNDRED_DAYS_IN_SECONDS = 100 * 24 * 60 * 60
TestHelper.TWO_HUNDREDS_DAYS_IN_SECONDS = 200 * 24 * 60 * 60
TestHelper.maxBytes32 = '0x' + 'f'.repeat(64)
TestHelper._100pct = '1000000000000000000'
TestHelper.latestRandomSeed = 31337
Expand Down
1 change: 1 addition & 0 deletions packages/dev-frontend/src/components/Vault/Adjusting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ export const Adjusting = (props: AdjustingProps): JSX.Element => {
<Card variant="tooltip" sx={{ width: "240px" }}>
This amount is deducted from the borrowed amount as a one-time fee. There are no
recurring fees for borrowing, which is thus interest-free.
<Link variant="cardLinks" href="https://docs.threshold.network/applications/threshold-usd/borrowing#how-is-the-borrowing-fee-calculated" target="_blank">Read more</Link>
</Card>
}
/>
Expand Down
3 changes: 2 additions & 1 deletion packages/dev-frontend/src/components/Vault/Opening.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useState } from "react";
import { Button, Card, Flex, Link, Spinner } from "theme-ui";
import { Button, Card, Flex, Link, Spinner, Text } from "theme-ui";
import {
LiquityStoreState as ThresholdStoreState,
Decimal,
Expand Down Expand Up @@ -213,6 +213,7 @@ export const Opening = (props: OpeningProps): JSX.Element => {
<Card variant="tooltip" sx={{ width: "240px" }}>
This amount is deducted from the borrowed amount as a one-time fee. There are no
recurring fees for borrowing, which is thus interest-free.
<Link variant="cardLinks" href="https://docs.threshold.network/applications/threshold-usd/borrowing#how-is-the-borrowing-fee-calculated" target="_blank">Read more</Link>
</Card>
}
/>
Expand Down
3 changes: 2 additions & 1 deletion packages/dev-frontend/src/components/Vault/VaultEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { Card, Flex } from "theme-ui";
import { Card, Flex, Link } from "theme-ui";

import {
Percent,
Expand Down Expand Up @@ -116,6 +116,7 @@ export const VaultEditor = ({
<Card variant="tooltip" sx={{ width: "240px" }}>
This amount is deducted from the borrowed amount as a one-time fee. There are no
recurring fees for borrowing, which is thus interest-free.
<Link variant="cardLinks" href="https://docs.threshold.network/applications/threshold-usd/borrowing#how-is-the-borrowing-fee-calculated" target="_blank">Read more</Link>
</Card>
}
/>
Expand Down
Loading

0 comments on commit 3424690

Please sign in to comment.