UNPKG

@dydxfoundation/governance

Version:
133 lines (132 loc) 8.97 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const chai_1 = require("chai"); const utils_1 = require("ethers/lib/utils"); const lodash_1 = __importDefault(require("lodash")); const util_1 = require("../../src/lib/util"); const impersonate_account_1 = require("../../src/migrations/helpers/impersonate-account"); const types_1 = require("../../src/types"); const StarkProxyV2__factory_1 = require("../../types/factories/StarkProxyV2__factory"); const describe_contract_1 = require("../helpers/describe-contract"); const evm_1 = require("../helpers/evm"); const get_address_with_role_1 = require("../helpers/get-address-with-role"); let deployer; let shortTimelockSigner; let merkleTimelockSigner; // Each borrower is represented by a stark proxy contract. let liquidityStaking; let mockStarkPerpetual; let borrowerStarkProxy; let forcedTradeWaitingPeriod; let forcedTradeGracePeriod; async function init(ctx) { deployer = ctx.deployer; shortTimelockSigner = await (0, impersonate_account_1.impersonateAndFundAccount)(ctx.shortTimelock.address); merkleTimelockSigner = await (0, impersonate_account_1.impersonateAndFundAccount)(ctx.merklePauserTimelock.address); liquidityStaking = ctx.liquidityStaking; mockStarkPerpetual = ctx.starkPerpetual; const ownerAddress = await (0, get_address_with_role_1.findAddressWithRole)(ctx.starkProxies[0], types_1.Role.OWNER_ROLE); const borrower = await (0, impersonate_account_1.impersonateAndFundAccount)(ownerAddress); borrowerStarkProxy = new StarkProxyV2__factory_1.StarkProxyV2__factory(borrower).attach(ctx.starkProxies[0].address); // Grant exchange operator role. await borrowerStarkProxy.grantRole((0, util_1.getRole)(types_1.Role.EXCHANGE_OPERATOR_ROLE), deployer.address); forcedTradeWaitingPeriod = await borrowerStarkProxy.FORCED_TRADE_WAITING_PERIOD(); forcedTradeGracePeriod = await borrowerStarkProxy.FORCED_TRADE_GRACE_PERIOD(); } (0, describe_contract_1.describeContract)('SP2Guardian', init, (ctx) => { (0, describe_contract_1.describeContractForNetwork)('SP2Guardian Hardhat Tests', ctx, types_1.NetworkName.hardhat, false, () => { it('No borrowers are initially in default', async () => { for (const borrower of ctx.starkProxies) { (0, chai_1.expect)(await liquidityStaking.isBorrowerOverdue(borrower.address)).to.equal(false); } }); it('Can veto a forced trade request', async () => { // Register STARK key and add it to the allowlist. const mockStarkKey = 0; await mockStarkPerpetual.registerUser(borrowerStarkProxy.address, mockStarkKey, []); await borrowerStarkProxy.allowStarkKey(mockStarkKey); // Owner enqueues a forced trade request. const args = lodash_1.default.range(12); const signature = Buffer.from('mock-signature'); const tx = await borrowerStarkProxy.queueForcedTradeRequest(args); const receipt = await (0, util_1.waitForTx)(tx); const { argsHash } = borrowerStarkProxy.interface.parseLog(receipt.logs[0]).args; // Expect to fail during waiting period. await (0, chai_1.expect)(borrowerStarkProxy.forcedTradeRequest(args, signature)).to.be.revertedWith('SP2Owner: Waiting period has not elapsed for forced trade'); // Allow waiting period to elapse. let timestamp = await (0, evm_1.latestBlockTimestamp)(); await (0, evm_1.incrementTimeToTimestamp)(forcedTradeWaitingPeriod.add(timestamp).toString()); // Note: 'function selector was not recognized' means it has attempted to call through to the // mock StarkPerpetual contract, and did not revert on StarkProxy. await (0, chai_1.expect)(borrowerStarkProxy.forcedTradeRequest(args, signature)).to.be.revertedWith('function selector was not recognized'); // Expect to fail after waiting period, if vetoed. await borrowerStarkProxy.connect(merkleTimelockSigner).guardianVetoForcedTradeRequests([argsHash]); await (0, chai_1.expect)(borrowerStarkProxy.forcedTradeRequest(args, signature)).to.be.revertedWith('SP2Owner: Forced trade not queued or was vetoed'); // Queue it again and expect it to fail if outside of the grace period. await borrowerStarkProxy.queueForcedTradeRequest(args); timestamp = await (0, evm_1.latestBlockTimestamp)(); await (0, evm_1.incrementTimeToTimestamp)(forcedTradeWaitingPeriod .add(forcedTradeGracePeriod) .add(timestamp) .add(1) .toString()); await (0, chai_1.expect)(borrowerStarkProxy.forcedTradeRequest(args, signature)).to.be.revertedWith('SP2Owner: Grace period has elapsed for forced trade'); }); it('GUARDIAN_ROLE can cancel and reclaim a deposit', async () => { // Register STARK key and add it to the allowlist. const mockStarkKey = 0; const mockAssetType = 1; const mockVaultId = 2; await mockStarkPerpetual.registerUser(borrowerStarkProxy.address, mockStarkKey, []); await borrowerStarkProxy.allowStarkKey(mockStarkKey); const guardianStarkProxy = borrowerStarkProxy.connect(shortTimelockSigner); await (0, chai_1.expect)(guardianStarkProxy.guardianDepositCancel(mockStarkKey, mockAssetType, mockVaultId)) .to.emit(guardianStarkProxy, 'DepositCanceled') .withArgs(mockStarkKey, mockAssetType, mockVaultId, true); await (0, chai_1.expect)(guardianStarkProxy.guardianDepositReclaim(mockStarkKey, mockAssetType, mockVaultId)) .to.emit(guardianStarkProxy, 'DepositReclaimed') .withArgs(mockStarkKey, mockAssetType, mockVaultId, 0, true); }); it('User without GUARDIAN_ROLE cannot cancel or reclaim a deposit', async () => { // Register STARK key and add it to the allowlist. const mockStarkKey = 0; const mockAssetType = 1; const mockVaultId = 2; await mockStarkPerpetual.registerUser(borrowerStarkProxy.address, mockStarkKey, []); await borrowerStarkProxy.allowStarkKey(mockStarkKey); const ownerAddress = (await borrowerStarkProxy.signer.getAddress()).toLowerCase(); const accessControlError = `AccessControl: account ${ownerAddress} is missing role ${(0, util_1.getRole)(types_1.Role.GUARDIAN_ROLE)}`; await (0, chai_1.expect)(borrowerStarkProxy.guardianDepositCancel(mockStarkKey, mockAssetType, mockVaultId)) .to.be.revertedWith(accessControlError); await (0, chai_1.expect)(borrowerStarkProxy.guardianDepositReclaim(mockStarkKey, mockAssetType, mockVaultId)) .to.be.revertedWith(accessControlError); }); }); (0, describe_contract_1.describeContractForNetwork)('Stark Proxy deposit cancel and reclaim', ctx, types_1.NetworkName.hardhat, true, () => { it('GUARDIAN_ROLE can cancel faulty deposit and reclaim funds', async () => { const wintermuteStarkProxy = borrowerStarkProxy.connect(shortTimelockSigner); const starkProxyBalanceBefore = await wintermuteStarkProxy.getTokenBalance(); const depositEvents = await wintermuteStarkProxy.queryFilter(wintermuteStarkProxy.filters.DepositedToExchange(null, null, null, null)); const badVaultId = '32'; const faultyDeposits = depositEvents.filter((e) => e.args.starkVaultId.toString() === badVaultId); (0, chai_1.expect)(faultyDeposits.length).to.equal(1); const starkKey = faultyDeposits[0].args.starkKey; const assetType = faultyDeposits[0].args.starkAssetType; await (0, chai_1.expect)(wintermuteStarkProxy.guardianDepositCancel(starkKey, assetType, badVaultId)) .to.emit(wintermuteStarkProxy, 'DepositCanceled') .withArgs(starkKey, assetType, badVaultId, true); const twoDaysSeconds = 2 * 24 * 60 * 60; await (0, evm_1.increaseTimeAndMine)(twoDaysSeconds); const lockedFunds = (0, utils_1.parseUnits)('50000000', 6); await (0, chai_1.expect)(wintermuteStarkProxy.guardianDepositReclaim(starkKey, assetType, badVaultId)) .to.emit(wintermuteStarkProxy, 'DepositReclaimed') .withArgs(starkKey, assetType, badVaultId, lockedFunds, true); const starkProxyBalanceAfter = await wintermuteStarkProxy.getTokenBalance(); const diff = starkProxyBalanceAfter.sub(starkProxyBalanceBefore).toString(); (0, chai_1.expect)(diff).to.equal(lockedFunds); }); }); });