From 6bbd799e32e30f6c0b4776564dc6ec5a326fb6a5 Mon Sep 17 00:00:00 2001 From: Michael FIG Date: Tue, 10 Dec 2024 17:09:18 -0600 Subject: [PATCH] fixup! feat(vats): first cut of Address Hooks in JS --- packages/cosmic-proto/src/address-hooks.js | 38 ++++++++++++++----- .../cosmic-proto/test/address-hooks.test.js | 30 ++++++++++++++- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/cosmic-proto/src/address-hooks.js b/packages/cosmic-proto/src/address-hooks.js index 86c4d130f3a..073f0783c71 100644 --- a/packages/cosmic-proto/src/address-hooks.js +++ b/packages/cosmic-proto/src/address-hooks.js @@ -47,16 +47,23 @@ const ADDRESS_HOOK_MAGIC = new Uint8Array([ 0x70 | ADDRESS_HOOK_VERSION, ]); -// The default maximum number of characters in a bech32-encoded hooked address. +/** + * The default maximum number of characters in a bech32-encoded hooked address. + */ export const DEFAULT_HOOKED_ADDRESS_CHAR_LIMIT = 1024; /** - * @typedef {Record} HookQuery + * @typedef {Record} HookQuery A + * record of query keys mapped to query values. `null` values denote valueless + * keys. Array values denote multiple occurrences of a key: + * + * { key: null } // '?key' + * { key: 'value' } // '?key=value' + * { key: ['value1', 'value2', 'value3'] } // '?key=value1&key=value2&key=value3' + * { key: ['value1', null, 'value3'] } // '?key=value1&key&key=value3' */ export const BASE_ADDRESS_LENGTH_BYTES = 2; -export const MAX_BASE_ADDRESS_LENGTH = - (1 << (BASE_ADDRESS_LENGTH_BYTES * 8 - 1)) + 1; /** * @param {string} specimen @@ -119,22 +126,33 @@ export const joinHookedAddress = ( const { prefix, bytes } = decodeBech32(baseAddress, charLimit); const baseAddressLength = bytes.length; - if (baseAddressLength > MAX_BASE_ADDRESS_LENGTH) { + const b = baseAddressLength; + const hd = hookData.length; + + const maxBaseAddressLength = 2 ** (BASE_ADDRESS_LENGTH_BYTES * 8); + if (b >= maxBaseAddressLength) { throw RangeError( - `Base address length 0x${baseAddressLength.toString(16)} exceeds maximum 0x${MAX_BASE_ADDRESS_LENGTH.toString(16)}`, + `Base address length 0x${b.toString(16)} exceeds maximum 0x${maxBaseAddressLength.toString(16)}`, ); } - const b = baseAddressLength; + + if (!Number.isSafeInteger(hd) || hd < 0) { + throw RangeError(`Hook data length ${hd} is not a non-negative integer`); + } const magicLength = ADDRESS_HOOK_MAGIC.length; const hookBuf = new Uint8Array( - magicLength + bytes.length + hookData.length + BASE_ADDRESS_LENGTH_BYTES, + magicLength + b + hd + BASE_ADDRESS_LENGTH_BYTES, ); - hookBuf.set(ADDRESS_HOOK_MAGIC); + hookBuf.set(ADDRESS_HOOK_MAGIC, 0); hookBuf.set(bytes, magicLength); hookBuf.set(hookData, magicLength + b); - // Append the big-endian address length. + // Append the address length bytes, since we've already ensured these do not + // exceed maxBaseAddressLength above. These are big-endian because the length + // is at the end of the payload, so if we want to support more bytes for the + // length, we just need encroach further into the payload. We can do that + // without changing the meaning of the bytes at the end of existing payloads. let len = b; for (let i = 0; i < BASE_ADDRESS_LENGTH_BYTES; i += 1) { hookBuf[hookBuf.length - 1 - i] = len & 0xff; diff --git a/packages/cosmic-proto/test/address-hooks.test.js b/packages/cosmic-proto/test/address-hooks.test.js index 4dcea332637..31ce2334f10 100644 --- a/packages/cosmic-proto/test/address-hooks.test.js +++ b/packages/cosmic-proto/test/address-hooks.test.js @@ -3,6 +3,10 @@ import rawTest from '@endo/ses-ava/prepare-endo.js'; import bundleSourceAmbient from '@endo/bundle-source'; import { importBundle } from '@endo/import-bundle'; +import { createRequire } from 'node:module'; + +const require = createRequire(import.meta.url); + /** * @type {import('ava').TestFn<{ * addressHooks: import('../src/address-hooks.js'); @@ -11,9 +15,11 @@ import { importBundle } from '@endo/import-bundle'; const test = rawTest; const makeTestContext = async () => { + // Do all this work so that we test bundling and evaluation of the module in a + // fresh compartment. const bundleSource = bundleSourceAmbient; const loadBundle = async specifier => { - const modulePath = new URL(specifier, import.meta.url).pathname; + const modulePath = require.resolve(specifier); const bundle = await bundleSource(modulePath); return bundle; }; @@ -212,3 +218,25 @@ test( }, 'cosmos10rchqqplv3ehg0tpyej8xapavgnxgum5843jvetkv4e8jargd9hxwqqp4vx73n', ); + +test( + 'slideshow hook', + addressHookMacro, + 'agoric1qqp0e5ys', + { + stake: 'TIA', + strat: 'compound', + holder: 'agoric1adjbkubiukd', + }, + 'agoric10rchqqpldphkcer9wg7kzem0wf5kxvtpv34xy6m4vf5h26myyeehgcttv574gj2pyeehgunpws7kxmmdwphh2mnyqqqsc2lz8v', +); + +test( + 'Fast USDC hook', + addressHookMacro, + 'agoric16kv2g7snfc4q24vg3pjdlnnqgngtjpwtetd2h689nz09lcklvh5s8u37ek', + { + EUD: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men', + }, + 'agoric10rchp4vc53apxn32q42c3zryml8xq3xshyzuhjk6405wtxy7tl3d7e0f8az423padaek6me38qekget2vdhx66mtvy6kg7nrw5uhsaekd4uhwufswqex6dtsv44hxv3cd4jkuqpqvduyhf', +);