import * as anchor from '@project-serum/anchor' ; import { Program , Provider } from '@project-serum/anchor' ; import { AccountLayout , MintLayout , Token , TOKEN_PROGRAM_ID , } from '@solana/spl-token' ; import { Keypair , PublicKey , sendAndConfirmTransaction , SystemProgram , Transaction , TransactionSignature , } from '@solana/web3.js' ; import { assert } from 'chai' ; import buffer from 'buffer' ; import BN from 'bn.js' ; import { ClearingHouse , ClearingHouseUser } from '../sdk/src' ; export async function mockOracle ( price = 50 * 10e7 , expo = - 7 ) { const program = anchor . workspace . Pyth ; anchor . setProvider ( anchor . Provider . env ( ) ) ; const priceFeedAddress = await createPriceFeed ( { oracleProgram : program , initPrice : price , expo : expo , } ) ; const feedData = await getFeedData ( program , priceFeedAddress ) ; assert . ok ( feedData . price === price ) ; return priceFeedAddress ; } export async function mockUSDCMint ( provider ) { const fakeUSDCMint = anchor . web3 . Keypair . generate ( ) ; const createUSDCMintAccountIx = SystemProgram . createAccount ( { fromPubkey : provider . wallet . publicKey , newAccountPubkey : fakeUSDCMint . publicKey , lamports : await Token . getMinBalanceRentForExemptMint ( provider . connection ) , space : MintLayout . span , programId : TOKEN_PROGRAM_ID , } ) ; const initCollateralMintIx = Token . createInitMintInstruction ( TOKEN_PROGRAM_ID , fakeUSDCMint . publicKey , 6 , provider . wallet . publicKey , null ) ; const fakeUSDCTx = new Transaction ( ) ; fakeUSDCTx . add ( createUSDCMintAccountIx ) ; fakeUSDCTx . add ( initCollateralMintIx ) ; await sendAndConfirmTransaction ( provider . connection , fakeUSDCTx , [ provider . wallet . payer , fakeUSDCMint ] , { skipPreflight : false , commitment : 'recent' , preflightCommitment : 'recent' , } ) ; return fakeUSDCMint ; } export async function mockUserUSDCAccount ( fakeUSDCMint , usdcMintAmount , provider , owner ? ) { const userUSDCAccount = anchor . web3 . Keypair . generate ( ) ; const fakeUSDCTx = new Transaction ( ) ; if ( owner === undefined ) { owner = provider . wallet . publicKey ; } const createUSDCTokenAccountIx = SystemProgram . createAccount ( { fromPubkey : provider . wallet . publicKey , newAccountPubkey : userUSDCAccount . publicKey , lamports : await Token . getMinBalanceRentForExemptAccount ( provider . connection ) , space : AccountLayout . span , programId : TOKEN_PROGRAM_ID , } ) ; fakeUSDCTx . add ( createUSDCTokenAccountIx ) ; const initUSDCTokenAccountIx = Token . createInitAccountInstruction ( TOKEN_PROGRAM_ID , fakeUSDCMint . publicKey , userUSDCAccount . publicKey , owner ) ; fakeUSDCTx . add ( initUSDCTokenAccountIx ) ; const mintToUserAccountTx = await Token . createMintToInstruction ( TOKEN_PROGRAM_ID , fakeUSDCMint . publicKey , userUSDCAccount . publicKey , provider . wallet . publicKey , [ ] , usdcMintAmount . toNumber ( ) ) ; fakeUSDCTx . add ( mintToUserAccountTx ) ; const _fakeUSDCTxResult = await sendAndConfirmTransaction ( provider . connection , fakeUSDCTx , [ provider . wallet . payer , userUSDCAccount ] , { skipPreflight : false , commitment : 'recent' , preflightCommitment : 'recent' , } ) ; return userUSDCAccount ; } export async function mintToInsuranceFund ( chInsuranceAccount , fakeUSDCMint , amount , provider ) { const mintToUserAccountTx = await Token . createMintToInstruction ( TOKEN_PROGRAM_ID , fakeUSDCMint . publicKey , chInsuranceAccount . publicKey , provider . wallet . publicKey , [ ] , amount . toNumber ( ) ) ; const fakeUSDCTx = new Transaction ( ) ; fakeUSDCTx . add ( mintToUserAccountTx ) ; return await sendAndConfirmTransaction ( provider . connection , fakeUSDCTx , [ provider . wallet . payer ] , { skipPreflight : false , commitment : 'recent' , preflightCommitment : 'recent' , } ) ; } export async function initUserAccounts ( NUM_USERS , usdcMint , usdcAmount , provider ) { const user_keys = [ ] ; const userUSDCAccounts = [ ] ; const clearingHouses = [ ] ; const userAccountInfos = [ ] ; let userAccountPublicKey ; for ( let i = 0 ; i < NUM_USERS ; i ++ ) { console . log ( 'user' , i , 'initialize' ) ; const owner = anchor . web3 . Keypair . generate ( ) ; const ownerWallet = new anchor . Wallet ( owner ) ; await provider . connection . requestAirdrop ( ownerWallet . publicKey , 100000000 ) ; const newUserAcct = await mockUserUSDCAccount ( usdcMint , usdcAmount , provider , ownerWallet . publicKey ) ; const chProgram = anchor . workspace . ClearingHouse as anchor . Program ; const clearingHouse1 = ClearingHouse . from ( provider . connection , ownerWallet , chProgram . programId ) ; await clearingHouse1 . subscribe ( ) ; userUSDCAccounts . push ( newUserAcct ) ; clearingHouses . push ( clearingHouse1 ) ; [ , userAccountPublicKey ] = await clearingHouse1 . initializeUserAccountAndDepositCollateral ( usdcAmount , newUserAcct . publicKey ) ; const userAccount = ClearingHouseUser . from ( clearingHouse1 , ownerWallet . publicKey ) ; await userAccount . subscribe ( ) ; userAccountInfos . push ( userAccount ) ; user_keys . push ( userAccountPublicKey ) ; } return [ userUSDCAccounts , user_keys , clearingHouses , userAccountInfos ] ; } const empty32Buffer = buffer . Buffer . alloc ( 32 ) ; const PKorNull = ( data ) => data . equals ( empty32Buffer ) ? null : new anchor . web3 . PublicKey ( data ) ; export const createPriceFeed = async ( { oracleProgram , initPrice , confidence = undefined , expo = - 4 , } ) : Promise < PublicKey > => { const conf = confidence || new BN ( ( initPrice / 10 ) * 10 ** - expo ) ; const collateralTokenFeed = new anchor . web3 . Account ( ) ; await oracleProgram . rpc . initialize ( new BN ( initPrice * 10 ** - expo ) , expo , conf , { accounts : { price : collateralTokenFeed . publicKey } , signers : [ collateralTokenFeed ] , instructions : [ anchor . web3 . SystemProgram . createAccount ( { fromPubkey : oracleProgram . provider . wallet . publicKey , newAccountPubkey : collateralTokenFeed . publicKey , space : 3312 , lamports : await oracleProgram . provider . connection . getMinimumBalanceForRentExemption ( 3312 ) , programId : oracleProgram . programId , } ) , ] , } ) ; return collateralTokenFeed . publicKey ; } ; export const setFeedPrice = async ( oracleProgram , newPrice , priceFeed ) => { const info = await oracleProgram . provider . connection . getAccountInfo ( priceFeed ) ; const data = parsePriceData ( info . data ) ; await oracleProgram . rpc . setPrice ( new BN ( newPrice * 10 ** - data . exponent ) , { accounts : { price : priceFeed } , } ) ; } ; export const getFeedData = async ( oracleProgram , priceFeed ) => { const info = await oracleProgram . provider . connection . getAccountInfo ( priceFeed ) ; return parsePriceData ( info . data ) ; } ; const ERR_BUFFER_OUT_OF_BOUNDS = ( ) => new Error ( 'Attempt to access memory outside buffer bounds' ) ; const ERR_INVALID_ARG_TYPE = ( name , expected , actual ) => new Error ( `The "${ name }" argument must be of type ${ expected }. Received ${ actual }` ) ; const ERR_OUT_OF_RANGE = ( str , range , received ) => new Error ( `The value of "${ str } is out of range. It must be ${ range }. Received ${ received }` ) ; function validateNumber ( value , name ) { if ( typeof value !== 'number' ) throw ERR_INVALID_ARG_TYPE ( name , 'number' , value ) ; } function boundsError ( value , length ) { if ( Math . floor ( value ) !== value ) { validateNumber ( value , 'offset' ) ; throw ERR_OUT_OF_RANGE ( 'offset' , 'an integer' , value ) ; } if ( length < 0 ) throw ERR_BUFFER_OUT_OF_BOUNDS ( ) ; throw ERR_OUT_OF_RANGE ( 'offset' , `>= 0 and <= ${ length }` , value ) ; } function readBigInt64LE ( buffer , offset = 0 ) { validateNumber ( offset , 'offset' ) ; const first = buffer [ offset ] ; const last = buffer [ offset + 7 ] ; if ( first === undefined || last === undefined ) boundsError ( offset , buffer . length - 8 ) ; const val = buffer [ offset + 4 ] + buffer [ offset + 5 ] * 2 ** 8 + buffer [ offset + 6 ] * 2 ** 16 + ( last << 24 ) ; return ( ( BigInt ( val ) << BigInt ( 32 ) ) + BigInt ( first + buffer [ ++ offset ] * 2 ** 8 + buffer [ ++ offset ] * 2 ** 16 + buffer [ ++ offset ] * 2 ** 24 ) ) ; } function readBigUInt64LE ( buffer , offset = 0 ) { validateNumber ( offset , 'offset' ) ; const first = buffer [ offset ] ; const last = buffer [ offset + 7 ] ; if ( first === undefined || last === undefined ) boundsError ( offset , buffer . length - 8 ) ; const lo = first + buffer [ ++ offset ] * 2 ** 8 + buffer [ ++ offset ] * 2 ** 16 + buffer [ ++ offset ] * 2 ** 24 ; const hi = buffer [ ++ offset ] + buffer [ ++ offset ] * 2 ** 8 + buffer [ ++ offset ] * 2 ** 16 + last * 2 ** 24 ; return BigInt ( lo ) + ( BigInt ( hi ) << BigInt ( 32 ) ) ; } const parsePriceData = ( data ) => { const magic = data . readUInt32LE ( 0 ) ; const version = data . readUInt32LE ( 4 ) ; const type = data . readUInt32LE ( 8 ) ; const size = data . readUInt32LE ( 12 ) ; const priceType = data . readUInt32LE ( 16 ) ; const exponent = data . readInt32LE ( 20 ) ; const numComponentPrices = data . readUInt32LE ( 24 ) ; const currentSlot = readBigUInt64LE ( data , 32 ) ; const validSlot = readBigUInt64LE ( data , 40 ) ; const twapComponent = readBigInt64LE ( data , 48 ) ; const twap = Number ( twapComponent ) * 10 ** exponent ; const avolComponent = readBigUInt64LE ( data , 56 ) ; const avol = Number ( avolComponent ) * 10 ** exponent ; const drv0Component = readBigInt64LE ( data , 64 ) ; const drv0 = Number ( drv0Component ) * 10 ** exponent ; const drv1Component = readBigInt64LE ( data , 72 ) ; const drv1 = Number ( drv1Component ) * 10 ** exponent ; const drv2Component = readBigInt64LE ( data , 80 ) ; const drv2 = Number ( drv2Component ) * 10 ** exponent ; const drv3Component = readBigInt64LE ( data , 88 ) ; const drv3 = Number ( drv3Component ) * 10 ** exponent ; const drv4Component = readBigInt64LE ( data , 96 ) ; const drv4 = Number ( drv4Component ) * 10 ** exponent ; const drv5Component = readBigInt64LE ( data , 104 ) ; const drv5 = Number ( drv5Component ) * 10 ** exponent ; const productAccountKey = new anchor . web3 . PublicKey ( data . slice ( 112 , 144 ) ) ; const nextPriceAccountKey = PKorNull ( data . slice ( 144 , 176 ) ) ; const aggregatePriceUpdaterAccountKey = new anchor . web3 . PublicKey ( data . slice ( 176 , 208 ) ) ; const aggregatePriceInfo = parsePriceInfo ( data . slice ( 208 , 240 ) , exponent ) ; const priceComponents = [ ] ; let offset = 240 ; let shouldContinue = true ; while ( offset < data . length && shouldContinue ) { const publisher = PKorNull ( data . slice ( offset , offset + 32 ) ) ; offset += 32 ; if ( publisher ) { const aggregate = parsePriceInfo ( data . slice ( offset , offset + 32 ) , exponent ) ; offset += 32 ; const latest = parsePriceInfo ( data . slice ( offset , offset + 32 ) , exponent ) ; offset += 32 ; priceComponents . push ( { publisher , aggregate , latest } ) ; } else { shouldContinue = false ; } } return Object . assign ( Object . assign ( { magic , version , type , size , priceType , exponent , numComponentPrices , currentSlot , validSlot , twapComponent , twap , avolComponent , avol , drv0Component , drv0 , drv1Component , drv1 , drv2Component , drv2 , drv3Component , drv3 , drv4Component , drv4 , drv5Component , drv5 , productAccountKey , nextPriceAccountKey , aggregatePriceUpdaterAccountKey , } , aggregatePriceInfo ) , { priceComponents } ) ; } ; const _parseProductData = ( data ) => { const magic = data . readUInt32LE ( 0 ) ; const version = data . readUInt32LE ( 4 ) ; const type = data . readUInt32LE ( 8 ) ; const size = data . readUInt32LE ( 12 ) ; const priceAccountBytes = data . slice ( 16 , 48 ) ; const priceAccountKey = new anchor . web3 . PublicKey ( priceAccountBytes ) ; const product = { } ; let idx = 48 ; while ( idx < data . length ) { const keyLength = data [ idx ] ; idx ++ ; if ( keyLength ) { const key = data . slice ( idx , idx + keyLength ) . toString ( ) ; idx += keyLength ; const valueLength = data [ idx ] ; idx ++ ; const value = data . slice ( idx , idx + valueLength ) . toString ( ) ; idx += valueLength ; product [ key ] = value ; } } return { magic , version , type , size , priceAccountKey , product } ; } ; const parsePriceInfo = ( data , exponent ) => { const priceComponent = data . readBigUInt64LE ( 0 ) ; const price = Number ( priceComponent ) * 10 ** exponent ; const confidenceComponent = data . readBigUInt64LE ( 8 ) ; const confidence = Number ( confidenceComponent ) * 10 ** exponent ; const status = data . readUInt32LE ( 16 ) ; const corporateAction = data . readUInt32LE ( 20 ) ; const publishSlot = data . readBigUInt64LE ( 24 ) ; return { priceComponent , price , confidenceComponent , confidence , status , corporateAction , publishSlot , } ; } ;