-
Notifications
You must be signed in to change notification settings - Fork 17
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
Fix idb account contains asset, fix wasm gas prices #1603
Changes from 4 commits
b04fb2b
fce91c5
52d24b8
7fd2724
2b882ce
af28b4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@penumbra-zone/getters': minor | ||
--- | ||
|
||
add getAmountFromNote |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@penumbra-zone/storage': major | ||
'@penumbra-zone/types': major | ||
--- | ||
|
||
implement accountHasSpendableAsset correctly |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@penumbra-zone/wasm': minor | ||
--- | ||
|
||
do not fail planning upon undefined gas |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import { Note } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/shielded_pool/v1/shielded_pool_pb.js'; | ||
import { createGetter } from './utils/create-getter.js'; | ||
|
||
export const getAmountFromNote = createGetter((note?: Note) => note?.value?.amount); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/a | |
import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb.js'; | ||
import { assetIdFromBaseDenom } from '@penumbra-zone/wasm/asset'; | ||
import { IndexedDbInterface } from '@penumbra-zone/types/indexed-db'; | ||
import { bech32mAssetId } from '@penumbra-zone/bech32m/passet'; | ||
|
||
// Attempts to extract a fee token, with priority in descending order, from the assets used | ||
// in the actions of the transaction planner request (TPR). If no fee token is found from the | ||
|
@@ -117,9 +118,12 @@ export const getAssetFromGasPriceTable = async ( | |
// If a specific asset ID is provided, extracted from the transaction request, check its balance is | ||
// positive and GasPrices for that asset exist. | ||
if (assetId) { | ||
const balance = await indexedDb.hasTokenBalance(request.source, assetId); | ||
const balance = await indexedDb.accountHasSpendableAsset(request.source, assetId); | ||
// This check ensures that the alternative fee token is a valid fee token, for example, TestUSD is not. | ||
const isInGasTable = altGasPrices.find(gp => gp.assetId?.equals(assetId)); | ||
TalDerei marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const isInGasTable = altGasPrices.find( | ||
// TODO: assert this more thoroughly | ||
gp => bech32mAssetId(gp.assetId!) === bech32mAssetId(assetId), | ||
); | ||
if (balance && isInGasTable) { | ||
return assetId; | ||
} | ||
Comment on lines
120
to
129
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this part could be improved but i'm going to bed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this checks if the assetId passed in exists in the GasPrices table. since we're comparing assetId objects directly, why isn't the equality method sufficient here? |
||
|
@@ -129,7 +133,7 @@ export const getAssetFromGasPriceTable = async ( | |
// GasPrices table as a fallback case. | ||
for (const gasPrice of altGasPrices) { | ||
if (gasPrice.assetId) { | ||
const balance = await indexedDb.hasTokenBalance(request.source, gasPrice.assetId); | ||
const balance = await indexedDb.accountHasSpendableAsset(request.source, gasPrice.assetId); | ||
if (balance) { | ||
return gasPrice.assetId; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,22 +7,29 @@ import { TransactionPlannerRequest } from '@buf/penumbra-zone_penumbra.bufbuild_ | |
import { fvkCtx } from '../../ctx/full-viewing-key.js'; | ||
import { extractAltFee } from '../fees.js'; | ||
import { assertTransactionSource } from './assert-transaction-source.js'; | ||
import { AddressIndex } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb.js'; | ||
import { toPlainMessage } from '@bufbuild/protobuf'; | ||
|
||
export const transactionPlanner: Impl['transactionPlanner'] = async (req, ctx) => { | ||
assertValidRequest(req); | ||
|
||
const services = await ctx.values.get(servicesCtx)(); | ||
const { indexedDb } = await services.getWalletServices(); | ||
|
||
// Query IndexedDB directly to check for the existence of staking token | ||
const nativeToken = await indexedDb.hasTokenBalance(req.source!, indexedDb.stakingTokenAssetId); | ||
const noNativeTokenAvailable = !(await indexedDb.accountHasSpendableAsset( | ||
toPlainMessage(req.source ?? new AddressIndex()), | ||
indexedDb.stakingTokenAssetId, | ||
)); | ||
Comment on lines
+19
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. actually shouldn't |
||
|
||
const planningSwapClaim = req.swapClaims.length > 0; | ||
// Check if we should use the native token or extract an alternate gas fee token. | ||
// Special cased for swap claims as gas fee needs to match the claimFee on the corresponding swap. | ||
const needsAltFeeToken = !nativeToken || req.swapClaims.length > 0; | ||
const gasFeeToken = needsAltFeeToken | ||
? await extractAltFee(req, indexedDb) | ||
: indexedDb.stakingTokenAssetId; | ||
console.log('alt fee reasons', noNativeTokenAvailable, planningSwapClaim); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove log |
||
|
||
const gasFeeToken = | ||
noNativeTokenAvailable || planningSwapClaim | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually prefer keeping |
||
? await extractAltFee(req, indexedDb) | ||
: indexedDb.stakingTokenAssetId; | ||
|
||
const fmdParams = await indexedDb.getFmdParams(); | ||
if (!fmdParams) { | ||
|
@@ -37,12 +44,6 @@ export const transactionPlanner: Impl['transactionPlanner'] = async (req, ctx) = | |
throw new ConnectError('ChainId not available', Code.FailedPrecondition); | ||
} | ||
|
||
// Wasm planner needs the presence of gas prices in the db to work | ||
const gasPrices = await indexedDb.getNativeGasPrices(); | ||
if (!gasPrices) { | ||
throw new ConnectError('Gas prices is not available', Code.FailedPrecondition); | ||
} | ||
|
||
const idbConstants = indexedDb.constants(); | ||
const fvk = await ctx.values.get(fvkCtx)(); | ||
const plan = await planTransaction(idbConstants, req, fvk, gasFeeToken); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,8 +63,8 @@ import { | |
DutchAuctionDescription, | ||
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/auction/v1/auction_pb.js'; | ||
import { ChainRegistryClient } from '@penumbra-labs/registry'; | ||
import { PartialMessage } from '@bufbuild/protobuf'; | ||
import { getAmountFromRecord } from '@penumbra-zone/getters/spendable-note-record'; | ||
import { PartialMessage, PlainMessage, toPlainMessage } from '@bufbuild/protobuf'; | ||
import { getAmountFromNote } from '@penumbra-zone/getters/note'; | ||
import { isZero } from '@penumbra-zone/types/amount'; | ||
import { IDB_VERSION } from './config.js'; | ||
|
||
|
@@ -869,20 +869,26 @@ export class IndexedDb implements IndexedDbInterface { | |
}; | ||
} | ||
|
||
async hasTokenBalance(addressIndex: AddressIndex, assetId: AssetId): Promise<boolean> { | ||
const spendableNotes = await this.db.getAllFromIndex( | ||
'SPENDABLE_NOTES', | ||
'assetId', | ||
uint8ArrayToBase64(assetId.inner), | ||
); | ||
async accountHasSpendableAsset( | ||
{ account }: PlainMessage<AddressIndex>, | ||
{ inner }: PlainMessage<AssetId>, | ||
): Promise<boolean> { | ||
// asserts length of assetId.inner | ||
bech32mAssetId({ inner }); | ||
|
||
return spendableNotes.some(note => { | ||
const spendableNote = SpendableNoteRecord.fromJson(note); | ||
return ( | ||
spendableNote.heightSpent === 0n && | ||
!isZero(getAmountFromRecord(spendableNote)) && | ||
spendableNote.addressIndex?.equals(addressIndex) | ||
); | ||
const assetNotes = ( | ||
await this.db.getAllFromIndex('SPENDABLE_NOTES', 'assetId', uint8ArrayToBase64(inner)) | ||
).map(note => SpendableNoteRecord.fromJson(note)); | ||
|
||
const spendableNotes = assetNotes.filter(({ note, addressIndex, heightSpent }) => { | ||
const noteAddressIndex = toPlainMessage(addressIndex ?? new AddressIndex()); | ||
const accountMatch = noteAddressIndex.account === account; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it correct to ignore account randomizer for this comparison? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should filter out the randomizer here? https://github.com/penumbra-zone/web/blob/main/packages/wasm/crate/src/planner.rs#L177-L180. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the randomizer should be ignored |
||
const nonzero = !isZero(getAmountFromNote(note)); | ||
const unspent = heightSpent === 0n; | ||
return accountMatch && nonzero && unspent; | ||
}); | ||
|
||
console.log('accountHasSpendableAsset', account, bech32mAssetId({ inner }), spendableNotes); | ||
return spendableNotes.length > 0; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -205,7 +205,7 @@ pub async fn plan_transaction_inner<Db: Database>( | |
let gas_prices = storage | ||
.get_gas_prices_by_asset_id(&fee_asset_id) | ||
.await? | ||
.ok_or_else(|| anyhow!("GasPrices not available"))?; | ||
.unwrap_or_default(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see comment below addressing #1602 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's a mistake, we shouldn't use default gas prices There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree with this sentiment. For example, this could theoretically allow paying fees with unsupported gas tokens, like |
||
|
||
let fee_tier = match request.fee_mode { | ||
None => FeeTier::default(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
someone should fix these tests
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
af28b4d