@dydxfoundation/governance
Version:
dYdX governance smart contracts
147 lines (146 loc) • 8.22 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const chai_1 = require("chai");
const util_1 = require("../../src/lib/util");
const impersonate_account_1 = require("../../src/migrations/helpers/impersonate-account");
const types_1 = require("../../src/types");
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");
const staking_helper_1 = require("../helpers/staking-helper");
// Snapshots
const borrowerHasBorrowed = 'BorrowerHasBorrowed';
const borrowerRestrictedSnapshot = 'BorrowerRestrictedSnapshot';
const stakerInitialBalance = 1000000;
// Contracts.
let deployer;
let liquidityStaking;
let mockStakedToken;
let mockStarkPerpetual;
let shortTimelockSigner;
// Users.
let stakers;
let borrowerStarkProxies;
let contract;
async function init(ctx) {
({
liquidityStaking,
deployer,
} = ctx);
mockStakedToken = ctx.dydxCollateralToken;
mockStarkPerpetual = ctx.starkPerpetual;
// Users.
stakers = ctx.users.slice(1, 3); // 2 stakers
const borrowers = await Promise.all(ctx.starkProxies.map(async (b) => {
const ownerAddress = await (0, get_address_with_role_1.findAddressWithRole)(b, types_1.Role.OWNER_ROLE);
return (0, impersonate_account_1.impersonateAndFundAccount)(ownerAddress);
}));
borrowerStarkProxies = borrowers.map((b, i) => ctx.starkProxies[i].connect(b));
// Grant roles.
const deployerAddress = await deployer.getAddress();
await Promise.all(borrowerStarkProxies.map(async (b) => {
await b.grantRole((0, util_1.getRole)(types_1.Role.EXCHANGE_OPERATOR_ROLE), deployerAddress);
await b.grantRole((0, util_1.getRole)(types_1.Role.BORROWER_ROLE), deployerAddress);
}));
shortTimelockSigner = await (0, impersonate_account_1.impersonateAndFundAccount)(ctx.shortTimelock.address);
// Use helper class to automatically check contract invariants after every update.
contract = new staking_helper_1.StakingHelper(ctx, liquidityStaking, mockStakedToken, ctx.rewardsTreasury.address, ctx.deployer, shortTimelockSigner, stakers.concat(borrowers), false);
// Mint staked tokens and set allowances.
await Promise.all(stakers.map((s) => contract.mintAndApprove(s, stakerInitialBalance)));
// Initial stake of 1M.
await contract.stake(stakers[0], stakerInitialBalance / 4);
await contract.stake(stakers[1], (stakerInitialBalance / 4) * 3);
// Allocations: [40%, 60%], remaining borrowers 0%
await contract.setBorrowerAllocations({
[borrowerStarkProxies[0].address]: 0.4,
[borrowerStarkProxies[1].address]: 0.6,
[borrowerStarkProxies[2].address]: 0.0,
[borrowerStarkProxies[3].address]: 0.0,
[borrowerStarkProxies[4].address]: 0.0,
});
// Borrow full amount.
await contract.elapseEpoch();
await contract.fullBorrowViaProxy(borrowerStarkProxies[0], stakerInitialBalance * 0.4);
(0, chai_1.expect)(await mockStakedToken.balanceOf(borrowerStarkProxies[0].address)).to.equal(stakerInitialBalance * 0.4);
contract.saveSnapshot(borrowerHasBorrowed);
}
(0, describe_contract_1.describeContractHardhatRevertBefore)('SP2Exchange', init, () => {
describe('interacting with the exchange', () => {
const starkKey = 123;
let postInitSnapshotId;
before(async () => {
postInitSnapshotId = await (0, evm_1.evmSnapshot)();
});
beforeEach(async () => {
contract.loadSnapshot(borrowerHasBorrowed);
await (0, evm_1.evmReset)(postInitSnapshotId);
postInitSnapshotId = await (0, evm_1.evmSnapshot)();
});
after(async () => {
await (0, evm_1.evmReset)(postInitSnapshotId);
});
it('can add a STARK key that is registered to the StarkProxy contract', async () => {
await mockStarkPerpetual.registerUser(borrowerStarkProxies[0].address, starkKey, []);
await borrowerStarkProxies[0].allowStarkKey(starkKey);
});
it('cannot add a STARK key that is not registered', async () => {
await (0, chai_1.expect)(borrowerStarkProxies[0].allowStarkKey(starkKey)).to.be.revertedWith('USER_UNREGISTERED');
});
it('cannot add a STARK key that is registered to another address', async () => {
await mockStarkPerpetual.registerUser(borrowerStarkProxies[1].address, starkKey, []);
await (0, chai_1.expect)(borrowerStarkProxies[0].allowStarkKey(starkKey)).to.be.revertedWith('SP2Owner: STARK key not registered to this contract');
});
it('cannot deposit to a STARK key that has not been allowed', async () => {
await mockStarkPerpetual.registerUser(borrowerStarkProxies[0].address, starkKey, []);
await (0, chai_1.expect)(borrowerStarkProxies[0].depositToExchange(starkKey, 0, 0, 0)).to.be.revertedWith('SP1Storage: STARK key is not on the allowlist');
});
it('cannot deposit to a STARK key that has been disallowed', async () => {
await mockStarkPerpetual.registerUser(borrowerStarkProxies[0].address, starkKey, []);
await borrowerStarkProxies[0].allowStarkKey(starkKey);
await borrowerStarkProxies[0].disallowStarkKey(starkKey);
await (0, chai_1.expect)(borrowerStarkProxies[0].depositToExchange(starkKey, 0, 0, 0)).to.be.revertedWith('SP1Storage: STARK key is not on the allowlist');
});
it('can deposit and withdraw, and then repay', async () => {
await mockStarkPerpetual.registerUser(borrowerStarkProxies[0].address, starkKey, []);
await borrowerStarkProxies[0].allowStarkKey(starkKey);
await borrowerStarkProxies[0].depositToExchange(starkKey, 456, 789, stakerInitialBalance * 0.4);
await borrowerStarkProxies[0].withdrawFromExchange(starkKey, 456);
await contract.repayBorrowViaProxy(borrowerStarkProxies[0], stakerInitialBalance * 0.4);
});
});
describe('after restricted by guardian', () => {
const starkKey = 123;
let guardianSnapshot;
before(async () => {
// Restrict borrowing.
await (0, chai_1.expect)(borrowerStarkProxies[0].connect(shortTimelockSigner).guardianSetBorrowingRestriction(true))
.to.emit(borrowerStarkProxies[0], 'BorrowingRestrictionChanged')
.withArgs(true);
// Register with the exchange.
await mockStarkPerpetual.registerUser(borrowerStarkProxies[0].address, starkKey, []);
contract.saveSnapshot(borrowerRestrictedSnapshot);
guardianSnapshot = await (0, evm_1.evmSnapshot)();
});
beforeEach(async () => {
contract.loadSnapshot(borrowerRestrictedSnapshot);
await (0, evm_1.evmReset)(guardianSnapshot);
guardianSnapshot = await (0, evm_1.evmSnapshot)();
});
it('cannot deposit borrowed funds to the exchange', async () => {
await borrowerStarkProxies[0].allowStarkKey(starkKey);
await (0, chai_1.expect)(borrowerStarkProxies[0].depositToExchange(starkKey, 456, 789, stakerInitialBalance * 0.4)).to.be.revertedWith('SP2Exchange: Cannot deposit borrowed funds to the exchange while Restricted');
});
it('can still deposit own funds to the exchange', async () => {
await borrowerStarkProxies[0].allowStarkKey(starkKey);
// Transfer some funds directly to the StarkProxy contract.
const ownFundsAmount = 12340;
await mockStakedToken
.connect(stakers[0])
.transfer(borrowerStarkProxies[0].address, ownFundsAmount);
// Deposit own funds to the exchange.
await borrowerStarkProxies[0].depositToExchange(starkKey, 456, 789, ownFundsAmount);
// Cannot deposit any more.
await (0, chai_1.expect)(borrowerStarkProxies[0].depositToExchange(starkKey, 456, 789, 1)).to.be.revertedWith('SP2Exchange: Cannot deposit borrowed funds to the exchange while Restricted');
});
});
});