Skip to content
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

WIP: interAsset contract: addVaultCollateral etc. #8

Draft
wants to merge 15 commits into
base: dc-launchpad
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
},
"devDependencies": {
"@agoric/deploy-script-support": "^0.10.4-u12.0",
"@agoric/eslint-config": "dev",
"@agoric/inter-protocol": "0.16.2-u12.0",
"@agoric/smart-wallet": "^0.5.4-u12.0",
"@agoric/store": "^0.9.3-u12.0",
"@agoric/vats": "^0.15.2-u12.0",
"@endo/bundle-source": "^2.8.0",
"@endo/eslint-plugin": "^0.5.2",
"@endo/init": "^0.5.60",
Expand Down Expand Up @@ -57,11 +57,16 @@
"dependencies": {
"@agoric/ertp": "^0.16.3-u12.0",
"@agoric/time": "^0.3.3-u12.0",
"@agoric/store": "^0.9.3-u12.0",
"@agoric/vat-data": "0.5.2",
"@agoric/vats": "^0.15.2-u12.0",
"@agoric/zoe": "^0.26.3-u12.0",
"@agoric/zone": "^0.2.3-u12.0",
"@endo/captp": "3.1.1",
"@endo/far": "^0.2.22",
"@endo/marshal": "^0.8.9",
"@endo/patterns": "^0.2.5"
"@endo/patterns": "^0.2.5",
"@endo/promise-kit": "0.2.56"
},
"ava": {
"files": [
Expand All @@ -81,6 +86,11 @@
},
"homepage": "https://github.com/agoric-labs/dapp-join-game#readme",
"eslintConfig": {
"parserOptions": {
"sourceType": "module",
"ecmaVersion": 2021
},
"ignorePatterns": "bundles/**.js",
"extends": [
"@agoric"
]
Expand All @@ -90,4 +100,4 @@
"arrowParens": "avoid",
"singleQuote": true
}
}
}
40 changes: 40 additions & 0 deletions contract/src/@types/inter-types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// @ts-check
export {};

/**
* @typedef {object} VaultManagerParamValues
* @property {Ratio} liquidationMargin - margin below which collateral will be
* liquidated to satisfy the debt.
* @property {Ratio} liquidationPenalty - penalty charged upon liquidation as
* proportion of debt
* @property {Ratio} interestRate - annual interest rate charged on debt
* positions
* @property {Ratio} mintFee - The fee (in BasisPoints) charged when creating or
* increasing a debt position.
* @property {Amount<'nat'>} debtLimit
* @property {Ratio} [liquidationPadding] - vault must maintain this in order to
* remove collateral or add debt
*/

/**
* @typedef {object} InterchainAssetOptions
* @property {string} denom
* @property {number} decimalPlaces
* @property {string} keyword - used in regstering with reserve, vaultFactory
* @property {string} [issuerName] - used in agoricNames for compatibility:
* defaults to `keyword` if not provided
* @property {string} [proposedName] - defaults to `issuerName` if not provided
* @property {string} [oracleBrand] - defaults to `issuerName` if not provided
*/

/**
* @typedef {{
* addIssuer: (issuer: Issuer, keyword: string) => Promise<void>
* }} ReserveCreator
*/

/**
* @typedef {{
* addVaultType(collateralIssuer: Issuer<'nat'>, collateralKeyword: Keyword, initialParamValues: VaultManagerParamValues)
* }} VaultFactoryCreator
*/
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
type CopyRecord<T> = import('@endo/pass-style').CopyRecord<T>;
import type { CopyRecord } from '@endo/pass-style';

