Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loan Repayment - VIP 3 #85

Merged
merged 7 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update integration tests, deployment scripts, and VIP-3 proposal to w…
…ork with TribalCouncil timelock
  • Loading branch information
ElliotFriedman committed Jul 19, 2022
commit befb3504dfae724a1ed19d4f76d79b4dfbc77fa7
104 changes: 104 additions & 0 deletions contracts/pcv/IFeiPCVGuardian.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;

/// @title IFeiPCVGuardian
/// @notice an interface for defining how the PCVGuardian functions
/// @dev any implementation of this contract should be granted the roles of Guardian and PCVController in order to work correctly
interface IFeiPCVGuardian {
// ---------- Events ----------
event SafeAddressAdded(address indexed safeAddress);

event SafeAddressRemoved(address indexed safeAddress);

event PCVGuardianWithdrawal(
address indexed pcvDeposit,
address indexed destination,
uint256 amount
);

event PCVGuardianETHWithdrawal(
address indexed pcvDeposit,
address indexed destination,
uint256 amount
);

event PCVGuardianERC20Withdrawal(
address indexed pcvDeposit,
address indexed destination,
address indexed token,
uint256 amount
);

// ---------- Read-Only API ----------

/// @notice returns true if the the provided address is a valid destination to withdraw funds to
/// @param pcvDeposit the address to check
function isSafeAddress(address pcvDeposit) external view returns (bool);

/// @notice returns all safe addresses
function getSafeAddresses() external view returns (address[] memory);

// ---------- Governor-Only State-Changing API ----------

/// @notice governor-only method to set an address as "safe" to withdraw funds to
/// @param pcvDeposit the address to set as safe
function setSafeAddress(address pcvDeposit) external;

/// @notice batch version of setSafeAddress
/// @param safeAddresses the addresses to set as safe, as calldata
function setSafeAddresses(address[] calldata safeAddresses) external;

// ---------- Governor-or-Guardian-Only State-Changing API ----------

/// @notice governor-or-guardian-only method to un-set an address as "safe" to withdraw funds to
/// @param pcvDeposit the address to un-set as safe
function unsetSafeAddress(address pcvDeposit) external;

/// @notice batch version of unsetSafeAddresses
/// @param safeAddresses the addresses to un-set as safe
function unsetSafeAddresses(address[] calldata safeAddresses) external;

/// @notice governor-or-guardian-only method to withdraw funds from a pcv deposit, by calling the withdraw() method on it
/// @param pcvDeposit the address of the pcv deposit contract
/// @param safeAddress the destination address to withdraw to
/// @param amount the amount to withdraw
/// @param pauseAfter if true, the pcv contract will be paused after the withdraw
/// @param depositAfter if true, attempts to deposit to the target PCV deposit
function withdrawToSafeAddress(
address pcvDeposit,
address safeAddress,
uint256 amount,
bool pauseAfter,
bool depositAfter
) external;

/// @notice governor-or-guardian-only method to withdraw funds from a pcv deposit, by calling the withdraw() method on it
/// @param pcvDeposit the address of the pcv deposit contract
/// @param safeAddress the destination address to withdraw to
/// @param amount the amount of tokens to withdraw
/// @param pauseAfter if true, the pcv contract will be paused after the withdraw
/// @param depositAfter if true, attempts to deposit to the target PCV deposit
function withdrawETHToSafeAddress(
address pcvDeposit,
address payable safeAddress,
uint256 amount,
bool pauseAfter,
bool depositAfter
) external;

/// @notice governor-or-guardian-only method to withdraw funds from a pcv deposit, by calling the withdraw() method on it
/// @param pcvDeposit the deposit to pull funds from
/// @param safeAddress the destination address to withdraw to
/// @param token the token to withdraw
/// @param amount the amount of funds to withdraw
/// @param pauseAfter whether to pause the pcv after withdrawing
/// @param depositAfter if true, attempts to deposit to the target PCV deposit
function withdrawERC20ToSafeAddress(
address pcvDeposit,
address safeAddress,
address token,
uint256 amount,
bool pauseAfter,
bool depositAfter
) external;
}
46 changes: 32 additions & 14 deletions contracts/test/integration/IntegrationTestVoltDeployment.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {Vm} from "./../unit/utils/Vm.sol";
import {ICore} from "../../core/ICore.sol";
import {DSTest} from "../unit/utils/DSTest.sol";
import {StdLib} from "../unit/utils/StdLib.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {MockERC20} from "../../mock/MockERC20.sol";
import {IVolt, Volt} from "../../volt/Volt.sol";
import {OraclePassThrough} from "../../oracle/OraclePassThrough.sol";
Expand All @@ -21,6 +22,8 @@ import {NonCustodialPSM, GlobalRateLimitedMinter} from "./../../peg/NonCustodial
// - Oracle Pass Through

