Skip to content

Commit

Permalink
chore(a3p): create helper functions for Vaults tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorge-Lopes committed Oct 29, 2024
1 parent ccc0b84 commit 1d60661
Show file tree
Hide file tree
Showing 4 changed files with 445 additions and 1 deletion.
62 changes: 62 additions & 0 deletions a3p-integration/proposals/z:acceptance/test-lib/price-feed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* eslint-env node */

import {
agoric,
getContractInfo,
pushPrices,
getPriceQuote,
} from '@agoric/synthetic-chain';
import { retryUntilCondition } from './sync-tools.js';

export const scale6 = x => BigInt(x * 1_000_000);

/**
*
* @param {Map<any, any>} oraclesByBrand
* @param {string} brand
* @param {number} price
* @param {number} roundId
* @returns {Promise<void>}
*/
export const verifyPushedPrice = async (
oraclesByBrand,
brand,
price,
roundId,
) => {
const pushPriceRetryOpts = {
maxRetries: 5, // arbitrary
retryIntervalMs: 5000, // in ms
};

await pushPrices(price, brand, oraclesByBrand, roundId);
console.log(`Pushing price ${price} for ${brand}`);

await retryUntilCondition(
() => getPriceQuote(brand),
res => res === `+${scale6(price).toString()}`,
'price not pushed yet',
{
log: console.log,
setTimeout: global.setTimeout,
...pushPriceRetryOpts,
},
);
console.log(`Price ${price} pushed for ${brand}`);
};

/**
*
* @param {string} brand
* @returns {Promise<number>}
*/
export const getPriceFeedRoundId = async brand => {
const latestRoundPath = `published.priceFeed.${brand}-USD_price_feed.latestRound`;
const latestRound = await getContractInfo(latestRoundPath, {
agoric,
prefix: '',
});

console.log('latestRound: ', latestRound);
return Number(latestRound.roundId);
};
128 changes: 128 additions & 0 deletions a3p-integration/proposals/z:acceptance/test-lib/ratio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { AmountMath } from '@agoric/ertp';
import { q, Fail } from '@endo/errors';

/**
* Is `allegedNum` a number in the [contiguous range of exactly and
* unambiguously
* representable](https://esdiscuss.org/topic/more-numeric-constants-please-especially-epsilon#content-14)
* natural numbers (non-negative integers)?
*
* To qualify `allegedNum` must either be a
* non-negative `bigint`, or a non-negative `number` representing an integer
* within range of [integers safely representable in
* floating point](https://tc39.es/ecma262/#sec-number.issafeinteger).
*
* @param {unknown} allegedNum
* @returns {boolean}
*/
function isNat(allegedNum) {
if (typeof allegedNum === 'bigint') {
return allegedNum >= 0;
}
if (typeof allegedNum !== 'number') {
return false;
}

return Number.isSafeInteger(allegedNum) && allegedNum >= 0;
}

/**
* If `allegedNumber` passes the `isNat` test, then return it as a bigint.
* Otherwise throw an appropriate error.
*
* If `allegedNum` is neither a bigint nor a number, `Nat` throws a `TypeError`.
* Otherwise, if it is not a [safely
* representable](https://esdiscuss.org/topic/more-numeric-constants-please-especially-epsilon#content-14)
* non-negative integer, `Nat` throws a `RangeError`.
* Otherwise, it is converted to a bigint if necessary and returned.
*
* @param {unknown} allegedNum
* @returns {bigint}
*/
function Nat(allegedNum) {
if (typeof allegedNum === 'bigint') {
if (allegedNum < 0) {
throw RangeError(`${allegedNum} is negative`);
}
return allegedNum;
}

if (typeof allegedNum === 'number') {
if (!Number.isSafeInteger(allegedNum)) {
throw RangeError(`${allegedNum} is not a safe integer`);
}
if (allegedNum < 0) {
throw RangeError(`${allegedNum} is negative`);
}
return BigInt(allegedNum);
}

throw TypeError(
`${allegedNum} is a ${typeof allegedNum} but must be a bigint or a number`,
);
}

