Files
liquid-stacking/tests/lqstx-mint-endpoint-with-public-pools.test.ts
2024-03-24 11:08:28 +08:00

387 lines
14 KiB
TypeScript

// SPDX-License-Identifier: BUSL-1.1
import { tx } from '@hirosystems/clarinet-sdk';
import { Cl, TupleCV, UIntCV } from '@stacks/transactions';
import { describe, expect, it } from 'vitest';
import { createClientMockSetup } from './clients/mock-client';
const { contracts, user, user2, oracle, bot, manager,
prepareTest, goToNextCycle, goToNextRequestCycle,
requestMint, requestBurn, fundStrategy, finalizeMint } = createClientMockSetup();
// 1m STX
const mintAmount = 1_000_000e6;
const mintDelay = 432;
describe(contracts.endpoint, () => {
it('can request mint', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
const response = requestMint(mintAmount);
expect(response.result).toBeOk(Cl.uint(1));
});
it('can finalize mint', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextCycle();
const finaliseErr = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot);
expect(finaliseErr.result).toBeErr(Cl.uint(7006));
simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay
let responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user),
]);
expect(responses[0].result).toBeOk(Cl.bool(true));
expect(responses[1].result).toBeErr(Cl.uint(7007));
});
it('can revoke mint', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
let responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.endpoint, 'revoke-mint', [Cl.uint(1)], user),
]);
expect(responses[0].result).toBeErr(Cl.uint(3000));
expect(responses[1].result).toBeOk(Cl.bool(true));
goToNextCycle();
simnet.mineEmptyBlocks(mintDelay + 1);
responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
]);
expect(responses[0].result).toBeErr(Cl.uint(7007));
});
it('can request burn', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextRequestCycle();
expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount));
goToNextCycle();
simnet.mineEmptyBlocks(mintDelay + 1);
expect(finalizeMint(1).result).toBeOk(Cl.bool(true));
expect(requestBurn(mintAmount).result).toBeOk(
Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') })
);
});
it('can finalize burn', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextRequestCycle();
expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount));
goToNextCycle();
simnet.mineEmptyBlocks(mintDelay + 1);
expect(finalizeMint(1).result).toBeOk(Cl.bool(true));
expect(requestBurn(1e6).result).toBeOk(
Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') })
);
const responses = simnet.mineBlock([
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user),
]);
expect(responses[0].result).toBeOk(Cl.uint(1e6));
expect(responses[1].result).toBeOk(Cl.bool(true));
expect(responses[2].result).toBeErr(Cl.uint(7007));
});
it('can revoke burn', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextRequestCycle();
expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount));
goToNextCycle();
simnet.mineEmptyBlocks(mintDelay + 1);
expect(finalizeMint(1).result).toBeOk(Cl.bool(true));
expect(requestBurn(mintAmount).result).toBeOk(
Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') })
);
const responses = simnet.mineBlock([
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.endpoint, 'revoke-burn', [Cl.uint(1)], user),
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
]);
expect(responses[0].result).toBeOk(Cl.uint(1e6));
expect(responses[1].result).toBeErr(Cl.uint(3000));
expect(responses[2].result).toBeOk(Cl.bool(true));
expect(responses[3].result).toBeErr(Cl.uint(7007));
});
it('can interact with strategies', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
const cycle = (
simnet.callReadOnlyFn(
contracts.endpoint,
'get-reward-cycle',
[Cl.uint(simnet.blockHeight)],
user
).result as UIntCV
).value;
const blocksToMine =
Number(
(
simnet.callReadOnlyFn(
contracts.endpoint,
'get-first-burn-block-in-reward-cycle',
[Cl.uint(cycle + 1n)],
user
).result as UIntCV
).value
) - simnet.blockHeight;
simnet.mineEmptyBlocks(blocksToMine - 100);
let responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.manager, 'fund-strategy', [Cl.list([Cl.uint(mintAmount)])], bot),
tx.callPublicFn(
contracts.manager,
'fund-strategy',
[Cl.list([Cl.uint(mintAmount)])],
manager
),
]);
expect(responses[0].result).toBeErr(Cl.uint(7006)); // request pending
expect(responses[1].result).toBeErr(Cl.uint(3000)); // not authorized
expect(responses[2].result).toBeOk(Cl.uint(mintAmount)); // mintAmount stx transferred, mintAmount - 1 stx locked
const stxAccountFastPoolMember1 = simnet.runSnippet(
`(stx-account '${simnet.deployer}.fastpool-member1)`
) as TupleCV<{ locked: UIntCV; unlocked: UIntCV }>;
expect(stxAccountFastPoolMember1.data.locked).toBeUint(mintAmount - 1e6);
goToNextCycle(); // go to the next cycle
simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay
responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.endpoint, 'request-burn', [Cl.uint(mintAmount)], user),
tx.callPublicFn(contracts.endpoint, 'request-burn', [Cl.uint(1e6)], user),
tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot),
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], bot),
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot),
]);
expect(responses[0].result).toBeOk(Cl.bool(true));
expect(responses[1].result).toBeOk(
Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') })
);
expect(responses[2].result).toBeErr(Cl.uint(1)); // not enough funds
expect(responses[3].result).toBeErr(Cl.uint(7006)); // request pending
expect(responses[4].result).toBeErr(Cl.uint(3000)); // not authorized
expect(responses[5].result).toBeOk(Cl.uint(1e6)); // refund 1 stx
expect(responses[6].result).toBeOk(Cl.uint(0)); // refund 0 stx
expect(responses[7].result).toBeErr(Cl.uint(7006)); // request pending
// refund remaining stx after unlock
goToNextCycle();
simnet.mineEmptyBlocks(1);
responses = simnet.mineBlock([
tx.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
tx.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot),
]);
expect(responses[0].result).toBeOk(Cl.uint(mintAmount - 1e6));
expect(responses[1].result).toBeOk(Cl.bool(true));
});
// user1 mints 100 STX, user2 100m STX
it('can rebase', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
let response;
response = simnet.callPublicFn(contracts.endpoint, 'request-mint', [Cl.uint(100e6)], user);
expect(response.result).toBeOk(Cl.uint(1));
response = simnet.callPublicFn(
contracts.endpoint,
'request-mint',
[Cl.uint(100_000_000e6)],
user2
);
expect(response.result).toBeOk(Cl.uint(2));
goToNextCycle(); // go to the next cycle
simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay
response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle);
expect(response.result).toBeOk(Cl.uint(0));
response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot);
expect(response.result).toBeOk(Cl.bool(true));
response = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(2)], bot);
expect(response.result).toBeOk(Cl.bool(true));
response = simnet.callPublicFn(
contracts.manager,
'fund-strategy',
[
Cl.list([
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
Cl.uint(5_000_000e6),
]),
],
manager
);
response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle);
expect(response.result).toBeOk(Cl.uint(100_000_100e6));
response = simnet.callReadOnlyFn(
contracts.lqstx,
'get-balance',
[Cl.standardPrincipal(user)],
user
);
expect(response.result).toBeOk(Cl.uint(100e6));
response = simnet.callReadOnlyFn(
contracts.lqstx,
'get-share',
[Cl.standardPrincipal(user)],
user
);
expect(response.result).toBeOk(Cl.uint(100e6));
// receive rewards
response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member1`, oracle);
response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member2`, oracle);
response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member3`, oracle);
response = simnet.transferSTX(1_000_000e6, `${simnet.deployer}.fastpool-member4`, oracle);
response = simnet.callPublicFn(contracts.endpoint, 'rebase', [], oracle);
expect(response.result).toBeOk(Cl.uint(104_000_100e6));
response = simnet.callReadOnlyFn(
contracts.lqstx,
'get-balance',
[Cl.standardPrincipal(user)],
user
);
expect(response.result).toBeOk(Cl.uint(103_999_996));
response = simnet.callReadOnlyFn(
contracts.lqstx,
'get-share',
[Cl.standardPrincipal(user)],
user
);
expect(response.result).toBeOk(Cl.uint(100e6));
});
it('can set up amm pool', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextCycle(); // go to the next cycle
const finaliseErr = simnet.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot);
expect(finaliseErr.result).toBeErr(Cl.uint(7006));
simnet.mineEmptyBlocks(mintDelay + 1); // mint-delay
let responses = simnet.mineBlock([
tx.callPublicFn(contracts.endpoint, 'finalize-mint', [Cl.uint(1)], bot),
tx.callPublicFn(
contracts.amm,
'create-pool',
[
Cl.principal(simnet.deployer + '.' + contracts.wstx),
Cl.principal(simnet.deployer + '.' + contracts.wlqstx),
Cl.uint(1e8),
Cl.principal(user),
Cl.uint(1e8),
Cl.uint(1e8),
],
user
),
]);
expect(responses[0].result).toBeOk(Cl.bool(true));
expect(responses[1].result).toBeOk(Cl.bool(true));
});
it('user can transfer burn nft', () => {
prepareTest().map((e: any) => expect(e.result).toBeOk(Cl.bool(true)));
let response;
// request and finalize mint for 1m STX
expect(requestMint(mintAmount).result).toBeOk(Cl.uint(1));
goToNextRequestCycle();
expect(fundStrategy(mintAmount).result).toBeOk(Cl.uint(mintAmount));
goToNextCycle();
simnet.mineEmptyBlocks(mintDelay + 1);
expect(finalizeMint(1).result).toBeOk(Cl.bool(true));
expect(requestBurn(1e6).result).toBeOk(
Cl.tuple({ 'request-id': Cl.uint(1), status: Cl.bufferFromHex('00') })
);
// transfer burn of 1 stx
response = simnet.callPublicFn(
contracts.burnNft,
'transfer',
[Cl.uint(1), Cl.standardPrincipal(user), Cl.standardPrincipal(bot)],
user
);
// bot is now owning the nft
expect(simnet.callReadOnlyFn(contracts.burnNft, 'get-owner', [Cl.uint(1)], user).result).toBeOk(
Cl.some(Cl.standardPrincipal(bot))
);
simnet.callPublicFn(contracts.manager, 'refund-strategy', [Cl.list([Cl.bool(true)])], manager),
(response = simnet.callPublicFn(contracts.endpoint, 'finalize-burn', [Cl.uint(1)], bot));
expect(response.result).toBeOk(Cl.bool(true));
// check that bot received stx
expect(simnet.getAssetsMap().get('STX')?.get(bot)).toBe(100000001_000_000n);
expect(simnet.getAssetsMap().get('STX')?.get(user)).toBe(99000000_000_000n);
// check that user has 1m - 1 liquid stx
expect(simnet.getAssetsMap().get('.token-lqstx.lqstx')?.get(user)).toBe(999999_000_000n);
});
});