// export type {ContractMeta} from '@agoric/zoe/src/contractFacet/types-ambient';
export type ContractMeta = {
Expand Down
184 changes: 184 additions & 0 deletions contract/src/interAssets.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// @ts-check
import { E } from '@endo/far';
import { ToFarFunction } from '@endo/captp';
import { makePromiseKit } from '@endo/promise-kit';
import { makeDurableZone } from '@agoric/zone/durable.js';
import { provide } from '@agoric/vat-data';
import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js';
import { makeRatio } from '@agoric/zoe/src/contractSupport/index.js';

import { whenQuiescent } from './when-quiescent.js';

console.log('TODO: createPriceFeed from price-feed-proposal');
console.log('TODO: ensureOracleBrands from price-feed-proposal');
console.log('TODO: scaledPriceAuthority - or skip it?');

console.log('TODO: startPSM');

/**
* @typedef {{
* startUpgradable: StartUpgradable
* }} UpgradeTools
*/

export const oracleBrandFeedName = (inBrandName, outBrandName) =>
`${inBrandName}-${outBrandName} price feed`;

/**
* @param {ZCF<{ agoricNames: NameHub }>} zcf
* @param {{
* tools: UpgradeTools,
* chainTimerService: ERef<import('@agoric/time/src/types').TimerService>,
* contractAdmin: {
* reserve: import('./@types/inter-types.js').ReserveCreator,
* vaultFactory: import('./@types/inter-types.js').VaultFactoryCreator,
* auctioneer: GovernanceFacetKit<import('@agoric/inter-protocol/src/auction/auctioneer').start>['creatorFacet'],
* },
* nameAdmin: {
* issuer: import('@agoric/vats/src/types').NameAdmin,
* brand: import('@agoric/vats/src/types').NameAdmin,
* },
* bankManager: BankManager,
* }} privateArgs
*
* @param {import('@agoric/vat-data').Baggage} baggage
*/
export const start = async (zcf, privateArgs, baggage) => {
/** @type {import('@agoric/zone').Zone} */
const zone = makeDurableZone(baggage);
const { agoricNames } = zcf.getTerms();
const { tools, contractAdmin, nameAdmin, bankManager, chainTimerService } =
privateArgs;

const installation = {
/** @type {Promise<Installation<import('@agoric/vats/src/mintHolder').prepare>>} */
mintHolder: E(agoricNames).lookup('installation', 'mintHolder'),
};

const lookupInstance = name => E(agoricNames).lookup('instance', name);

/**
* @param {string} name
* @param {{ issuer: Issuer, brand: Brand}} kit
*/
const publishAsset = (name, { issuer, brand }) =>
Promise.all([
E(nameAdmin.issuer).update(name, issuer),
E(nameAdmin.brand).update(name, brand),
]);

const stable = await E(agoricNames).lookup('brand', 'IST');

/** @type {import('./@types/inter-types.js').VaultManagerParamValues} */
const initialVaultParams = {
debtLimit: AmountMath.make(stable, 1_000n * 1_000_000n),
interestRate: makeRatio(1n, stable),
liquidationPadding: makeRatio(25n, stable),
liquidationMargin: makeRatio(150n, stable),
mintFee: makeRatio(50n, stable, 10_000n),
liquidationPenalty: makeRatio(1n, stable),
};

const makeInterAssetKit = zone.exoClassKit(
'InterAsset',
undefined, // TODO: interface guards
() => ({}),
{
creator: {
/**
* @param {Pick<import('./@types/inter-types.js').InterchainAssetOptions,
* 'keyword' | 'issuerName' | 'decimalPlaces'>} assetOpts
* @returns {Promise<Exclude<IssuerKit<'nat'>, 'mintRecoveryPurse'>>}
*/
async startMintHolder(assetOpts) {
const { keyword, issuerName = keyword, decimalPlaces } = assetOpts;
/** @type {DisplayInfo<'nat'>} */
const displayInfo = {
decimalPlaces,
assetKind: AssetKind.NAT,
};
const terms = {
keyword: issuerName, // "keyword" is a misnomer in mintHolder terms
assetKind: AssetKind.NAT,
displayInfo,
};

const facets = await E(tools).startUpgradable({
installation: installation.mintHolder,
label: issuerName,
privateArgs: undefined,
terms,
});
const { creatorFacet: mint, publicFacet: issuer } = facets;
const brand = await E(issuer).getBrand();

// @ts-expect-error AssetKind NAT guaranteed by construction
return { mint, issuer, brand, displayInfo };
},

/** @param {import('./@types/inter-types.js').InterchainAssetOptions} assetOpts */
async makeVBankAsset(assetOpts) {
const {
keyword,
issuerName = keyword,
proposedName = issuerName,
denom,
} = assetOpts;
const { creator } = this.facets;
const { mint, issuer, brand } =
await creator.startMintHolder(assetOpts);
const kit = { mint, issuer, brand };

await Promise.all([
E(bankManager).addAsset(denom, issuerName, proposedName, kit),
E(contractAdmin.reserve).addIssuer(issuer, keyword),
publishAsset(issuerName, { issuer, brand }),
]);
return harden({ issuer, brand });
},

/** @param {import('./@types/inter-types.js').InterchainAssetOptions} assetOpts */
async addVaultCollateral(assetOpts) {
const {
keyword,
issuerName = keyword,
oracleBrand = issuerName,
} = assetOpts;
const { creator } = this.facets;
const { issuer: interchainIssuer } =
await creator.makeVBankAsset(assetOpts);

// don't add the collateral offering to vaultFactory until its price feed is available
// eslint-disable-next-line no-restricted-syntax -- allow this computed property
await lookupInstance(oracleBrandFeedName(oracleBrand, 'USD'));

const auctioneerCreator = contractAdmin.auctioneer;
const schedules = await E(auctioneerCreator).getSchedule();

const finishPromiseKit = makePromiseKit();
const addBrandThenResolve = ToFarFunction(
'addBrandThenResolve',
async () => {
await E(auctioneerCreator).addBrand(interchainIssuer, keyword);
finishPromiseKit.resolve(undefined);
},
);

// schedules actions on a timer (or does it immediately).
// finishPromiseKit signals completion.
void whenQuiescent(schedules, chainTimerService, addBrandThenResolve);
await finishPromiseKit.promise;

await E(contractAdmin.vaultFactory).addVaultType(
interchainIssuer,
keyword,
initialVaultParams,
);
},
},
public: {},
},
);
const kit = provide(baggage, 'interAssetKit', makeInterAssetKit);
return kit;
};
79 changes: 79 additions & 0 deletions contract/src/interchainMints.deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @ts-check
import { E } from '@endo/far';
import { allValues } from './objectTools.js';
import {
installContract,
startContract,
} from './platform-goals/start-contract.js';

const { Fail } = assert;

const contractName = 'interchainMints';

/**
* @param {BootstrapPowers} powers
* @param {{ options: { interchainMints: { bundleID: string }}}} config
*/
export const installMintsContract = async (powers, config) => {
const {
// must be supplied by caller or template-replaced
bundleID = Fail`no bundleID`,
} = config?.options?.[contractName] ?? {};

return installContract(powers, {
name: contractName,
bundleID,
});
};

/**
* @param {BootstrapPowers} powers
*/
export const deployInterchainMints = async powers => {
/** @type {Installation<typeof import('./interchainMints').prepare>} */
const installation = powers.installation.consume.interchainMints;
const { agoricNamesAdmin, bankManager } = powers.consume;
const { interchainMintsKit } = powers.produce;

const privateArgs = await allValues({
nameAdmins: allValues({
brand: E(agoricNamesAdmin).lookupAdmin('brand'),
issuer: E(agoricNamesAdmin).lookupAdmin('issuer'),
}),
bankManager,
});

const kit = await startContract(powers, {
name: contractName,
startArgs: { installation, privateArgs },
});
interchainMintsKit.reset();
interchainMintsKit.resolve(kit);
};

/**
* @param {BootstrapPowers} powers
* @param {{ options: { interchainMints: { bundleID: string }}}} config
*/
export const main = (powers, config) =>
Promise.all([
installMintsContract(powers, config),
deployInterchainMints(powers),
]);

export const permit = {
consume: {
agoricNamesAdmin: true,
bankManager: true,
zoe: true,
startUpgradable: true,
},
produce: { interchainMintsKit: true },
installation: {
produce: { interchainMints: true },
consume: { interchainMints: true },
},
instance: {
produce: { interchainMints: true },
},
};
Loading