-
Notifications
You must be signed in to change notification settings - Fork 208
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
jessie-check contracts #5497
base: master
Are you sure you want to change the base?
jessie-check contracts #5497
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some of these changes are not semantics-preserving:
- Adding + in
rec[+kwd]
coerceskwd
to anumber
. new Error(...)
->assert.fail(...)
changes control flow.
collateralSeat.getCurrentAllocation()[collateralKeyword], | ||
collateralSeat.getCurrentAllocation()[+collateralKeyword], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+
coerces to a number. I don't think this can work. Does it pass tests?
I suggest an extractFieldFromKeywordRecord(record, key)
library function imported from a module with keyword record helpers. Perhaps with a more concise name. Or perhaps there's some Object.getProperty
standard JS function that would work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was (intentionally) blindly following what the rule said to do,
106:9 error arbitrary computed property names are not allowed in Jessie; use leading '+' no-restricted-syntax
what should it say instead?
splitting out into a helper function would still be running the computed property name, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The helper function would not be in a @jessie-check
file. Just like makeSet
and such.
what should it say instead?
Maybe "if you're trying to do numeric indexing, prefix the index with a +". I agree that diagnostic is misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The helper function would not be in a @jessie-check file. Just like makeSet and such.
Sure. But won't it still run in a Jessie environment? I thought jessie-check was for ensuring that something can safely run in a Jessie environment. Below you show that curlies can't run at all. Is the arbitrarily computed property rule the same? If so, won't the imported module have the same problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But won't it still run in a Jessie environment?
No. Again, just like makeSet.
Jessie has 2 purposes:
- Reduce hazards in writing contracts
- Ease language implementation burden
In our system as a whole, we don't use Jessie in our contracts for the 2nd purpose. We support the whole (hardened) JS language at runtime.
Someone else might, for example, build a Jessie interpreter targeting WASM for use on some other platform. They could use the @jessie-check modules without modification. But they would have to do something platform-specific for makeSet and getFieldFromKeywordRecord. For the latter, perhaps makeMap(Object.entries(rec)).get(kw). We don't want to do that because it's O(n). But it's worth putting it in a library module that's reviewed with different expectations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the primary purpose of Jessie, and the only purpose for which we are currently using it, is to help us write more reliable and analyzable code. To be fully pedantic, the // @jessie-check
enforces only Tiny-SES rather than Jessie, where the difference is the primordials the code is allowed to assume exists. Aside from the primordials the code can assume exists, Jessie and Tiny-SES are identical.
In practice that was one too many distinctions so we use Jessie to describe both. In practice, when we say we're writing most of our code in Jessie, we do not generally mean to imply that the code cannot assume the presence of, for example Reflect
, which is part of SES but not Jessie.
We eventually intend to support Jessie as a std for universal mobile code, where a Jessie interpreter can be implemented simply in any normal imperative language. For that purpose, this difference will matter, as that implementation can only be simple if it omits many of the SES primordials including Reflect
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of the square bracket indexing rules is to make code that is using computed property lookup to index into arrays not be able to be tricked into looking up, for example, inherited method properties. In general, we want code that is using []
safely to index into a collection of some sort safely to be analyzably safe. Zoe introduced the notion of keywords for exactly the same reason, noting that at least all the primordial method names begin with a lowercase character, and so cannot be mistaken for keywords.
If we were willing to have Jessie understand that keywords are safe computed indexes, we could similarly enable syntax that enforced that the index expression evaluated to a keyword. We have not bothered to do that, and have instead just tried to use []
safely by feeding it only keywords, which a static or dynamic check ensuring this.
So we have choices wrt these uses of record[keyword]
in what would otherwise be Jessie code:
- Have Jessie drop the
[]
restrictions. Yes, we should consider this. - Rewrite such code to
Reflect.get(record, keyword)
which means the same thing given the availability ofReflect
which is technically part of SES and Tiny-SES but not Jessie. - Introduce a keyword enforcing helper function, upgrade Jessie to allow it, and use it for keyword lookup in the same way we use
+
for array lookup. - Do we have a
// @jessie-expect-error
directive analogous to// @ts-expect-error
? If so, you could use it to allow expressions you know to be safe -- perhaps even adding to the comment directive an explanation of why you believe it to be safe.
The path of least resistance for now is probably rewriting to Reflect.get(record, keyword)
. That's what I'd do, but feel free to consider the other alternatives.
throw new Error('insufficient reserves for that transaction'); | ||
throw assert.fail('insufficient reserves for that transaction'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw
is redundant with assert.fail
, yes? I think we have an assert.error
constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the throw assert.fail
should normally be just assert.fail
. Because assert.fail
@returns {never}
the two should also be equivalent to typescript.
I have encountered rare circumstances where the throw
was still necessary to inform some static check (probably es-lint) that control flow would not proceed normally past that point. Hope that you don't run into that.
if (_roundId <= add(lastStarted, restartDelay) && lastStarted !== 0n) | ||
if (_roundId <= add(lastStarted, restartDelay) && lastStarted !== 0n) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes a hazard that motivates the curly
rule. (The other motivation is simplifying the Jessie grammar, I think.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would hope that 'jessie-check' checks what's known to fail and doesn't get into style decisions.
in the absence of curlies does Jessie fail to parse?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes:
In Jessie, only blocks are accepted as arms
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation is writing reliable code. Before I religiously followed this style I would occasionally screw up by adding a statement to an indented block, not noticing that the indented block was not delimited by curlies.
There are also some strange language edge cases associated with curly-less control-flow arms. For example, what if it brings a variable into scope? What is the scope of the variable? What looks like the scope of the variable? Bad for these two answers to disagree.
throw buyerSeat.fail(new Error(rejectMsg)); | ||
throw buyerSeat.fail(assert.fail(rejectMsg)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assert.fail
throws. It's not a replacement for new Error
.
I suggest just taking out new
. Error(...)
and new Error(...)
mean the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better would be assert.error
. Like Error(...)
it makes and returns an error. Like the other assert
methods, you can use an
X`literal stuff ${redacted value}`
for better diagnostics. Also, all the assert
methods create the error object at the same line of code that becomes a useful place to put a breakpoint.
new Error(`The proposal did not match either a buy or sell order.`), | ||
assert.fail(`The proposal did not match either a buy or sell order.`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
again, this is not semantics-preserving.
I suggest just changing new Error(
to Error(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or assert.error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM AWESOME! I've been looking forward to this day for years. I am astonished how close so much of our code already was to Jessie prior to any automated checking!
Thanks for doing this!!
collateralSeat.getCurrentAllocation()[collateralKeyword], | ||
collateralSeat.getCurrentAllocation()[+collateralKeyword], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the primary purpose of Jessie, and the only purpose for which we are currently using it, is to help us write more reliable and analyzable code. To be fully pedantic, the // @jessie-check
enforces only Tiny-SES rather than Jessie, where the difference is the primordials the code is allowed to assume exists. Aside from the primordials the code can assume exists, Jessie and Tiny-SES are identical.
In practice that was one too many distinctions so we use Jessie to describe both. In practice, when we say we're writing most of our code in Jessie, we do not generally mean to imply that the code cannot assume the presence of, for example Reflect
, which is part of SES but not Jessie.
We eventually intend to support Jessie as a std for universal mobile code, where a Jessie interpreter can be implemented simply in any normal imperative language. For that purpose, this difference will matter, as that implementation can only be simple if it omits many of the SES primordials including Reflect
.
collateralSeat.getCurrentAllocation()[collateralKeyword], | ||
collateralSeat.getCurrentAllocation()[+collateralKeyword], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The purpose of the square bracket indexing rules is to make code that is using computed property lookup to index into arrays not be able to be tricked into looking up, for example, inherited method properties. In general, we want code that is using []
safely to index into a collection of some sort safely to be analyzably safe. Zoe introduced the notion of keywords for exactly the same reason, noting that at least all the primordial method names begin with a lowercase character, and so cannot be mistaken for keywords.
If we were willing to have Jessie understand that keywords are safe computed indexes, we could similarly enable syntax that enforced that the index expression evaluated to a keyword. We have not bothered to do that, and have instead just tried to use []
safely by feeding it only keywords, which a static or dynamic check ensuring this.
So we have choices wrt these uses of record[keyword]
in what would otherwise be Jessie code:
- Have Jessie drop the
[]
restrictions. Yes, we should consider this. - Rewrite such code to
Reflect.get(record, keyword)
which means the same thing given the availability ofReflect
which is technically part of SES and Tiny-SES but not Jessie. - Introduce a keyword enforcing helper function, upgrade Jessie to allow it, and use it for keyword lookup in the same way we use
+
for array lookup. - Do we have a
// @jessie-expect-error
directive analogous to// @ts-expect-error
? If so, you could use it to allow expressions you know to be safe -- perhaps even adding to the comment directive an explanation of why you believe it to be safe.
The path of least resistance for now is probably rewriting to Reflect.get(record, keyword)
. That's what I'd do, but feel free to consider the other alternatives.
throw new Error('insufficient reserves for that transaction'); | ||
throw assert.fail('insufficient reserves for that transaction'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the throw assert.fail
should normally be just assert.fail
. Because assert.fail
@returns {never}
the two should also be equivalent to typescript.
I have encountered rare circumstances where the throw
was still necessary to inform some static check (probably es-lint) that control flow would not proceed normally past that point. Hope that you don't run into that.
|
||
import { Far } from '@endo/marshal'; | ||
import { assert } from '@agoric/assert'; | ||
import { AmountMath, isNatValue } from '@agoric/ertp'; | ||
import { makeMap } from 'jessie.js'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@michaelfig is that the right way to import from the Jessie std library? No @something/something
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, absolutely! 'jessie.js'
is the stdlib package.
if (_roundId <= add(lastStarted, restartDelay) && lastStarted !== 0n) | ||
if (_roundId <= add(lastStarted, restartDelay) && lastStarted !== 0n) { | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The motivation is writing reliable code. Before I religiously followed this style I would occasionally screw up by adding a statement to an indented block, not noticing that the indented block was not delimited by curlies.
There are also some strange language edge cases associated with curly-less control-flow arms. For example, what if it brings a variable into scope? What is the scope of the variable? What looks like the scope of the variable? Bad for these two answers to disagree.
if (startingRound > _roundId) { | ||
return 'not yet enabled oracle'; | ||
} | ||
if (oracleStatuses.get(_oracle).endingRound < _roundId) { | ||
return 'no longer allowed oracle'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code before your change here is an example of the case that scares me: where the arm is on its own line and indented.
throw buyerSeat.fail(new Error(rejectMsg)); | ||
throw buyerSeat.fail(assert.fail(rejectMsg)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better would be assert.error
. Like Error(...)
it makes and returns an error. Like the other assert
methods, you can use an
X`literal stuff ${redacted value}`
for better diagnostics. Also, all the assert
methods create the error object at the same line of code that becomes a useful place to put a breakpoint.
new Error(`The proposal did not match either a buy or sell order.`), | ||
assert.fail(`The proposal did not match either a buy or sell order.`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or assert.error
What is the status of this? Is there at least an easy subset that could move forward quickly? |
It's a draft PR that passes 5 out of 30 CI checks. It doesn't have a ticket and if it did I don't think it would be required for MN-1, so it's prioritized behind MN-1 release.
Probably the Again, TMK it's not required for MN-1 but if it is let's prioritize it. |
It's not |
Description
A discussion on #4770 suggested we should be using
@jessie-check
in all our contracts. This PR attempts that.TODO
new Uint8Array
do/while statements are not allowed in Jessie
(why is this a rule?)generators are not allowed in Jessie
. Would it suffice to pull this out of the contract? there'd still be processTranchesUnexpected
awaitexpression (not top of function body)
without changing semantics (several in liquidateIncrementally)curly
rule. Why is this in Jessie?Security Considerations
Documentation Considerations
Testing Considerations