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 }