Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Flow EVM] COA ownership proof - part 2 #5342

Merged
merged 11 commits into from
Feb 12, 2024
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
232 changes: 232 additions & 0 deletions fvm/evm/precompiles/abi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
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.
Comment on lines +11 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move up, just above the package declaration

const (
FixedSizeUnitDataReadSize = 32
Bytes4DataReadSize = 4
Bytes8DataReadSize = 8
Bytes32DataReadSize = 32
Uint64ByteSize = 8

EncodedBoolSize = FixedSizeUnitDataReadSize
EncodedAddressSize = FixedSizeUnitDataReadSize
EncodedBytes32Size = FixedSizeUnitDataReadSize
EncodedBytes4Size = FixedSizeUnitDataReadSize
EncodedBytes8Size = FixedSizeUnitDataReadSize
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
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) {
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
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
}
headerIndex += EncodedUint64Size

Check failure on line 215 in fvm/evm/precompiles/abi.go

View workflow job for this annotation

GitHub Actions / Lint (./)

ineffectual assignment to headerIndex (ineffassign)

//// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to change the package name? why I'm asking because if not it seems we could keep encode/decode functions unexported.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't want people to use these methods yet, maybe we move them into abi package in the future.


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) {
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
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
Loading