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

Implement withdrawable assets #358

Merged
merged 15 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/backend/src/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,7 @@ export class Application {
userTransactionRepository,
userRegistrationEventRepository,
assetRepository,
withdrawableAssetRepository,
config.starkex.tradingMode,
config.starkex.contracts.perpetual,
collateralAsset
Expand Down
13 changes: 12 additions & 1 deletion packages/backend/src/api/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
UserTransactionRepository,
} from '../../peripherals/database/transactions/UserTransactionRepository'
import { UserRegistrationEventRepository } from '../../peripherals/database/UserRegistrationEventRepository'
import { WithdrawableAssetRepository } from '../../peripherals/database/WithdrawableAssetRepository'
import { ControllerResult } from './ControllerResult'
import { sentTransactionToEntry } from './sentTransactionToEntry'
import { userTransactionToEntry } from './userTransactionToEntry'
Expand All @@ -45,6 +46,7 @@ export class UserController {
private readonly userTransactionRepository: UserTransactionRepository,
private readonly userRegistrationEventRepository: UserRegistrationEventRepository,
private readonly assetRepository: AssetRepository,
private readonly withdrawableAssetRepository: WithdrawableAssetRepository,
private readonly tradingMode: TradingMode,
private readonly exchangeAddress: EthereumAddress,
private readonly collateralAsset?: CollateralAsset
Expand All @@ -64,6 +66,7 @@ export class UserController {
sentTransactions,
userTransactions,
userTransactionsCount,
withdrawableAssets,
] = await Promise.all([
this.userService.getUserDetails(givenUser),
this.userRegistrationEventRepository.findByStarkKey(starkKey),
Expand All @@ -86,13 +89,15 @@ export class UserController {
limit: 10,
}),
this.userTransactionRepository.getCountByStarkKey(starkKey),
this.withdrawableAssetRepository.getAssetBalancesByStarkKey(starkKey),
])

const assetDetailsMap = await this.assetDetailsService.getAssetDetailsMap({
userAssets: userAssets,
assetHistory: history,
sentTransactions,
userTransactions,
withdrawableAssets,
})

const assetEntries = userAssets.map((a) =>
Expand Down Expand Up @@ -122,8 +127,14 @@ export class UserController {
tradingMode: this.tradingMode,
starkKey,
ethereumAddress: registeredUser?.ethAddress ?? EthereumAddress.ZERO,
withdrawableAssets: withdrawableAssets.map((asset) => ({
asset: {
hashOrId: asset.assetHash,
details: assetDetailsMap?.getByAssetHash(asset.assetHash),
},
amount: asset.balanceDelta,
})),
exchangeAddress: this.exchangeAddress,
withdrawableAssets: [],
offersToAccept: [],
assets: assetEntries,
totalAssets,
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/core/AssetDetailsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class AssetDetailsService {
assetHistory?: PreprocessedAssetHistoryRecord[]
sentTransactions?: SentTransactionRecord[]
userTransactions?: UserTransactionRecord[]
withdrawableAssets?: { assetHash: AssetHash; balanceDelta: bigint }[]
}): Promise<AssetDetailsMap | undefined> {
if (this.tradingMode !== 'spot') {
return undefined
Expand All @@ -33,6 +34,7 @@ export class AssetDetailsService {
...(records.userTransactions?.map((tx) =>
this.getUserTransactionAssetHash(tx)
) ?? []),
...(records.withdrawableAssets?.map((a) => a.assetHash) ?? []),
].filter((hash): hash is AssetHash => AssetHash.check(hash))

const assetTypeAndTokenIds: { assetType: AssetHash; tokenId: bigint }[] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,99 @@ describe(WithdrawableAssetRepository.name, () => {
expect(await repository.findById(id2)).toEqual(undefined)
})
})
describe(
WithdrawableAssetRepository.prototype.getAssetBalancesByStarkKey.name,
Wendrowiec13 marked this conversation as resolved.
Show resolved Hide resolved
() => {
it('returns all records for the given stark key', async () => {
const starkKey1 = StarkKey.fake()
const starkKey2 = StarkKey.fake()

const firstAsset = AssetHash.fake()
const secondAsset = AssetHash.fake()
const thirdAsset = AssetHash.fake()

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey1,
assetType: firstAsset,
}),
})

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey2,
assetType: secondAsset,
}),
})

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey1,
assetType: firstAsset,
}),
})

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey2,
assetType: secondAsset,
}),
})

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey1,
assetType: secondAsset,
}),
})

await repository.add({
transactionHash: Hash256.fake(),
blockNumber: 123,
timestamp: Timestamp(123000),
data: fakeWithdrawalAllowedData({
starkKey: starkKey1,
assetType: thirdAsset,
quantizedAmount: 0n,
}),
})

const records1 = await repository.getAssetBalancesByStarkKey(starkKey1)
const records2 = await repository.getAssetBalancesByStarkKey(starkKey2)

expect(records1).toEqualUnsorted([
{
assetHash: secondAsset,
balanceDelta: 123n,
},
{
assetHash: firstAsset,
balanceDelta: 246n,
},
])

expect(records2).toEqual([
{
assetHash: secondAsset,
balanceDelta: 246n,
},
])
})
}
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export class WithdrawableAssetRepository extends BaseRepository {
/* eslint-disable @typescript-eslint/unbound-method */

this.add = this.wrapAdd(this.add)
this.getAssetBalancesByStarkKey = this.wrapGet(
this.getAssetBalancesByStarkKey
)
this.findById = this.wrapFind(this.findById)
this.deleteAfter = this.wrapDelete(this.deleteAfter)
this.deleteAll = this.wrapDelete(this.deleteAll)
Expand Down Expand Up @@ -63,6 +66,24 @@ export class WithdrawableAssetRepository extends BaseRepository {
return results[0]!.id
}

async getAssetBalancesByStarkKey(
Wendrowiec13 marked this conversation as resolved.
Show resolved Hide resolved
starkKey: StarkKey
): Promise<{ assetHash: AssetHash; balanceDelta: bigint }[]> {
Wendrowiec13 marked this conversation as resolved.
Show resolved Hide resolved
const knex = await this.knex()
const results: { assetHash: string; balanceDelta: string }[] = await knex(
'withdrawable_assets'
)
.select('asset_hash as assetHash')
.where('stark_key', starkKey.toString())
.groupBy('asset_hash')
.having(knex.raw('sum(balance_delta) > 0'))
.sum('balance_delta as balanceDelta')
Wendrowiec13 marked this conversation as resolved.
Show resolved Hide resolved
return results.map((x) => ({
assetHash: AssetHash(x.assetHash),
balanceDelta: BigInt(x.balanceDelta),
Wendrowiec13 marked this conversation as resolved.
Show resolved Hide resolved
}))
}

async findById(id: number): Promise<WithdrawableAssetRecord | undefined> {
const knex = await this.knex()
const result = await knex('withdrawable_assets').where('id', id).first()
Expand Down