diff --git a/compressor/cbuffer.go b/compressor/cbuffer.go new file mode 100644 index 0000000..41ed874 --- /dev/null +++ b/compressor/cbuffer.go @@ -0,0 +1,121 @@ +package compressor + +type CBuffer struct { + SignatureLevel uint + + Commited []byte + Pending []byte + + Refs *References +} + +type References struct { + useContractStorage bool + + usedFlags map[string]int + usedStorageFlags map[string]int +} + +func NewCBuffer(useStorage bool) *CBuffer { + return &CBuffer{ + // Start with an empty byte, this + // will be used as the method when calling the compressor + // contract. + Commited: make([]byte, 1), + Pending: make([]byte, 0), + + Refs: &References{ + useContractStorage: useStorage, + usedFlags: make(map[string]int), + usedStorageFlags: make(map[string]int), + }, + } +} + +func (r *References) Copy() *References { + usedFlags := make(map[string]int, len(r.usedFlags)) + for k, v := range r.usedFlags { + usedFlags[k] = v + } + + usedStorageFlags := make(map[string]int, len(r.usedStorageFlags)) + for k, v := range r.usedStorageFlags { + usedStorageFlags[k] = v + } + + return &References{ + useContractStorage: r.useContractStorage, + + usedFlags: usedFlags, + usedStorageFlags: usedStorageFlags, + } +} + +func (cb *CBuffer) Data() []byte { + return cb.Commited +} + +func (cb *CBuffer) Len() int { + return len(cb.Commited) +} + +func (cb *CBuffer) WriteByte(b byte) { + cb.Pending = append(cb.Pending, b) +} + +func (cb *CBuffer) WriteBytes(b []byte) { + cb.Pending = append(cb.Pending, b...) +} + +func (cb *CBuffer) WriteInt(i uint) { + cb.WriteByte(byte(i)) +} + +func (cb *CBuffer) End(uncompressed []byte, t EncodeType) { + // We need 2 bytes to point to a flag, so any uncompressed value + // that is 2 bytes or less is not worth saving. + if len(uncompressed) > 2 { + rindex := cb.Len() + + switch t { + case ReadStorage: + case Stateless: + cb.Refs.usedFlags[string(uncompressed)] = rindex + 1 + case WriteStorage: + cb.Refs.usedStorageFlags[string(uncompressed)] = rindex + 1 + default: + } + } + + cb.Commited = append(cb.Commited, cb.Pending...) + cb.Pending = nil +} + +type Snapshot struct { + Commited []byte + + SignatureLevel uint + + Refs *References +} + +func (cb *CBuffer) Snapshot() *Snapshot { + // Create a copy of the commited buffer + // and of the references. + com := make([]byte, len(cb.Commited)) + copy(com, cb.Commited) + + refs := cb.Refs.Copy() + + return &Snapshot{ + Commited: com, + SignatureLevel: cb.SignatureLevel, + Refs: refs, + } +} + +func (cb *CBuffer) Restore(snap *Snapshot) { + cb.Commited = snap.Commited + cb.Refs = snap.Refs + cb.SignatureLevel = snap.SignatureLevel +} diff --git a/compressor/compressor.go b/compressor/compressor.go new file mode 100644 index 0000000..18287b2 --- /dev/null +++ b/compressor/compressor.go @@ -0,0 +1,242 @@ +package compressor + +import ( + "bytes" + "context" + "fmt" + "sync" + "time" + + "github.com/0xsequence/ethkit/ethrpc" + "github.com/0xsequence/ethkit/go-ethereum/common" + "github.com/0xsequence/go-sequence" +) + +type Compressor struct { + ctx context.Context + + Encoder *Encoder + CostModel *CostModel + Contract common.Address + + UseStorage bool + + UpdateInterval uint + TrailBlocks uint + BatchSize uint + + AddressesHeight uint + Bytes32Height uint + + LastIndexUpdate uint + Provider *ethrpc.Provider + + onLoadedIndexes func(uint, uint) + + mu *sync.RWMutex +} + +func NewCompressor( + ctx context.Context, + provider *ethrpc.Provider, + contract common.Address, + costModel *CostModel, + userStorage bool, + updateInterval uint, + trailBlocks uint, + batchSize uint, +) (*Compressor, error) { + if userStorage { + if updateInterval == 0 { + return nil, fmt.Errorf("update interval must be greater than 0") + } + + if batchSize == 0 { + return nil, fmt.Errorf("batch size must be greater than 0") + } + } + + // Check if the contract exists + code, err := provider.CodeAt(ctx, contract, nil) + if err != nil { + return nil, err + } + + if len(code) == 0 { + return nil, fmt.Errorf("contract %s does not exist", contract.Hex()) + } + + c := &Compressor{ + ctx: ctx, + Encoder: NewEncoder(), + CostModel: costModel, + Contract: contract, + LastIndexUpdate: 0, + Provider: provider, + UseStorage: userStorage, + UpdateInterval: updateInterval, + TrailBlocks: trailBlocks, + BatchSize: batchSize, + mu: &sync.RWMutex{}, + } + + c.StartIndexUpdater() + + return c, nil +} + +func (cm *Compressor) SetOnLoadedIndexes(f func(uint, uint)) { + cm.onLoadedIndexes = f +} + +func LoadIndexes(ci *Compressor) error { + ci.mu.RLock() + lenAddrs := ci.AddressesHeight + lenBytes32s := ci.Bytes32Height + ci.mu.RUnlock() + + // Don't lock while we read the state, it can take a while + + nah, addrs, nbh, bytes32s, err := LoadState( + ci.ctx, + ci.Provider, + ci.Contract, + ci.BatchSize, + lenAddrs, + lenBytes32s, + ci.TrailBlocks, + ) + + if err != nil { + return err + } + + // Lock it only for the time it takes to update the indexes + ci.mu.Lock() + defer ci.mu.Unlock() + + for k, v := range addrs { + ci.Encoder.AddressIndexes[k] = v + } + + for k, v := range bytes32s { + ci.Encoder.Bytes32Indexes[k] = v + } + + ci.AddressesHeight = nah + ci.Bytes32Height = nbh + + if ci.onLoadedIndexes != nil { + ci.onLoadedIndexes(nah-lenAddrs, nbh-lenBytes32s) + } + + return nil +} + +func (cm *Compressor) StartIndexUpdater() { + // No need to start the updater if we don't use storage + if !cm.UseStorage { + return + } + + go func() { + ticker := time.NewTicker(time.Duration(cm.UpdateInterval) * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + err := LoadIndexes(cm) + if err != nil { + fmt.Printf("Error updating indexes for chain %d: %v\n", cm.Contract, err) + } + case <-cm.ctx.Done(): + // Context cancelled, stop the ticker + return + } + } + }() +} + +func (cm *Compressor) performCompress(ci *Compressor, wallet []byte, transaction *sequence.Transaction) ([]byte, EncodeType, error) { + ci.mu.RLock() + defer ci.mu.RUnlock() + + cbuffer := NewCBuffer(ci.UseStorage) + et, err := ci.Encoder.WriteExecute(cbuffer, wallet, transaction) + if err != nil { + return nil, et, err + } + + return cbuffer.Data(), et, nil +} + +func (cm *Compressor) IsSaneCompression( + input []byte, + entrypoint common.Address, + transaction *sequence.Transaction, + decompressedEntrypoint common.Address, + decompressed []byte, +) error { + // The decompressed entrypoint should match the input entrypoint + if !bytes.Equal(entrypoint.Bytes(), decompressedEntrypoint.Bytes()) { + return fmt.Errorf("decompressed entrypoin t does not match input") + } + + ed1, err := transaction.Execdata() + if err != nil { + return err + } + + // We need to normalize the signature before comparing the exec data + err = NormalizeTransactionSignature(transaction) + if err != nil { + return err + } + + // Now we can re-compute the exec data and compare it with the decompressed data + ed2, err := transaction.Execdata() + if err != nil { + return err + } + + // One of the two ways of representing a Sequence signature in v2 is the dynamic format + // should match the input data + if !bytes.Equal(input, ed1) && !bytes.Equal(input, ed2) { + return fmt.Errorf("exec data does not match input") + } + + if !bytes.Equal(decompressed, ed2) { + return fmt.Errorf("exec data does not match input") + } + + return nil +} + +// We return two errors, the first one is the inner compression error, we returns it for traceability +// but the real error is the second one, which should only appear on a non-recoverable situation +func (cm *Compressor) TryCompress( + input []byte, + entrypoint common.Address, + transaction *sequence.Transaction, +) (common.Address, []byte, EncodeType, error) { + ci := cm + compressed, et, err := cm.performCompress(ci, entrypoint.Bytes(), transaction) + if err != nil { + return common.Address{}, nil, 0, err + } + + // We do a dry run of decompression to make sure it's sane + decompressedEntrypoint, decompressed, err := DecompressTransaction(ci.ctx, ci.Provider, ci.Contract, compressed) + if err != nil { + return common.Address{}, nil, 0, err + } + + // We need to validate that the compresseded data decompresses to what we expect + err = cm.IsSaneCompression(input, entrypoint, transaction, decompressedEntrypoint, decompressed) + if err != nil { + return common.Address{}, nil, 0, err + } + + return ci.Contract, compressed, et, nil +} diff --git a/compressor/constants.go b/compressor/constants.go new file mode 100644 index 0000000..8aca409 --- /dev/null +++ b/compressor/constants.go @@ -0,0 +1,113 @@ +package compressor + +import "github.com/0xsequence/ethkit/go-ethereum/common" + +const ( + METHOD_EXECUTE_1 uint = iota + METHOD_EXECUTE_N + METHOD_READ_ADDRESS + METHOD_READ_BYTES32 + METHOD_READ_SIZES + METHOD_READ_STORAGE_SLOTS + METHOD_DECOMPRESS_1 + METHOD_DECOMPRESS_N +) + +const ( + FLAG_READ_POWER_OF_10_MISC uint = iota + FLAG_READ_BYTES32_1_BYTES + FLAG_READ_BYTES32_2_BYTES + FLAG_READ_BYTES32_3_BYTES + FLAG_READ_BYTES32_4_BYTES + FLAG_READ_BYTES32_5_BYTES + FLAG_READ_BYTES32_6_BYTES + FLAG_READ_BYTES32_7_BYTES + FLAG_READ_BYTES32_8_BYTES + FLAG_READ_BYTES32_9_BYTES + FLAG_READ_BYTES32_10_BYTES + FLAG_READ_BYTES32_11_BYTES + FLAG_READ_BYTES32_12_BYTES + FLAG_READ_BYTES32_13_BYTES + FLAG_READ_BYTES32_14_BYTES + FLAG_READ_BYTES32_15_BYTES + FLAG_READ_BYTES32_16_BYTES + FLAG_READ_BYTES32_17_BYTES + FLAG_READ_BYTES32_18_BYTES + FLAG_READ_BYTES32_19_BYTES + FLAG_READ_BYTES32_20_BYTES + FLAG_READ_BYTES32_21_BYTES + FLAG_READ_BYTES32_22_BYTES + FLAG_READ_BYTES32_23_BYTES + FLAG_READ_BYTES32_24_BYTES + FLAG_READ_BYTES32_25_BYTES + FLAG_READ_BYTES32_26_BYTES + FLAG_READ_BYTES32_27_BYTES + FLAG_READ_BYTES32_28_BYTES + FLAG_READ_BYTES32_29_BYTES + FLAG_READ_BYTES32_30_BYTES + FLAG_READ_BYTES32_31_BYTES + FLAG_READ_BYTES32_32_BYTES + FLAG_SAVE_ADDRESS + FLAG_SAVE_BYTES32 + FLAG_READ_ADDRESS_2 + FLAG_READ_ADDRESS_3 + FLAG_READ_ADDRESS_4 + FLAG_READ_EXECUTE + FLAG_READ_BYTES32_2 + FLAG_READ_BYTES32_3 + FLAG_READ_BYTES32_4 + FLAG_READ_POW_10_MANTISSA + FLAG_READ_N_BYTES + FLAG_READ_POWER_OF_2 + FLAG_ABI_0_PARAM + FLAG_ABI_1_PARAM + FLAG_ABI_2_PARAMS + FLAG_ABI_3_PARAMS + FLAG_ABI_4_PARAMS + FLAG_ABI_5_PARAMS + FLAG_ABI_6_PARAMS + FLAG_NESTED_N_FLAGS_8 + FLAG_NESTED_N_FLAGS_16 + FLAG_SIGNATURE_W0 + FLAG_SIGNATURE_W1 + FLAG_SIGNATURE_W2 + FLAG_SIGNATURE_W3 + FLAG_SIGNATURE_W4 + FLAG_ADDRESS_W0 + FLAG_ADDRESS_W1 + FLAG_ADDRESS_W2 + FLAG_ADDRESS_W3 + FLAG_ADDRESS_W4 + FLAG_NODE + FLAG_BRANCH + FLAG_SUBDIGEST + FLAG_NESTED + FLAG_DYNAMIC_SIGNATURE + FLAG_S_SIG_NO_CHAIN + FLAG_S_SIG + FLAG_S_L_SIG_NO_CHAIN + FLAG_S_L_SIG + FLAG_READ_CHAINED + FLAG_READ_CHAINED_L + FLAG_READ_DYNAMIC_ABI + FLAG_NO_OP + FLAG_MIRROR_FLAG + FLAG_COPY_CALLDATA + FLAG_READ_STORE_FLAG +) + +const LITERAL_ZERO = FLAG_READ_STORE_FLAG + 1 +const MAX_LITERAL = 0xff - LITERAL_ZERO + +const BYTES4_TABLE = "00000000a9059cbb095ea7b37ff36ab538ed173918cbafe5202ee0edfb3bdb41e2bbb158ab834bab6ea056a923b872dda68a76cc5f5755298803dbeea22cb465c89e43612da0340990411a321cff79cd223da1ba2e1a7d4df305d71939125215d0e30db0f7654176a694fc3a1a695230b6b55f25791ac94764887334c658695c441a3e704f1d48324a25d94aa454dfa9c18a84bce2b39746178979aec9807539ddd81f82a8a41c70cf557fe33d18b9129cec63924ab0d1900dcd7a6cd9627aa49149bafe672a9400dfbe4a31c6bf3262f242432ae5ab4da240c10f192e7ba6efc23e1a211aa3a008d6b347f7ded9382a00000003e9fad8eefaebafa8ae169a50e8e3370041fe00a0fa558b712e95b6c8c48fdfca000000006a80c33f627dd56a5c11d7954946e2065e83b463ca722cdcfb90b32000000008f7c1e582a32fe0a1db006a75000000010002191ce6d66ac8a0712d685d5d442296aa7368d3392ddf0ea5812fa5d754d1d29dff129979ef457901451ca64f797659d667a500032587865a6b4f379607f57c02520082d2697fc11695488758a5f34e71d92d9ddd67ba183d4e0b8f69c188e3dec8fbedc9af952d2da8066a761202a9b1d507ca120b1ff14fcbc8961c9ae442842e0e2195995c94b918de608060405174e8532505c3d98568523a0e89439bd149d05cefef39a14ce6931a000225879bfcb236415565b0454a2ab3ce558087f7a1696342966c688b4cb0ec4faa8a26e4a76726e8eda9df1519cdeb356282bfe17376b5009952eb3d7989fe34b0793b38bcdfc0f053566e02751cecc01a8c84f463e18e3cd18ca029ada03907d6b3483805550fa59f3e0c89bbb8b2c5ebeaec4997adb6f5e54063761610fcb88a802f3ccfd60b2e2d726ca4202615b44848f51610ca95bcf64e0579b177ec22895118ed436a474d474898c0f4ed31b967cb0ca6e158f8db7fd4089120491ca415bcad8201aa3f6e5110ae5312ea8e3df02124b77d239ba67a6a45156e29f6241735bbd017e8c73f7658fd86b2ecc4c44193c39bc12042d96a094a13d98d135d4c66a3ad4451a32e17de789ec9b36be47d166cbfff3b87f884e54a0b020003ad58bdd147e7ef24bad42590c8fd6ed002032587c6427474f6162b01baa2abde1ff013f11846eac55915d806f6aa658b00024a9c564a515869328dec4454b20df5298aca853828b6f06427e5b6b4af05f3fef3a352a438b81249c58bfeab2e5af9d83bb568c2c5fb02022587d586d8e0db254e5005eec2890e7527028f4af52f6a627842508c1dbd0f694584a6417ed63049105d1e9a6950d9caed120103258748d5c7e3be389d577430e0c649b780f00af49149d508e6238e1e280cae47bea8683fa88d5db3b4df1e83409a852a12e3c2998238343009a2daa6d5560f0439589c1298a06aa1e6d24d559317" + +func LoadBytes4() map[string]uint { + btable := common.Hex2Bytes(BYTES4_TABLE) + table := make(map[string]uint) + + for i := uint(0); i < uint(len(btable)); i += 4 { + table[string(btable[i:i+4])] = i / 4 + } + + return table +} diff --git a/compressor/contract.go b/compressor/contract.go new file mode 100644 index 0000000..b8498e9 --- /dev/null +++ b/compressor/contract.go @@ -0,0 +1,81 @@ +package compressor + +import ( + "context" + "encoding/binary" + "fmt" + "math/big" + + "github.com/0xsequence/ethkit/ethrpc" + "github.com/0xsequence/ethkit/go-ethereum" + "github.com/0xsequence/ethkit/go-ethereum/common" +) + +func AddressIndex(i uint) []byte { + padded32 := make([]byte, 32) + binary.BigEndian.PutUint64(padded32[24:32], uint64(i+1)) + return padded32 +} + +func Bytes32Index(i uint) []byte { + padded32 := make([]byte, 32) + binary.BigEndian.PutUint64(padded32[8:16], uint64(i)) + return padded32 +} + +func GetTotals(ctx context.Context, provider *ethrpc.Provider, contract common.Address, skipBlocks uint) (uint, uint, error) { + // Get the last block + block, err := provider.BlockNumber(ctx) + if err != nil { + return 0, 0, err + } + + block -= uint64(skipBlocks) + + res, err := provider.CallContract(ctx, ethereum.CallMsg{ + To: &contract, + Data: []byte{byte(METHOD_READ_SIZES)}, + }, big.NewInt(int64(block))) + + if err != nil { + return 0, 0, err + } + + // First 16 bytes are the total number of addresses + // Next 16 bytes are the total number of bytes32 + + // Read only an uint64, since there will be no more than 2^64 addresses + asize := uint(binary.BigEndian.Uint64(res[8:16])) + 1 + bsize := uint(binary.BigEndian.Uint64(res[24:32])) + 1 + + return asize, bsize, nil +} + +func DecompressTransaction(ctx context.Context, provider *ethrpc.Provider, contract common.Address, compressed []byte) (common.Address, []byte, error) { + // Replace the first byte with `METHOD_DECOMPRESS_1` + if len(compressed) == 0 { + return common.Address{}, nil, fmt.Errorf("empty compressed data") + } + + c2 := make([]byte, len(compressed)) + copy(c2, compressed) + c2[0] = byte(METHOD_DECOMPRESS_1) + res, err := provider.CallContract(ctx, ethereum.CallMsg{ + To: &contract, + Data: c2, + }, nil) + + if err != nil { + return common.Address{}, nil, err + } + + if len(res) < 32 { + return common.Address{}, nil, fmt.Errorf("decompressed data too short") + } + + // The last 32 bytes are the address + addr := common.BytesToAddress(res[len(res)-32:]) + + // The rest is the transaction, encoded as a function call to execute + return addr, res[:len(res)-32], nil +} diff --git a/compressor/costmodel.go b/compressor/costmodel.go new file mode 100644 index 0000000..c71571b --- /dev/null +++ b/compressor/costmodel.go @@ -0,0 +1,46 @@ +package compressor + +import "strings" + +type CostModel struct { + ZeroByteCost int + OneByteCost int + + FixedCost int + OverHeadPersentage int // 0 - 1000 +} + +func NewCostModel(zeroByteCost, oneByteCost, fixedCost, overHeadPersentage int) *CostModel { + return &CostModel{ + ZeroByteCost: zeroByteCost, + OneByteCost: oneByteCost, + FixedCost: fixedCost, + OverHeadPersentage: overHeadPersentage, + } +} + +func GetCostModel(network string) *CostModel { + // Convert to lowerscase, trim spaces + n := strings.ToLower(strings.TrimSpace(network)) + + switch n { + case "mainnet": + return NewCostModel(4, 16, 2300, 5) + default: + return GetCostModel("mainnet") + } +} + +func (cm *CostModel) Cost(data []byte) int { + var calldataCost int + + for _, b := range data { + if b == 0 { + calldataCost += cm.ZeroByteCost + } else { + calldataCost += cm.OneByteCost + } + } + + return calldataCost +} diff --git a/compressor/decoder.go b/compressor/decoder.go new file mode 100644 index 0000000..73a7b3d --- /dev/null +++ b/compressor/decoder.go @@ -0,0 +1,1054 @@ +package compressor + +import ( + "encoding/binary" + "fmt" + "math/big" +) + +type Decoder struct { + calldata []byte + rindex uint + buffer []byte + + indexToAddress map[uint][]byte + indexToBytes32 map[uint][]byte + indexToBytes4 map[uint][]byte +} + +func (d *Decoder) Buffer() []byte { + return d.buffer +} + +func NewDecoder(calldata []byte) *Decoder { + return &Decoder{ + calldata: calldata, + rindex: 1, + buffer: make([]byte, 0), + + indexToAddress: make(map[uint][]byte), + indexToBytes32: make(map[uint][]byte), + indexToBytes4: make(map[uint][]byte), + } +} + +func (d *Decoder) LogFlag(flag string) { + // fmt.Println("flag:", flag) +} + +func (d *Decoder) ReadFlag() error { + flag := uint(d.calldata[d.rindex]) + d.rindex++ + + if flag == FLAG_READ_POWER_OF_10_MISC { + return d.ReadPow10Misc() + } + + if flag >= FLAG_READ_BYTES32_1_BYTES && flag <= FLAG_READ_BYTES32_32_BYTES { + return d.ReadBytes32(flag) + } + + if flag >= FLAG_READ_ADDRESS_2 && flag <= FLAG_READ_ADDRESS_4 { + return d.ReadAddressStorage(flag) + } + + if flag >= FLAG_READ_BYTES32_2 && flag <= FLAG_READ_BYTES32_4 { + return d.ReadBytes32Storage(flag) + } + + if flag == FLAG_SAVE_ADDRESS { + return d.ReadSaveAddress() + } + + if flag == FLAG_SAVE_BYTES32 { + return d.ReadSaveBytes32() + } + + if flag == FLAG_READ_N_BYTES { + return d.ReadNBytes(flag) + } + + if flag == FLAG_READ_POWER_OF_2 { + return d.ReadPow2() + } + + if flag >= FLAG_ABI_0_PARAM && flag <= FLAG_ABI_6_PARAMS { + return d.ReadAbiStatic(flag) + } + + if flag >= FLAG_NESTED_N_FLAGS_8 && flag <= FLAG_NESTED_N_FLAGS_16 { + return d.ReadNestedFlags(flag) + } + + if flag >= FLAG_SIGNATURE_W0 && flag <= FLAG_SIGNATURE_W4 { + return d.ReadSignature(flag) + } + + if flag >= FLAG_ADDRESS_W0 && flag <= FLAG_ADDRESS_W4 { + return d.ReadAddress(flag) + } + + if flag == FLAG_NODE { + return d.FlagNode() + } + + if flag == FLAG_BRANCH { + return d.ReadBranch() + } + + if flag == FLAG_NESTED { + return d.ReadNested() + } + + if flag == FLAG_DYNAMIC_SIGNATURE { + return d.ReadDynamicSignature() + } + + if flag >= FLAG_S_SIG_NO_CHAIN && flag <= FLAG_S_L_SIG { + return d.ReadSequenceSignatureV2(flag) + } + + if flag == FLAG_READ_CHAINED || flag == FLAG_READ_CHAINED_L { + return fmt.Errorf("read chained not implemented") + } + + if flag == FLAG_READ_DYNAMIC_ABI { + return d.ReadAbiDynamic() + } + + if flag == FLAG_NO_OP { + return nil + } + + if flag == FLAG_MIRROR_FLAG || flag == FLAG_READ_STORE_FLAG { + return d.ReadMirrorFlag() + } + + if flag == FLAG_COPY_CALLDATA { + return d.ReadCopyCalldata() + } + + return d.ReadLiteral(flag) +} + +func (d *Decoder) ReadAndLoad32Bytes() ([]byte, error) { + d.LogFlag("read_and_load_flag") + + d.ReadFlag() + + // Load the last 32 bytes and return them to the caller + // they must be removed from the buffer + if len(d.buffer) < 32 { + return nil, fmt.Errorf("not enough bytes in buffer") + } + + // Read the last 32 bytes + word := d.buffer[len(d.buffer)-32:] + d.buffer = d.buffer[:len(d.buffer)-32] + return word, nil +} + +func (d *Decoder) ReadBytes32(flag uint) error { + d.LogFlag("bytes32") + + // FLAG_READ_BYTES32_1_BYTES reads 1 byte + // FLAG_READ_BYTES32_1_BYTES + 1 reads 2 bytes + // ... etc + readb := flag - FLAG_READ_BYTES32_1_BYTES + 1 + word := d.calldata[d.rindex : d.rindex+readb] + + // Word is always read padded with 0s to 32 bytes + padded, err := padToX(word, 32) + if err != nil { + return err + } + + d.buffer = append(d.buffer, padded...) + d.rindex += readb + + return nil +} + +func (d *Decoder) ReadSaveAddress() error { + d.LogFlag("save_address") + + // Read 20 bytes, pad it to 32 bytes + addr := d.calldata[d.rindex : d.rindex+20] + padded, err := padToX(addr, 32) + if err != nil { + return err + } + + d.buffer = append(d.buffer, padded...) + d.rindex += 20 + + return nil +} + +func (d *Decoder) ReadSaveBytes32() error { + d.LogFlag("save_bytes32") + + // Read 32 bytes + word := d.calldata[d.rindex : d.rindex+32] + d.buffer = append(d.buffer, word...) + d.rindex += 32 + + return nil +} + +func (d *Decoder) ReadAddressStorage(flag uint) error { + d.LogFlag("address_storage") + + // Number of bytes used for the index: + // FLAG_READ_ADDRESS_2 -> 2 bytes + // FLAG_READ_ADDRESS_2 + 1 -> 3 bytes + // ... etc + readb := flag - FLAG_READ_ADDRESS_2 + 2 + if readb < 2 { + return fmt.Errorf("reading less than 2 bytes on address %d", flag) + } + + if readb > 4 { + return fmt.Errorf("reading more than 5 bytes on address %d", flag) + } + + ibs := d.calldata[d.rindex : d.rindex+readb] + d.rindex += readb + + index := bytesToUint64(ibs) + + // Read the address from storage + addr := d.indexToAddress[index] + if addr == nil { + return fmt.Errorf("address %d not found", index) + } + + d.buffer = append(d.buffer, addr...) + return nil +} + +func (d *Decoder) ReadBytes32Storage(flag uint) error { + d.LogFlag("bytes32_storage") + + // Number of bytes used for the index: + // FLAG_READ_BYTES32_2 -> 2 bytes + // FLAG_READ_BYTES32_2 + 1 -> 3 bytes + // ... etc + readb := flag - FLAG_READ_BYTES32_2 + 2 + + if readb < 2 { + return fmt.Errorf("reading less than 2 bytes on bytes32 %d", flag) + } + + if readb > 4 { + return fmt.Errorf("reading more than 5 bytes on bytes32 %d", flag) + } + + ibs := d.calldata[d.rindex : d.rindex+readb] + d.rindex += readb + + index := bytesToUint64(ibs) + + // Read the bytes32 from storage + bytes32 := d.indexToBytes32[index] + if bytes32 == nil { + return fmt.Errorf("bytes32 %d not found", index) + } + + d.buffer = append(d.buffer, bytes32...) + return nil +} + +func (d *Decoder) ReadNBytes(flag uint) error { + d.LogFlag("n_bytes") + + // Read a nested flag, this gives us the number of bytes to read + nbytes, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + n := uint(binary.BigEndian.Uint64(nbytes[len(nbytes)-8:])) + d.LogFlag("n_bytes " + fmt.Sprintf("%d", n)) + + // Read the number of bytes specified by the nested flag + d.buffer = append(d.buffer, d.calldata[d.rindex:d.rindex+n]...) + d.rindex += n + + return nil +} + +func (d *Decoder) ReadPow2() error { + d.LogFlag("pow2") + + exp := int(d.calldata[d.rindex]) + d.rindex++ + + var num *big.Int + if exp == 0 { + // We need to read another exp, and this time do 2 ** exp - 1 + exp = int(d.calldata[d.rindex]) + d.rindex++ + + base := big.NewInt(2) + pow := big.NewInt(int64(exp)) + num = new(big.Int).Exp(base, pow, nil) + } else { + base := big.NewInt(2) + pow := big.NewInt(int64(exp)) + num = new(big.Int).Exp(base, pow, nil) + } + + // Write the number padded to 32 bytes + padded, err := padToX(num.Bytes(), 32) + if err != nil { + return err + } + + d.buffer = append(d.buffer, padded...) + + return nil +} + +func (d *Decoder) ReadAbi4Bytes() error { + d.LogFlag("abi_4_bytes") + + // The first value is always the bytes4, it may be an index + // or the bytes4 itself (if it is prefixed with 00) + ib4 := d.calldata[d.rindex] + d.rindex++ + + var selector []byte + + if ib4 == 0 { + // Read the next 4 bytes + selector = d.calldata[d.rindex : d.rindex+4] + d.rindex += 4 + } else { + selector = d.indexToBytes4[uint(ib4)] + } + + d.buffer = append(d.buffer, selector...) + return nil +} + +func (d *Decoder) ReadAbiStatic(flag uint) error { + d.LogFlag("abi_static") + + err := d.ReadAbi4Bytes() + if err != nil { + return err + } + + // The number of args is determined by the flag + // FLAG_ABI_0_PARAM -> 0 args + // FLAG_ABI_0_PARAM + 1 -> 1 arg + // ... etc + + nargs := flag - FLAG_ABI_0_PARAM + if nargs > 6 { + return fmt.Errorf("reading more than 6 args on abi static %d", flag) + } + + return d.ReadNFlags(nargs) +} + +func (d *Decoder) ReadAbiDynamic() error { + d.LogFlag("abi_dynamic") + + err := d.ReadAbi4Bytes() + if err != nil { + return err + } + + // There are two flags, the second one marks which args are dynamic + // the first one marks how many args there are, both are 1 byte + // (it is only possible to make the first 8 args as dynamic) + fs := uint(d.calldata[d.rindex]) + d.rindex++ + + fd := d.calldata[d.rindex] + d.rindex++ + + windex2 := uint(len(d.buffer)) + + // Reserve 32 bytes for each arg + d.buffer = append(d.buffer, make([]byte, fs*32)...) + + for i := uint(0); i < fs; i++ { + argSpot := windex2 + (i * 32) + + if (fd & (1 << i)) != 0 { + // This arg is dynamic. we need to write a relative pointer + // to the end of the buffer (from the start of the args) + rpointer := uint(len(d.buffer)) - windex2 + padded, err := uintPadToX(rpointer, 32) + if err != nil { + return err + } + + copy(d.buffer[argSpot:], padded) + + // Reserve 32 bytes for the size + sizeSpot := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, 32)...) + + // Now we track the size and we write another flag + // to read the actual bytes + windex3 := uint(len(d.buffer)) + err = d.ReadFlag() + if err != nil { + return err + } + + size := uint(len(d.buffer)) - windex3 + + // Whatever we wrote, it has to be a multiple of 32 + // so we pad the buffer to 32 bytes + pdamount := uint(32 - (size % 32)) + if pdamount != 32 { + d.buffer = append(d.buffer, make([]byte, pdamount)...) + } + + padded = make([]byte, 32) + binary.BigEndian.PutUint64(padded, uint64(size)) + copy(d.buffer[sizeSpot:], padded) + } else { + // This arg is static, we just read a nested ON argSpot + val, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + copy(d.buffer[argSpot:], val) + } + } + + return nil +} + +func (d *Decoder) ReadNestedFlags(flag uint) error { + d.LogFlag("nested_flags") + + // FLAG_NESTED_N_FLAGS_8 -> 1 byte for n of flags + // FLAG_NESTED_N_FLAGS_8 + 1 -> 2 bytes for n of flags + nb := flag - FLAG_NESTED_N_FLAGS_8 + 1 + + if nb < 1 || nb > 2 { + return fmt.Errorf("reading more than 2 bytes on nested flags %d", flag) + } + + var nflags uint + + // Read the number of flags + if nb == 1 { + nflags = uint(d.calldata[d.rindex]) + } else { + nflags = uint(binary.BigEndian.Uint16(d.calldata[d.rindex : d.rindex+nb])) + } + d.rindex += nb + + return d.ReadNFlags(nflags) +} + +func (d *Decoder) ReadNFlags(n uint) error { + d.LogFlag("n_flags") + + for i := uint(0); i < n; i++ { + err := d.ReadFlag() + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) ReadSignature(flag uint) error { + d.LogFlag("signature_part") + + // FLAG_SIGNATURE_W0 -> for 1 byte for the weight (has to read more) + // FLAG_SIGNATURE_W1 -> for weight == 1 + // FLAG_SIGNATURE_W2 -> for weight == 2 + // ... etc (max 4) + + weight := flag - FLAG_SIGNATURE_W0 + + if weight > 4 { + return fmt.Errorf("signature static weight too high %d", flag) + } + + if weight == 0 { + // Read another byte for the weight + weight = uint(d.calldata[d.rindex]) + d.rindex++ + } + + // Write the "signature" sequence flag (0x00) + d.buffer = append(d.buffer, 0x00) + + // Write the weight + d.buffer = append(d.buffer, byte(weight)) + + // Now just copy 66 bytes + d.buffer = append(d.buffer, d.calldata[d.rindex:d.rindex+66]...) + d.rindex += 66 + + return nil +} + +func (d *Decoder) ReadAddress(flag uint) error { + d.LogFlag("address_part") + + // FLAG_ADDRESS_W0 -> for 1 byte for the weight (has to read more) + // FLAG_ADDRESS_W1 + 1 -> for weight == 1 + // ... etc (max 4) + weight := flag - FLAG_ADDRESS_W0 + + if weight > 4 { + return fmt.Errorf("address static weight too high %d", flag) + } + + if weight == 0 { + // Read another byte for the weight + weight = uint(d.calldata[d.rindex]) + d.rindex++ + } + + // Write the "address" sequence flag (0x01) + d.buffer = append(d.buffer, 0x01) + + // Write the weight + d.buffer = append(d.buffer, byte(weight)) + + // Now read a nested flag, the difference is that we will shift the value + // to the left by 12 bytes + val, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + // Pad the value to 20 bytes + padded, err := padToX(val, 20) + d.buffer = append(d.buffer, padded...) + + return nil +} + +func (d *Decoder) ReadDynamicSignature() error { + d.LogFlag("dynamic_signature_part") + + // Write the "dynamic signature" sequence flag (0x02) + d.buffer = append(d.buffer, 0x02) + + // Read 1 byte as the weight + weight := uint(d.calldata[d.rindex]) + d.rindex++ + d.buffer = append(d.buffer, byte(weight)) + + // Read the address as a nested flag + val, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + // Write only the last 20 bytes + d.buffer = append(d.buffer, val[12:]...) + + // Reserve 3 bytes for the size, we are going to read + // the dynamic signature as a nested flag + d.buffer = append(d.buffer, make([]byte, 3)...) + windex := uint(len(d.buffer)) + + err = d.ReadFlag() + if err != nil { + return err + } + + // Write an extra "03" to the end, this is the 1271 flag + d.buffer = append(d.buffer, 0x03) + + size := uint(len(d.buffer)) - windex + if size > 0xffffff { + return fmt.Errorf("dynamic signature size too big %d", size) + } + + padded, err := uintPadToX(size, 3) + if err != nil { + return err + } + + copy(d.buffer[windex-3:], padded) + + return nil +} + +func (d *Decoder) FlagNode() error { + d.LogFlag("node") + + // Write the node flag (0x03) and just read a nested flag + d.buffer = append(d.buffer, 0x03) + return d.ReadFlag() +} + +func (d *Decoder) ReadBranch() error { + d.LogFlag("branch") + + // Write the branch flag (0x04) and just read a nested flag + d.buffer = append(d.buffer, 0x04) + + // Reserve 3 bytes for the size + sizeSpot := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, 3)...) + + // Now track the size and read another flag + windex := uint(len(d.buffer)) + err := d.ReadFlag() + if err != nil { + return err + } + + size := uint(len(d.buffer)) - windex + + // Write the size on the reserved spot + // notice that it must not be bigger than 2^24 + if size > 0xffffff { + return fmt.Errorf("branch size too big %d", size) + } + + padded, err := uintPadToX(size, 3) + if err != nil { + return err + } + + copy(d.buffer[sizeSpot:], padded) + + return nil +} + +func (d *Decoder) FlagSubdigest() error { + d.LogFlag("subdigest") + + // Write the subdigest flag (0x05) and just read a nested flag + d.buffer = append(d.buffer, 0x05) + return d.ReadFlag() +} + +func (d *Decoder) ReadNested() error { + d.LogFlag("nested") + + // Write the nested flag (0x06) and just read a nested flag + d.buffer = append(d.buffer, 0x06) + + // We use 1 byte for the weight + weight := uint(d.calldata[d.rindex]) + d.rindex++ + + // Write the weight + d.buffer = append(d.buffer, byte(weight)) + + // Another byte represent the threshold, but we write it on 2 bytes + threshold := uint(d.calldata[d.rindex]) + d.rindex++ + + padded, err := uintPadToX(threshold, 2) + if err != nil { + return err + } + + d.buffer = append(d.buffer, padded...) + + // Now read a nested flag, keeping track of the size + // and reserving 3 bytes for the size + d.buffer = append(d.buffer, make([]byte, 3)...) + + windex := uint(len(d.buffer)) + err = d.ReadFlag() + if err != nil { + return err + } + + size := uint(len(d.buffer)) - windex + if size > 0xffffff { + return fmt.Errorf("nested size too big %d", size) + } + + padded = make([]byte, 3) + binary.BigEndian.PutUint64(padded, uint64(size)) + copy(d.buffer[windex-3:], padded) + + return nil +} + +func (d *Decoder) ReadPow10Misc() error { + d.LogFlag("pow10misc") + + exp := uint(d.calldata[d.rindex]) + d.rindex++ + + // If the exp is 0, then we are actually pointing to an extension of the flagset + // in this case we only have one method "READ_SELF_EXECUTE" + if exp == 0 { + return fmt.Errorf("read self execute not implemented") + } + + // The first bit determines if we have a mantissa or not + hasm := (exp & 0x80) == 0 + // Print exp in binary + exp &= 0x7f + + var num *big.Int + num = big.NewInt(10) + num = num.Exp(num, big.NewInt(int64(exp)), nil) + + if hasm { + // Read another byte for the mantissa + mantissa := uint(d.calldata[d.rindex]) + d.rindex++ + + num = num.Mul(num, big.NewInt(int64(mantissa))) + } + + // It should not exceed 2 ** 256 - 1 + if len(num.Bytes()) > 32 { + return fmt.Errorf("pow10misc number too big %d", exp) + } + + // Write the number padded to 32 bytes + padded, err := padToX(num.Bytes(), 32) + if err != nil { + return err + } + d.buffer = append(d.buffer, padded...) + + return nil +} + +func (d *Decoder) ReadMirrorFlag() error { + // The next 2 bytes determine the temporal rindex + trindex := uint(binary.BigEndian.Uint16(d.calldata[d.rindex : d.rindex+2])) + d.rindex += 2 + + prevrindex := d.rindex + d.LogFlag("mirror " + fmt.Sprintf("%d %d", trindex, prevrindex)) + + if prevrindex-3 == trindex { + return fmt.Errorf("mirror flag pointing to the same rindex %d", trindex) + } + + // Replace the rindex with the temporal one + // and read another flag + d.rindex = trindex + err := d.ReadFlag() + if err != nil { + return err + } + + // Restore the rindex + d.rindex = prevrindex + + return nil +} + +func (d *Decoder) ReadCopyCalldata() error { + d.LogFlag("copy_calldata") + + // The next 2 bytes determine from where to copy, the next byte determines the size + // this is a simple copy of the calldata + from := uint(binary.BigEndian.Uint16(d.calldata[d.rindex : d.rindex+2])) + d.rindex += 2 + + size := uint(d.calldata[d.rindex]) + d.rindex++ + + d.buffer = append(d.buffer, d.calldata[from:from+size]...) + return nil +} + +func (d *Decoder) ReadSequenceSignatureV2(flag uint) error { + d.LogFlag("sequence_signature_v2") + + // The flag determines the type of signature + var noChainId bool + if flag == FLAG_S_L_SIG || flag == FLAG_S_SIG { + noChainId = false + } else { + noChainId = true + } + + // Write the sequence signature flag (0x02 for noChain, 0x01 for chain) + if noChainId { + d.buffer = append(d.buffer, 0x02) + } else { + d.buffer = append(d.buffer, 0x01) + } + + // The threshold may be provided as 1 or 2 bytes (the flag also tells you) + var threshold uint + if flag == FLAG_S_L_SIG || flag == FLAG_S_L_SIG_NO_CHAIN { + threshold = uint(binary.BigEndian.Uint16(d.calldata[d.rindex : d.rindex+2])) + d.rindex += 2 + } else { + threshold = uint(d.calldata[d.rindex]) + d.rindex++ + } + + // Write the threshold (padded to two bytes) + padded, err := uintPadToX(threshold, 2) + if err != nil { + return err + } + d.buffer = append(d.buffer, padded...) + + // The checkpoint is always just 4 bytes, read it as a word + val, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + padded, err = padToX(val, 4) + if err != nil { + return err + } + + d.buffer = append(d.buffer, padded...) + + // Now we read the signature tree, this is just a nested flag + return d.ReadFlag() +} + +func (d *Decoder) ReadNonce() error { + d.LogFlag("read_nonce") + + // Read a word, but use only the last 20 bytes + // then read another word, and use only the last 12 bytes + val, err := d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + d.buffer = append(d.buffer, val[12:]...) + + val, err = d.ReadAndLoad32Bytes() + if err != nil { + return err + } + + d.buffer = append(d.buffer, val[20:]...) + return nil +} + +func (d *Decoder) ReadTransactions() error { + // The first byte determines the number of transactions + n := uint(d.calldata[d.rindex]) + d.rindex++ + + d.LogFlag("read_transactions " + fmt.Sprintf("%d", n)) + + // Write the number of transactions, padded to 32 bytes + padded, err := uintPadToX(n, 32) + if err != nil { + return err + } + d.buffer = append(d.buffer, padded...) + + // Reserve 32 bytes for each transaction's pointer + posPointer := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, n*32)...) + + for i := uint(0); i < n; i++ { + // Write the pointer to the transaction (this is to the end of the buffer) + pointer := uint(len(d.buffer)) - posPointer + (i * 32) + padded, err := uintPadToX(pointer, 32) + if err != nil { + return err + } + copy(d.buffer[posPointer:], padded) + + // Now read the transaction + err = d.ReadTransaction() + if err != nil { + return err + } + } + + return nil +} + +func (d *Decoder) ReadTransaction() error { + d.LogFlag("read_transaction") + + // The first byte is a bitmap, it contains information about what values are defined + // - 1000 0000 - 1 if it uses delegate call + // - 0100 0000 - 1 if it uses revert on error + // - 0010 0000 - 1 if it has a defined gas limit + // - 0001 0000 - 1 if it has a defined value + // - 0000 1000 - Unused + // - 0000 0100 - Unused + // - 0000 0010 - Unused + // - 0000 0001 - 1 if it has a defined data + bitmap := d.calldata[d.rindex] + d.rindex++ + + // Write 0x01 or 0x00 (delegatecall) padded to 32 bytes + d.buffer = append(d.buffer, make([]byte, 31)...) + if (bitmap & 0x80) != 0 { + d.buffer = append(d.buffer, 0x01) + } else { + d.buffer = append(d.buffer, 0x00) + } + + // Write 0x01 or 0x00 (revertOnError) padded to 32 bytes + d.buffer = append(d.buffer, make([]byte, 31)...) + if (bitmap & 0x40) != 0 { + d.buffer = append(d.buffer, 0x01) + } else { + d.buffer = append(d.buffer, 0x00) + } + + // Now gas limit may need to be read as a flag + if (bitmap & 0x20) != 0 { + err := d.ReadFlag() + if err != nil { + return err + } + } else { + // Write 0x00 padded to 32 bytes + d.buffer = append(d.buffer, make([]byte, 32)...) + } + + // All transactions have a defined address, this is just a flag + err := d.ReadFlag() + if err != nil { + return err + } + + // Same for value, read as a flag + if (bitmap & 0x10) != 0 { + err := d.ReadFlag() + if err != nil { + return err + } + } else { + // Write 0x00 padded to 32 bytes + d.buffer = append(d.buffer, make([]byte, 32)...) + } + + // If the transaction has no data, we need to write 0x00 padded to 32 bytes twice + if (bitmap & 0x01) == 0 { + d.buffer = append(d.buffer, make([]byte, 64)...) + } else { + // Write the pointer of the data, it is always padded 0xc0 + d.buffer = append(d.buffer, make([]byte, 31)...) + d.buffer = append(d.buffer, 0xc0) + + // Reserve 32 bytes for the size + sizeSpot := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, 32)...) + windex := uint(len(d.buffer)) + + err := d.ReadFlag() + if err != nil { + return err + } + + size := uint(len(d.buffer)) - windex + + // Write the size on the reserved spot, padded to 32 bytes + padded, err := uintPadToX(size, 32) + if err != nil { + return err + } + + copy(d.buffer[sizeSpot:], padded) + + // Pad the buffer to 32 bytes + pdamount := uint(32 - (size % 32)) + if pdamount != 32 { + d.buffer = append(d.buffer, make([]byte, pdamount)...) + } + } + + return nil +} + +func (d *Decoder) ReadExecute() error { + d.LogFlag("execute") + + // Write the execute method (0x7a9a1628) + d.buffer = append(d.buffer, []byte{0x7a, 0x9a, 0x16, 0x28}...) + + // Write the start of the transaction's list (always 0x60 padded to 32 bytes) + d.buffer = append(d.buffer, make([]byte, 31)...) + d.buffer = append(d.buffer, 0x60) + + // Read the nonce + err := d.ReadNonce() + if err != nil { + return err + } + + // We can't know where the signatures will start (we need to read the transactions) + // so we leave 32 bytes and a pointer to the start of the signatures + // (we will write the pointer later) + sigsPointer := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, 32)...) + + // Read the transactions + err = d.ReadTransactions() + if err != nil { + return err + } + + // Write the pointer to the start of the signatures + pointer := uint(len(d.buffer)) - 4 + padded, err := uintPadToX(pointer, 32) + if err != nil { + return err + } + copy(d.buffer[sigsPointer:], padded) + + // Read the signatures, handle the size and the padding + sizePointer := uint(len(d.buffer)) + d.buffer = append(d.buffer, make([]byte, 32)...) + windex := uint(len(d.buffer)) + + err = d.ReadFlag() + if err != nil { + return err + } + + // Write the size padded to 32 bytes on the reserved spot + size := uint(len(d.buffer)) - windex + padded, err = uintPadToX(size, 32) + if err != nil { + return err + } + copy(d.buffer[sizePointer:], padded) + + // Padd the buffer to 32 bytes + pdamount := uint(32 - (size % 32)) + if pdamount != 32 { + d.buffer = append(d.buffer, make([]byte, pdamount)...) + } + + return nil +} + +func (d *Decoder) ReadLiteral(flag uint) error { + d.LogFlag("literal " + fmt.Sprintf("%d", flag)) + + if flag < LITERAL_ZERO { + return fmt.Errorf("literal flag too low %d", flag) + } + + // Write the literal number (LITERAL_ZERO = 0, LITERAL_ZERO + 1 = 1, etc) + // padded to 32 bytes + padded, err := uintPadToX(flag-LITERAL_ZERO, 32) + if err != nil { + return err + } + d.buffer = append(d.buffer, padded...) + + return nil +} diff --git a/compressor/encoder.go b/compressor/encoder.go new file mode 100644 index 0000000..95e4164 --- /dev/null +++ b/compressor/encoder.go @@ -0,0 +1,975 @@ +package compressor + +import ( + "bytes" + "fmt" + "math/big" + + "github.com/0xsequence/go-sequence" +) + +type Encoder struct { + AddressIndexes map[string]uint + Bytes32Indexes map[string]uint + Bytes4Indexes map[string]uint +} + +type EncodeType int + +const ( + Stateless EncodeType = iota + Mirror + ReadStorage + WriteStorage +) + +func NewEncoder() *Encoder { + return &Encoder{ + Bytes4Indexes: LoadBytes4(), + AddressIndexes: make(map[string]uint), + Bytes32Indexes: make(map[string]uint), + } +} + +func HighestPriority(a EncodeType, b EncodeType) EncodeType { + if a == WriteStorage || b == WriteStorage { + return WriteStorage + } + + if a == ReadStorage || b == ReadStorage { + return ReadStorage + } + + if a == Mirror || b == Mirror { + return Mirror + } + + return Stateless +} + +// Encodes a 32 bytes word, trying to optimize it as much as possible +func (c *Encoder) EncodeWordOptimized(pastData *CBuffer, word []byte, saveWord bool) ([]byte, EncodeType, error) { + if len(word) > 32 { + return nil, Stateless, fmt.Errorf("word exceeds 32 bytes") + } + + trimmed := bytes.TrimLeft(word, "\x00") + + // If empty then it can be encoded as literal zero + if len(trimmed) == 0 { + return []byte{byte(LITERAL_ZERO)}, Stateless, nil + } + + // Literals are the cheapest encoding + if len(trimmed) <= 1 && trimmed[0] <= byte(MAX_LITERAL) { + return []byte{trimmed[0] + byte(LITERAL_ZERO)}, Stateless, nil + } + + // If it only has 1 byte, then we encode it as a word + // all other methods use 2 bytes anyway + if len(trimmed) == 1 { + return c.EncodeWordBytes32(trimmed) + } + + // If the word is a power of 2 or 10, we can encode it using 1 byte + pow2 := ispow2(trimmed) + if pow2 != -1 { + return []byte{byte(FLAG_READ_POWER_OF_2), byte(pow2)}, Stateless, nil + } + + // Notice that marking the first bit of N as 1 denotes that we are going to do + // 10 ** N and not 10 ** N * X, that's why we do `| 0x80` + pow10 := pow10(trimmed) + if pow10 != -1 && pow10 <= 77 { + return []byte{byte(FLAG_READ_POWER_OF_10_MISC), byte(pow10 | 0x80)}, Stateless, nil + } + + // 2 ** n - 1 can be represented by two bytes (and the first two are 00) + // so it goes next. We do it before word encoding since word encoding won't + // have the first 2 bytes as 00, in reality this only applies to 0xffff + pow2minus1 := ispow2minus1(trimmed) + if pow2minus1 != -1 { + // The opcode adds an extra 1 to the value, so we need to subtract 1 + return []byte{byte(FLAG_READ_POWER_OF_2), 0x00, byte(pow2minus1 - 1)}, Stateless, nil + } + + // Now we can store words of 2 bytes, we have exhausted all the 1 byte options + if len(trimmed) <= 2 { + return c.EncodeWordBytes32(trimmed) + } + + // We can also use (10 ** N) * X, this uses 2 bytes + pow10fn, pow10fm := pow10Mantissa(trimmed, 127, 255) + if pow10fn != -1 && pow10fm != -1 { + return []byte{byte(FLAG_READ_POWER_OF_10_MISC), byte(pow10fn), byte(pow10fm)}, Stateless, nil + } + + // Mirror flag uses 2 bytes, it lets us point to another flag that we had already used before + // but we need to find a flag that mirrors the data with the padding included! + padded32 := make([]byte, 32) + copy(padded32[32-len(trimmed):], trimmed) + padded32str := string(padded32) + + usedFlag := pastData.Refs.usedFlags[padded32str] + if usedFlag != 0 { + usedFlag -= 1 + + // We can only encode 16 bits for the mirror flag + // if it exceeds this value, then we can't mirror it + // NOR it can be this pointer itself + if usedFlag <= 0xffff && usedFlag != pastData.Len() { + return []byte{byte(FLAG_MIRROR_FLAG), byte(usedFlag >> 8), byte(usedFlag)}, Mirror, nil + } + } + + // Mirror storage flags are different because we don't want to just + // re-read the storage flag, that would mean writting to the storage twice + // apart from that, they work like normal mirror flags + usedStorageFlag := pastData.Refs.usedStorageFlags[padded32str] + if usedStorageFlag != 0 { + usedStorageFlag -= 1 + + // We can only encode 16 bits for the mirror flag + // if it exceeds this value, then we can't mirror it + if usedStorageFlag <= 0xffff { + return []byte{byte(FLAG_READ_STORE_FLAG), byte(usedStorageFlag >> 8), byte(usedStorageFlag)}, Mirror, nil + } + } + + // Now any 3 byte word can be encoded as-is, all the other + // methods use more than 3 bytes + if len(trimmed) <= 3 { + return c.EncodeWordBytes32(trimmed) + } + + // With 3 bytes we can encode 10 ** N * X (with a mantissa of 18 bits and an exp of 6 bits) + pow10fn, pow10fm = pow10Mantissa(trimmed, 63, 262143) + if pow10fn != -1 && pow10fm != -1 { + // The first byte is 6 bits of exp and 2 bits of mantissa + b1 := byte(uint64(pow10fn)<<2) | byte(uint64(pow10fm)>>16) + // The next 16 bits are the last 16 bits of the mantissa + b2 := byte(pow10fm >> 8) + b3 := byte(pow10fm) + + return []byte{byte(FLAG_READ_POW_10_MANTISSA), byte(b1), byte(b2), byte(b3)}, Stateless, nil + } + + // With 3 bytes we can also copy any other word from the calldata + // this can be anything but notice: we must copy the value already padded + copyIndex := FindPastData(pastData, padded32) + if copyIndex != -1 && copyIndex <= 0xffff { + return []byte{byte(FLAG_COPY_CALLDATA), byte(copyIndex >> 8), byte(copyIndex), byte(0x20)}, Stateless, nil + } + + // Contract storage is only enabled on some networks + // in most of L1s is cheaper to provide the data from calldata + // rather than reading it from storage, let alone writing it + if pastData.Refs.useContractStorage { + // If the data is already on storage, we can look it up + // on the addresses or bytes32 repositories + addressIndex := c.AddressIndexes[padded32str] + if addressIndex != 0 { + // There are 4 different flags for reading addresses, + // depending if the index fits on 2, 3, or 4 bytes + bytesNeeded := minBytesToRepresent(addressIndex) + switch bytesNeeded { + case 1, 2: + return []byte{ + byte(FLAG_READ_ADDRESS_2), + byte(addressIndex >> 8), + byte(addressIndex), + }, ReadStorage, nil + case 3: + return []byte{ + byte(FLAG_READ_ADDRESS_3), + byte(addressIndex >> 16), + byte(addressIndex >> 8), + byte(addressIndex), + }, ReadStorage, nil + case 4: + return []byte{ + byte(FLAG_READ_ADDRESS_4), + byte(addressIndex >> 24), + byte(addressIndex >> 16), + byte(addressIndex >> 8), + byte(addressIndex), + }, ReadStorage, nil + } + } + + bytes32Index := c.Bytes32Indexes[padded32str] + if bytes32Index != 0 { + // There are 4 different flags for reading bytes32, + // depending if the index fits on 2, 3, or 4 bytes + bytesNeeded := minBytesToRepresent(bytes32Index) + switch bytesNeeded { + case 1, 2: + return []byte{ + byte(FLAG_READ_BYTES32_2), + byte(bytes32Index >> 8), + byte(bytes32Index), + }, ReadStorage, nil + case 3: + return []byte{ + byte(FLAG_READ_BYTES32_3), + byte(bytes32Index >> 16), + byte(bytes32Index >> 8), + byte(bytes32Index), + }, ReadStorage, nil + case 4: + return []byte{ + byte(FLAG_READ_BYTES32_4), + byte(bytes32Index >> 24), + byte(bytes32Index >> 16), + byte(bytes32Index >> 8), + byte(bytes32Index), + }, ReadStorage, nil + } + } + + if saveWord { + // Any value smaller than 20 bytes can be saved as an address + // ALL saved values must be padded to either 20 bytes or 32 bytes + // For both cases skip values that are too short already + if len(trimmed) <= 20 && len(trimmed) >= 15 { + padded20 := make([]byte, 20) + copy(padded20[20-len(trimmed):], trimmed) + encoded := []byte{byte(FLAG_SAVE_ADDRESS)} + encoded = append(encoded, padded20...) + return encoded, WriteStorage, nil + + } else if len(trimmed) >= 27 { + encoded := []byte{byte(FLAG_SAVE_BYTES32)} + encoded = append(encoded, padded32...) + return encoded, WriteStorage, nil + } + } + } + + // We are out of options now, we need to encode the word as-is + return c.EncodeWordBytes32(trimmed) +} + +// Encodes a 32 word, without any optimizations +func (c *Encoder) EncodeWordBytes32(word []byte) ([]byte, EncodeType, error) { + if len(word) > 32 { + return nil, Stateless, fmt.Errorf("word exceeds 32 bytes") + } + + if len(word) == 0 { + return nil, Stateless, fmt.Errorf("word is empty") + } + + encodedWord := []byte{byte(FLAG_READ_BYTES32_1_BYTES + uint(len(word)) - 1)} + encodedWord = append(encodedWord, word...) + return encodedWord, Stateless, nil +} + +func (c *Encoder) WriteWord(word []byte, dest *CBuffer, useStorage bool) (EncodeType, error) { + encoded, t, err := c.EncodeWordOptimized(dest, word, useStorage) + if err != nil { + return Stateless, err + } + + paddedWord := make([]byte, 32) + copy(paddedWord[32-len(word):], word) + + dest.WriteBytes(encoded) + dest.End(paddedWord, t) + + return t, nil +} + +// Encode N bytes, as optimized as possible +func (c *Encoder) WriteBytesOptimized(dest *CBuffer, bytes []byte, saveWord bool) (EncodeType, error) { + // Empty bytes can be represented with a no-op + // cost: 0 + if len(bytes) == 0 { + dest.WriteInt(FLAG_NO_OP) + dest.End(bytes, Stateless) + return Stateless, nil + } + + // 32 bytes long can be encoded as a word, it has its own set of optimizations. + // cost: word + if len(bytes) == 32 { + return c.WriteWord(bytes, dest, saveWord) + } + + // Now we can try to find a mirror flag for the bytes + // cost: 2 bytes + bytesStr := string(bytes) + usedFlag := dest.Refs.usedFlags[bytesStr] + if usedFlag != 0 { + usedFlag -= 1 + + // We can only encode 16 bits for the mirror flag + // if it exceeds this value, then we can't mirror it + if usedFlag <= 0xffff { + dest.WriteInt(FLAG_MIRROR_FLAG) + dest.WriteBytes([]byte{byte(usedFlag >> 8), byte(usedFlag)}) + // End without creating a second pointer + // otherwise we will be creating a pointer to a pointer + dest.End([]byte{}, Mirror) + return Mirror, nil + } + } + + // Another optimization is to copy the bytes from the calldata + // cost: 3 bytes + copyIndex := FindPastData(dest, bytes) + if copyIndex != -1 && copyIndex <= 0xffff { + dest.WriteInt(FLAG_COPY_CALLDATA) + dest.WriteBytes([]byte{byte(copyIndex >> 8), byte(copyIndex), byte(len(bytes))}) + // End without creating a second pointer + // data can be copied from the calldata directly + dest.End([]byte{}, Stateless) + return Mirror, nil + } + + // If the bytes are 33 bytes long, and the first byte is 0x03 it can be represented as a "node" + // cost: 0 bytes + word + if len(bytes) == 33 && bytes[0] == 0x03 { + dest.WriteInt(FLAG_NODE) + dest.End(bytes, Stateless) + + t, err := c.WriteWord(bytes[1:], dest, saveWord) + if err != nil { + return Stateless, err + } + + return t, nil + } + + // If the bytes are 33 bytes long and starts with 0x05 it can be represented as a "subdigest" + // cost: 0 bytes + word + if len(bytes) == 33 && bytes[0] == 0x05 { + dest.WriteInt(FLAG_SUBDIGEST) + dest.End(bytes, Stateless) + + t, err := c.WriteWord(bytes[1:], dest, saveWord) + if err != nil { + return Stateless, err + } + + return t, nil + } + + // If bytes has 22 bytes and starts with 0x01, then it is probably an address on a signature + // cost: 1 / 0 bytes + address word + if len(bytes) == 22 && bytes[0] == 0x01 { + // If the firt byte (weight) is between 1 and 4, then there is a special flag + if bytes[1] >= 1 && bytes[1] <= 4 { + dest.WriteInt(FLAG_ADDRESS_W0 + uint(bytes[1])) + } else { + // We need to use FLAG_ADDRES_W0 and 1 extra byte for the weight + dest.WriteInt(FLAG_ADDRESS_W0) + dest.WriteByte(bytes[1]) + } + + dest.End(bytes, Stateless) + + t, err := c.WriteWord(bytes[2:], dest, saveWord) + if err != nil { + return Stateless, err + } + + return t, nil + } + + // If the bytes are 68 bytes long and starts with 0x00, the it is probably a signature for a Sequence wallet + // cost: 66/67 bytes + if len(bytes) == 68 && bytes[0] == 0x00 { + // If the first byte (weight) is between 1 and 4, then there is a special flag + if bytes[1] >= 1 && bytes[1] <= 4 { + dest.WriteInt(FLAG_SIGNATURE_W0 + uint(bytes[1])) + } else { + // We need to use FLAG_SIGNATURE_W0 and 1 extra byte for the weight + dest.WriteInt(FLAG_SIGNATURE_W0) + dest.WriteByte(bytes[1]) + } + + dest.WriteBytes(bytes[2:]) + dest.End(bytes, Stateless) + return Stateless, nil + } + + // We can try encoding this as a signature, we don't know if it is a Sequence signature + // we generate a snapshot of the dest buffer and try to encode it as a signature, if it fails + // or if the result is bigger than the original bytes, we restore the snapshot and continue. + // Notice: pass `false` to `mayUseBytes` or else this will be an infinite loop + snapshot := dest.Snapshot() + t, err := c.WriteSignature(dest, bytes, false) + if err == nil && dest.Len() < len(bytes)+3+len(snapshot.Commited) { + return t, nil + } + dest.Restore(snapshot) + + // If the bytes are a multiple of 32 + 4 bytes (max 6 * 32 + 4) then it + // can be encoded as an ABI call with 0 to 6 parameters + if len(bytes) <= 6*32+4 && (len(bytes)-4)%32 == 0 { + dest.WriteInt(FLAG_ABI_0_PARAM + uint((len(bytes)-4)/32)) + dest.WriteBytes(c.Encode4Bytes(bytes[:4])) + dest.End(bytes, Stateless) + + encodeType := Stateless + + for i := 4; i < len(bytes); i += 32 { + t, err := c.WriteWord(bytes[i:i+32], dest, saveWord) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil + } + + // If the bytes are a multiple of 32 + 4 bytes (max 256 * 32 + 4) then it + // can be represented using dynamic encoded ABI + if len(bytes) <= 256*32+4 && (len(bytes)-4)%32 == 0 { + dest.WriteInt(FLAG_READ_DYNAMIC_ABI) + dest.WriteBytes(c.Encode4Bytes(bytes[:4])) + dest.WriteInt(uint((len(bytes) - 4) / 32)) // The number of ARGs + // This flag can be used to compress dynamic size arguments too + // but in this case, we just leave it as 0s so all arguments are 32 bytes + dest.WriteInt(0) + dest.End(bytes, Stateless) + + encodeType := Stateless + + for i := 4; i < len(bytes); i += 32 { + t, err := c.WriteWord(bytes[i:i+32], dest, saveWord) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil + } + + // If there are no other options, then we encode the bytes as-is + // we can try two different methods: bytes_n or splitting it in many words + an extra bytes + // for now we leave it as bytes_n for simplicity + return c.WriteNBytesRaw(dest, bytes) +} + +// Encodes N bytes, without any optimizations +func (c *Encoder) WriteNBytesRaw(dest *CBuffer, bytes []byte) (EncodeType, error) { + dest.WriteInt(FLAG_READ_N_BYTES) + dest.End(bytes, Stateless) + + t, err := c.WriteWord(uintToBytes(uint64(len(bytes))), dest, false) + if err != nil { + return Stateless, err + } + + dest.WriteBytes(bytes) + + // End this last write without creating a flag pointer + // this is a data blob, not a flag + dest.End([]byte{}, t) + + return t, nil +} + +func (c *Encoder) Encode4Bytes(bytes []byte) []byte { + // If Bytes4Indexes has this value, then we can just use the index + index := c.Bytes4Indexes[string(bytes)] + if index != 0 { + return []byte{byte(index)} + } + + // If don't then we need to provide it as-is, but it has to be prefixed with 0x00 + return append([]byte{0x00}, bytes...) +} + +func (c *Encoder) WriteNonce(dest *CBuffer, nonce *big.Int, randomNonce bool) (EncodeType, error) { + paddedNonce := make([]byte, 32) + copy(paddedNonce[32-len(nonce.Bytes()):], nonce.Bytes()) + + // The nonce is encoded in two parts, the first 160 bits are the space + // the last 96 bits are the nonce itself, the nonce space can be saved to storage + // unless it is a random nonce + spaceBytes := paddedNonce[:20] + nonceBytes := paddedNonce[20:] + + t1, err := c.WriteWord(spaceBytes, dest, !randomNonce) + if err != nil { + return Stateless, err + } + + t2, err := c.WriteWord(nonceBytes, dest, false) + if err != nil { + return Stateless, err + } + + return HighestPriority(t1, t2), nil +} + +func (c *Encoder) WriteTransactions(dest *CBuffer, transactions sequence.Transactions) (EncodeType, error) { + if len(transactions) == 0 { + return Stateless, fmt.Errorf("transactions is empty") + } + + if len(transactions) > 255 { + return Stateless, fmt.Errorf("transactions exceeds 255") + } + + // The first byte is the number of transactions + dest.WriteInt(uint(len(transactions))) + dest.End([]byte{}, Stateless) + + encodeType := Stateless + + for _, transaction := range transactions { + t, err := c.WriteTransaction(dest, transaction) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil +} + +func (c *Encoder) WriteTransaction(dest *CBuffer, transaction *sequence.Transaction) (EncodeType, error) { + // The first byte is the flag of the transaction, the specification follows: + // - 1000 0000 - 1 if it uses delegate call + // - 0100 0000 - 1 if it uses revert on error + // - 0010 0000 - 1 if it has a defined gas limit + // - 0001 0000 - 1 if it has a defined value + // - 0000 1000 - Unused + // - 0000 0100 - Unused + // - 0000 0010 - Unused + // - 0000 0001 - 1 if it has a defined data + + flag := byte(0x00) + + if transaction.DelegateCall { + flag |= 0x80 + } + + if transaction.RevertOnError { + flag |= 0x40 + } + + hasGasLimit := transaction.GasLimit != nil && transaction.GasLimit.Cmp(big.NewInt(0)) != 0 + if hasGasLimit { + flag |= 0x20 + } + + hasValue := transaction.Value != nil && transaction.Value.Cmp(big.NewInt(0)) != 0 + if hasValue { + flag |= 0x10 + } + + hasData := len(transaction.Data) > 0 || len(transaction.Transactions) > 0 + if hasData { + flag |= 0x01 + } + + dest.WriteByte(flag) + dest.End([]byte{}, Stateless) + + encodeType := Stateless + + // Now we start writing the values that we do have + if hasGasLimit { + t, err := c.WriteWord(transaction.GasLimit.Bytes(), dest, false) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + // Encode the address as a word + t, err := c.WriteWord(transaction.To.Bytes(), dest, dest.Refs.useContractStorage) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + + if hasValue { + t, err := c.WriteWord(transaction.Value.Bytes(), dest, false) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + if hasData { + // If the transaction has "transactions" then it is another sequence transaction + // in that case, we must Write a transaction, since data is empty + var t EncodeType + + if len(transaction.Transactions) > 0 { + dest.WriteInt(FLAG_READ_EXECUTE) + dest.End([]byte{}, Stateless) + + t, err = c.WriteExecute(dest, nil, transaction) + } else { + t, err = c.WriteBytesOptimized(dest, transaction.Data, dest.Refs.useContractStorage) + } + + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil +} + +func (c *Encoder) WriteSignature(dest *CBuffer, signature []byte, mayUseBytes bool) (EncodeType, error) { + dest.SignatureLevel += 1 + + defer func() { + dest.SignatureLevel -= 1 + }() + + // First byte determines the signature type + if mayUseBytes && (len(signature) == 0) { + // Guestmodule signatures are empty + return c.WriteBytesOptimized(dest, signature, false) + } + + typeByte := signature[0] + + switch typeByte { + case 0x00: // Legacy + return c.WriteSignatureBody(dest, false, signature) + case 0x01: // Dynamic + return c.WriteSignatureBody(dest, false, signature[1:]) + case 0x02: // No chain ID + return c.WriteSignatureBody(dest, true, signature[1:]) + case 0x03: // Chained + return c.WriteChainedSignature(dest, signature[1:]) + + default: + return Stateless, fmt.Errorf("invalid signature type %d", typeByte) + } +} + +func (c *Encoder) WriteChainedSignature(dest *CBuffer, signature []byte) (EncodeType, error) { + // First we need to count how many chained signatures are there + // for this we read the first 3 bytes of each, as they contain the size + var pointer uint + var parts [][]byte + + for pointer < uint(len(signature)) { + length := uint(signature[pointer])<<16 | uint(signature[pointer+1])<<8 | uint(signature[pointer+2]) + pointer += 3 + + npointer := pointer + length + parts = append(parts, signature[pointer:npointer]) + pointer = npointer + } + + // We have two instructions for this, one for 8 bits and one for 16 bits + // depending on the number of parts + totalParts := uint(len(parts)) + if totalParts > 255 { + dest.WriteInt(FLAG_READ_CHAINED_L) + dest.WriteByte(byte(totalParts >> 8)) + dest.WriteByte(byte(totalParts)) + } else { + dest.WriteInt(FLAG_READ_CHAINED) + dest.WriteByte(byte(totalParts)) + } + + dest.End([]byte{}, Stateless) + + // Now we need to encode every nested part, one for each signature part + encodeType := Stateless + + for _, part := range parts { + t, err := c.WriteSignature(dest, part, false) + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil +} + +func (c *Encoder) WriteExecute(dest *CBuffer, wallet []byte, transaction *sequence.Transaction) (EncodeType, error) { + var t EncodeType + + t, err := c.WriteNonce(dest, transaction.Nonce, false) + if err != nil { + return Stateless, err + } + + tt, err := c.WriteTransactions(dest, transaction.Transactions) + if err != nil { + return Stateless, err + } + t = HighestPriority(t, tt) + + tt, err = c.WriteSignature(dest, transaction.Signature, true) + if err != nil { + return Stateless, err + } + t = HighestPriority(t, tt) + + if wallet != nil { + tt, err = c.WriteWord(wallet, dest, true) + if err != nil { + return Stateless, err + } + } + + return HighestPriority(t, tt), nil +} + +func (c *Encoder) WriteSignatureBody(dest *CBuffer, noChain bool, body []byte) (EncodeType, error) { + // The first 2 bytes are the threshold + // this (alongside the noChain flag) defines + // the encoding flag + if len(body) < 2 { + return Stateless, fmt.Errorf("signature is too short") + } + + threshold := uint(body[0])<<8 | uint(body[1]) + longThreshold := threshold > 0xff + + var tflag uint + + if !longThreshold && !noChain { + tflag = FLAG_S_SIG + } else if !longThreshold && noChain { + tflag = FLAG_S_SIG_NO_CHAIN + } else if longThreshold && !noChain { + tflag = FLAG_S_L_SIG + } else { + tflag = FLAG_S_L_SIG_NO_CHAIN + } + + dest.WriteInt(tflag) + + // On long threshold we use 2 bytes for the threshold + if longThreshold { + dest.WriteBytes(body[:2]) + } else { + dest.WriteByte(body[1]) + } + + dest.End(body, Stateless) + + // Next 4 bytes is the checkpoint + if len(body) < 6 { + return Stateless, fmt.Errorf("signature is too short") + } + + checkpoint := body[2:6] + c.WriteWord(checkpoint, dest, false) + + return c.WriteSignatureTree(dest, body[6:]) +} + +func (c *Encoder) WriteSignatureTree(dest *CBuffer, tree []byte) (EncodeType, error) { + // Signature trees need to be encoded as N nested bytes (one per part) + // for this we first need to count the number of parts + if len(tree) == 0 { + return Stateless, fmt.Errorf("signature tree is empty") + } + + totalParts := 0 + pointer := uint(0) + + for pointer < uint(len(tree)) { + partType := tree[pointer] + pointer += 1 + + switch partType { + case 0x00: // EOA Signature + pointer += (1 + 66) + case 0x01: // Address + pointer += (1 + 20) + case 0x02: // Dynamic + pointer += (1 + 20) + // 3 bytes after address and weight are the length + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += (3 + length) + case 0x03: // Node + pointer += 32 + case 0x04: // Branch + // 3 bytes of length + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += (3 + length) + case 0x05: // Subdigest + pointer += 32 + case 0x06: // Nested + pointer += 3 + // 3 bytes of length + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += (3 + length) + default: + return Stateless, fmt.Errorf("invalid signature part type %d", partType) + } + + totalParts += 1 + } + + if totalParts > 1 { + if totalParts > 255 { + dest.WriteInt(FLAG_NESTED_N_FLAGS_16) + dest.WriteByte(byte(totalParts >> 8)) + dest.WriteByte(byte(totalParts)) + } else { + dest.WriteInt(FLAG_NESTED_N_FLAGS_8) + dest.WriteByte(byte(totalParts)) + } + } + + dest.End([]byte{}, Stateless) + + // Now we need to encode every nested part, one for each signature part + encodeType := Stateless + pointer = uint(0) + for pointer < uint(len(tree)) { + partType := tree[pointer] + pointer += 1 + + var t EncodeType + var err error + + switch partType { + case 0x00: // EOA Signature + next := pointer + (1 + 66) + t, err = c.WriteBytesOptimized(dest, tree[pointer-1:next], false) + pointer = next + case 0x01: // Address + next := pointer + (1 + 20) + t, err = c.WriteBytesOptimized(dest, tree[pointer-1:next], true) + encodeType = HighestPriority(encodeType, t) + pointer = next + case 0x02: // Dynamic + weight := uint(tree[pointer]) + pointer += 1 + + addr := tree[pointer : pointer+20] + pointer += 20 + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += 3 + + next := pointer + length + t, err = c.WriteDynamicSignaturePart(dest, addr, weight, tree[pointer:next]) + pointer = next + case 0x03: // Node + next := pointer + 32 + t, err = c.WriteBytesOptimized(dest, tree[pointer-1:next], true) + pointer = next + case 0x04: // Branch + // 3 bytes of length + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += 3 + + next := pointer + length + t, err = c.WriteBranchSignaturePart(dest, tree[pointer:next]) + pointer = next + case 0x05: // Subdigest + next := pointer + 32 + t, err = c.WriteBytesOptimized(dest, tree[pointer-1:next], false) + pointer = next + case 0x06: // Nested + weight := uint(tree[pointer]) + pointer += 1 + threshold := uint(tree[pointer])<<8 | uint(tree[pointer+1]) + pointer += 2 + length := uint(tree[pointer])<<16 | uint(tree[pointer+1])<<8 | uint(tree[pointer+2]) + pointer += 3 + + next := pointer + length + t, err = c.WriteNestedSignaturePart(dest, weight, threshold, tree[pointer:next]) + pointer = next + default: + // This should never happen + return Stateless, fmt.Errorf("invalid signature part type, wtf, unreachable code") + } + + if err != nil { + return Stateless, err + } + + encodeType = HighestPriority(encodeType, t) + } + + return encodeType, nil +} + +func (c *Encoder) WriteNestedSignaturePart(dest *CBuffer, weight uint, threshold uint, branch []byte) (EncodeType, error) { + if weight > 255 { + return Stateless, fmt.Errorf("weight exceeds 255") + } + + if threshold > 255 { + return Stateless, fmt.Errorf("threshold exceeds 255") + } + + dest.WriteInt(FLAG_NESTED) + dest.WriteInt(weight) + dest.WriteInt(threshold) + dest.End([]byte{}, Stateless) + + return c.WriteSignatureTree(dest, branch) +} + +func (c *Encoder) WriteBranchSignaturePart(dest *CBuffer, branch []byte) (EncodeType, error) { + if len(branch) == 0 { + return Stateless, fmt.Errorf("branch is empty") + } + + dest.WriteInt(FLAG_BRANCH) + dest.End([]byte{}, Stateless) + + return c.WriteSignatureTree(dest, branch) +} + +func (c *Encoder) WriteDynamicSignaturePart(dest *CBuffer, address []byte, weight uint, signature []byte) (EncodeType, error) { + if weight > 255 { + return Stateless, fmt.Errorf("weight exceeds 255") + } + + if signature[len(signature)-1] != 0x03 { + return Stateless, fmt.Errorf("signature is not a dynamic signature") + } + + unsuffixed := signature[:len(signature)-1] + + dest.WriteInt(FLAG_DYNAMIC_SIGNATURE) + dest.WriteInt(weight) + dest.End([]byte{}, Stateless) + + // The address must be 20 bytes long + // write it as a word + if len(address) != 20 { + return Stateless, fmt.Errorf("address is not 20 bytes long") + } + + t1, err := c.WriteWord(address, dest, true) + if err != nil { + return Stateless, err + } + + // Encode the signature using bytes, as it may or may not be a Sequence signature + // bytes is going to try to encode it as a Sequence signature, if it fails it will + // encode it as a bunch of bytes + t2, err := c.WriteBytesOptimized(dest, unsuffixed, true) + if err != nil { + return Stateless, err + } + + return HighestPriority(t1, t2), nil +} diff --git a/compressor/loadstate.go b/compressor/loadstate.go new file mode 100644 index 0000000..6e562cb --- /dev/null +++ b/compressor/loadstate.go @@ -0,0 +1,127 @@ +package compressor + +import ( + "context" + "fmt" + + "github.com/0xsequence/ethkit/ethrpc" + "github.com/0xsequence/ethkit/go-ethereum" + "github.com/0xsequence/ethkit/go-ethereum/common" +) + +// The compressor contract has no standard ABI +// instead, it has some custom 1 byte functions +// - 0x00: Execute 1 execute +// - 0x01: Execute N executes +// - 0x02: Read address (32 bytes for the index) +// - 0x03: Read bytes32 (32 bytes for the index) +// - 0x04: Read sizes +// - 0x05: Read storage slots (32 bytes for each index) +// - 0x06: Decompress 1 execute +// - 0x07: Decompress N executes + +func GenBatch(from uint, to uint, max uint, itemplate func(uint) []byte) []byte { + var end uint + + if to < max { + end = to + } else { + end = max + } + + indexes := make([]byte, end*32) + + for j := uint(0); j < end; j++ { + copy(indexes[j*32:j*32+32], itemplate(from+j)) + } + + return indexes +} + +func ParseBatchResult(to map[string]uint, res []byte, offset uint) error { + if len(res)%32 != 0 { + return fmt.Errorf("invalid result length") + } + + for j := uint(0); j < uint(len(res)/32); j++ { + // Ignore results that all 0s + r := res[j*32 : j*32+32] + allZero := true + for i := 31; i >= 0; i-- { + if r[i] != 0 { + allZero = false + break + } + } + if !allZero { + to[string(res[j*32:j*32+32])] = j + offset + } + } + + return nil +} + +func LoadState(ctx context.Context, provider *ethrpc.Provider, contract common.Address, batchSize uint, skipa uint, skipb uint, skipBlocks uint) (uint, map[string]uint, uint, map[string]uint, error) { + ah, addresses, err := LoadAddresses(ctx, provider, contract, batchSize, skipa, skipBlocks) + if err != nil { + return 0, nil, 0, nil, err + } + + bh, bytes32, err := LoadBytes32(ctx, provider, contract, batchSize, skipb, skipBlocks) + if err != nil { + return 0, nil, 0, nil, err + } + + return ah, addresses, bh, bytes32, nil +} + +func LoadAddresses(ctx context.Context, provider *ethrpc.Provider, contract common.Address, batchSize uint, skip uint, skipBlocks uint) (uint, map[string]uint, error) { + // Load total number of addresses + asize, _, err := GetTotals(ctx, provider, contract, skipBlocks) + if err != nil { + return 0, nil, err + } + + return LoadStorage(ctx, provider, contract, batchSize, skip, asize, AddressIndex) +} + +func LoadBytes32(ctx context.Context, provider *ethrpc.Provider, contract common.Address, batchSize uint, skip uint, skipBlocks uint) (uint, map[string]uint, error) { + // Always skip index 0 for bytes32, it maps to the size slot + // it technically can be used, but it is not write-once, so + // it will lead to decompression errors + if skip == 0 { + skip = 1 + } + + // Load total number of bytes32 + _, bsize, err := GetTotals(ctx, provider, contract, skipBlocks) + if err != nil { + return 0, nil, err + } + + return LoadStorage(ctx, provider, contract, batchSize, skip, bsize, Bytes32Index) +} + +func LoadStorage(ctx context.Context, provider *ethrpc.Provider, contract common.Address, batchSize uint, skip uint, total uint, itemplate func(uint) []byte) (uint, map[string]uint, error) { + out := make(map[string]uint) + + for i := skip; i < total; i += batchSize { + batch := GenBatch(i, total, batchSize, itemplate) + + res, err := provider.CallContract(ctx, ethereum.CallMsg{ + To: &contract, + Data: append([]byte{byte(METHOD_READ_STORAGE_SLOTS)}, batch...), + }, nil) + + if err != nil { + return 0, nil, err + } + + err = ParseBatchResult(out, res, i) + if err != nil { + return 0, nil, err + } + } + + return total, out, nil +} diff --git a/compressor/utils.go b/compressor/utils.go new file mode 100644 index 0000000..420e198 --- /dev/null +++ b/compressor/utils.go @@ -0,0 +1,256 @@ +package compressor + +import ( + "bytes" + "encoding/binary" + "fmt" + "log" + "math/big" + + "github.com/0xsequence/go-sequence" +) + +func ispow2minus1(b []byte) int { + seen1 := false + val := 1 + + for _, byteVal := range b { + for ii := 7; ii >= 0; ii-- { + bit := (byteVal >> ii) & 1 + if bit == 1 { + if !seen1 { + seen1 = true + continue + } + val += 1 + } else { + if seen1 { + return -1 + } + } + } + } + + return val +} + +func ispow2(b []byte) int { + seen1 := false + val := 0 + + for _, byteVal := range b { + for ii := 7; ii >= 0; ii-- { + bit := (byteVal >> ii) & 1 + if bit == 1 { + if !seen1 { + seen1 = true + continue + } else { + return -1 + } + } else { + if seen1 { + val++ + } + } + } + } + + return val +} + +func pow10(b []byte) int { + num := big.NewInt(0).SetBytes(b) + + ten := big.NewInt(10) + zero := big.NewInt(0) + count := 0 + + for num.Cmp(ten) >= 0 { + remainder := big.NewInt(0) + remainder.Mod(num, ten) + + if remainder.Cmp(zero) != 0 { + return -1 + } + + num.Div(num, ten) + count++ + } + + if num.Cmp(big.NewInt(1)) != 0 { + return -1 + } + + return count +} + +func pow10Mantissa(b []byte, maxExp int, maxMantissa int) (int, int) { + num := big.NewInt(0).SetBytes(b) + + var n int + + ten := big.NewInt(10) + maxByteValue := big.NewInt(int64(maxMantissa)) + zero := big.NewInt(0) + + for n = 0; n < maxExp; n++ { + powerOfTen := new(big.Int).Exp(ten, big.NewInt(int64(n)), nil) + + if powerOfTen.Cmp(num) > 0 { + break + } + + quotient := new(big.Int) + remainder := new(big.Int) + quotient.DivMod(num, powerOfTen, remainder) + + if remainder.Cmp(zero) == 0 && quotient.Cmp(maxByteValue) <= 0 { + return n, int(quotient.Int64()) + } + } + + return -1, -1 +} + +func minBytesToRepresent(num uint) uint { + if num == 0 { + return 1 + } + + var bitsNeeded uint + for num > 0 { + bitsNeeded++ + num >>= 1 + } + + // Convert bits to bytes + return uint((bitsNeeded + 7) / 8) +} + +func uintToBytes(n uint64) []byte { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, n) + + if err != nil { + log.Fatalf("binary.Write failed: %v", err) + } + + return buf.Bytes() +} + +func padToX(b []byte, n uint) ([]byte, error) { + if len(b) > int(n) { + // Try trimming leading zeros, if that doesn't work, return an error + b = bytes.TrimLeft(b, "\x00") + if len(b) > int(n) { + return nil, fmt.Errorf("value too large to pad") + } + } + + if len(b) == int(n) { + return b, nil + } + + pad := make([]byte, int(n)-len(b)) + return append(pad, b...), nil +} + +func uintPadToX(u uint, n uint) ([]byte, error) { + buff := make([]byte, 8) + binary.BigEndian.PutUint64(buff, uint64(u)) + return padToX(buff, n) +} + +func bytesToUint64(b []byte) uint { + // Read byte by byte, as it may be shorter + // this is big endian + var val uint + for _, byteVal := range b { + val <<= 8 + val |= uint(byteVal) + } + return val +} + +func FindPastData(pastData *CBuffer, data []byte) int { + for i := 0; i+len(data) < len(pastData.Data()); i++ { + if bytes.Equal(pastData.Data()[i:i+len(data)], data) { + return i + } + } + + return -1 +} + +func NormalizeSignature(signature []byte) ([]byte, error) { + if len(signature) == 0 { + return signature, nil + } + // One thing that happens is that there are two ways of representing a Sequence signature in v2 + // lagacy and dynamic, decompressor will ALWAYS decompress to the dynamic format, so if + // the input is in the legacy format, we need to convert it to the dynamic format. + // This is easy, because the legacy format starts with 0x00, if that's the case we just + // add 0x01 at the beginning of the signature + // Notice that guest executes have no signature, so we don't need to fix those + if signature[0] == 0 { + return append([]byte{1}, signature...), nil + } + + // Chained signatures are a bit harder, as they need to be dissected and normalized + // then-reassembled again + if signature[0] == 3 { + normalized := []byte{0x03} + var rindex uint = 1 + + for rindex < uint(len(signature)) { + // The first 3 bytes are the length of the part + length := bytesToUint64(signature[rindex : rindex+3]) + rindex += 3 + + part := signature[rindex : rindex+uint(length)] + rindex += uint(length) + + npart, err := NormalizeSignature(part) + if err != nil { + return nil, err + } + + // Notice that we need to write it with the new length + plen, err := uintPadToX(uint(len(npart)), 3) + if err != nil { + return nil, err + } + + normalized = append(normalized, plen...) + normalized = append(normalized, npart...) + } + + return normalized, nil + } + + return signature, nil +} + +func NormalizeTransactionSignature( + transaction *sequence.Transaction, +) error { + sig, err := NormalizeSignature(transaction.Signature) + if err != nil { + return err + } + + transaction.Signature = sig + + // If the transaction is a nested sequence transaction, we need to normalize its calldata too + for _, tx := range transaction.Transactions { + if tx.Signature != nil && len(tx.Signature) != 0 { + err := NormalizeTransactionSignature(tx) + if err != nil { + return err + } + } + } + + return nil +} diff --git a/receipts.go b/receipts.go index 42b329d..eefa6a5 100644 --- a/receipts.go +++ b/receipts.go @@ -8,6 +8,7 @@ import ( "github.com/0xsequence/ethkit/ethcoder" "github.com/0xsequence/ethkit/ethrpc" + "github.com/0xsequence/ethkit/go-ethereum" "github.com/0xsequence/ethkit/go-ethereum/accounts/abi" "github.com/0xsequence/ethkit/go-ethereum/common" "github.com/0xsequence/ethkit/go-ethereum/core/types" @@ -53,19 +54,80 @@ func (r *Receipt) setNativeReceipt(receipt *types.Receipt) { } } +// This method is duplicated code from: `compressor/contract.go` +// can't be used directly, because it would create a circular dependency +func DecompressCalldata(ctx context.Context, provider *ethrpc.Provider, transaction *types.Transaction) (common.Address, []byte, error) { + data := transaction.Data() + + if len(data) == 0 { + return common.Address{}, nil, fmt.Errorf("empty transaction data") + } + + if data[0] != byte(0x00) { + return common.Address{}, nil, fmt.Errorf("not compressed data") + } + + // Replace first byte with `METHOD_DECOMPRESS_1` (0x06) + // and call `.to` + c2 := make([]byte, len(data)) + copy(c2, data) + c2[0] = byte(0x06) + + res, err := provider.CallContract(ctx, ethereum.CallMsg{ + To: transaction.To(), + Data: c2, + }, nil) + + if err != nil { + return common.Address{}, nil, err + } + + if len(res) < 32 { + return common.Address{}, nil, fmt.Errorf("decompressed data too short") + } + + addr := common.BytesToAddress(res[len(res)-32:]) + return addr, res[:len(res)-32], nil +} + +func TryDecodeCalldata( + ctx context.Context, + provider *ethrpc.Provider, + transaction *types.Transaction, +) (common.Address, Transactions, *big.Int, []byte, error) { + decodedTransactions, decodedNonce, decodedSignature, err := DecodeExecdata(transaction.Data()) + if err == nil { + return *transaction.To(), decodedTransactions, decodedNonce, decodedSignature, nil + } + + // Try decoding it decompressed + addr, decompressed, err2 := DecompressCalldata(ctx, provider, transaction) + if err2 != nil { + // Don't bubble up the decompression error, as it might not be a decompression error + return common.Address{}, nil, nil, nil, err + } + + decodedTransactions, decodedNonce, decodedSignature, err = DecodeExecdata(decompressed) + if err != nil { + return common.Address{}, nil, nil, nil, err + } + + return addr, decodedTransactions, decodedNonce, decodedSignature, nil +} + func DecodeReceipt(ctx context.Context, receipt *types.Receipt, provider *ethrpc.Provider) ([]*Receipt, []*types.Log, error) { transaction, _, err := provider.TransactionByHash(ctx, receipt.TxHash) if err != nil { return nil, nil, err } - decodedTransactions, decodedNonce, decodedSignature, err := DecodeExecdata(transaction.Data()) + wallet, decodedTransactions, decodedNonce, decodedSignature, err := TryDecodeCalldata(ctx, provider, transaction) if err != nil { return nil, nil, err } isGuestExecute := decodedNonce != nil && len(decodedSignature) == 0 - logs, receipts, err := decodeReceipt(receipt.Logs, decodedTransactions, decodedNonce, *transaction.To(), transaction.ChainId(), isGuestExecute) + logs, receipts, err := decodeReceipt(receipt.Logs, decodedTransactions, decodedNonce, wallet, transaction.ChainId(), isGuestExecute) if err != nil { return nil, nil, err }