Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adding balance locked, reserved and free #364

Merged
merged 10 commits into from
Apr 6, 2021
2 changes: 1 addition & 1 deletion docs/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async function setup(): Promise<{
)
console.log(
'Attester balance is:',
await Kilt.Balance.getBalance(attester.address)
await Kilt.Balance.getBalances(attester.address)
)

// ------------------------- CType ----------------------------------------
Expand Down
65 changes: 34 additions & 31 deletions packages/core/src/__integrationtests__/Balance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import BN from 'bn.js/'
import { BlockchainUtils } from '@kiltprotocol/chain-helpers'
import {
getBalance,
getBalances,
listenToBalanceChanges,
makeTransfer,
} from '../balance/Balance.chain'
Expand Down Expand Up @@ -35,45 +35,47 @@ describe('when there is a dev chain with a faucet', () => {
})

it('should have enough coins available on the faucet', async () => {
const balance = await getBalance(faucet.address)
expect(balance.gt(new BN(100_000_000))).toBeTruthy()
const balance = await getBalances(faucet.address)
expect(balance.free.gt(new BN(100_000_000))).toBeTruthy()
// console.log(`Faucet has ${Number(balance)} micro Kilt`)
})

it('Bob has tokens', async () => {
const balance = await getBalance(bob.address)
expect(balance.gt(new BN(100_000_000))).toBeTruthy()
const balance = await getBalances(bob.address)
expect(balance.free.gt(new BN(100_000_000))).toBeTruthy()
})

it('Alice has tokens', async () => {
const balance = await getBalance(alice.address)
expect(balance.gt(new BN(100_000_000))).toBeTruthy()
const balance = await getBalances(alice.address)
expect(balance.free.gt(new BN(100_000_000))).toBeTruthy()
})

it('getBalance should return 0 for new identity', async () => {
it('getBalances should return 0 for new identity', async () => {
return expect(
getBalance(
getBalances(
Identity.buildFromMnemonic(Identity.generateMnemonic()).address
).then((n) => n.toNumber())
).then((n) => n.free.toNumber())
).resolves.toEqual(0)
})

it('should be able to faucet coins to a new identity', async () => {
const ident = Identity.buildFromMnemonic(Identity.generateMnemonic())
const funny = jest.fn()
listenToBalanceChanges(ident.address, funny)
const balanceBefore = await getBalance(faucet.address)
const balanceBefore = await getBalances(faucet.address)
await makeTransfer(faucet, ident.address, MIN_TRANSACTION).then((tx) =>
BlockchainUtils.submitTxWithReSign(tx, faucet, {
resolveOn: BlockchainUtils.IS_IN_BLOCK,
})
)
const [balanceAfter, balanceIdent] = await Promise.all([
getBalance(faucet.address),
getBalance(ident.address),
getBalances(faucet.address),
getBalances(ident.address),
])
expect(balanceBefore.sub(balanceAfter).gt(MIN_TRANSACTION)).toBeTruthy()
expect(balanceIdent.toNumber()).toBe(MIN_TRANSACTION.toNumber())
expect(
balanceBefore.free.sub(balanceAfter.free).gt(MIN_TRANSACTION)
).toBeTruthy()
expect(balanceIdent.free.toNumber()).toBe(MIN_TRANSACTION.toNumber())
expect(funny).toBeCalled()
}, 30_000)
})
Expand All @@ -98,12 +100,12 @@ describe('When there are haves and have-nots', () => {
resolveOn: BlockchainUtils.IS_IN_BLOCK,
})
)
const balanceTo = await getBalance(stormyD.address)
expect(balanceTo.toNumber()).toBe(MIN_TRANSACTION.toNumber())
const balanceTo = await getBalances(stormyD.address)
expect(balanceTo.free.toNumber()).toBe(MIN_TRANSACTION.toNumber())
}, 40_000)

