-
Notifications
You must be signed in to change notification settings - Fork 180
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
Changes from 9 commits
d909f97
74f60d1
eaf39d9
741008f
50f2855
7e4e216
40c9647
b89ac22
6f3d51c
eeaa7e9
79b5462
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
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 | ||
|
||
//// 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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package precompiles_test | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
}) | ||
|
||
} |
There was a problem hiding this comment.
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