contract IntegrationTestVoltDeployment is DSTest, StdLib {
using SafeCast for *;

GlobalRateLimitedMinter private rateLimitedMinter;
NonCustodialPSM private psm;
ICore private core;
Expand Down Expand Up @@ -202,10 +205,16 @@ contract IntegrationTestVoltDeployment is DSTest, StdLib {
uint256 endingUserVoltBalance = volt.balanceOf(address(this));
uint256 endingPCVDepositFeiBalance = rariFEIPCVDeposit.balance();

assertEq(endingUserVoltBalance - startingUserVoltBalance, mintAmount);
assertEq(
(endingPCVDepositFeiBalance - startingPCVDepositFeiBalance),
(mintAmount - 1) /// goes down by 1 because of cToken pricing rounding down
assertApproxEq(
(endingUserVoltBalance - startingUserVoltBalance).toInt256(),
mintAmount.toInt256(),
0
);
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
assertApproxEq(
(endingPCVDepositFeiBalance - startingPCVDepositFeiBalance)
.toInt256(),
mintAmount.toInt256(), /// goes down by 1 because of cToken pricing rounding down
0
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
);
}

Expand All @@ -226,13 +235,16 @@ contract IntegrationTestVoltDeployment is DSTest, StdLib {
uint256 endingUserVoltBalance = volt.balanceOf(address(this));
uint256 endingPCVDepositFeiBalance = rariFEIPCVDeposit.balance();

assertEq(
endingUserVoltBalance - startingUserVoltBalance,
amountVoltOut
assertApproxEq(
(endingUserVoltBalance - startingUserVoltBalance).toInt256(),
amountVoltOut.toInt256(),
0
);
assertEq(
endingPCVDepositFeiBalance - startingPCVDepositFeiBalance,
amountFeiIn
assertApproxEq(
(endingPCVDepositFeiBalance - startingPCVDepositFeiBalance)
.toInt256(),
amountFeiIn.toInt256(),
0
);
}

Expand All @@ -253,10 +265,16 @@ contract IntegrationTestVoltDeployment is DSTest, StdLib {
uint256 endingUserVoltBalance = volt.balanceOf(address(this));
uint256 endingPCVDepositFeiBalance = rariFEIPCVDeposit.balance();

assertEq(startingUserVoltBalance - endingUserVoltBalance, amountVoltIn);
assertEq(
startingPCVDepositFeiBalance - endingPCVDepositFeiBalance,
amountFeiOut
assertApproxEq(
(startingUserVoltBalance - endingUserVoltBalance).toInt256(),
amountVoltIn.toInt256(),
0
);
assertApproxEq(
(startingPCVDepositFeiBalance - endingPCVDepositFeiBalance)
.toInt256(),
amountFeiOut.toInt256(),
0
);
}

Expand Down
39 changes: 27 additions & 12 deletions contracts/test/integration/LoanRepayment.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IVolt, Volt} from "../../volt/Volt.sol";
import {OtcEscrow} from "./../../utils/OtcEscrow.sol";
import {MainnetAddresses} from "./fixtures/MainnetAddresses.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {IFeiPCVGuardian} from "../../pcv/IFeiPCVGuardian.sol";

contract IntegrationTestLoanRepayment is DSTest {
IVolt private volt = IVolt(MainnetAddresses.VOLT);
Expand All @@ -23,37 +24,51 @@ contract IntegrationTestLoanRepayment is DSTest {
/// @notice escrow contract
OtcEscrow public otcEscrow;

address public feiDaoTimelock = MainnetAddresses.FEI_DAO_TIMELOCK;
address public feiTcTimelock = MainnetAddresses.FEI_TC_TIMELOCK;
address public voltTimelock = MainnetAddresses.VOLT_TIMELOCK;

IFeiPCVGuardian private pcvGuardian =
IFeiPCVGuardian(MainnetAddresses.FEI_PCV_GUARDIAN);

function setUp() public {
otcEscrow = new OtcEscrow(
MainnetAddresses.FEI_DAO_TIMELOCK, /// beneficiary
MainnetAddresses.FEI_TC_TIMELOCK, /// beneficiary
MainnetAddresses.VOLT_TIMELOCK, /// recipient
MainnetAddresses.VOLT, /// received token
MainnetAddresses.FEI, /// sent token
voltAmount, /// received amount
feiAmount /// sent amount
);

if (volt.allowance(feiDaoTimelock, address(otcEscrow)) < voltAmount) {
vm.prank(feiDaoTimelock);
if (volt.allowance(feiTcTimelock, address(otcEscrow)) < voltAmount) {
vm.prank(feiTcTimelock);
volt.approve(address(otcEscrow), voltAmount);
}
if (!pcvGuardian.isSafeAddress(feiTcTimelock)) {
vm.prank(feiTcTimelock);
pcvGuardian.setSafeAddress(feiTcTimelock);
}
if (volt.balanceOf(feiTcTimelock) < voltAmount) {
vm.prank(feiTcTimelock);
pcvGuardian.withdrawToSafeAddress(
MainnetAddresses.VOLT_DEPOSIT,
feiTcTimelock,
voltAmount,
false,
false
);
}
if (fei.balanceOf(address(otcEscrow)) < feiAmount) {
vm.prank(MainnetAddresses.GOVERNOR);
fei.transfer(address(otcEscrow), feiAmount);
}
}

function testSetup() public {
assertEq(
volt.allowance(feiDaoTimelock, address(otcEscrow)),
voltAmount
);
assertEq(volt.allowance(feiTcTimelock, address(otcEscrow)), voltAmount);
assertEq(fei.balanceOf(address(otcEscrow)), feiAmount);

assertEq(otcEscrow.beneficiary(), feiDaoTimelock);
assertEq(otcEscrow.beneficiary(), feiTcTimelock);
assertEq(otcEscrow.recipient(), voltTimelock);
assertEq(otcEscrow.receivedToken(), MainnetAddresses.VOLT);
assertEq(otcEscrow.sentToken(), MainnetAddresses.FEI);
Expand All @@ -63,13 +78,13 @@ contract IntegrationTestLoanRepayment is DSTest {

function testSwap() public {
uint256 startingBalanceOtcEscrowFei = fei.balanceOf(address(otcEscrow));
uint256 startingBalanceFeiTimelockVolt = volt.balanceOf(feiDaoTimelock);
uint256 startingBalanceFeiTimelockVolt = volt.balanceOf(feiTcTimelock);

vm.prank(feiDaoTimelock);
vm.prank(feiTcTimelock);
otcEscrow.swap();

uint256 endingBalanceOtcEscrowFei = fei.balanceOf(address(otcEscrow));
uint256 endingBalanceFeiTimelockVolt = volt.balanceOf(feiDaoTimelock);
uint256 endingBalanceFeiTimelockVolt = volt.balanceOf(feiTcTimelock);

assertEq(
startingBalanceFeiTimelockVolt - endingBalanceFeiTimelockVolt,
Expand Down
9 changes: 9 additions & 0 deletions contracts/test/integration/fixtures/MainnetAddresses.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,18 @@ library MainnetAddresses {
address public constant FEI_DAO_TIMELOCK =
0xd51dbA7a94e1adEa403553A8235C302cEbF41a3c;

address public constant FEI_TC_TIMELOCK =
0xe0C7DE94395B629860Cbb3c42995F300F56e6d7a;

address public constant FEI_GOVERNOR =
0xd51dbA7a94e1adEa403553A8235C302cEbF41a3c;

address public constant FEI_PCV_GUARDIAN =
0x02435948F84d7465FB71dE45ABa6098Fc6eC2993;

address public constant VOLT_DEPOSIT =
0xBDC01c9743989429df9a4Fe24c908D87e462AbC1;

// ---------- USDC ADDRESSES ----------

address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
"console:ropsten": "npx hardhat console --network ropsten",
"clean": "forge clean && rm -rf artifacts",
"test": "forge test --no-match-contract IntegrationTest -vvv",
"test:integration": "FORK_BLOCK=15097262; forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/$MAINNET_ALCHEMY_API_KEY --fork-block-number $FORK_BLOCK --match-contract IntegrationTest -vvv",
"test:integration:gas": "FORK_BLOCK=15097262; forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/$MAINNET_ALCHEMY_API_KEY --fork-block-number $FORK_BLOCK --match-contract IntegrationTest -vvv --gas-report",
"test:integration": "FORK_BLOCK=15174572; forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/$MAINNET_ALCHEMY_API_KEY --fork-block-number $FORK_BLOCK --match-contract IntegrationTest -vvv",
"test:integration:gas": "FORK_BLOCK=15174572; forge test --fork-url https://eth-mainnet.alchemyapi.io/v2/$MAINNET_ALCHEMY_API_KEY --fork-block-number $FORK_BLOCK --match-contract IntegrationTest -vvv --gas-report",
"test:hardhat": "npx hardhat test",
"test:dependencies": "LOGGING=true NO_RESET=true npx hardhat test test/integration/tests/dependencies.ts",
"test:gas": "REPORT_GAS=true npx hardhat test",
Expand Down
12 changes: 6 additions & 6 deletions proposals/dao/vip_3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ Description:
Steps:
1 - Send the OTC Escrow contract 10.17m FEI from the timelock
2 - Wait for TribeDAO to approve
3 - Swap occurs in TribeDAO script
3 - Swap occurs from TribeDAO Tribal Council timelock

*/

// volt is sent to volt timelock
// fei is sent to tribe dao timelock
// fei is sent to tribe dao tribal council timelock

const vipNumber = '3';
const feiAmount = ethers.constants.WeiPerEther.mul(10_170_000);
Expand All @@ -33,11 +33,11 @@ const { VOLT_SWAP_AMOUNT } = config;
// Do any deployments
// This should exclusively include new contract deployments
const deploy: DeployUpgradeFunc = async (deployAddress: string, addresses: NamedAddresses, logging: boolean) => {
const { fei, volt, feiDAOTimelock, optimisticTimelock } = addresses;
const { fei, volt, tribalCouncilTimelock, optimisticTimelock } = addresses;

const factory = await ethers.getContractFactory('OtcEscrow');
const otcEscrowRepayment = await factory.deploy(
feiDAOTimelock, // FEI DAO timelock receives the FEI
tribalCouncilTimelock, // FEI tribal council timelock receives the FEI
optimisticTimelock, // Volt optimisticTimelock is the recipient of the VOLT
volt, // transfer VOLT from fei dao timelock to the volt timelock
fei, // transfer FEI to the FEI DAO timelock
Expand Down Expand Up @@ -69,11 +69,11 @@ const teardown: TeardownUpgradeFunc = async (addresses, oldContracts, contracts,
// IE check balances, check state of contracts, etc.
const validate: ValidateUpgradeFunc = async (addresses, oldContracts, contracts, logging) => {
const { fei, volt, otcEscrowRepayment, optimisticTimelock } = contracts;
const { feiDAOTimelock } = addresses;
const { tribalCouncilTimelock } = addresses;

expect(await fei.balanceOf(otcEscrowRepayment.address)).to.be.equal(feiAmount);
ElliotFriedman marked this conversation as resolved.
Show resolved Hide resolved
expect(await otcEscrowRepayment.recipient()).to.be.equal(optimisticTimelock.address);
expect(await otcEscrowRepayment.beneficiary()).to.be.equal(feiDAOTimelock);
expect(await otcEscrowRepayment.beneficiary()).to.be.equal(tribalCouncilTimelock);

expect(await otcEscrowRepayment.receivedToken()).to.be.equal(volt.address);
expect(await otcEscrowRepayment.sentToken()).to.be.equal(fei.address);
Expand Down
6 changes: 6 additions & 0 deletions protocol-configuration/mainnetAddresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ const MainnetAddresses: MainnetAddresses = {
category: AddressCategory.External,
network: Network.Mainnet
},
tribalCouncilTimelock: {
address: '0xe0C7DE94395B629860Cbb3c42995F300F56e6d7a',
artifactName: 'unknown', /// Timelock Controller
category: AddressCategory.External,
network: Network.Mainnet
},
feiVoltOTCSwap: {
address: '0xeF152E462B59940616E667E801762dA9F2AF97b9',
artifactName: 'OtcEscrow',
Expand Down