Skip to content

Commit 1ae75bd

Browse files
committed
fix(xdr): throw on legacy union construction at the call site
1 parent 38a1932 commit 1ae75bd

119 files changed

Lines changed: 1636 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ A breaking change will get clearly marked in this log.
55
## Unreleased
66

77
### Fixed
8+
* The legacy `new xdr.SomeUnion(discriminant, value)` form throws a `TypeError` naming the arm factory to call instead, on all 115 generated union types ([#1640](https://github.com/stellar/js-stellar-sdk/issues/1640)). Unions are abstract base classes exported as values, and `abstract` is erased at runtime, so the pre-v17 call built a base instance that silently discarded both arguments — `Object.keys(m)` was `[]` and `m.type` was `undefined`. It only failed once something serialized it, with `TypeError: this.toXdrObject is not a function` thrown from inside the SDK, naming neither the union nor the call that created it; a `new` in a module-level constant surfaced as the whole module failing at import. TypeScript already rejected the form (`TS2511: Cannot create an instance of an abstract class`), so this reaches plain JavaScript and TypeScript run without a type-check pass. `xdr.Int64` / `xdr.Uint64` / `xdr.Int32` / `xdr.Uint32` have guarded their equivalent legacy form since 17.0.0-rc.1; unions now match.
9+
10+
```diff
11+
-new xdr.TransactionMeta(3, transactionMetaV3);
12+
+xdr.TransactionMeta.v3(transactionMetaV3);
13+
```
14+
815
* `equals()` on XDR values is now callable from TypeScript on union types like `xdr.ScVal`, `xdr.TransactionEnvelope`, and `xdr.Memo` — which is what the SDK's accessors return ([#1630](https://github.com/stellar/js-stellar-sdk/issues/1630)). The parameter was typed as polymorphic `this`, which reduces to `never` on a union, so every call failed with TS2345 even though the runtime worked. The parameter is now `XdrValue`, so comparing two different XDR types compiles and returns `false`.
916

1017
* `Keypair.verify` and `Keypair.verifyMessage` throw a `TypeError` for arguments whose type they don't accept, instead of returning `false` ([#1649](https://github.com/stellar/js-stellar-sdk/pull/1649)). Both previously swallowed every error and reported `false`, so a caller mistake was indistinguishable from an invalid signature. `verify` requires `data` to be a `Uint8Array` and `signature` to be either a `Uint8Array` or an `xdr.Signature`; `verifyMessage` takes the same `signature` and a `message` that is a string or a `Uint8Array`. Anything else now throws — a hex/base64 signature string, a plain array of byte values, the `xdr.DecoratedSignature` that `tx.signatures[0]` holds, or a `message` that is neither string nor bytes. A well-formed signature that doesn't match still returns `false`. Accepting an `xdr.Signature` — what `DecoratedSignature.signature` holds — means `kp.verify(tx.hash(), tx.signatures[0].signature)` works again. `authorizeEntry` likewise rejects a signer result it would have passed on unchecked — a callback returning none of its three shapes, a non-bytes `signature`, a non-string `publicKey`, or a `signatureScVal` that isn't an `xdr.ScVal`.

docs/XDR_MIGRATION.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ xdr.TransactionMeta.v2(transactionMetaV2);
177177
xdr.ExtensionPoint.v0();
178178
```
179179

180+
The legacy form throws a `TypeError` naming a factory to use instead, so a call
181+
site TypeScript can't reach — plain JavaScript, or TypeScript run without a
182+
type-check pass — fails at the `new` rather than later, inside serialization:
183+
184+
```text
185+
TypeError: new xdr.TransactionMeta(...) is not supported: XDR unions are built
186+
from per-variant factories. Call xdr.TransactionMeta.operations(...) (or another
187+
arm factory) instead.
188+
```
189+
180190
### Narrowing helpers
181191

182192
Two helpers ship on the `xdr` namespace for narrowing a union to a specific

src/xdr/generated/account-entry-ext.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ export type AccountEntryExtVariantName = "v0" | "v1";
3636
abstract class AccountEntryExtBase extends XdrValue {
3737
abstract readonly type: AccountEntryExtVariantName;
3838

39+
constructor() {
40+
super();
41+
// `new.target`, not an unconditional throw: every arm subclass reaches
42+
// this constructor through `super()`, void arms via an implicit one
43+
if (new.target === AccountEntryExtBase) {
44+
throw new TypeError(
45+
"new xdr.AccountEntryExt(...) is not supported: XDR unions are built from " +
46+
"per-variant factories. Call xdr.AccountEntryExt.v0() " +
47+
"(or another arm factory) instead.",
48+
);
49+
}
50+
}
51+
3952
static readonly schema: XdrType<AccountEntryExtWire> = union(
4053
"AccountEntryExt",
4154
{

src/xdr/generated/account-entry-extension-v1-ext.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ export type AccountEntryExtensionV1ExtVariantName = "v0" | "v2";
3636
abstract class AccountEntryExtensionV1ExtBase extends XdrValue {
3737
abstract readonly type: AccountEntryExtensionV1ExtVariantName;
3838

39+
constructor() {
40+
super();
41+
// `new.target`, not an unconditional throw: every arm subclass reaches
42+
// this constructor through `super()`, void arms via an implicit one
43+
if (new.target === AccountEntryExtensionV1ExtBase) {
44+
throw new TypeError(
45+
"new xdr.AccountEntryExtensionV1Ext(...) is not supported: XDR unions are built from " +
46+
"per-variant factories. Call xdr.AccountEntryExtensionV1Ext.v0() " +
47+
"(or another arm factory) instead.",
48+
);
49+
}
50+
}
51+
3952
static readonly schema: XdrType<AccountEntryExtensionV1ExtWire> = union(
4053
"AccountEntryExtensionV1Ext",
4154
{

src/xdr/generated/account-entry-extension-v2-ext.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ export type AccountEntryExtensionV2ExtVariantName = "v0" | "v3";
3636
abstract class AccountEntryExtensionV2ExtBase extends XdrValue {
3737
abstract readonly type: AccountEntryExtensionV2ExtVariantName;
3838

39+
constructor() {
40+
super();
41+
// `new.target`, not an unconditional throw: every arm subclass reaches
42+
// this constructor through `super()`, void arms via an implicit one
43+
if (new.target === AccountEntryExtensionV2ExtBase) {
44+
throw new TypeError(
45+
"new xdr.AccountEntryExtensionV2Ext(...) is not supported: XDR unions are built from " +
46+
"per-variant factories. Call xdr.AccountEntryExtensionV2Ext.v0() " +
47+
"(or another arm factory) instead.",
48+
);
49+
}
50+
}
51+
3952
static readonly schema: XdrType<AccountEntryExtensionV2ExtWire> = union(
4053
"AccountEntryExtensionV2Ext",
4154
{

src/xdr/generated/account-merge-result.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,19 @@ export type AccountMergeResultVariantName =
5353
abstract class AccountMergeResultBase extends XdrValue {
5454
abstract readonly type: AccountMergeResultVariantName;
5555

56+
constructor() {
57+
super();
58+
// `new.target`, not an unconditional throw: every arm subclass reaches
59+
// this constructor through `super()`, void arms via an implicit one
60+
if (new.target === AccountMergeResultBase) {
61+
throw new TypeError(
62+
"new xdr.AccountMergeResult(...) is not supported: XDR unions are built from " +
63+
"per-variant factories. Call xdr.AccountMergeResult.accountMergeSuccess(...) " +
64+
"(or another arm factory) instead.",
65+
);
66+
}
67+
}
68+
5669
static readonly schema: XdrType<AccountMergeResultWire> = union(
5770
"AccountMergeResult",
5871
{

src/xdr/generated/allow-trust-result.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,19 @@ export type AllowTrustResultVariantName =
4444
abstract class AllowTrustResultBase extends XdrValue {
4545
abstract readonly type: AllowTrustResultVariantName;
4646

47+
constructor() {
48+
super();
49+
// `new.target`, not an unconditional throw: every arm subclass reaches
50+
// this constructor through `super()`, void arms via an implicit one
51+
if (new.target === AllowTrustResultBase) {
52+
throw new TypeError(
53+
"new xdr.AllowTrustResult(...) is not supported: XDR unions are built from " +
54+
"per-variant factories. Call xdr.AllowTrustResult.allowTrustSuccess() " +
55+
"(or another arm factory) instead.",
56+
);
57+
}
58+
}
59+
4760
static readonly schema: XdrType<AllowTrustResultWire> = union(
4861
"AllowTrustResult",
4962
{

src/xdr/generated/asset-code.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,19 @@ export type AssetCodeVariantName =
3434
abstract class AssetCodeBase extends XdrValue {
3535
abstract readonly type: AssetCodeVariantName;
3636

37+
constructor() {
38+
super();
39+
// `new.target`, not an unconditional throw: every arm subclass reaches
40+
// this constructor through `super()`, void arms via an implicit one
41+
if (new.target === AssetCodeBase) {
42+
throw new TypeError(
43+
"new xdr.AssetCode(...) is not supported: XDR unions are built from " +
44+
"per-variant factories. Call xdr.AssetCode.assetTypeCreditAlphanum4(...) " +
45+
"(or another arm factory) instead.",
46+
);
47+
}
48+
}
49+
3750
static readonly schema: XdrType<AssetCodeWire> = union("AssetCode", {
3851
switchOn: AssetType.schema,
3952
cases: [

src/xdr/generated/asset.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,19 @@ export type AssetVariantName =
3939
abstract class AssetBase extends XdrValue {
4040
abstract readonly type: AssetVariantName;
4141

42+
constructor() {
43+
super();
44+
// `new.target`, not an unconditional throw: every arm subclass reaches
45+
// this constructor through `super()`, void arms via an implicit one
46+
if (new.target === AssetBase) {
47+
throw new TypeError(
48+
"new xdr.Asset(...) is not supported: XDR unions are built from " +
49+
"per-variant factories. Call xdr.Asset.assetTypeNative() " +
50+
"(or another arm factory) instead.",
51+
);
52+
}
53+
}
54+
4255
static readonly schema: XdrType<AssetWire> = union("Asset", {
4356
switchOn: AssetType.schema,
4457
cases: [

src/xdr/generated/authenticated-message.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,19 @@ export type AuthenticatedMessageVariantName = "v0";
3131
abstract class AuthenticatedMessageBase extends XdrValue {
3232
abstract readonly type: AuthenticatedMessageVariantName;
3333

34+
constructor() {
35+
super();
36+
// `new.target`, not an unconditional throw: every arm subclass reaches
37+
// this constructor through `super()`, void arms via an implicit one
38+
if (new.target === AuthenticatedMessageBase) {
39+
throw new TypeError(
40+
"new xdr.AuthenticatedMessage(...) is not supported: XDR unions are built from " +
41+
"per-variant factories. Call xdr.AuthenticatedMessage.v0(...) " +
42+
"(or another arm factory) instead.",
43+
);
44+
}
45+
}
46+
3447
static readonly schema: XdrType<AuthenticatedMessageWire> = union(
3548
"AuthenticatedMessage",
3649
{

0 commit comments

Comments
 (0)