@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
202 lines • 8.17 kB
JavaScript
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
;