Skip to content

Commit

Permalink
Merge pull request #5342 from onflow/ramtin/5197-part2-precompile
Browse files Browse the repository at this point in the history
[Flow EVM] COA ownership proof - part 2
  • Loading branch information
ramtinms authored Feb 12, 2024
2 parents 51202b7 + 79b5462 commit 1baa345
Show file tree
Hide file tree
Showing 11 changed files with 781 additions and 37 deletions.
4 changes: 4 additions & 0 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package handler

import (
"bytes"
"fmt"
"math/big"

gethCommon "github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -58,6 +59,9 @@ func getPrecompiles(
archContract := precompiles.ArchContract(
archAddress,
backend.GetCurrentBlockHeight,
func(cpic *types.COAOwnershipProofInContext) (bool, error) {
return false, fmt.Errorf("not implemented")
},
)
return []types.Precompile{archContract}
}
Expand Down
231 changes: 231 additions & 0 deletions fvm/evm/precompiles/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package precompiles

import (
"encoding/binary"
"errors"
"math/big"

gethCommon "github.com/ethereum/go-ethereum/common"
)

// This package provides fast and efficient
// utilities needed for abi encoding and decoding
// encodings are mostly used for testing purpose
// if more complex encoding and decoding is needed please
// use the abi package and pass the ABIs, though
// that has a performance overhead.
const (
FixedSizeUnitDataReadSize = 32
Bytes4DataReadSize = 4
Bytes8DataReadSize = 8
Bytes32DataReadSize = 32
Uint64ByteSize = 8

EncodedBoolSize = FixedSizeUnitDataReadSize
EncodedAddressSize = FixedSizeUnitDataReadSize
EncodedBytes32Size = FixedSizeUnitDataReadSize
EncodedBytes4Size = FixedSizeUnitDataReadSize
EncodedBytes8Size = FixedSizeUnitDataReadSize
EncodedUint64Size = FixedSizeUnitDataReadSize
EncodedUint256Size = FixedSizeUnitDataReadSize
)

var ErrInputDataTooSmall = errors.New("input data is too small for decoding")
var ErrBufferTooSmall = errors.New("buffer too small for encoding")
var ErrDataTooLarge = errors.New("input data is too large for encoding")

// ReadAddress reads an address from the buffer at index
func ReadAddress(buffer []byte, index int) (gethCommon.Address, error) {
if len(buffer) < index+FixedSizeUnitDataReadSize {
return gethCommon.Address{}, ErrInputDataTooSmall
}
paddedData := buffer[index : index+FixedSizeUnitDataReadSize]
// addresses are zero-padded on the left side.
addr := gethCommon.BytesToAddress(
paddedData[FixedSizeUnitDataReadSize-gethCommon.AddressLength:])
return addr, nil
}

// EncodeAddress encodes the address and add it to the buffer at the index
func EncodeAddress(address gethCommon.Address, buffer []byte, index int) error {
if len(buffer) < index+EncodedAddressSize {
return ErrBufferTooSmall
}
copy(buffer[index:index+EncodedAddressSize],
gethCommon.LeftPadBytes(address[:], EncodedAddressSize))
return nil
}

// ReadBool reads a boolean from the buffer at the index
func ReadBool(buffer []byte, index int) (bool, error) {
if len(buffer) < index+EncodedBoolSize {
return false, ErrInputDataTooSmall
}
// bools are zero-padded on the left side
// so we only need to read the last byte
return uint8(buffer[index+EncodedBoolSize-1]) > 0, nil
}

// EncodeBool encodes a boolean into fixed size unit of encoded data
func EncodeBool(bitSet bool, buffer []byte, index int) error {
if len(buffer) < index+EncodedBoolSize {
return ErrBufferTooSmall
}
// bit set with left padding
for i := 0; i < EncodedBoolSize; i++ {
buffer[index+i] = 0
}
if bitSet {
buffer[index+EncodedBoolSize-1] = 1
}
return nil
}

// ReadUint64 reads a uint64 from the buffer at index
func ReadUint64(buffer []byte, index int) (uint64, error) {
if len(buffer) < index+EncodedUint64Size {
return 0, ErrInputDataTooSmall
}
// data is expected to be big endian (zero-padded on the left side)
return binary.BigEndian.Uint64(
buffer[index+EncodedUint64Size-Uint64ByteSize : index+EncodedUint64Size]), nil
}

// EncodeUint64 encodes a uint64 into fixed size unit of encoded data (zero-padded on the left side)
func EncodeUint64(inp uint64, buffer []byte, index int) error {
if len(buffer) < index+EncodedUint64Size {
return ErrBufferTooSmall
}
encoded := make([]byte, 8)
binary.BigEndian.PutUint64(encoded, inp)
copy(buffer[index:index+EncodedUint64Size],
gethCommon.LeftPadBytes(encoded, EncodedUint64Size),
)
return nil
}

