Skip to content

Commit 7948b20

Browse files
committed
Add support for ALTs
1 parent f4cd871 commit 7948b20

File tree

6 files changed

+206
-5
lines changed

6 files changed

+206
-5
lines changed

pkg/solana/address_lookup_table.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package solana
2+
3+
import (
4+
"bytes"
5+
"crypto/ed25519"
6+
)
7+
8+
type AddressLookupTable struct {
9+
PublicKey ed25519.PublicKey
10+
Addresses []ed25519.PublicKey
11+
}
12+
13+
type SortableAddressLookupTables []AddressLookupTable
14+
15+
func (s SortableAddressLookupTables) Len() int {
16+
return len(s)
17+
}
18+
19+
func (s SortableAddressLookupTables) Less(i int, j int) bool {
20+
return bytes.Compare(s[i].PublicKey, s[j].PublicKey) < 0
21+
}
22+
23+
func (s SortableAddressLookupTables) Swap(i int, j int) {
24+
s[i], s[j] = s[j], s[i]
25+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package address_lookup_table
2+
3+
import (
4+
"crypto/ed25519"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/code-payments/code-server/pkg/solana/binary"
9+
"github.com/mr-tron/base58"
10+
)
11+
12+
var (
13+
ErrInvalidAccountSize = errors.New("invalid address lookup table account size")
14+
ErrInvalidAccountType = errors.New("invalid account type")
15+
)
16+
17+
const (
18+
altDescriminator = 1
19+
20+
metadataSize = 56
21+
maxAddresses = 256
22+
23+
optionSize = 1
24+
)
25+
26+
type AddressLookupTableAccount struct {
27+
DeactivationSlot uint64
28+
LastExtendedSlot uint64
29+
LastExtendedSlotStartIndex uint8
30+
Authority ed25519.PublicKey
31+
Addresses []ed25519.PublicKey
32+
}
33+
34+
func (obj *AddressLookupTableAccount) Unmarshal(data []byte) error {
35+
if len(data) < metadataSize {
36+
return ErrInvalidAccountSize
37+
}
38+
39+
var offset int
40+
41+
var descriminator uint32
42+
binary.GetUint32(data[offset:], &descriminator, &offset)
43+
if descriminator != altDescriminator {
44+
return ErrInvalidAccountType
45+
}
46+
47+
binary.GetUint64(data[offset:], &obj.DeactivationSlot, &offset)
48+
binary.GetUint64(data[offset:], &obj.LastExtendedSlot, &offset)
49+
binary.GetUint8(data[offset:], &obj.LastExtendedSlotStartIndex, &offset)
50+
binary.GetOptionalKey32(data[offset:], &obj.Authority, &offset, optionSize)
51+
52+
offset = metadataSize
53+
54+
addressBufferSize := len(data) - offset
55+
addressCount := addressBufferSize / 32
56+
if addressBufferSize%ed25519.PublicKeySize != 0 {
57+
return ErrInvalidAccountSize
58+
} else if addressCount > maxAddresses {
59+
return ErrInvalidAccountSize
60+
}
61+
62+
obj.Addresses = make([]ed25519.PublicKey, addressCount)
63+
for i := range addressCount {
64+
binary.GetKey32(data[offset:], &obj.Addresses[i], &offset)
65+
}
66+
67+
return nil
68+
}
69+
70+
func (obj *AddressLookupTableAccount) String() string {
71+
addressesString := "{"
72+
for i, address := range obj.Addresses {
73+
addressesString += fmt.Sprintf("%d:%s,", i, base58.Encode(address))
74+
}
75+
addressesString += "}"
76+
77+
return fmt.Sprintf(
78+
"AddressLookupTable{deactivation_slot=%d,last_extended_slot=%d,last_extended_slot_start_index=%d,authority=%s,addresses=%s}",
79+
obj.DeactivationSlot,
80+
obj.LastExtendedSlot,
81+
obj.LastExtendedSlotStartIndex,
82+
base58.Encode(obj.Authority),
83+
addressesString,
84+
)
85+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package address_lookup_table
2+
3+
import (
4+
"crypto/ed25519"
5+
"encoding/binary"
6+
7+
"github.com/code-payments/code-server/pkg/solana"
8+
)
9+
10+
func GetAddress(authority ed25519.PublicKey, recentSlot uint64) (ed25519.PublicKey, uint8, error) {
11+
var recentSlotBytes [8]byte
12+
binary.LittleEndian.PutUint64(recentSlotBytes[:], recentSlot)
13+
14+
return solana.FindProgramAddressAndBump(
15+
ProgramKey,
16+
authority,
17+
recentSlotBytes[:],
18+
)
19+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package address_lookup_table
2+
3+
import (
4+
"crypto/ed25519"
5+
6+
"github.com/code-payments/code-server/pkg/solana"
7+
"github.com/code-payments/code-server/pkg/solana/binary"
8+
"github.com/code-payments/code-server/pkg/solana/system"
9+
)
10+
11+
// Reference: https://github.com/solana-program/address-lookup-table/blob/main/program/src/instruction.rs
12+
13+
// AddressLookupTab1e1111111111111111111111111
14+
var ProgramKey = ed25519.PublicKey{2, 119, 166, 175, 151, 51, 155, 122, 200, 141, 24, 146, 201, 4, 70, 245, 0, 2, 48, 146, 102, 246, 46, 83, 193, 24, 36, 73, 130, 0, 0, 0}
15+
16+
const (
17+
commandCreateLookupTable uint32 = iota
18+
commandFreezeLookupTable
19+
commandExtendLookupTable
20+
commandDeactivateLookupTable
21+
commandCloseLookupTable
22+
)
23+
24+
func Create(alt, authority, payer ed25519.PublicKey, recentSlot uint64, bumpSeed uint8) solana.Instruction {
25+
data := make([]byte, 4+8+1)
26+
27+
var offset int
28+
binary.PutUint32(data[offset:], commandCreateLookupTable, &offset)
29+
binary.PutUint64(data[offset:], recentSlot, &offset)
30+
binary.PutUint8(data[offset:], bumpSeed, &offset)
31+
32+
return solana.NewInstruction(
33+
ProgramKey[:],
34+
data,
35+
solana.NewAccountMeta(alt, false),
36+
solana.NewReadonlyAccountMeta(authority, true),
37+
solana.NewAccountMeta(payer, true),
38+
solana.NewReadonlyAccountMeta(system.ProgramKey[:], false),
39+
)
40+
}
41+
42+
func Extend(alt, authority, payer ed25519.PublicKey, addresses ...ed25519.PublicKey) solana.Instruction {
43+
data := make([]byte, 4+8+len(addresses)*ed25519.PublicKeySize)
44+
45+
var offset int
46+
binary.PutUint32(data[offset:], commandExtendLookupTable, &offset)
47+
binary.PutUint64(data[offset:], uint64(len(addresses)), &offset)
48+
for _, address := range addresses {
49+
binary.PutKey32(data[offset:], address, &offset)
50+
}
51+
52+
return solana.NewInstruction(
53+
ProgramKey[:],
54+
data,
55+
solana.NewAccountMeta(alt, false),
56+
solana.NewReadonlyAccountMeta(authority, true),
57+
solana.NewAccountMeta(payer, true),
58+
solana.NewReadonlyAccountMeta(system.ProgramKey[:], false),
59+
)
60+
}

pkg/solana/binary/utils.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ func PutUint32(dst []byte, v uint32, offset *int) {
2929
*offset += 4
3030
}
3131

32+
func PutUint8(dst []byte, v uint8, offset *int) {
33+
dst[0] = v
34+
*offset += 1
35+
}
36+
3237
func PutOptionalUint64(dst []byte, v *uint64, offset *int) {
3338
if v != nil {
3439
dst[0] = 1
@@ -43,12 +48,12 @@ func GetKey32(src []byte, dst *ed25519.PublicKey, offset *int) {
4348
*offset += ed25519.PublicKeySize
4449
}
4550

46-
func GetOptionalKey32(src []byte, dst *ed25519.PublicKey, offset *int) {
51+
func GetOptionalKey32(src []byte, dst *ed25519.PublicKey, offset *int, optionSize int) {
4752
if src[0] == 1 {
4853
*dst = make([]byte, ed25519.PublicKeySize)
49-
copy(*dst, src[4:])
54+
copy(*dst, src[optionSize:])
5055
}
51-
*offset += 4 + ed25519.PublicKeySize
56+
*offset += optionSize + ed25519.PublicKeySize
5257
}
5358

5459
func GetUint64(src []byte, dst *uint64, offset *int) {
@@ -61,6 +66,11 @@ func GetUint32(src []byte, dst *uint32, offset *int) {
6166
*offset += 4
6267
}
6368

69+
func GetUint8(src []byte, dst *uint8, offset *int) {
70+
*dst = src[0]
71+
*offset += 1
72+
}
73+
6474
func GetOptionalUint64(src []byte, dst **uint64, offset *int) {
6575
if src[0] == 1 {
6676
val := binary.LittleEndian.Uint64(src[4:])

pkg/solana/token/state.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ const AccountSize = 165
2020
// Reference: https://github.com/solana-labs/solana-program-library/blob/8944f428fe693c3a4226bf766a79be9c75e8e520/token/program/src/state.rs#L214
2121
const MultisigAccountSize = 355
2222

23+
const optionSize = 4
24+
2325
type Account struct {
2426
// The mint associated with this account
2527
Mint ed25519.PublicKey
@@ -68,12 +70,12 @@ func (a *Account) Unmarshal(b []byte) bool {
6870
binary.GetKey32(b, &a.Mint, &offset)
6971
binary.GetKey32(b[offset:], &a.Owner, &offset)
7072
binary.GetUint64(b[offset:], &a.Amount, &offset)
71-
binary.GetOptionalKey32(b[offset:], &a.Delegate, &offset)
73+
binary.GetOptionalKey32(b[offset:], &a.Delegate, &offset, optionSize)
7274
a.State = AccountState(b[offset])
7375
offset++
7476
binary.GetOptionalUint64(b[offset:], &a.IsNative, &offset)
7577
binary.GetUint64(b[offset:], &a.DelegatedAmount, &offset)
76-
binary.GetOptionalKey32(b[offset:], &a.CloseAuthority, &offset)
78+
binary.GetOptionalKey32(b[offset:], &a.CloseAuthority, &offset, optionSize)
7779

7880
return true
7981
}

0 commit comments

Comments
 (0)