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

decode delegation query response fails in Compartment due to undefined Decimal #9408

Closed
dckc opened this issue May 24, 2024 · 3 comments · Fixed by #9583
Closed

decode delegation query response fails in Compartment due to undefined Decimal #9408

dckc opened this issue May 24, 2024 · 3 comments · Fixed by #9583
Assignees
Labels
bug Something isn't working installation-bundling The bundling of installations

Comments

@dckc
Copy link
Member

dckc commented May 24, 2024

Describe the bug

While working on getDelegations for the unbondExample.contract.js, (#9070, #8863), I'm trying to use QueryDelegatorDelegationsResponse.decode(...) from @agoric/cosmic-proto inside a contract, but it fails due to Decimal from @cosmjs/math being undefined.

To Reproduce

I reduced it to running this in a compartment:

import { Decimal } from '@cosmjs/math';

export const fun = () => {
  const e18 = 10n ** 18n;
  return Decimal.fromAtomics(`${e18}`, 18);
};

3aa40db has a test for that and a test for using QueryDelegatorDelegationsResponse.decode(...).

  1. check out 3aa40db
  2. yarn; yarn build
  3. cd packages/orchestration
  4. yarn test test/tx-encoding.test.ts -m 'compartment*'
  5. 2 tests fail with Cannot read properties of undefined
yarn test run log
agoric-sdk/packages/orchestration$ yarn test  test/tx-encoding.test.ts -m 'compartment*'
yarn run v1.22.19
$ ava test/tx-encoding.test.ts -m 'compartment*'

  ✘ [fail]: compartment use of Decimal Rejected promise returned by test
    ℹ REJECTED from ava test("compartment use of Decimal"): (TypeError#1)
    ℹ TypeError#1: Cannot read properties of undefined (reading 'fromAtomics')
    ℹ   at fun (.../orchestration/test/decimalFun.js:5:16)
        at packages/orchestration/test/tx-encoding.test.ts:1:2989
      
  ✘ [fail]: compartment use of getDelegations() response Rejected promise returned by test
    ℹ REJECTED from ava test("compartment use of getDelegations() response"): (TypeError#2)
    ℹ TypeError#2: Cannot read properties of undefined (reading 'fromUserInput')
    ℹ   at Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/staking.js:982:16)
        at Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/staking.js:1587:12)
        at Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/query.js:991:20)
        at plz (.../orchestration/test/delegationFun.js:19:50)
        at packages/orchestration/test/tx-encoding.test.ts:1:3197
      
  ─

  compartment use of Decimal
  Rejected promise returned by test. Reason:

  TypeError {
    message: 'Cannot read properties of undefined (reading \'fromAtomics\')',
  }

  › fun (.../orchestration/test/decimalFun.js:5:16)
  › packages/orchestration/test/tx-encoding.test.ts:1:2989



  compartment use of getDelegations() response
  Rejected promise returned by test. Reason:

  TypeError {
    message: 'Cannot read properties of undefined (reading \'fromUserInput\')',
  }

  › Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/staking.js:982:16)
  › Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/staking.js:1587:12)
  › Object.encode (.../cosmic-proto/dist/codegen/cosmos/staking/v1beta1/query.js:991:20)
  › plz (.../orchestration/test/delegationFun.js:19:50)
  › packages/orchestration/test/tx-encoding.test.ts:1:3197

  ─

  2 tests failed
error Command failed with exit code 1.

Expected behavior

QueryDelegatorDelegationsResponse.decode(...) works inside a compartment just like it does outside a compartment.

Platform Environment

  • what version of the Agoric-SDK are you using? 3aa40db

cc @kriskowal @turadg @0xpatrickdev

@dckc dckc added the bug Something isn't working label May 24, 2024
@kriskowal
Copy link
Member

I have reproduced the error locally but not yet isolated a defect.

Given isolate-me.js

import { Decimal } from "@cosmjs/math";
import * as math from "@cosmjs/math";
console.log(Decimal);
console.log(math);
console.log(math.Decimal);

I get the correct behavior from Node.js and incorrect behavior from importBundle from @endo/import-bundle and importLocation from @endo/compartment-mapper. This is the narrower test harness that reproduces the failure:

import "ses";
import url from "url";
import fs from "fs";
import { importLocation } from "@endo/compartment-mapper";
import { makeReadPowers } from "@endo/compartment-mapper/node-powers.js";
const readPowers = makeReadPowers({ url, fs });
const location = new URL("isolate-me.js", import.meta.url).href;
importLocation(readPowers, location, {
  globals: { console },
});

The defect occurs at the boundary between an ESM/MJS module importing the CJS decimal module, which is compiled from TS. The generated code assigns to exports.Decimal twice. First, with void 0, then with the Decimal class. I attempted to isolate this case:

Given b.cjs

