forked from cerc-io/registry-sdk
349 lines
14 KiB
TypeScript
349 lines
14 KiB
TypeScript
import Long from 'long';
|
|
|
|
import { coin } from '@cosmjs/amino';
|
|
import { Coin } from '@cosmjs/proto-signing';
|
|
|
|
import { Registry, Account, createBid, INVALID_BID_ERROR } from './index';
|
|
import { getConfig } from './testing/helper';
|
|
import { DENOM } from './constants';
|
|
import { Duration } from './proto/google/protobuf/duration';
|
|
|
|
jest.setTimeout(30 * 60 * 1000);
|
|
const { chainId, rpcEndpoint, gqlEndpoint, privateKey, fee } = getConfig();
|
|
|
|
const duration = 60;
|
|
const commitsDuration = Duration.fromPartial({ seconds: Long.fromNumber(60) });
|
|
const revealsDuration = Duration.fromPartial({ seconds: Long.fromNumber(60) });
|
|
|
|
const commitFee = coin('1000', DENOM);
|
|
const revealFee = coin('1000', DENOM);
|
|
|
|
const creatorInitialBalance = 1000000000000;
|
|
const bidderInitialBalance = 20000000;
|
|
const lowestBidAmount = 10000000;
|
|
|
|
const auctionTests = () => {
|
|
let registry: Registry;
|
|
let auctionId: string;
|
|
|
|
let auctionCreatorAccount: { address: string, privateKey: string };
|
|
let bidderAccounts: { address: string, privateKey: string, bid?: any }[] = [];
|
|
|
|
const numBidders = 3;
|
|
let bidAmounts: Coin[] = [];
|
|
|
|
beforeAll(async () => {
|
|
registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId });
|
|
|
|
// Create auction creator account
|
|
const mnenonic1 = Account.generateMnemonic();
|
|
const auctionCreator = await Account.generateFromMnemonic(mnenonic1);
|
|
await auctionCreator.init();
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: creatorInitialBalance.toString(), destinationAddress: auctionCreator.address }, privateKey, fee);
|
|
auctionCreatorAccount = { address: auctionCreator.address, privateKey: auctionCreator.privateKey.toString('hex') };
|
|
|
|
// Create bidder accounts
|
|
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: bidderInitialBalance.toString(), destinationAddress: account.address }, privateKey, fee);
|
|
bidderAccounts.push({ address: account.address, privateKey: account.privateKey.toString('hex') });
|
|
}
|
|
});
|
|
|
|
test('Create a vickrey auction', async () => {
|
|
const minimumBid = coin('1000000', DENOM);
|
|
|
|
const auction = await registry.createAuction(
|
|
{
|
|
commitsDuration,
|
|
revealsDuration,
|
|
commitFee,
|
|
revealFee,
|
|
minimumBid
|
|
},
|
|
auctionCreatorAccount.privateKey,
|
|
fee
|
|
);
|
|
|
|
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++) {
|
|
bidAmounts.push(coin((lowestBidAmount + (i * 500)).toString(), DENOM));
|
|
bidderAccounts[i].bid = await createBid(chainId, auctionId, bidderAccounts[i].address, `${bidAmounts[i].amount}${bidAmounts[i].denom}`);
|
|
await registry.commitBid({ auctionId, commitHash: bidderAccounts[i].bid.commitHash }, bidderAccounts[i].privateKey, fee);
|
|
}
|
|
|
|
const [auction] = await registry.getAuctionsByIds([auctionId]);
|
|
expect(auction.status).toEqual('commit');
|
|
expect(auction.bids.length).toEqual(3);
|
|
auction.bids.forEach((bid: any) => {
|
|
expect(bid.status).toEqual('commit');
|
|
});
|
|
});
|
|
|
|
test('Wait for reveal phase.', (done) => {
|
|
const commitTime = duration * 1000;
|
|
const waitTime = commitTime + (6 * 1000);
|
|
|
|
setTimeout(done, waitTime);
|
|
});
|
|
|
|
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: bidderAccounts[i].bid.revealString }, bidderAccounts[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');
|
|
});
|
|
|
|
const expectedBidAmounts = bidAmounts.map(bidAmount => { return { quantity: bidAmount.amount, type: bidAmount.denom }; });
|
|
const actualBidAmounts = auction.bids.map((bid: any) => bid.bidAmount);
|
|
expect(actualBidAmounts).toEqual(expect.arrayContaining(expectedBidAmounts));
|
|
|
|
// Check that the bid amounts are locked after reveal phase
|
|
for (let i = 0; i < numBidders; i++) {
|
|
const [bidderrAccountObj] = await registry.getAccounts([bidderAccounts[i].address]);
|
|
expect(bidderrAccountObj).toBeDefined();
|
|
|
|
const [{ type, quantity }] = bidderrAccountObj.balance;
|
|
const actualBalance = parseInt(quantity);
|
|
const expectedBalance = bidderInitialBalance - parseInt(bidAmounts[i].amount);
|
|
|
|
expect(type).toBe(DENOM);
|
|
expect(actualBalance).toBeLessThan(expectedBalance);
|
|
}
|
|
});
|
|
|
|
test('Wait for auction completion.', (done) => {
|
|
const revealTime = duration * 1000;
|
|
const waitTime = revealTime + (6 * 1000);
|
|
|
|
setTimeout(done, waitTime);
|
|
});
|
|
|
|
test('Check auction winner, status, and winner balance.', async () => {
|
|
const [auction] = await registry.getAuctionsByIds([auctionId]);
|
|
expect(auction.status).toEqual('completed');
|
|
|
|
const highestBidder = bidderAccounts[bidderAccounts.length - 1];
|
|
const secondHighestBidder = bidderAccounts[bidderAccounts.length - 2];
|
|
|
|
expect(auction.winnerAddresses).toHaveLength(1);
|
|
|
|
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 winningPriceAmount = parseInt(auction.winnerPrice.quantity);
|
|
|
|
const [winnerAccountObj] = await registry.getAccounts([highestBidder.address]);
|
|
expect(winnerAccountObj).toBeDefined();
|
|
|
|
// Balance should be less than bidder's initial balance - winning price as tx fees also gets deducted
|
|
const [{ type, quantity }] = winnerAccountObj.balance;
|
|
expect(type).toBe(DENOM);
|
|
expect(parseInt(quantity)).toBeLessThan(bidderInitialBalance - winningPriceAmount - 400000);
|
|
});
|
|
};
|
|
|
|
const providerAuctionTestsWithBids = (bidsAmount: number[], numProviders: number) => {
|
|
let registry: Registry;
|
|
let auctionId: string;
|
|
|
|
let auctionCreatorAccount: { address: string, privateKey: string };
|
|
let bidderAccounts: { address: string, privateKey: string, bid?: any }[] = [];
|
|
|
|
const bidAmounts: Coin[] = bidsAmount.map(amount => coin(amount.toString(), DENOM));
|
|
const numBidders = bidAmounts.length;
|
|
|
|
const maxPrice = coin((10 * lowestBidAmount).toString(), DENOM);
|
|
|
|
beforeAll(async () => {
|
|
registry = new Registry(gqlEndpoint, rpcEndpoint, { chainId });
|
|
|
|
// Create auction creator account
|
|
const mnenonic1 = Account.generateMnemonic();
|
|
const auctionCreator = await Account.generateFromMnemonic(mnenonic1);
|
|
await auctionCreator.init();
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: creatorInitialBalance.toString(), destinationAddress: auctionCreator.address }, privateKey, fee);
|
|
auctionCreatorAccount = { address: auctionCreator.address, privateKey: auctionCreator.privateKey.toString('hex') };
|
|
|
|
// Create bidder accounts
|
|
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: bidderInitialBalance.toString(), destinationAddress: account.address }, privateKey, fee);
|
|
bidderAccounts.push({ address: account.address, privateKey: account.privateKey.toString('hex') });
|
|
}
|
|
});
|
|
|
|
test('Create a provider auction', async () => {
|
|
const auction = await registry.createProviderAuction(
|
|
{
|
|
commitsDuration,
|
|
revealsDuration,
|
|
commitFee,
|
|
revealFee,
|
|
maxPrice,
|
|
numProviders
|
|
},
|
|
auctionCreatorAccount.privateKey, fee
|
|
);
|
|
|
|
expect(auction.auction?.id).toBeDefined();
|
|
auctionId = auction.auction?.id || '';
|
|
expect(auction.auction?.status).toEqual('commit');
|
|
|
|
// Check that the total locked amount is deducted from the creator's account
|
|
const [creatorAccountObj] = await registry.getAccounts([auctionCreatorAccount.address]);
|
|
expect(creatorAccountObj).toBeDefined();
|
|
|
|
const [{ type, quantity }] = creatorAccountObj.balance;
|
|
const actualBalance = parseInt(quantity);
|
|
const expectedBalance = creatorInitialBalance - (parseInt(maxPrice.amount) * numProviders);
|
|
|
|
expect(type).toBe(DENOM);
|
|
expect(actualBalance).toBe(expectedBalance - 200000);
|
|
});
|
|
|
|
test('Commit bids.', async () => {
|
|
for (let i = 0; i < numBidders; i++) {
|
|
bidderAccounts[i].bid = await createBid(chainId, auctionId, bidderAccounts[i].address, `${bidAmounts[i].amount}${bidAmounts[i].denom}`);
|
|
await registry.commitBid({ auctionId, commitHash: bidderAccounts[i].bid.commitHash }, bidderAccounts[i].privateKey, fee);
|
|
}
|
|
|
|
const [auction] = await registry.getAuctionsByIds([auctionId]);
|
|
expect(auction.status).toEqual('commit');
|
|
expect(auction.bids.length).toEqual(numBidders);
|
|
auction.bids.forEach((bid: any) => {
|
|
expect(bid.status).toEqual('commit');
|
|
});
|
|
});
|
|
|
|
test('Wait for reveal phase.', (done) => {
|
|
const commitTime = duration * 1000;
|
|
const waitTime = commitTime + (6 * 1000);
|
|
|
|
setTimeout(done, waitTime);
|
|
});
|
|
|
|
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: bidderAccounts[i].bid.revealString }, bidderAccounts[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');
|
|
});
|
|
|
|
const expectedBidAmounts = bidAmounts.map(bidAmount => { return { quantity: bidAmount.amount, type: bidAmount.denom }; });
|
|
const actualBidAmounts = auction.bids.map((bid: any) => bid.bidAmount);
|
|
expect(actualBidAmounts).toEqual(expect.arrayContaining(expectedBidAmounts));
|
|
});
|
|
|
|
test('Wait for auction completion.', (done) => {
|
|
const revealTime = duration * 1000;
|
|
const waitTime = revealTime + (6 * 1000);
|
|
|
|
setTimeout(done, waitTime);
|
|
});
|
|
|
|
test('Check auction winner, status, and all bidder balances.', async () => {
|
|
const [auction] = await registry.getAuctionsByIds([auctionId]);
|
|
expect(auction.status).toEqual('completed');
|
|
|
|
const sortedBidders = bidderAccounts.slice().sort((a, b) => {
|
|
return parseInt(a.bid.reveal.bidAmount) - parseInt(b.bid.reveal.bidAmount);
|
|
});
|
|
|
|
const filteredBidders = sortedBidders.filter(bidder => {
|
|
return parseInt(bidder.bid.reveal.bidAmount) <= parseInt(maxPrice.amount);
|
|
});
|
|
|
|
const numWinners = Math.min(filteredBidders.length, numProviders);
|
|
const winningBidders = filteredBidders.slice(0, numWinners);
|
|
const losingBidders = bidderAccounts.filter(bidder => !winningBidders.includes(bidder));
|
|
|
|
// Check winner price is equal to highest winning bid
|
|
const winningBidAmount = parseInt(auction.winnerPrice.quantity);
|
|
const expectedWinningPrice = winningBidders[numWinners - 1].bid.reveal.bidAmount;
|
|
expect(`${auction.winnerPrice.quantity}${auction.winnerPrice.type}`).toEqual(expectedWinningPrice);
|
|
|
|
// Check balances of auction winners
|
|
for (let i = 0; i < winningBidders.length; i++) {
|
|
const bidWinner = winningBidders[i];
|
|
|
|
expect(auction.winnerAddresses[i]).toEqual(bidWinner.address);
|
|
expect(`${auction.winnerBids[i].quantity}${auction.winnerBids[i].type}`).toEqual(bidWinner.bid.reveal.bidAmount);
|
|
|
|
const [winnerAccountObj] = await registry.getAccounts([bidWinner.address]);
|
|
expect(winnerAccountObj).toBeDefined();
|
|
|
|
const [{ type, quantity }] = winnerAccountObj.balance;
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
// Balance should be more than bidder's initial balance but
|
|
// less than initial balance + winning price as tx fees also get deducted
|
|
expect(type).toBe(DENOM);
|
|
expect(actualBalance).toBeGreaterThan(bidderInitialBalance);
|
|
expect(actualBalance).toBeLessThan(bidderInitialBalance + winningBidAmount);
|
|
}
|
|
|
|
// Check balances of non-winners
|
|
for (const bidder of losingBidders) {
|
|
const [bidderAccountObj] = await registry.getAccounts([bidder.address]);
|
|
expect(bidderAccountObj).toBeDefined();
|
|
|
|
const [{ type, quantity }] = bidderAccountObj.balance;
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
// Balance should be less than the initial balance as fees would get deducted
|
|
expect(type).toBe(DENOM);
|
|
expect(actualBalance).toBeLessThan(bidderInitialBalance);
|
|
}
|
|
|
|
const [creatorAccountObj] = await registry.getAccounts([auctionCreatorAccount.address]);
|
|
expect(creatorAccountObj).toBeDefined();
|
|
|
|
const [{ type, quantity }] = creatorAccountObj.balance;
|
|
|
|
const totalWinningAmount = parseInt(auction.winnerPrice.quantity) * winningBidders.length;
|
|
const expectedCreatorBalance = creatorInitialBalance - totalWinningAmount;
|
|
|
|
// Check whether the balance after deducting locked amount is less than the actual balance
|
|
expect(type).toBe(DENOM);
|
|
expect(parseInt(quantity)).toBeLessThan(expectedCreatorBalance);
|
|
});
|
|
};
|
|
|
|
describe('Vickrey Auction', () => auctionTests());
|
|
const randomBids = [10002000, 10009000, 10006000, 10004000];
|
|
describe('Provider Auction', () => providerAuctionTestsWithBids(randomBids, 3));
|
|
describe('Provider Auction', () => providerAuctionTestsWithBids(randomBids, 5));
|