Skip to content

Commit

Permalink
Merge pull request #108 from Threshold-USD/feat/proxy-contract-for-au…
Browse files Browse the repository at this point in the history
…tomated-revoke-of-mint-capability

feat: thusd owner contract for revoking deprecated thusd contracts
  • Loading branch information
evandrosaturnino authored Feb 7, 2024
2 parents a7e0f37 + dddc9fb commit 2e79588
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 4 deletions.
6 changes: 5 additions & 1 deletion packages/contracts/contracts/Interfaces/ITHUSDToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ interface ITHUSDToken is IERC20Metadata, IERC2612 {

event THUSDTokenBalanceUpdated(address _user, uint256 _amount);

// --- Functions ---
// --- External Functions ---
function mintList(address contractAddress) external view returns (bool);
function burnList(address contractAddress) external view returns (bool);

Expand All @@ -26,4 +26,8 @@ interface ITHUSDToken is IERC20Metadata, IERC2612 {
function increaseAllowance(address spender, uint256 addedValue) external returns (bool);

function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool);

// --- Governance functions ---
function startRevokeMintList(address _account) external;
function finalizeRevokeMintList() external;
}
41 changes: 41 additions & 0 deletions packages/contracts/contracts/THUSDOwner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import "./THUSDToken.sol";
import "./Dependencies/CheckContract.sol";
import "./Dependencies/Ownable.sol";

contract THUSDOwner is Ownable, CheckContract {
THUSDToken public immutable thusdToken;

address public immutable governorBravoAddress;

constructor(
address _governorBravoAddress,
address _thusdTokenAddress,
address _integrationsGuild
) {
checkContract(_thusdTokenAddress);
thusdToken = THUSDToken(_thusdTokenAddress);

require(_integrationsGuild != address(0), "Integrations Guild address must be specified");
require(_governorBravoAddress != address(0), "Governor Bravo address must be specified");

governorBravoAddress = _governorBravoAddress;

_transferOwnership(_integrationsGuild);
}

function startRevokeMintList(address _account) external onlyOwner {
thusdToken.startRevokeMintList(_account);
}

function finalizeRevokeMintList() external onlyOwner {
thusdToken.finalizeRevokeMintList();
}

function transferThusdOwnershipToGovernorBravo() external onlyOwner {
thusdToken.transferOwnership(governorBravoAddress);
}
}
2 changes: 1 addition & 1 deletion packages/contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@
"@nomiclabs/hardhat-truffle5": "^2.0.0",
"@nomiclabs/hardhat-web3": "^2.0.0",
"@openzeppelin/contracts": "4.9.2",
"usingtellor": "5.0.4",
"@openzeppelin/hardhat-upgrades": "1.12.0",
"@openzeppelin/test-helpers": "0.5.13",
"eth-gas-reporter": "0.2.22",
"hardhat": "^2.6.1",
"hardhat-gas-reporter": "^1.0.1",
"npm-run-all": "4.1.5",
"solidity-coverage": "0.7.16",
"usingtellor": "5.0.4",
"web3": "1.3.4"
}
}
134 changes: 134 additions & 0 deletions packages/contracts/test/THUSDOwnerTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
const deploymentHelper = require("../utils/deploymentHelpers.js")
const testHelpers = require("../utils/testHelpers.js")

const {
assertRevert,
ZERO_ADDRESS,
getLatestBlockTimestamp,
fastForwardTime,
} = testHelpers.TestHelper

