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

Manage parameters for a contract #3208

Merged
merged 11 commits into from
Jun 18, 2021
Merged

Manage parameters for a contract #3208

merged 11 commits into from
Jun 18, 2021

Conversation

Chris-Hibbert
Copy link
Contributor

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?

@Chris-Hibbert Chris-Hibbert added enhancement New feature or request Small Governance Governance labels May 28, 2021
@Chris-Hibbert Chris-Hibbert added this to the Beta Phase 4: Governance milestone May 28, 2021
@Chris-Hibbert Chris-Hibbert requested a review from dtribble May 28, 2021 22:38
Copy link
Member

@dtribble dtribble left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't have actions against these changes or notification of the changes. I need to see the context to comment further.

STRING: 'string',
RATIO: 'ratio',
AMOUNT: 'amount',
BRAND: 'brand',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need an "instance" or "installation" reference? We could just use boardID for that, but might want direct support for "boardID" usage.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on @katelynsills' comments, I think these will be handles.

I'll add a case for handles and one for untyped ANY.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we should only ever use boardIds where we can't pass objects. So anything on-chain should not be using a boardId and should use the handle instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added instance and installation as (currently unverified) types. No sign of boardIDs.

const types = makeStore('name');

paramDesc.forEach(({ name, value, type }) => {
assert.typeof(name, 'string');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems unnecessary given that looking it up will do the same assert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropped.

Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small suggestions, most of which are optional. Looks really good!

"parsers": {
"js": "mjs"
},
"main": "src/missing.js",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the actual file here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update it to param-manager.js for now, but it should eventually be the registrar or something else. This won't be the most important thing for long.

},
"main": "src/missing.js",
"engines": {
"node": ">=11.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be "node": ">=14.15.0", since 11 won't actually work, as far as I know.

Suggested change
"node": ">=11.0"
"node": ">=14.15.0"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Both treasury and zoe still have 11.0. I didn't check anywhere else.

"@agoric/notifier": "^0.3.14",
"@agoric/promise-kit": "^0.2.13",
"@agoric/store": "^0.4.14",
"@agoric/swingset-vat": "^0.17.2",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a dependency. Could you check the rest to make sure they are used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed everything that isn't currently in use. I'll add them individually as I introduce dependencies.

"dependencies": {
"@agoric/assert": "^0.3.0",
"@agoric/bundle-source": "^1.3.7",
"@agoric/captp": "^1.7.13",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a dependency

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropped.

"@agoric/install-ses": "^0.5.13",
"ava": "^3.12.1",
"esm": "^3.2.25",
"ses": "^0.12.7"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a dependency? I don't see it used anywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gone

},
};

return { publicFacet, manager };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm torn on this name, since it's potentially confused with the contract instance publicFacet. But on the other hand, it might be nice for people to be familiar with the pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by it, worrying that it would need to be accessed async. So I advocate read or params.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to params.


/**
* @typedef {Object} ParamManager
* @property {(name: string, value: any) => void} update
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we restrict types, we could tighten up this any to be the types we support. This would help developers a lot because they could find bugs statically before deploying (or testing locally, I suppose).

Suggested change
* @property {(name: string, value: any) => void} update
* @property {(name: string, value: ParamValue) => void} update

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I thought we had gotten everything that devs might want to put in a contract, I would be in favor of this, but I think devs are going to need an escape hatch. Having the explicit ParamType.ANY lets them clearly escape our limits. We should try to be welcoming to new suggestions, but there's clearly going to be a release cycle before we can get them support for new types.


/**
* @typedef {Object} ParamManagerPublic
* @property {(name: string) => any} lookup
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @property {(name: string) => any} lookup
* @property {(name: string) => ParamValue} lookup

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* }} ParamDescription
*/

// type: string above should be type: ParamType
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, why not just use ParamType as the type rather than string?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typescript was fighting with me. I got it this time.


/**
* @typedef {{name: string,
* value: any,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* value: any,
* value: ParamValue,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@dtribble dtribble left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think publicFacet needs to be changed, but the rest of discussion.

ANY: 'any',
BIGINT: 'bigint',
BRAND: 'brand',
HANDLE: 'handle',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those seems plausible, assuming Zoe is the manager of such things. It does make the test async, though, which introduces a lot of complexity.

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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. We should stick with Amounts, and just e.g., assert that they are of a particular Brand if that's relevant.

// 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),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the non-allocaating version in asserts :). a coerce often allocates

},
};

return { publicFacet, manager };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was confused by it, worrying that it would need to be accessed async. So I advocate read or params.

type: ParamType.NAT,
},
]);
t.is(publicFacet.lookup('number'), 13);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah params looks like a good name for that thing :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@katelynsills
Copy link
Contributor

Adding my feedback from the discussion this morning based on what @dtribble brought up:

  1. We should change the manager API to allow for POLA patterns by default. Instead of holding the manager meaning that you can change every parameter that you know of (or can guess the name of), it should be easy to attenuate the manager object such that only one parameter or groups of parameters can be changed.

During the meeting, we discussed a few ways to do this. The first option was return one manager object per parameter. I might be misunderstanding this, but this seems like it would create a ton of objects unnecessarily, and it would be difficult to recombine the objects to allow groups of parameters to be changed at once. I would suggest a second option, which is to return one manager object, but instead of update(name, value), the manager object has methods for updating each name:

const manager = {
   updateFeeRatio: value => ...,
   updateContractName: value => ...,
}

Then in the documentation we can explain how to attenuate:

const attenuatedManager = {
  updateFeeRatio: manager.updateFeeRatio,
};
  1. We should optimize for the least number of lookups when reading the values, so that UI and other downstream clients don't need to query the chain to look up a value. This could mean having a function that returns a JavaScript object:
publicFacet.getParams();

// returns
{ 
    myName: {
        name: 'myName',
        type: 'string',
        value: 'Alice',
    },
   anotherName: {
....

rather than definedNames or lookup

Copy link
Contributor Author

@Chris-Hibbert Chris-Hibbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't remove ParamType.ANY. LMK if we don't need to leave this escape hatch for 3rd party devs.

"strictNullChecks": true,
"moduleResolution": "node",
},
"include": ["src/**/*.js", "test/**/*.js", "exported.js", "globals.d.ts"],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, good catch.

"parsers": {
"js": "mjs"
},
"main": "src/missing.js",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update it to param-manager.js for now, but it should eventually be the registrar or something else. This won't be the most important thing for long.

},
"main": "src/missing.js",
"engines": {
"node": ">=11.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done. Both treasury and zoe still have 11.0. I didn't check anywhere else.

"@agoric/notifier": "^0.3.14",
"@agoric/promise-kit": "^0.2.13",
"@agoric/store": "^0.4.14",
"@agoric/swingset-vat": "^0.17.2",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed everything that isn't currently in use. I'll add them individually as I introduce dependencies.

"ses": "^0.12.7"
},
"files": [
"bundles/",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dropped

},
};

