UNPKG

@kamino-finance/klend-sdk

Version:

Typescript SDK for interacting with the Kamino Lending (klend) protocol

202 lines 8.17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConfigUpdater = exports.ConfigItemUpdater = exports.CompositeConfigItem = void 0; exports.arrayElementConfigItems = arrayElementConfigItems; exports.encodeUsingLayout = encodeUsingLayout; const borsh_1 = require("@coral-xyz/borsh"); const utils_1 = require("./utils"); const buffer_1 = require("buffer"); /** * A composite {@link ConfigItem}, allowing to encode multiple fields together. * * @example * ``` * ... * [CartoonsUpdateMode.UpdateTomAndJerry.discriminator]: new CompositeConfigItem( * CARTOONS.value().characters.cats.tom, * CARTOONS.value().characters.rodents.jerry * ), * ... * ``` */ class CompositeConfigItem { __layout; __getter; constructor(...components) { this.__layout = (0, borsh_1.struct)(components.map((component, index) => component.__layout.replicate(index.toString()))); this.__getter = (config) => Object.fromEntries(components.map((component, index) => [index.toString(), component.__getter(config)])); } } exports.CompositeConfigItem = CompositeConfigItem; /** * Creates an array of config items - one per each element of the given array. * * An example use-case is `LendingMarket.elevationGroups[]`: to update all of them, we need N ixs: * - `updateLendingMarket(mode = ElevationGroup, value = elevationGroups[0])` * - `updateLendingMarket(mode = ElevationGroup, value = elevationGroups[1])` * - `updateLendingMarket(mode = ElevationGroup, value = elevationGroups[2])` * ... * * So: conceptually, the *array* is not "a config item". Each *slot* in that array is its own config item. */ function arrayElementConfigItems(arrayConfigItem) { const arrayGetter = arrayConfigItem.__getter; const wrappedSequenceLayout = arrayConfigItem.__layout; const sequenceLayout = wrappedSequenceLayout.layout?.fields?.[0]; if (sequenceLayout === undefined) { throw new Error(`unexpected layout of the input array config item: ${(0, utils_1.toJson)(wrappedSequenceLayout, true)}`); } return new Array(sequenceLayout.count) .fill(sequenceLayout.elementLayout) .map((elementLayout, index) => ({ __layout: elementLayout, __getter: (config) => arrayGetter(config)[index], })); } /** * A part of a {@link ConfigUpdater} responsible for a single config item. */ class ConfigItemUpdater { item; constructor(item) { this.item = item; } /** * Returns a serialized value of the specific config item extracted from the given top-level {@code newConfig}, or * `undefined` if the value has not changed from the given {@code currentConfig}. */ encodeUpdatedItemFrom(currentConfig, newConfig) { const getter = this.item.__getter; const newItemValue = this.encodeItem(getter(newConfig)); if (currentConfig === undefined) { return newItemValue; } if ((0, utils_1.blobEquals)(newItemValue, this.encodeItem(getter(currentConfig)))) { return undefined; } return newItemValue; } /** * Borsh-serializes the given value. * * Only exposed for some legacy callers which still construct the update ixs manually (in tests). */ encodeItem(item) { return encodeUsingLayout(this.item.__layout, item); } } exports.ConfigItemUpdater = ConfigItemUpdater; /** * A resolver of config item changes. */ class ConfigUpdater { itemUpdaters; /** * A resolving constructor. * * The inputs: * - `fromDecoded`: a reference to the codegen'ed enum-decoding function, e.g. `UpdateConfigMode.fromDecoded`. Needed * to turn the `<enumClass>.kind` strings into enums (i.e. instances of `M`). * - `configClass`: a reference to the codegen'ed top-level config class, e.g. `ReserveConfig`. Need to create a * completion helper for the `itemMapBuilder`. * - `itemMapBuilder`: a function building a {@link ConfigItemMap} using the completion helper provided as its * argument, e.g. `(config) => ({[UpdateLendingMarketMode.UpdateImmutableFlag.kind]: config.immutable, ...})`. * * See the usage example at {@link ConfigItemMap}. */ constructor(fromDecoded, configClass, itemMapBuilder) { this.itemUpdaters = new Map(Object.entries(itemMapBuilder(wrap(configClass))).map(([kind, itemOrArray]) => [ kind, [ fromDecoded({ [kind]: {} }), toArray(itemOrArray).map((item) => new ConfigItemUpdater(item)), ], ])); } /** * Returns all changes between the given current and new configs - in particular, this will be *all* supported fields' * changes if the previous config does not exist. * * Please note that more than one {@link EncodedConfigUpdate}s can be associated with a single `M` enum value (in * cases where e.g. an array property is updated by individual updates of its elements). */ encodeAllUpdates(currentConfig, newConfig) { const updates = []; for (const [mode, itemUpdaters] of this.itemUpdaters.values()) { for (const itemUpdater of itemUpdaters) { const value = itemUpdater.encodeUpdatedItemFrom(currentConfig, newConfig); if (value === undefined) { continue; } updates.push({ mode, value }); } } return updates; } /** * Gets the single updater of the given config item. * * Throws an error if the updates are not supported (e.g. for deprecated modes) or if the given item is handled by * multiple updaters (e.g. for an array property) - to handle these cases, use {@link allForMode()}. */ forMode(mode) { const itemUpdaters = this.allForMode(mode); switch (itemUpdaters.length) { case 0: throw new Error(`updates not supported (updaters for ${mode.kind} were explicitly set to [])`); case 1: return itemUpdaters[0]; default: throw new Error(`${mode.kind} defines multiple (${itemUpdaters.length}) updaters`); } } /** * Gets all the updaters of the given config item. * * This may be an empty array (e.g. for deprecated modes), or multiple elements (e.g. if an array property is updated * by individual updates of its elements). If you expect a single updater, use {@link forMode()}. */ allForMode(mode) { const [_mode, itemUpdaters] = this.itemUpdaters.get(mode.kind) ?? (0, utils_1.orThrow)(`updaters for ${mode.kind} were not set (should not be possible, due to type-safety)`); return itemUpdaters; } } exports.ConfigUpdater = ConfigUpdater; /** * Borsh-serializes the given value according to the given layout. * * Only exposed for some legacy callers which still construct the update ixs manually (in tests). */ function encodeUsingLayout(layout, value) { const buffer = buffer_1.Buffer.alloc(layout.span); const length = layout.encode(value, buffer, 0); if (length !== layout.span) { throw new Error(`layout span declared incorrect length ${layout.span}; got ${length}`); } return Uint8Array.from(buffer); } // Only internals below: function wrap(configClass) { return withPotentialChildren({ __layout: typeof configClass.layout === 'function' ? configClass.layout() : configClass.layout, __getter: (config) => config, }); } function wrapChild(layout, parent) { return withPotentialChildren({ __layout: layout, __getter: (config) => parent.__getter(config)[layout.property], }); } function withPotentialChildren(item) { for (const fieldLayout of item.__layout.fields ?? []) { const structItem = item; structItem[fieldLayout.property] = wrapChild(fieldLayout, structItem); } return item; } function toArray(singleOrArray) { return Array.isArray(singleOrArray) ? singleOrArray : [singleOrArray]; } //# sourceMappingURL=configItems.js.map