contract('THUSDOwner', async accounts => {
const [owner, integrationsGuild, governorBravo] = accounts;

let thusdOwner
let thusdToken
let borrowerOperations

let delay

beforeEach(async () => {
const contracts = await deploymentHelper.deployTesterContractsHardhat(accounts)
await deploymentHelper.connectCoreContracts(contracts)

thusdOwner = contracts.thusdOwner
thusdToken = contracts.thusdToken
borrowerOperations = contracts.borrowerOperations

await thusdToken.transferOwnership(
thusdOwner.address,
{ from: owner }
)

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

it('owner(): owner is integrations guild', async () => {
await assert.equal(await thusdOwner.owner(), integrationsGuild)
})

it('startRevokeMintList(): reverts when caller is not owner', async () => {
await assertRevert(
thusdOwner.startRevokeMintList(
borrowerOperations.address,
{ from: governorBravo }),
"Ownable: caller is not the owner")
})

it('startRevokeMintList(): reverts when account has no minting role', async () => {
await assertRevert(
thusdOwner.startRevokeMintList(
integrationsGuild,
{ from: integrationsGuild }),
"Incorrect address to revoke")
})

it('startRevokeMintList(): puts account to pending list', async () => {
await thusdOwner.startRevokeMintList(borrowerOperations.address, { from: integrationsGuild })

const timeNow = await getLatestBlockTimestamp(web3)
assert.equal(await thusdToken.pendingRevokedMintAddress(), borrowerOperations.address)
assert.equal(await thusdToken.revokeMintListInitiated(), timeNow)

assert.isTrue(await thusdToken.mintList(borrowerOperations.address))
})

it('finalizeRevokeMintList(): reverts when caller is not owner', async () => {
await assertRevert(
thusdOwner.finalizeRevokeMintList(
{ from: owner }),
"Ownable: caller is not the owner")
})

it('finalizeRevokeMintList(): reverts when change is not initiated', async () => {
await assertRevert(
thusdOwner.finalizeRevokeMintList(
{ from: integrationsGuild }),
"Change not initiated")
})

it('finalizeRevokeMintList(): reverts when passed not enough time', async () => {
await thusdOwner.startRevokeMintList(
borrowerOperations.address,
{ from: integrationsGuild }
)
await assertRevert(
thusdOwner.finalizeRevokeMintList(
{ from: integrationsGuild }),
"Governance delay has not elapsed")
})

it('finalizeRevokeMintList(): removes account from minting list', async () => {
await thusdOwner.startRevokeMintList(
borrowerOperations.address,
{ from: integrationsGuild }
)
await fastForwardTime(delay, web3.currentProvider)

await thusdOwner.finalizeRevokeMintList({ from: integrationsGuild })

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

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

it('finalizeRevokeMintList(): removes account from minting list', async () => {
await thusdOwner.startRevokeMintList(
borrowerOperations.address,
{ from: integrationsGuild }
)
await fastForwardTime(delay, web3.currentProvider)

await thusdOwner.finalizeRevokeMintList({ from: integrationsGuild })

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

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

it('transferThusdOwnershipToGovernorBravo(): reverts when caller is not owner', async () => {
await assertRevert(thusdOwner.transferThusdOwnershipToGovernorBravo(
{ from: owner }),
"Ownable: caller is not the owner"
)
})

it('transferThusdOwnershipToGovernorBravo(): transfer thusd ownership to governor bravo', async () => {
await thusdOwner.transferThusdOwnershipToGovernorBravo(
{ from: integrationsGuild }
)
await assert.equal(await thusdToken.owner(), governorBravo)
})
})
11 changes: 9 additions & 2 deletions packages/contracts/utils/deploymentHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const SortedTroves = artifacts.require("./SortedTroves.sol")
const TroveManager = artifacts.require("./TroveManager.sol")
const PriceFeedTestnet = artifacts.require("./PriceFeedTestnet.sol")
const THUSDToken = artifacts.require("./THUSDToken.sol")
const THUSDOwner = artifacts.require("./THUSDOwner.sol")
const ActivePool = artifacts.require("./ActivePool.sol");
const DefaultPool = artifacts.require("./DefaultPool.sol");
const StabilityPool = artifacts.require("./StabilityPool.sol")
Expand Down Expand Up @@ -75,7 +76,7 @@ class DeploymentHelper {
delay
)
const pcv = await PCV.new(delay)

PCV.setAsDeployed(pcv)
THUSDToken.setAsDeployed(thusdToken)
DefaultPool.setAsDeployed(defaultPool)
Expand Down Expand Up @@ -120,6 +121,7 @@ class DeploymentHelper {
}

static async deployTesterContractsHardhat(accounts) {
const [owner, integrationsGuild, governorBravo] = accounts;
const testerContracts = {}

// Contract without testers (yet)
Expand All @@ -137,12 +139,17 @@ class DeploymentHelper {
testerContracts.troveManager = await TroveManagerTester.new()
testerContracts.functionCaller = await FunctionCaller.new()
testerContracts.hintHelpers = await HintHelpers.new()
testerContracts.thusdToken = await THUSDTokenTester.new(
testerContracts.thusdToken = await THUSDTokenTester.new(
testerContracts.troveManager.address,
testerContracts.stabilityPool.address,
testerContracts.borrowerOperations.address,
delay
)
testerContracts.thusdOwner = await THUSDOwner.new(
governorBravo,
testerContracts.thusdToken.address,
integrationsGuild
)
testerContracts.pcv = await PCV.new(delay)

let index = 0;
Expand Down

0 comments on commit 2e79588

Please sign in to comment.