return { publicFacet, manager };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed to params.


/**
* @typedef {Object} ParamManagerPublic
* @property {(name: string) => any} lookup
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


/**
* @typedef {{name: string,
* value: any,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

STRING: 'string',
RATIO: 'ratio',
AMOUNT: 'amount',
BRAND: 'brand',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added instance and installation as (currently unverified) types. No sign of boardIDs.

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated comment

harden(ParamType);

const assertType = (type, value, name) => {
if (!type) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just pointing to this directly because I think the comment got lost.

I think this is unnecessary since we already have the ParamType.ANY which is handled below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

@katelynsills
Copy link
Contributor

One last comment - instead of ParamType.ANY, should it be UNKNOWN? https://stackoverflow.com/a/57356103 A JSDoc/TypeScript type of unknown requires the recipient to type it before using, but any does not.

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?
dependency reduction
support instance and installation rather than handle
support NAT type which must be a bigint
rename publicFacet to params
change 'any' to 'unknown'
drop an unneeded guard clause
a single getParams() describes all parameters
Thanks to Michael Fig.
Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The most important changes are changing the dependencies, moving the keyword check up, and not creating new describer functions.

yarn.lock Outdated
@@ -2,6 +2,13 @@
# yarn lockfile v1


"@agoric/assert@^0.3.1":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, most of the changes in this yarn.lock shouldn't be happening. Before we merge this, can we make sure we've gotten the package dependencies right? For instance, an old version of Nat is being installed, and multiple Agoric packages are being added to the root yarn.lock even though they are already widely used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

};
// @ts-ignore illegal value for testing
t.throws(() => buildParamManager([stuffDescription]), {
message: 'unknown type guard "quote"',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is kind of strange. Do we mean "unrecognized type"?

Suggested change
message: 'unknown type guard "quote"',
message: 'unrecognized type "quote"',

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

value: 314159n,
type: ParamType.NAT,
};
const { getParams, updateNat: _updateNat, updateStuff } = buildParamManager([
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const { getParams, updateNat: _updateNat, updateStuff } = buildParamManager([
const { getParams, updateStuff } = buildParamManager([

Seems to be unused. Is this intentional? If so, let's add a comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unused, but I wanted it to be visible that updateNat is defined. Perhaps I'll just use it instead of making a big deal of it.

*/