Object.defineProperty(exports, "__esModule", { value: true });
exports.b = void 0;
class B {}
exports.b = B;

Given a.mjs

import { b } from "./b.cjs";
console.log(b);

And this case works correctly in isolation under all of Node.js, importBundle, and importLocation.

@kriskowal
Copy link
Member

kriskowal commented Jun 22, 2024

The culprit turns out to be the intermediate index.js (CommonJS compiled from TS) that reexports Decimal using Object.defineProperty(exports… with a getter. This is hard to trap to propagate the live binding but I have a fix endojs/endo#2330

@dckc
Copy link
Member Author

dckc commented Jun 22, 2024

Thanks for the diagnosis!

Our approach to endo compat so far has mostly been to patch the offending parts of cosmjs; for example, bn.js patch

I'm not sure what would be the preferred tweak, but this seems to alleviate the symptoms:

$ cat patches/@cosmjs+math+0.32.3.patch
diff --git a/node_modules/@cosmjs/math/build/index.js b/node_modules/@cosmjs/math/build/index.js
index 1f812f6..41f6398 100644
--- a/node_modules/@cosmjs/math/build/index.js
+++ b/node_modules/@cosmjs/math/build/index.js
@@ -2,7 +2,8 @@
 Object.defineProperty(exports, "__esModule", { value: true });
 exports.Uint64 = exports.Uint53 = exports.Uint32 = exports.Int53 = exports.Decimal = void 0;
 var decimal_1 = require("./decimal");
-Object.defineProperty(exports, "Decimal", { enumerable: true, get: function () { return decimal_1.Decimal; } });
+// Object.defineProperty(exports, "Decimal", { enumerable: true, get: function () { return decimal_1.Decimal; } });
+exports.Decimal = decimal_1.Decimal;
 var integers_1 = require("./integers");
 Object.defineProperty(exports, "Int53", { enumerable: true, get: function () { return integers_1.Int53; } });
 Object.defineProperty(exports, "Uint32", { enumerable: true, get: function () { return integers_1.Uint32; } });

p.s. here's hoping for a .ts compiler that erases the types and does nothing else; especially no conversion to .cjs

@mergify mergify bot closed this as completed in #9583 Jun 28, 2024
@mergify mergify bot closed this as completed in 4874a7f Jun 28, 2024
mergify bot added a commit that referenced this issue Jul 12, 2024
refs: #9408

## Description
cosmjs deps add a lot of size and complicates our builds (#9583) At runtime we only need `Decimal` so this vendors that (just the subset necessary) and drops all other runtime deps.

### Security Considerations
less supply chain

### Scaling Considerations
none

### Documentation Considerations
Maybe get this upstream to reduce maintenance.

### Testing Considerations
CI suffices?

### Upgrade Considerations
None, not on chain.
kriskowal added a commit to endojs/endo that referenced this issue Jul 17, 2024
#2330)

…S from set property of CJS

Refs: Agoric/agoric-sdk#9408

## Description

If a modern JavaScript module imports a named binding from a CommonJS
module, we can hardly guarantee that it will always work. Outrageous
heroics make it work under most circumstances using a heuristic lexical
analysis of the CommonJS module to determine which names should exist on
the module’s internal namespace. Then, the CommonJS runtime must account
for all variety of shenanigans that might cause the binding for that
name to change. These include assignment to property names on `exports`,
reassignment to `module.exports`, and `defineProperty` on the _original_
`exports` object. These changes must be reflected both on the module
namespace object and on the `default` property thereof in _in every case
where this is in fact possible_.

Consider a CJS module (2.cjs) that exports a name:

```js
exports.meaning = 42;
```

Then, consider a TypeScript module (1.ts) that reexports this name:

```ts
export { meaning } from './2.cjs';
```

Which gets compiled down to a CommonJS module (1.cjs):

```js
const universe = require('./1.cjs');
Object.defineProperty(exports, 'meaning', {
  enumerable: true,
  get() {
    return universe.meaning
  }
});
```

Which gets imported by an ESM by name (0.mjs):

```js
import { meaning } from './1.cjs';
```

This has no right to work. We can trivially copy the getter from 1.cjs
to the `default` export but the internal namespace object we use to
emulate ESM in SES emits live binding notifications for every named
property and doesn’t suffer `defineProperty` well. So, in the CommonJS
emulation, we must attempt to propagate the current value from the
getter to the module environment record after the module initializes.

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

None. Folks already assume this works. It works in Node.js. Code exists
that relies on it.

### Testing Considerations

We have an existing test that exercises the case that the getter under a
defineProperty can’t be expected to succeed on the stack of
defineProperty.

### Compatibility Considerations

This increases ecosystem compatibility.

### Upgrade Considerations

None.

### PS

It didn’t have to be like this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working installation-bundling The bundling of installations
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants