From 27c12a6833014beec356f8b97429ffad02c82b6a Mon Sep 17 00:00:00 2001 From: IshaVenikar Date: Mon, 16 Sep 2024 14:07:25 +0530 Subject: [PATCH] Add test for creating vickrey auctions --- package.json | 3 +- src/auction.test.ts | 128 +++++++++++++++++---------------- src/authority-auction.test.ts | 129 ++++++++++++++++++++++++++++++++++ src/index.ts | 37 +++++++++- src/laconic-client.ts | 37 +++++++++- src/registry-client.ts | 10 ++- 6 files changed, 275 insertions(+), 69 deletions(-) create mode 100644 src/authority-auction.test.ts diff --git a/package.json b/package.json index d74b0ad..05311b5 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ }, "scripts": { "test": "jest --runInBand --verbose --testPathPattern=src", - "test:auctions": "TEST_AUCTIONS_ENABLED=1 jest --runInBand --verbose src/auction.test.ts", + "test:authority-auctions": "TEST_AUCTIONS_ENABLED=1 jest --runInBand --verbose src/authority-auction.test.ts", + "test:auction": "TEST_AUCTIONS_ENABLED=1 jest --runInBand --verbose src/auction.test.ts", "test:nameservice-expiry": "TEST_NAMESERVICE_EXPIRY=1 jest --runInBand --verbose src/nameservice-expiry.test.ts", "test:onboarding": "ONBOARDING_ENABLED=1 jest --runInBand --verbose src/onboarding.test.ts", "build": "tsc", diff --git a/src/auction.test.ts b/src/auction.test.ts index 4bec436..2ca8486 100644 --- a/src/auction.test.ts +++ b/src/auction.test.ts @@ -1,69 +1,93 @@ +import Long from 'long'; + import { Registry, Account, createBid } from './index'; import { getConfig } from './testing/helper'; import { DENOM } from './constants'; jest.setTimeout(30 * 60 * 1000); const { chainId, rpcEndpoint, gqlEndpoint, privateKey, fee } = getConfig(); +let auctionCreatorAccount: { address: string, privateKey: string, bid?: any }; +let bidderAccounts: { address: string, privateKey: string, bid?: any }[] = []; -const auctionTests = (numBidders = 3) => { +const createAuctionTests = () => { let registry: Registry; - const accounts: { address: string, privateKey: string, bid?: any }[] = []; + const commitsDuration = { + seconds: Long.fromNumber(60), + nanos: 0 + }; + + const revealsDuration = { + seconds: Long.fromNumber(60), + nanos: 0 + }; + + const commitFee = { + denom: DENOM, + amount: '1000' + }; + + const revealFee = { + denom: DENOM, + amount: '1000' + }; let auctionId: string; - let authorityName: string; beforeAll(async () => { - console.log('Running auction tests with num bidders', numBidders); + console.log('Running auction tests'); registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId }); + + // Create auction creator account and a bidder account + const mnenonic1 = Account.generateMnemonic(); + const auctionCreator = await Account.generateFromMnemonic(mnenonic1); + await auctionCreator.init(); + await registry.sendCoins({ denom: DENOM, amount: '1000000000000', destinationAddress: auctionCreator.address }, privateKey, fee); + auctionCreatorAccount = { address: auctionCreator.address, privateKey: auctionCreator.privateKey.toString('hex') }; }); test('Setup bidder accounts', async () => { - for (let i = 0; i < numBidders; i++) { + for (let i = 0; i < 3; i++) { const mnenonic = Account.generateMnemonic(); const account = await Account.generateFromMnemonic(mnenonic); await account.init(); await registry.sendCoins({ denom: DENOM, amount: '20000000', destinationAddress: account.address }, privateKey, fee); - accounts.push({ address: account.address, privateKey: account.privateKey.toString('hex') }); + bidderAccounts.push({ address: account.address, privateKey: account.privateKey.toString('hex') }); } }); - test('Reserve authority.', async () => { - authorityName = `laconic-${Date.now()}`; - await registry.reserveAuthority({ name: authorityName }, accounts[0].privateKey, fee); - }); + test('Create auction', async () => { + const minimumBid = { + denom: 'alnt', + amount: '1000000' + }; - test('Authority should be under auction.', async () => { - const [record] = await registry.lookupAuthorities([authorityName], true); - expect(record.ownerAddress).toEqual(''); - expect(record.height).toBeDefined(); - expect(record.status).toEqual('auction'); + const auction = await registry.createAuction({ + commitsDuration, + revealsDuration, + commitFee, + revealFee, + minimumBid, + maxPrice: undefined, + kind: 'vickrey', + numProviders: 1, + signer: auctionCreatorAccount.address + }, + auctionCreatorAccount.privateKey, fee); - expect(record.auction.id).toBeDefined(); - expect(record.auction.status).toEqual('commit'); - - auctionId = record.auction.id; + expect(auction.auction?.id).toBeDefined(); + auctionId = auction.auction?.id || ''; + expect(auction.auction?.status).toEqual('commit'); }); test('Commit bids.', async () => { - for (let i = 0; i < numBidders; i++) { - accounts[i].bid = await createBid(chainId, auctionId, accounts[i].address, `${10000000 + (i * 500)}${DENOM}`); - await registry.commitBid({ auctionId, commitHash: accounts[i].bid.commitHash }, accounts[i].privateKey, fee); + for (let i = 0; i < 3; i++) { + bidderAccounts[i].bid = await createBid(chainId, auctionId, bidderAccounts[i].address, `${10000000 + (i * 500)}${DENOM}`); + await registry.commitBid({ auctionId, commitHash: bidderAccounts[i].bid.commitHash }, bidderAccounts[i].privateKey, fee); } }); - test('Check bids are committed', async () => { - const [record] = await registry.lookupAuthorities([authorityName], true); - expect(record.auction.id).toBeDefined(); - expect(record.auction.status).toEqual('commit'); - expect(record.auction.bids).toHaveLength(accounts.length); - - record.auction.bids.forEach((bid: any) => { - expect(bid.status).toEqual('commit'); - }); - }); - test('Wait for reveal phase.', (done) => { setTimeout(done, 60 * 1000); }); @@ -72,8 +96,8 @@ const auctionTests = (numBidders = 3) => { const [auction] = await registry.getAuctionsByIds([auctionId]); expect(auction.status).toEqual('reveal'); - for (let i = 0; i < numBidders; i++) { - await registry.revealBid({ auctionId, reveal: accounts[i].bid.revealString }, accounts[i].privateKey, fee); + for (let i = 0; i < 3; i++) { + await registry.revealBid({ auctionId, reveal: bidderAccounts[i].bid.revealString }, bidderAccounts[i].privateKey, fee); } }); @@ -94,36 +118,16 @@ const auctionTests = (numBidders = 3) => { const [auction] = await registry.getAuctionsByIds([auctionId]); expect(auction.status).toEqual('completed'); - const highestBidder = accounts[accounts.length - 1]; - const secondHighestBidder = (accounts.length > 1 ? accounts[accounts.length - 2] : highestBidder); + const highestBidder = bidderAccounts[bidderAccounts.length - 1]; + const secondHighestBidder = (bidderAccounts.length > 1 ? bidderAccounts[bidderAccounts.length - 2] : highestBidder); - expect(auction.winnerAddress).toEqual(highestBidder.address); - expect(highestBidder.bid.reveal.bidAmount).toEqual(`${auction.winnerBid.quantity}${auction.winnerBid.type}`); + expect(auction.winnerAddresses[0]).toEqual(highestBidder.address); + expect(highestBidder.bid.reveal.bidAmount).toEqual(`${auction.winnerBids[0].quantity}${auction.winnerBids[0].type}`); expect(secondHighestBidder.bid.reveal.bidAmount).toEqual(`${auction.winnerPrice.quantity}${auction.winnerPrice.type}`); - - const [record] = await registry.lookupAuthorities([authorityName], true); - expect(record.ownerAddress).toEqual(highestBidder.address); - expect(record.height).toBeDefined(); - expect(record.status).toEqual('active'); }); }; -const withNumBidders = (numBidders: number) => () => auctionTests(numBidders); +const createSPAuctionTests = () => {}; -if (!process.env.TEST_AUCTIONS_ENABLED) { - // Required as jest complains if file has no tests. - test('skipping auction tests', () => {}); -} else { - /** - Running these tests requires name auctions enabled. In laconicd repo run: - - TEST_AUCTION_ENABLED=true ./init.sh - - Run tests: - - yarn test:auctions - */ - describe('Auction (1 bidder)', withNumBidders(1)); - describe('Auction (2 bidders)', withNumBidders(2)); - describe('Auction (4 bidders)', withNumBidders(4)); -} +describe('Vickrey Auction', () => createAuctionTests()); +describe('Service provider Auction', () => createSPAuctionTests()); diff --git a/src/authority-auction.test.ts b/src/authority-auction.test.ts new file mode 100644 index 0000000..4bec436 --- /dev/null +++ b/src/authority-auction.test.ts @@ -0,0 +1,129 @@ +import { Registry, Account, createBid } from './index'; +import { getConfig } from './testing/helper'; +import { DENOM } from './constants'; + +jest.setTimeout(30 * 60 * 1000); +const { chainId, rpcEndpoint, gqlEndpoint, privateKey, fee } = getConfig(); + +const auctionTests = (numBidders = 3) => { + let registry: Registry; + + const accounts: { address: string, privateKey: string, bid?: any }[] = []; + + let auctionId: string; + let authorityName: string; + + beforeAll(async () => { + console.log('Running auction tests with num bidders', numBidders); + + registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId }); + }); + + test('Setup bidder accounts', async () => { + for (let i = 0; i < numBidders; i++) { + const mnenonic = Account.generateMnemonic(); + const account = await Account.generateFromMnemonic(mnenonic); + await account.init(); + await registry.sendCoins({ denom: DENOM, amount: '20000000', destinationAddress: account.address }, privateKey, fee); + accounts.push({ address: account.address, privateKey: account.privateKey.toString('hex') }); + } + }); + + test('Reserve authority.', async () => { + authorityName = `laconic-${Date.now()}`; + await registry.reserveAuthority({ name: authorityName }, accounts[0].privateKey, fee); + }); + + test('Authority should be under auction.', async () => { + const [record] = await registry.lookupAuthorities([authorityName], true); + expect(record.ownerAddress).toEqual(''); + expect(record.height).toBeDefined(); + expect(record.status).toEqual('auction'); + + expect(record.auction.id).toBeDefined(); + expect(record.auction.status).toEqual('commit'); + + auctionId = record.auction.id; + }); + + test('Commit bids.', async () => { + for (let i = 0; i < numBidders; i++) { + accounts[i].bid = await createBid(chainId, auctionId, accounts[i].address, `${10000000 + (i * 500)}${DENOM}`); + await registry.commitBid({ auctionId, commitHash: accounts[i].bid.commitHash }, accounts[i].privateKey, fee); + } + }); + + test('Check bids are committed', async () => { + const [record] = await registry.lookupAuthorities([authorityName], true); + expect(record.auction.id).toBeDefined(); + expect(record.auction.status).toEqual('commit'); + expect(record.auction.bids).toHaveLength(accounts.length); + + record.auction.bids.forEach((bid: any) => { + expect(bid.status).toEqual('commit'); + }); + }); + + test('Wait for reveal phase.', (done) => { + setTimeout(done, 60 * 1000); + }); + + test('Reveal bids.', async () => { + const [auction] = await registry.getAuctionsByIds([auctionId]); + expect(auction.status).toEqual('reveal'); + + for (let i = 0; i < numBidders; i++) { + await registry.revealBid({ auctionId, reveal: accounts[i].bid.revealString }, accounts[i].privateKey, fee); + } + }); + + test('Check bids are revealed', async () => { + const [auction] = await registry.getAuctionsByIds([auctionId]); + expect(auction.status).toEqual('reveal'); + + auction.bids.forEach((bid: any) => { + expect(bid.status).toEqual('reveal'); + }); + }); + + test('Wait for auction completion.', (done) => { + setTimeout(done, 60 * 1000); + }); + + test('Check auction winner, authority owner and status.', async () => { + const [auction] = await registry.getAuctionsByIds([auctionId]); + expect(auction.status).toEqual('completed'); + + const highestBidder = accounts[accounts.length - 1]; + const secondHighestBidder = (accounts.length > 1 ? accounts[accounts.length - 2] : highestBidder); + + expect(auction.winnerAddress).toEqual(highestBidder.address); + expect(highestBidder.bid.reveal.bidAmount).toEqual(`${auction.winnerBid.quantity}${auction.winnerBid.type}`); + expect(secondHighestBidder.bid.reveal.bidAmount).toEqual(`${auction.winnerPrice.quantity}${auction.winnerPrice.type}`); + + const [record] = await registry.lookupAuthorities([authorityName], true); + expect(record.ownerAddress).toEqual(highestBidder.address); + expect(record.height).toBeDefined(); + expect(record.status).toEqual('active'); + }); +}; + +const withNumBidders = (numBidders: number) => () => auctionTests(numBidders); + +if (!process.env.TEST_AUCTIONS_ENABLED) { + // Required as jest complains if file has no tests. + test('skipping auction tests', () => {}); +} else { + /** + Running these tests requires name auctions enabled. In laconicd repo run: + + TEST_AUCTION_ENABLED=true ./init.sh + + Run tests: + + yarn test:auctions + */ + describe('Auction (1 bidder)', withNumBidders(1)); + describe('Auction (2 bidders)', withNumBidders(2)); + describe('Auction (4 bidders)', withNumBidders(4)); +} diff --git a/src/index.ts b/src/index.ts index 4ea8b7d..2636c3d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -32,6 +32,7 @@ import { MsgCancelBondResponse, MsgCreateBondResponse, MsgRefillBondResponse, Ms import { MsgOnboardParticipantResponse } from './proto/cerc/onboarding/v1/tx'; import { MsgSendResponse } from './proto/cosmos/bank/v1beta1/tx'; import { DEFAULT_GAS_ESTIMATION_MULTIPLIER } from './constants'; +import { MsgCreateAuction, MsgCreateAuctionResponse } from './proto/cerc/auction/v1/tx'; /** * Create an auction bid. @@ -472,8 +473,8 @@ export class Registry { } /** - * Query participants. - */ + * Query participants. + */ async getParticipants () { return this._client.getParticipants(); } @@ -499,6 +500,38 @@ export class Registry { { gasPrice: this._gasPrice } ); } + + async createAuction ({ + commitsDuration, + revealsDuration, + commitFee, + revealFee, + minimumBid, + signer, + kind, + maxPrice, + numProviders + }: MsgCreateAuction, + privateKey: string, + fee: StdFee | number = DEFAULT_GAS_ESTIMATION_MULTIPLIER): Promise { + const account = new Account(Buffer.from(privateKey, 'hex')); + await account.init(); + const laconicClient = await this.getLaconicClient(account); + + return laconicClient.createAuction( + commitsDuration, + revealsDuration, + commitFee, + revealFee, + minimumBid, + signer, + kind, + maxPrice, + numProviders, + fee, + privateKey + ); + } } export { Account }; diff --git a/src/laconic-client.ts b/src/laconic-client.ts index ae8f90c..07076cc 100644 --- a/src/laconic-client.ts +++ b/src/laconic-client.ts @@ -12,17 +12,18 @@ import { Comet38Client } from '@cosmjs/tendermint-rpc'; import { MsgCancelBondEncodeObject, MsgCreateBondEncodeObject, MsgRefillBondEncodeObject, MsgWithdrawBondEncodeObject, bondTypes, typeUrlMsgCancelBond, typeUrlMsgCreateBond, typeUrlMsgRefillBond, typeUrlMsgWithdrawBond } from './types/cerc/bond/message'; import { Coin } from './proto/cosmos/base/v1beta1/coin'; import { MsgAssociateBondEncodeObject, MsgDeleteNameEncodeObject, MsgDissociateBondEncodeObject, MsgDissociateRecordsEncodeObject, MsgReassociateRecordsEncodeObject, MsgReserveAuthorityEncodeObject, MsgSetAuthorityBondEncodeObject, MsgSetNameEncodeObject, MsgSetRecordEncodeObject, registryTypes, typeUrlMsgAssociateBond, typeUrlMsgDeleteName, typeUrlMsgDissociateBond, typeUrlMsgDissociateRecords, typeUrlMsgReassociateRecords, typeUrlMsgReserveAuthority, typeUrlMsgSetAuthorityBond, typeUrlMsgSetName, typeUrlMsgSetRecord, NAMESERVICE_ERRORS } from './types/cerc/registry/message'; -import { MsgCommitBidEncodeObject, MsgRevealBidEncodeObject, auctionTypes, typeUrlMsgCommitBid, typeUrlMsgRevealBid } from './types/cerc/auction/message'; +import { MsgCommitBidEncodeObject, MsgCreateAuctionEncodeObject, MsgRevealBidEncodeObject, auctionTypes, typeUrlMsgCommitBid, typeUrlMsgCreateAuction, typeUrlMsgRevealBid } from './types/cerc/auction/message'; import { MsgOnboardParticipantEncodeObject, ONBOARDING_DISABLED_ERROR, onboardingTypes, typeUrlMsgOnboardParticipant } from './types/cerc/onboarding/message'; import { MsgAssociateBondResponse, MsgDeleteNameResponse, MsgDissociateBondResponse, MsgDissociateRecordsResponse, MsgReassociateRecordsResponse, MsgReserveAuthorityResponse, MsgSetAuthorityBondResponse, MsgSetNameResponse, MsgSetRecordResponse, Payload } from './proto/cerc/registry/v1/tx'; import { Record, Signature } from './proto/cerc/registry/v1/registry'; import { Account } from './account'; import { Util } from './util'; -import { MsgCommitBidResponse, MsgRevealBidResponse } from './proto/cerc/auction/v1/tx'; +import { MsgCommitBidResponse, MsgCreateAuction, MsgCreateAuctionResponse, MsgRevealBidResponse } from './proto/cerc/auction/v1/tx'; import { MsgCancelBondResponse, MsgCreateBondResponse, MsgRefillBondResponse, MsgWithdrawBondResponse } from './proto/cerc/bond/v1/tx'; import { MsgOnboardParticipantResponse } from './proto/cerc/onboarding/v1/tx'; import { bankTypes } from './types/cosmos/bank/message'; import { EthPayload } from './proto/cerc/onboarding/v1/onboarding'; +import { Duration } from './proto/google/protobuf/duration'; const DEFAULT_WRITE_ERROR = 'Unable to write to laconicd.'; @@ -413,4 +414,36 @@ export class LaconicClient extends SigningStargateClient { const response = await this.signAndBroadcast(signer, [onboardParticipantMsg], fee, memo); return this.parseResponse(response); } + + public async createAuction ( + commitsDuration: Duration | undefined, + revealsDuration: Duration | undefined, + commitFee: Coin | undefined, + revealFee: Coin | undefined, + minimumBid: Coin | undefined, + signer: string, + kind: string, + maxPrice: Coin | undefined, + numProviders: number, + fee: StdFee | 'auto' | number, + memo = '' + ) { + const createAuctionMsg: MsgCreateAuctionEncodeObject = { + typeUrl: typeUrlMsgCreateAuction, + value: { + commitsDuration, + revealsDuration, + commitFee, + revealFee, + minimumBid, + signer, + kind, + maxPrice, + numProviders + } + }; + + const response = await this.signAndBroadcast(signer, [createAuctionMsg], fee, memo); + return this.parseResponse(response); + } } diff --git a/src/registry-client.ts b/src/registry-client.ts index a1cdde1..e7d973f 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -44,6 +44,7 @@ const historyFields = ` const auctionFields = ` id + kind status ownerAddress createTime @@ -61,8 +62,8 @@ const auctionFields = ` type quantity } - winnerAddress - winnerBid { + winnerAddresses + winnerBids { type quantity } @@ -70,6 +71,11 @@ const auctionFields = ` type quantity } + maxPrice { + type + quantity + } + numProviders bids { bidderAddress status