diff --git a/solend-sdk/src/instructions/forgiveDebt.ts b/solend-sdk/src/instructions/forgiveDebt.ts new file mode 100644 index 00000000..928d2cc1 --- /dev/null +++ b/solend-sdk/src/instructions/forgiveDebt.ts @@ -0,0 +1,40 @@ +import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import * as BufferLayout from "buffer-layout"; +import * as Layout from "../utils/layout"; +import { LendingInstruction } from "./instruction"; + +export const ForgiveDebtInstruction = ( + obligation: PublicKey, + reserve: PublicKey, + lendingMarket: PublicKey, + lendingMarketOwner: PublicKey, + liquidityAmount: number, + lendingProgramId: PublicKey +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8("instruction"), + Layout.uint64("liquidityAmount"), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.SetLendingMarketOwnerAndConfig, + liquidityAmount: liquidityAmount, + }, + data + ); + + const keys = [ + { pubkey: obligation, isSigner: false, isWritable: true }, + { pubkey: reserve, isSigner: false, isWritable: true }, + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketOwner, isSigner: true, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: lendingProgramId, + data, + }); +}; diff --git a/solend-sdk/src/instructions/initReserve.ts b/solend-sdk/src/instructions/initReserve.ts index 2e550ec5..bb6ba155 100644 --- a/solend-sdk/src/instructions/initReserve.ts +++ b/solend-sdk/src/instructions/initReserve.ts @@ -6,7 +6,7 @@ import { } from "@solana/web3.js"; import BN from "bn.js"; import * as Layout from "../utils/layout"; -import { ReserveConfig, ReserveConfigLayout } from "../state"; +import { ReserveConfig } from "../state"; import { LendingInstruction } from "./instruction"; const BufferLayout = require("buffer-layout"); @@ -34,7 +34,26 @@ export const initReserveInstruction = ( const dataLayout = BufferLayout.struct([ BufferLayout.u8("instruction"), Layout.uint64("liquidityAmount"), - ReserveConfigLayout, + BufferLayout.u8("optimalUtilizationRate"), + BufferLayout.u8("maxUtilizationRate"), + BufferLayout.u8("loanToValueRatio"), + BufferLayout.u8("liquidationBonus"), + BufferLayout.u8("liquidationThreshold"), + BufferLayout.u8("minBorrowRate"), + BufferLayout.u8("optimalBorrowRate"), + BufferLayout.u8("maxBorrowRate"), + Layout.uint64("borrowFeeWad"), + Layout.uint64("flashLoanFeeWad"), + BufferLayout.u8("hostFeePercentage"), + Layout.uint64("depositLimit"), + Layout.uint64("borrowLimit"), + Layout.publicKey("feeReceiver"), + BufferLayout.u8("protocolLiquidationFee"), + BufferLayout.u8("protocolTakeRate"), + Layout.uint64("addedBorrowWeightBPS"), + BufferLayout.u8("reserveType"), + BufferLayout.u8("maxLiquidationBonus"), + BufferLayout.u8("maxLiquidationThreshold"), ]); const data = Buffer.alloc(dataLayout.span); @@ -42,7 +61,26 @@ export const initReserveInstruction = ( { instruction: LendingInstruction.InitReserve, liquidityAmount: new BN(liquidityAmount), - config, + optimalUtilizationRate: config.optimalUtilizationRate, + maxUtilizationRate: config.maxUtilizationRate, + loanToValueRatio: config.loanToValueRatio, + liquidationBonus: config.liquidationBonus, + liquidationThreshold: config.liquidationThreshold, + minBorrowRate: config.minBorrowRate, + optimalBorrowRate: config.optimalBorrowRate, + maxBorrowRate: config.maxBorrowRate, + borrowFeeWad: config.fees.borrowFeeWad, + flashLoanFeeWad: config.fees.flashLoanFeeWad, + hostFeePercentage: config.fees.hostFeePercentage, + depositLimit: config.depositLimit, + borrowLimit: config.borrowLimit, + feeReceiver: config.feeReceiver, + protocolLiquidationFee: config.protocolLiquidationFee, + protocolTakeRate: config.protocolTakeRate, + addedBorrowWeightBPS: config.addedBorrowWeightBPS, + reserveType: config.reserveType, + maxLiquidationBonus: config.maxLiquidationBonus, + maxLiquidationThreshold: config.maxLiquidationThreshold, }, data ); diff --git a/solend-sdk/src/instructions/instruction.ts b/solend-sdk/src/instructions/instruction.ts index e94bf677..f929c533 100644 --- a/solend-sdk/src/instructions/instruction.ts +++ b/solend-sdk/src/instructions/instruction.ts @@ -1,6 +1,6 @@ export enum LendingInstruction { InitLendingMarket = 0, - SetLendingMarketOwner = 1, + SetLendingMarketOwnerAndConfig = 1, InitReserve = 2, RefreshReserve = 3, DepositReserveLiquidity = 4, @@ -18,6 +18,8 @@ export enum LendingInstruction { UpdateReserveConfig = 16, FlashBorrowReserveLiquidity = 19, FlashRepayReserveLiquidity = 20, + ForgiveDebt = 21, + UpdateMetadata = 22, } /** Instructions defined by the program */ diff --git a/solend-sdk/src/instructions/setLendingMarketOwnerAndConfig.ts b/solend-sdk/src/instructions/setLendingMarketOwnerAndConfig.ts new file mode 100644 index 00000000..ebf282cf --- /dev/null +++ b/solend-sdk/src/instructions/setLendingMarketOwnerAndConfig.ts @@ -0,0 +1,58 @@ +import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import * as BufferLayout from "buffer-layout"; +import { RateLimiterConfig } from "../state/rateLimiter"; +import * as Layout from "../utils/layout"; +import { LendingInstruction } from "./instruction"; + +/// Sets the new owner of a lending market. +/// +/// Accounts expected by this instruction: +/// +/// 0. `[writable]` Lending market account. +/// 1. `[signer]` Current owner. +export const SetLendingMarketOwnerAndConfigInstruction = ( + lendingMarket: PublicKey, + currentMarketOwner: PublicKey, + newMarketOwner: PublicKey, + newRateLimiterConfig: RateLimiterConfig, + whitelistedLiquidator: PublicKey | null, + riskAuthority: PublicKey, + lendingProgramId: PublicKey +): TransactionInstruction => { + if (whitelistedLiquidator != null) { + throw new Error("Whitelisted liquidator not supported yet"); + } + + const dataLayout = BufferLayout.struct([ + BufferLayout.u8("instruction"), + Layout.publicKey("newOwner"), + Layout.uint64("windowDuration"), + Layout.uint64("maxOutflow"), + BufferLayout.u8("whitelistedLiquidator"), + Layout.publicKey("riskAuthority"), + ]); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.SetLendingMarketOwnerAndConfig, + newOwner: newMarketOwner, + windowDuration: newRateLimiterConfig.windowDuration, + maxOutflow: newRateLimiterConfig.maxOutflow, + whitelistedLiquidator: 0, + riskAuthority: riskAuthority, + }, + data + ); + + const keys = [ + { pubkey: lendingMarket, isSigner: false, isWritable: true }, + { pubkey: currentMarketOwner, isSigner: true, isWritable: false }, + ]; + + return new TransactionInstruction({ + keys, + programId: lendingProgramId, + data, + }); +}; diff --git a/solend-sdk/src/instructions/updateMetadata.ts b/solend-sdk/src/instructions/updateMetadata.ts new file mode 100644 index 00000000..961f6aff --- /dev/null +++ b/solend-sdk/src/instructions/updateMetadata.ts @@ -0,0 +1,59 @@ +import * as anchor from "@coral-xyz/anchor"; +import { PublicKey, SystemProgram, TransactionInstruction } from "@solana/web3.js"; +import { findProgramAddressSync } from "@project-serum/anchor/dist/cjs/utils/pubkey"; +import { LendingInstruction } from "./instruction"; + +const BufferLayout = require("buffer-layout"); + +export const updateMetadataInstruction = ( + lendingMarket: PublicKey, + lendingMarketOwner: PublicKey, + lendingProgramId: PublicKey, + marketName: string, + marketDescription: string, + marketImageUrl: string +): TransactionInstruction => { + const dataLayout = BufferLayout.struct([ + BufferLayout.u8("instruction"), + BufferLayout.blob(50, "marketName"), + BufferLayout.blob(250, "marketDescription"), + BufferLayout.blob(250, "marketImageUrl"), + BufferLayout.blob(200, "padding"), + BufferLayout.u8("bumpSeed"), + ]); + + const [lendingMarketMetadata, _] = findProgramAddressSync( + [ + lendingMarket.toBytes(), + Buffer.from(anchor.utils.bytes.utf8.encode("MetaData")), + ], + + lendingProgramId + ); + + const data = Buffer.alloc(dataLayout.span); + dataLayout.encode( + { + instruction: LendingInstruction.UpdateMetadata, + marketName, + marketDescription, + marketImageUrl, + padding: Buffer.alloc(200), + }, + data + ); + + const keys = [ + { pubkey: lendingMarket, isSigner: false, isWritable: false }, + { pubkey: lendingMarketOwner, isSigner: true, isWritable: false }, + { pubkey: lendingMarketMetadata, isSigner: false, isWritable: true }, + { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, + ]; + + + return new TransactionInstruction({ + keys, + programId: lendingProgramId, + data, + }); +}; diff --git a/solend-sdk/src/instructions/updateReserveConfig.ts b/solend-sdk/src/instructions/updateReserveConfig.ts index 63e687b5..78ab030f 100644 --- a/solend-sdk/src/instructions/updateReserveConfig.ts +++ b/solend-sdk/src/instructions/updateReserveConfig.ts @@ -1,4 +1,5 @@ import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { RateLimiterConfig } from "../state/rateLimiter"; import { ReserveConfig } from "../state/reserve"; import * as Layout from "../utils/layout"; import { LendingInstruction } from "./instruction"; @@ -25,11 +26,13 @@ export const updateReserveConfig = ( pythPrice: PublicKey, switchboardOracle: PublicKey, reserveConfig: ReserveConfig, + rateLimiterConfig: RateLimiterConfig, solendProgramAddress: PublicKey ): TransactionInstruction => { const dataLayout = BufferLayout.struct([ BufferLayout.u8("instruction"), BufferLayout.u8("optimalUtilizationRate"), + BufferLayout.u8("maxUtilizationRate"), BufferLayout.u8("loanToValueRatio"), BufferLayout.u8("liquidationBonus"), BufferLayout.u8("liquidationThreshold"), @@ -44,6 +47,12 @@ export const updateReserveConfig = ( Layout.publicKey("feeReceiver"), BufferLayout.u8("protocolLiquidationFee"), BufferLayout.u8("protocolTakeRate"), + Layout.uint64("addedBorrowWeightBPS"), + BufferLayout.u8("reserveType"), + BufferLayout.u8("maxLiquidationBonus"), + BufferLayout.u8("maxLiquidationThreshold"), + Layout.uint64("windowDuration"), + Layout.uint64("maxOutflow"), ]); const data = Buffer.alloc(dataLayout.span); @@ -51,6 +60,7 @@ export const updateReserveConfig = ( { instruction: LendingInstruction.UpdateReserveConfig, optimalUtilizationRate: reserveConfig.optimalUtilizationRate, + maxUtilizationRate: reserveConfig.maxUtilizationRate, loanToValueRatio: reserveConfig.loanToValueRatio, liquidationBonus: reserveConfig.liquidationBonus, liquidationThreshold: reserveConfig.liquidationThreshold, @@ -65,6 +75,12 @@ export const updateReserveConfig = ( feeReceiver: reserveConfig.feeReceiver, protocolLiquidationFee: reserveConfig.protocolLiquidationFee, protocolTakeRate: reserveConfig.protocolTakeRate, + addedBorrowWeightBPS: reserveConfig.addedBorrowWeightBPS, + reserveType: reserveConfig.reserveType, + maxLiquidationBonus: reserveConfig.maxLiquidationBonus, + maxLiquidationThreshold: reserveConfig.maxLiquidationThreshold, + windowDuration: rateLimiterConfig.windowDuration, + maxOutflow: rateLimiterConfig.maxOutflow, }, data ); diff --git a/solend-sdk/src/state/lendingMarket.ts b/solend-sdk/src/state/lendingMarket.ts index 5b410366..75ee0350 100644 --- a/solend-sdk/src/state/lendingMarket.ts +++ b/solend-sdk/src/state/lendingMarket.ts @@ -1,11 +1,7 @@ import { AccountInfo, PublicKey } from "@solana/web3.js"; import * as fzstd from "fzstd"; import * as Layout from "../utils/layout"; -import { - RateLimiter, - RateLimiterLayout, - RATE_LIMITER_LEN, -} from "./rateLimiter"; +import { RateLimiter, RateLimiterLayout } from "./rateLimiter"; const BufferLayout = require("buffer-layout"); @@ -18,6 +14,8 @@ export interface LendingMarket { oracleProgramId: PublicKey; switchboardOracleProgramId: PublicKey; rateLimiter: RateLimiter; + whitelistedLiquidator: PublicKey | null; + riskAuthority: PublicKey; } export const LendingMarketLayout: typeof BufferLayout.Structure = @@ -30,7 +28,9 @@ export const LendingMarketLayout: typeof BufferLayout.Structure = Layout.publicKey("oracleProgramId"), Layout.publicKey("switchboardOracleProgramId"), RateLimiterLayout, - BufferLayout.blob(128 - RATE_LIMITER_LEN, "padding"), + Layout.publicKey("whitelistedLiquidator"), + Layout.publicKey("riskAuthority"), + BufferLayout.blob(8, "padding"), ]); export const LENDING_MARKET_SIZE = LendingMarketLayout.span; diff --git a/solend-sdk/src/state/obligation.ts b/solend-sdk/src/state/obligation.ts index 781faa72..78f5d5e2 100644 --- a/solend-sdk/src/state/obligation.ts +++ b/solend-sdk/src/state/obligation.ts @@ -17,9 +17,11 @@ export interface Obligation { borrows: ObligationLiquidity[]; depositedValue: BN; // decimals borrowedValue: BN; // decimals + borrowedValueUpperBound: BN; // decimals allowedBorrowValue: BN; // decimals unhealthyBorrowValue: BN; // decimals - borrowedValueUpperBound: BN; // decimals + superUnhealthyBorrowValue: BN; // decimals + borrowingIsolatedAsset: boolean; } // BN defines toJSON property, which messes up serialization @@ -73,7 +75,9 @@ export const ObligationLayout: typeof BufferLayout.Structure = Layout.uint128("allowedBorrowValue"), Layout.uint128("unhealthyBorrowValue"), Layout.uint128("borrowedValueUpperBound"), - BufferLayout.blob(48, "_padding"), + BufferLayout.u8("borrowingIsolatedAsset"), + Layout.uint128("superUnhealthyBorrowValue"), + BufferLayout.blob(31, "_padding"), BufferLayout.u8("depositsLen"), BufferLayout.u8("borrowsLen"), diff --git a/solend-sdk/src/state/reserve.ts b/solend-sdk/src/state/reserve.ts index 8e9adc2b..71c704a4 100644 --- a/solend-sdk/src/state/reserve.ts +++ b/solend-sdk/src/state/reserve.ts @@ -43,12 +43,16 @@ export interface ReserveCollateral { export interface ReserveConfig { optimalUtilizationRate: number; + maxUtilizationRate: number; loanToValueRatio: number; liquidationBonus: number; + maxLiquidationBonus: number; liquidationThreshold: number; + maxLiquidationThreshold: number; minBorrowRate: number; optimalBorrowRate: number; maxBorrowRate: number; + superMaxBorrowRate: BN; fees: { borrowFeeWad: BN; flashLoanFeeWad: BN; @@ -61,17 +65,27 @@ export interface ReserveConfig { protocolTakeRate: number; addedBorrowWeightBPS: BN; borrowWeight: BigNumber; + reserveType: AssetType; +} + +export enum AssetType { + Regular = 0, + Isolated = 1, } export const ReserveConfigLayout = BufferLayout.struct( [ BufferLayout.u8("optimalUtilizationRate"), + BufferLayout.u8("maxUtilizationRate"), BufferLayout.u8("loanToValueRatio"), BufferLayout.u8("liquidationBonus"), + BufferLayout.u8("maxLiquidationBonus"), BufferLayout.u8("liquidationThreshold"), + BufferLayout.u8("maxLiquidationThreshold"), BufferLayout.u8("minBorrowRate"), BufferLayout.u8("optimalBorrowRate"), BufferLayout.u8("maxBorrowRate"), + BufferLayout.u8("superMaxBorrowRate"), BufferLayout.struct( [ Layout.uint64("borrowFeeWad"), @@ -86,6 +100,7 @@ export const ReserveConfigLayout = BufferLayout.struct( BufferLayout.u8("protocolLiquidationFee"), BufferLayout.u8("protocolTakeRate"), Layout.uint64("addedBorrowWeightBPS"), + BufferLayout.u8("reserveType"), ], "config" ); @@ -131,7 +146,12 @@ export const ReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct( RateLimiterLayout, Layout.uint64("addedBorrowWeightBPS"), Layout.uint128("liquiditySmoothedMarketPrice"), - BufferLayout.blob(150, "padding"), + BufferLayout.u8("reserveType"), + BufferLayout.u8("maxUtilizationRate"), + BufferLayout.u8("superMaxBorrowRate"), + BufferLayout.u8("maxLiquidationBonus"), + BufferLayout.u8("maxLiquidationThreshold"), + BufferLayout.blob(138, "padding"), ] ); @@ -163,12 +183,16 @@ function decodeReserve(buffer: Buffer): Reserve { }, config: { optimalUtilizationRate: reserve.optimalUtilizationRate, + maxUtilizationRate: reserve.maxUtilizationRate, loanToValueRatio: reserve.loanToValueRatio, liquidationBonus: reserve.liquidationBonus, + maxLiquidationBonus: reserve.maxLiquidationBonus, liquidationThreshold: reserve.liquidationThreshold, + maxLiquidationThreshold: reserve.maxLiquidationThreshold, minBorrowRate: reserve.minBorrowRate, optimalBorrowRate: reserve.optimalBorrowRate, maxBorrowRate: reserve.maxBorrowRate, + superMaxBorrowRate: reserve.superMaxBorrowRate, fees: { borrowFeeWad: reserve.borrowFeeWad, flashLoanFeeWad: reserve.flashLoanFeeWad, @@ -183,6 +207,8 @@ function decodeReserve(buffer: Buffer): Reserve { borrowWeight: new BigNumber(reserve.addedBorrowWeightBPS.toString()) .dividedBy(new BigNumber(10000)) .plus(new BigNumber(1)), + reserveType: + reserve.reserveType == 0 ? AssetType.Regular : AssetType.Isolated, }, rateLimiter: reserve.rateLimiter, }; @@ -225,11 +251,7 @@ export function reserveToString(reserve: Reserve) { reserve, (key, value) => { // Skip padding - if ( - key === "padding" || - key === "oracleOption" || - value === undefined - ) { + if (key === "padding" || key === "oracleOption" || value === undefined) { return null; } switch (value.constructor.name) {