diff --git a/__tests__/functional/transaction-forging/htlc-lock.test.ts b/__tests__/functional/transaction-forging/htlc-lock.test.ts index 9363cf3831..3aae555efa 100644 --- a/__tests__/functional/transaction-forging/htlc-lock.test.ts +++ b/__tests__/functional/transaction-forging/htlc-lock.test.ts @@ -1,4 +1,5 @@ -import { Crypto, Enums, Identities } from "@arkecosystem/crypto"; +import { Crypto, Enums, Identities, Utils } from "@arkecosystem/crypto"; +import got from "got"; import { TransactionFactory } from "../../helpers/transaction-factory"; import { secrets } from "../../utils/config/testnet/delegates.json"; import * as support from "./__support__"; @@ -130,4 +131,79 @@ describe("Transaction Forging - HTLC Lock", () => { await support.snoozeForBlock(1); await expect(transaction.id).toBeForged(); }); + + it("should update delegates vote balance using locked balance when voting and unvoting delegates", async () => { + const newWalletPassphrase = "this is a new wallet passphrase"; + // Initial Funds + const initialBalance = 100 * 1e8; + const initialFunds = TransactionFactory.transfer( + Identities.Address.fromPassphrase(newWalletPassphrase), + initialBalance, + ) + .withPassphrase(secrets[0]) + .createOne(); + + await expect(initialFunds).toBeAccepted(); + await support.snoozeForBlock(1); + await expect(initialFunds.id).toBeForged(); + + const delegateToVote = Identities.PublicKey.fromPassphrase(secrets[9]); + const { body } = await got.get(`http://localhost:4003/api/v2/delegates/${delegateToVote}`); + const parsedBody = JSON.parse(body); + const initialDelegateVoteValance = Utils.BigNumber.make(parsedBody.data.votes); + + // Submit a vote + const vote = TransactionFactory.vote(delegateToVote) + .withPassphrase(newWalletPassphrase) + .createOne(); + + await expect(vote).toBeAccepted(); + await support.snoozeForBlock(1); + await expect(vote.id).toBeForged(); + + const expectedBalanceAfterVote = initialDelegateVoteValance.plus(initialBalance).minus(vote.fee); + await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterVote.toString()); + + // Submit htlc lock transaction + const lockTransaction = TransactionFactory.htlcLock({ + secretHash: "0f128d401958b1b30ad0d10406f47f9489321017b4614e6cb993fc63913c5454", + expiration: { + type: EpochTimestamp, + value: Crypto.Slots.getTime() + 1000, + }, + }) + .withPassphrase(newWalletPassphrase) + .createOne(); + + await expect(lockTransaction).toBeAccepted(); + await support.snoozeForBlock(1); + await expect(lockTransaction.id).toBeForged(); + + const expectedBalanceAfterLock = expectedBalanceAfterVote.minus(lockTransaction.fee); + await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterLock.toString()); + + // Unvote + const unvote = TransactionFactory.unvote(delegateToVote) + .withPassphrase(newWalletPassphrase) + .createOne(); + + await expect(unvote).toBeAccepted(); + await support.snoozeForBlock(1); + await expect(unvote.id).toBeForged(); + + const expectedBalanceAfterUnvote = initialDelegateVoteValance; + await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterUnvote.toString()); + + // Vote again + const voteAgain = TransactionFactory.vote(delegateToVote) + .withPassphrase(newWalletPassphrase) + .createOne(); + + await expect(voteAgain).toBeAccepted(); + await support.snoozeForBlock(1); + await expect(voteAgain.id).toBeForged(); + + const expectedBalanceAfterVoteAgain = expectedBalanceAfterLock.minus(unvote.fee).minus(voteAgain.fee); + await expect(delegateToVote).toHaveVoteBalance(expectedBalanceAfterVoteAgain.toString()); + }); }); diff --git a/packages/core-jest-matchers/src/functional/index.ts b/packages/core-jest-matchers/src/functional/index.ts index 5cb0520da2..0dc1431a08 100644 --- a/packages/core-jest-matchers/src/functional/index.ts +++ b/packages/core-jest-matchers/src/functional/index.ts @@ -2,3 +2,4 @@ import "./accepted"; import "./forged"; import "./rejected"; import "./unconfirmed"; +import "./vote-balance"; diff --git a/packages/core-jest-matchers/src/functional/vote-balance.ts b/packages/core-jest-matchers/src/functional/vote-balance.ts new file mode 100644 index 0000000000..cc573eee2a --- /dev/null +++ b/packages/core-jest-matchers/src/functional/vote-balance.ts @@ -0,0 +1,35 @@ +import got from "got"; + +export {}; + +declare global { + namespace jest { + // tslint:disable-next-line:interface-name + interface Matchers { + toHaveVoteBalance(voteBalance: string): Promise; + } + } +} + +expect.extend({ + toHaveVoteBalance: async (publicKey: string, voteBalance: string) => { + let pass: boolean = false; + let fetchedVoteBalance: string; + try { + const { body } = await got.get(`http://localhost:4003/api/v2/delegates/${publicKey}`); + + const parsedBody = JSON.parse(body); + + fetchedVoteBalance = parsedBody.data.votes; + pass = fetchedVoteBalance === voteBalance; + } catch (e) {} // tslint:disable-line + + return { + pass, + message: () => + `expected delegate ${publicKey} ${ + this.isNot ? "not" : "" + } to have vote balance = ${voteBalance}, got ${fetchedVoteBalance}`, + }; + }, +}); diff --git a/packages/core-state/src/wallets/wallet-manager.ts b/packages/core-state/src/wallets/wallet-manager.ts index bfadef1774..dec17540d6 100644 --- a/packages/core-state/src/wallets/wallet-manager.ts +++ b/packages/core-state/src/wallets/wallet-manager.ts @@ -481,15 +481,19 @@ export class WalletManager implements State.IWalletManager { const vote: string = transaction.asset.votes[0]; const delegate: State.IWallet = this.findByPublicKey(vote.substr(1)); let voteBalance: Utils.BigNumber = delegate.getAttribute("delegate.voteBalance", Utils.BigNumber.ZERO); + const senderLockedBalance: Utils.BigNumber = sender.getAttribute( + "htlc.lockedBalance", + Utils.BigNumber.ZERO, + ); if (vote.startsWith("+")) { voteBalance = revert - ? voteBalance.minus(sender.balance.minus(transaction.fee)) - : voteBalance.plus(sender.balance); + ? voteBalance.minus(sender.balance.minus(transaction.fee)).minus(senderLockedBalance) + : voteBalance.plus(sender.balance).plus(senderLockedBalance); } else { voteBalance = revert - ? voteBalance.plus(sender.balance) - : voteBalance.minus(sender.balance.plus(transaction.fee)); + ? voteBalance.plus(sender.balance).plus(senderLockedBalance) + : voteBalance.minus(sender.balance.plus(transaction.fee)).minus(senderLockedBalance); } delegate.setAttribute("delegate.voteBalance", voteBalance);