Skip to content

Commit

Permalink
feat: getQueryParams takes shape parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Nov 15, 2024
1 parent 305c98c commit 165b3d7
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 47 deletions.
15 changes: 6 additions & 9 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import { assertAllDefined } from '@agoric/internal';
import { ChainAddressShape } from '@agoric/orchestration';
import { pickFacet } from '@agoric/vat-data';
import { VowShape } from '@agoric/vow';
import { makeError, q } from '@endo/errors';
import { q } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';
import { CctpTxEvidenceShape } from '../typeGuards.js';
import { CctpTxEvidenceShape, QueryParamsShape } from '../typeGuards.js';
import { addressTools } from '../utils/address.js';

const { isGTE } = AmountMath;
Expand Down Expand Up @@ -115,13 +115,10 @@ export const prepareAdvancerKit = (
// TODO poolAccount might be a vow we need to unwrap
const { assetManagerFacet, poolAccount } = this.state;
const { recipientAddress } = evidence.aux;
const { EUD } =
addressTools.getQueryParams(recipientAddress).params;
if (!EUD) {
throw makeError(
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
);
}
const { EUD } = addressTools.getQueryParams(
recipientAddress,
QueryParamsShape,
);

// this will throw if the bech32 prefix is not found, but is handled by the catch
const destination = chainHub.makeChainAddress(EUD);
Expand Down
5 changes: 2 additions & 3 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ export const prepareSettler = (zone, { statusManager }) => {
return;
}

const { params } = addressTools.getQueryParams(tx.receiver);
// TODO - what's the schema address parameter schema for FUSDC?
if (!params?.EUD) {
const { EUD } = addressTools.getQueryParams(tx.receiver);
if (!EUD) {
// only interested in receivers with EUD parameter
return;
}
Expand Down
5 changes: 5 additions & 0 deletions packages/fast-usdc/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ export const PendingTxShape = {
status: M.or(...Object.values(PendingTxStatus)),
};
harden(PendingTxShape);

export const QueryParamsShape = {
EUD: M.string(),
};
harden(QueryParamsShape);
46 changes: 27 additions & 19 deletions packages/fast-usdc/src/utils/address.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
import { makeError, q } from '@endo/errors';
import { M, mustMatch } from '@endo/patterns';

/**
* @import {Pattern} from '@endo/patterns';
*/

/**
* Default pattern matcher for `getQueryParams`.
* Does not assert keys exist, but ensures existing keys are strings.
*/
const QueryParamsShape = M.splitRecord(
{},
{},
M.recordOf(M.string(), M.string()),
);

/**
* Very minimal 'URL query string'-like parser that handles:
Expand All @@ -22,42 +37,35 @@ export const addressTools = {
*/
hasQueryParams: address => {
try {
const { params } = addressTools.getQueryParams(address);
const params = addressTools.getQueryParams(address);
return Object.keys(params).length > 0;
} catch {
return false;
}
},
/**
* @param {string} address
* @returns {{ address: string, params: Record<string, string>}}
* @param {Pattern} [shape]
* @returns {Record<string, string>}
* @throws {Error} if the address cannot be parsed or params do not match `shape`
*/
getQueryParams: address => {
getQueryParams: (address, shape = QueryParamsShape) => {
const parts = address.split('?');
if (parts.length === 0 || parts.length > 2) {
throw makeError(
`Invalid input. Must be of the form 'address?params': ${q(address)}`,
);
}
const result = {
address: parts[0],
params: {},
};

// no parameters, return early
if (parts.length === 1) {
return result;
if (parts.length !== 2) {
throw makeError(`Unable to parse query params: ${q(address)}`);
}

/** @type {Record<string, string>} */
const result = {};
const paramPairs = parts[1].split('&');
for (const pair of paramPairs) {
const [key, value] = pair.split('=');
if (!key || !value) {
throw makeError(`Invalid parameter format in pair: ${q(pair)}`);
}
result.params[key] = value;
result[key] = value;
}

harden(result);
mustMatch(result, shape);
return result;
},
};
2 changes: 1 addition & 1 deletion packages/fast-usdc/test/exos/advancer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ test('updates status to OBSERVED if pre-condition checks fail', async t => {

t.deepEqual(inspectLogs(0), [
'Advancer error:',
'"[Error: recipientAddress does not contain EUD param: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"',
'"[Error: Unable to parse query params: \\"agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek\\"]"',
]);
});

Expand Down
39 changes: 24 additions & 15 deletions packages/fast-usdc/test/utils/address.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { M } from '@endo/patterns';

import { addressTools } from '../../src/utils/address.js';
import { QueryParamsShape } from '../../src/typeGuards.js';

const FIXTURES = {
AGORIC_WITH_DYDX:
Expand Down Expand Up @@ -32,31 +34,38 @@ test('hasQueryParams: returns false for invalid parameter formats', t => {

// getQueryParams tests - positive cases
test('getQueryParams: correctly parses address with single EUD parameter', t => {
const result = addressTools.getQueryParams(FIXTURES.AGORIC_WITH_DYDX);
const result = addressTools.getQueryParams(
FIXTURES.AGORIC_WITH_DYDX,
QueryParamsShape,
);
t.deepEqual(result, {
address: 'agoric1bech32addr',
params: {
EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
EUD: 'dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
});
});

test('getQueryParams: correctly parses address with multiple parameters', t => {
const pattern = harden({ EUD: M.string(), CID: M.string() });
const result = addressTools.getQueryParams(
FIXTURES.AGORIC_WITH_MULTIPLE,
pattern,
);
t.deepEqual(result, {
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
CID: 'dydx-mainnet-1',
});
});

test('getQueryParams: returns all parameters when no shape is provided', t => {
const result = addressTools.getQueryParams(FIXTURES.AGORIC_WITH_MULTIPLE);
t.deepEqual(result, {
address: 'agoric1bech32addr',
params: {
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
CID: 'dydx-mainnet-1',
},
EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
CID: 'dydx-mainnet-1',
});
});

test('getQueryParams: correctly handles address with no parameters', t => {
const result = addressTools.getQueryParams(FIXTURES.AGORIC_NO_PARAMS);
t.deepEqual(result, {
address: 'agoric1bech32addr',
params: {},
t.throws(() => addressTools.getQueryParams(FIXTURES.AGORIC_NO_PARAMS), {
message: 'Unable to parse query params: "agoric1bech32addr"',
});
});

Expand All @@ -66,7 +75,7 @@ test('getQueryParams: throws error for multiple question marks', t => {
() => addressTools.getQueryParams(FIXTURES.INVALID_MULTIPLE_QUESTION),
{
message:
'Invalid input. Must be of the form \'address?params\': "agoric1bech32addr?param1=value1?param2=value2"',
'Unable to parse query params: "agoric1bech32addr?param1=value1?param2=value2"',
},
);
});
Expand Down

0 comments on commit 165b3d7

Please sign in to comment.