From 03422548a41d54de21b6bf8ab560205aaaecac42 Mon Sep 17 00:00:00 2001 From: Prathamesh Musale Date: Wed, 25 Sep 2024 13:57:55 +0000 Subject: [PATCH] Add command to create an auction and add auction CLI tests (#83) Part of [Service provider auctions](https://www.notion.so/Service-provider-auctions-a7b63697d818479493ec145ea6ea3c1c) Examples: ```bash # vickrey auction laconic registry auction create --kind vickrey --commits-duration 60 --reveals-duration 60 --commit-fee 1000 --reveal-fee 1000 --minimum-bid 1000000 ``` ```bash # provider auction laconic registry auction create --kind provider --commits-duration 60 --reveals-duration 60 --commit-fee 1000 --reveal-fee 1000 --max-price 3000000 --num-providers 5 # Release provider auction funds laconic registry auction release-funds --auction-id ``` Co-authored-by: IshaVenikar Reviewed-on: https://git.vdb.to/cerc-io/laconic-registry-cli/pulls/83 Co-authored-by: Prathamesh Musale Co-committed-by: Prathamesh Musale --- .gitignore | 3 +- README.md | 136 ++++++- package.json | 4 +- src/cmds/registry-cmds/auction-cmds/create.ts | 106 ++++++ .../auction-cmds/release-funds.ts | 34 ++ src/cmds/registry-cmds/bond-cmds/create.ts | 1 + test/README.md | 2 +- test/cli.test.ts | 332 +++++++++++++++++- test/helpers.ts | 13 +- yarn.lock | 8 +- 10 files changed, 612 insertions(+), 27 deletions(-) create mode 100644 src/cmds/registry-cmds/auction-cmds/create.ts create mode 100644 src/cmds/registry-cmds/auction-cmds/release-funds.ts diff --git a/.gitignore b/.gitignore index c36d7c2..d1c0f4f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ dist/* out config.yml +.env *~ -.idea \ No newline at end of file +.idea diff --git a/README.md b/README.md index 09016fc..50b0f76 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,7 @@ These commands require a `config.yml` file present in the current working direct Get node status: ```bash -$ laconic registry status +laconic registry status { "version": "0.3.0", "node": { @@ -186,7 +186,7 @@ $ laconic registry status Get account details: ```bash -$ laconic registry account get --address laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k +laconic registry account get --address laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k [ { "address": "laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k", @@ -206,7 +206,7 @@ $ laconic registry account get --address laconic15za32wly5exgcrt2zfr8php4ya49n5y Send tokens: ```bash -$ laconic registry tokens send --address laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k --type alnt --quantity 1000000000 +laconic registry tokens send --address laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k --type alnt --quantity 1000000000 { "tx": { "hash": "977152CBE474613E1BBAFEF286F12134829FAF3C9E7C8349149DE3E687B816FC", @@ -250,7 +250,7 @@ $ laconic registry tokens send --address laconic15za32wly5exgcrt2zfr8php4ya49n5y Get token TX details: ```bash -$ laconic registry tokens gettx --hash 977152CBE474613E1BBAFEF286F12134829FAF3C9E7C8349149DE3E687B816FC +laconic registry tokens gettx --hash 977152CBE474613E1BBAFEF286F12134829FAF3C9E7C8349149DE3E687B816FC { "hash": "977152CBE474613E1BBAFEF286F12134829FAF3C9E7C8349149DE3E687B816FC", "height": 343369, @@ -280,7 +280,7 @@ record: Publish record (see below for commands to create/query bonds): ```bash -$ laconic registry record publish --filename watcher.yml --bond-id 58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785 --gas 250000 --fees 250000alnt +laconic registry record publish --filename watcher.yml --bond-id 58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785 --gas 250000 --fees 250000alnt { id: 'bafyreic3auqajvgszh3vfjsouew2rsctswukc346dmlf273ln4g6iyyhba' } ``` @@ -288,7 +288,7 @@ $ laconic registry record publish --filename watcher.yml --bond-id 58508984500aa Get record: ```bash -$ laconic registry record get --id bafyreic3auqajvgszh3vfjsouew2rsctswukc346dmlf273ln4g6iyyhba +laconic registry record get --id bafyreic3auqajvgszh3vfjsouew2rsctswukc346dmlf273ln4g6iyyhba [ { "id": "bafyreic3auqajvgszh3vfjsouew2rsctswukc346dmlf273ln4g6iyyhba", @@ -341,7 +341,7 @@ laconic registry authority reserve laconic Check authority information: ```bash -$ laconic registry authority whois laconic +laconic registry authority whois laconic [ { "ownerAddress": "", @@ -387,7 +387,7 @@ $ laconic registry authority whois laconic Get authority auction info: ```bash -$ laconic registry auction get 0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2468d5d63abacd48 +laconic registry auction get 0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2468d5d63abacd48 [ { "id": "0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2468d5d63abacd48", @@ -425,7 +425,7 @@ $ laconic registry auction get 0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2 Commit an auction bid: ```bash -$ laconic registry auction bid commit 0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2468d5d63abacd48 25000000 alnt +laconic registry auction bid commit 0294fb2e3659c347b53a6faf4bef041fd934f0f3ab13df6d2468d5d63abacd48 25000000 alnt Reveal file: ./out/bafyreiay2rccax64yn4ljhvzvm3jkbebvzheyucuma5jlbpzpzd5i5gjuy.json ``` @@ -475,7 +475,7 @@ laconic registry name set lrn://laconic/watcher/erc20 bafyreic3auqajvgszh3vfjsou Lookup name information: ```bash -$ laconic registry name lookup lrn://laconic/watcher/erc20 +laconic registry name lookup lrn://laconic/watcher/erc20 [ { "latest": { @@ -489,7 +489,7 @@ $ laconic registry name lookup lrn://laconic/watcher/erc20 Resolve name: ```bash -$ laconic registry name resolve lrn://laconic/watcher/erc20 +laconic registry name resolve lrn://laconic/watcher/erc20 [ { "id": "bafyreic3auqajvgszh3vfjsouew2rsctswukc346dmlf273ln4g6iyyhba", @@ -530,9 +530,9 @@ $ laconic registry name resolve lrn://laconic/watcher/erc20 Delete name: ```bash -$ laconic registry name delete lrn://laconic/watcher/erc20 +laconic registry name delete lrn://laconic/watcher/erc20 -$ laconic registry name resolve lrn://laconic/watcher/erc20 +laconic registry name resolve lrn://laconic/watcher/erc20 [ null ] @@ -547,7 +547,7 @@ laconic registry bond create --type alnt --quantity 1000 List bonds: ```bash -$ laconic registry bond list +laconic registry bond list [ { "id": "58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785", @@ -575,7 +575,7 @@ $ laconic registry bond list Get bond: ```bash -$ laconic registry bond get --id 58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785 +laconic registry bond get --id 58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785 [ { "id": "58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785", @@ -593,7 +593,7 @@ $ laconic registry bond get --id 58508984500aa2ed18e059fa8203b40fbc9828e3bfa1953 Query bonds by owner: ```bash -$ laconic registry bond list --owner laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k +laconic registry bond list --owner laconic15za32wly5exgcrt2zfr8php4ya49n5y7masu7k [ { "id": "58508984500aa2ed18e059fa8203b40fbc9828e3bfa195361335c4e4524c4785", @@ -659,3 +659,107 @@ Reassociate records (switch bond): ```bash laconic registry bond records reassociate --old-bond-id 5c40abd336ae1561f2a1b55be73b12f5a083080bf879b4c9288d182d238badb0 --new-bond-id 3e11c61f179897e4b12e9b63de35d36f88ac146755e7a28ce0bcdd07cf3a03ae ``` + +Create a `provider` auction: + +```bash +laconic registry auction create --kind provider --commits-duration 60 --reveals-duration 60 --denom alnt --commit-fee 1000 --reveal-fee 1000 --max-price 100000 --num-providers 5 + +{"auctionId":"73c5fa4b91bb973641ccbb6901a8404745fb8793c95485b00d5a791e6b6c1630"} + +# Set auction id in a variable +AUCTION= +``` + +Commit an auction bid: + +```bash +laconic registry auction bid commit $AUCTION 25000 alnt + +{"reveal_file":"/home/user/laconic-registry-cli/out/bafyreiai5upey4562ont54pe7m3buiphtd6n3q2vr5lxdcj3gpyklbbgvy.json"} +``` + +Reveal an auction bid: + +```bash +laconic registry auction bid reveal $AUCTION /home/user/laconic-registry-cli/out/bafyreiai5upey4562ont54pe7m3buiphtd6n3q2vr5lxdcj3gpyklbbgvy.json + +{"success": true} +``` + +Check the auction state on completion: + +```bash +laconic registry auction get $AUCTION + +[ + { + "id": "b66b74048fc360de6a926123b760e6485276d90ad2274b5386c02664cd04bace", + "kind": "provider", + "status": "completed", + "ownerAddress": "laconic1maqfgs93hnvzqh5mfj9kxt4e3n27vhd0w7emrx", + "createTime": "2024-09-17T09:51:48.605610628", + "commitsEndTime": "2024-09-17T09:52:48.605610628", + "revealsEndTime": "2024-09-17T09:53:48.605610628", + "commitFee": { + "type": "alnt", + "quantity": 1000 + }, + "revealFee": { + "type": "alnt", + "quantity": 1000 + }, + "minimumBid": { + "type": "", + "quantity": 0 + }, + "winnerAddresses": [ + "laconic13qrlfkgl02wgwpw0n4j8kswygwnukphy92249r" + ], + "winnerBids": [ + { + "type": "alnt", + "quantity": 25000 + } + ], + "winnerPrice": { + "type": "alnt", + "quantity": 25000 + }, + "maxPrice": { + "type": "alnt", + "quantity": 100000 + }, + "numProviders": 5, + "bids": [ + { + "bidderAddress": "laconic13qrlfkgl02wgwpw0n4j8kswygwnukphy92249r", + "status": "reveal", + "commitHash": "bafyreifjkhiakayvvaasnsw7ufaax54ncow4xuycqnox7hxay34c6yod7a", + "commitTime": "2024-09-17T09:52:03.665761945", + "revealTime": "2024-09-17T09:53:00.904061323", + "commitFee": { + "type": "alnt", + "quantity": 1000 + }, + "revealFee": { + "type": "alnt", + "quantity": 1000 + }, + "bidAmount": { + "type": "alnt", + "quantity": 25000 + } + } + ] + } +] +``` + +Release provider winning funds: + +```bash +laconic registry auction release-funds $AUCTION + +{"success": true} +``` diff --git a/package.json b/package.json index f351c2b..c8c4b64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cerc-io/laconic-registry-cli", - "version": "0.2.8", + "version": "0.2.9", "main": "index.js", "repository": "git@github.com:cerc-io/laconic-registry-cli.git", "author": "", @@ -29,7 +29,7 @@ "typescript": "^4.6.3" }, "dependencies": { - "@cerc-io/registry-sdk": "^0.2.9", + "@cerc-io/registry-sdk": "^0.2.10", "@cosmjs/stargate": "^0.32.2", "fs-extra": "^10.1.0", "js-yaml": "^3.14.1", diff --git a/src/cmds/registry-cmds/auction-cmds/create.ts b/src/cmds/registry-cmds/auction-cmds/create.ts new file mode 100644 index 0000000..089a114 --- /dev/null +++ b/src/cmds/registry-cmds/auction-cmds/create.ts @@ -0,0 +1,106 @@ +import { Arguments } from 'yargs'; +import assert from 'assert'; + +import { AUCTION_KIND_PROVIDER, AUCTION_KIND_VICKREY, Registry } from '@cerc-io/registry-sdk'; + +import { getConfig, getConnectionInfo, getGasAndFees, getGasPrice, txOutput } from '../../../util'; + +export const command = 'create'; + +export const desc = 'Create auction.'; + +export const builder = { + kind: { + type: 'string', + describe: 'Auction kind (vickrey | provider)' + }, + 'commits-duration': { + type: 'string', + describe: 'Duration for bid commit phase in seconds' + }, + 'reveals-duration': { + type: 'string', + describe: 'Duration for bid reveal phase in seconds' + }, + denom: { + type: 'string', + describe: 'Denom to use' + }, + 'commit-fee': { + type: 'string', + describe: 'Fee for committing a bid to the auction' + }, + 'reveal-fee': { + type: 'string', + describe: 'Fee for revealing a bid in the auction' + }, + 'minimum-bid': { + type: 'string', + default: 0, + describe: 'Minimum bid amount (only for vickrey auction)' + }, + 'max-price': { + type: 'string', + default: 0, + describe: 'Max acceptable bid price (only for provider auction)' + }, + 'num-providers': { + type: 'number', + describe: 'Number ofdesired providers (only for provider auction)' + } +}; + +export const handler = async (argv: Arguments) => { + const { config } = argv; + + const kind = argv.kind as string; + const validAuctionKinds = [AUCTION_KIND_VICKREY, AUCTION_KIND_PROVIDER]; + assert(validAuctionKinds.includes(kind), `Invalid auction kind, has to be one of ${validAuctionKinds}.`); + + if (kind === AUCTION_KIND_VICKREY) { + assert(argv.minimumBid, 'Invalid minimum bid.'); + assert(!argv.maxPrice, `Max price can only be used with ${AUCTION_KIND_PROVIDER} auction.`); + assert(!argv.numProviders, `Num providers can only be used with ${AUCTION_KIND_PROVIDER} auction.`); + } else if (kind === AUCTION_KIND_PROVIDER) { + assert(argv.maxPrice, 'Invalid max price.'); + assert(argv.numProviders, 'Invalid num providers.'); + assert(!argv.minimumBid, `Minimum bid can only be used with ${AUCTION_KIND_VICKREY} auction.`); + } + + assert(argv.commitsDuration, 'Invalid commits duration.'); + assert(argv.revealsDuration, 'Invalid reveals duration.'); + assert(argv.commitFee, 'Invalid commit fee.'); + assert(argv.revealFee, 'Invalid reveal fee.'); + + const commitsDuration = argv.commitsDuration as string; + const revealsDuration = argv.revealsDuration as string; + + const denom = argv.denom as string; + const commitFee = argv.commitFee as string; + const revealFee = argv.revealFee as string; + const minimumBid = argv.minimumBid as string; + const maxPrice = argv.maxPrice as string; + const numProviders = argv.numProviders as number; + + const { services: { registry: registryConfig } } = getConfig(config as string); + const { rpcEndpoint, gqlEndpoint, privateKey, chainId } = getConnectionInfo(argv, registryConfig); + assert(rpcEndpoint, 'Invalid registry RPC endpoint.'); + assert(gqlEndpoint, 'Invalid registry GQL endpoint.'); + assert(privateKey, 'Invalid Transaction Key.'); + assert(chainId, 'Invalid registry Chain ID.'); + + const gasPrice = getGasPrice(argv, registryConfig); + const registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId, gasPrice }); + + const fee = getGasAndFees(argv, registryConfig); + + let result: any; + if (kind === AUCTION_KIND_VICKREY) { + result = await registry.createAuction({ commitsDuration, revealsDuration, denom, commitFee, revealFee, minimumBid }, privateKey, fee); + } else { + result = await registry.createProviderAuction({ commitsDuration, revealsDuration, denom, commitFee, revealFee, maxPrice, numProviders }, privateKey, fee); + } + + const jsonString = `{"auctionId":"${result.auction?.id}"}`; + txOutput(result, jsonString, argv.output, argv.verbose); +}; diff --git a/src/cmds/registry-cmds/auction-cmds/release-funds.ts b/src/cmds/registry-cmds/auction-cmds/release-funds.ts new file mode 100644 index 0000000..f555f3a --- /dev/null +++ b/src/cmds/registry-cmds/auction-cmds/release-funds.ts @@ -0,0 +1,34 @@ +import { Arguments } from 'yargs'; +import assert from 'assert'; + +import { Account, Registry } from '@cerc-io/registry-sdk'; + +import { getConfig, getConnectionInfo, getGasAndFees, getGasPrice, txOutput } from '../../../util'; + +export const command = 'release-funds [auction-id]'; + +export const desc = 'Release funds of provider auction winners.'; + +export const handler = async (argv: Arguments) => { + const auctionId = argv.auctionId as string; + assert(auctionId, 'Invalid auction ID.'); + + const { services: { registry: registryConfig } } = getConfig(argv.config as string); + const { rpcEndpoint, gqlEndpoint, privateKey, chainId } = getConnectionInfo(argv, registryConfig); + assert(rpcEndpoint, 'Invalid registry RPC endpoint.'); + assert(gqlEndpoint, 'Invalid registry GQL endpoint.'); + assert(privateKey, 'Invalid Transaction Key.'); + assert(chainId, 'Invalid registry Chain ID.'); + + const account = new Account(Buffer.from(privateKey, 'hex')); + await account.init(); + + const gasPrice = getGasPrice(argv, registryConfig); + const registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId, gasPrice }); + const fee = getGasAndFees(argv, registryConfig); + + const result = await registry.releaseFunds({ auctionId }, privateKey, fee); + + const success = '{"success": true}'; + txOutput(result, success, argv.output, argv.verbose); +}; diff --git a/src/cmds/registry-cmds/bond-cmds/create.ts b/src/cmds/registry-cmds/bond-cmds/create.ts index 2385d94..e6440c9 100644 --- a/src/cmds/registry-cmds/bond-cmds/create.ts +++ b/src/cmds/registry-cmds/bond-cmds/create.ts @@ -1,5 +1,6 @@ import { Arguments } from 'yargs'; import assert from 'assert'; + import { Registry } from '@cerc-io/registry-sdk'; import { getConfig, getConnectionInfo, getGasAndFees, getGasPrice, txOutput } from '../../../util'; diff --git a/test/README.md b/test/README.md index b2eb727..1975ddd 100644 --- a/test/README.md +++ b/test/README.md @@ -1,4 +1,4 @@ -## Run CLI tests +# Run CLI tests * Follow the project `Setup` and `Account Setup` from root [README](./../README.md) diff --git a/test/cli.test.ts b/test/cli.test.ts index 4f0168a..f284598 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -2,6 +2,8 @@ import fs from 'fs'; import assert from 'assert'; import { spawnSync } from 'child_process'; +import { AUCTION_KIND_PROVIDER, AUCTION_KIND_VICKREY } from '@cerc-io/registry-sdk'; + import { CHAIN_ID, TOKEN_TYPE, @@ -16,7 +18,8 @@ import { getAuthorityObj, getAuctionObj, getBidObj, - updateGasAndFeesConfig + updateGasAndFeesConfig, + AUCTION_STATUS } from './helpers'; describe('Test laconic CLI commands', () => { @@ -225,6 +228,7 @@ describe('Test laconic CLI commands', () => { expect(outputObj.accounts.length).toEqual(2); expect(outputObj.accounts).toMatchObject(expectedAccounts); }); + test('laconic registry tokens gettx --hash ', async () => { const sendAmount = 1000000000; @@ -369,7 +373,7 @@ describe('Test laconic CLI commands', () => { }); }); - describe('Auction operations', () => { + describe('Authority auction operations', () => { const bidAmount = 25000000; let bidRevealFilePath: string; @@ -587,6 +591,330 @@ describe('Test laconic CLI commands', () => { }); }); + describe('Vickrey Auction operations', () => { + const commitFee = 1000; + const revealFee = 1000; + const minimumBid = 100000; + + const bidAmount = 25000000; + let bidRevealFilePath: string; + + test('laconic registry auction create --kind --commits-duration --reveals-duration --denom --commit-fee --reveal-fee --minimum-bid ', async () => { + const createAuctionResult = spawnSync('laconic', [ + 'registry', + 'auction', + 'create', + '--kind', AUCTION_KIND_VICKREY, + '--commits-duration', AUCTION_COMMIT_DURATION.toString(), + '--reveals-duration', AUCTION_REVEAL_DURATION.toString(), + '--denom', TOKEN_TYPE, + '--commit-fee', commitFee.toString(), + '--reveal-fee', revealFee.toString(), + '--minimum-bid', minimumBid.toString() + ]); + const outputObj = checkResultAndRetrieveOutput(createAuctionResult); + + expect(outputObj).toHaveProperty('auctionId'); + + testAuctionId = outputObj.auctionId; + const getAuctionResult = spawnSync('laconic', ['registry', 'auction', 'get', '--id', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(getAuctionResult); + + const expectedAuctionObjPartial = { + kind: AUCTION_KIND_VICKREY, + status: AUCTION_STATUS.COMMIT, + ownerAddress: testAccount, + commitFee: { quantity: commitFee }, + revealFee: { quantity: revealFee }, + minimumBid: { quantity: minimumBid }, + winnerAddresses: [], + winnerBids: [], + maxPrice: { quantity: 0 }, + numProviders: 0, + bids: [] + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + }); + + test('laconic registry auction bid commit ', async () => { + const result = spawnSync('laconic', ['registry', 'auction', 'bid', 'commit', testAuctionId, bidAmount.toString(), TOKEN_TYPE]); + const outputObj = checkResultAndRetrieveOutput(result); + + // Expected output + expect(outputObj.reveal_file).toBeDefined(); + + bidRevealFilePath = outputObj.reveal_file; + }); + + test('laconic registry auction bid reveal ', async () => { + // Wait for auction commits duration (60s) + await delay(AUCTION_COMMIT_DURATION * 1000); + + let auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + let auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedAuctionObjPartial = { + status: AUCTION_STATUS.REVEAL, + ownerAddress: testAccount, + winnerAddresses: [], + winnerBids: [], + bids: [{ + bidderAddress: testAccount, + status: AUCTION_STATUS.COMMIT, + bidAmount: { quantity: 0 } + }] + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + + // Reveal bid + const result = spawnSync('laconic', ['registry', 'auction', 'bid', 'reveal', testAuctionId, bidRevealFilePath]); + const outputObj = checkResultAndRetrieveOutput(result); + + // Expected output + expect(outputObj).toEqual({ success: true }); + + const revealObject = JSON.parse(fs.readFileSync(bidRevealFilePath, 'utf8')); + expect(revealObject).toMatchObject({ + chainId: CHAIN_ID, + auctionId: testAuctionId, + bidderAddress: testAccount, + bidAmount: `${bidAmount}${TOKEN_TYPE}` + }); + + // Get auction with revealed bid + auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedAuctionObjPartialOnBidReveal = { + status: AUCTION_STATUS.REVEAL, + winnerAddresses: [], + bids: [{ + bidderAddress: testAccount, + status: AUCTION_STATUS.REVEAL, + bidAmount: { quantity: bidAmount } + }] + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartialOnBidReveal); + }, (AUCTION_COMMIT_DURATION + 5) * 1000); + + test('laconic registry auction get ', async () => { + // Wait for auction reveals duration (60s) + await delay(AUCTION_REVEAL_DURATION * 1000); + + const auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedAuctionObjPartial = { + status: AUCTION_STATUS.COMPLETED, + ownerAddress: testAccount, + winnerAddresses: [testAccount], + winnerBids: [{ quantity: bidAmount }], + winnerPrice: { quantity: bidAmount } + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + }, (AUCTION_COMMIT_DURATION + 5) * 1000); + }); + + describe.only('Provider Auction operations', () => { + const commitFee = 1000; + const revealFee = 1000; + const maxPrice = 1000000; + const numProviders = 2; + const bidderInitialBlanace = 1000000000; + const txFees = 200000; + testAuctionId = '5e9dd5501e965f25db4fa62635d0ce5f6c59d73ab1a2ea999f8c5bf2f6fb6350'; + + const bidderAccounts = [ + { + privateKey: 'f40f8e2c9ba70595b6d1cf3bcc47ba539e7d6ad2bcdb16e26c1e369378fd5a55', + address: 'laconic13cd6ntlcf5y0zmafg6wf96y6vsnq46xagpmjtc', + bidAmount: 25000 + }, + { + privateKey: '2c70e81c285e12f196837911aa258b11dff7e4189fc0f11e28cb228956807881', + address: 'laconic15x7sw49w3x2pahjlr48hunp5gpr7hm54eg3f8h', + bidAmount: 25300 + }, + { + privateKey: '1d3a47900e1a5980b171419ac700e779330bc0f85389a4113ff608ca314e25bb', + address: 'laconic1lkgay8ejvcwmngj3jua2ancdxxkukecz7hty89', + bidAmount: 25200 + } + ]; + const winnerAccounts = [bidderAccounts[0], bidderAccounts[2]]; + const winnerPrice = bidderAccounts[2].bidAmount; + + const bidRevealFilePaths: string[] = []; + + beforeAll(() => { + // Fund all bidder accounts + bidderAccounts.forEach(account => { + spawnSync('laconic', ['registry', 'tokens', 'send', '--address', account.address, '--type', TOKEN_TYPE, '--quantity', bidderInitialBlanace.toString()]); + }); + }); + + test('laconic registry auction create --kind --commits-duration --reveals-duration --denom --commit-fee --reveal-fee --max-price --num-providers ', async () => { + const createAuctionResult = spawnSync('laconic', [ + 'registry', + 'auction', + 'create', + '--kind', AUCTION_KIND_PROVIDER, + '--commits-duration', AUCTION_COMMIT_DURATION.toString(), + '--reveals-duration', AUCTION_REVEAL_DURATION.toString(), + '--denom', TOKEN_TYPE, + '--commit-fee', commitFee.toString(), + '--reveal-fee', revealFee.toString(), + '--max-price', maxPrice.toString(), + '--num-providers', numProviders.toString() + ]); + + const outputObj = checkResultAndRetrieveOutput(createAuctionResult); + + expect(outputObj).toHaveProperty('auctionId'); + + testAuctionId = outputObj.auctionId; + const getAuctionResult = spawnSync('laconic', ['registry', 'auction', 'get', '--id', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(getAuctionResult); + + const expectedAuctionObjPartial = { + kind: AUCTION_KIND_PROVIDER, + status: AUCTION_STATUS.COMMIT, + ownerAddress: testAccount, + commitFee: { quantity: commitFee }, + revealFee: { quantity: revealFee }, + minimumBid: { quantity: 0 }, + winnerAddresses: [], + winnerBids: [], + maxPrice: { quantity: maxPrice }, + numProviders: numProviders, + bids: [] + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + }); + + test('laconic registry auction bid commit ', async () => { + for (const bidderAccount of bidderAccounts) { + const result = spawnSync('laconic', ['registry', 'auction', 'bid', 'commit', testAuctionId, bidderAccount.bidAmount.toString(), TOKEN_TYPE, '--txKey', bidderAccount.privateKey]); + const outputObj = checkResultAndRetrieveOutput(result); + + // Expected output + expect(outputObj.reveal_file).toBeDefined(); + + bidRevealFilePaths.push(outputObj.reveal_file); + } + + const auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedBids = bidderAccounts.map(account => ({ + bidderAddress: account.address, + status: AUCTION_STATUS.COMMIT, + bidAmount: { quantity: 0 } + })); + const expectedAuctionObjPartial = { + status: AUCTION_STATUS.COMMIT, + ownerAddress: testAccount, + winnerAddresses: [], + winnerBids: [], + bids: expectedBids + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + }); + + test('laconic registry auction bid reveal ', async () => { + // Wait for auction commits duration (60s) + await delay(AUCTION_COMMIT_DURATION * 1000); + + // Reveal bid + for (let i = 0; i < bidderAccounts.length; i++) { + const result = spawnSync('laconic', ['registry', 'auction', 'bid', 'reveal', testAuctionId, bidRevealFilePaths[i], '--txKey', bidderAccounts[i].privateKey]); + const outputObj = checkResultAndRetrieveOutput(result); + + // Expected output + expect(outputObj).toEqual({ success: true }); + + const revealObject = JSON.parse(fs.readFileSync(bidRevealFilePaths[i], 'utf8')); + expect(revealObject).toMatchObject({ + chainId: CHAIN_ID, + auctionId: testAuctionId, + bidderAddress: bidderAccounts[i].address, + bidAmount: `${bidderAccounts[i].bidAmount}${TOKEN_TYPE}` + }); + } + + // Get auction with revealed bid + const auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedBids = bidderAccounts.map(account => ({ + bidderAddress: account.address, + status: AUCTION_STATUS.REVEAL, + bidAmount: { quantity: account.bidAmount } + })); + const expectedAuctionObjPartialOnBidReveal = { + status: AUCTION_STATUS.REVEAL, + winnerAddresses: [], + bids: expectedBids + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartialOnBidReveal); + }, (AUCTION_COMMIT_DURATION + 60) * 1000); + + test('laconic registry auction get ', async () => { + // Wait for auction reveals duration (60s) + await delay(AUCTION_REVEAL_DURATION * 1000); + + const auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedWinnerAddresses = winnerAccounts.map(account => account.address); + const expectedWinnerBids = winnerAccounts.map(account => ({ quantity: account.bidAmount })); + + const expectedAuctionObjPartial = { + status: AUCTION_STATUS.COMPLETED, + ownerAddress: testAccount, + winnerAddresses: expectedWinnerAddresses, + winnerBids: expectedWinnerBids, + winnerPrice: { quantity: winnerPrice }, + fundsReleased: false + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + }, (AUCTION_REVEAL_DURATION + 5) * 1000); + + test('laconic registry auction release-funds ', async () => { + const result = spawnSync('laconic', ['registry', 'auction', 'release-funds', testAuctionId]); + const outputObj = checkResultAndRetrieveOutput(result); + + expect(outputObj).toEqual({ success: true }); + + const auctionResult = spawnSync('laconic', ['registry', 'auction', 'get', testAuctionId]); + const auctionOutputObj = checkResultAndRetrieveOutput(auctionResult); + + const expectedAuctionObjPartial = { + status: AUCTION_STATUS.COMPLETED, + ownerAddress: testAccount, + fundsReleased: true + }; + expect(auctionOutputObj[0]).toMatchObject(expectedAuctionObjPartial); + + const expectedBalances = [ + bidderInitialBlanace - (commitFee) - (2 * txFees) + winnerPrice, + bidderInitialBlanace - (commitFee) - (2 * txFees), + bidderInitialBlanace - (commitFee) - (2 * txFees) + winnerPrice + ]; + + for (let i = 0; i < bidderAccounts.length; i++) { + const result = spawnSync('laconic', ['registry', 'account', 'get', '--address', bidderAccounts[i].address]); + const outputObj = checkResultAndRetrieveOutput(result); + + // Expected account + const expectedAccount = getAccountObj({ address: bidderAccounts[i].address, balance: expectedBalances[i] }); + + expect(outputObj.length).toEqual(1); + expect(outputObj[0]).toMatchObject(expectedAccount); + } + }); + }); + describe('Gas and fees config', () => { const bondAmount = 1000; diff --git a/test/helpers.ts b/test/helpers.ts index 84d8326..b98687e 100644 --- a/test/helpers.ts +++ b/test/helpers.ts @@ -8,6 +8,12 @@ import { getConfig } from '../src/util'; export const CHAIN_ID = 'laconic_9000-1'; export const TOKEN_TYPE = 'alnt'; +export enum AUCTION_STATUS { + COMMIT = 'commit', + REVEAL = 'reveal', + COMPLETED = 'completed' +} + export const AUCTION_FEES = { commit: 1000000, reveal: 1000000, @@ -17,6 +23,10 @@ export const AUCTION_COMMIT_DURATION = 60; // 60s export const AUCTION_REVEAL_DURATION = 60; // 60s export function checkResultAndRetrieveOutput (result: SpawnSyncReturns): any { + if (result.status !== 0) { + console.log('stderr', result.stderr.toString().trim()); + } + expect(result.status).toBe(0); const errorOutput = result.stderr.toString().trim(); @@ -95,7 +105,8 @@ export function getAuctionObj (params: { owner: string, status?: string }): any type: TOKEN_TYPE, quantity: AUCTION_FEES.minimumBid }, - winnerAddress: '' + winnerAddresses: [], + winnerBids: [] }; } diff --git a/yarn.lock b/yarn.lock index a832ebf..404ec4e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -302,10 +302,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@cerc-io/registry-sdk@^0.2.9": - version "0.2.9" - resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fregistry-sdk/-/0.2.9/registry-sdk-0.2.9.tgz#da0645d30a1975bf6c0869dff4ab51fafd73c6ed" - integrity sha512-ZLlYa2yNvd19mA2MYr7qW0fFkouOxK5w8EzsIYXUm+YokLZXCaiUIUjvDtnebPppLZDPqNUjxJrNFC70wBhT0A== +"@cerc-io/registry-sdk@^0.2.10": + version "0.2.10" + resolved "https://git.vdb.to/api/packages/cerc-io/npm/%40cerc-io%2Fregistry-sdk/-/0.2.10/registry-sdk-0.2.10.tgz#15773ea36a862585cdcb0991cbf075736f845f96" + integrity sha512-xxVD7ylrN951TFoSFbluz7mt4SwSCv7z+yry3jGd8v8TWnycoBMMrrYSTfETs6Ydxwziiz/uLrRwk59vFZxLEA== dependencies: "@cosmjs/amino" "^0.28.1" "@cosmjs/crypto" "^0.28.1"