Skip to content

Commit

Permalink
Merge pull request #301 from algorandfoundation/fix-box-refs
Browse files Browse the repository at this point in the history
fix: add appid to box resources
  • Loading branch information
neilcampbell authored Oct 28, 2024
2 parents 30c1ff8 + 7742cfb commit 794eba8
Show file tree
Hide file tree
Showing 10 changed files with 116 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await waitFor(() => within(addMethodPanel).getByLabelText(transactionGroupTableLabel))
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Switch to echo_bytes method and save
Expand Down Expand Up @@ -396,7 +396,7 @@ describe('application-method-definitions', () => {
transactionGroupTable = await waitFor(() => within(addMethodPanel).getByLabelText(transactionGroupTableLabel))
firstBodyRow = within(transactionGroupTable).getAllByRole('row')[1]
await user.click(await waitFor(() => within(firstBodyRow).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))

formDialog = component.getByRole('dialog')
amountInput = await within(formDialog).findByLabelText(/Amount/)
Expand Down Expand Up @@ -514,7 +514,7 @@ describe('application-method-definitions', () => {
transactionGroupTable = await waitFor(() => within(addMethodPanel).getByLabelText(transactionGroupTableLabel))
firstBodyRow = within(transactionGroupTable).getAllByRole('row')[2]
await user.click(await waitFor(() => within(firstBodyRow).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))

formDialog = component.getByRole('dialog')
const noteInput = await within(formDialog).findByLabelText(/Note/)
Expand Down Expand Up @@ -631,7 +631,7 @@ describe('application-method-definitions', () => {
transactionGroupTable = await waitFor(() => within(addMethodPanel).getByLabelText(transactionGroupTableLabel))
firstBodyRow = within(transactionGroupTable).getAllByRole('row')[2]
await user.click(await waitFor(() => within(firstBodyRow).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))

formDialog = component.getByRole('dialog')
await selectOption(formDialog.parentElement!, user, /Method/, 'add')
Expand Down Expand Up @@ -763,7 +763,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await waitFor(() => within(echoBytesPanel).getByLabelText(transactionGroupTableLabel))
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Input the new byte array in base64
Expand Down Expand Up @@ -969,7 +969,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await waitFor(() => within(echoArrayPanel).getByLabelText(transactionGroupTableLabel))
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Check the existing values
Expand Down Expand Up @@ -1152,7 +1152,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await waitFor(() => within(echoDynamicArrayPanel).getByLabelText(transactionGroupTableLabel))
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Check the existing values
Expand Down Expand Up @@ -1369,7 +1369,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await within(methodPanel).findByLabelText(transactionGroupTableLabel)
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Verify the existing values
Expand Down Expand Up @@ -1542,7 +1542,7 @@ describe('application-method-definitions', () => {
// Edit the transaction
const transactionGroupTable = await within(methodPanel).findByLabelText(transactionGroupTableLabel)
await user.click(await waitFor(() => within(transactionGroupTable).getByRole('button', { name: transactionActionsLabel })))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }))
await user.click(await component.findByRole('menuitem', { name: 'Edit' }, { timeout: 2_000 }))
formDialog = component.getByRole('dialog')

// Verify the existing values
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import { addressFieldSchema } from '@/features/transaction-wizard/data/common'
import { useCallback, useMemo } from 'react'
import { z } from 'zod'
import { zfd } from 'zod-form-data'
import { ApplicationId } from '../data/types'
import { randomGuid } from '@/utils/random-guid'

export type TransactionResources = {
accounts: Address[]
assets: number[]
applications: number[]
boxes: string[]
boxes: (readonly [ApplicationId, string])[]
}

type Props = {
Expand All @@ -26,27 +28,30 @@ const formSchema = zfd.formData({
accounts: zfd.repeatable(z.array(z.object({ id: z.string(), address: addressFieldSchema })).max(4)),
assets: zfd.repeatable(z.array(z.object({ id: z.string(), assetId: numberSchema(z.number().min(0)) })).max(8)),
applications: zfd.repeatable(z.array(z.object({ id: z.string(), applicationId: numberSchema(z.number().min(0)) })).max(8)),
boxes: zfd.repeatable(z.array(z.object({ id: z.string(), boxName: zfd.text() })).max(8)),
boxes: zfd.repeatable(
z.array(z.object({ id: z.string(), applicationId: numberSchema(z.number().min(0)), boxName: zfd.text(z.string().optional()) })).max(8)
),
})

export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel }: Props) {
const defaultValues = useMemo(() => {
return {
accounts: (resources.accounts ?? []).map((address) => ({
id: address,
id: randomGuid(),
address: address,
})),
assets: (resources.assets ?? []).map((asset) => ({
id: asset.toString(),
id: randomGuid(),
assetId: asset,
})),
applications: (resources.applications ?? []).map((application) => ({
id: application.toString(),
id: randomGuid(),
applicationId: application,
})),
boxes: (resources.boxes ?? []).map((box) => ({
id: box,
boxName: box,
boxes: (resources.boxes ?? []).map(([applicationId, boxName]) => ({
id: randomGuid(),
applicationId,
boxName,
})),
}
}, [resources])
Expand All @@ -56,7 +61,7 @@ export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel
const accounts = data.accounts.map((account) => account.address)
const assets = data.assets.map((asset) => asset.assetId)
const applications = data.applications.map((application) => application.applicationId)
const boxes = data.boxes.map((box) => box.boxName)
const boxes = data.boxes.map((box) => [box.applicationId, box.boxName ?? ''] as const)
if (accounts.length + assets.length + applications.length + boxes.length > 8) {
throw new Error('Total number of references cannot exceed 8')
}
Expand Down Expand Up @@ -93,7 +98,7 @@ export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel
label: `Account ${index + 1}`,
field: `accounts.${index}.address`,
}),
newItem: () => ({ id: new Date().getTime().toString(), address: '' }),
newItem: () => ({ id: randomGuid(), address: '' }),
max: 4,
addButtonLabel: 'Add Account',
noItemsLabel: 'No accounts.',
Expand All @@ -107,7 +112,7 @@ export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel
label: `Asset ${index + 1}`,
field: `assets.${index}.assetId`,
}),
newItem: () => ({ id: new Date().getTime().toString(), assetId: '' as unknown as number }),
newItem: () => ({ id: randomGuid(), assetId: '' as unknown as number }),
max: 8,
addButtonLabel: 'Add Asset',
noItemsLabel: 'No assets.',
Expand All @@ -121,7 +126,7 @@ export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel
label: `Application ${index + 1}`,
field: `applications.${index}.applicationId`,
}),
newItem: () => ({ id: new Date().getTime().toString(), applicationId: '' as unknown as number }),
newItem: () => ({ id: randomGuid(), applicationId: '' as unknown as number }),
max: 8,
addButtonLabel: 'Add Application',
noItemsLabel: 'No Applications.',
Expand All @@ -130,13 +135,26 @@ export function ConfirmTransactionsResourcesForm({ resources, onSubmit, onCancel
{helper.arrayField({
label: 'Boxes',
field: `boxes`,
renderChildField: (_, index) =>
helper.textField({
label: `Box ${index + 1}`,
field: `boxes.${index}.boxName`,
helpText: 'A Base64 encoded box name',
}),
newItem: () => ({ id: new Date().getTime().toString(), boxName: '' }),
deleteButtonClassName: 'mt-12',
renderChildField: (_, index) => (
<div className="flex flex-col gap-1">
<span className="ml-0.5 text-sm font-medium">Box {index + 1}</span>
<div className="flex gap-2">
{helper.textField({
label: 'Application ID',
field: `boxes.${index}.applicationId`,
className: 'w-1/4 self-start',
})}
{helper.textField({
label: 'Name',
field: `boxes.${index}.boxName`,
helpText: 'A Base64 encoded box name',
className: 'w-3/4 self-start',
})}
</div>
</div>
),
newItem: () => ({ id: randomGuid(), boxName: '' }),
max: 8,
addButtonLabel: 'Add Box',
noItemsLabel: 'No Boxes.',
Expand Down
17 changes: 8 additions & 9 deletions src/features/common/data/atom-cache.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { createReadOnlyAtomAndTimestamp, createTimestamp, readOnlyAtomCache, writableAtomCache } from './atom-cache'
import { atom, Atom, Getter, SetStateAction, Setter, WritableAtom } from 'jotai'
import { shortId } from '@/utils/short-id'

const myStore = await vi.hoisted(async () => {
const { createStore } = await import('jotai/index')
Expand All @@ -16,15 +17,13 @@ vi.mock('@/features/common/data/data-store', async () => {
})

describe('atom-cache', () => {
const asShortId = (date: Date) => Number(date).toString(36).toUpperCase()

afterEach(async () => {
vi.resetAllMocks()
})

describe('readonly atom cache', () => {
const atomInitialiser = (_: Getter, __: Setter, date: Date) => {
return asShortId(date)
return shortId(date)
}

it('can add an item', () => {
Expand All @@ -36,7 +35,7 @@ describe('atom-cache', () => {

myStore.set(myThingsAtom, (prev) => {
const next = new Map(prev)
next.set(dateEpoch, createReadOnlyAtomAndTimestamp(asShortId(date)))
next.set(dateEpoch, createReadOnlyAtomAndTimestamp(shortId(date)))
return next
})

Expand All @@ -53,7 +52,7 @@ describe('atom-cache', () => {
const [myThingsAtom, getMyThingAtom] = readOnlyAtomCache(
atomInitialiser,
(date) => Number(date),
new Map<number, readonly [Atom<string>, number]>([[dateEpoch, [atom(() => asShortId(date)), 1723999500000]]])
new Map<number, readonly [Atom<string>, number]>([[dateEpoch, [atom(() => shortId(date)), 1723999500000]]])
)

const myThingAtom = getMyThingAtom(date)
Expand Down Expand Up @@ -85,7 +84,7 @@ describe('atom-cache', () => {

describe('writeable atom cache', () => {
const atomInitialiser = (_: Getter, __: Setter, date: Date) => {
return atom(asShortId(date))
return atom(shortId(date))
}

it('can add an item', () => {
Expand All @@ -100,7 +99,7 @@ describe('atom-cache', () => {

myStore.set(myThingsAtom, (prev) => {
const next = new Map(prev)
next.set(dateEpoch, createWritableAtomAndTimestamp(asShortId(date)))
next.set(dateEpoch, createWritableAtomAndTimestamp(shortId(date)))
return next
})

Expand All @@ -118,7 +117,7 @@ describe('atom-cache', () => {
atomInitialiser,
(date) => Number(date),
new Map<number, readonly [WritableAtom<string, [SetStateAction<string>], void>, number]>([
[dateEpoch, [atom(asShortId(date)), 1723999500000]],
[dateEpoch, [atom(shortId(date)), 1723999500000]],
])
)

Expand All @@ -138,7 +137,7 @@ describe('atom-cache', () => {
atomInitialiser,
(date) => Number(date),
new Map<number, readonly [WritableAtom<string, [SetStateAction<string>], void>, number]>([
[dateEpoch, [atom(asShortId(date)), 1723999500000]],
[dateEpoch, [atom(shortId(date)), 1723999500000]],
])
)

Expand Down
3 changes: 2 additions & 1 deletion src/features/common/data/data-provider.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { settingsStore } from '@/features/settings/data'
import { shortId } from '@/utils/short-id'
import { useAtomValue, useSetAtom } from 'jotai'
import { atomWithRefresh } from 'jotai/utils'

const dataProviderTokenAtom = atomWithRefresh(() => {
return Number(new Date()).toString(36).toUpperCase()
return shortId()
})

export const useDataProviderToken = () => {
Expand Down
5 changes: 4 additions & 1 deletion src/features/forms/components/array-form-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Plus, TrashIcon } from 'lucide-react'
import React, { useCallback } from 'react'
import { FieldArray, FieldArrayWithId, FieldPath, FieldValues, Path, useFieldArray } from 'react-hook-form'
import { FormItemProps } from './form-item'
import { cn } from '@/features/common/utils'

export interface ArrayFormItemProps<TSchema extends Record<string, unknown> = Record<string, unknown>>
extends Omit<FormItemProps<TSchema>, 'children'> {
Expand All @@ -12,6 +13,7 @@ export interface ArrayFormItemProps<TSchema extends Record<string, unknown> = Re
max?: number
addButtonLabel?: string
noItemsLabel?: string
deleteButtonClassName?: string
}

export function ArrayFormItem<TSchema extends Record<string, unknown> = Record<string, unknown>>({
Expand All @@ -22,6 +24,7 @@ export function ArrayFormItem<TSchema extends Record<string, unknown> = Record<s
max,
addButtonLabel = 'Add',
noItemsLabel = 'No items.',
deleteButtonClassName,
}: ArrayFormItemProps<TSchema>) {
const { fields, append, remove } = useFieldArray({
name: field,
Expand Down Expand Up @@ -57,7 +60,7 @@ export function ArrayFormItem<TSchema extends Record<string, unknown> = Record<s
<div className="grow">{renderChildField(childField, index)}</div>
<Button
type="button"
className="mt-6"
className={cn('mt-6', deleteButtonClassName)}
variant="destructive"
size="sm"
onClick={() => remove(index)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,8 +197,8 @@ export function TransactionsBuilder({
accounts: (transactionWithResources.txn.appAccounts ?? []).map((account) => algosdk.encodeAddress(account.publicKey)),
assets: transactionWithResources.txn.appForeignAssets ?? [],
applications: transactionWithResources.txn.appForeignApps ?? [],
boxes: transactionWithResources.txn.boxes?.map((box) => uint8ArrayToBase64(box.name)) ?? [],
}
boxes: transactionWithResources.txn.boxes?.map((box) => [box.appIndex, uint8ArrayToBase64(box.name)] as const) ?? [],
} satisfies TransactionResources
newTransactions = setTransactionResources(newTransactions, transaction.id, resources)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ export const asMethodCallParams = async (transaction: BuildMethodCallTransaction
accountReferences: transaction.accounts ?? [],
appReferences: transaction.foreignApps?.map((app) => BigInt(app)) ?? [],
assetReferences: transaction.foreignAssets?.map((asset) => BigInt(asset)) ?? [],
boxReferences: transaction.boxes ?? [],
boxReferences:
transaction.boxes?.map(([appId, boxName]) => {
return { appId: BigInt(appId), name: Uint8Array.from(Buffer.from(boxName, 'base64')) }
}) ?? [],
note: transaction.note,
...asFee(transaction.fee),
...asValidRounds(transaction.validRounds),
Expand Down Expand Up @@ -151,7 +154,10 @@ export const asAppCallTransactionParams = (transaction: BuildAppCallTransactionR
accountReferences: transaction.accounts ?? [],
appReferences: transaction.foreignApps?.map((app) => BigInt(app)) ?? [],
assetReferences: transaction.foreignAssets?.map((asset) => BigInt(asset)) ?? [],
boxReferences: transaction.boxes ?? [],
boxReferences:
transaction.boxes?.map(([appId, boxName]) => {
return { appId: BigInt(appId), name: Uint8Array.from(Buffer.from(boxName, 'base64')) }
}) ?? [],
note: transaction.note,
...asFee(transaction.fee),
...asValidRounds(transaction.validRounds),
Expand Down
Loading

0 comments on commit 794eba8

Please sign in to comment.