From 0b6b07b5029ffc420d8c89e848a17923d3c34848 Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Fri, 28 May 2021 15:32:41 -0700 Subject: [PATCH] feat: manage parameters for a contract closes #3186 outstanding issues: * should this validate types? * is there a clean way to assert something is an amount? * should managed amounts (and ratios) only set the value and leave the brand to the managed contract? * are there others types to add or remove? --- packages/governance/src/param-manager.js | 93 +++++++++++++++++++ .../governance/test/test-param-manager.js | 5 +- 2 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 packages/governance/src/param-manager.js diff --git a/packages/governance/src/param-manager.js b/packages/governance/src/param-manager.js new file mode 100644 index 000000000000..b06771ea8829 --- /dev/null +++ b/packages/governance/src/param-manager.js @@ -0,0 +1,93 @@ +// @ts-check + +import { makeStore } from '@agoric/store'; +import { assert, details as X } from '@agoric/assert'; +import { Nat } from '@agoric/nat'; +import { assertIsRatio } from '@agoric/zoe/src/contractSupport'; +import { AmountMath, looksLikeBrand } from '@agoric/ertp'; + +const ParamType = { + NAT: 'nat', + BIGINT: 'bigint', + STRING: 'string', + RATIO: 'ratio', + AMOUNT: 'amount', + BRAND: 'brand', +}; +harden(ParamType); + +function assertType(type, value, name) { + if (!type) { + // undefined type means don't verify. Did we omit an interesting type? + return; + } + + switch (type) { + case ParamType.NAT: + Nat(value); + break; + case ParamType.BIGINT: + assert.typeof(value, 'bigint'); + break; + case ParamType.STRING: + assert.typeof(value, 'string'); + break; + case ParamType.RATIO: + assertIsRatio(value); + break; + case ParamType.AMOUNT: + // TODO(hibbert): is there a clean way to assert something is an amount? + // An alternate approach would be to say the contract knows the brand and + // the managed parameter only needs to supply the value. + assert( + AmountMath.isEqual(value, value), + X`value for ${name} must be an Amount, was ${value}`, + ); + break; + case ParamType.BRAND: + assert( + looksLikeBrand(value), + X`value for ${name} must be a brand, was ${value}`, + ); + break; + default: + assert.fail(X`unknown type guard ${type}`); + } +} + +function parse(paramDesc) { + const bindings = makeStore('name'); + const types = makeStore('name'); + + paramDesc.forEach(({ name, value, type }) => { + assert.typeof(name, 'string'); + assertType(type, value, name); + bindings.init(name, value); + types.init(name, type); + }); + + return { bindings, types }; +} + +/** @type {BuildParamManager} */ +function buildParamManager(paramDesc) { + const { bindings, types } = parse(paramDesc); + + const publicFacet = { + lookup(name) { + return bindings.get(name); + }, + }; + + const manager = { + update(name, value) { + assertType(types.get(name), value, name); + bindings.set(name, value); + }, + }; + + return { publicFacet, manager }; +} +harden(buildParamManager); + +export { ParamType, buildParamManager }; diff --git a/packages/governance/test/test-param-manager.js b/packages/governance/test/test-param-manager.js index ed6e9e06f9f0..7214a1448291 100644 --- a/packages/governance/test/test-param-manager.js +++ b/packages/governance/test/test-param-manager.js @@ -10,6 +10,7 @@ import { buildParamManager, ParamType } from '../src/paramManager'; const BASIS_POINTS = 10_000; test('params one Nat', async t => { + const numberKey = 'Number'; const numberDescription = { name: numberKey, @@ -19,7 +20,7 @@ test('params one Nat', async t => { const { getParams, updateNumber } = buildParamManager([numberDescription]); t.deepEqual(getParams()[numberKey], numberDescription); updateNumber(42n); - t.is(getParams()[numberKey].value, 42n); + t.deepEqual(getParams()[numberKey].value, 42n); t.throws( () => updateNumber(18.1), @@ -47,7 +48,7 @@ test('params one String', async t => { const { getParams, updateString } = buildParamManager([stringDescription]); t.deepEqual(getParams()[stringKey], stringDescription); updateString('bar'); - t.is(getParams()[stringKey].value, 'bar'); + t.deepEqual(getParams()[stringKey].value, 'bar'); t.throws( () => updateString(18.1),