refactoring

This commit is contained in:
jordansexton 2021-05-06 12:41:07 -05:00
parent 97bc5f76e3
commit cee89c00d3
16 changed files with 553 additions and 197 deletions

View File

@ -34,7 +34,7 @@ export const borrowObligationLiquidity = async (
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Borrowing funds...',
message: 'Borrowing liquidity...',
description: 'Please review transactions to approve.',
type: 'warn',
});
@ -146,7 +146,7 @@ export const borrowObligationLiquidity = async (
);
notify({
message: 'Funds borrowed.',
message: 'Liquidity borrowed.',
type: 'success',
description: `Transaction - ${txid}`,
});

View File

@ -0,0 +1,100 @@
import {
contexts,
LENDING_PROGRAM_ID,
models,
notify,
TokenAccount,
} from '@oyster/common';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
depositObligationCollateralInstruction,
refreshReserveInstruction,
Reserve,
} from '../models';
const { approve } = models;
const { sendTransaction } = contexts.Connection;
// @FIXME
export const depositObligationCollateral = async (
connection: Connection,
wallet: any,
collateralAmount: number,
source: TokenAccount,
reserve: Reserve,
reserveAddress: PublicKey,
obligationAddress: PublicKey
) => {
notify({
message: 'Depositing collateral...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
const sourceCollateral = source.pubkey;
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
sourceCollateral,
wallet.publicKey,
collateralAmount,
);
signers.push(transferAuthority);
instructions.push(
refreshReserveInstruction(
reserveAddress,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
depositObligationCollateralInstruction(
collateralAmount,
sourceCollateral,
reserve.collateral.mintPubkey,
reserveAddress,
obligationAddress,
reserve.lendingMarket,
lendingMarketAuthority,
// @FIXME: wallet must sign
wallet.publicKey,
transferAuthority.publicKey,
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Collateral deposited.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch {
// TODO:
}
};

View File

@ -15,20 +15,14 @@ import {
} from '@solana/web3.js';
import {
depositReserveLiquidityInstruction,
initReserveInstruction,
refreshReserveInstruction,
Reserve,
} from '../models';
const { sendTransaction } = contexts.Connection;
const {
createUninitializedAccount,
ensureSplAccount,
findOrCreateAccountByMint,
} = actions;
const { ensureSplAccount, findOrCreateAccountByMint } = actions;
const { approve } = models;
// @FIXME: split up into deposit, and init which requires lending market owner
export const depositReserveLiquidity = async (
connection: Connection,
wallet: any,
@ -38,13 +32,11 @@ export const depositReserveLiquidity = async (
reserveAddress: PublicKey,
) => {
notify({
message: 'Depositing funds...',
message: 'Depositing liquidity...',
description: 'Please review transactions to approve.',
type: 'warn',
});
const isInitalized = true; // TODO: finish reserve init
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
@ -79,70 +71,35 @@ export const depositReserveLiquidity = async (
signers.push(transferAuthority);
let destinationCollateralAccount: PublicKey = isInitalized
? await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.collateral.mintPubkey,
signers,
)
: createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
let destinationCollateralAccount: PublicKey = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.collateral.mintPubkey,
signers,
);
if (isInitalized) {
instructions.push(
refreshReserveInstruction(
reserveAddress,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
depositReserveLiquidityInstruction(
liquidityAmount,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidity.supplyPubkey,
reserve.collateral.mintPubkey,
reserve.lendingMarket,
lendingMarketAuthority,
transferAuthority.publicKey,
),
);
} else {
// TODO: finish reserve init
// @FIXME: reserve config
const MAX_UTILIZATION_RATE = 80;
instructions.push(
initReserveInstruction(
liquidityAmount,
MAX_UTILIZATION_RATE,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidity.mintPubkey,
reserve.liquidity.supplyPubkey,
reserve.liquidity.feeReceiver,
reserve.collateral.mintPubkey,
reserve.collateral.supplyPubkey,
reserve.lendingMarket,
lendingMarketAuthority,
// @FIXME: lending market owner
lendingMarketOwner,
transferAuthority.publicKey,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
);
}
instructions.push(
refreshReserveInstruction(
reserveAddress,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
depositReserveLiquidityInstruction(
liquidityAmount,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidity.supplyPubkey,
reserve.collateral.mintPubkey,
reserve.lendingMarket,
lendingMarketAuthority,
transferAuthority.publicKey,
),
);
try {
let { txid } = await sendTransaction(
@ -154,7 +111,7 @@ export const depositReserveLiquidity = async (
);
notify({
message: 'Funds deposited.',
message: 'Liquidity deposited.',
type: 'success',
description: `Transaction - ${txid}`,
});

View File

@ -0,0 +1,59 @@
import { contexts, notify } from '@oyster/common';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { initObligationInstruction, Obligation } from '../models';
const { sendTransaction } = contexts.Connection;
export const initObligation = async (
connection: Connection,
wallet: any,
obligation: Obligation,
obligationAddress: PublicKey
) => {
notify({
message: 'Initializing obligation...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
// @FIXME: obligation owner must sign
signers.push(wallet.info.account);
instructions.push(
initObligationInstruction(
obligationAddress,
obligation.lendingMarket,
// @FIXME: need to sign with wallet
wallet.publicKey
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Obligation initialized.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch {
// TODO:
throw new Error();
}
};

View File

@ -0,0 +1,122 @@
import {
actions,
contexts,
LENDING_PROGRAM_ID,
models,
notify,
TokenAccount,
} from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { initReserveInstruction, Reserve } from '../models';
const { sendTransaction } = contexts.Connection;
const {
createUninitializedAccount,
ensureSplAccount,
} = actions;
const { approve } = models;
export const initReserve = async (
connection: Connection,
// @FIXME: wallet must be lending market owner
wallet: any,
liquidityAmount: number,
source: TokenAccount,
reserve: Reserve,
reserveAddress: PublicKey,
) => {
notify({
message: 'Initializing reserve...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()], // which account should be authority
LENDING_PROGRAM_ID,
);
const sourceLiquidityAccount = ensureSplAccount(
instructions,
cleanupInstructions,
source,
wallet.publicKey,
liquidityAmount + accountRentExempt,
signers,
);
// create approval for transfer transactions
const transferAuthority = approve(
instructions,
cleanupInstructions,
sourceLiquidityAccount,
wallet.publicKey,
liquidityAmount,
);
signers.push(transferAuthority);
let destinationCollateralAccount: PublicKey = createUninitializedAccount(
instructions,
wallet.publicKey,
accountRentExempt,
signers,
);
instructions.push(
initReserveInstruction(
liquidityAmount,
reserve.config,
sourceLiquidityAccount,
destinationCollateralAccount,
reserveAddress,
reserve.liquidity.mintPubkey,
reserve.liquidity.supplyPubkey,
reserve.liquidity.feeReceiver,
reserve.collateral.mintPubkey,
reserve.collateral.supplyPubkey,
reserve.lendingMarket,
lendingMarketAuthority,
// @FIXME: need to sign with wallet as lending market owner
wallet.publicKey,
transferAuthority.publicKey,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Reserve initialized.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch {
// TODO:
throw new Error();
}
};

View File

@ -39,7 +39,7 @@ export const liquidateObligation = async (
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Repaying funds...',
message: 'Liquidating obligation...',
description: 'Please review transactions to approve.',
type: 'warn',
});
@ -146,7 +146,7 @@ export const liquidateObligation = async (
);
notify({
message: 'Funds liquidated.',
message: 'Obligation liquidated.',
type: 'success',
description: `Transaction - ${txid}`,
});

View File

@ -32,7 +32,7 @@ export const redeemReserveCollateral = async (
reserveAddress: PublicKey,
) => {
notify({
message: 'Withdrawing funds...',
message: 'Redeeming collateral...',
description: 'Please review transactions to approve.',
type: 'warn',
});
@ -105,7 +105,7 @@ export const redeemReserveCollateral = async (
);
notify({
message: 'Funds deposited.',
message: 'Collateral redeemed.',
type: 'success',
description: `Transaction - ${txid}`,
});

View File

@ -35,7 +35,7 @@ export const repayObligationLiquidity = async (
obligation: ParsedAccount<Obligation>,
) => {
notify({
message: 'Repaying funds...',
message: 'Repaying liquidity...',
description: 'Please review transactions to approve.',
type: 'warn',
});
@ -117,7 +117,7 @@ export const repayObligationLiquidity = async (
);
notify({
message: 'Funds repaid.',
message: 'Liquidity repaid.',
type: 'success',
description: `Transaction - ${txid}`,
});

View File

@ -0,0 +1,106 @@
import {
contexts,
findOrCreateAccountByMint,
LENDING_PROGRAM_ID,
models,
notify,
TokenAccount,
} from '@oyster/common';
import { AccountLayout } from '@solana/spl-token';
import {
Account,
Connection,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import {
withdrawObligationCollateralInstruction,
refreshReserveInstruction,
Reserve,
} from '../models';
const { approve } = models;
const { sendTransaction } = contexts.Connection;
// @FIXME
export const withdrawObligationCollateral = async (
connection: Connection,
wallet: any,
collateralAmount: number,
source: TokenAccount,
reserve: Reserve,
reserveAddress: PublicKey,
obligationAddress: PublicKey,
) => {
notify({
message: 'Withdrawing collateral...',
description: 'Please review transactions to approve.',
type: 'warn',
});
// user from account
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span,
);
const [lendingMarketAuthority] = await PublicKey.findProgramAddress(
[reserve.lendingMarket.toBuffer()],
LENDING_PROGRAM_ID,
);
// @FIXME: wallet must sign as obligation owner
signers.push(wallet.info.account);
// get destination account
const destinationCollateral = await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
reserve.collateral.mintPubkey,
signers,
);
instructions.push(
refreshReserveInstruction(
reserveAddress,
reserve.liquidity.oracleOption
? reserve.liquidity.oraclePubkey
: undefined,
),
withdrawObligationCollateralInstruction(
collateralAmount,
reserve.collateral.supplyPubkey,
destinationCollateral,
reserveAddress,
obligationAddress,
reserve.lendingMarket,
lendingMarketAuthority,
// @FIXME: wallet must sign
wallet.publicKey
),
);
try {
let { txid } = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers,
true,
);
notify({
message: 'Collateral withdrawn.',
type: 'success',
description: `Transaction - ${txid}`,
});
} catch {
// TODO:
}
};

View File

@ -145,9 +145,7 @@ export const BorrowInput = (props: {
parseFloat(value),
borrowReserve,
// TODO: select existing obligations by collateral reserve
userObligationsByReserve.length > 0
? userObligationsByReserve[0].obligation.account
: undefined,
userObligationsByReserve[0].obligation.account
);
setValue('');

View File

@ -45,13 +45,11 @@ export const useLending = () => {
const processAccount = useCallback(
(item: { pubkey: PublicKey; account: AccountInfo<Buffer> }) => {
if (isReserve(item.account)) {
const reserve = cache.add(
return cache.add(
item.pubkey.toBase58(),
item.account,
ReserveParser,
);
return reserve;
} else if (isLendingMarket(item.account)) {
return cache.add(
item.pubkey.toBase58(),

View File

@ -8,6 +8,7 @@ import {
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
import * as Layout from '../../utils/layout';
import { ReserveConfig } from '../state';
import { LendingInstruction } from './instruction';
/// Initializes a new lending market reserve.
@ -42,8 +43,7 @@ import { LendingInstruction } from './instruction';
// },
export const initReserveInstruction = (
liquidityAmount: number | BN,
// @FIXME: reserve config
maxUtilizationRate: number,
config: ReserveConfig,
sourceLiquidity: PublicKey,
destinationCollateral: PublicKey,
reserve: PublicKey,
@ -61,8 +61,15 @@ export const initReserveInstruction = (
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
Layout.uint64('liquidityAmount'),
// @FIXME: reserve config
BufferLayout.u8('maxUtilizationRate'),
BufferLayout.u8('optimalUtilizationRate'),
BufferLayout.u8('loanToValueRatio'),
BufferLayout.u8('liquidationBonus'),
BufferLayout.u8('liquidationThreshold'),
BufferLayout.u8('minBorrowRate'),
BufferLayout.u8('optimalBorrowRate'),
BufferLayout.u8('maxBorrowRate'),
Layout.uint64('borrowFeeWad'),
BufferLayout.u8('hostFeePercentage'),
]);
const data = Buffer.alloc(dataLayout.span);
@ -70,7 +77,15 @@ export const initReserveInstruction = (
{
instruction: LendingInstruction.InitReserve,
liquidityAmount: new BN(liquidityAmount),
maxUtilizationRate,
optimalUtilizationRate: config.optimalUtilizationRate,
loanToValueRatio: config.loanToValueRatio,
liquidationBonus: config.liquidationBonus,
liquidationThreshold: config.liquidationThreshold,
minBorrowRate: config.minBorrowRate,
optimalBorrowRate: config.optimalBorrowRate,
maxBorrowRate: config.maxBorrowRate,
borrowFeeWad: config.fees.borrowFeeWad,
hostFeePercentage: config.fees.hostFeePercentage,
},
data,
);

View File

@ -1,4 +1,11 @@
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
import * as Layout from '../../utils/layout';
export const LastUpdateLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[Layout.uint64('slot'), BufferLayout.u8('stale')],
'lastUpdate'
);
export interface LastUpdate {
slot: BN;

View File

@ -2,6 +2,13 @@ import { AccountInfo, PublicKey } from '@solana/web3.js';
import * as BufferLayout from 'buffer-layout';
import * as Layout from '../../utils/layout';
export interface LendingMarket {
version: number;
isInitialized: boolean;
quoteTokenMint: PublicKey;
tokenProgramId: PublicKey;
}
export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('version'),
@ -14,13 +21,6 @@ export const LendingMarketLayout: typeof BufferLayout.Structure = BufferLayout.s
],
);
export interface LendingMarket {
version: number;
isInitialized: boolean;
quoteTokenMint: PublicKey;
tokenProgramId: PublicKey;
}
export const isLendingMarket = (info: AccountInfo<Buffer>) => {
return info.data.length === LendingMarketLayout.span;
};

View File

@ -2,16 +2,41 @@ import { AccountInfo, PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
import * as Layout from '../../utils/layout';
import { LastUpdate } from './lastUpdate';
import { LastUpdate, LastUpdateLayout } from './lastUpdate';
export interface Obligation {
version: number;
lastUpdate: LastUpdate;
lendingMarket: PublicKey;
owner: PublicKey;
// @FIXME: check usages
deposits: ObligationCollateral[];
// @FIXME: check usages
borrows: ObligationLiquidity[];
depositedValue: BN; // decimals
borrowedValue: BN; // decimals
allowedBorrowValue: BN; // decimals
unhealthyBorrowValue: BN; // decimals
}
export interface ObligationCollateral {
depositReserve: PublicKey;
depositedAmount: BN;
marketValue: BN; // decimals
}
export interface ObligationLiquidity {
borrowReserve: PublicKey;
cumulativeBorrowRateWads: BN; // decimals
borrowedAmountWads: BN; // decimals
marketValue: BN; // decimals
}
export const ObligationLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('version'),
BufferLayout.struct(
[Layout.uint64('slot'), BufferLayout.u8('stale')],
'lastUpdate',
),
LastUpdateLayout,
Layout.publicKey('lendingMarket'),
Layout.publicKey('owner'),
@ -61,34 +86,6 @@ export interface ProtoObligation {
dataFlat: Buffer;
}
export interface Obligation {
version: number;
lastUpdate: LastUpdate;
lendingMarket: PublicKey;
owner: PublicKey;
// @FIXME: check usages
deposits: ObligationCollateral[];
// @FIXME: check usages
borrows: ObligationLiquidity[];
depositedValue: BN; // decimals
borrowedValue: BN; // decimals
allowedBorrowValue: BN; // decimals
unhealthyBorrowValue: BN; // decimals
}
export interface ObligationCollateral {
depositReserve: PublicKey;
depositedAmount: BN;
marketValue: BN; // decimals
}
export interface ObligationLiquidity {
borrowReserve: PublicKey;
cumulativeBorrowRateWads: BN; // decimals
borrowedAmountWads: BN; // decimals
marketValue: BN; // decimals
}
export const ObligationParser = (
pubkey: PublicKey,
info: AccountInfo<Buffer>,

View File

@ -3,70 +3,7 @@ import { AccountInfo, PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
import * as BufferLayout from 'buffer-layout';
import * as Layout from '../../utils/layout';
import { LastUpdate } from './lastUpdate';
export const ReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('version'),
BufferLayout.struct(
[Layout.uint64('slot'), BufferLayout.u8('stale')],
'lastUpdate',
),
Layout.publicKey('lendingMarket'),
BufferLayout.struct(
[
Layout.publicKey('mintPubkey'),
BufferLayout.u8('mintDecimals'),
Layout.publicKey('supplyPubkey'),
Layout.publicKey('feeReceiver'),
// @FIXME: oracle option
// TODO: replace u32 option with generic equivalent
BufferLayout.u32('oracleOption'),
Layout.publicKey('oracle'),
Layout.uint64('availableAmount'),
Layout.uint128('borrowedAmountWads'),
Layout.uint128('cumulativeBorrowRateWads'),
Layout.uint64('marketPrice'),
],
'liquidity',
),
BufferLayout.struct(
[
Layout.publicKey('mintPubkey'),
Layout.uint64('mintTotalSupply'),
Layout.publicKey('supplyPubkey'),
],
'collateral',
),
BufferLayout.struct(
[
BufferLayout.u8('optimalUtilizationRate'),
BufferLayout.u8('loanToValueRatio'),
BufferLayout.u8('liquidationBonus'),
BufferLayout.u8('liquidationThreshold'),
BufferLayout.u8('minBorrowRate'),
BufferLayout.u8('optimalBorrowRate'),
BufferLayout.u8('maxBorrowRate'),
BufferLayout.struct(
[Layout.uint64('borrowFeeWad'), BufferLayout.u8('hostFeePercentage')],
'fees',
),
],
'config',
),
BufferLayout.blob(256, 'padding'),
],
);
export const isReserve = (info: AccountInfo<Buffer>) => {
return info.data.length === ReserveLayout.span;
};
import { LastUpdate, LastUpdateLayout } from './lastUpdate';
export interface Reserve {
version: number;
@ -111,6 +48,66 @@ export interface ReserveConfig {
};
}
export const ReserveLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('version'),
LastUpdateLayout,
Layout.publicKey('lendingMarket'),
BufferLayout.struct(
[
Layout.publicKey('mintPubkey'),
BufferLayout.u8('mintDecimals'),
Layout.publicKey('supplyPubkey'),
Layout.publicKey('feeReceiver'),
// @FIXME: oracle option
// TODO: replace u32 option with generic equivalent
BufferLayout.u32('oracleOption'),
Layout.publicKey('oracle'),
Layout.uint64('availableAmount'),
Layout.uint128('borrowedAmountWads'),
Layout.uint128('cumulativeBorrowRateWads'),
Layout.uint64('marketPrice'),
],
'liquidity',
),
BufferLayout.struct(
[
Layout.publicKey('mintPubkey'),
Layout.uint64('mintTotalSupply'),
Layout.publicKey('supplyPubkey'),
],
'collateral'
),
BufferLayout.struct(
[
BufferLayout.u8('optimalUtilizationRate'),
BufferLayout.u8('loanToValueRatio'),
BufferLayout.u8('liquidationBonus'),
BufferLayout.u8('liquidationThreshold'),
BufferLayout.u8('minBorrowRate'),
BufferLayout.u8('optimalBorrowRate'),
BufferLayout.u8('maxBorrowRate'),
BufferLayout.struct(
[Layout.uint64('borrowFeeWad'), BufferLayout.u8('hostFeePercentage')],
'fees',
),
],
'config'
),
BufferLayout.blob(256, 'padding'),
],
);
export const isReserve = (info: AccountInfo<Buffer>) => {
return info.data.length === ReserveLayout.span;
};
export const ReserveParser = (pubkey: PublicKey, info: AccountInfo<Buffer>) => {
const buffer = Buffer.from(info.data);
const reserve = ReserveLayout.decode(buffer) as Reserve;