github.com/gagliardetto/solana-go@v1.11.0/programs/address-lookup-table/address-lookup.go (about)

     1  package addresslookuptable
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math"
     7  
     8  	bin "github.com/gagliardetto/binary"
     9  	"github.com/gagliardetto/solana-go"
    10  	"github.com/gagliardetto/solana-go/rpc"
    11  )
    12  
    13  // The serialized size of lookup table metadata.
    14  const (
    15  	LOOKUP_TABLE_META_SIZE     = 56
    16  	LOOKUP_TABLE_MAX_ADDRESSES = 256
    17  )
    18  
    19  // DecodeAddressLookupTableState decodes the given account bytes into a AddressLookupTableState.
    20  func DecodeAddressLookupTableState(data []byte) (*AddressLookupTableState, error) {
    21  	decoder := bin.NewBinDecoder(data)
    22  	var state AddressLookupTableState
    23  	if err := state.UnmarshalWithDecoder(decoder); err != nil {
    24  		return nil, err
    25  	}
    26  	return &state, nil
    27  }
    28  
    29  func GetAddressLookupTable(
    30  	ctx context.Context,
    31  	rpcClient *rpc.Client,
    32  	address solana.PublicKey,
    33  ) (*AddressLookupTableState, error) {
    34  	account, err := rpcClient.GetAccountInfo(ctx, address)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  	if account == nil {
    39  		return nil, fmt.Errorf("account not found")
    40  	}
    41  	return DecodeAddressLookupTableState(account.GetBinary())
    42  }
    43  
    44  func GetAddressLookupTableStateWithOpts(
    45  	ctx context.Context,
    46  	rpcClient *rpc.Client,
    47  	address solana.PublicKey,
    48  	opts *rpc.GetAccountInfoOpts,
    49  ) (*AddressLookupTableState, error) {
    50  	account, err := rpcClient.GetAccountInfoWithOpts(ctx, address, opts)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  	if account == nil {
    55  		return nil, fmt.Errorf("account not found")
    56  	}
    57  	return DecodeAddressLookupTableState(account.GetBinary())
    58  }
    59  
    60  type AddressLookupTableState struct {
    61  	TypeIndex                  uint32
    62  	DeactivationSlot           uint64
    63  	LastExtendedSlot           uint64
    64  	LastExtendedSlotStartIndex uint8
    65  	Authority                  *solana.PublicKey
    66  	Addresses                  solana.PublicKeySlice
    67  }
    68  
    69  func (a *AddressLookupTableState) UnmarshalWithDecoder(decoder *bin.Decoder) (err error) {
    70  	if a.TypeIndex, err = decoder.ReadUint32(bin.LE); err != nil {
    71  		return fmt.Errorf("failed to decode TypeIndex: %w", err)
    72  	}
    73  	if a.DeactivationSlot, err = decoder.ReadUint64(bin.LE); err != nil {
    74  		return fmt.Errorf("failed to decode DeactivationSlot: %w", err)
    75  	}
    76  	if a.LastExtendedSlot, err = decoder.ReadUint64(bin.LE); err != nil {
    77  		return fmt.Errorf("failed to decode LastExtendedSlot: %w", err)
    78  	}
    79  	if a.LastExtendedSlotStartIndex, err = decoder.ReadUint8(); err != nil {
    80  		return fmt.Errorf("failed to decode LastExtendedSlotStartIndex: %w", err)
    81  	}
    82  	has, err := decoder.ReadOption()
    83  	if err != nil {
    84  		return fmt.Errorf("failed to decode Authority option: %w", err)
    85  	}
    86  	if has {
    87  		var auth solana.PublicKey
    88  		if _, err := decoder.Read(auth[:]); err != nil {
    89  			return fmt.Errorf("failed to decode Authority: %w", err)
    90  		}
    91  		a.Authority = &auth
    92  	} else {
    93  		err = decoder.Discard(32)
    94  		if err != nil {
    95  			return fmt.Errorf("failed to decode Authority: %w", err)
    96  		}
    97  	}
    98  	serializedAddressesNumBytes := decoder.Len() - LOOKUP_TABLE_META_SIZE
    99  	if serializedAddressesNumBytes == 0 {
   100  		// No addresses (the lookup table is empty).
   101  		a.Addresses = make(solana.PublicKeySlice, 0)
   102  		return nil
   103  	}
   104  
   105  	numSerializedAddresses := serializedAddressesNumBytes / 32
   106  	if serializedAddressesNumBytes%32 != 0 {
   107  		// cut off the remaining bytes
   108  		// skip the difference
   109  		if err := decoder.Discard(serializedAddressesNumBytes % 32); err != nil {
   110  			return fmt.Errorf("failed to discard remaining bytes: %w", err)
   111  		}
   112  		// return fmt.Errorf("lookup table is invalid; serialized addresses are not a multiple of 32 bytes, with %d bytes remaining", serializedAddressesNumBytes%32)
   113  	}
   114  	if numSerializedAddresses > LOOKUP_TABLE_MAX_ADDRESSES {
   115  		return fmt.Errorf("lookup table is invalid: max addresses exceeded (%d > %d)", numSerializedAddresses, LOOKUP_TABLE_MAX_ADDRESSES)
   116  	}
   117  
   118  	// Set the position to the start of the serialized addresses.
   119  	if err := decoder.SetPosition(LOOKUP_TABLE_META_SIZE); err != nil {
   120  		return fmt.Errorf("failed to set position: %w", err)
   121  	}
   122  	a.Addresses = make(solana.PublicKeySlice, numSerializedAddresses)
   123  
   124  	for i := 0; i < numSerializedAddresses; i++ {
   125  		var address solana.PublicKey
   126  		numRead, err := decoder.Read(address[:])
   127  		if err != nil {
   128  			return fmt.Errorf("failed to read addresses[%d]: %w", i, err)
   129  		}
   130  		if numRead != 32 {
   131  			return fmt.Errorf("failed to read addresses[%d]: expected to read 32, but read %d", i, numRead)
   132  		}
   133  		a.Addresses[i] = address
   134  	}
   135  	if decoder.Remaining() != 0 {
   136  		// return fmt.Errorf("failed to read all addresses: remaining %d bytes", decoder.Remaining())
   137  	}
   138  	return nil
   139  }
   140  
   141  func (a AddressLookupTableState) MarshalWithEncoder(encoder *bin.Encoder) error {
   142  	if err := encoder.WriteUint32(a.TypeIndex, bin.LE); err != nil {
   143  		return err
   144  	}
   145  	if err := encoder.WriteUint64(a.DeactivationSlot, bin.LE); err != nil {
   146  		return err
   147  	}
   148  	if err := encoder.WriteUint64(a.LastExtendedSlot, bin.LE); err != nil {
   149  		return err
   150  	}
   151  	if err := encoder.WriteUint8(a.LastExtendedSlotStartIndex); err != nil {
   152  		return err
   153  	}
   154  	if a.Authority != nil {
   155  		if err := encoder.WriteOption(true); err != nil {
   156  			return err
   157  		}
   158  		if _, err := encoder.Write(a.Authority[:]); err != nil {
   159  			return err
   160  		}
   161  	} else {
   162  		if err := encoder.WriteOption(false); err != nil {
   163  			return err
   164  		}
   165  		if _, err := encoder.Write(make([]byte, 32)); err != nil {
   166  			return err
   167  		}
   168  	}
   169  	if _, err := encoder.Write(make([]byte, 2)); err != nil {
   170  		return err
   171  	}
   172  	for _, address := range a.Addresses {
   173  		if _, err := encoder.Write(address[:]); err != nil {
   174  			return err
   175  		}
   176  	}
   177  	return nil
   178  }
   179  
   180  func (a AddressLookupTableState) IsActive() bool {
   181  	return a.DeactivationSlot == math.MaxUint64
   182  }
   183  
   184  type KeyedAddressLookupTable struct {
   185  	Key   solana.PublicKey
   186  	State AddressLookupTableState
   187  }
   188  
   189  func NewKeyedAddressLookupTable(key solana.PublicKey) *KeyedAddressLookupTable {
   190  	return &KeyedAddressLookupTable{
   191  		Key: key,
   192  	}
   193  }
   194  
   195  func (a *KeyedAddressLookupTable) UnmarshalWithDecoder(decoder *bin.Decoder) error {
   196  	if err := a.State.UnmarshalWithDecoder(decoder); err != nil {
   197  		return err
   198  	}
   199  	return nil
   200  }
   201  
   202  func (a KeyedAddressLookupTable) MarshalWithEncoder(encoder *bin.Encoder) error {
   203  	if err := a.State.MarshalWithEncoder(encoder); err != nil {
   204  		return err
   205  	}
   206  	return nil
   207  }