diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 72ef0887d8..3a0fdc8726 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,3 +22,6 @@ updates: interval: "daily" labels: - "kind/dependencies" + commit-message: + include: scope + prefix: bump diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f439cf5bcb..dabc12fdeb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 + - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 with: node-version: '16' cache: 'npm' @@ -35,7 +35,7 @@ jobs: GH_INSTANCE_TOTAL: 10 steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 + - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 with: node-version: '16' cache: 'npm' @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 + - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 with: node-version: '16' cache: 'npm' diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index cc259c1841..bf36720058 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -11,7 +11,7 @@ jobs: environment: NPM Release Publishing steps: - uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 - - uses: actions/setup-node@04c56d2f954f1e4c69436aa54cfef261a018f458 + - uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 with: node-version: '16' diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 940a0d738f..a8e014c56e 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -33,6 +33,7 @@ + + - + \ No newline at end of file diff --git a/.idea/dictionaries/fuxing.xml b/.idea/dictionaries/fuxing.xml index bec803bfec..dde207b1bf 100644 --- a/.idea/dictionaries/fuxing.xml +++ b/.idea/dictionaries/fuxing.xml @@ -216,6 +216,7 @@ setgov setgovheight setloantoken + setmocktime setwalletflag sighash sighashtype diff --git a/apps/website/package.json b/apps/website/package.json index 0f538874be..f7acbd2351 100644 --- a/apps/website/package.json +++ b/apps/website/package.json @@ -24,8 +24,8 @@ "@docusaurus/module-type-aliases": "^2.0.0-beta.14", "@docusaurus/types": "^2.0.0-beta.14", "@tsconfig/docusaurus": "^1.0.4", - "@types/react": "^17.0.37", - "@types/react-helmet": "^6.1.4", + "@types/react": "^17.0.38", + "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.2" }, "browserslist": { diff --git a/docs/node/CATEGORIES/11-masternode.md b/docs/node/CATEGORIES/11-masternode.md index 3db7185cd1..b9e64b865a 100644 --- a/docs/node/CATEGORIES/11-masternode.md +++ b/docs/node/CATEGORIES/11-masternode.md @@ -135,6 +135,22 @@ interface MasternodeResult { } ``` +## getMasternodeBlocks + +Returns blocks generated by the specified masternode + +```ts title="client.masternode.getMasternodeBlocks" +interface masternode { + getMasternodeBlocks(identifier: MasternodeBlock, depth?: number): Promise> +} + +interface MasternodeBlock { + id?: string + ownerAddress?: string + operatorAddress?: string +} +``` + ## resignMasternode Creates a transaction resigning a masternode. @@ -190,6 +206,21 @@ interface masternode { } ``` +## getAnchorTeams + +Returns the auth and confirm anchor masternode teams at current or specified height + +```ts title="client.masternode.getAnchorTeams" +interface masternode { + getAnchorTeams (blockHeight?: number): Promise +} + +interface AnchorTeamResult { + auth: string[] + confirm: string[] +} +``` + ## getActiveMasternodeCount Returns number of unique masternodes in the last specified number of blocks. diff --git a/docs/node/CATEGORIES/14-spv.md b/docs/node/CATEGORIES/14-spv.md index d053ac43c2..691db1f94f 100644 --- a/docs/node/CATEGORIES/14-spv.md +++ b/docs/node/CATEGORIES/14-spv.md @@ -283,7 +283,7 @@ List anchors. ```ts title=client.spv.listAnchors()" interface spv { listAnchors ( - options: ListAnchorsOptions = { minBtcHeight: -1, maxBtcHeight: -1, minConfs: -1, maxConfs: -1 } + options: ListAnchorsOptions = { minBtcHeight: -1, maxBtcHeight: -1, minConfs: -1, maxConfs: -1, startBTCHeight: -1, limit: -1 } ): Promise } @@ -292,6 +292,8 @@ interface ListAnchorsOptions { maxBtcHeight?: number minConfs?: number maxConfs?: number + startBTCHeight?: number + limit?: number } interface ListAnchorsResult { diff --git a/package-lock.json b/package-lock.json index 03c9df753c..7e8d301d9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "husky": "^7.0.4", "jest": "^27.4.5", "lerna": "^4.0.0", - "lint-staged": "^12.1.3", + "lint-staged": "^12.1.4", "nock": "^13.1.3", "shuffle-seed": "^1.1.6", "ts-jest": "^27.1.2", @@ -47,8 +47,8 @@ "@docusaurus/module-type-aliases": "^2.0.0-beta.14", "@docusaurus/types": "^2.0.0-beta.14", "@tsconfig/docusaurus": "^1.0.4", - "@types/react": "^17.0.37", - "@types/react-helmet": "^6.1.4", + "@types/react": "^17.0.38", + "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.2" } }, @@ -2482,6 +2482,10 @@ "resolved": "packages/ocean-api-client", "link": true }, + "node_modules/@defichain/playground": { + "resolved": "packages/playground", + "link": true + }, "node_modules/@defichain/testcontainers": { "resolved": "packages/testcontainers", "link": true @@ -4844,9 +4848,9 @@ } }, "node_modules/@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -4854,9 +4858,10 @@ } }, "node_modules/@types/react-helmet": { - "version": "6.1.4", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz", + "integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==", "dev": true, - "license": "MIT", "dependencies": { "@types/react": "*" } @@ -13195,9 +13200,9 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.3.tgz", - "integrity": "sha512-ajapdkaFxx+MVhvq6xQRg9zCnCLz49iQLJZP7+w8XaA3U4B35Z9xJJGq9vxmWo73QTvJLG+N2NxhjWiSexbAWQ==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.4.tgz", + "integrity": "sha512-RgDz9nsFsE0/5eL9Vat0AvCuk0+j5mEuzBIVfrRH5FRtt5wibYe8zTjZs2nuqLFrLAGQGYnj8+HJxolcj08i/A==", "dev": true, "dependencies": { "cli-truncate": "^3.1.0", @@ -21013,6 +21018,7 @@ } }, "packages/ocean-api-client": { + "name": "@defichain/ocean-api-client", "version": "0.0.0", "license": "MIT", "dependencies": { @@ -21024,6 +21030,18 @@ "defichain": "^0.0.0" } }, + "packages/playground": { + "name": "@defichain/playground", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@defichain/jellyfish-api-core": "0.0.0", + "@defichain/jellyfish-network": "0.0.0" + }, + "peerDependencies": { + "defichain": "0.0.0" + } + }, "packages/testcontainers": { "name": "@defichain/testcontainers", "version": "0.0.0", @@ -22200,8 +22218,8 @@ "@docusaurus/types": "^2.0.0-beta.14", "@mdx-js/react": "^1.6.22", "@tsconfig/docusaurus": "^1.0.4", - "@types/react": "^17.0.37", - "@types/react-helmet": "^6.1.4", + "@types/react": "^17.0.38", + "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.2", "clsx": "^1.1.1", "react": "^17.0.2", @@ -22770,6 +22788,13 @@ "url-search-params-polyfill": "8.1.1" } }, + "@defichain/playground": { + "version": "file:packages/playground", + "requires": { + "@defichain/jellyfish-api-core": "0.0.0", + "@defichain/jellyfish-network": "0.0.0" + } + }, "@defichain/testcontainers": { "version": "file:packages/testcontainers", "requires": { @@ -24526,9 +24551,9 @@ } }, "@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -24536,7 +24561,9 @@ } }, "@types/react-helmet": { - "version": "6.1.4", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz", + "integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==", "dev": true, "requires": { "@types/react": "*" @@ -26739,6 +26766,7 @@ "@defichain/jellyfish-wallet-encrypted": "file:packages/jellyfish-wallet-encrypted", "@defichain/jellyfish-wallet-mnemonic": "file:packages/jellyfish-wallet-mnemonic", "@defichain/ocean-api-client": "file:packages/ocean-api-client", + "@defichain/playground": "file:packages/playground", "@defichain/testcontainers": "file:packages/testcontainers", "@defichain/testing": "file:packages/testing", "@types/jest": "^27.0.3", @@ -26749,7 +26777,7 @@ "husky": "^7.0.4", "jest": "^27.4.5", "lerna": "^4.0.0", - "lint-staged": "^12.1.3", + "lint-staged": "^12.1.4", "nock": "^13.1.3", "shuffle-seed": "^1.1.6", "ts-jest": "^27.1.2", @@ -27881,8 +27909,8 @@ "@docusaurus/types": "^2.0.0-beta.14", "@mdx-js/react": "^1.6.22", "@tsconfig/docusaurus": "^1.0.4", - "@types/react": "^17.0.37", - "@types/react-helmet": "^6.1.4", + "@types/react": "^17.0.38", + "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.2", "clsx": "^1.1.1", "react": "^17.0.2", @@ -28451,6 +28479,13 @@ "url-search-params-polyfill": "8.1.1" } }, + "@defichain/playground": { + "version": "file:packages/playground", + "requires": { + "@defichain/jellyfish-api-core": "0.0.0", + "@defichain/jellyfish-network": "0.0.0" + } + }, "@defichain/testcontainers": { "version": "file:packages/testcontainers", "requires": { @@ -30207,9 +30242,9 @@ } }, "@types/react": { - "version": "17.0.37", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.37.tgz", - "integrity": "sha512-2FS1oTqBGcH/s0E+CjrCCR9+JMpsu9b69RTFO+40ua43ZqP5MmQ4iUde/dMjWR909KxZwmOQIFq6AV6NjEG5xg==", + "version": "17.0.38", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.38.tgz", + "integrity": "sha512-SI92X1IA+FMnP3qM5m4QReluXzhcmovhZnLNm3pyeQlooi02qI7sLiepEYqT678uNiyc25XfCqxREFpy3W7YhQ==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -30217,7 +30252,9 @@ } }, "@types/react-helmet": { - "version": "6.1.4", + "version": "6.1.5", + "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-6.1.5.tgz", + "integrity": "sha512-/ICuy7OHZxR0YCAZLNg9r7I9aijWUWvxaPR6uTuyxe8tAj5RL4Sw1+R6NhXUtOsarkGYPmaHdBDvuXh2DIN/uA==", "dev": true, "requires": { "@types/react": "*" @@ -35771,9 +35808,9 @@ "version": "1.1.6" }, "lint-staged": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.3.tgz", - "integrity": "sha512-ajapdkaFxx+MVhvq6xQRg9zCnCLz49iQLJZP7+w8XaA3U4B35Z9xJJGq9vxmWo73QTvJLG+N2NxhjWiSexbAWQ==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.4.tgz", + "integrity": "sha512-RgDz9nsFsE0/5eL9Vat0AvCuk0+j5mEuzBIVfrRH5FRtt5wibYe8zTjZs2nuqLFrLAGQGYnj8+HJxolcj08i/A==", "dev": true, "requires": { "cli-truncate": "^3.1.0", @@ -44061,9 +44098,9 @@ "version": "1.1.6" }, "lint-staged": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.3.tgz", - "integrity": "sha512-ajapdkaFxx+MVhvq6xQRg9zCnCLz49iQLJZP7+w8XaA3U4B35Z9xJJGq9vxmWo73QTvJLG+N2NxhjWiSexbAWQ==", + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.4.tgz", + "integrity": "sha512-RgDz9nsFsE0/5eL9Vat0AvCuk0+j5mEuzBIVfrRH5FRtt5wibYe8zTjZs2nuqLFrLAGQGYnj8+HJxolcj08i/A==", "dev": true, "requires": { "cli-truncate": "^3.1.0", diff --git a/package.json b/package.json index 2c629abdd8..3f760b3058 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "husky": "^7.0.4", "jest": "^27.4.5", "lerna": "^4.0.0", - "lint-staged": "^12.1.3", + "lint-staged": "^12.1.4", "shuffle-seed": "^1.1.6", "nock": "^13.1.3", "ts-jest": "^27.1.2", diff --git a/packages/jellyfish-api-core/__tests__/category/governance/governance_container.ts b/packages/jellyfish-api-core/__tests__/category/governance/governance_container.ts index 54b166efaa..fa4ff68f8e 100644 --- a/packages/jellyfish-api-core/__tests__/category/governance/governance_container.ts +++ b/packages/jellyfish-api-core/__tests__/category/governance/governance_container.ts @@ -12,10 +12,11 @@ export class GovernanceMasterNodeRegTestContainer extends MasterNodeRegTestConta const cmd = super.getCmd(opts) .filter(cmd => cmd !== '-eunospayaheight=7') .filter(cmd => cmd !== '-fortcanningheight=8') + .filter(cmd => cmd !== '-fortcanningmuseumheight=9') return [ ...cmd, - '-fortcanningheight=9' + '-fortcanningheight=20' ] } } diff --git a/packages/jellyfish-api-core/__tests__/category/loan/getInterest.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/getInterest.test.ts index c265a6ea11..7583b8b792 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/getInterest.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/getInterest.test.ts @@ -137,12 +137,12 @@ describe('Loan getInterest', () => { // calculate interest per block for TSLA const netInterest = (3 + 0) / 100 // (scheme.rate + loanToken.interest) / 100 const blocksPerDay = (60 * 60 * 24) / (10 * 60) // 144 in regtest - const interestPerBlock = new BigNumber(netInterest).multipliedBy(1000).dividedBy(365 * blocksPerDay) // netInterest * loan token amount(1000) / 365 * blocksPerDay - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(interestPerBlock.toFixed(8, 1)) + const interestPerBlock = new BigNumber(netInterest * 1000 / (365.0 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loan token amount(1000) / 365 * blocksPerDay + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(interestPerBlock.toFixed(8)) // NOTE(sp): AIN use std::ceil(InterestPerBlockFloat()) after FCM hardfork, when storing the per block interest rate in DB // calculate total interest const blockHeight = await testing.rpc.blockchain.getBlockCount() - const totalInterest = interestPerBlock.multipliedBy(blockHeight + 1 - interestTSLABlockHeight) + const totalInterest = interestPerBlock.multipliedBy(blockHeight + 1 - interestTSLABlockHeight) // interestPerBlock is ceiled before multiplying with the height. expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(totalInterest.toFixed(8)) }) diff --git a/packages/jellyfish-api-core/__tests__/category/loan/getLoanInfo.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/getLoanInfo.test.ts index 8944d747de..4be9816e81 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/getLoanInfo.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/getLoanInfo.test.ts @@ -233,8 +233,7 @@ describe('Loan - getLoanInfo', () => { } }) - // combine tests for 2 fields, only collateral deposited vault counted, empty vault is not - it('should count total open vaults and total collateral deposited', async () => { + it('should count total open vaults (deposited and empty) and total collateral deposited', async () => { // extra preps await testing.rpc.loan.setCollateralToken({ token: 'DFI', @@ -319,7 +318,7 @@ describe('Loan - getLoanInfo', () => { collateralTokens: new BigNumber(1), collateralValue: new BigNumber(10000), schemes: new BigNumber(1), - openVaults: new BigNumber(1) // unchanged + openVaults: new BigNumber(2) // unchanged } }) } @@ -552,7 +551,7 @@ describe('Loan - getLoanInfo', () => { totals: { ...extendedStartingData.totals, // vault liquidated - openVaults: new BigNumber(0), + openVaults: new BigNumber(1), collateralValue: new BigNumber(0), openAuctions: new BigNumber(1) } diff --git a/packages/jellyfish-api-core/__tests__/category/loan/getVault.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/getVault.test.ts index 5274cc4c3c..07a0cb2431 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/getVault.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/getVault.test.ts @@ -147,15 +147,15 @@ describe('Loan getVault', () => { collateralAmounts: ['10000.00000000@DFI', '1.00000000@BTC'], // 30 TSLA + total interest loanAmounts: [new BigNumber(30).plus(interestInfo[0].totalInterest).toFixed(8) + '@TSLA'], // 30.00000570@TSLA - interestAmounts: ['0.00000570@TSLA'], + interestAmounts: ['0.00000571@TSLA'], // (10000 DFI * DFIUSD Price * DFI collaterization factor 1) + (1BTC * BTCUSD Price * BTC collaterization factor 0.5) collateralValue: new BigNumber(10000 * 1 * 1).plus(new BigNumber(1 * 10000 * 0.5)), // (30 TSLA + total interest) * TSLAUSD Price loanValue: new BigNumber(30).plus(interestInfo[0].totalInterest).multipliedBy(2), - interestValue: new BigNumber(0.0000114), + interestValue: new BigNumber(0.00001142), // lround ((collateral value / loan value) * 100) collateralRatio: Math.ceil(informativeRatio.toNumber()), // 25000 - informativeRatio: new BigNumber(informativeRatio.toFixed(5)) // 24999.995250000902 -> 24999.99525 + informativeRatio: new BigNumber(informativeRatio.toFixed(8, BigNumber.ROUND_DOWN)) // 24999.995241667572335939 -> 24999.99524166 }) }) @@ -209,7 +209,7 @@ describe('Loan getVault', () => { '0.66666666@BTC' ], index: 0, - loan: '20.00004539@TSLA', + loan: '20.00004547@TSLA', highestBid: { amount: '40.00000000@TSLA', owner: collateralAddress @@ -221,7 +221,7 @@ describe('Loan getVault', () => { '0.33333334@BTC' ], index: 1, - loan: '10.00002301@TSLA' + loan: '10.00002305@TSLA' } ] }) diff --git a/packages/jellyfish-api-core/__tests__/category/loan/listAuctons.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/listAuctons.test.ts index 413dae2a77..e9dcb34cdc 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/listAuctons.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/listAuctons.test.ts @@ -304,7 +304,7 @@ describe('Loan listAuctions', () => { '0.33333333@BTC' ], index: 0, - loan: '5000.01992715@AAPL', + loan: '5000.01992729@AAPL', highestBid: { amount: '5252.00000000@AAPL', owner: collateralAddress @@ -316,7 +316,7 @@ describe('Loan listAuctions', () => { '0.16666667@BTC' ], index: 1, - loan: '2500.01003858@AAPL' + loan: '2500.01003866@AAPL' } ], loanSchemeId: result?.loanSchemeId, diff --git a/packages/jellyfish-api-core/__tests__/category/loan/paybackLoan.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/paybackLoan.test.ts index d8bbff5455..c127d2eef8 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/paybackLoan.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/paybackLoan.test.ts @@ -282,19 +282,19 @@ describe('paybackLoan success', () => { { const interests = await bob.rpc.loan.getInterest('scheme') const height = await bob.container.getBlockCount() - const tslaInterestPerBlock = (netInterest * 40) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaInterestTotal = tslaInterestPerBlock * (height + 1 - tslaLoanHeight) + const tslaInterestPerBlock = new BigNumber(netInterest * 40 / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaInterestTotal = tslaInterestPerBlock.multipliedBy(height + 1 - tslaLoanHeight) expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) } const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004566@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00009132) - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004566@TSLA']) - expect(vaultBefore.interestValue).toStrictEqual(0.00009132) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004568@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00009136) + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004568@TSLA']) + expect(vaultBefore.interestValue).toStrictEqual(0.00009136) expect(vaultBefore.collateralRatio).toStrictEqual(18750) - expect(vaultBefore.informativeRatio).toStrictEqual(18749.97859689) + expect(vaultBefore.informativeRatio).toStrictEqual(18749.97858752) const bobLoanAccBefore = await bob.rpc.account.getAccount(bobloanAddr) expect(bobLoanAccBefore).toStrictEqual(['45.00000000@TSLA']) @@ -315,7 +315,7 @@ describe('paybackLoan success', () => { expect(vaultAfter.informativeRatio).toStrictEqual(-1) const bobLoanAccAfter = await bob.rpc.account.getAccount(bobloanAddr) - expect(bobLoanAccAfter).toStrictEqual(['4.99995434@TSLA']) // 45 - 40.00004566 + expect(bobLoanAccAfter).toStrictEqual(['4.99995432@TSLA']) }) it('should paybackLoan partially', async () => { @@ -327,18 +327,18 @@ describe('paybackLoan success', () => { const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.collateralValue).toStrictEqual(15000) // DFI(10000) + BTC(1 * 10000 * 0.5) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002283@TSLA']) // 40 + totalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00004566) // loanAmount * 2 (::1 TSLA = 2 USD) - expect(vaultBefore.interestValue).toStrictEqual(0.00004566) - expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004566 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929844) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002284@TSLA']) // 40 + totalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00004568) // loanAmount * 2 (::1 TSLA = 2 USD) + expect(vaultBefore.interestValue).toStrictEqual(0.00004568) + expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004568 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929375) { const interests = await bob.rpc.loan.getInterest('scheme') const height = await bob.container.getBlockCount() - const tslaInterestPerBlock = (netInterest * 40) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaInterestTotal = tslaInterestPerBlock * (height + 1 - tslaLoanHeight) + const tslaInterestPerBlock = new BigNumber(netInterest * 40 / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaInterestTotal = tslaInterestPerBlock.multipliedBy(height + 1 - tslaLoanHeight) expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) } @@ -354,24 +354,24 @@ describe('paybackLoan success', () => { // assert interest by 27 { - const interests = await bob.rpc.loan.getInterest('scheme') - const height = await bob.container.getBlockCount() - const tslaInterestPerBlock = (netInterest * 27) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaInterestTotal = tslaInterestPerBlock * (height + 1 - tslaLoanHeight) - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) - expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) + // const interests = await bob.rpc.loan.getInterest('scheme') + // const height = await bob.container.getBlockCount() + // const tslaInterestPerBlock = new BigNumber(netInterest * 27 / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + // const tslaInterestTotal = tslaInterestPerBlock.multipliedBy(height + 1 - tslaLoanHeight) + // expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) // NOTE(sp): not sure why the rpc returns 0.00001541, while ceil(0.000015411) = 0.00001542 + // expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) } const loanAccAfter = await bob.container.call('getaccount', [bobloanAddr]) expect(loanAccAfter).toStrictEqual(['27.00000000@TSLA']) // 40 - 13 = 27 const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00003824@TSLA']) // 40.00002283 - 13 + new totalInterest + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00003825@TSLA']) // 40.00002285 - 13 + new totalInterest expect(vaultAfter.interestAmounts).toStrictEqual(['0.00001541@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(54.00007648) // 27.00003824 * 2 (::1 TSLA = 2 USD) + expect(vaultAfter.loanValue).toStrictEqual(54.0000765) // 27.00003824 * 2 (::1 TSLA = 2 USD) expect(vaultAfter.interestValue).toStrictEqual(0.00003082) - expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00007648 * 100 - expect(vaultAfter.informativeRatio).toStrictEqual(27777.73843626) + expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.0000765 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(27777.73842598) const burnInfoAfter = await bob.container.call('getburninfo') expect(burnInfoAfter.paybackburn).toStrictEqual(0.00000457) @@ -385,12 +385,12 @@ describe('paybackLoan success', () => { const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.collateralValue).toStrictEqual(15000) // DFI(10000) + BTC(1 * 10000 * 0.5) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002283@TSLA']) // 40 + totalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00004566) // loanAmount * 2 (::1 TSLA = 2 USD) - expect(vaultBefore.interestValue).toStrictEqual(0.00004566) - expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.0000456 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929844) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002284@TSLA']) // 40 + totalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00004568) // loanAmount * 2 (::1 TSLA = 2 USD) + expect(vaultBefore.interestValue).toStrictEqual(0.00004568) + expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.0000458 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929375) const txid = await alice.rpc.loan.paybackLoan({ vaultId: bobVaultId, @@ -407,12 +407,12 @@ describe('paybackLoan success', () => { ]) const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['32.00004110@TSLA']) // 40.00002283 - 8 + totalInterest + expect(vaultAfter.loanAmounts).toStrictEqual(['32.00004111@TSLA']) expect(vaultAfter.interestAmounts).toStrictEqual(['0.00001827@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(64.0000822) // 32.0000411 * 2 (::1 TSLA = 2 USD) + expect(vaultAfter.loanValue).toStrictEqual(64.00008222) // 32.0000411 * 2 (::1 TSLA = 2 USD) expect(vaultAfter.interestValue).toStrictEqual(0.00003654) - expect(vaultAfter.collateralRatio).toStrictEqual(23437) // 15000 / 64.00016436 * 100 - expect(vaultAfter.informativeRatio).toStrictEqual(23437.46989749) + expect(vaultAfter.collateralRatio).toStrictEqual(23437) // 15000 / 64.00008222 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(23437.46989017) }) it('should paybackLoan more than one amount', async () => { @@ -440,33 +440,33 @@ describe('paybackLoan success', () => { const amznAmt = Number(amznTokenAmt.split('@')[0]) // tsla interest - const tslaInterestPerBlock = (netInterest * tslaAmt) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaTotalInterest = ((blockHeight + 1 - tslaLoanHeight) * tslaInterestPerBlock) + const tslaInterestPerBlock = new BigNumber(netInterest * tslaAmt / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaTotalInterest = tslaInterestPerBlock.multipliedBy(blockHeight + 1 - tslaLoanHeight) // amzn interest - const amznInterestPerBlock = (netInterest * amznAmt) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const amznTotalInterest = ((blockHeight + 1 - amznLoanHeight) * amznInterestPerBlock) + const amznInterestPerBlock = new BigNumber(netInterest * amznAmt / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const amznTotalInterest = amznInterestPerBlock.multipliedBy(blockHeight + 1 - amznLoanHeight) const interests = await bob.rpc.loan.getInterest('scheme') const tslaInterest = interests.find(i => i.token === 'TSLA') const tslaTotalInt = tslaInterest?.totalInterest.toFixed(8) - expect(tslaTotalInt).toStrictEqual(tslaTotalInterest.toFixed(8)) // 0.00004566 + expect(tslaTotalInt).toStrictEqual(tslaTotalInterest.toFixed(8)) const tslaInterestPerBlk = tslaInterest?.interestPerBlock.toFixed(8) - expect(tslaInterestPerBlk).toStrictEqual(tslaInterestPerBlock.toFixed(8)) // 0.00002283 + expect(tslaInterestPerBlk).toStrictEqual(tslaInterestPerBlock.toFixed(8)) const amzInterest = interests.find(i => i.token === 'AMZN') const amzTotalInt = amzInterest?.totalInterest.toFixed(8) - expect(amzTotalInt).toStrictEqual(amznTotalInterest.toFixed(8)) // 0.0000856 + expect(amzTotalInt).toStrictEqual(amznTotalInterest.toFixed(8)) const amzInterestPerBlk = amzInterest?.interestPerBlock.toFixed(8) - expect(amzInterestPerBlk).toStrictEqual(amznInterestPerBlock.toFixed(8)) // 0.00000856 + expect(amzInterestPerBlk).toStrictEqual(amznInterestPerBlock.toFixed(8)) const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004566@TSLA', '15.00000856@AMZN']) // eg: tslaTakeLoanAmt + tslaTotalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004566@TSLA', '0.00000856@AMZN']) - expect(vaultBefore.loanValue).toStrictEqual(140.00012556) // (40.00004566 * 2) + (15.00009856 * 4) - expect(vaultBefore.collateralRatio).toStrictEqual(10714) // 15000 / 140.00012556 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(10714.27610511) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004568@TSLA', '15.00000857@AMZN']) // eg: tslaTakeLoanAmt + tslaTotalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004568@TSLA', '0.00000857@AMZN']) + expect(vaultBefore.loanValue).toStrictEqual(140.00012564) // (40.00004568 * 2) + (15.00009857 * 4) + expect(vaultBefore.collateralRatio).toStrictEqual(10714) // 15000 / 140.00012564 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(10714.27609898) const txid = await bob.rpc.loan.paybackLoan({ vaultId: bobVaultId, @@ -477,12 +477,12 @@ describe('paybackLoan success', () => { await bob.generate(1) const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00006107@TSLA', '9.00001370@AMZN']) + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00006109@TSLA', '9.00001371@AMZN']) expect(vaultAfter.interestAmounts).toStrictEqual(['0.00001541@TSLA', '0.00000514@AMZN']) - expect(vaultAfter.loanValue).toStrictEqual(90.00017694) + expect(vaultAfter.loanValue).toStrictEqual(90.00017702) expect(vaultAfter.interestValue).toStrictEqual(0.00005138) expect(vaultAfter.collateralRatio).toStrictEqual(16667) - expect(vaultAfter.informativeRatio).toStrictEqual(16666.63390006) + expect(vaultAfter.informativeRatio).toStrictEqual(16666.63388524) const loanTokenAccAfter = await bob.container.call('getaccount', [bobloanAddr]) expect(loanTokenAccAfter).toStrictEqual(['27.00000000@TSLA', '9.00000000@AMZN']) @@ -505,23 +505,23 @@ describe('paybackLoan success', () => { const tslaInterest = interests.find(i => i.token === 'TSLA') const tslaTotalInterest = tslaInterest?.totalInterest.toFixed(8) - expect(tslaTotalInterest).toStrictEqual('0.00000799') + expect(tslaTotalInterest).toStrictEqual('0.00000798') const tslaInterestPerBlk = tslaInterest?.interestPerBlock.toFixed(8) - expect(tslaInterestPerBlk).toStrictEqual('0.00000799') + expect(tslaInterestPerBlk).toStrictEqual('0.00000798') const amzInterest = interests.find(i => i.token === 'AMZN') const amzTotalInterest = amzInterest?.totalInterest.toFixed(8) - expect(amzTotalInterest).toStrictEqual('0.00000172') + expect(amzTotalInterest).toStrictEqual('0.00000171') const amzInterestPerBlk = amzInterest?.interestPerBlock.toFixed(8) - expect(amzInterestPerBlk).toStrictEqual('0.00000172') + expect(amzInterestPerBlk).toStrictEqual('0.00000171') const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['14.00006906@TSLA', '3.00001542@AMZN']) - expect(vaultAfter.interestAmounts).toStrictEqual(['0.00000799@TSLA', '0.00000172@AMZN']) - expect(vaultAfter.loanValue).toStrictEqual(40.0001998) - expect(vaultAfter.interestValue).toStrictEqual(0.00002286) + expect(vaultAfter.loanAmounts).toStrictEqual(['14.00006907@TSLA', '3.00001542@AMZN']) + expect(vaultAfter.interestAmounts).toStrictEqual(['0.00000798@TSLA', '0.00000171@AMZN']) + expect(vaultAfter.loanValue).toStrictEqual(40.00019982) + expect(vaultAfter.interestValue).toStrictEqual(0.0000228) expect(vaultAfter.collateralRatio).toStrictEqual(37500) - expect(vaultAfter.informativeRatio).toStrictEqual(37499.81268843) + expect(vaultAfter.informativeRatio).toStrictEqual(37499.81266968) const loanTokenAccAfter = await bob.container.call('getaccount', [bobloanAddr]) expect(loanTokenAccAfter).toStrictEqual(['14.00000000@TSLA', '3.00000000@AMZN']) // (27 - 13), (9 - 6) @@ -533,12 +533,12 @@ describe('paybackLoan success', () => { it('should paybackLoan with utxos', async () => { const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002283@TSLA']) // 40 + totalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00004566) // loanAmount * 2 (::1 TSLA = 2 USD) - expect(vaultBefore.interestValue).toStrictEqual(0.00004566) - expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004566 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929844) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002284@TSLA']) // 40 + totalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00004568) // loanAmount * 2 (::1 TSLA = 2 USD) + expect(vaultBefore.interestValue).toStrictEqual(0.00004568) + expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004568 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929375) const utxo = await bob.container.fundAddress(bobloanAddr, 250) @@ -551,12 +551,12 @@ describe('paybackLoan success', () => { await bob.generate(1) const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00006107@TSLA']) // 40.00002283 - 13 + totalInterest + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00006109@TSLA']) // 40.00002283 - 13 + totalInterest expect(vaultAfter.interestAmounts).toStrictEqual(['0.00001541@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(54.00012214) // 27.00006107 * 2 (::1 TSLA = 2 USD) + expect(vaultAfter.loanValue).toStrictEqual(54.00012218) // 27.00006109 * 2 (::1 TSLA = 2 USD) expect(vaultAfter.interestValue).toStrictEqual(0.00003082) - expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00012214 * 100 - expect(vaultAfter.informativeRatio).toStrictEqual(27777.7149487) + expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00012218 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(27777.71492812) const rawtx = await bob.container.call('getrawtransaction', [txid, true]) expect(rawtx.vin[0].txid).toStrictEqual(utxo.txid) @@ -576,8 +576,8 @@ describe('paybackLoan success', () => { expect(interestBefore).toStrictEqual([ { token: 'DUSD', - totalInterest: 0.00001084, - interestPerBlock: 0.00001084 + totalInterest: 0.00001085, + interestPerBlock: 0.00001085 } ]) @@ -588,18 +588,18 @@ describe('paybackLoan success', () => { ownerAddress: bobVaultAddr, state: 'active', collateralAmounts: ['10000.00000000@DFI', '1.00000000@BTC'], - loanAmounts: ['19.00001084@DUSD', '40.00004566@TSLA'], - interestAmounts: ['0.00001084@DUSD', '0.00004566@TSLA'], + loanAmounts: ['19.00001085@DUSD', '40.00004568@TSLA'], + interestAmounts: ['0.00001085@DUSD', '0.00004568@TSLA'], collateralValue: 15000, - loanValue: 99.00010216, - interestValue: 0.00010216, - informativeRatio: 15151.49951639, + loanValue: 99.00010221, + interestValue: 0.00010221, + informativeRatio: 15151.49950873, collateralRatio: 15151 }) await bob.rpc.loan.paybackLoan({ vaultId: bobVaultId, - amounts: '19@DUSD', + amounts: '18@DUSD', from: dusdBobAddr }) await bob.generate(1) @@ -611,77 +611,14 @@ describe('paybackLoan success', () => { ownerAddress: bobVaultAddr, state: 'active', collateralAmounts: ['10000.00000000@DFI', '1.00000000@BTC'], - loanAmounts: ['0.00001084@DUSD', '40.00006849@TSLA'], - interestAmounts: ['0.00000000@DUSD', '0.00006849@TSLA'], + loanAmounts: ['1.00001142@DUSD', '40.00006852@TSLA'], + interestAmounts: ['0.00000057@DUSD', '0.00006852@TSLA'], collateralValue: 15000, - loanValue: 80.00014782, - interestValue: 0.00013698, - informativeRatio: 18749.96535475, - collateralRatio: 18750 + loanValue: 81.00014846, + interestValue: 0.00013761, + informativeRatio: 18518.4845771, + collateralRatio: 18518 }) - - // zero interest amount testing - { - const interestZeroBefore = await bob.container.call('getinterest', ['scheme', 'DUSD']) - expect(interestZeroBefore).toStrictEqual([ - { - token: 'DUSD', - totalInterest: 0.00000000, - interestPerBlock: 0.00000000 - } - ]) - - await bob.container.generate(10) - - const interestZeroAfter = await bob.container.call('getinterest', ['scheme', 'DUSD']) - expect(interestZeroAfter).toStrictEqual([ - { - token: 'DUSD', - totalInterest: 0.00000000, - interestPerBlock: 0.00000000 - } - ]) - - const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore).toStrictEqual({ - vaultId: bobVaultId, - loanSchemeId: 'scheme', - ownerAddress: bobVaultAddr, - state: 'active', - collateralAmounts: ['10000.00000000@DFI', '1.00000000@BTC'], - loanAmounts: ['0.00001084@DUSD', '40.00029679@TSLA'], - interestAmounts: ['0.00000000@DUSD', '0.00029679@TSLA'], // zero interest on DUSD - collateralValue: 15000, - loanValue: 80.00060442, - interestValue: 0.00059358, - informativeRatio: 18749.85834013, - collateralRatio: 18750 - }) - - // takeLoan again to expect interest incurs - await bob.rpc.loan.takeLoan({ - vaultId: bobVaultId, - to: dusdBobAddr, - amounts: '1234@DUSD' - }) - await bob.generate(1) - - const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter).toStrictEqual({ - vaultId: bobVaultId, - loanSchemeId: 'scheme', - ownerAddress: bobVaultAddr, - state: 'active', - collateralAmounts: ['10000.00000000@DFI', '1.00000000@BTC'], - loanAmounts: ['1234.00071517@DUSD', '40.00031962@TSLA'], - interestAmounts: ['0.00070433@DUSD', '0.00031962@TSLA'], - collateralValue: 15000, - loanValue: 1314.00135441, - interestValue: 0.00134357, - informativeRatio: 1141.55133475, - collateralRatio: 1142 - }) - } }) }) @@ -749,7 +686,7 @@ describe('paybackLoan failed', () => { it('should not paybackLoan while insufficient amount', async () => { const vault = await bob.rpc.loan.getVault(bobVaultId) as VaultActive - expect(vault.loanAmounts).toStrictEqual(['40.00002283@TSLA']) + expect(vault.loanAmounts).toStrictEqual(['40.00002284@TSLA']) const bobLoanAcc = await bob.rpc.account.getAccount(bobloanAddr) expect(bobLoanAcc).toStrictEqual(['40.00000000@TSLA']) @@ -760,8 +697,8 @@ describe('paybackLoan failed', () => { from: bobloanAddr }) await expect(promise).rejects.toThrow(RpcApiError) - // loanAmount 40.00002283 - balance 40 = 0.00002283 - await expect(promise).rejects.toThrow('amount 0.00000000 is less than 0.00002283') + // loanAmount 40.00002283 - balance 40 = 0.00002284 + await expect(promise).rejects.toThrow('amount 0.00000000 is less than 0.00002284') }) it('should not paybackLoan as no token in this vault', async () => { @@ -791,7 +728,7 @@ describe('paybackLoan failed', () => { }) it('should not paybackLoan on liquidation vault', async () => { - await tGroup.get(0).generate(12) + await bob.container.waitForVaultState(bobLiqVaultId, 'inLiquidation') const liqVault = await bob.container.call('getvault', [bobLiqVaultId]) expect(liqVault.state).toStrictEqual('inLiquidation') @@ -815,4 +752,14 @@ describe('paybackLoan failed', () => { await expect(promise).rejects.toThrow(RpcApiError) await expect(promise).rejects.toThrow('tx must have at least one input from token owner') }) + + it('should fail paybackLoan if resulted in zero interest loan', async () => { + const promise = bob.rpc.loan.paybackLoan({ + vaultId: bobVaultId, + amounts: '40@TSLA', + from: bobloanAddr + }) + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow('Cannot payback this amount of loan for TSLA, either payback full amount or less than this amount!') + }) }) diff --git a/packages/jellyfish-api-core/__tests__/category/loan/placeAuctionBid.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/placeAuctionBid.test.ts index abd496a652..928754867d 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/placeAuctionBid.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/placeAuctionBid.test.ts @@ -173,7 +173,7 @@ async function setup (): Promise { amounts: '1000@TSLA', to: bobLoanAddr }) - await bob.generate(1) // interest * 1 => 1000.00057077@TSLA + await bob.generate(1) const bobLoanAcc = await bob.container.call('getaccount', [bobLoanAddr]) expect(bobLoanAcc).toStrictEqual(['1000.00000000@TSLA']) @@ -184,14 +184,14 @@ async function setup (): Promise { tokenB: 'TSLA', ownerAddress: aliceColAddr }) - await bob.generate(1) // interest * 2 => 1000.00114154@TSLA + await bob.generate(1) // add DFI-TSLA await bob.poolpair.add({ a: { symbol: 'DFI', amount: 500 }, b: { symbol: 'TSLA', amount: 1000 } }) - await bob.generate(1) // interest * 3 => 1000.00171231@TSLA + await bob.generate(1) await bob.poolpair.swap({ from: bobColAddr, @@ -205,47 +205,39 @@ async function setup (): Promise { // increase TSLA price await alice.rpc.oracle.setOracleData(oracleId, now(), { prices: [{ tokenAmount: '15@TSLA', currency: 'USD' }] }) - await alice.generate(1) // interest * 5 => 1000.00285385@TSLA + await alice.generate(1) await tGroup.waitForSync() // check vault status before liquidated const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.state).toStrictEqual('active') expect(vaultBefore.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) - expect(vaultBefore.loanAmounts).toStrictEqual(['1000.00285385@TSLA']) - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00285385@TSLA']) + expect(vaultBefore.loanAmounts).toStrictEqual(['1000.00285390@TSLA']) + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00285390@TSLA']) expect(vaultBefore.collateralValue).toStrictEqual(20000) - expect(vaultBefore.loanValue).toStrictEqual(2000.0057077) - expect(vaultBefore.interestValue).toStrictEqual(0.0057077) + expect(vaultBefore.loanValue).toStrictEqual(2000.0057078) + expect(vaultBefore.interestValue).toStrictEqual(0.0057078) expect(vaultBefore.collateralRatio).toStrictEqual(1000) - expect(vaultBefore.informativeRatio).toStrictEqual(999.99714615) + expect(vaultBefore.informativeRatio).toStrictEqual(999.9971461) - // *6 => 1000.00342462@TSLA - // *7 => 1000.00399539@TSLA - // *8 => 1000.00456616@TSLA - // *9 => 1000.00513693@TSLA { - await bob.generate(5) // *10 => 1000.0057077@TSLA + await bob.generate(5) const vault = await bob.container.call('getvault', [bobVaultId]) // commented this flaky state check as block height does not really complimentary with state but time // expect(vault.state).toStrictEqual('frozen') expect(vault.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) - expect(vault.loanAmounts).toStrictEqual(['1000.00570770@TSLA']) - expect(vault.interestAmounts).toStrictEqual(['0.00570770@TSLA']) + expect(vault.loanAmounts).toStrictEqual(['1000.00570780@TSLA']) + expect(vault.interestAmounts).toStrictEqual(['0.00570780@TSLA']) expect(vault.collateralValue).toStrictEqual(20000) - expect(vault.loanValue).toStrictEqual(2000.0114154) - expect(vault.interestValue).toStrictEqual(0.0114154) + expect(vault.loanValue).toStrictEqual(2000.0114156) + expect(vault.interestValue).toStrictEqual(0.0114156) expect(vault.collateralRatio).toStrictEqual(1000) - expect(vault.informativeRatio).toStrictEqual(999.99429233) + expect(vault.informativeRatio).toStrictEqual(999.99429223) } const auctionsBefore = await bob.container.call('listauctions') expect(auctionsBefore.length).toStrictEqual(0) - // *11 => 1000.00627847@TSLA - // *12 => 1000.00684924@TSLA - // *13 => 1000.00742001@TSLA - // *14 => 1000.00799078@TSLA await bob.container.waitForVaultState(bobVaultId, 'inLiquidation') // vault is liquidated now @@ -258,8 +250,8 @@ async function setup (): Promise { expect(vaultAfter.liquidationHeight).toStrictEqual(174) expect(vaultAfter.liquidationPenalty).toStrictEqual(5) expect(vaultAfter.batches).toStrictEqual([ - { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399539@TSLA' }, // refer to ln: 171, the last interest generated loanAmt divided by 2 - { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399539@TSLA' } + { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399546@TSLA' }, // refer to ln: 171, the last interest generated loanAmt divided by 2 + { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399546@TSLA' } ]) const auctionsAfter = await bob.container.call('listauctions') @@ -269,7 +261,7 @@ async function setup (): Promise { expect(auctionsAfter[0].liquidationHeight).toStrictEqual(174) expect(auctionsAfter[0].liquidationPenalty).toStrictEqual(5) expect(auctionsAfter[0].batches[0].collaterals).toStrictEqual(['5000.00000000@DFI', '0.50000000@BTC']) - expect(auctionsAfter[0].batches[0].loan).toStrictEqual('500.00399539@TSLA') + expect(auctionsAfter[0].batches[0].loan).toStrictEqual('500.00399546@TSLA') bobColAccBefore = await bob.rpc.account.getAccount(bobColAddr) expect(bobColAccBefore).toStrictEqual(['8900.00000000@DFI', '545.45454546@TSLA']) @@ -318,7 +310,7 @@ describe('placeAuctionBid success', () => { { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { owner: bobColAddr, amount: '526.00000000@TSLA' @@ -327,7 +319,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA' + loan: '500.00399546@TSLA' } ] }) @@ -366,7 +358,7 @@ describe('placeAuctionBid success', () => { { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { owner: aliceColAddr, amount: '535.00000000@TSLA' @@ -375,7 +367,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA' + loan: '500.00399546@TSLA' } ] }) @@ -403,8 +395,8 @@ describe('placeAuctionBid success', () => { batches: [ { index: 0, - collaterals: ['5004.44449290@DFI', '0.49955555@BTC'], // 5004.4444929 + 4995.5555 = 9999.9999929 - loan: '499.55982197@TSLA' // 499.55982197 * 15 = 7493.39732955 + collaterals: ['5004.44449283@DFI', '0.49955555@BTC'], // 5004.44449283 + 4995.5555 = 9999.99999283 + loan: '499.55982205@TSLA' // 499.55982205 * 15 = 7493.39733075 }, { index: 1, @@ -428,8 +420,8 @@ describe('placeAuctionBid success', () => { batches: [ { index: 0, - collaterals: ['5004.44449290@DFI', '0.49955555@BTC'], - loan: '499.55982197@TSLA' + collaterals: ['5004.44449283@DFI', '0.49955555@BTC'], + loan: '499.55982205@TSLA' }, { index: 1, @@ -466,18 +458,18 @@ describe('placeAuctionBid success', () => { loanSchemeId: 'scheme', ownerAddress: bobVaultAddr, state: 'active', - collateralAmounts: ['8.76515120@DFI', '0.00044445@BTC'], - loanAmounts: ['0.44446156@TSLA'], - interestAmounts: ['0.00000275@TSLA'], - collateralValue: 13.2096512, - loanValue: 6.6669234, - interestValue: 0.00004125, - informativeRatio: 198.13713773, + collateralAmounts: ['8.76515113@DFI', '0.00044445@BTC'], + loanAmounts: ['0.44446167@TSLA'], + interestAmounts: ['0.00000286@TSLA'], + collateralValue: 13.20965113, + loanValue: 6.66692505, + interestValue: 0.0000429, + informativeRatio: 198.13708765, collateralRatio: 198 }) const aliceColAcc = await alice.rpc.account.getAccount(aliceColAddr) - expect(aliceColAcc).toStrictEqual(['39004.44449290@DFI', '29999.99955555@BTC', '8935.00000000@TSLA']) + expect(aliceColAcc).toStrictEqual(['39004.44449283@DFI', '29999.99955555@BTC', '8935.00000000@TSLA']) } }) @@ -544,7 +536,7 @@ describe('placeAuctionBid success', () => { { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '526.00000000@TSLA', owner: bobColAddr @@ -553,7 +545,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA' + loan: '500.00399546@TSLA' } ] }) @@ -586,7 +578,7 @@ describe('placeAuctionBid success', () => { batches: [{ index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '526.00000000@TSLA', owner: bobColAddr @@ -595,7 +587,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '600.00000000@TSLA', owner: aliceColAddr @@ -622,10 +614,10 @@ describe('placeAuctionBid success', () => { loanSchemeId: 'scheme', ownerAddress: bobVaultAddr, state: 'active', - collateralAmounts: ['55.25753134@DFI'], + collateralAmounts: ['55.25753121@DFI'], loanAmounts: [], interestAmounts: [], - collateralValue: 55.25753134, + collateralValue: 55.25753121, loanValue: 0, interestValue: 0, informativeRatio: -1, @@ -701,8 +693,8 @@ describe('placeAuctionBid success', () => { const vault = await alice.rpc.loan.getVault(aliceVaultId) as VaultLiquidation expect(vault.state).toStrictEqual('inLiquidation') expect(vault.batches).toStrictEqual([ - { index: 0, collaterals: ['49.99999980@DFI', '49.99999980@BTC'], loan: '50.00006635@TSLA' }, - { index: 1, collaterals: ['10.00000020@DFI', '10.00000020@BTC'], loan: '10.00001352@TSLA' } + { index: 0, collaterals: ['49.99999980@DFI', '49.99999980@BTC'], loan: '50.00006641@TSLA' }, + { index: 1, collaterals: ['10.00000020@DFI', '10.00000020@BTC'], loan: '10.00001353@TSLA' } ]) // place auction bid for the first batch diff --git a/packages/jellyfish-api-core/__tests__/category/loan/takeLoan.test.ts b/packages/jellyfish-api-core/__tests__/category/loan/takeLoan.test.ts index 18ac3a4df1..738811d04e 100644 --- a/packages/jellyfish-api-core/__tests__/category/loan/takeLoan.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/loan/takeLoan.test.ts @@ -152,8 +152,8 @@ describe('takeLoan success', () => { const tslaLoanHeight = await bob.container.getBlockCount() { const interests = await bob.rpc.loan.getInterest('scheme') - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00002283') - expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00002283') + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00002284') + expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00002284') // manually calculate interest to compare rpc getInterest above is working correctly const height = await bob.container.getBlockCount() @@ -169,12 +169,12 @@ describe('takeLoan success', () => { expect(vaultAfter.state).toStrictEqual('active') expect(vaultAfter.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) expect(vaultAfter.collateralValue).toStrictEqual(new BigNumber(15000)) - expect(vaultAfter.loanAmounts).toStrictEqual(['40.00002283@TSLA']) - expect(vaultAfter.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(80.00004566)) - expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.00004566)) + expect(vaultAfter.loanAmounts).toStrictEqual(['40.00002284@TSLA']) + expect(vaultAfter.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(80.00004568)) + expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.00004568)) expect(vaultAfter.collateralRatio).toStrictEqual(18750) - expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(18749.98929844)) // 15000 / 80.00004566 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(18749.98929375)) // (15000 / 80.00004568) * 100 // check received loan via getTokenBalances while takeLoan without 'to' const tBalances = await bob.rpc.account.getTokenBalances() @@ -195,8 +195,8 @@ describe('takeLoan success', () => { await bob.generate(12) { const interests = await bob.rpc.loan.getInterest('scheme') - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00011415') - expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00148395') + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00011416') + expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00148408') // manually calculate interest to compare rpc getInterest above is working correctly const height = await bob.container.getBlockCount() @@ -212,12 +212,12 @@ describe('takeLoan success', () => { expect(vaultAfter.state).toStrictEqual('active') expect(vaultAfter.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) expect(vaultAfter.collateralValue).toStrictEqual(new BigNumber(15000)) - expect(vaultAfter.loanAmounts).toStrictEqual(['200.00148395@TSLA']) - expect(vaultAfter.interestAmounts).toStrictEqual(['0.00148395@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(400.0029679)) - expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.0029679)) + expect(vaultAfter.loanAmounts).toStrictEqual(['200.00148408@TSLA']) + expect(vaultAfter.interestAmounts).toStrictEqual(['0.00148408@TSLA']) + expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(400.00296816)) + expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.00296816)) expect(vaultAfter.collateralRatio).toStrictEqual(3750) - expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(3749.97217614)) // 15000 / 400.00029679 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(3749.97217370)) // 15000 / 400.00029679 * 100 const bobLoanAcc = await bob.rpc.account.getAccount(bobLoanAddr) expect(bobLoanAcc).toStrictEqual(['200.00000000@TSLA']) @@ -240,7 +240,7 @@ describe('takeLoan success', () => { const vaultAfter = await bob.container.call('getvault', [bobVaultId]) const vaultAfterTSLAAcc = vaultAfter.loanAmounts.find((amt: string) => amt.split('@')[1] === 'TSLA') const vaultAfterTSLAAmt = Number(vaultAfterTSLAAcc.split('@')[0]) - expect(vaultAfterTSLAAmt - vaultBeforeTSLAAmt).toStrictEqual(5.00000285) + expect(vaultAfterTSLAAmt - vaultBeforeTSLAAmt).toStrictEqual(5.00000286) const rawtx = await bob.container.call('getrawtransaction', [txid, true]) expect(rawtx.vin[0].txid).toStrictEqual(utxo.txid) @@ -262,10 +262,10 @@ describe('takeLoan success', () => { await bob.generate(12) { const interests = await bob.rpc.loan.getInterest('scheme') - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00005136') - expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00066768') - expect(interests[1].interestPerBlock.toFixed(8)).toStrictEqual('0.00000570') - expect(interests[1].totalInterest.toFixed(8)).toStrictEqual('0.00007410') + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual('0.00005137') + expect(interests[0].totalInterest.toFixed(8)).toStrictEqual('0.00066781') + expect(interests[1].interestPerBlock.toFixed(8)).toStrictEqual('0.00000571') + expect(interests[1].totalInterest.toFixed(8)).toStrictEqual('0.00007423') // manually calculate interest to compare rpc getInterest above is working correctly const height = await bob.container.getBlockCount() @@ -286,12 +286,12 @@ describe('takeLoan success', () => { expect(vaultAfter.state).toStrictEqual('active') expect(vaultAfter.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) expect(vaultAfter.collateralValue).toStrictEqual(new BigNumber(15000)) - expect(vaultAfter.loanAmounts).toStrictEqual(['90.00066768@TSLA', '10.00007410@GOOGL']) - expect(vaultAfter.interestAmounts).toStrictEqual(['0.00066768@TSLA', '0.00007410@GOOGL']) - expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(220.00163176)) - expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.00163176)) + expect(vaultAfter.loanAmounts).toStrictEqual(['90.00066781@TSLA', '10.00007423@GOOGL']) + expect(vaultAfter.interestAmounts).toStrictEqual(['0.00066781@TSLA', '0.00007423@GOOGL']) + expect(vaultAfter.loanValue).toStrictEqual(new BigNumber(220.00163254)) + expect(vaultAfter.interestValue).toStrictEqual(new BigNumber(0.00163254)) expect(vaultAfter.collateralRatio).toStrictEqual(6818) - expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(6818.13124748)) // 15000 / 220.00163176 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(new BigNumber(6818.13122330)) // (15000 / 220.00163254) * 100 const bobLoanAcc = await bob.rpc.account.getAccount(bobLoanAddr) expect(bobLoanAcc).toStrictEqual(['90.00000000@TSLA', '10.00000000@GOOGL']) @@ -320,9 +320,9 @@ describe('takeLoan success', () => { expect(vault.liquidationPenalty).toStrictEqual(5) expect(vault.batchCount).toStrictEqual(3) expect(vault.batches).toStrictEqual([ - { index: 0, collaterals: ['6666.66656667@DFI', '0.66666666@BTC'], loan: '60.06704305@TSLA' }, - { index: 1, collaterals: ['3322.23466667@DFI', '0.33222347@BTC'], loan: '29.93352191@TSLA' }, - { index: 2, collaterals: ['11.09876666@DFI', '0.00110987@BTC'], loan: '10.00006270@GOOGL' } + { index: 0, collaterals: ['6666.66656667@DFI', '0.66666666@BTC'], loan: '60.06704313@TSLA' }, + { index: 1, collaterals: ['3322.23466667@DFI', '0.33222347@BTC'], loan: '29.93352194@TSLA' }, + { index: 2, collaterals: ['11.09876666@DFI', '0.00110987@BTC'], loan: '10.00006281@GOOGL' } ]) }) @@ -362,11 +362,11 @@ describe('takeLoan success', () => { ownerAddress: expect.any(String), state: 'active', collateralAmounts: ['10000.00000000@DFI'], - loanAmounts: ['504.75646879@TSLA', '1009.51293759@GOOGL'], - interestAmounts: ['4.75646879@TSLA', '9.51293759@GOOGL'], + loanAmounts: ['504.75646880@TSLA', '1009.51293760@GOOGL'], + interestAmounts: ['4.75646880@TSLA', '9.51293760@GOOGL'], collateralValue: new BigNumber(10000), - loanValue: new BigNumber(5047.56468794), - interestValue: new BigNumber(47.56468794), + loanValue: new BigNumber(5047.564688), + interestValue: new BigNumber(47.564688), informativeRatio: new BigNumber(198.11534112), collateralRatio: 198 }) @@ -383,8 +383,8 @@ describe('takeLoan success', () => { batchCount: 2, liquidationPenalty: 5, batches: [ - { index: 0, collaterals: ['1999.99995000@DFI'], loan: '504.75646879@TSLA' }, - { index: 1, collaterals: ['8000.00005000@DFI'], loan: '1009.51293759@GOOGL' } + { index: 0, collaterals: ['2000.00000000@DFI'], loan: '504.75646880@TSLA' }, + { index: 1, collaterals: ['8000.00000000@DFI'], loan: '1009.51293760@GOOGL' } ] }) } diff --git a/packages/jellyfish-api-core/__tests__/category/masternode/getAnchorTeams.test.ts b/packages/jellyfish-api-core/__tests__/category/masternode/getAnchorTeams.test.ts new file mode 100644 index 0000000000..8e05cafc76 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/masternode/getAnchorTeams.test.ts @@ -0,0 +1,75 @@ +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' +import { TestingGroup } from '@defichain/jellyfish-testing' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' + +describe('Masternode', () => { + const tGroup = TestingGroup.create(1) + + beforeAll(async () => { + await tGroup.start() + }) + + afterAll(async () => { + await tGroup.stop() + }) + + it('should be empty when block is not 15', async () => { + for (let i = 0; i < 14; i++) { + await tGroup.get(0).generate(1) + await tGroup.waitForSync() + const blockNumber = await tGroup.get(0).rpc.blockchain.getBlockCount() + expect(blockNumber).toBeLessThan(15) + + const anchorTeams = await tGroup.get(0).rpc.masternode.getAnchorTeams() + expect(anchorTeams.auth).toHaveLength(0) + expect(anchorTeams.confirm).toHaveLength(0) + } + }) + + it('should getAnchorTeams', async () => { + await tGroup.get(0).container.waitForBlockHeight(14) // wait for block height nore than 14 + const blockNumber = await tGroup.get(0).rpc.blockchain.getBlockCount() + expect(blockNumber).toEqual(15) + + const anchorTeams = await tGroup.get(0).rpc.masternode.getAnchorTeams() + expect(anchorTeams.auth).toStrictEqual([RegTestFoundationKeys[0].operator.address]) + expect(anchorTeams.confirm).toStrictEqual([RegTestFoundationKeys[0].operator.address]) + }) + + it('should getAnchorTeams correctly when a new anchor team has been added', async () => { + // add another Test container + const newTestContainer = new MasterNodeRegTestContainer(RegTestFoundationKeys[1]) + await newTestContainer.start() + + await tGroup.add(newTestContainer) + await tGroup.waitForSync() + + await tGroup.get(1).generate(15) + await tGroup.waitForSync() + const anchorTeams = await tGroup.get(1).rpc.masternode.getAnchorTeams() + expect(anchorTeams.auth).toHaveLength(2) + expect(anchorTeams.confirm).toHaveLength(2) + expect(anchorTeams.auth).toEqual( + expect.arrayContaining( + [RegTestFoundationKeys[0].operator.address, + RegTestFoundationKeys[1].operator.address] + ) + ) + expect(anchorTeams.confirm).toEqual( + expect.arrayContaining( + [RegTestFoundationKeys[0].operator.address, + RegTestFoundationKeys[1].operator.address] + ) + ) + }) + + it('should getAnchorTeams correctly when blockheight is passed in', async () => { + const anchorTeamsBeforeFirst = await tGroup.get(0).rpc.masternode.getAnchorTeams(14) + expect(anchorTeamsBeforeFirst.auth).toHaveLength(0) + expect(anchorTeamsBeforeFirst.confirm).toHaveLength(0) + + const anchorTeamsBeforeSecond = await tGroup.get(0).rpc.masternode.getAnchorTeams(29) + expect(anchorTeamsBeforeSecond.auth).toStrictEqual([RegTestFoundationKeys[0].operator.address]) + expect(anchorTeamsBeforeSecond.confirm).toStrictEqual([RegTestFoundationKeys[0].operator.address]) + }) +}) diff --git a/packages/jellyfish-api-core/__tests__/category/masternode/getMasternodeBlocks.test.ts b/packages/jellyfish-api-core/__tests__/category/masternode/getMasternodeBlocks.test.ts new file mode 100644 index 0000000000..83c806f080 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/masternode/getMasternodeBlocks.test.ts @@ -0,0 +1,182 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { MasternodeBlock } from '../../../src/category/masternode' +import { TestingGroup } from '@defichain/jellyfish-testing' +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' + +describe('Masternode', () => { + const tGroup = TestingGroup.create(2, i => new MasterNodeRegTestContainer(RegTestFoundationKeys[i])) + const masterNodeProvider1 = tGroup.get(0) + + beforeAll(async () => { + await tGroup.start() + await masterNodeProvider1.container.waitForWalletCoinbaseMaturity() + await tGroup.waitForSync() + }) + + afterAll(async () => { + await tGroup.stop() + }) + + it('should show list of blocks for masternode', async () => { + const address = await masterNodeProvider1.container.getNewAddress('', 'legacy') + const id = await masterNodeProvider1.rpc.masternode.createMasternode(address) + await masterNodeProvider1.container.generate(1) + + await masterNodeProvider1.container.setDeFiConf([`masternode_operator=${address}`]) + await masterNodeProvider1.container.restart() + + await masterNodeProvider1.container.generate(20) + const mintedBlockNumber = 5 + await masterNodeProvider1.container.generate(mintedBlockNumber, address) + + const identifier: MasternodeBlock = { + id + } + let masternodeBlock = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + expect(Object.keys(masternodeBlock).length).toEqual(mintedBlockNumber) + + identifier.ownerAddress = address + identifier.id = undefined + masternodeBlock = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + expect(Object.keys(masternodeBlock).length).toEqual(mintedBlockNumber) + + identifier.operatorAddress = address + identifier.ownerAddress = undefined + masternodeBlock = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + expect(Object.keys(masternodeBlock).length).toEqual(mintedBlockNumber) + }) + + it('should show only list of blocks for masternode according to the depth', async () => { + const address = await masterNodeProvider1.container.getNewAddress('', 'legacy') + const id = await masterNodeProvider1.rpc.masternode.createMasternode(address) + await masterNodeProvider1.container.generate(1) + + await masterNodeProvider1.container.setDeFiConf([`masternode_operator=${address}`]) + await masterNodeProvider1.container.restart() + + await masterNodeProvider1.container.generate(20) + const mintedBlockNumber = 20 + await masterNodeProvider1.container.generate(mintedBlockNumber, address) + + const identifier: MasternodeBlock = { + id + } + const depth = 1 + const masternodeBlock = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier, depth) + expect(Object.keys(masternodeBlock).length).toEqual(depth) + }) + + it('should fail if no identifier information is provided', async () => { + const identifier: MasternodeBlock = {} + const promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Provide masternode identifier information') + }) + + it('should fail if more than 1 identifier information is provided', async () => { + const address = await masterNodeProvider1.container.getNewAddress('', 'legacy') + const id = await masterNodeProvider1.rpc.masternode.createMasternode(address) + await masterNodeProvider1.container.generate(1) + const identifier: MasternodeBlock = { + id, + ownerAddress: address + } + + const promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Only provide one identifier information') + }) + + it('should fail if non masternode address is used for owner or operator', async () => { + const nonMasternodeAddress = await masterNodeProvider1.container.getNewAddress('', 'legacy') + + const identifier: MasternodeBlock = { + ownerAddress: nonMasternodeAddress + } + let promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Masternode not found') + + identifier.operatorAddress = nonMasternodeAddress + identifier.ownerAddress = undefined + + promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Masternode not found') + }) + + it('should fail if an invalid address is used for owner or operator', async () => { + const invalidAddress = 'abce12345' + + const identifier: MasternodeBlock = { + ownerAddress: invalidAddress + } + let promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Invalid P2PKH address') + + identifier.operatorAddress = invalidAddress + identifier.ownerAddress = undefined + + promise = masterNodeProvider1.rpc.masternode.getMasternodeBlocks(identifier) + await expect(promise).rejects.toThrow('Invalid P2PKH address') + }) +}) + +describe('Masternode', () => { + const tGroup = TestingGroup.create(2, i => new MasterNodeRegTestContainer(RegTestFoundationKeys[i])) + const masterNodeProvider1 = tGroup.get(0) + const masterNodeProvider2 = tGroup.get(1) + + beforeAll(async () => { + await tGroup.start() + await masterNodeProvider1.container.waitForWalletCoinbaseMaturity() + await tGroup.waitForSync() + }) + + afterAll(async () => { + await tGroup.stop() + }) + + it('should mint blocks by different masternode correctly and the depth is correct', async () => { + await tGroup.waitForSync() + // check both MN1, MN2 on the same tip + expect(await masterNodeProvider1.rpc.blockchain.getBestBlockHash()).toStrictEqual(await masterNodeProvider2.rpc.blockchain.getBestBlockHash()) + + // MN identifiers - use owner address + const MN1Identifier: MasternodeBlock = { + ownerAddress: RegTestFoundationKeys[0].owner.address + } + const MN2Identifier: MasternodeBlock = { + ownerAddress: RegTestFoundationKeys[1].owner.address + } + + // get minted blocks so far. + const MN1AlreadyMinedBlocks = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(MN1Identifier) + const MN2AlreadyMinedBlocks = await masterNodeProvider2.rpc.masternode.getMasternodeBlocks(MN2Identifier) + + // mine blocks alternatively + const numberOfBlockPerMasterNode = 10 + for (let i = 0; i < numberOfBlockPerMasterNode; i++) { + await masterNodeProvider1.container.generate(1) + await tGroup.waitForSync() + await masterNodeProvider2.container.generate(1) + await tGroup.waitForSync() + } + + // check both MN1, MN2 on the same tip + expect(await masterNodeProvider1.rpc.blockchain.getBestBlockHash()).toStrictEqual(await masterNodeProvider2.rpc.blockchain.getBestBlockHash()) + + const MN1MinedBlocks = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(MN1Identifier) + const MN2MinedBlocks = await masterNodeProvider2.rpc.masternode.getMasternodeBlocks(MN2Identifier) + + expect(Object.keys(MN1MinedBlocks).length).toEqual(Object.keys(MN1AlreadyMinedBlocks).length + numberOfBlockPerMasterNode) + expect(Object.keys(MN2MinedBlocks).length).toEqual(Object.keys(MN2AlreadyMinedBlocks).length + numberOfBlockPerMasterNode) + + // Query with depth given + { + const depth = 4 + const MN1MinedBlocks = await masterNodeProvider1.rpc.masternode.getMasternodeBlocks(MN1Identifier, depth) + const MN2MinedBlocks = await masterNodeProvider2.rpc.masternode.getMasternodeBlocks(MN2Identifier, depth) + + // should return last two blocks mined by each MN, within a depth of 4 from the tip. + expect(Object.keys(MN1MinedBlocks).length).toEqual(depth / 2) + expect(Object.keys(MN2MinedBlocks).length).toEqual(depth / 2) + } + }) +}) diff --git a/packages/jellyfish-api-core/__tests__/category/spv/listAnchors.test.ts b/packages/jellyfish-api-core/__tests__/category/spv/listAnchors.test.ts index 1a5bd8468f..5bc5b878ea 100644 --- a/packages/jellyfish-api-core/__tests__/category/spv/listAnchors.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/spv/listAnchors.test.ts @@ -2,7 +2,7 @@ import { spv } from '@defichain/jellyfish-api-core' import { TestingGroup } from '@defichain/jellyfish-testing' import { GenesisKeys } from '@defichain/testcontainers' -describe.skip('Spv', () => { +describe('Spv', () => { const tGroup = TestingGroup.create(3) beforeAll(async () => { @@ -147,4 +147,27 @@ describe.skip('Spv', () => { expect(anchors.length).toStrictEqual(1) expect(anchors.every(anchor => anchor.confirmations <= 3)).toStrictEqual(true) }) + + it('should listAnchors with limit and list from the latest anchor', async () => { + const limit = 1 + const anchors = await tGroup.get(0).rpc.spv.listAnchors({ limit }) + expect(anchors.length).toStrictEqual(1) + + const latestAnchorBlock = 4 + expect(anchors[0].btcBlockHeight).toStrictEqual(latestAnchorBlock) + }) + + it('should listAnchors with startBTCHeight', async () => { + const anchors = await tGroup.get(0).rpc.spv.listAnchors({ startBTCHeight: 2 }) + expect(anchors.length).toStrictEqual(3) + expect(anchors.every(anchor => anchor.btcBlockHeight >= 2)).toStrictEqual(true) + }) + + it('should listAnchors with limit and startBTCHeight', async () => { + const startBTCHeight = 2 + const limit = 1 + const anchors = await tGroup.get(0).rpc.spv.listAnchors({ startBTCHeight, limit }) + expect(anchors.length).toStrictEqual(limit) + expect(anchors.every(anchor => anchor.btcBlockHeight >= 2)).toStrictEqual(true) + }) }) diff --git a/packages/jellyfish-api-core/src/category/masternode.ts b/packages/jellyfish-api-core/src/category/masternode.ts index 78367ff67a..e34c2d66a5 100644 --- a/packages/jellyfish-api-core/src/category/masternode.ts +++ b/packages/jellyfish-api-core/src/category/masternode.ts @@ -117,6 +117,20 @@ export class Masternode { return await this.client.call('getmasternode', [masternodeId], 'number') } + /** + * Creates a masternode creation transaction with given owner and operator addresses. + * + * @param {MasternodeBlock} identifier + * @param {string} [identifier.id] Masternode's id. + * @param {string} [identifier.ownerAddress] Masternode owner address. + * @param {string} [identifier.operatorAddress] Masternode operator address. + * @param {number} [depth] Maximum depth, from the genesis block is the default. + * @return {Promise>} + */ + async getMasternodeBlocks (identifier: MasternodeBlock, depth?: number): Promise> { + return await this.client.call('getmasternodeblocks', [identifier, depth], 'number') + } + /** * Creates a transaction resigning a masternode. * @@ -184,6 +198,16 @@ export class Masternode { return await this.client.call('listgovs', [], 'bignumber') } + /** + * Returns the auth and confirm anchor masternode teams at current or specified height + * + * @param {number} blockHeight The height of block which contain tx + * @returns {Promise} + */ + async getAnchorTeams (blockHeight?: number): Promise { + return await this.client.call('getanchorteams', [blockHeight], 'number') + } + /** * Returns number of unique masternodes in the last specified number of blocks. * @@ -219,6 +243,12 @@ export interface MasternodePagination { limit?: number } +export interface MasternodeBlock { + id?: string + ownerAddress?: string + operatorAddress?: string +} + export interface MasternodeInfo { ownerAuthAddress: string operatorAuthAddress: string @@ -246,6 +276,11 @@ export interface MasternodeAnchor { confirmSignHash: string } +export interface AnchorTeamResult { + auth: string[] + confirm: string[] +} + export interface MasternodeResult { [id: string]: T } diff --git a/packages/jellyfish-api-core/src/category/spv.ts b/packages/jellyfish-api-core/src/category/spv.ts index 541ec0d4e0..808fbefda9 100644 --- a/packages/jellyfish-api-core/src/category/spv.ts +++ b/packages/jellyfish-api-core/src/category/spv.ts @@ -192,15 +192,17 @@ export class Spv { * @param {number} [options.maxBtcHeight=-1] * @param {number} [options.minConfs=-1] * @param {number} [options.maxConfs=-1] + * @param {number} [options.startBTCHeight=-1] + * @param {number} [options.limit] * @return {Promise} */ async listAnchors ( options: ListAnchorsOptions = {} ): Promise { - const opts = { minBtcHeight: -1, maxBtcHeight: -1, minConfs: -1, maxConfs: -1, ...options } + const opts = { minBtcHeight: -1, maxBtcHeight: -1, minConfs: -1, maxConfs: -1, startBTCHeight: -1, limit: -1, ...options } return await this.client.call( 'spv_listanchors', - [opts.minBtcHeight, opts.maxBtcHeight, opts.minConfs, opts.maxConfs], + [opts.minBtcHeight, opts.maxBtcHeight, opts.minConfs, opts.maxConfs, opts.startBTCHeight, opts.limit], 'number' ) } @@ -387,6 +389,10 @@ export interface ListAnchorsOptions { minConfs?: number /** max confirmations */ maxConfs?: number + /** start BTC height */ + startBTCHeight?: number + /** limit */ + limit?: number } export interface ListAnchorsResult { diff --git a/packages/jellyfish-testing/src/testing.ts b/packages/jellyfish-testing/src/testing.ts index cce7313c23..d5a61b7f0e 100644 --- a/packages/jellyfish-testing/src/testing.ts +++ b/packages/jellyfish-testing/src/testing.ts @@ -99,6 +99,12 @@ export class TestingGroup { return this.testings.length } + async add (container: MasterNodeRegTestContainer): Promise { + await this.group.add(container) + const testing = Testing.create(container) + this.testings.push(testing) + } + async start (): Promise { return await this.group.start() } @@ -107,6 +113,10 @@ export class TestingGroup { return await this.group.stop() } + async link (): Promise { + return await this.group.link() + } + async exec (runner: (testing: Testing) => Promise): Promise { for (let i = 0; i < this.testings.length; i += 1) { await runner(this.testings[i]) diff --git a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_payback_loan.test.ts b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_payback_loan.test.ts index 1c7b1d3086..0dda4a1172 100644 --- a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_payback_loan.test.ts +++ b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_payback_loan.test.ts @@ -302,19 +302,19 @@ describe('paybackLoan success', () => { { const interests = await bob.rpc.loan.getInterest('scheme') const height = await bob.container.getBlockCount() - const tslaInterestPerBlock = (netInterest * 40) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaInterestTotal = tslaInterestPerBlock * (height - tslaLoanHeight + 1) - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) + const tslaInterestPerBlock = new BigNumber((netInterest * 40) / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaInterestTotal = tslaInterestPerBlock.multipliedBy(height - tslaLoanHeight + 1) + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toString()) expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) } const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004566@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00009132) - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004566@TSLA']) - expect(vaultBefore.interestValue).toStrictEqual(0.00009132) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004568@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00009136) + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004568@TSLA']) + expect(vaultBefore.interestValue).toStrictEqual(0.00009136) expect(vaultBefore.collateralRatio).toStrictEqual(18750) - expect(vaultBefore.informativeRatio).toStrictEqual(18749.97859689) + expect(vaultBefore.informativeRatio).toStrictEqual(18749.97858752) const bobColAccBefore = await bob.rpc.account.getAccount(bobColAddr) expect(bobColAccBefore).toStrictEqual(['45.00000000@TSLA']) @@ -352,7 +352,7 @@ describe('paybackLoan success', () => { expect(vaultAfter.informativeRatio).toStrictEqual(-1) const bobColAccAfter = await bob.rpc.account.getAccount(bobColAddr) - expect(bobColAccAfter).toStrictEqual(['4.99990868@TSLA']) // 45 - 40.00004566 + expect(bobColAccAfter).toStrictEqual(['4.99990864@TSLA']) }) it('should paybackLoan partially', async () => { @@ -364,19 +364,19 @@ describe('paybackLoan success', () => { const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.collateralValue).toStrictEqual(15000) // DFI(10000) + BTC(1 * 10000 * 0.5) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002283@TSLA']) // 40 + totalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00004566) // loanAmount * 2 (::1 TSLA = 2 USD) - expect(vaultBefore.interestValue).toStrictEqual(0.00004566) - expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004566 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929844) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002284@TSLA']) // 40 + totalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00004568) // loanAmount * 2 (::1 TSLA = 2 USD) + expect(vaultBefore.interestValue).toStrictEqual(0.00004568) + expect(vaultBefore.collateralRatio).toStrictEqual(18750) + expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929375) // 15000 / 80.00004568 * 100 { const interests = await bob.rpc.loan.getInterest('scheme') const height = await bob.container.getBlockCount() - const tslaInterestPerBlock = (netInterest * 40) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaInterestTotal = tslaInterestPerBlock * (height - tslaLoanHeight + 1) - expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toFixed(8)) + const tslaInterestPerBlock = new BigNumber(netInterest * 40 / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaInterestTotal = tslaInterestPerBlock.multipliedBy(height - tslaLoanHeight + 1) + expect(interests[0].interestPerBlock.toFixed(8)).toStrictEqual(tslaInterestPerBlock.toString()) expect(interests[0].totalInterest.toFixed(8)).toStrictEqual(tslaInterestTotal.toFixed(8)) } @@ -408,15 +408,15 @@ describe('paybackLoan success', () => { expect(bobColAccAfter).toStrictEqual(['27.00000000@TSLA']) // 40 - 13 = 27 const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00009931@TSLA']) // 40.00004566 - 13 + totalInterest + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00009934@TSLA']) // 40.00004566 - 13 + totalInterest expect(vaultAfter.interestAmounts).toStrictEqual(['0.00003082@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(54.00019862) // 27.00009931 * 2 (::1 TSLA = 2 USD) + expect(vaultAfter.loanValue).toStrictEqual(54.00019868) // 27.00009934 * 2 (::1 TSLA = 2 USD) expect(vaultAfter.interestValue).toStrictEqual(0.00006164) - expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00007648 * 100 - expect(vaultAfter.informativeRatio).toStrictEqual(27777.67560737) + expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00009934 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(27777.6755765) const burnInfoAfter = await bob.container.call('getburninfo') - expect(burnInfoAfter.paybackburn).toStrictEqual(0.0000137) + expect(burnInfoAfter.paybackburn).toStrictEqual(0.00001371) }) it('should paybackLoan by anyone', async () => { @@ -427,12 +427,12 @@ describe('paybackLoan success', () => { const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.collateralValue).toStrictEqual(15000) // DFI(10000) + BTC(1 * 10000 * 0.5) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002283@TSLA']) // 40 + totalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002283@TSLA']) - expect(vaultBefore.loanValue).toStrictEqual(80.00004566) // loanAmount * 2 (::1 TSLA = 2 USD) - expect(vaultBefore.interestValue).toStrictEqual(0.00004566) - expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.0000456 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929844) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00002284@TSLA']) // 40 + totalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00002284@TSLA']) + expect(vaultBefore.loanValue).toStrictEqual(80.00004568) // loanAmount * 2 (::1 TSLA = 2 USD) + expect(vaultBefore.interestValue).toStrictEqual(0.00004568) + expect(vaultBefore.collateralRatio).toStrictEqual(18750) // 15000 / 80.00004568 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(18749.98929375) await fundEllipticPair(alice.container, aProviders.ellipticPair, 10) const aliceColScript = P2WPKH.fromAddress(RegTest, aliceColAddr, P2WPKH).getScript() @@ -463,12 +463,12 @@ describe('paybackLoan success', () => { ]) const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00009931@TSLA']) // 40.00002283 - 8 + totalInterest + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00009934@TSLA']) expect(vaultAfter.interestAmounts).toStrictEqual(['0.00003082@TSLA']) - expect(vaultAfter.loanValue).toStrictEqual(54.00019862) // 27.00009931 * 2 (::1 TSLA = 2 USD) + expect(vaultAfter.loanValue).toStrictEqual(54.00019868) // 27.00009934 * 2 (::1 TSLA = 2 USD) expect(vaultAfter.interestValue).toStrictEqual(0.00006164) - expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00019862 * 100 - expect(vaultAfter.informativeRatio).toStrictEqual(27777.67560737) + expect(vaultAfter.collateralRatio).toStrictEqual(27778) // 15000 / 54.00019868 * 100 + expect(vaultAfter.informativeRatio).toStrictEqual(27777.6755765) }) it('should paybackLoan more than one amount', async () => { @@ -496,33 +496,33 @@ describe('paybackLoan success', () => { const amznAmt = Number(amznTokenAmt.split('@')[0]) // tsla interest - const tslaInterestPerBlock = (netInterest * tslaAmt) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const tslaTotalInterest = ((blockHeight - tslaLoanHeight + 1) * tslaInterestPerBlock) + const tslaInterestPerBlock = new BigNumber(netInterest * tslaAmt / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const tslaTotalInterest = tslaInterestPerBlock.multipliedBy(blockHeight - tslaLoanHeight + 1) // amzn interest - const amznInterestPerBlock = (netInterest * amznAmt) / (365 * blocksPerDay) // netInterest * loanAmt / 365 * blocksPerDay - const amznTotalInterest = ((blockHeight - amznLoanHeight) * amznInterestPerBlock) + const amznInterestPerBlock = new BigNumber(netInterest * amznAmt / (365 * blocksPerDay)).decimalPlaces(8, BigNumber.ROUND_CEIL) // netInterest * loanAmt / 365 * blocksPerDay + const amznTotalInterest = amznInterestPerBlock.multipliedBy(blockHeight - amznLoanHeight) const interests = await bob.rpc.loan.getInterest('scheme') const tslaInterest = interests.find(i => i.token === 'TSLA') const tslaTotalInt = tslaInterest?.totalInterest.toFixed(8) - expect(tslaTotalInt).toStrictEqual(tslaTotalInterest.toFixed(8)) // 0.00004566 + expect(tslaTotalInt).toStrictEqual(tslaTotalInterest.toFixed(8)) const tslaInterestPerBlk = tslaInterest?.interestPerBlock.toFixed(8) - expect(tslaInterestPerBlk).toStrictEqual(tslaInterestPerBlock.toFixed(8)) // 0.00002283 + expect(tslaInterestPerBlk).toStrictEqual(tslaInterestPerBlock.toFixed(8)) const amzInterest = interests.find(i => i.token === 'AMZN') const amzTotalInt = amzInterest?.totalInterest.toFixed(8) - expect(amzTotalInt).toStrictEqual(amznTotalInterest.toFixed(8)) // 0.0000856 + expect(amzTotalInt).toStrictEqual(amznTotalInterest.toFixed(8)) const amzInterestPerBlk = amzInterest?.interestPerBlock.toFixed(8) - expect(amzInterestPerBlk).toStrictEqual(amznInterestPerBlock.toFixed(8)) // 0.00000856 + expect(amzInterestPerBlk).toStrictEqual(amznInterestPerBlock.toFixed(8)) const vaultBefore = await bob.container.call('getvault', [bobVaultId]) - expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004566@TSLA', '15.00000856@AMZN']) // eg: tslaTakeLoanAmt + tslaTotalInterest - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004566@TSLA', '0.00000856@AMZN']) - expect(vaultBefore.loanValue).toStrictEqual(140.00012556) // (40.00004566 * 2) + (15.00009856 * 4) - expect(vaultBefore.collateralRatio).toStrictEqual(10714) // 15000 / 140.00012556 * 100 - expect(vaultBefore.informativeRatio).toStrictEqual(10714.27610511) + expect(vaultBefore.loanAmounts).toStrictEqual(['40.00004568@TSLA', '15.00000857@AMZN']) // eg: tslaTakeLoanAmt + tslaTotalInterest + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00004568@TSLA', '0.00000857@AMZN']) + expect(vaultBefore.loanValue).toStrictEqual(140.00012564) // (40.00004568 * 2) + (15.00000857 * 4) + expect(vaultBefore.collateralRatio).toStrictEqual(10714) // 15000 / 140.00012564 * 100 + expect(vaultBefore.informativeRatio).toStrictEqual(10714.27609898) await fundEllipticPair(bob.container, bProviders.ellipticPair, 10) const bobColScript = P2WPKH.fromAddress(RegTest, bobColAddr, P2WPKH).getScript() @@ -549,18 +549,18 @@ describe('paybackLoan success', () => { await bob.generate(1) const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['27.00012214@TSLA', '9.00003596@AMZN']) + expect(vaultAfter.loanAmounts).toStrictEqual(['27.00012218@TSLA', '9.00003599@AMZN']) expect(vaultAfter.interestAmounts).toStrictEqual(['0.00003082@TSLA', '0.00001028@AMZN']) - expect(vaultAfter.loanValue).toStrictEqual(90.00038812) + expect(vaultAfter.loanValue).toStrictEqual(90.00038832) expect(vaultAfter.interestValue).toStrictEqual(0.00010276) expect(vaultAfter.collateralRatio).toStrictEqual(16667) - expect(vaultAfter.informativeRatio).toStrictEqual(16666.5947929) + expect(vaultAfter.informativeRatio).toStrictEqual(16666.59475586) const loanTokenAccAfter = await bob.container.call('getaccount', [bobColAddr]) expect(loanTokenAccAfter).toStrictEqual(['27.00000000@TSLA', '9.00000000@AMZN']) const burnInfoAfter = await bob.container.call('getburninfo') - expect(burnInfoAfter.paybackburn).toStrictEqual(0.00002084) + expect(burnInfoAfter.paybackburn).toStrictEqual(0.00002086) } // second paybackLoan @@ -590,29 +590,29 @@ describe('paybackLoan success', () => { const tslaInterest = interests.find(i => i.token === 'TSLA') const tslaTotalInterest = tslaInterest?.totalInterest.toFixed(8) - expect(tslaTotalInterest).toStrictEqual('0.00000799') + expect(tslaTotalInterest).toStrictEqual('0.00000798') const tslaInterestPerBlk = tslaInterest?.interestPerBlock.toFixed(8) - expect(tslaInterestPerBlk).toStrictEqual('0.00000799') + expect(tslaInterestPerBlk).toStrictEqual('0.00000798') const amzInterest = interests.find(i => i.token === 'AMZN') const amzTotalInterest = amzInterest?.totalInterest.toFixed(8) - expect(amzTotalInterest).toStrictEqual('0.00000172') + expect(amzTotalInterest).toStrictEqual('0.00000171') const amzInterestPerBlk = amzInterest?.interestPerBlock.toFixed(8) - expect(amzInterestPerBlk).toStrictEqual('0.00000172') + expect(amzInterestPerBlk).toStrictEqual('0.00000171') const vaultAfter = await bob.container.call('getvault', [bobVaultId]) - expect(vaultAfter.loanAmounts).toStrictEqual(['14.00013013@TSLA', '3.00003768@AMZN']) - expect(vaultAfter.interestAmounts).toStrictEqual(['0.00000799@TSLA', '0.00000172@AMZN']) - expect(vaultAfter.loanValue).toStrictEqual(40.00041098) - expect(vaultAfter.interestValue).toStrictEqual(0.00002286) + expect(vaultAfter.loanAmounts).toStrictEqual(['14.00013016@TSLA', '3.00003770@AMZN']) + expect(vaultAfter.interestAmounts).toStrictEqual(['0.00000798@TSLA', '0.00000171@AMZN']) + expect(vaultAfter.loanValue).toStrictEqual(40.00041112) + expect(vaultAfter.interestValue).toStrictEqual(0.0000228) expect(vaultAfter.collateralRatio).toStrictEqual(37500) - expect(vaultAfter.informativeRatio).toStrictEqual(37499.6147102) + expect(vaultAfter.informativeRatio).toStrictEqual(37499.61457896) const loanTokenAccAfter = await bob.container.call('getaccount', [bobColAddr]) expect(loanTokenAccAfter).toStrictEqual(['14.00000000@TSLA', '3.00000000@AMZN']) // (27 - 13), (9 - 6) const burnInfoAfter = await bob.container.call('getburninfo') - expect(burnInfoAfter.paybackburn).toStrictEqual(0.00002804) + expect(burnInfoAfter.paybackburn).toStrictEqual(0.00002806) } }) }) @@ -709,7 +709,7 @@ describe('paybackLoan failed', () => { }) it('should not paybackLoan on liquidation vault', async () => { - await alice.generate(6) + await bob.container.waitForVaultState(bobLiqVaultId, 'inLiquidation') const liqVault = await bob.container.call('getvault', [bobLiqVaultId]) expect(liqVault.state).toStrictEqual('inLiquidation') @@ -763,7 +763,7 @@ describe('paybackLoan failed #2', () => { it('should not paybackLoan while insufficient amount', async () => { const vault = await bob.rpc.loan.getVault(bobVaultId) as VaultActive - expect(vault.loanAmounts).toStrictEqual(['40.00002283@TSLA']) + expect(vault.loanAmounts).toStrictEqual(['40.00002284@TSLA']) const bobLoanAcc = await bob.rpc.account.getAccount(bobColAddr) expect(bobLoanAcc).toStrictEqual(['40.00000000@TSLA']) @@ -781,6 +781,6 @@ describe('paybackLoan failed #2', () => { const promise = sendTransaction(bob.container, txn) await expect(promise).rejects.toThrow(DeFiDRpcError) - await expect(promise).rejects.toThrow('amount 0.00000000 is less than 0.00006849') + await expect(promise).rejects.toThrow('amount 0.00000000 is less than 0.00006852') }) }) diff --git a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_place_auction_bid.test.ts b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_place_auction_bid.test.ts index 1bbc1be508..2438cf1f10 100644 --- a/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_place_auction_bid.test.ts +++ b/packages/jellyfish-transaction-builder/__tests__/txn/txn_builder_loan_place_auction_bid.test.ts @@ -216,47 +216,39 @@ async function setup (): Promise { // increase TSLA price await alice.rpc.oracle.setOracleData(oracleId, now(), { prices: [{ tokenAmount: '15@TSLA', currency: 'USD' }] }) - await alice.generate(1) // interest * 5 => 1000.00285385@TSLA + await alice.generate(1) await tGroup.waitForSync() // check vault status before liquidated const vaultBefore = await bob.container.call('getvault', [bobVaultId]) expect(vaultBefore.state).toStrictEqual('active') expect(vaultBefore.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) - expect(vaultBefore.loanAmounts).toStrictEqual(['1000.00285385@TSLA']) - expect(vaultBefore.interestAmounts).toStrictEqual(['0.00285385@TSLA']) + expect(vaultBefore.loanAmounts).toStrictEqual(['1000.00285390@TSLA']) + expect(vaultBefore.interestAmounts).toStrictEqual(['0.00285390@TSLA']) expect(vaultBefore.collateralValue).toStrictEqual(20000) - expect(vaultBefore.loanValue).toStrictEqual(2000.0057077) - expect(vaultBefore.interestValue).toStrictEqual(0.0057077) + expect(vaultBefore.loanValue).toStrictEqual(2000.0057078) + expect(vaultBefore.interestValue).toStrictEqual(0.0057078) expect(vaultBefore.collateralRatio).toStrictEqual(1000) - expect(vaultBefore.informativeRatio).toStrictEqual(999.99714615) + expect(vaultBefore.informativeRatio).toStrictEqual(999.9971461) - // *6 => 1000.00342462@TSLA - // *7 => 1000.00399539@TSLA - // *8 => 1000.00456616@TSLA - // *9 => 1000.00513693@TSLA { - await bob.generate(5) // *10 => 1000.0057077@TSLA + await bob.generate(5) const vault = await bob.container.call('getvault', [bobVaultId]) // commented this flaky state check as block height does not really complimentary with state but time // expect(vault.state).toStrictEqual('frozen') expect(vault.collateralAmounts).toStrictEqual(['10000.00000000@DFI', '1.00000000@BTC']) - expect(vault.loanAmounts).toStrictEqual(['1000.00570770@TSLA']) - expect(vault.interestAmounts).toStrictEqual(['0.00570770@TSLA']) + expect(vault.loanAmounts).toStrictEqual(['1000.00570780@TSLA']) + expect(vault.interestAmounts).toStrictEqual(['0.00570780@TSLA']) expect(vault.collateralValue).toStrictEqual(20000) - expect(vault.loanValue).toStrictEqual(2000.0114154) - expect(vault.interestValue).toStrictEqual(0.0114154) + expect(vault.loanValue).toStrictEqual(2000.0114156) + expect(vault.interestValue).toStrictEqual(0.0114156) expect(vault.collateralRatio).toStrictEqual(1000) - expect(vault.informativeRatio).toStrictEqual(999.99429233) + expect(vault.informativeRatio).toStrictEqual(999.99429223) } const auctionsBefore = await bob.container.call('listauctions') expect(auctionsBefore.length).toStrictEqual(0) - // *11 => 1000.00627847@TSLA - // *12 => 1000.00684924@TSLA - // *13 => 1000.00742001@TSLA - // *14 => 1000.00799078@TSLA await bob.container.waitForVaultState(bobVaultId, 'inLiquidation') // vault is liquidated now @@ -269,8 +261,8 @@ async function setup (): Promise { expect(vaultAfter.liquidationHeight).toStrictEqual(expect.any(Number)) expect(vaultAfter.liquidationPenalty).toStrictEqual(5) expect(vaultAfter.batches).toStrictEqual([ - { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399539@TSLA' }, - { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399539@TSLA' } + { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399546@TSLA' }, + { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], loan: '500.00399546@TSLA' } ]) const auctionsAfter = await bob.container.call('listauctions') @@ -280,7 +272,7 @@ async function setup (): Promise { expect(auctionsAfter[0].liquidationHeight).toStrictEqual(expect.any(Number)) expect(auctionsAfter[0].liquidationPenalty).toStrictEqual(5) expect(auctionsAfter[0].batches[0].collaterals).toStrictEqual(['5000.00000000@DFI', '0.50000000@BTC']) - expect(auctionsAfter[0].batches[0].loan).toStrictEqual('500.00399539@TSLA') + expect(auctionsAfter[0].batches[0].loan).toStrictEqual('500.00399546@TSLA') bobColAccBefore = await bob.rpc.account.getAccount(bobColAddr) expect(bobColAccBefore).toStrictEqual(['8900.00000000@DFI', '545.45454546@TSLA']) @@ -402,8 +394,8 @@ describe('placeAuctionBid success', () => { batches: [ { index: 0, - collaterals: ['5004.44449290@DFI', '0.49955555@BTC'], - loan: '499.55982197@TSLA' + collaterals: ['5004.44449283@DFI', '0.49955555@BTC'], + loan: '499.55982205@TSLA' }, { index: 1, @@ -465,7 +457,7 @@ describe('placeAuctionBid success', () => { { index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '526.00000000@TSLA', owner: bobColAddr @@ -474,7 +466,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA' + loan: '500.00399546@TSLA' } ] }) @@ -520,7 +512,7 @@ describe('placeAuctionBid success', () => { batches: [{ index: 0, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '526.00000000@TSLA', owner: bobColAddr @@ -529,7 +521,7 @@ describe('placeAuctionBid success', () => { { index: 1, collaterals: ['5000.00000000@DFI', '0.50000000@BTC'], - loan: '500.00399539@TSLA', + loan: '500.00399546@TSLA', highestBid: { amount: '600.00000000@TSLA', owner: aliceColAddr @@ -552,10 +544,10 @@ describe('placeAuctionBid success', () => { const vault = await alice.container.call('getvault', [bobVaultId]) expect(vault.state).toStrictEqual('active') - expect(vault.collateralAmounts).toStrictEqual(['55.25753134@DFI']) + expect(vault.collateralAmounts).toStrictEqual(['55.25753121@DFI']) expect(vault.loanAmounts).toStrictEqual([]) expect(vault.interestAmounts).toStrictEqual([]) - expect(vault.collateralValue).toStrictEqual(55.25753134) + expect(vault.collateralValue).toStrictEqual(55.25753121) expect(vault.loanValue).toStrictEqual(0) expect(vault.interestValue).toStrictEqual(0) expect(vault.collateralRatio).toStrictEqual(-1) diff --git a/packages/playground/README.md b/packages/playground/README.md new file mode 100644 index 0000000000..f69658d544 --- /dev/null +++ b/packages/playground/README.md @@ -0,0 +1,56 @@ +# `@defichain/playground` + +> This package is not published, for internal use within `@defichain-apps/ocean-api` only. + +`@defichain/playground` is a specialized testing blockchain isolated from MainNet for testing DeFi applications. Assets +are not real, they can be minted by anyone. Blocks are configured to generate every 3 seconds, the chain can reset +anytime. + +A bot-like design centers the playground as a mechanism that allows bootstrapping with an interval cycle. This allows +the developer to mock any behaviors they want with a simulated testing blockchain. + +## Playground Design + +Playground follows the chain of responsibility pattern (think Filter in ExpressJS). Each bot has its own concern that it +manages; it can be generating block, publishing oracle data, or setting up foundation auth. Each bot enforced with +`AbstractBot` abstraction comes with `bot.bootstrap()` and `bot.cycle()` method. + +```typescript +class AbstractBot { + constructor ( + protected readonly apiClient: ApiClient, + protected readonly logger: BotLogger + ) { + } + + bootstrap (): Promise { + } + + cycle (nextBlockCount: number): Promise { + } +} +``` + +## Playground Setup + +### Initialize + +```typescript +const logger = { + info: (action: string, message: string) => console.log(`...`) +} +const rpc = new JsonRpcClient() +const playground = new Playground(rpc, logger) +``` + +### Boostrap + +```typescript +await playground.bootstrap() +``` + +### Run each cycle and your defined interval + +```typescript +await playground.cycle() +``` diff --git a/packages/playground/__tests__/bots/BlockGenerateBot.test.ts b/packages/playground/__tests__/bots/BlockGenerateBot.test.ts new file mode 100644 index 0000000000..0e4766010b --- /dev/null +++ b/packages/playground/__tests__/bots/BlockGenerateBot.test.ts @@ -0,0 +1,22 @@ +import { PlaygroundTesting } from '../../testing/PlaygroundTesting' + +const playgroundTesting = PlaygroundTesting.create() + +beforeAll(async () => { + await playgroundTesting.start() + await playgroundTesting.bootstrap() +}) + +afterAll(async () => { + await playgroundTesting.stop() +}) + +it('should block generate when cycle', async () => { + const initial = await playgroundTesting.rpc.blockchain.getBlockCount() + expect(initial).toStrictEqual(0) + + await playgroundTesting.cycle() + + const next = await playgroundTesting.rpc.blockchain.getBlockCount() + expect(next).toStrictEqual(1) +}) diff --git a/packages/playground/__tests__/bots/FoundationBot.test.ts b/packages/playground/__tests__/bots/FoundationBot.test.ts new file mode 100644 index 0000000000..52b90e8afb --- /dev/null +++ b/packages/playground/__tests__/bots/FoundationBot.test.ts @@ -0,0 +1,33 @@ +import { PlaygroundTesting } from '../../testing/PlaygroundTesting' +import { FoundationBot } from '../../src/bots/FoundationBot' + +const playgroundTesting = PlaygroundTesting.create() + +beforeAll(async () => { + await playgroundTesting.start() + await playgroundTesting.bootstrap() +}) + +afterAll(async () => { + await playgroundTesting.stop() +}) + +it('should have all foundation keys after bootstrap', async () => { + for (const key of FoundationBot.Keys) { + { + const info = await playgroundTesting.rpc.wallet.getAddressInfo(key.owner.address) + expect(info).toBeDefined() + + const privKey = await playgroundTesting.rpc.wallet.dumpPrivKey(key.owner.address) + expect(privKey).toStrictEqual(key.owner.privKey) + } + + { + const info = await playgroundTesting.rpc.wallet.getAddressInfo(key.operator.address) + expect(info).toBeDefined() + + const privKey = await playgroundTesting.rpc.wallet.dumpPrivKey(key.operator.address) + expect(privKey).toStrictEqual(key.operator.privKey) + } + } +}) diff --git a/packages/playground/package.json b/packages/playground/package.json new file mode 100644 index 0000000000..7ba6b50efe --- /dev/null +++ b/packages/playground/package.json @@ -0,0 +1,24 @@ +{ + "private": true, + "name": "@defichain/playground", + "version": "0.0.0", + "description": "DeFiChain Jellyfish Ecosystem", + "repository": "DeFiCh/jellyfish", + "bugs": "https://github.com/DeFiCh/jellyfish/issues", + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b tsconfig.build.json" + }, + "dependencies": { + "@defichain/jellyfish-api-core": "0.0.0", + "@defichain/jellyfish-network": "0.0.0" + }, + "peerDependencies": { + "defichain": "0.0.0" + } +} diff --git a/packages/playground/src/AbstractBot.ts b/packages/playground/src/AbstractBot.ts new file mode 100644 index 0000000000..dbb599ecec --- /dev/null +++ b/packages/playground/src/AbstractBot.ts @@ -0,0 +1,27 @@ +import { ApiClient } from '@defichain/jellyfish-api-core' +import { BotLogger } from './BotLogger' + +/** + * Abstract Playground with bootstrap and cycle ability. + */ +export abstract class AbstractBot { + constructor ( + protected readonly apiClient: ApiClient, + protected readonly logger: BotLogger + ) { + } + + /** + * Bootstrap the bot at the start + */ + async bootstrap (): Promise { + } + + /** + * Configured and ran on upstream. + * + * @param {number} nextBlockCount + */ + async cycle (nextBlockCount: number): Promise { + } +} diff --git a/packages/playground/src/BotLogger.ts b/packages/playground/src/BotLogger.ts new file mode 100644 index 0000000000..f91f5e494d --- /dev/null +++ b/packages/playground/src/BotLogger.ts @@ -0,0 +1,3 @@ +export interface BotLogger { + info: (action: string, message: string) => void +} diff --git a/packages/playground/src/Playground.ts b/packages/playground/src/Playground.ts new file mode 100644 index 0000000000..0e549c763e --- /dev/null +++ b/packages/playground/src/Playground.ts @@ -0,0 +1,42 @@ +import { ApiClient } from '@defichain/jellyfish-api-core' +import { BotLogger } from './BotLogger' +import { AbstractBot } from './AbstractBot' +import { BlockGenerateBot } from './bots/BlockGenerateBot' +import { FoundationBot } from './bots/FoundationBot' + +/** + * Playground Root Bot with all subsequent bot configured to run at boostrap and at each cycle. + */ +export class Playground { + private readonly bots: AbstractBot[] = [] + private readonly generate: BlockGenerateBot + + constructor (private readonly apiClient: ApiClient, logger: BotLogger) { + this.bots = [ + new FoundationBot(apiClient, logger) + ] + this.generate = new BlockGenerateBot(apiClient, logger) + } + + /** + * Bootstrapping of all bots + */ + async bootstrap (): Promise { + for (const bot of this.bots) { + await bot.bootstrap() + } + } + + /** + * Cycle through all bots, generate will always be cycled last. + */ + async cycle (): Promise { + const count = await this.apiClient.blockchain.getBlockCount() + const next = count + 1 + + for (const bot of this.bots) { + await bot.cycle(next) + } + await this.generate.cycle(next) + } +} diff --git a/packages/playground/src/bots/BlockGenerateBot.ts b/packages/playground/src/bots/BlockGenerateBot.ts new file mode 100644 index 0000000000..f0ed38443e --- /dev/null +++ b/packages/playground/src/bots/BlockGenerateBot.ts @@ -0,0 +1,20 @@ +import { AbstractBot } from '../AbstractBot' +import { FoundationBot } from './FoundationBot' + +/** + * Generate a block every cycle + */ +export class BlockGenerateBot extends AbstractBot { + private static randomNodeAddress (): string { + const items = FoundationBot.Keys + return items[Math.floor(Math.random() * items.length)].operator.address + } + + /** + * Generate a block every cycle into a random node address from the foundation keys list + */ + async cycle (nextBlockCount: number): Promise { + await this.apiClient.call('generatetoaddress', [1, BlockGenerateBot.randomNodeAddress(), 1], 'number') + this.logger.info('BlockGenerate', `height: ${nextBlockCount}`) + } +} diff --git a/packages/playground/src/bots/FoundationBot.ts b/packages/playground/src/bots/FoundationBot.ts new file mode 100644 index 0000000000..9fe5d31599 --- /dev/null +++ b/packages/playground/src/bots/FoundationBot.ts @@ -0,0 +1,16 @@ +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' +import { AbstractBot } from '../AbstractBot' + +/** + * Bootstrap Foundation Keys + */ +export class FoundationBot extends AbstractBot { + static Keys = RegTestFoundationKeys + + async bootstrap (): Promise { + for (const key of FoundationBot.Keys) { + await this.apiClient.wallet.importPrivKey(key.owner.privKey, undefined, true) + await this.apiClient.wallet.importPrivKey(key.operator.privKey, undefined, true) + } + } +} diff --git a/packages/playground/src/index.ts b/packages/playground/src/index.ts new file mode 100644 index 0000000000..2776d6612a --- /dev/null +++ b/packages/playground/src/index.ts @@ -0,0 +1,2 @@ +export * from './BotLogger' +export * from './Playground' diff --git a/packages/playground/testing/PlaygroundTesting.ts b/packages/playground/testing/PlaygroundTesting.ts new file mode 100644 index 0000000000..6694595383 --- /dev/null +++ b/packages/playground/testing/PlaygroundTesting.ts @@ -0,0 +1,73 @@ +import { Testing, TestingGroup } from '@defichain/jellyfish-testing' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { BotLogger, Playground } from '@defichain/playground' +import { ApiClient } from '@defichain/jellyfish-api-core' + +/** + * Universal Playground Testing framework for internal package use. + * + * As bot have cross-cutting concerns. PlaygroundTesting is an e2e setup, + * it configures the entire Bot and run them all. + * + * @see Playground + */ +export class PlaygroundTesting { + public counter: number = 0 + + constructor ( + private readonly testingGroup: TestingGroup, + private readonly logger: PlaygroundTestingLogger = new PlaygroundTestingLogger(), + private readonly playground: Playground = new Playground(testingGroup.get(0).rpc, logger) + ) { + } + + static create (testingGroup: TestingGroup = TestingGroup.create(1)): PlaygroundTesting { + return new PlaygroundTesting(testingGroup) + } + + get group (): TestingGroup { + return this.testingGroup + } + + get testing (): Testing { + return this.testingGroup.get(0) + } + + get container (): MasterNodeRegTestContainer { + return this.testing.container + } + + get rpc (): ApiClient { + return this.testing.rpc + } + + /** + * @see TestingGroup + * @see Testing + */ + async start (): Promise { + await this.group.start() + } + + /** + * @see TestingGroup + * @see Testing + */ + async stop (): Promise { + await this.group.stop() + } + + async bootstrap (): Promise { + await this.playground.bootstrap() + } + + async cycle (): Promise { + await this.playground.cycle() + } +} + +class PlaygroundTestingLogger implements BotLogger { + info (action: string, message: string): void { + // not logged during testing + } +} diff --git a/packages/playground/tsconfig.build.json b/packages/playground/tsconfig.build.json new file mode 100644 index 0000000000..3808cd271f --- /dev/null +++ b/packages/playground/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.build.json", + "include": [ + "./src/**/*" + ], + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + } +} diff --git a/packages/testcontainers/__tests__/containers/ContainerRestart.test.ts b/packages/testcontainers/__tests__/containers/ContainerRestart.test.ts index 186fd34fab..d47bb7cde1 100644 --- a/packages/testcontainers/__tests__/containers/ContainerRestart.test.ts +++ b/packages/testcontainers/__tests__/containers/ContainerRestart.test.ts @@ -1,4 +1,6 @@ import { GenesisKeys, MasterNodeRegTestContainer } from '../../src' +import { RegTestFoundationKeys } from '@defichain/jellyfish-network' +import { TestingGroup } from '@defichain/jellyfish-testing' describe('container restart with setDeFiConf', () => { const container = new MasterNodeRegTestContainer() @@ -39,3 +41,56 @@ describe('container restart with setDeFiConf', () => { }) }) }) + +describe('container group restart', () => { + const tGroup = TestingGroup.create(2, i => new MasterNodeRegTestContainer(RegTestFoundationKeys[i])) + const alice = tGroup.get(0) + const bob = tGroup.get(1) + + beforeAll(async () => { + await tGroup.start() + await alice.container.waitForWalletCoinbaseMaturity() + }) + + afterAll(async () => { + await tGroup.stop() + }) + + it('test container group restart', async () => { + { + const mnAddr = await alice.container.getNewAddress('', 'legacy') + const mnId1 = await alice.container.call('createmasternode', [mnAddr]) + await alice.container.generate(1) + + await alice.container.setDeFiConf([`masternode_operator=${mnAddr}`]) + await alice.container.restart() + await tGroup.link() + await tGroup.waitForSync() + + await alice.generate(20) + await alice.container.generate(5, mnAddr) + + await alice.container.call('getmasternodeblocks', [{ id: mnId1 }]) + await alice.container.call('getmasternodeblocks', [{ id: mnId1 }, 1]) + await tGroup.waitForSync() + } + + { + const mnAddr = await bob.container.getNewAddress('', 'legacy') + const mnId1 = await bob.container.call('createmasternode', [mnAddr]) + await bob.container.generate(1) + await tGroup.waitForSync() + + await bob.container.setDeFiConf([`masternode_operator=${mnAddr}`]) + await bob.container.restart() + await tGroup.link() + + await bob.generate(20) + await tGroup.waitForSync() + await bob.container.generate(5, mnAddr) + + await bob.container.call('getmasternodeblocks', [{ id: mnId1 }]) + await bob.container.call('getmasternodeblocks', [{ id: mnId1 }, 1]) + } + }) +}) diff --git a/packages/testcontainers/__tests__/containers/RegTestContainer/Coinbase.test.ts b/packages/testcontainers/__tests__/containers/RegTestContainer/Coinbase.test.ts index d83bffbbdb..ffb4d80c5e 100644 --- a/packages/testcontainers/__tests__/containers/RegTestContainer/Coinbase.test.ts +++ b/packages/testcontainers/__tests__/containers/RegTestContainer/Coinbase.test.ts @@ -66,7 +66,11 @@ describe('coinbase maturity', () => { }) it('should be able to get new address and priv/pub key for testing', async () => { - const { address, privKey, pubKey } = await container.newAddressKeys() + const { + address, + privKey, + pubKey + } = await container.newAddressKeys() await container.waitForWalletBalanceGTE(10) const { txid } = await container.fundAddress(address, 1) @@ -84,3 +88,19 @@ describe('coinbase maturity', () => { }) }) }) + +describe('coinbase maturity faster by time travel', () => { + const container = new MasterNodeRegTestContainer() + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should speed up coinbase maturity', async () => { + await container.waitForWalletCoinbaseMaturity() + }) +}) diff --git a/packages/testcontainers/src/containers/DeFiDContainer.ts b/packages/testcontainers/src/containers/DeFiDContainer.ts index e1314403c6..93d1cb574e 100644 --- a/packages/testcontainers/src/containers/DeFiDContainer.ts +++ b/packages/testcontainers/src/containers/DeFiDContainer.ts @@ -29,7 +29,7 @@ export abstract class DeFiDContainer extends DockerContainer { if (process?.env?.DEFICHAIN_DOCKER_IMAGE !== undefined) { return process.env.DEFICHAIN_DOCKER_IMAGE } - return 'defi/defichain:2.2.1' + return 'defi/defichain:2.3.2' } public static readonly DefaultStartOptions = { diff --git a/packages/testcontainers/src/containers/RegTestContainer/ContainerGroup.ts b/packages/testcontainers/src/containers/RegTestContainer/ContainerGroup.ts index 0db6e53c1d..78d74c205e 100644 --- a/packages/testcontainers/src/containers/RegTestContainer/ContainerGroup.ts +++ b/packages/testcontainers/src/containers/RegTestContainer/ContainerGroup.ts @@ -46,6 +46,14 @@ export class ContainerGroup { } } + async link (): Promise { + for (const container of this.containers) { + for (const each of this.containers) { + await each.addNode(await container.getIp(this.name)) + } + } + } + /** * Require network, else error exceptionally. * Not a clean design, but it keep the complexity of this implementation low. diff --git a/packages/testcontainers/src/containers/RegTestContainer/Masternode.ts b/packages/testcontainers/src/containers/RegTestContainer/Masternode.ts index 2cd457c9cc..78d71b6d03 100644 --- a/packages/testcontainers/src/containers/RegTestContainer/Masternode.ts +++ b/packages/testcontainers/src/containers/RegTestContainer/Masternode.ts @@ -80,7 +80,7 @@ export class MasterNodeRegTestContainer extends RegTestContainer { * Wait for block height by minting towards the target * * @param {number} height to wait for - * @param {number} [timeout=90000] in ms + * @param {number} [timeout=590000] in ms */ async waitForBlockHeight (height: number, timeout = 590000): Promise { return await waitForCondition(async () => { @@ -101,9 +101,25 @@ export class MasterNodeRegTestContainer extends RegTestContainer { * un-spendable (in the event the mined block moves out of the active chain due to a fork). * * @param {number} [timeout=180000] in ms + * @param {boolean} [mockTime=true] to generate blocks faster */ - async waitForWalletCoinbaseMaturity (timeout = 180000): Promise { - return await this.waitForBlockHeight(100, timeout) + async waitForWalletCoinbaseMaturity (timeout: number = 180000, mockTime: boolean = true): Promise { + if (!mockTime) { + return await this.waitForBlockHeight(100, timeout) + } + + let fakeTime: number = 1579045065 + await this.call('setmocktime', [fakeTime]) + + const intervalId = setInterval(() => { + fakeTime += 3 + void this.call('setmocktime', [fakeTime]) + }, 200) + + await this.waitForBlockHeight(100, timeout) + + clearInterval(intervalId) + await this.call('setmocktime', [0]) } /** diff --git a/packages/testcontainers/src/containers/RegTestContainer/index.ts b/packages/testcontainers/src/containers/RegTestContainer/index.ts index 1da77dda6f..bfd2c0534b 100644 --- a/packages/testcontainers/src/containers/RegTestContainer/index.ts +++ b/packages/testcontainers/src/containers/RegTestContainer/index.ts @@ -32,7 +32,8 @@ export class RegTestContainer extends DeFiDContainer { '-dakotacrescentheight=5', '-eunosheight=6', '-eunospayaheight=7', - '-fortcanningheight=8' + '-fortcanningheight=8', + '-fortcanningmuseumheight=9' ] }