UNPKG

@dydxfoundation/governance

Version:
187 lines (186 loc) 11.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const bignumber_js_1 = __importDefault(require("bignumber.js")); const chai_1 = require("chai"); const constants_1 = require("../../src/lib/constants"); const types_1 = require("../../types"); const describe_contract_1 = require("../helpers/describe-contract"); const evm_1 = require("../helpers/evm"); const staking_helper_1 = require("../helpers/staking-helper"); const stakerInitialBalance = 1000000; const stakerInitialBalance2 = 4000000; // Users. let staker1; let staker2; // Users calling the liquidity staking contract. let stakerSigner1; let mockSubclassSigner1; let contract; async function init(ctx) { // Users. [staker1, staker2] = ctx.users; // Users calling the liquidity staking contract. stakerSigner1 = ctx.safetyModule.connect(staker1); const distributionStart2 = await (0, evm_1.latestBlockTimestamp)() + 500; mockSubclassSigner1 = await new types_1.MockSafetyModuleSubclass__factory(staker1).deploy(ctx.dydxToken.address, ctx.dydxToken.address, ctx.rewardsTreasury.address, distributionStart2, ctx.config.SM_DISTRIBUTION_END); // Use helper class to automatically check contract invariants after every update. contract = new staking_helper_1.StakingHelper(ctx, ctx.safetyModule, ctx.dydxToken, ctx.rewardsTreasury.address, ctx.deployer, ctx.deployer, [staker1, staker2], true); // Mint to stakers. await contract.mintAndApprove(staker1, stakerInitialBalance); await contract.mintAndApprove(staker2, stakerInitialBalance2); } (0, describe_contract_1.describeContractHardhatRevertBeforeEach)('SM1Erc20', init, (ctx) => { before(() => { contract.saveSnapshot('main'); }); afterEach(() => { contract.loadSnapshot('main'); }); describe('Token details', () => { it('Has correct name', async () => { (0, chai_1.expect)(await ctx.safetyModule.name()).to.equal('Staked DYDX'); }); it('Has correct symbol', async () => { (0, chai_1.expect)(await ctx.safetyModule.symbol()).to.equal('stkDYDX'); }); it('Has correct decimals', async () => { (0, chai_1.expect)(await ctx.safetyModule.decimals()).to.equal(18); }); }); describe('totalSupply', () => { it('totalSupply is zero with no staked funds', async () => { (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(0); }); it('totalSupply increases when users stake funds and does not decrease when funds are inactive', async () => { await contract.stake(staker1, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(stakerInitialBalance); await contract.requestWithdrawal(staker1, stakerInitialBalance); // funds aren't inactive yet (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(stakerInitialBalance); // another user stakes funds await contract.stake(staker2, stakerInitialBalance2); const totalFunds = stakerInitialBalance + stakerInitialBalance2; (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(totalFunds); // funds are inactive in next epoch await contract.elapseEpoch(); (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(totalFunds); await contract.withdrawMaxStake(staker1, staker1); // should be stakerInitialBalance2 after staker1 withdraws funds (0, chai_1.expect)(await ctx.safetyModule.totalSupply()).to.equal(stakerInitialBalance2); }); }); describe('balanceOf', () => { it('balanceOf is zero if user has no staked funds', async () => { (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(0); }); it('User balance increases when user stakes funds and does not decrease when funds are inactive', async () => { await contract.stake(staker1, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(stakerInitialBalance); await contract.requestWithdrawal(staker1, stakerInitialBalance); // funds aren't inactive yet (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(stakerInitialBalance); // funds are inactive in next epoch await contract.elapseEpoch(); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(stakerInitialBalance); await contract.withdrawMaxStake(staker1, staker1); // should be 0 after user withdraws funds (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(0); }); }); describe('allowance', () => { it('allowance is initially zero for user', async () => { (0, chai_1.expect)(await ctx.safetyModule.allowance(staker1.address, staker2.address)).to.equal(0); }); it('allowance can be set to non-zero with approve', async () => { await (0, chai_1.expect)(stakerSigner1.approve(staker2.address, stakerInitialBalance)) .to.emit(stakerSigner1, 'Approval') .withArgs(staker1.address, staker2.address, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.allowance(staker1.address, staker2.address)).to.equal(stakerInitialBalance); }); it('cannot approve to address(0)', async () => { await (0, chai_1.expect)(ctx.safetyModule.approve(constants_1.ZERO_ADDRESS, 1)).to.be.revertedWith('SM1ERC20: Approve to address(0)'); }); it('cannot approve from address(0)', async () => { await (0, chai_1.expect)(mockSubclassSigner1.mockApproveFromZero(staker1.address, 1)).to.be.revertedWith('SM1ERC20: Approve from address(0)'); }); }); describe('increaseAllowance', () => { it('allowance can be increased', async () => { await (0, chai_1.expect)(stakerSigner1.increaseAllowance(staker2.address, stakerInitialBalance)) .to.emit(stakerSigner1, 'Approval') .withArgs(staker1.address, staker2.address, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.allowance(staker1.address, staker2.address)).to.equal(stakerInitialBalance); }); }); describe('decreaseAllowance', () => { it('allowance can be decreased', async () => { await (0, chai_1.expect)(stakerSigner1.increaseAllowance(staker2.address, stakerInitialBalance)) .to.emit(stakerSigner1, 'Approval') .withArgs(staker1.address, staker2.address, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.allowance(staker1.address, staker2.address)).to.equal(stakerInitialBalance); await (0, chai_1.expect)(stakerSigner1.decreaseAllowance(staker2.address, stakerInitialBalance)) .to.emit(stakerSigner1, 'Approval') .withArgs(staker1.address, staker2.address, 0); // allowance should now be 0 (0, chai_1.expect)(await ctx.safetyModule.allowance(staker1.address, staker2.address)).to.equal(0); }); }); describe('transfer', () => { it('User cannot transfer funds if they have not staked', async () => { (0, chai_1.expect)(await ctx.safetyModule.getTransferableBalance(staker1.address)).to.equal(0); await (0, chai_1.expect)(stakerSigner1.transfer(staker2.address, stakerInitialBalance)).to.be.revertedWith('SM1ERC20: Transfer exceeds next epoch active balance'); }); it('User with staked balance can transfer to another user', async () => { await contract.stake(staker1, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.getTransferableBalance(staker1.address)).to.equal(stakerInitialBalance); await contract.transfer(staker1, staker2, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(0); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker2.address)).to.equal(stakerInitialBalance); }); it('User with staked balance for one epoch can transfer to another user and claim rewards', async () => { // change EMISSION_RATE to be greater than 0 const emissionRate = 1; await (0, chai_1.expect)(ctx.safetyModule.connect(ctx.deployer).setRewardsPerSecond(emissionRate)) .to.emit(ctx.safetyModule, 'RewardsPerSecondUpdated') .withArgs(emissionRate); await contract.stake(staker1, stakerInitialBalance); const stakeTimestamp = await (0, evm_1.latestBlockTimestamp)(); // increase time to next epoch, so user can earn rewards await contract.elapseEpoch(); (0, chai_1.expect)(await ctx.safetyModule.getTransferableBalance(staker1.address)).to.equal(stakerInitialBalance); await contract.transfer(staker1, staker2, stakerInitialBalance); const balanceBeforeClaiming = await ctx.dydxToken.balanceOf(staker1.address); const now = await (0, evm_1.latestBlockTimestamp)(); const numTokens = new bignumber_js_1.default(now).minus(stakeTimestamp).times(emissionRate).toString(); await (0, chai_1.expect)(stakerSigner1.claimRewards(staker1.address)) .to.emit(ctx.safetyModule, 'ClaimedRewards') .withArgs(staker1.address, staker1.address, numTokens); (0, chai_1.expect)(await ctx.dydxToken.balanceOf(staker1.address)).to.equal(balanceBeforeClaiming.add(numTokens).toString()); }); it('User cannot transfer funds that are going to be inactive in the next epoch', async () => { await contract.stake(staker1, stakerInitialBalance); await contract.requestWithdrawal(staker1, stakerInitialBalance); // funds will be inactive next epoch, cannot transfer them (0, chai_1.expect)(await ctx.safetyModule.getTransferableBalance(staker1.address)).to.equal(0); await (0, chai_1.expect)(stakerSigner1.transfer(staker2.address, stakerInitialBalance)).to.be.revertedWith('SM1ERC20: Transfer exceeds next epoch active balance'); }); it('cannot transfer to address(0)', async () => { await (0, chai_1.expect)(ctx.safetyModule.transfer(constants_1.ZERO_ADDRESS, 1)).to.be.revertedWith('SM1ERC20: Transfer to address(0)'); }); it('cannot transfer from address(0)', async () => { await (0, chai_1.expect)(mockSubclassSigner1.mockTransferFromZero(staker1.address, 1)).to.be.revertedWith('SM1ERC20: Transfer from address(0)'); }); }); describe('transferFrom', () => { it('User with staked balance can transfer to another user', async () => { await contract.stake(staker1, stakerInitialBalance); await contract.approve(staker1, staker2, stakerInitialBalance); await contract.transferFrom(staker2, staker1, staker2, stakerInitialBalance); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker1.address)).to.equal(0); (0, chai_1.expect)(await ctx.safetyModule.balanceOf(staker2.address)).to.equal(stakerInitialBalance); }); }); });