github.com/gagliardetto/solana-go@v1.11.0/programs/bpf-loader/loader.go (about)

     1  package bpfloader
     2  
     3  import (
     4  	"encoding/binary"
     5  	"fmt"
     6  
     7  	"github.com/gagliardetto/solana-go"
     8  	"github.com/gagliardetto/solana-go/programs/system"
     9  	"github.com/gagliardetto/solana-go/rpc"
    10  )
    11  
    12  const (
    13  	PACKET_DATA_SIZE int = 1280 - 40 - 8
    14  )
    15  
    16  // https://github.com/solana-labs/solana/blob/v1.7.15/cli/src/program.rs#L1683
    17  func calculateMaxChunkSize(
    18  	createBuilder func(offset int, data []byte) *solana.TransactionBuilder,
    19  ) (size int, err error) {
    20  	transaction, err := createBuilder(0, []byte{}).Build()
    21  	if err != nil {
    22  		return
    23  	}
    24  	signatures := make(
    25  		[]solana.Signature,
    26  		transaction.Message.Header.NumRequiredSignatures,
    27  	)
    28  	transaction.Signatures = append(transaction.Signatures, signatures...)
    29  	serialized, err := transaction.MarshalBinary()
    30  	if err != nil {
    31  		return
    32  	}
    33  	size = PACKET_DATA_SIZE - len(serialized) - 1
    34  	return
    35  }
    36  
    37  // https://github.com/solana-labs/solana/blob/v1.7.15/cli/src/program.rs#L2006
    38  func completePartialProgramInit(
    39  	loaderId solana.PublicKey,
    40  	payerPubkey solana.PublicKey,
    41  	elfPubkey solana.PublicKey,
    42  	account *rpc.Account,
    43  	accountDataLen int,
    44  	minimumBalance uint64,
    45  	allowExcessiveBalance bool,
    46  ) (instructions []solana.Instruction, balanceNeeded uint64, err error) {
    47  	if account.Executable {
    48  		err = fmt.Errorf("buffer account is already executable")
    49  		return
    50  	}
    51  	if !account.Owner.Equals(loaderId) &&
    52  		!account.Owner.Equals(solana.SystemProgramID) {
    53  		err = fmt.Errorf(
    54  			"buffer account passed is already in use by another program",
    55  		)
    56  		return
    57  	}
    58  	if len(account.Data.GetBinary()) > 0 &&
    59  		len(account.Data.GetBinary()) < accountDataLen {
    60  		err = fmt.Errorf(
    61  			"buffer account passed is not large enough, may have been for a " +
    62  				" different deploy?",
    63  		)
    64  		return
    65  	}
    66  
    67  	if len(account.Data.GetBinary()) == 0 &&
    68  		account.Owner.Equals(solana.SystemProgramID) {
    69  		instructions = append(
    70  			instructions,
    71  			system.NewAllocateInstruction(uint64(accountDataLen), elfPubkey).
    72  				Build(),
    73  		)
    74  		instructions = append(
    75  			instructions,
    76  			system.NewAssignInstruction(loaderId, elfPubkey).Build(),
    77  		)
    78  		if account.Lamports < minimumBalance {
    79  			balance := minimumBalance - account.Lamports
    80  			instructions = append(
    81  				instructions,
    82  				system.NewTransferInstruction(balance, payerPubkey, elfPubkey).
    83  					Build(),
    84  			)
    85  			balanceNeeded = balance
    86  		} else if account.Lamports > minimumBalance &&
    87  			account.Owner.Equals(solana.SystemProgramID) &&
    88  			!allowExcessiveBalance {
    89  			err = fmt.Errorf(
    90  				"buffer account has a balance: %v.%v; it may already be in use",
    91  				account.Lamports/solana.LAMPORTS_PER_SOL,
    92  				account.Lamports%solana.LAMPORTS_PER_SOL,
    93  			)
    94  			return
    95  		}
    96  	}
    97  	return
    98  }
    99  
   100  func load(
   101  	payerPubkey solana.PublicKey,
   102  	account *rpc.Account,
   103  	programData []byte,
   104  	bufferDataLen int,
   105  	minimumBalance uint64,
   106  	loaderId solana.PublicKey,
   107  	bufferPubkey solana.PublicKey,
   108  	allowExcessiveBalance bool,
   109  ) (
   110  	initialBuilder *solana.TransactionBuilder,
   111  	writeBuilders []*solana.TransactionBuilder,
   112  	finalBuilder *solana.TransactionBuilder,
   113  	balanceNeeded uint64,
   114  	err error,
   115  ) {
   116  	var instructions []solana.Instruction
   117  	if account != nil {
   118  		instructions, balanceNeeded, err = completePartialProgramInit(
   119  			loaderId,
   120  			payerPubkey,
   121  			bufferPubkey,
   122  			account,
   123  			bufferDataLen,
   124  			minimumBalance,
   125  			allowExcessiveBalance,
   126  		)
   127  		if err != nil {
   128  			return
   129  		}
   130  	} else {
   131  		instructions = append(
   132  			instructions,
   133  			system.NewCreateAccountInstruction(
   134  				minimumBalance,
   135  				uint64(bufferDataLen),
   136  				loaderId,
   137  				payerPubkey,
   138  				bufferPubkey,
   139  			).Build(),
   140  		)
   141  		balanceNeeded = minimumBalance
   142  	}
   143  	if len(instructions) > 0 {
   144  		initialBuilder = solana.NewTransactionBuilder().SetFeePayer(payerPubkey)
   145  		for _, instruction := range instructions {
   146  			initialBuilder = initialBuilder.AddInstruction(instruction)
   147  		}
   148  	}
   149  
   150  	createBuilder := func(offset int, chunk []byte) *solana.TransactionBuilder {
   151  		data := make([]byte, len(chunk)+16)
   152  		binary.LittleEndian.PutUint32(data[0:], 0)
   153  		binary.LittleEndian.PutUint32(data[4:], uint32(offset))
   154  		binary.LittleEndian.PutUint32(data[8:], uint32(len(chunk)))
   155  		binary.LittleEndian.PutUint32(data[12:], 0)
   156  		copy(data[16:], chunk)
   157  		instruction := solana.NewInstruction(
   158  			loaderId,
   159  			solana.AccountMetaSlice{
   160  				solana.NewAccountMeta(bufferPubkey, true, true),
   161  			},
   162  			data,
   163  		)
   164  		return solana.NewTransactionBuilder().
   165  			AddInstruction(instruction).
   166  			SetFeePayer(payerPubkey)
   167  	}
   168  
   169  	chunkSize, err := calculateMaxChunkSize(createBuilder)
   170  	if err != nil {
   171  		return
   172  	}
   173  	writeBuilders = []*solana.TransactionBuilder{}
   174  	for i := 0; i < len(programData); i += chunkSize {
   175  		end := i + chunkSize
   176  		if end > len(programData) {
   177  			end = len(programData)
   178  		}
   179  		writeBuilders = append(
   180  			writeBuilders,
   181  			createBuilder(i, programData[i:end]),
   182  		)
   183  	}
   184  
   185  	finalBuilder = solana.NewTransactionBuilder().SetFeePayer(payerPubkey)
   186  	{
   187  		data := make([]byte, 4)
   188  		binary.LittleEndian.PutUint32(data[0:], 1)
   189  		instruction := solana.NewInstruction(
   190  			loaderId,
   191  			solana.AccountMetaSlice{
   192  				solana.NewAccountMeta(bufferPubkey, true, true),
   193  			},
   194  			data,
   195  		)
   196  		finalBuilder.AddInstruction(instruction)
   197  	}
   198  	return
   199  }
   200  
   201  func Deploy(
   202  	payerPubkey solana.PublicKey,
   203  	account *rpc.Account,
   204  	programData []byte,
   205  	minimumBalance uint64,
   206  	loaderId solana.PublicKey,
   207  	bufferPubkey solana.PublicKey,
   208  	allowExcessiveBalance bool,
   209  ) (
   210  	initialBuilder *solana.TransactionBuilder,
   211  	writeBuilders []*solana.TransactionBuilder,
   212  	finalBuilder *solana.TransactionBuilder,
   213  	balanceNeeded uint64,
   214  	err error,
   215  ) {
   216  	return load(
   217  		payerPubkey,
   218  		account,
   219  		programData,
   220  		len(programData),
   221  		minimumBalance,
   222  		loaderId,
   223  		bufferPubkey,
   224  		allowExcessiveBalance,
   225  	)
   226  }