/**
* These operations should be used for calculations with the values of
* basic fungible tokens.
*
* natSafeMath is designed to be used directly, and so it needs to
* validate the inputs, as well as the outputs when necessary.
*/
export const natSafeMath = harden({
// BigInts don't observably overflow
add: (x, y) => Nat(x) + Nat(y),
subtract: (x, y) => Nat(Nat(x) - Nat(y)),
multiply: (x, y) => Nat(x) * Nat(y),
floorDivide: (x, y) => Nat(x) / Nat(y),
ceilDivide: (x, y) => {
y = Nat(y);
return Nat(Nat(x) + y - 1n) / y;
},
});

const ratioPropertyNames = ['numerator', 'denominator'];

export const assertIsRatio = ratio => {
const keys = Object.keys(ratio);
keys.length === 2 || Fail`Ratio ${ratio} must be a record with 2 fields.`;
for (const name of keys) {
ratioPropertyNames.includes(name) ||
Fail`Parameter must be a Ratio record, but ${ratio} has ${q(name)}`;
}
const numeratorValue = ratio.numerator.value;
const denominatorValue = ratio.denominator.value;
isNat(numeratorValue) ||
Fail`The numerator value must be a NatValue, not ${numeratorValue}`;
isNat(denominatorValue) ||
Fail`The denominator value must be a NatValue, not ${denominatorValue}`;
};

/**
* @param {import('@agoric/ertp/src/types.js').Amount<'nat'>} amount
* @param {*} ratio
* @param {*} divideOp
* @returns {import('@agoric/ertp/src/types.js').Amount<'nat'>}
*/
const multiplyHelper = (amount, ratio, divideOp) => {
AmountMath.coerce(amount.brand, amount);
assertIsRatio(ratio);
amount.brand === ratio.denominator.brand ||
Fail`amount's brand ${q(amount.brand)} must match ratio's denominator ${q(
ratio.denominator.brand,
)}`;

return /** @type {import('@agoric/ertp/src/types.js').Amount<'nat'>} */ (
AmountMath.make(
ratio.numerator.brand,
divideOp(
natSafeMath.multiply(amount.value, ratio.numerator.value),
ratio.denominator.value,
),
)
);
};

export const ceilMultiplyBy = (amount, ratio) => {
return multiplyHelper(amount, ratio, natSafeMath.ceilDivide);
};
23 changes: 22 additions & 1 deletion a3p-integration/proposals/z:acceptance/test-lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { makeAgd, agops } from '@agoric/synthetic-chain';
import '@endo/init';
import { makeAgd, agops, agoric } from '@agoric/synthetic-chain';
import { execFileSync } from 'node:child_process';
import { readFile, writeFile } from 'node:fs/promises';
import { boardSlottingMarshaller, makeFromBoard } from './rpc.js';

/**
* @param {string} fileName base file name without .tjs extension
Expand Down Expand Up @@ -66,3 +68,22 @@ export const makeTimerUtils = ({ setTimeout }) => {
waitUntil,
};
};

const fromBoard = makeFromBoard();
const marshaller = boardSlottingMarshaller(fromBoard.convertSlotToVal);

export const getAgoricNamesBrands = async () => {
const brands = await agoric
.follow('-lF', ':published.agoricNames.brand', '-o', 'text')
.then(res => Object.fromEntries(marshaller.fromCapData(JSON.parse(res))));

return brands;
};

export const getAgoricNamesInstances = async () => {
const instances = await agoric
.follow('-lF', ':published.agoricNames.instance', '-o', 'text')
.then(res => Object.fromEntries(marshaller.fromCapData(JSON.parse(res))));

return instances;
};
Loading

0 comments on commit 1d60661

Please sign in to comment.