-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5342 from onflow/ramtin/5197-part2-precompile
[Flow EVM] COA ownership proof - part 2
- Loading branch information
Showing
11 changed files
with
781 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
|
||
} |
Oops, something went wrong.