// ReadUint256 reads an address from the buffer at index
func ReadUint256(buffer []byte, index int) (*big.Int, error) {
if len(buffer) < index+EncodedUint256Size {
return nil, ErrInputDataTooSmall
}
// data is expected to be big endian (zero-padded on the left side)
return new(big.Int).SetBytes(buffer[index : index+EncodedUint256Size]), nil
}

// ReadBytes4 reads a 4 byte slice from the buffer at index
func ReadBytes4(buffer []byte, index int) ([]byte, error) {
if len(buffer) < index+EncodedBytes4Size {
return nil, ErrInputDataTooSmall
}
// fixed-size byte values are zero-padded on the right side.
return buffer[index : index+Bytes4DataReadSize], nil
}

// ReadBytes8 reads a 8 byte slice from the buffer at index
func ReadBytes8(buffer []byte, index int) ([]byte, error) {
if len(buffer) < index+EncodedBytes8Size {
return nil, ErrInputDataTooSmall
}
// fixed-size byte values are zero-padded on the right side.
return buffer[index : index+Bytes8DataReadSize], nil
}

// ReadBytes32 reads a 32 byte slice from the buffer at index
func ReadBytes32(buffer []byte, index int) ([]byte, error) {
if len(buffer) < index+Bytes32DataReadSize {
return nil, ErrInputDataTooSmall
}
return buffer[index : index+Bytes32DataReadSize], nil
}

// EncodeBytes32 encodes data into a bytes 32
func EncodeBytes32(data []byte, buffer []byte, index int) error {
if len(data) > EncodedBytes32Size {
return ErrDataTooLarge
}
if len(buffer) < index+EncodedBytes32Size {
return ErrBufferTooSmall
}
copy(buffer[index:index+EncodedBytes32Size],
gethCommon.RightPadBytes(data, EncodedBytes32Size),
)
return nil
}

// ReadBytes reads a variable length bytes from the buffer
func ReadBytes(buffer []byte, index int) ([]byte, error) {
if len(buffer) < index+EncodedUint64Size {
return nil, ErrInputDataTooSmall
}
// reading offset (we read into uint64) and adjust index
offset, err := ReadUint64(buffer, index)
if err != nil {
return nil, err
}
index = int(offset)
if len(buffer) < index+EncodedUint64Size {
return nil, ErrInputDataTooSmall
}
// reading length of byte slice
length, err := ReadUint64(buffer, index)
if err != nil {
return nil, err
}
index += EncodedUint64Size
if len(buffer) < index+int(length) {
return nil, ErrInputDataTooSmall
}
return buffer[index : index+int(length)], nil
}

// SizeNeededForBytesEncoding computes the number of bytes needed for bytes encoding
func SizeNeededForBytesEncoding(data []byte) int {
if len(data) == 0 {
return EncodedUint64Size + EncodedUint64Size + FixedSizeUnitDataReadSize
}
paddedSize := (len(data) / FixedSizeUnitDataReadSize)
if len(data)%FixedSizeUnitDataReadSize != 0 {
paddedSize += 1
}
return EncodedUint64Size + EncodedUint64Size + paddedSize*FixedSizeUnitDataReadSize
}

// EncodeBytes encodes the data into the buffer at index and append payload to the
// end of buffer
func EncodeBytes(data []byte, buffer []byte, headerIndex, payloadIndex int) error {
//// updating offset
if len(buffer) < headerIndex+EncodedUint64Size {
return ErrBufferTooSmall
}
dataSize := len(data)
// compute padded data size
paddedSize := (dataSize / FixedSizeUnitDataReadSize)
if dataSize%FixedSizeUnitDataReadSize != 0 {
paddedSize += FixedSizeUnitDataReadSize
}
if len(buffer) < payloadIndex+EncodedUint64Size+paddedSize {
return ErrBufferTooSmall
}

err := EncodeUint64(uint64(payloadIndex), buffer, headerIndex)
if err != nil {
return err
}

//// updating payload
// padding data
if dataSize%FixedSizeUnitDataReadSize != 0 {
data = gethCommon.RightPadBytes(data, paddedSize)
}

// adding length
err = EncodeUint64(uint64(dataSize), buffer, payloadIndex)
if err != nil {
return err
}
payloadIndex += EncodedUint64Size
// adding data
copy(buffer[payloadIndex:payloadIndex+len(data)], data)
return nil
}
131 changes: 131 additions & 0 deletions fvm/evm/precompiles/abi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package precompiles_test

import (
"encoding/hex"
"math/big"
"testing"

gethCommon "github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"

"github.com/onflow/flow-go/fvm/evm/precompiles"
)