/**
* @typedef {Object} ParamDetails
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type doesn't seem to be used at all. Seems like ParamDescription is the same thing and is the one used.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

* @typedef {Object} ParamManagerBase
* @property {() => Record<Keyword,ParamDescription>} getParams
*
* @typedef {{ [updater: string]: (arg: any) => void }} ParamManagerUpdaters
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be:

Suggested change
* @typedef {{ [updater: string]: (arg: any) => void }} ParamManagerUpdaters
* @typedef {{ [updater: string]: (arg: ParamValue) => void }} ParamManagerUpdaters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. Done

case ParamType.AMOUNT:
// It would be nice to have a clean way to assert something is an amount.
assert(
AmountMath.isEqual(value, value),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use isEqual? AmountMath.coerce is the clean way to do it. We just don't have a brand to test against objectively, so the call becomes:

Suggested change
AmountMath.isEqual(value, value),
AmountMath.coerce(value.brand, value),

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +24 to +25
INSTANCE: 'instance',
INSTALLATION: 'installation',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we test INSTANCE and INSTALLATION?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

assertType(type, value, name);
// we want to create function names like updateFeeRatio(), so we insist that
// the name has Keyword-nature.
assertKeywordName(name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Can we move this up to line 86? I think we don't want to use the name as a property accessor into values until after this check.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 101 to 106
const describer = () => ({
name,
type,
value: values[name].value,
});
describers.push(describer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems very strange to have a function per value for making descriptions. Is there a reason we need this? Or could we have a single function that knows how to turn values into what should be returned from getParams?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 112 to 115
describers.forEach(d => {
const description = d();
descriptions[description.name] = description;
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't need describers and can just iterate over values directly. This is making and using a ton of functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. Done

dependency trimming
better test for AMOUNT.
improve some error messages
narrow some type declarations
add tests for INSTANCE and INSTALLATION
Copy link
Contributor

@katelynsills katelynsills left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but please change esm to use the Agoric version before merging. Looks great!

yarn.lock Outdated
esm@agoric-labs/esm#Agoric-built:
esm@^3.2.25, esm@agoric-labs/esm#Agoric-built:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should only be esm@agoric-labs/esm#Agoric-built

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

"devDependencies": {
"@agoric/install-ses": "^0.5.13",
"ava": "^3.12.1",
"esm": "^3.2.25"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"esm": "^3.2.25"
"esm": "agoric-labs/esm#Agoric-built",

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@Chris-Hibbert Chris-Hibbert merged commit 8447993 into master Jun 18, 2021
@Chris-Hibbert Chris-Hibbert deleted the gov-lib-3186 branch June 18, 2021 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Governance Governance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Contract-embeddable library for managed parameters (Governance)
3 participants