Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions src/address.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ 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, 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
*
* @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) {
Expand All @@ -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}`);
}
Expand Down Expand Up @@ -58,7 +67,37 @@ export class Address {
}

/**
* Convert this from an xdr.ScVal type
* Creates a new claimable balance Address object from a buffer of raw bytes.
*
* @param {Buffer} buffer - The bytes of a claimable balance ID to parse.
* @returns {Address}
*/
static claimableBalance(buffer) {
return new Address(StrKey.encodeClaimableBalance(buffer));
}

/**
* 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));
}

/**
* Convert this from an xdr.ScVal type.
*
* @param {xdr.ScVal} scVal - The xdr.ScVal type to parse
* @returns {Address}
Expand All @@ -79,8 +118,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}`);
}
}

Expand All @@ -95,6 +140,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');
}
Expand Down Expand Up @@ -122,8 +173,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}`);
}
}

Expand Down
29 changes: 29 additions & 0 deletions src/operations/invoke_host_function.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if this validation is worthwhile as convenience for clients or is it possibly too opinionated for this layer. Maybe the contract fn wants to receive cb/lp addresses as opaque data of which that intent is not known here at this layer and appears like it could inadvertently trip on such cases?

If this validation were removed where would the problem with cb/lp addresses occur further downstream? Would simulation catch it? Is it feasible to let the lower downstream layer be the source of truth rather than duplicating some of that rule here in js layer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe the contract fn wants to receive cb/lp addresses as opaque data

It can't/shouldn't.

Is it feasible to let the lower downstream layer be the source of truth

Theoretically yes, this is higher-level validation to provide a useful error early rather than punting it to later at the simulation/execution layer. It was actually requested/suggested by the community to implement this here: Discord thread,

Unified Address supporting all possible types (including claimable balances and liquidity pools) looks like a better idea to me. Can we just add the verification on the SDK level whether the particular address is supported for the contract calls (maybe inside TransactionBuilder)? This way, all address types will be supported everywhere, but once someone tries to use inappropriate address in a smart contract call, this will result in an error.

I think it's a little overzealous since as you said it will be caught later, but it does provide a better devx experience to see it prevented early. I think if someone truly wants to do this, they'd file an issue and demonstrate a real need at which point we'd do the whole "okay but why?" back and forth.

// 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 || []
Expand Down
2 changes: 2 additions & 0 deletions src/strkey.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
Loading