148 lines
3.9 KiB
Go
148 lines
3.9 KiB
Go
package sealevel
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/gagliardetto/solana-go"
|
|
)
|
|
|
|
// Params is the data passed to programs via the Sealevel VM input segment.
|
|
type Params struct {
|
|
Accounts []AccountParam
|
|
Data []byte // per-instruction data
|
|
ProgramID solana.PublicKey
|
|
}
|
|
|
|
// ReallocSpace is the allowed length by which an account is allowed to grow.
|
|
const ReallocSpace = 1024 * 10
|
|
|
|
// ReallocAlign is the byte amount by which the data following a realloc is aligned.
|
|
const ReallocAlign = 8
|
|
|
|
// AccountParam is an account input to a program execution.
|
|
type AccountParam struct {
|
|
IsDuplicate bool
|
|
DuplicateIndex uint8 // must not be 0xFF
|
|
IsSigner bool
|
|
IsWritable bool
|
|
IsExecutable bool
|
|
Key solana.PublicKey
|
|
Owner solana.PublicKey
|
|
Lamports uint64
|
|
Data []byte
|
|
Padding int // ignored, written by serializer
|
|
RentEpoch uint64
|
|
}
|
|
|
|
// Serialize writes the params to the provided buffer.
|
|
func (p *Params) Serialize(buf *bytes.Buffer) {
|
|
buf.Reset()
|
|
|
|
_ = binary.Write(buf, binary.LittleEndian, uint64(len(p.Accounts)))
|
|
for i := range p.Accounts {
|
|
acc := &p.Accounts[i]
|
|
|
|
if acc.IsDuplicate {
|
|
_, _ = buf.Write([]byte{acc.DuplicateIndex})
|
|
_ = writeZeros(buf, 7)
|
|
continue
|
|
}
|
|
_ = binary.Write(buf, binary.LittleEndian, uint8(0xFF))
|
|
_ = binary.Write(buf, binary.LittleEndian, acc.IsSigner)
|
|
_ = binary.Write(buf, binary.LittleEndian, acc.IsWritable)
|
|
_ = binary.Write(buf, binary.LittleEndian, acc.IsExecutable)
|
|
_ = writeZeros(buf, 4)
|
|
_, _ = buf.Write(acc.Key[:])
|
|
_, _ = buf.Write(acc.Owner[:])
|
|
_ = binary.Write(buf, binary.LittleEndian, acc.Lamports)
|
|
|
|
_ = binary.Write(buf, binary.LittleEndian, uint64(len(acc.Data)))
|
|
// This account copy cannot be avoided without a significant redesign of the VM
|
|
_, _ = buf.Write(acc.Data[:])
|
|
|
|
acc.Padding = ReallocSpace
|
|
if offset := buf.Len() % ReallocAlign; offset != 0 {
|
|
acc.Padding += ReallocAlign - offset
|
|
}
|
|
_ = writeZeros(buf, acc.Padding)
|
|
|
|
_ = binary.Write(buf, binary.LittleEndian, acc.RentEpoch)
|
|
}
|
|
|
|
_ = binary.Write(buf, binary.LittleEndian, uint64(len(p.Data)))
|
|
_, _ = buf.Write(p.Data)
|
|
|
|
_, err := buf.Write(p.ProgramID[:])
|
|
if err != nil {
|
|
panic("writes to buffer failed: " + err.Error()) // OOM
|
|
}
|
|
}
|
|
|
|
// Update writes data modified by a program back to the params struct.
|
|
func (p *Params) Update(buf *bytes.Reader) error {
|
|
// TODO authorization checks
|
|
|
|
for i := 0; true; i++ {
|
|
if i >= len(p.Accounts) {
|
|
return fmt.Errorf("number of accounts changed")
|
|
}
|
|
acc := &p.Accounts[i]
|
|
|
|
idx, err := buf.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (!acc.IsDuplicate && idx != 0xFF) || acc.DuplicateIndex != idx {
|
|
return fmt.Errorf("account order changed")
|
|
}
|
|
|
|
if idx != 0xFF {
|
|
continue
|
|
}
|
|
|
|
// TODO is deferring error check okay here?
|
|
_ = binary.Read(buf, binary.LittleEndian, &acc.IsSigner)
|
|
_ = binary.Read(buf, binary.LittleEndian, &acc.IsWritable)
|
|
_ = binary.Read(buf, binary.LittleEndian, &acc.IsExecutable)
|
|
_, _ = buf.Seek(4, io.SeekCurrent)
|
|
_, _ = buf.Read(acc.Key[:])
|
|
_, _ = buf.Read(acc.Owner[:])
|
|
_ = binary.Read(buf, binary.LittleEndian, &acc.Lamports)
|
|
|
|
oldLen := uint64(len(acc.Data))
|
|
var newLen uint64
|
|
_ = binary.Read(buf, binary.LittleEndian, &newLen)
|
|
if newLen < oldLen {
|
|
return fmt.Errorf("attempted to shrink account")
|
|
}
|
|
if newLen > oldLen+ReallocSpace {
|
|
return fmt.Errorf("attempted to grow account too much")
|
|
}
|
|
acc.Data, _ = io.ReadAll(io.LimitReader(buf, int64(newLen)))
|
|
_, _ = buf.Seek(int64(acc.Padding-int(newLen-oldLen)), io.SeekCurrent)
|
|
|
|
_ = binary.Read(buf, binary.LittleEndian, &acc.RentEpoch)
|
|
}
|
|
|
|
_, _ = buf.Seek(int64(len(p.Data)), io.SeekCurrent)
|
|
_, err := buf.Read(p.ProgramID[:])
|
|
return err
|
|
}
|
|
|
|
func writeZeros(b *bytes.Buffer, n int) error {
|
|
_, err := io.Copy(b, io.LimitReader(zeroRd{}, int64(n)))
|
|
return err
|
|
}
|
|
|
|
type zeroRd struct{}
|
|
|
|
func (zeroRd) Read(buf []byte) (int, error) {
|
|
for i := range buf {
|
|
buf[i] = 0
|
|
}
|
|
return len(buf), nil
|
|
}
|