func TestABIEncodingDecodingFunctions(t *testing.T) {
t.Parallel()

t.Run("test address", func(t *testing.T) {
encodedAddress, err := hex.DecodeString("000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564")
require.NoError(t, err)
addr, err := precompiles.ReadAddress(encodedAddress, 0)
require.NoError(t, err)
expectedAddress := gethCommon.HexToAddress("e592427a0aece92de3edee1f18e0157c05861564")
require.Equal(t, expectedAddress, addr)
reEncoded := make([]byte, precompiles.EncodedAddressSize)
err = precompiles.EncodeAddress(addr, reEncoded, 0)
require.NoError(t, err)
require.Equal(t, encodedAddress, reEncoded)
})

t.Run("test boolean", func(t *testing.T) {
encodedBool, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000001")
require.NoError(t, err)
ret, err := precompiles.ReadBool(encodedBool, 0)
require.NoError(t, err)
require.True(t, ret)
reEncoded := make([]byte, precompiles.EncodedBoolSize)
err = precompiles.EncodeBool(ret, reEncoded, 0)
require.NoError(t, err)
require.Equal(t, encodedBool, reEncoded)
})

t.Run("test uint64", func(t *testing.T) {
encodedUint64, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000046")
require.NoError(t, err)
ret, err := precompiles.ReadUint64(encodedUint64, 0)
require.NoError(t, err)
expectedUint64 := uint64(70)
require.Equal(t, expectedUint64, ret)
reEncoded := make([]byte, precompiles.EncodedUint64Size)
err = precompiles.EncodeUint64(ret, reEncoded, 0)
require.NoError(t, err)
require.Equal(t, encodedUint64, reEncoded)

})

t.Run("test read uint256", func(t *testing.T) {
encodedUint256, err := hex.DecodeString("1000000000000000000000000000000000000000000000000000000000000046")
require.NoError(t, err)
ret, err := precompiles.ReadUint256(encodedUint256, 0)
require.NoError(t, err)
expectedValue, success := new(big.Int).SetString("7237005577332262213973186563042994240829374041602535252466099000494570602566", 10)
require.True(t, success)
require.Equal(t, expectedValue, ret)
})

t.Run("test fixed size bytes", func(t *testing.T) {
encodedFixedSizeBytes, err := hex.DecodeString("abcdef1200000000000000000000000000000000000000000000000000000000")
require.NoError(t, err)
ret, err := precompiles.ReadBytes4(encodedFixedSizeBytes, 0)
require.NoError(t, err)
require.Equal(t, encodedFixedSizeBytes[0:4], ret)

ret, err = precompiles.ReadBytes8(encodedFixedSizeBytes, 0)
require.NoError(t, err)
require.Equal(t, encodedFixedSizeBytes[0:8], ret)

ret, err = precompiles.ReadBytes32(encodedFixedSizeBytes, 0)
require.NoError(t, err)
require.Equal(t, encodedFixedSizeBytes[0:32], ret)

reEncoded := make([]byte, precompiles.EncodedBytes32Size)
err = precompiles.EncodeBytes32(ret, reEncoded, 0)
require.NoError(t, err)
require.Equal(t, encodedFixedSizeBytes, reEncoded)
})

t.Run("test read bytes (variable size)", func(t *testing.T) {
encodedData, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000")
require.NoError(t, err)

ret, err := precompiles.ReadBytes(encodedData, 0)
require.NoError(t, err)
expectedData, err := hex.DecodeString("48656c6c6f20576f726c64")
require.NoError(t, err)
require.Equal(t, expectedData, ret)

bufferSize := precompiles.SizeNeededForBytesEncoding(expectedData)
buffer := make([]byte, bufferSize)
err = precompiles.EncodeBytes(expectedData, buffer, 0, precompiles.EncodedUint64Size)
require.NoError(t, err)
require.Equal(t, encodedData, buffer)
})

t.Run("test size needed for encoding bytes", func(t *testing.T) {
// len zero
data := []byte{}
ret := precompiles.SizeNeededForBytesEncoding(data)
offsetAndLenEncodingSize := precompiles.EncodedUint64Size + precompiles.EncodedUint64Size
expectedSize := offsetAndLenEncodingSize + precompiles.FixedSizeUnitDataReadSize
require.Equal(t, expectedSize, ret)

// data size 1
data = []byte{1}
ret = precompiles.SizeNeededForBytesEncoding(data)
expectedSize = offsetAndLenEncodingSize + precompiles.FixedSizeUnitDataReadSize
require.Equal(t, expectedSize, ret)

// data size 32
data = make([]byte, 32)
ret = precompiles.SizeNeededForBytesEncoding(data)
expectedSize = offsetAndLenEncodingSize + precompiles.FixedSizeUnitDataReadSize
require.Equal(t, expectedSize, ret)

// data size 33
data = make([]byte, 33)
ret = precompiles.SizeNeededForBytesEncoding(data)
expectedSize = offsetAndLenEncodingSize + precompiles.FixedSizeUnitDataReadSize*2
require.Equal(t, expectedSize, ret)
})

}
Loading

0 comments on commit 1baa345

Please sign in to comment.