-
Notifications
You must be signed in to change notification settings - Fork 75
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
feat(import-bundle): Test bundle format #2719 #2735
Conversation
058918b
to
c18ed26
Compare
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 think we can be a little more conformant without a much higher cost
# Next release | ||
|
||
- Adds support for `test` format bundles, which simply return a promise for an | ||
object that resembles a module exports namespace with the objects specified |
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.
Oh interesting in French it's "ressemble"
packages/import-bundle/src/index.js
Outdated
return Object.assign( | ||
Object.create(null, { | ||
[Symbol.toStringTag]: { | ||
value: 'Module', | ||
writable: false, | ||
enumerable: false, | ||
configurable: false, | ||
}, | ||
}), | ||
exports, | ||
); |
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 conformance feedback:
Object.assign
would copy any string or symbol own properties, but really we should only allow string properties as exports.- It does only copy enumerable properties, and sets values (doesn't copy accessors), which is nice.
- The exports string properties are supposed to be sorted
- The symbol property is supposed to enumerate after the string export properties
- The namespace object should be
sealed
- While the exotic behavior is to have properties
writable: true
and deny any external changes to the properties, I think we should be fine with non-writable regular data properties- This would effectively make the namespace object frozen
There is a question of whether we should trigger getters on the test bundle exports at all, or we should deny this. In a perfect world we'd detect proxies and deny that as well.
packages/import-bundle/src/index.js
Outdated
* as JSON or passable. | ||
* @param {Record<PropertyKey, unknown>} exports | ||
*/ | ||
export const bundleTestExports = exports => ({ |
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.
We should probably enforce string only data properties here too to catch mistakes early.
looks like fun! To partially validate that it should help with these sorts of things...
I did import test from '@endo/ses-ava/prepare-endo.js';
import { importBundle } from '../src/index.js';
import * as f from '@agoric/fast-usdc/src/fast-usdc.contract.js';
test('get live contract code using importBundle', async t => {
const bundle = {
moduleFormat: 'test',
[Symbol.for('exports')]: f,
};
const ns = await importBundle(bundle);
t.log(ns);
t.is(typeof ns.start, 'function');
}); works as expected: ~/projects/endo/packages/import-bundle$ yarn test test/contract-fun.test.js
✔ get live contract code using importBundle
ℹ @Module {
contract: AsyncFunction {},
meta: {
customTermsShape: {
usdcDenom: Object @match:string { … },
},
privateArgsShape: {
agoricNames: Object @match:kind { … },
assetInfo: Object @match:arrayOf { … },
chainInfo: Object @match:recordOf { … },
feeConfig: Object { … },
localchain: Object @match:kind { … },
marshaller: Object @match:kind { … },
orchestrationService: Object @match:kind { … },
poolMetricsNode: Object @match:kind { … },
storageNode: Object @match:kind { … },
timerService: Object @match:kind { … },
},
},
start: AsyncFunction {},
}
─
1 test passed |
6b9805d
to
bf80971
Compare
I’ve added an early error detection guard for symbol-named properties to the |
bf80971
to
05577a0
Compare
packages/import-bundle/src/index.js
Outdated
@@ -124,6 +174,21 @@ export async function importBundle(bundle, options = {}, powers = {}) { | |||
} | |||
} | |||
|
|||
/** | |||
* A utility function for producing test bundles, which are not seerializable |
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.
* A utility function for producing test bundles, which are not seerializable | |
* A utility function for producing test bundles, which are not serializable |
packages/import-bundle/src/index.js
Outdated
// We emulate a module exports namespace object, which has certain invariants: | ||
// Property names are only strings, so we will ignore symbol-named properties. | ||
// All properties are enumerable, so we will ignore non-enumerable properties. | ||
// All properties are non-writable values. |
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.
Nit: String keys on Module namespace objects are actually all writable
but we purposely deviate here.
packages/import-bundle/src/index.js
Outdated
const symbols = Object.getOwnPropertySymbols(exports); | ||
symbols.length > 0 && | ||
Fail`exports must not have symbol-named properties, got: ${symbols.map(String).join(', ')}`; |
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.
Let's be flexible and allow a Symbol.toStringTag
own prop so that a module namespace object can be used directly.
return { | ||
moduleFormat: 'test', | ||
[Symbol.for('exports')]: exports, | ||
}; |
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.
Should we freeze the object (not harden it since we don't care about the stability of exports at this point)
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.
That would be a departure from the norm for bundleSource
. I think it’s reasonable to defer that choice to the caller. But, if we at some point reörganize the bundler infrastructure and create a new entry-point, I’d be open to changing the contract. Other reasons to consider reorganizing around an @endo/bundler
module maybe someday would be to consolidate @endo/import-bundle
, @endo/bundle-source
, and @endo/check-bundle
into a single package so they can more easily cross-test, do a better job of isolating powers and decoupling them from the underlying platform (like node-powers.js
in the Compartment Mapper), remove deprecated powers like the ones taken by Rollup, one README for all the bundle formats and how to read and write them, &c. The list is growing but not pressing.
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
5a383a2
to
17ec018
Compare
Closes: #2719
Description
This change introduces a new bundle format to simplify tests for subsystems that take a bundle that they will ultimately use
importBundle
to extract a mock module exports namespace.The new bundle format takes care to render impossible any accidental or malicious use of a test h
To that end, the format captures the mock exports namespace on a symbol-named property of then bundle, so it will be elided or rejected when serialized.
And, to compensate for the cryptic protocol, this change provides a tiny utility function for making test bundles from mock exports.
Security Considerations
Existing bundle importers expect only to be able to confine execution of local, serialized bundles.
This change takes some care to maintain the expectation by limiting exposure to live objects in a way that’s only reachable by test code.
If an adversary were able to present a test bundle, it’s not clear that this would constitute any escalation in privilege, given that an attacker would need to arrange for a live object by some other escalator.
Scaling Considerations
This change introduces a very small amount of new code to the Agoric kernel and, out of an abundance of caution, is not reached in the common case of importing the endoZipBase64 bundle format.
Documentation Considerations
The change includes relevant documentation in NEWS.md and README.md for the import-bundle package. This is a new platform API and may be relevant in the context of tutorials for testing applications on Agoric and other Endo platforms.
Testing Considerations
Includes unit tests exercising the new behavior and invariants.
Compatibility Considerations
New features, no breaking changes.
Upgrade Considerations
None.