|
|
|
@ -1,12 +1,12 @@
|
|
|
|
|
import { Registry, Account, createBid, INVALID_BID_ERROR } from './index';
|
|
|
|
|
import { getConfig } from './testing/helper';
|
|
|
|
|
import { Registry, Account, createBid, INVALID_BID_ERROR, RELEASE_FUNDS_ERROR, OWNER_MISMATCH_ERROR } from './index';
|
|
|
|
|
import { checkAccountBalance, createTestAccounts, getConfig } from './testing/helper';
|
|
|
|
|
import { DENOM } from './constants';
|
|
|
|
|
|
|
|
|
|
jest.setTimeout(30 * 60 * 1000);
|
|
|
|
|
const { chainId, rpcEndpoint, gqlEndpoint, privateKey, fee } = getConfig();
|
|
|
|
|
|
|
|
|
|
const duration = 60;
|
|
|
|
|
const txFees = 200000;
|
|
|
|
|
const txFees = Number(fee.amount[0].amount);
|
|
|
|
|
const commitFee = '1000';
|
|
|
|
|
const revealFee = '1000';
|
|
|
|
|
|
|
|
|
@ -28,21 +28,15 @@ const auctionTests = () => {
|
|
|
|
|
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') };
|
|
|
|
|
const auctionCreator = await createTestAccounts(1);
|
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: creatorInitialBalance.toString(), destinationAddress: auctionCreator[0].address }, privateKey, fee);
|
|
|
|
|
auctionCreatorAccount = { address: auctionCreator[0].address, privateKey: auctionCreator[0].privateKey.toString('hex') };
|
|
|
|
|
|
|
|
|
|
// Create bidder accounts
|
|
|
|
|
const accounts = await createTestAccounts(numBidders);
|
|
|
|
|
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') });
|
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: bidderInitialBalance.toString(), destinationAddress: accounts[i].address }, privateKey, fee);
|
|
|
|
|
bidderAccounts.push({ address: accounts[i].address, privateKey: accounts[i].privateKey.toString('hex') });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
@ -111,12 +105,10 @@ const auctionTests = () => {
|
|
|
|
|
|
|
|
|
|
// 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 { type, quantity } = await checkAccountBalance(registry, bidderAccounts[i].address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
// The bid amount, commit fee, reveal fee and tx fees (for commit and reveal txs) will be deducted from the bidder's acoount
|
|
|
|
|
const expectedBalance = bidderInitialBalance - parseInt(bidAmounts[i]) - (2 * txFees) - parseInt(commitFee) - parseInt(revealFee);
|
|
|
|
|
expect(actualBalance).toBe(expectedBalance);
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
@ -130,7 +122,7 @@ const auctionTests = () => {
|
|
|
|
|
setTimeout(done, waitTime);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Check auction winner, status, and winner balance.', async () => {
|
|
|
|
|
test('Check auction status, winner address and balance.', async () => {
|
|
|
|
|
const [auction] = await registry.getAuctionsByIds([auctionId]);
|
|
|
|
|
expect(auction.status).toEqual('completed');
|
|
|
|
|
|
|
|
|
@ -145,12 +137,10 @@ const auctionTests = () => {
|
|
|
|
|
|
|
|
|
|
const winningPriceAmount = parseInt(auction.winnerPrice.quantity);
|
|
|
|
|
|
|
|
|
|
const [winnerAccountObj] = await registry.getAccounts([highestBidder.address]);
|
|
|
|
|
expect(winnerAccountObj).toBeDefined();
|
|
|
|
|
|
|
|
|
|
const [{ type, quantity }] = winnerAccountObj.balance;
|
|
|
|
|
const { type, quantity } = await checkAccountBalance(registry, highestBidder.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
// The winning price will get deducted after auction completion
|
|
|
|
|
expect(actualBalance).toBe(bidderInitialBalance - winningPriceAmount - (2 * txFees) - parseInt(commitFee));
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
|
});
|
|
|
|
@ -164,7 +154,8 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
let bidderAccounts: { address: string, privateKey: string, bid?: any }[] = [];
|
|
|
|
|
let sortedBidders: { address: string, privateKey: string, bid?: any }[] = [];
|
|
|
|
|
let filteredBidders: { address: string, privateKey: string, bid?: any }[] = [];
|
|
|
|
|
let invalidBidderAddresses: string[] = [];
|
|
|
|
|
let invalidBidderAddress: string;
|
|
|
|
|
let otherAccount: { address: string, privateKey: string };
|
|
|
|
|
|
|
|
|
|
const numBidders = bidAmounts.length;
|
|
|
|
|
const maxPrice = 10 * lowestBidAmount;
|
|
|
|
@ -173,22 +164,23 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
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') };
|
|
|
|
|
const auctionCreator = await createTestAccounts(1);
|
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: creatorInitialBalance.toString(), destinationAddress: auctionCreator[0].address }, privateKey, fee);
|
|
|
|
|
auctionCreatorAccount = { address: auctionCreator[0].address, privateKey: auctionCreator[0].privateKey.toString('hex') };
|
|
|
|
|
|
|
|
|
|
// Create bidder accounts
|
|
|
|
|
const accounts = await createTestAccounts(numBidders);
|
|
|
|
|
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') });
|
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: bidderInitialBalance.toString(), destinationAddress: accounts[i].address }, privateKey, fee);
|
|
|
|
|
bidderAccounts.push({ address: accounts[i].address, privateKey: accounts[i].privateKey.toString('hex') });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mnenonic3 = Account.generateMnemonic();
|
|
|
|
|
const otherAcc = await Account.generateFromMnemonic(mnenonic3);
|
|
|
|
|
await otherAcc.init();
|
|
|
|
|
|
|
|
|
|
await registry.sendCoins({ denom: DENOM, amount: creatorInitialBalance.toString(), destinationAddress: otherAcc.address }, privateKey, fee);
|
|
|
|
|
otherAccount = { address: otherAcc.address, privateKey: otherAcc.privateKey.toString('hex') };
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Create a provider auction', async () => {
|
|
|
|
@ -210,12 +202,10 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
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 { type, quantity } = await checkAccountBalance(registry, auctionCreatorAccount.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
// The locked amount and tx fees are deducted from the auction creator's account
|
|
|
|
|
const expectedBalance = creatorInitialBalance - (maxPrice * numProviders) - txFees;
|
|
|
|
|
expect(actualBalance).toBe(expectedBalance);
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
@ -233,7 +223,7 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
|
|
|
|
|
for (const bidder of sortedBidders) {
|
|
|
|
|
if (parseInt(bidder.bid.reveal.bidAmount) > maxPrice) {
|
|
|
|
|
invalidBidderAddresses.push(bidder.address);
|
|
|
|
|
invalidBidderAddress = bidder.address;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -264,7 +254,7 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
fee
|
|
|
|
|
);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (invalidBidderAddresses.includes(bidderAccounts[i].address)) {
|
|
|
|
|
if (invalidBidderAddress === bidderAccounts[i].address) {
|
|
|
|
|
expect(error.toString()).toContain(INVALID_BID_ERROR);
|
|
|
|
|
} else {
|
|
|
|
|
throw error;
|
|
|
|
@ -278,7 +268,7 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
expect(auction.status).toEqual('reveal');
|
|
|
|
|
|
|
|
|
|
const expectedBids = sortedBidders.map((bidder: any) => {
|
|
|
|
|
if (invalidBidderAddresses.includes(bidder.address)) {
|
|
|
|
|
if (invalidBidderAddress === bidder.address) {
|
|
|
|
|
return '0';
|
|
|
|
|
}
|
|
|
|
|
return bidder.bid.reveal.bidAmount;
|
|
|
|
@ -311,17 +301,29 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
const expectedWinningPrice = winningBidders[numWinners - 1].bid.reveal.bidAmount;
|
|
|
|
|
expect(`${auction.winnerPrice.quantity}${auction.winnerPrice.type}`).toEqual(expectedWinningPrice);
|
|
|
|
|
|
|
|
|
|
const [creatorAccountObj] = await registry.getAccounts([auctionCreatorAccount.address]);
|
|
|
|
|
expect(creatorAccountObj).toBeDefined();
|
|
|
|
|
|
|
|
|
|
const [{ type, quantity }] = creatorAccountObj.balance;
|
|
|
|
|
const { type, quantity } = await checkAccountBalance(registry, auctionCreatorAccount.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
// Check auction creator balance
|
|
|
|
|
// The total winning amount will get deducted and the leftover locked amount is returned
|
|
|
|
|
const totalWinningAmount = parseInt(auction.winnerPrice.quantity) * winningBidders.length;
|
|
|
|
|
const expectedCreatorBalance = creatorInitialBalance - totalWinningAmount - txFees;
|
|
|
|
|
expect(actualBalance).toBe(expectedCreatorBalance);
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
|
|
|
|
|
|
// Funds should not be given to winners on auction completion
|
|
|
|
|
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 { type, quantity } = await checkAccountBalance(registry, bidWinner.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
const expectedBidderBalance = bidderInitialBalance - (2 * txFees) - parseInt(commitFee);
|
|
|
|
|
expect(actualBalance).toBe(expectedBidderBalance);
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Release funds and check provider balances.', async () => {
|
|
|
|
@ -333,20 +335,25 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
|
|
|
|
|
const winningBidAmount = parseInt(auction.winnerPrice.quantity);
|
|
|
|
|
|
|
|
|
|
// Release funds
|
|
|
|
|
await registry.releaseFunds({ auctionId }, auctionCreatorAccount.privateKey, fee);
|
|
|
|
|
// Only owner can release funds
|
|
|
|
|
try {
|
|
|
|
|
await registry.releaseFunds({ auctionId }, otherAccount.privateKey, fee);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
expect(error.toString()).toContain(OWNER_MISMATCH_ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check balances of auction winners
|
|
|
|
|
// Release funds
|
|
|
|
|
const auctionObj = await registry.releaseFunds({ auctionId }, auctionCreatorAccount.privateKey, fee);
|
|
|
|
|
expect(auctionObj.auction?.fundsReleased).toBe(true);
|
|
|
|
|
|
|
|
|
|
// Auction winners get the winning amount after funds are released
|
|
|
|
|
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 { type, quantity } = await checkAccountBalance(registry, bidWinner.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
const expectedBidderBalance = bidderInitialBalance + winningBidAmount - (2 * txFees) - parseInt(commitFee);
|
|
|
|
@ -356,16 +363,11 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
|
|
|
|
|
// 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 { type, quantity } = await checkAccountBalance(registry, bidder.address);
|
|
|
|
|
const actualBalance = parseInt(quantity);
|
|
|
|
|
|
|
|
|
|
let expectedBidderBalance: number;
|
|
|
|
|
// Remove 'alnt' from bidAmount for comparison
|
|
|
|
|
const sanitizedBidAmount = parseInt(bidder.bid.reveal.bidAmount.toString().replace(/\D/g, ''), 10);
|
|
|
|
|
if (sanitizedBidAmount < maxPrice) {
|
|
|
|
|
if (invalidBidderAddress !== bidder.address) {
|
|
|
|
|
expectedBidderBalance = bidderInitialBalance - (2 * txFees) - parseInt(commitFee);
|
|
|
|
|
} else {
|
|
|
|
|
// Bid is invalid, reveal fees are not returned
|
|
|
|
@ -375,19 +377,24 @@ const providerAuctionTestsWithBids = (bidAmounts: number[], numProviders: number
|
|
|
|
|
expect(actualBalance).toBe(expectedBidderBalance);
|
|
|
|
|
expect(type).toBe(DENOM);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Funds cannot be released twice
|
|
|
|
|
try {
|
|
|
|
|
await registry.releaseFunds({ auctionId }, auctionCreatorAccount.privateKey, fee);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
expect(error.toString()).toContain(RELEASE_FUNDS_ERROR);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const providerAuctionTests = () => {
|
|
|
|
|
const bids1 = [10002000, 10009000, 10006000, 10004000];
|
|
|
|
|
// With a bid amount greater than the max price
|
|
|
|
|
const bids1 = [10002000, 10009000, 10006000 * 10, 10004000];
|
|
|
|
|
|
|
|
|
|
// Number of providers is less than number of bidders
|
|
|
|
|
describe('Auction with numProviders < numBidders', () => providerAuctionTestsWithBids(bids1, 3));
|
|
|
|
|
// Number of providers is greater than number of bidders
|
|
|
|
|
describe('Auction numProviders > numBidders', () => providerAuctionTestsWithBids(bids1, 5));
|
|
|
|
|
|
|
|
|
|
// With bid greater than max price
|
|
|
|
|
const bids2 = [10002000, 10009000, 100060000, 10004000];
|
|
|
|
|
describe('Auction with a bid greater than the max price', () => providerAuctionTestsWithBids(bids2, 5));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
describe('Vickrey Auction', () => auctionTests());
|
|
|
|
|