From 93cf83efa86de0bc281aa171e3100b668fd03746 Mon Sep 17 00:00:00 2001 From: anilhelvaci Date: Wed, 8 May 2024 15:21:53 +0300 Subject: [PATCH] chore(default-params): Add new CM with new default `LiquidationMargin` value TODO: Clear core-eval-support.js Refs: #12 --- .../b:default-params/add-collateral-core.js | 115 ++++ proposals/b:default-params/addAssetToVault.js | 338 +++++++++++ .../b:default-params/core-eval-support.js | 529 ++++++++++++++++++ proposals/b:default-params/package.json | 1 + .../b:default-params/test-defaultParams.js | 73 ++- proposals/b:default-params/yarn.lock | 89 +++ 6 files changed, 1140 insertions(+), 5 deletions(-) create mode 100644 proposals/b:default-params/add-collateral-core.js create mode 100644 proposals/b:default-params/addAssetToVault.js create mode 100644 proposals/b:default-params/core-eval-support.js diff --git a/proposals/b:default-params/add-collateral-core.js b/proposals/b:default-params/add-collateral-core.js new file mode 100644 index 00000000..4a82e375 --- /dev/null +++ b/proposals/b:default-params/add-collateral-core.js @@ -0,0 +1,115 @@ +/* global process */ +import { makeHelpers } from '@agoric/deploy-script-support'; + +import { getManifestForAddAssetToVault } from '../src/proposals/addAssetToVault.js'; +import { getManifestForPsm } from '../src/proposals/startPSM.js'; +import { makeInstallCache } from '../src/proposals/utils.js'; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const defaultProposalBuilder = async ( + { publishRef, install: install0, wrapInstall }, + { + debtLimitValue = undefined, + interestRateValue = undefined, + liquidationMarginValue = undefined, + interchainAssetOptions = /** @type {object} */ ({}), + } = {}, + { env = process.env } = {}, +) => { + /** @type {import('../src/proposals/addAssetToVault.js').InterchainAssetOptions} */ + const { + issuerBoardId = env.INTERCHAIN_ISSUER_BOARD_ID, + denom = env.INTERCHAIN_DENOM, + oracleBrand = 'ATOM', + decimalPlaces = 6, + keyword = 'ATOM', + proposedName = oracleBrand, + initialPrice = undefined, + } = interchainAssetOptions; + + if (!denom) { + assert(issuerBoardId, 'INTERCHAIN_ISSUER_BOARD_ID is required'); + } + + const install = wrapInstall ? wrapInstall(install0) : install0; + + return harden({ + sourceSpec: '../src/proposals/addAssetToVault.js', + getManifestCall: [ + getManifestForAddAssetToVault.name, + { + debtLimitValue: debtLimitValue && BigInt(debtLimitValue), + interestRateValue: interestRateValue && BigInt(interestRateValue), + liquidationMarginValue: liquidationMarginValue && BigInt(liquidationMarginValue), + interchainAssetOptions: { + denom, + issuerBoardId, + decimalPlaces, + initialPrice, + keyword, + proposedName, + oracleBrand, + }, + scaledPriceAuthorityRef: publishRef( + install( + '@agoric/zoe/src/contracts/scaledPriceAuthority.js', + '../bundles/bundle-scaledPriceAuthority.js', + { persist: true }, + ), + ), + }, + ], + }); +}; + +/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */ +export const psmProposalBuilder = async ( + { publishRef, install: install0, wrapInstall }, + { anchorOptions = /** @type {object} */ ({}) } = {}, + { env = process.env } = {}, +) => { + const { denom = env.ANCHOR_DENOM, decimalPlaces = 6 } = anchorOptions; + + assert(denom, 'ANCHOR_DENOM is required'); + + const install = wrapInstall ? wrapInstall(install0) : install0; + + return harden({ + sourceSpec: '../src/proposals/startPSM.js', + getManifestCall: [ + getManifestForPsm.name, + { + anchorOptions: { + ...anchorOptions, + denom, + decimalPlaces, + }, + installKeys: { + psm: publishRef( + install('../src/psm/psm.js', '../bundles/bundle-psm.js'), + ), + mintHolder: publishRef( + install( + '@agoric/vats/src/mintHolder.js', + '../../vats/bundles/bundle-mintHolder.js', + ), + ), + }, + }, + ], + }); +}; + +export default async (homeP, endowments) => { + const { writeCoreProposal } = await makeHelpers(homeP, endowments); + + const tool = await makeInstallCache(homeP, { + loadBundle: spec => import(spec), + }); + + await writeCoreProposal('gov-add-collateral', defaultProposalBuilder); + await writeCoreProposal('gov-start-psm', opts => + // @ts-expect-error XXX makeInstallCache types + psmProposalBuilder({ ...opts, wrapInstall: tool.wrapInstall }), + ); +}; diff --git a/proposals/b:default-params/addAssetToVault.js b/proposals/b:default-params/addAssetToVault.js new file mode 100644 index 00000000..4fff5cff --- /dev/null +++ b/proposals/b:default-params/addAssetToVault.js @@ -0,0 +1,338 @@ +// @jessie-check + +import { AmountMath, AssetKind } from '@agoric/ertp'; +import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js'; +import { deeplyFulfilledObject } from '@agoric/internal'; +import { Stable } from '@agoric/vats/src/tokens.js'; +import { E } from '@endo/far'; +import { parseRatio } from '@agoric/zoe/src/contractSupport/ratio.js'; +import { reserveThenGetNames } from './utils.js'; + +export * from './startPSM.js'; + +/** + * @typedef {object} InterchainAssetOptions + * @property {string} [issuerBoardId] + * @property {string} [denom] + * @property {number} [decimalPlaces] + * @property {string} [proposedName] + * @property {string} keyword + * @property {string} oracleBrand + * @property {number} [initialPrice] + */ + +/** + * @param {EconomyBootstrapPowers} powers + * @param {object} config + * @param {object} config.options + * @param {InterchainAssetOptions} config.options.interchainAssetOptions + */ +export const publishInterchainAssetFromBoardId = async ( + { consume: { board, agoricNamesAdmin } }, + { options: { interchainAssetOptions } }, +) => { + const { issuerBoardId, keyword } = interchainAssetOptions; + // Incompatible with denom. + assert.equal(interchainAssetOptions.denom, undefined); + assert.typeof(issuerBoardId, 'string'); + assert.typeof(keyword, 'string'); + + const issuer = await E(board).getValue(issuerBoardId); + const brand = await E(issuer).getBrand(); + + return Promise.all([ + E(E(agoricNamesAdmin).lookupAdmin('issuer')).update(keyword, issuer), + E(E(agoricNamesAdmin).lookupAdmin('brand')).update(keyword, brand), + ]); +}; + +/** + * @param {EconomyBootstrapPowers} powers + * @param {object} config + * @param {object} config.options + * @param {InterchainAssetOptions} config.options.interchainAssetOptions + */ +export const publishInterchainAssetFromBank = async ( + { + consume: { bankManager, agoricNamesAdmin, reserveKit, startUpgradable }, + installation: { + consume: { mintHolder }, + }, + }, + { options: { interchainAssetOptions } }, +) => { + const { denom, decimalPlaces, proposedName, keyword } = + interchainAssetOptions; + + // Incompatible with issuerBoardId. + assert.equal(interchainAssetOptions.issuerBoardId, undefined); + assert.typeof(denom, 'string'); + assert.typeof(keyword, 'string'); + assert.typeof(decimalPlaces, 'number'); + assert.typeof(proposedName, 'string'); + + const terms = { + keyword, + assetKind: AssetKind.NAT, + displayInfo: { + decimalPlaces, + assetKind: AssetKind.NAT, + }, + }; + + const { creatorFacet: mint, publicFacet: issuer } = await E(startUpgradable)({ + installation: mintHolder, + label: keyword, + privateArgs: undefined, + terms, + }); + + const brand = await E(issuer).getBrand(); + const kit = { mint, issuer, brand }; + + await E(E.get(reserveKit).creatorFacet).addIssuer(issuer, keyword); + + await Promise.all([ + E(E(agoricNamesAdmin).lookupAdmin('issuer')).update(keyword, issuer), + E(E(agoricNamesAdmin).lookupAdmin('brand')).update(keyword, brand), + E(bankManager).addAsset(denom, keyword, proposedName, kit), + ]); +}; + +/** + * @param {BootstrapPowers} powers + * @param {object} config + * @param {object} config.options + * @param {InterchainAssetOptions} config.options.interchainAssetOptions + */ +export const registerScaledPriceAuthority = async ( + { + consume: { + agoricNamesAdmin, + startUpgradable, + priceAuthorityAdmin, + priceAuthority, + }, + }, + { options: { interchainAssetOptions } }, +) => { + const { + keyword, + oracleBrand, + initialPrice: initialPriceRaw, + } = interchainAssetOptions; + assert.typeof(keyword, 'string'); + assert.typeof(oracleBrand, 'string'); + + const [ + sourcePriceAuthority, + [interchainBrand, stableBrand], + [interchainOracleBrand, usdBrand], + [scaledPriceAuthority], + ] = await Promise.all([ + priceAuthority, + reserveThenGetNames(E(agoricNamesAdmin).lookupAdmin('brand'), [ + keyword, + 'IST', + ]), + reserveThenGetNames(E(agoricNamesAdmin).lookupAdmin('oracleBrand'), [ + oracleBrand, + 'USD', + ]), + reserveThenGetNames(E(agoricNamesAdmin).lookupAdmin('installation'), [ + 'scaledPriceAuthority', + ]), + ]); + + // We need "unit amounts" of each brand in order to get the ratios right. You + // can ignore decimalPlaces when adding and subtracting a brand with itself, + // but not when creating ratios. + const getDecimalP = async brand => { + const displayInfo = E(brand).getDisplayInfo(); + return E.get(displayInfo).decimalPlaces; + }; + const [ + decimalPlacesInterchainOracle = 0, + decimalPlacesInterchain = 0, + decimalPlacesUsd = 0, + decimalPlacesRun = 0, + ] = await Promise.all([ + getDecimalP(interchainOracleBrand), + getDecimalP(interchainBrand), + getDecimalP(usdBrand), + getDecimalP(stableBrand), + ]); + + const scaleIn = makeRatio( + 10n ** BigInt(decimalPlacesInterchainOracle), + interchainOracleBrand, + 10n ** BigInt(decimalPlacesInterchain), + interchainBrand, + ); + const scaleOut = makeRatio( + 10n ** BigInt(decimalPlacesUsd), + usdBrand, + 10n ** BigInt(decimalPlacesRun), + stableBrand, + ); + const initialPrice = initialPriceRaw + ? parseRatio(initialPriceRaw, stableBrand, interchainBrand) + : undefined; + + const terms = await deeplyFulfilledObject( + harden({ + sourcePriceAuthority, + scaleIn, + scaleOut, + initialPrice, + }), + ); + + const spaKit = await E(startUpgradable)({ + installation: scaledPriceAuthority, + label: `scaledPriceAuthority-${keyword}`, + terms, + }); + + await E(priceAuthorityAdmin).registerPriceAuthority( + // @ts-expect-error The public facet should have getPriceAuthority + E(spaKit.publicFacet).getPriceAuthority(), + interchainBrand, + stableBrand, + true, // force + ); +}; + +/** @typedef {import('./econ-behaviors.js').EconomyBootstrapPowers} EconomyBootstrapPowers */ + +/** + * @param {EconomyBootstrapPowers} powers + * @param {object} config + * @param {object} config.options + * @param {InterchainAssetOptions} config.options.interchainAssetOptions + * @param {bigint | number | string} config.options.debtLimitValue + * @param {bigint} config.options.interestRateValue + */ +export const addAssetToVault = async ( + { + consume: { vaultFactoryKit, agoricNamesAdmin, auctioneerKit }, + brand: { + consume: { [Stable.symbol]: stableP }, + }, + }, + { + options: { + // Default to 1000 IST to simplify testing. A production proposal will set this. + debtLimitValue = 1_000n * 1_000_000n, + // Default to a safe value. Production will likely set this through governance. + // Allow setting through bootstrap to simplify testing. + interestRateValue = 1n, + liquidationMarginValue = 380n, + interchainAssetOptions, + }, + }, +) => { + const { keyword, oracleBrand } = interchainAssetOptions; + assert.typeof(keyword, 'string'); + assert.typeof(oracleBrand, 'string'); + const [interchainIssuer] = await reserveThenGetNames( + E(agoricNamesAdmin).lookupAdmin('issuer'), + [keyword], + ); + + const stable = await stableP; + const vaultFactoryCreator = E.get(vaultFactoryKit).creatorFacet; + await E(vaultFactoryCreator).addVaultType(interchainIssuer, oracleBrand, { + debtLimit: AmountMath.make(stable, BigInt(debtLimitValue)), + interestRate: makeRatio(interestRateValue, stable), + // The rest of these we use safe defaults. + // In production they will be governed by the Econ Committee. + // Product deployments are also expected to have a low debtLimitValue at the outset, + // limiting the impact of these defaults. + liquidationPadding: makeRatio(25n, stable), + liquidationMargin: makeRatio(liquidationMarginValue, stable), + mintFee: makeRatio(50n, stable, 10_000n), + liquidationPenalty: makeRatio(1n, stable), + }); + const auctioneerCreator = E.get(auctioneerKit).creatorFacet; + await E(auctioneerCreator).addBrand(interchainIssuer, keyword); +}; + +export const getManifestForAddAssetToVault = ( + { restoreRef }, + { + debtLimitValue, + interestRateValue, + liquidationMarginValue, + interchainAssetOptions, + scaledPriceAuthorityRef, + }, +) => { + const publishIssuerFromBoardId = + typeof interchainAssetOptions.issuerBoardId === 'string'; + const publishIssuerFromBank = + !publishIssuerFromBoardId && + typeof interchainAssetOptions.denom === 'string'; + return { + manifest: { + ...(publishIssuerFromBoardId && { + [publishInterchainAssetFromBoardId.name]: { + consume: { + board: true, + agoricNamesAdmin: true, + }, + }, + }), + ...(publishIssuerFromBank && { + [publishInterchainAssetFromBank.name]: { + consume: { + bankManager: true, + agoricNamesAdmin: true, + bankMints: true, + reserveKit: true, + vBankKits: true, + startUpgradable: true, + }, + produce: { bankMints: true, vBankKits: true }, + installation: { + consume: { mintHolder: true }, + }, + }, + }), + [registerScaledPriceAuthority.name]: { + consume: { + agoricNamesAdmin: true, + startUpgradable: true, + priceAuthorityAdmin: true, + priceAuthority: true, + scaledPriceAuthorityKits: true, + }, + produce: { + scaledPriceAuthorityKits: true, + }, + installation: { + consume: { scaledPriceAuthority: true }, + }, + }, + [addAssetToVault.name]: { + consume: { + auctioneerKit: 'auctioneer', + vaultFactoryKit: 'vaultFactory', + agoricNamesAdmin: true, + }, + brand: { + consume: { [Stable.symbol]: true }, + }, + }, + }, + installations: { + scaledPriceAuthority: restoreRef(scaledPriceAuthorityRef), + }, + options: { + debtLimitValue, + interchainAssetOptions, + interestRateValue, + liquidationMarginValue, + }, + }; +}; diff --git a/proposals/b:default-params/core-eval-support.js b/proposals/b:default-params/core-eval-support.js new file mode 100644 index 00000000..e261878c --- /dev/null +++ b/proposals/b:default-params/core-eval-support.js @@ -0,0 +1,529 @@ +// @ts-check +// TODO: factor out ambient authority from these +// or at least allow caller to supply authority. +import '@endo/init'; +import { + agd, + agops, + agopsLocation, + agoric, + dbTool, + executeCommand, + executeOffer, + getContractInfo, + makeAgd, + makeFileRd, + makeFileRW, + waitForBlock, +} from '@agoric/synthetic-chain'; +import { + boardValToSlot, + slotToBoardRemote +} from "@agoric/vats/tools/board-utils.js"; +import { makeMarshal } from "@endo/marshal"; +import processAmbient from "process"; +import cpAmbient from "child_process"; +import dbOpenAmbient from "better-sqlite3"; +import fspAmbient from "fs/promises"; +import pathAmbient from "path"; +import { tmpName as tmpNameAmbient } from "tmp"; + +const AdvanceTimeExactOfferSpec = ({ id, timestamp }) => ({ + id, + invitationSpec: { + source: "agoricContract", + instancePath: ['manualTimerInstance'], + callPipe: [["makeAdvanceTimeInvitation"]], + }, + proposal: {}, + offerArgs: { timestamp }, +}); + +const AdvanceTimeStepByStepOfferSpec = ({ id, steps, duration }) => ({ + id, + invitationSpec: { + source: "agoricContract", + instancePath: ['manualTimerInstance'], + callPipe: [["makeAdvanceTimeStepByStepInvitation"]], + }, + proposal: {}, + offerArgs: { duration }, +}); + +export const makeTestContext = async ({ io = {}, testConfig, srcDir }) => { + const { + process: { env, cwd } = processAmbient, + child_process: { execFileSync } = cpAmbient, + dbOpen = dbOpenAmbient, + fsp = fspAmbient, + path = pathAmbient, + tmpName = tmpNameAmbient, + } = io; + + const src = srcDir ? makeFileRd(`${cwd()}/${srcDir}`, { fsp, path }) : {}; + const tmpNameP = prefix => + new Promise((resolve, reject) => + tmpName({ prefix }, (err, x) => (err ? reject(err) : resolve(x))), + ); + + const config = { + chainId: 'agoriclocal', + ...testConfig, + }; + + // This agd API is based on experience "productizing" + // the inter bid CLI in #7939 + const agd = makeAgd({ execFileSync: execFileSync }).withOpts({ + keyringBackend: 'test', + }); + + const dbPath = testConfig.swingstorePath.replace(/^~/, env.HOME); + const swingstore = dbTool(dbOpen(dbPath, { readonly: true })); + + /* @param {string} baseName */ + const mkTempRW = async baseName => + makeFileRW(await tmpNameP(baseName), { fsp, path }); + return { agd, agoric, agops, swingstore, config, mkTempRW, src, tmpNameP }; +}; + +/** @param {number[]} xs */ +export const sum = xs => xs.reduce((a, b) => a + b, 0); + +/** + * + * @param {import('@agoric/synthetic-chain').FileRW} src + * @param {string} fileName + * @return {Promise} + */ +export const getFileSize = async (src, fileName) => { + const file = src.join(fileName); + const { size } = await file.stat(); + return size; +}; + +export const poll = async (check, maxTries) => { + for (let tries = 0; tries < maxTries; tries += 1) { + const ok = await check(); + if (ok) return; + await waitForBlock(); + } + throw Error(`tried ${maxTries} times without success`); +}; + +/** + * @typedef AgopsOfferParams + * @property t + * @property {string[]} agopsParams + * @property {string[]} txParams Without --offer flag + * @property {string} from + * @property {import('@agoric/synthetic-chain').FileRW} src + * + * @param {AgopsOfferParams} + */ +export const agopsOffer = async ({ + t, + agopsParams, + txParams, + from, + src, + }) => { + const { agops, agoric } = t.context; + + await src.mkdir(from); + const fileRW = await src.join(from); + + try { + const test = await agops.oracle(...agopsParams); + await fileRW.writeText(test); + t.log({ test }) + await agoric.wallet(...txParams, '--offer', fileRW.toString()); + } catch (e) { + t.fail(e); + } +}; + +/** + * + * @param {string} path + * @return {Promise} + */ +export const getStorageChildren = async path => { + const { children } = await agd.query('vstorage', + 'children', + path); + + return children; +}; + +/** + * @return {Promise} + */ +const getPriceRound = async () => { + const children = await getStorageChildren('published.priceFeed.STARS-USD_price_feed'); + console.log({ children }); + const roundChild = [...children].find(element => element === 'latestRound'); + if (roundChild === undefined) return 0; + + const { roundId } = await getContractInfo('priceFeed.STARS-USD_price_feed.latestRound', { agoric }); + return Number(roundId); +}; + +/** + * + * @param t + * @param price + * @param {Array<{address, acceptId}>} oracles + * @return {Promise} + */ +export const pushPrice = async (t, price, oracles) => { + const { mkTempRW } = t.context; + const tmpRW = await mkTempRW('pushPrices'); + + const curRound = await getPriceRound(); + + const buildAgopsArgs = id => { + return [ + 'pushPriceRound', + '--price', + price, + '--roundId', + curRound + 1, + '--oracleAdminAcceptOfferId', + id, + ] + }; + + const buildOfferArgs = from => { + return [ + 'send', + '--from', + from, + '--keyring-backend=test', + ] + }; + + for (const { address, acceptId } of oracles) { + await agopsOffer({ + t, + agopsParams: buildAgopsArgs(acceptId), + txParams: buildOfferArgs(address), + src: tmpRW, + from: address + } + ) + } + + await waitForBlock(5); +}; + +export const acceptsOracleInvitations = async (t, oracles) => { + const { mkTempRW } = t.context; + const tmpRW = await mkTempRW('acceptInvites'); + + const buildAgopsParams = (id = Date.now()) => { + return ['accept', '--offerId', id, '--pair', 'STARS.USD']; + }; + + const buildOfferParams = from => { + return ['send', '--from', from, '--keyring-backend=test']; + }; + + const offersP = []; + for (const { address, acceptId } of oracles) { + offersP.push( + agopsOffer({ t, agopsParams: buildAgopsParams(acceptId), txParams: buildOfferParams(address), from: address, src: tmpRW}), + ) + } + + await Promise.all(offersP); + + // Wait 5 blocks + await waitForBlock(5); +}; + +/** + * + * @param {{ + * src: string, + * dest: string + * }[]} config dest must be absolute and src can be relative + * @param fsp + */ +export const copyAll = (config, { fsp }) => { + const copyPs = []; + for (const { src, dest } of config) { + const srcUrl = new URL(src, import.meta.url); + copyPs.push(fsp.cp(srcUrl, dest)); + } + + return Promise.all(copyPs); +} + +/** + * Use this method when you need to extract filename from a path + * + * @param {string} filePath + */ +export const extractNameFromPath = filePath => filePath.split('/').at(-1) + +export const makeBoardMarshaller = () => makeMarshal(boardValToSlot, slotToBoardRemote, { serializeBodyFormat: 'smallcaps'}); + + +/** + * Like getContractInfo from @agoric/synthetic-chain but also returns + * the marshaller itself as well. + * + * @param io + * @return {{data: any, marshaller: import('@endo/marshal').Marshal}} + * + */ +export const makeStorageInfoGetter = io => { + const { + agoric + } = io; + + const marshaller = makeBoardMarshaller(); + + const getStorageInfo = async path => { + const stdout = await agoric.follow('-lF', `:${path}`, '-o', 'text'); + const tx = JSON.parse(stdout); + return marshaller.fromCapData(tx); + }; + + return { getStorageInfo, marshaller }; +} + +export const makeAuctionTimerDriver = async (context, from) => { + const { mkTempRW, agoric } = context; + const id = `manual-timer-${Date.now()}`; + const tmpRW = await mkTempRW(id); + + const { getStorageInfo, marshaller } = makeStorageInfoGetter({ agoric }); + + + const startAuction = async () => { + const { nextStartTime, nominalStart } = await calculateNominalStart({ agoric }); + + // First move the timer to nominalStart + await sendTimerOffer(from, marshaller, tmpRW, 'exact', { timestamp: nominalStart }); + + // Now start the auction + await sendTimerOffer(from, marshaller, tmpRW, 'exact', { timestamp: nextStartTime}); + + return { nextStartTime, nominalStart }; + }; + + const advanceAuctionStepByOne = async () => { + const schedule = await getStorageInfo('published.fakeAuctioneer.schedule'); + + const { nextDescendingStepTime } = schedule; + console.log(schedule); + + // Now start the auction + await sendTimerOffer(from, marshaller, tmpRW, 'exact', { timestamp: nextDescendingStepTime.absValue }); + }; + + /** + * + * @param {BigInt} steps + * @return {Promise} + */ + const advanceAuctionStepMulti = async steps => { + const governance = await getStorageInfo('published.fakeAuctioneer.governance'); + + const { ClockStep: { + value: { relValue: clockStepVal } + } } = governance.current; + + let currentStep = 0; + while(currentStep < steps) { + await sendTimerOffer(from, marshaller, tmpRW, 'step', { duration: clockStepVal }); + currentStep += 1; + await waitForBlock(5); + } + } + + return { + advanceAuctionStepByOne, + advanceAuctionStepMulti, + startAuction, + }; +} + +export const sendTimerOffer = async (from, marshaller, fileSrc, type, offerArgs) => { + let offerSpec; + if (type === 'exact') { + offerSpec = AdvanceTimeExactOfferSpec({ id: `${Date.now()}`, ...offerArgs }); + } else if(type === 'step') { + offerSpec = AdvanceTimeStepByStepOfferSpec({ id: `${Date.now()}`, ...offerArgs }); + } + + const spendAction = { + method: "executeOffer", + offer: offerSpec, + }; + + const offer = JSON.stringify(marshaller.toCapData(harden(spendAction))); + await fileSrc.writeText(offer); + + return agoric.wallet( + 'send', + '--from', + from, + '--keyring-backend=test', + '--offer', + fileSrc.toString() + ); +} + +// TODO Feature request: Open an issue asking for a parameterized collateral +// brand +export const openVault = (address, mint, collateral, collateralBrand = "ATOM") => { + return executeOffer( + address, + agops.vaults( + 'open', + '--wantMinted', + mint, + '--giveCollateral', + collateral, + '--collateralBrand', + collateralBrand + ), + ); +}; + +const agopsInter = async (...params) => { + const newParams = ['inter', ...params, '--keyring-backend=test']; + return executeCommand(agopsLocation, newParams); +}; + +export const bidByPrice = (address, spend, colKeyword, price) => { + /** + * agops inter bid by-price --from user1 --give 90IST --price 9.2 --maxBuy + * 10STARS --keyring-backend=test --generate-only + */ + + return executeOffer( + address, + agopsInter( + 'bid', + 'by-price', + '--give', + `${spend}`, + `--maxBuy`, + `10000${colKeyword}`, // 10k ATOM is the default, use the same forSTARS + `--price`, + price, + `--from`, + address, + '--generate-only' + ), + ); +} + +export const bidByDiscount = (address, spend, colKeyword, discount) => { + /** + * agops inter bid by-discount --from user1 --give 150IST --maxBuy 10000STARS --discount 15 --keyring-backend=test --generate-only + */ + + return executeOffer( + address, + agopsInter( + 'bid', + 'by-discount', + '--give', + `${spend}`, + `--maxBuy`, + `10000${colKeyword}`, // 10k ATOM is the default, use the same forSTARS + `--discount`, + discount, + `--from`, + address, + '--generate-only' + ), + ); +}; + +/** + * + * priceLockWakeTime = nominalStart - priceLockPeriod + * + * @param {{ + * agoric: any + * }} io + * @return {Promise<{nominalStart: number, nextStartTime: number}>} + */ +const calculateNominalStart = async ({ agoric }) => { + const [schedule, governance] = await Promise.all([ + getContractInfo('fakeAuctioneer.schedule', { agoric }), + getContractInfo('fakeAuctioneer.governance', { agoric }), + ]); + + const { nextStartTime: { absValue: nextStartTimeVal } } = schedule; + const { + AuctionStartDelay: { value: { relValue: auctionStartDelay} }, + } = governance.current; + + const nominalStart = nextStartTimeVal - auctionStartDelay; + + return { nominalStart, nextStartTime: nextStartTimeVal }; +}; + +export const scale6 = x => BigInt(Math.round(x * 1_000_000)); + +export const assertVisibility = async (t, managerIndex, base = 0, { nominalStart }) => { + const { agoric } = t.context; + + const [preAuction, postAuction, auctionResult] = await Promise.all([ + getContractInfo(`vaultFactory.managers.manager${managerIndex}.liquidations.${nominalStart}.vaults.preAuction`, { agoric }), + getContractInfo(`vaultFactory.managers.manager${managerIndex}.liquidations.${nominalStart}.vaults.postAuction`, { agoric }), + getContractInfo(`vaultFactory.managers.manager${managerIndex}.liquidations.${nominalStart}.auctionResult`, { agoric }), + ]); + + const expectedPreAuction = []; + for (let i = 0; i < Liquidation.setup.vaults.length; i += 1) { + expectedPreAuction.push([ + `vault${base + i}`, + { + collateralAmount: { value: scale6(Liquidation.setup.vaults[i].collateral) }, + debtAmount: { value: scale6(Liquidation.setup.vaults[i].debt) }, + }, + ]); + } + + t.like( + Object.fromEntries(preAuction), + Object.fromEntries(expectedPreAuction), + ); + + const expectedPostAuction = []; + // Iterate from the end because we expect the post auction vaults + // in best to worst order. + for (let i = Liquidation.outcome.vaults.length - 1; i >= 0; i -= 1) { + expectedPostAuction.push([ + `vault${base + i}`, + { Collateral: { value: scale6(Liquidation.outcome.vaults[i].locked) } }, + ]); + } + t.like( + Object.fromEntries(postAuction), + Object.fromEntries(expectedPostAuction), + ); + + t.like(auctionResult, { + collateralOffered: { value: scale6(Liquidation.setup.auction.start.collateral) }, + istTarget: { value: scale6(Liquidation.setup.auction.start.debt) }, + collateralForReserve: { value: scale6(Liquidation.outcome.reserve.allocations.ATOM) }, + shortfallToReserve: { value: 0n }, + mintedProceeds: { value: scale6(Liquidation.setup.auction.start.debt) }, + collateralSold: { + value: + scale6(Liquidation.setup.auction.start.collateral) - + scale6(Liquidation.setup.auction.end.collateral), + }, + collateralRemaining: { value: 0n }, + // endTime: { absValue: endTime.absValue }, Figure out how to read the + // schedule + }); +}; diff --git a/proposals/b:default-params/package.json b/proposals/b:default-params/package.json index b1f93a8f..e8cf4772 100644 --- a/proposals/b:default-params/package.json +++ b/proposals/b:default-params/package.json @@ -11,6 +11,7 @@ "@agoric/synthetic-chain": "^0.1.0", "@agoric/time": "^0.3.3-u14.0", "@agoric/vats": "^0.15.2-u15.0", + "@endo/marshal": "^1.5.0", "@endo/zip": "^0.2.35", "ava": "^5.3.1", "better-sqlite3": "^8.5.1", diff --git a/proposals/b:default-params/test-defaultParams.js b/proposals/b:default-params/test-defaultParams.js index de16254c..9eee47d0 100644 --- a/proposals/b:default-params/test-defaultParams.js +++ b/proposals/b:default-params/test-defaultParams.js @@ -6,25 +6,38 @@ import { // makeWebRd, // bundleDetail, proposalBuilder, -// readBundles, -// passCoreEvalProposal, + readBundles, + passCoreEvalProposal, getContractInfo, agoric, } from '@agoric/synthetic-chain'; import * as fsp from 'fs/promises'; +import { + extractNameFromPath, + getStorageChildren, + makeStorageInfoGetter, + makeTestContext +} from "./core-eval-support.js"; // import { existsSync } from 'fs'; // import { tmpName } from 'tmp'; // import * as path from 'path'; const config = { + installer: 'user1', + proposer: 'validator', script: './add-STARS.js', dest: '/usr/src/agoric-sdk/packages/inter-protocol/scripts/add-STARS.js', + swingstorePath: '~/.agoric/data/agoric/swingstore.sqlite', }; +test.before(async t => (t.context = await makeTestContext({ testConfig: config, srcDir: 'assets' }))); + test.serial('copy add-STARS.js to @agoric/inter-protocol/scripts', async t => { await fsp.cp(config.script, config.dest); - t.pass();1 + await fsp.cp('./add-collateral-core.js', '/usr/src/agoric-sdk/packages/inter-protocol/scripts/add-collateral-core.js'); + await fsp.cp('./addAssetToVault.js', '/usr/src/agoric-sdk/packages/inter-protocol/src/proposals/addAssetToVault.js'); + t.pass(); }); -test.serial.only('build prop', async t => { +test.serial('build prop', async t => { const { evals, bundles @@ -33,6 +46,56 @@ test.serial.only('build prop', async t => { evals, bundles }); + config.proposal = { + evals, + bundles + }; + t.pass(); +}); + +test.serial('run prop', async t => { + t.log(config.proposal); + const { tmpNameP } = t.context; + const { proposal: { evals, bundles } } = config; + const tmpName = await tmpNameP('default-params'); + await fsp.mkdir(tmpName); + + const evalsCopyP = evals.flatMap( + ({ + permit, + script + }) => [ + fsp.cp(permit, `${tmpName}/${extractNameFromPath(permit)}`), + fsp.cp(script, `${tmpName}/${extractNameFromPath(script)}`) + ]); + const bundlesCopyP = bundles.map( + bundlePath => fsp.cp(bundlePath, `${tmpName}/${extractNameFromPath(bundlePath)}`) + ); + + await Promise.all([ + ...evalsCopyP, + ...bundlesCopyP, + ]) + + t.log({ tmpName }); + const bundleInfos = await readBundles(tmpName); + t.log('bundleInfos', bundleInfos); + + await passCoreEvalProposal( + bundleInfos, + { title: `Core eval of ${tmpName}`, ...config } + ); + t.pass(); +}); + +test.only('display', async t => { + const children = await getStorageChildren('published.vaultFactory.managers'); + t.log(children); + + const { getStorageInfo } = makeStorageInfoGetter({ agoric: t.context.agoric }); + const data = await getStorageInfo('published.vaultFactory.managers.manager2.governance'); + t.log('Data: ', data.current.LiquidationMargin.value); + t.is(data.current.LiquidationMargin.value.numerator.value, 380n); t.pass(); -}) +}); diff --git a/proposals/b:default-params/yarn.lock b/proposals/b:default-params/yarn.lock index 549a1a8d..4592ad34 100644 --- a/proposals/b:default-params/yarn.lock +++ b/proposals/b:default-params/yarn.lock @@ -1355,6 +1355,17 @@ __metadata: languageName: node linkType: hard +"@endo/common@npm:^1.2.2": + version: 1.2.2 + resolution: "@endo/common@npm:1.2.2" + dependencies: + "@endo/errors": "npm:^1.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/promise-kit": "npm:^1.1.2" + checksum: 10c0/04a516c12be5146a2b15a31842e8f13a090efb19e674b018c45241ce1c13a9e2480f23df2240105fc6819dc596bac9d2e4746b6a5798b6e7a0b7fc019336e58e + languageName: node + linkType: hard + "@endo/compartment-mapper@npm:0.8.4": version: 0.8.4 resolution: "@endo/compartment-mapper@npm:0.8.4" @@ -1405,6 +1416,13 @@ __metadata: languageName: node linkType: hard +"@endo/env-options@npm:^1.1.4": + version: 1.1.4 + resolution: "@endo/env-options@npm:1.1.4" + checksum: 10c0/8816c8fe1332a6f3366e7e4849b000d757fcd181eac011ed8363ccc4e66dfa2f2d975f8d5cbfb3844f3e327c5391e77ee7e234a59a21744c74c945f683b56df1 + languageName: node + linkType: hard + "@endo/errors@npm:^1.2.1": version: 1.2.1 resolution: "@endo/errors@npm:1.2.1" @@ -1414,6 +1432,15 @@ __metadata: languageName: node linkType: hard +"@endo/errors@npm:^1.2.2": + version: 1.2.2 + resolution: "@endo/errors@npm:1.2.2" + dependencies: + ses: "npm:^1.5.0" + checksum: 10c0/d90baaf803b17130b83fbc562e253504a6a05e4843d63536e74578503f6bd937fdba3464c4d8eeb9df16b795639cd9c4aad143520525f9e54aa3e91dc5184c17 + languageName: node + linkType: hard + "@endo/eventual-send@npm:0.17.2": version: 0.17.2 resolution: "@endo/eventual-send@npm:0.17.2" @@ -1439,6 +1466,15 @@ __metadata: languageName: node linkType: hard +"@endo/eventual-send@npm:^1.2.2": + version: 1.2.2 + resolution: "@endo/eventual-send@npm:1.2.2" + dependencies: + "@endo/env-options": "npm:^1.1.4" + checksum: 10c0/d19fcee64b7113e5ff7fc4886d47bc722c4edd49a107f0952760567a4dd31cabd14abd788cc2c0c9d4b907e2c737944a389fd0ab5b0917809ef47a1df6803642 + languageName: node + linkType: hard + "@endo/exo@npm:0.2.2": version: 0.2.2 resolution: "@endo/exo@npm:0.2.2" @@ -1569,6 +1605,20 @@ __metadata: languageName: node linkType: hard +"@endo/marshal@npm:^1.5.0": + version: 1.5.0 + resolution: "@endo/marshal@npm:1.5.0" + dependencies: + "@endo/common": "npm:^1.2.2" + "@endo/errors": "npm:^1.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/nat": "npm:^5.0.7" + "@endo/pass-style": "npm:^1.4.0" + "@endo/promise-kit": "npm:^1.1.2" + checksum: 10c0/1de746affa338e40da61d4a3fecb138724f58bf54a05060efc0996798c4fb2ca508923ac8815c8611741ec6207169517cfb4580dfd24d18e4d144ed412678047 + languageName: node + linkType: hard + "@endo/nat@npm:4.1.27": version: 4.1.27 resolution: "@endo/nat@npm:4.1.27" @@ -1583,6 +1633,13 @@ __metadata: languageName: node linkType: hard +"@endo/nat@npm:^5.0.7": + version: 5.0.7 + resolution: "@endo/nat@npm:5.0.7" + checksum: 10c0/ee7d659a958fa0157767a0aa8a9425c99fe358754d3d1c93f6be169d5d92409427a88d5593da923614b087ad9faf717065ad58c3b4793dfb4049ce59b5bce63a + languageName: node + linkType: hard + "@endo/netstring@npm:0.3.26": version: 0.3.26 resolution: "@endo/netstring@npm:0.3.26" @@ -1638,6 +1695,19 @@ __metadata: languageName: node linkType: hard +"@endo/pass-style@npm:^1.4.0": + version: 1.4.0 + resolution: "@endo/pass-style@npm:1.4.0" + dependencies: + "@endo/env-options": "npm:^1.1.4" + "@endo/errors": "npm:^1.2.2" + "@endo/eventual-send": "npm:^1.2.2" + "@endo/promise-kit": "npm:^1.1.2" + "@fast-check/ava": "npm:^1.1.5" + checksum: 10c0/28ef4b4293dbae98b17ba8165dddf80edec4a1b7f94664b09adfb5fcd5927f2a4aa0679081b94876c997d4df559a192ef4ec5e8a9444fe671966488f6e098b50 + languageName: node + linkType: hard + "@endo/patterns@npm:0.2.2": version: 0.2.2 resolution: "@endo/patterns@npm:0.2.2" @@ -1687,6 +1757,15 @@ __metadata: languageName: node linkType: hard +"@endo/promise-kit@npm:^1.1.2": + version: 1.1.2 + resolution: "@endo/promise-kit@npm:1.1.2" + dependencies: + ses: "npm:^1.5.0" + checksum: 10c0/12bf81d5b3efd77f75c95d4d287a9b46f9f69adc9cb25586d5a25e1d01a5392fe2e268adf5e3f61ef77187f134ffeab9182deb1b9f35598fb14c9a89b38e3a88 + languageName: node + linkType: hard + "@endo/ses-ava@npm:0.2.40": version: 0.2.40 resolution: "@endo/ses-ava@npm:0.2.40" @@ -5488,6 +5567,15 @@ __metadata: languageName: node linkType: hard +"ses@npm:^1.5.0": + version: 1.5.0 + resolution: "ses@npm:1.5.0" + dependencies: + "@endo/env-options": "npm:^1.1.4" + checksum: 10c0/bd5e230ff07abe84632ff212a5dce5163d979ab0ae657c4ac0ee2748aea9f587d83c75639d0058def62eae6dde55c0ae9652f33aecc5e0fe538c89ce54bffcdc + languageName: node + linkType: hard + "set-function-length@npm:^1.2.1": version: 1.2.2 resolution: "set-function-length@npm:1.2.2" @@ -5859,6 +5947,7 @@ __metadata: "@agoric/synthetic-chain": "npm:^0.1.0" "@agoric/time": "npm:^0.3.3-u14.0" "@agoric/vats": "npm:^0.15.2-u15.0" + "@endo/marshal": "npm:^1.5.0" "@endo/zip": "npm:^0.2.35" ava: "npm:^5.3.1" better-sqlite3: "npm:^8.5.1"