From eb4658a9ed27c1fb671f602f1d702d9fcf050564 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 19 May 2025 17:14:04 -0700 Subject: [PATCH 1/4] Extend Address to support going to/from new ScAddress types --- src/address.js | 71 +++++++++++-- test/unit/address_test.js | 209 +++++++++++++++++++++++++++----------- 2 files changed, 213 insertions(+), 67 deletions(-) diff --git a/src/address.js b/src/address.js index d88c8500..08f244f1 100644 --- a/src/address.js +++ b/src/address.js @@ -1,18 +1,18 @@ +import { MuxedAccount } from './muxed_account'; import { StrKey } from './strkey'; import xdr from './xdr'; /** * Create a new Address object. * - * `Address` represents a single address in the Stellar network. An address can - * represent an account or a contract. + * `Address` represents a single address in the Stellar network that can be + * inputted to or outputted by a smart contract. An address can represent an + * account, muxed account, contract, claimable balance (output only), or + * liquidity pool (output only). * * @constructor * - * @param {string} address - ID of the account (ex. - * `GB3KJPLFUYN5VL6R3GU3EGCGVCKFDSD7BEDX42HWG5BWFKB3KQGJJRMA`). If you - * provide a muxed account address, this will throw; use {@link - * MuxedAccount} instead. + * @param {string} address - a {@link StrKey} of the address value */ export class Address { constructor(address) { @@ -22,6 +22,15 @@ export class Address { } else if (StrKey.isValidContract(address)) { this._type = 'contract'; this._key = StrKey.decodeContract(address); + } else if (StrKey.isValidMed25519PublicKey(address)) { + this._type = 'muxedAccount'; + this._key = StrKey.decodeMed25519PublicKey(address); + } else if (StrKey.isValidClaimableBalance(address)) { + this._type = 'claimableBalance'; + this._key = StrKey.decodeClaimableBalance(address); + } else if (StrKey.isValidLiquidityPool(address)) { + this._type = 'liquidityPool'; + this._key = StrKey.decodeLiquidityPool(address); } else { throw new Error(`Unsupported address type: ${address}`); } @@ -58,11 +67,35 @@ export class Address { } /** - * Convert this from an xdr.ScVal type + * Creates a new claimable balance Address object from a buffer of raw bytes. * - * @param {xdr.ScVal} scVal - The xdr.ScVal type to parse + * @param {Buffer} buffer - The bytes of an address to parse. * @returns {Address} */ + static claimableBalance(buffer) { + return new Address(StrKey.encodeClaimableBalance(buffer)); + } + + /** + * + */ + static liquidityPool(buffer) { + return new Address(StrKey.encodeLiquidityPool(buffer)); + } + + static muxedAccount(buffer) { + return new Address(StrKey.encodeMed25519PublicKey(buffer)); + } + + /** + * Convert this from an xdr.ScVal type. + * + * If the given ScVal is an address with a muxed account, this will instead + * return a {@link MuxedAccount} object. + * + * @param {xdr.ScVal} scVal - The xdr.ScVal type to parse + * @returns {Address|MuxedAccount} + */ static fromScVal(scVal) { return Address.fromScAddress(scVal.address()); } @@ -79,8 +112,14 @@ export class Address { return Address.account(scAddress.accountId().ed25519()); case xdr.ScAddressType.scAddressTypeContract().value: return Address.contract(scAddress.contractId()); + case xdr.ScAddressType.scAddressTypeMuxedAccount().value: + return Address.muxedAccount(scAddress.muxedAccount()); + case xdr.ScAddressType.scAddressTypeClaimableBalance().value: + return Address.claimableBalance(scAddress.claimableBalanceId()); + case xdr.ScAddressType.scAddressTypeLiquidityPool().value: + return Address.liquidityPool(scAddress.liquidityPoolId()); default: - throw new Error('Unsupported address type'); + throw new Error(`Unsupported address type: ${scAddress.switch().name}`); } } @@ -95,6 +134,12 @@ export class Address { return StrKey.encodeEd25519PublicKey(this._key); case 'contract': return StrKey.encodeContract(this._key); + case 'claimableBalance': + return StrKey.encodeClaimableBalance(this._key); + case 'liquidityPool': + return StrKey.encodeLiquidityPool(this._key); + case 'muxedAccount': + return StrKey.encodeMed25519PublicKey(this._key); default: throw new Error('Unsupported address type'); } @@ -122,8 +167,14 @@ export class Address { ); case 'contract': return xdr.ScAddress.scAddressTypeContract(this._key); + case 'claimableBalance': + return xdr.ScAddress.scAddressTypeClaimableBalance(this._key); + case 'liquidityPool': + return xdr.ScAddress.scAddressTypeLiquidityPool(this._key); + case 'muxedAccount': + return xdr.ScAddress.scAddressTypeMuxedAccount(this._key); default: - throw new Error('Unsupported address type'); + throw new Error(`Unsupported address type: ${this._type}`); } } diff --git a/test/unit/address_test.js b/test/unit/address_test.js index 87ec3765..8f90c4b3 100644 --- a/test/unit/address_test.js +++ b/test/unit/address_test.js @@ -1,9 +1,20 @@ describe('Address', function () { const ACCOUNT = 'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB'; const CONTRACT = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE'; + // Valid muxed-account taken from the Stellar test-vectors const MUXED_ADDRESS = 'MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLK'; + const MUXED_ZERO = StellarBase.StrKey.encodeMed25519PublicKey( + Buffer.alloc(40) + ); + const CLAIMABLE_BALANCE_ZERO = StellarBase.StrKey.encodeClaimableBalance( + Buffer.alloc(33) + ); + const LIQUIDITY_POOL_ZERO = StellarBase.StrKey.encodeLiquidityPool( + Buffer.alloc(32) + ); + describe('.constructor', function () { it('fails to create Address object from an invalid address', function () { expect(() => new StellarBase.Address('GBBB')).to.throw( @@ -12,129 +23,213 @@ describe('Address', function () { }); it('creates an Address object for accounts', function () { - let account = new StellarBase.Address(ACCOUNT); - expect(account.toString()).to.equal(ACCOUNT); + const a = new StellarBase.Address(ACCOUNT); + expect(a.toString()).to.equal(ACCOUNT); }); it('creates an Address object for contracts', function () { - let account = new StellarBase.Address(CONTRACT); - expect(account.toString()).to.equal(CONTRACT); + const c = new StellarBase.Address(CONTRACT); + expect(c.toString()).to.equal(CONTRACT); + }); + + it('creates an Address object for muxed accounts', function () { + const m = new StellarBase.Address(MUXED_ADDRESS); + expect(m.toString()).to.equal(MUXED_ADDRESS); + }); + + it('creates an Address object for claimable balances', function () { + const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); + expect(cb.toString()).to.equal(CLAIMABLE_BALANCE_ZERO); }); - it('wont create Address objects from muxed account strings', function () { - expect(() => { - new StellarBase.Account(MUXED_ADDRESS, '123'); - }).to.throw(/MuxedAccount/); + it('creates an Address object for liquidity pools', function () { + const lp = new StellarBase.Address(LIQUIDITY_POOL_ZERO); + expect(lp.toString()).to.equal(LIQUIDITY_POOL_ZERO); }); }); describe('static constructors', function () { it('.fromString', function () { - let account = StellarBase.Address.fromString(ACCOUNT); - expect(account.toString()).to.equal(ACCOUNT); + const a = StellarBase.Address.fromString(ACCOUNT); + expect(a.toString()).to.equal(ACCOUNT); }); it('.account', function () { - let account = StellarBase.Address.account(Buffer.alloc(32)); - expect(account.toString()).to.equal( + const a = StellarBase.Address.account(Buffer.alloc(32)); + expect(a.toString()).to.equal( 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF' ); }); it('.contract', function () { - let account = StellarBase.Address.contract(Buffer.alloc(32)); - expect(account.toString()).to.equal( + const c = StellarBase.Address.contract(Buffer.alloc(32)); + expect(c.toString()).to.equal( 'CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4' ); }); + it('.muxedAccount', function () { + const m = StellarBase.Address.muxedAccount(Buffer.alloc(40)); + expect(m.toString()).to.equal(MUXED_ZERO); + }); + + it('.claimableBalance', function () { + const cb = StellarBase.Address.claimableBalance(Buffer.alloc(33)); + expect(cb.toString()).to.equal(CLAIMABLE_BALANCE_ZERO); + }); + + it('.liquidityPool', function () { + const lp = StellarBase.Address.liquidityPool(Buffer.alloc(32)); + expect(lp.toString()).to.equal(LIQUIDITY_POOL_ZERO); + }); + describe('.fromScAddress', function () { - it('creates an Address object for accounts', function () { - let scAddress = StellarBase.xdr.ScAddress.scAddressTypeAccount( + it('parses account addresses', function () { + const sc = StellarBase.xdr.ScAddress.scAddressTypeAccount( StellarBase.xdr.PublicKey.publicKeyTypeEd25519( StellarBase.StrKey.decodeEd25519PublicKey(ACCOUNT) ) ); - let account = StellarBase.Address.fromScAddress(scAddress); - expect(account.toString()).to.equal(ACCOUNT); + const a = StellarBase.Address.fromScAddress(sc); + expect(a.toString()).to.equal(ACCOUNT); }); - it('creates an Address object for contracts', function () { - let scAddress = StellarBase.xdr.ScAddress.scAddressTypeContract( + it('parses contract addresses', function () { + const sc = StellarBase.xdr.ScAddress.scAddressTypeContract( StellarBase.StrKey.decodeContract(CONTRACT) ); - let contract = StellarBase.Address.fromScAddress(scAddress); - expect(contract.toString()).to.equal(CONTRACT); + const c = StellarBase.Address.fromScAddress(sc); + expect(c.toString()).to.equal(CONTRACT); + }); + + it('parses muxed-account addresses', function () { + const sc = StellarBase.xdr.ScAddress.scAddressTypeMuxedAccount( + StellarBase.StrKey.decodeMed25519PublicKey(MUXED_ADDRESS) + ); + const m = StellarBase.Address.fromScAddress(sc); + expect(m.toString()).to.equal(MUXED_ADDRESS); + }); + + it('parses claimable-balance addresses', function () { + const sc = StellarBase.xdr.ScAddress.scAddressTypeClaimableBalance( + Buffer.alloc(33) + ); + const cb = StellarBase.Address.fromScAddress(sc); + expect(cb.toString()).to.equal(CLAIMABLE_BALANCE_ZERO); + }); + + it('parses liquidity-pool addresses', function () { + const sc = StellarBase.xdr.ScAddress.scAddressTypeLiquidityPool( + Buffer.alloc(32) + ); + const lp = StellarBase.Address.fromScAddress(sc); + expect(lp.toString()).to.equal(LIQUIDITY_POOL_ZERO); }); }); describe('.fromScVal', function () { - it('creates an Address object for accounts', function () { - let scVal = StellarBase.xdr.ScVal.scvAddress( - StellarBase.xdr.ScAddress.scAddressTypeAccount( - StellarBase.xdr.PublicKey.publicKeyTypeEd25519( - StellarBase.StrKey.decodeEd25519PublicKey(ACCOUNT) - ) + it('parses muxed-account ScVals', function () { + const scVal = StellarBase.xdr.ScVal.scvAddress( + StellarBase.xdr.ScAddress.scAddressTypeMuxedAccount( + StellarBase.StrKey.decodeMed25519PublicKey(MUXED_ADDRESS) ) ); - let account = StellarBase.Address.fromScVal(scVal); - expect(account.toString()).to.equal(ACCOUNT); + const m = StellarBase.Address.fromScVal(scVal); + expect(m.toString()).to.equal(MUXED_ADDRESS); }); - it('creates an Address object for contracts', function () { - let scVal = StellarBase.xdr.ScVal.scvAddress( - StellarBase.xdr.ScAddress.scAddressTypeContract( - StellarBase.StrKey.decodeContract(CONTRACT) + it('parses claimable-balance ScVals', function () { + const scVal = StellarBase.xdr.ScVal.scvAddress( + StellarBase.xdr.ScAddress.scAddressTypeClaimableBalance( + Buffer.alloc(33) ) ); - let contract = StellarBase.Address.fromScVal(scVal); - expect(contract.toString()).to.equal(CONTRACT); + const cb = StellarBase.Address.fromScVal(scVal); + expect(cb.toString()).to.equal(CLAIMABLE_BALANCE_ZERO); + }); + + it('parses liquidity-pool ScVals', function () { + const scVal = StellarBase.xdr.ScVal.scvAddress( + StellarBase.xdr.ScAddress.scAddressTypeLiquidityPool(Buffer.alloc(32)) + ); + const lp = StellarBase.Address.fromScVal(scVal); + expect(lp.toString()).to.equal(LIQUIDITY_POOL_ZERO); }); }); }); describe('.toScAddress', function () { - it('converts accounts to an ScAddress', function () { - const a = new StellarBase.Address(ACCOUNT); - const s = a.toScAddress(); + it('converts muxed accounts', function () { + const m = new StellarBase.Address(MUXED_ADDRESS); + const s = m.toScAddress(); expect(s).to.be.instanceof(StellarBase.xdr.ScAddress); expect(s.switch()).to.equal( - StellarBase.xdr.ScAddressType.scAddressTypeAccount() + StellarBase.xdr.ScAddressType.scAddressTypeMuxedAccount() ); }); - it('converts contracts to an ScAddress', function () { - const a = new StellarBase.Address(CONTRACT); - const s = a.toScAddress(); - expect(s).to.be.instanceof(StellarBase.xdr.ScAddress); + it('converts claimable balances', function () { + const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); + const s = cb.toScAddress(); expect(s.switch()).to.equal( - StellarBase.xdr.ScAddressType.scAddressTypeContract() + StellarBase.xdr.ScAddressType.scAddressTypeClaimableBalance() + ); + }); + + it('converts liquidity pools', function () { + const lp = new StellarBase.Address(LIQUIDITY_POOL_ZERO); + const s = lp.toScAddress(); + expect(s.switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeLiquidityPool() ); }); }); describe('.toScVal', function () { - it('converts to an ScAddress', function () { - const a = new StellarBase.Address(ACCOUNT); - const s = a.toScVal(); - expect(s).to.be.instanceof(StellarBase.xdr.ScVal); - expect(s.address()).to.deep.equal(a.toScAddress()); + it('wraps claimable-balance ScAddress types', function () { + const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); + const val = cb.toScVal(); + expect(val).to.be.instanceof(StellarBase.xdr.ScVal); + expect(val.address().switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeClaimableBalance() + ); }); }); describe('.toBuffer', function () { - it('returns the raw public key bytes for accounts', function () { + it('returns the raw public-key bytes for accounts', function () { const a = new StellarBase.Address(ACCOUNT); - const b = a.toBuffer(); - expect(b).to.deep.equal( + expect(a.toBuffer()).to.deep.equal( StellarBase.StrKey.decodeEd25519PublicKey(ACCOUNT) ); }); - it('returns the raw public key bytes for contracts', function () { - const a = new StellarBase.Address(CONTRACT); - const b = a.toBuffer(); - expect(b).to.deep.equal(StellarBase.StrKey.decodeContract(CONTRACT)); + it('returns the raw hash for contracts', function () { + const c = new StellarBase.Address(CONTRACT); + expect(c.toBuffer()).to.deep.equal( + StellarBase.StrKey.decodeContract(CONTRACT) + ); + }); + + it('returns raw bytes for muxed accounts', function () { + const m = new StellarBase.Address(MUXED_ADDRESS); + expect(m.toBuffer()).to.deep.equal( + StellarBase.StrKey.decodeMed25519PublicKey(MUXED_ADDRESS) + ); + }); + + it('returns raw bytes for claimable balances', function () { + const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); + expect(cb.toBuffer()).to.deep.equal( + StellarBase.StrKey.decodeClaimableBalance(CLAIMABLE_BALANCE_ZERO) + ); + }); + + it('returns raw bytes for liquidity pools', function () { + const lp = new StellarBase.Address(LIQUIDITY_POOL_ZERO); + expect(lp.toBuffer()).to.deep.equal( + StellarBase.StrKey.decodeLiquidityPool(LIQUIDITY_POOL_ZERO) + ); }); }); }); From e424e69ce09284b27a5eb1d2ca5006615183abe4 Mon Sep 17 00:00:00 2001 From: George Date: Mon, 19 May 2025 17:36:28 -0700 Subject: [PATCH 2/4] Add missing test cases for completeness --- src/address.js | 17 ++++--- src/strkey.js | 2 + test/unit/address_test.js | 101 +++++++++++++++++++++++++++++--------- 3 files changed, 90 insertions(+), 30 deletions(-) diff --git a/src/address.js b/src/address.js index 08f244f1..c6e7486a 100644 --- a/src/address.js +++ b/src/address.js @@ -1,4 +1,3 @@ -import { MuxedAccount } from './muxed_account'; import { StrKey } from './strkey'; import xdr from './xdr'; @@ -69,7 +68,7 @@ export class Address { /** * Creates a new claimable balance Address object from a buffer of raw bytes. * - * @param {Buffer} buffer - The bytes of an address to parse. + * @param {Buffer} buffer - The bytes of a claimable balance ID to parse. * @returns {Address} */ static claimableBalance(buffer) { @@ -77,12 +76,21 @@ export class Address { } /** + * Creates a new liquidity pool Address object from a buffer of raw bytes. * + * @param {Buffer} buffer - The bytes of an LP ID to parse. + * @returns {Address} */ static liquidityPool(buffer) { return new Address(StrKey.encodeLiquidityPool(buffer)); } + /** + * Creates a new muxed account Address object from a buffer of raw bytes. + * + * @param {Buffer} buffer - The bytes of an address to parse. + * @returns {Address} + */ static muxedAccount(buffer) { return new Address(StrKey.encodeMed25519PublicKey(buffer)); } @@ -90,11 +98,8 @@ export class Address { /** * Convert this from an xdr.ScVal type. * - * If the given ScVal is an address with a muxed account, this will instead - * return a {@link MuxedAccount} object. - * * @param {xdr.ScVal} scVal - The xdr.ScVal type to parse - * @returns {Address|MuxedAccount} + * @returns {Address} */ static fromScVal(scVal) { return Address.fromScAddress(scVal.address()); diff --git a/src/strkey.js b/src/strkey.js index 3a7b32fd..9b6d818a 100644 --- a/src/strkey.js +++ b/src/strkey.js @@ -33,6 +33,8 @@ const strkeyTypes = { * string (i.e. "GABCD...", etc.) representations. */ export class StrKey { + static types = strkeyTypes; + /** * Encodes `data` to strkey ed25519 public key. * diff --git a/test/unit/address_test.js b/test/unit/address_test.js index 8f90c4b3..b54a9ddd 100644 --- a/test/unit/address_test.js +++ b/test/unit/address_test.js @@ -1,7 +1,6 @@ describe('Address', function () { const ACCOUNT = 'GBBM6BKZPEHWYO3E3YKREDPQXMS4VK35YLNU7NFBRI26RAN7GI5POFBB'; const CONTRACT = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE'; - // Valid muxed-account taken from the Stellar test-vectors const MUXED_ADDRESS = 'MA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJVAAAAAAAAAAAAAJLK'; @@ -22,29 +21,17 @@ describe('Address', function () { ); }); - it('creates an Address object for accounts', function () { - const a = new StellarBase.Address(ACCOUNT); - expect(a.toString()).to.equal(ACCOUNT); - }); - - it('creates an Address object for contracts', function () { - const c = new StellarBase.Address(CONTRACT); - expect(c.toString()).to.equal(CONTRACT); - }); - - it('creates an Address object for muxed accounts', function () { - const m = new StellarBase.Address(MUXED_ADDRESS); - expect(m.toString()).to.equal(MUXED_ADDRESS); - }); - - it('creates an Address object for claimable balances', function () { - const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); - expect(cb.toString()).to.equal(CLAIMABLE_BALANCE_ZERO); - }); - - it('creates an Address object for liquidity pools', function () { - const lp = new StellarBase.Address(LIQUIDITY_POOL_ZERO); - expect(lp.toString()).to.equal(LIQUIDITY_POOL_ZERO); + [ + ACCOUNT, + CONTRACT, + MUXED_ADDRESS, + CLAIMABLE_BALANCE_ZERO, + LIQUIDITY_POOL_ZERO + ].forEach((strkey) => { + const type = StellarBase.StrKey.types[strkey[0]]; + it(`creates an Address for ${type}`, function () { + expect(new StellarBase.Address(strkey).toString()).to.equal(strkey); + }); }); }); @@ -128,6 +115,28 @@ describe('Address', function () { }); describe('.fromScVal', function () { + it('parses account ScVals', function () { + const scVal = StellarBase.xdr.ScVal.scvAddress( + StellarBase.xdr.ScAddress.scAddressTypeAccount( + StellarBase.xdr.PublicKey.publicKeyTypeEd25519( + StellarBase.StrKey.decodeEd25519PublicKey(ACCOUNT) + ) + ) + ); + const a = StellarBase.Address.fromScVal(scVal); + expect(a.toString()).to.equal(ACCOUNT); + }); + + it('parses contract ScVals', function () { + const scVal = StellarBase.xdr.ScVal.scvAddress( + StellarBase.xdr.ScAddress.scAddressTypeContract( + StellarBase.StrKey.decodeContract(CONTRACT) + ) + ); + const c = StellarBase.Address.fromScVal(scVal); + expect(c.toString()).to.equal(CONTRACT); + }); + it('parses muxed-account ScVals', function () { const scVal = StellarBase.xdr.ScVal.scvAddress( StellarBase.xdr.ScAddress.scAddressTypeMuxedAccount( @@ -159,6 +168,22 @@ describe('Address', function () { }); describe('.toScAddress', function () { + it('converts accounts', function () { + const a = new StellarBase.Address(ACCOUNT); + const s = a.toScAddress(); + expect(s.switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeAccount() + ); + }); + + it('converts contracts', function () { + const c = new StellarBase.Address(CONTRACT); + const s = c.toScAddress(); + expect(s.switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeContract() + ); + }); + it('converts muxed accounts', function () { const m = new StellarBase.Address(MUXED_ADDRESS); const s = m.toScAddress(); @@ -186,6 +211,34 @@ describe('Address', function () { }); describe('.toScVal', function () { + it('wraps account ScAddress types', function () { + const a = new StellarBase.Address(ACCOUNT); + expect(a.toScVal().address().switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeAccount() + ); + }); + + it('wraps contract ScAddress types', function () { + const c = new StellarBase.Address(CONTRACT); + expect(c.toScVal().address().switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeContract() + ); + }); + + it('wraps muxed-account ScAddress types', function () { + const m = new StellarBase.Address(MUXED_ADDRESS); + expect(m.toScVal().address().switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeMuxedAccount() + ); + }); + + it('wraps liquidity-pool ScAddress types', function () { + const lp = new StellarBase.Address(LIQUIDITY_POOL_ZERO); + expect(lp.toScVal().address().switch()).to.equal( + StellarBase.xdr.ScAddressType.scAddressTypeLiquidityPool() + ); + }); + it('wraps claimable-balance ScAddress types', function () { const cb = new StellarBase.Address(CLAIMABLE_BALANCE_ZERO); const val = cb.toScVal(); From 45e7b1569f874ff9dcca5fe6cd89978e95ea5971 Mon Sep 17 00:00:00 2001 From: George Date: Tue, 20 May 2025 15:28:58 -0700 Subject: [PATCH 3/4] Prevent CBs and LPs from being used as arguments --- src/address.js | 10 +++---- src/operations/invoke_host_function.js | 29 +++++++++++++++++++ .../operations/invoke_host_function_test.js | 16 ++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/src/address.js b/src/address.js index c6e7486a..e9b13006 100644 --- a/src/address.js +++ b/src/address.js @@ -86,11 +86,11 @@ export class Address { } /** - * Creates a new muxed account Address object from a buffer of raw bytes. - * - * @param {Buffer} buffer - The bytes of an address to parse. - * @returns {Address} - */ + * Creates a new muxed account Address object from a buffer of raw bytes. + * + * @param {Buffer} buffer - The bytes of an address to parse. + * @returns {Address} + */ static muxedAccount(buffer) { return new Address(StrKey.encodeMed25519PublicKey(buffer)); } diff --git a/src/operations/invoke_host_function.js b/src/operations/invoke_host_function.js index 94e61a5a..47998179 100644 --- a/src/operations/invoke_host_function.js +++ b/src/operations/invoke_host_function.js @@ -34,6 +34,35 @@ export function invokeHostFunction(opts) { ); } + if ( + opts.func.switch().value === + xdr.HostFunctionType.hostFunctionTypeInvokeContract().value + ) { + // Ensure that there are no claimable balance or liquidity pool IDs in the + // invocation because those are not allowed. + opts.func + .invokeContract() + .args() + .forEach((arg) => { + let scv; + try { + scv = Address.fromScVal(arg); + } catch { + // swallow non-Address errors + return; + } + + switch (scv._type) { + case 'claimableBalance': + case 'liquidityPool': + throw new TypeError( + `claimable balances and liquidity pools cannot be arguments to invokeHostFunction` + ); + default: + } + }); + } + const invokeHostFunctionOp = new xdr.InvokeHostFunctionOp({ hostFunction: opts.func, auth: opts.auth || [] diff --git a/test/unit/operations/invoke_host_function_test.js b/test/unit/operations/invoke_host_function_test.js index 5c846d3b..50a5acfb 100644 --- a/test/unit/operations/invoke_host_function_test.js +++ b/test/unit/operations/invoke_host_function_test.js @@ -175,6 +175,22 @@ describe('Operation', function () { // compare that way instead. expect(ctorArgs[0].str().toString()).to.eql(constructorArgs[0].str()); }); + + it('prevents invocation with claimable balances', function () { + expect(() => + Operation.invokeContractFunction({ + contract: + 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE', + function: 'increment', + args: [ + nativeToScVal( + 'LA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUPJN', + { type: 'address' } + ) + ] + }) + ).to.throw(/liquidity pool/); + }); }); }); }); From 914e264a11c80e33f4f209aacee535d6918a39cf Mon Sep 17 00:00:00 2001 From: George Date: Thu, 12 Jun 2025 10:49:49 -0700 Subject: [PATCH 4/4] Clarify what "output" means in the docstring --- src/address.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/address.js b/src/address.js index e9b13006..c228abd4 100644 --- a/src/address.js +++ b/src/address.js @@ -6,8 +6,9 @@ import xdr from './xdr'; * * `Address` represents a single address in the Stellar network that can be * inputted to or outputted by a smart contract. An address can represent an - * account, muxed account, contract, claimable balance (output only), or - * liquidity pool (output only). + * account, muxed account, contract, claimable balance, or a liquidity pool + * (the latter two can only be present as the *output* of Core in the form + * of an event, never an input to a smart contract). * * @constructor *