it('should not accept transactions from identity with zero balance', async () => {
const originalBalance = await getBalance(stormyD.address)
const originalBalance = await getBalances(stormyD.address)
await expect(
makeTransfer(bobbyBroke, stormyD.address, MIN_TRANSACTION).then((tx) =>
BlockchainUtils.submitTxWithReSign(tx, bobbyBroke, {
Expand All @@ -112,28 +114,29 @@ describe('When there are haves and have-nots', () => {
)
).rejects.toThrowError('1010: Invalid Transaction')
const [newBalance, zeroBalance] = await Promise.all([
getBalance(stormyD.address),
getBalance(bobbyBroke.address),
getBalances(stormyD.address),
getBalances(bobbyBroke.address),
])
expect(newBalance.toNumber()).toBe(originalBalance.toNumber())
expect(zeroBalance.toNumber()).toBe(0)
expect(newBalance.free.toNumber()).toBe(originalBalance.free.toNumber())
expect(zeroBalance.free.toNumber()).toBe(0)
}, 50_000)

xit('should not accept transactions when sender cannot pay gas, but will keep gas fee', async () => {
const RichieBalance = await getBalance(richieRich.address)
const RichieBalance = await getBalances(richieRich.address)
await expect(
makeTransfer(richieRich, bobbyBroke.address, RichieBalance).then((tx) =>
BlockchainUtils.submitTxWithReSign(tx, richieRich, {
resolveOn: BlockchainUtils.IS_IN_BLOCK,
})
makeTransfer(richieRich, bobbyBroke.address, RichieBalance.free).then(
(tx) =>
BlockchainUtils.submitTxWithReSign(tx, richieRich, {
resolveOn: BlockchainUtils.IS_IN_BLOCK,
})
)
).rejects.toThrowError()
const [newBalance, zeroBalance] = await Promise.all([
getBalance(richieRich.address),
getBalance(bobbyBroke.address),
getBalances(richieRich.address),
getBalances(bobbyBroke.address),
])
expect(zeroBalance.toString()).toEqual('0')
expect(newBalance.lt(RichieBalance))
expect(zeroBalance.free.toString()).toEqual('0')
expect(newBalance.free.lt(RichieBalance.free))
}, 30_000)

it('should be able to make a new transaction once the last is ready', async () => {
Expand Down
69 changes: 41 additions & 28 deletions packages/core/src/balance/Balance.chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,57 +10,59 @@

import { UnsubscribePromise } from '@polkadot/api/types'
import BN from 'bn.js'
import type { IPublicIdentity, SubmittableExtrinsic } from '@kiltprotocol/types'
import type {
Balances,
IPublicIdentity,
SubmittableExtrinsic,
} from '@kiltprotocol/types'
import { BlockchainApiConnection } from '@kiltprotocol/chain-helpers'

import Identity from '../identity/Identity'
import BalanceUtils from './Balance.utils'

/**
* Fetches the current balance of the account with [accountAddress].
* <B>Note that balance amount is in Femto-Kilt (1e-15)and must be translated to Kilt-Coin</B>.
* Fetches the current balances of the account with [accountAddress].
* <B>Note that the balance amounts is in Femto-Kilt (1e-15)and must be translated to Kilt-Coin</B>.
*
* @param accountAddress Address of the account for which to get the balance.
* @returns A promise containing the current balance of the account.
* @param accountAddress Address of the account for which to get the balances.
* @returns A promise containing the current balances of the account.
*
* @example
* <BR>
*
* ```javascript
*
* const address = ...
* sdk.Balance.getBalance(address)
* .then((balance: BN) => {
* console.log(`balance is ${balance.toNumber()}`)
* sdk.Balance.getBalances(address)
* .then((balanceData: AccountData) => {
* console.log(`free balances is ${balanceData.free.toNumber()}`)
* })
* ```
*/
export async function getBalance(
export async function getBalances(
accountAddress: IPublicIdentity['address']
): Promise<BN> {
): Promise<Balances> {
const blockchain = await BlockchainApiConnection.getConnectionOrConnect()
return new BN(
(
await blockchain.api.query.system.account(accountAddress)
).data.free.toString()
)

return (await blockchain.api.query.system.account(accountAddress)).data
}

/**
* Attaches the given [listener] for balance changes on the account with [accountAddress].
* <B>Note that balance amount is in Femto-Kilt (1e-15) and must be translated to Kilt-Coin</B>.
* <B>Note that the balance amounts is in Femto-Kilt (1e-15) and must be translated to Kilt-Coin</B>.
*
* @param accountAddress Address of the account on which to listen for balance changes.
* @param listener Listener to receive balance change updates.
* @returns A promise containing a function that let's you unsubscribe from balance changes.
* @param accountAddress Address of the account on which to listen for all balance changes.
* @param listener Listener to receive all balance change updates.
* @returns A promise containing a function that let's you unsubscribe from all balance changes.
*
* @example
* <BR>
*
* ```javascript
* const address = ...
* const unsubscribe = await sdk.Balance.listenToBalanceChanges(address,
* (account: IPublicIdentity['address'], balance: BN, change: BN) => {
* console.log(`Balance has changed by ${change.toNumber()} to ${balance.toNumber()}`)
* (account: IPublicIdentity['address'], balances: Balances, changes: Balances) => {
* console.log(`Balance has changed by ${changes.free.toNumber()} to ${balances.free.toNumber()}`)
* });
* // later
* unsubscribe();
Expand All @@ -70,19 +72,30 @@ export async function listenToBalanceChanges(
accountAddress: IPublicIdentity['address'],
listener: (
account: IPublicIdentity['address'],
balance: BN,
change: BN
balances: Balances,
changes: Balances
) => void
): Promise<UnsubscribePromise> {
const blockchain = await BlockchainApiConnection.getConnectionOrConnect()
let previous = await getBalance(accountAddress)
const previousBalances = await getBalances(accountAddress)

return blockchain.api.query.system.account(
accountAddress,
({ data: { free: current } }) => {
const change = current.sub(previous)
previous = current
listener(accountAddress, current, change)
({ data: { free, reserved, miscFrozen, feeFrozen } }) => {
const balancesChange = {
free: free.sub(previousBalances.free),
reserved: reserved.sub(previousBalances.reserved),
miscFrozen: miscFrozen.sub(previousBalances.miscFrozen),
feeFrozen: feeFrozen.sub(previousBalances.feeFrozen),
}

const current = {
free: new BN(free),
reserved: new BN(reserved),
miscFrozen: new BN(miscFrozen),
feeFrozen: new BN(feeFrozen),
}
listener(accountAddress, current, balancesChange)
}
)
}
Expand Down
19 changes: 12 additions & 7 deletions packages/core/src/balance/Balance.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { AccountData, AccountInfo } from '@polkadot/types/interfaces'
import BN from 'bn.js/'
import TYPE_REGISTRY from '@kiltprotocol/chain-helpers/lib/blockchainApiConnection/__mocks__/BlockchainQuery'
import { BlockchainUtils } from '@kiltprotocol/chain-helpers'
import { Balances } from '@kiltprotocol/types'
import Identity from '../identity/Identity'
import {
getBalance,
getBalances,
listenToBalanceChanges,
makeTransfer,
} from './Balance.chain'
Expand Down Expand Up @@ -58,17 +59,21 @@ describe('Balance', () => {
bob = Identity.buildFromURI('//Bob')
})
it('should listen to balance changes', async (done) => {
const listener = (account: string, balance: BN, change: BN): void => {
const listener = (
account: string,
balances: Balances,
changes: Balances
): void => {
expect(account).toBe(bob.address)
expect(balance.toNumber()).toBe(BALANCE)
expect(change.toNumber()).toBe(FEE)
expect(balances.free.toNumber()).toBe(BALANCE)
expect(changes.free.toNumber()).toBe(FEE)
done()
}

await listenToBalanceChanges(bob.address, listener)
const currentBalance = await getBalance(bob.address)
expect(currentBalance.toNumber()).toBeTruthy()
expect(currentBalance.toNumber()).toEqual(BALANCE - FEE)
const currentBalance = await getBalances(bob.address)
expect(currentBalance.free.toNumber()).toBeTruthy()
expect(currentBalance.free.toNumber()).toEqual(BALANCE - FEE)
})

it('should make transfer', async () => {
Expand Down
13 changes: 13 additions & 0 deletions packages/types/src/Balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* @packageDocumentation
* @module IBalance
*/

import BN from 'bn.js'

export type Balances = {
free: BN
reserved: BN
miscFrozen: BN
feeFrozen: BN
}
1 change: 1 addition & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * as SubscriptionPromise from './SubscriptionPromise'

export * from './AttestedClaim'
export * from './Attestation'
export * from './Balance'
export * from './CType'
export * from './CTypeMetadata'
export * from './Claim'
Expand Down