From ef0443ba2b0de29267405dc503bf64c5e752afa0 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 16 Sep 2024 17:14:26 +0400 Subject: [PATCH 01/12] feat(eth/contracts): introduce ScaleCodec.sol library --- ethexe/contracts/src/ScaleCodec.sol | 479 ++++++++++++++++++ ethexe/contracts/test/ScaleCodec/Array.t.sol | 53 ++ ethexe/contracts/test/ScaleCodec/Bool.t.sol | 23 + ethexe/contracts/test/ScaleCodec/Bytes.t.sol | 14 + ethexe/contracts/test/ScaleCodec/Int.t.sol | 51 ++ .../contracts/test/ScaleCodec/Optional.t.sol | 51 ++ ethexe/contracts/test/ScaleCodec/Result.t.sol | 58 +++ ethexe/contracts/test/ScaleCodec/String.t.sol | 15 + ethexe/contracts/test/ScaleCodec/Struct.t.sol | 42 ++ ethexe/contracts/test/ScaleCodec/Uint.t.sol | 63 +++ ethexe/contracts/test/ScaleCodec/Vec.t.sol | 100 ++++ 11 files changed, 949 insertions(+) create mode 100644 ethexe/contracts/src/ScaleCodec.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Array.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Bool.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Bytes.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Int.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Optional.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Result.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/String.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Struct.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Uint.t.sol create mode 100644 ethexe/contracts/test/ScaleCodec/Vec.t.sol diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol new file mode 100644 index 00000000000..e8b9ae9d592 --- /dev/null +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -0,0 +1,479 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +function bytesToUint(bytes memory data, uint256 byte_length) pure returns (uint256) { + uint256 result = 0; + + for (uint256 i = 0; i < byte_length; i++) { + result = result | (uint256(uint8(data[i])) << (i * 8)); + } + + return result; +} + +library ScaleCodec { + struct CompactInt { + uint256 value; + uint256 offset; + } + + struct DecodedString { + string value; + uint256 offset; + } + + struct Optional { + bool isSome; + bytes value; + } + + struct Result { + bool isOk; + bool isErr; + bytes value; + } + + function sliceBytes(bytes memory data, uint256 start, uint256 end) public pure returns (bytes memory) { + bytes memory result = new bytes(end - start); + + for (uint256 i = 0; i < end - start; i++) { + result[i] = data[i + start]; + } + + return result; + } + + function concatBytes(bytes[] memory value) public pure returns (bytes memory) { + if (value.length == 1) { + return value[0]; + } + + bytes memory res; + + for (uint256 i = 0; i < value.length; i++) { + res = bytes.concat(res, value[i]); + } + + return res; + } + + function bytes1Tobytes(bytes1 value) public pure returns (bytes memory) { + bytes memory result = new bytes(1); + result[0] = value; + return result; + } + + function bytes2Tobytes(bytes2 value) public pure returns (bytes memory) { + bytes memory result = new bytes(2); + result[0] = value[0]; + result[1] = value[1]; + return result; + } + + function bytes4Tobytes(bytes4 value) public pure returns (bytes memory) { + bytes memory result = new bytes(4); + for (uint256 i = 0; i < 4; i++) { + result[i] = value[i]; + } + return result; + } + + function bytes8Tobytes(bytes8 value) public pure returns (bytes memory) { + bytes memory result = new bytes(8); + for (uint256 i = 0; i < 8; i++) { + result[i] = value[i]; + } + return result; + } + + function bytes16Tobytes(bytes16 value) public pure returns (bytes memory) { + bytes memory result = new bytes(16); + for (uint256 i = 0; i < 16; i++) { + result[i] = value[i]; + } + return result; + } + + function bytes32Tobytes(bytes32 value) public pure returns (bytes memory) { + bytes memory result = new bytes(32); + for (uint256 i = 0; i < 32; i++) { + result[i] = value[i]; + } + return result; + } + + function bytesToBytes32(bytes memory value) public pure returns (bytes32) { + return bytes32(value); + } + + function encodeBool(bool value) public pure returns (bytes memory) { + bytes memory result = new bytes(1); + if (value) { + result[0] = 0x01; + } else { + result[0] = 0x00; + } + return result; + } + + function decodeBool(bytes memory _bytes) public pure returns (bool) { + return _bytes[0] == 0x01; + } + + function encodeUint8(uint8 value) public pure returns (bytes memory) { + bytes memory result = new bytes(1); + result[0] = bytes1(value); + return result; + } + + function decodeUint8(bytes memory _bytes) public pure returns (uint8) { + return uint8(_bytes[0]); + } + + function encodeInt8(int8 value) public pure returns (bytes memory) { + return encodeUint8(uint8(value)); + } + + function decodeInt8(bytes memory _bytes) public pure returns (int8) { + return int8(uint8(_bytes[0])); + } + + function encodeUint16(uint16 value) public pure returns (bytes memory) { + bytes memory result = new bytes(2); + result[0] = bytes2(value)[1]; + result[1] = bytes2(value)[0]; + return result; + } + + function decodeUint16(bytes memory _bytes) public pure returns (uint16) { + return uint16(bytesToUint(_bytes, 2)); + } + + function encodeInt16(int16 value) public pure returns (bytes memory) { + return encodeUint16(uint16(value)); + } + + function decodeInt16(bytes memory _bytes) public pure returns (int16) { + return int16(decodeUint16(_bytes)); + } + + function encodeUint32(uint32 value) public pure returns (bytes memory) { + bytes memory result = new bytes(4); + + bytes4 _value = bytes4(value); + + for (uint8 i = 0; i < 4; i++) { + result[i] = _value[3 - i]; + } + return result; + } + + function decodeUint32(bytes memory _bytes) public pure returns (uint32) { + return uint32(bytesToUint(_bytes, 4)); + } + + function encodeInt32(int32 value) public pure returns (bytes memory) { + return encodeUint32(uint32(value)); + } + + function decodeInt32(bytes memory _bytes) public pure returns (int32) { + return int32(decodeUint32(_bytes)); + } + + function encodeUint64(uint64 value) public pure returns (bytes memory) { + bytes memory result = new bytes(8); + bytes8 _value = bytes8(value); + + for (uint8 i = 0; i < 8; i++) { + result[i] = _value[7 - i]; + } + return result; + } + + function decodeUint64(bytes memory _bytes) public pure returns (uint64) { + return uint64(bytesToUint(_bytes, 8)); + } + + function encodeInt64(int64 value) public pure returns (bytes memory) { + return encodeUint64(uint64(value)); + } + + function decodeInt64(bytes memory _bytes) public pure returns (int64) { + return int64(decodeUint64(_bytes)); + } + + function encodeUint128(uint128 value) public pure returns (bytes memory) { + bytes memory result = new bytes(16); + bytes16 _value = bytes16(value); + for (uint8 i = 0; i < 16; i++) { + result[i] = _value[15 - i]; + } + return result; + } + + function decodeUint128(bytes memory _bytes) public pure returns (uint128) { + return uint128(bytesToUint(_bytes, 16)); + } + + function encodeInt128(int128 value) public pure returns (bytes memory) { + return encodeUint128(uint128(value)); + } + + function decodeInt128(bytes memory _bytes) public pure returns (int128) { + return int128(decodeUint128(_bytes)); + } + + function encodeUint256(uint256 value) public pure returns (bytes memory) { + bytes memory result = new bytes(32); + bytes32 _value = bytes32(value); + for (uint8 i = 0; i < 32; i++) { + result[i] = _value[31 - i]; + } + return result; + } + + function decodeUint256(bytes memory _bytes) public pure returns (uint256) { + return bytesToUint(_bytes, 32); + } + + function encodeCompactInt(uint256 value) public pure returns (bytes memory) { + if (value < 1 << 6) { + uint8 v = uint8(value << 2); + bytes memory result = new bytes(1); + result[0] = bytes1(v); + return result; + } else if (value < 1 << 14) { + uint16 v = uint16((value << 2) + 1); + bytes memory result = new bytes(2); + result[0] = bytes2(v)[1]; + result[1] = bytes2(v)[0]; + return result; + } else if (value < 1 << 30) { + uint32 v = uint32((value << 2) + 2); + bytes memory result = new bytes(4); + result[0] = bytes4(v)[3]; + result[1] = bytes4(v)[2]; + result[2] = bytes4(v)[1]; + result[3] = bytes4(v)[0]; + return result; + } else { + bytes memory _value = new bytes(32); + + bytes32 v = bytes32(uint256(value)); + + for (uint256 i = 0; i < 32; i++) { + _value[i] = v[31 - i]; + } + + uint8 bytes_len = uint8(_value.length); + + while (_value[bytes_len - 1] == 0) { + bytes_len--; + } + + bytes1 _len = bytes1(((bytes_len - 4) << 2) + 3); + + bytes memory result = new bytes(bytes_len + 1); + + result[0] = _len; + + for (uint256 i = 0; i < bytes_len; i++) { + result[i + 1] = _value[i]; + } + + return result; + } + } + + function decodeCompactInt(bytes memory _bytes) public pure returns (CompactInt memory) { + uint8 mode = uint8(_bytes[0]) & 0x03; + + if (mode == 0x00) { + return CompactInt(uint8(_bytes[0]) >> 2, 1); + } else if (mode == 0x01) { + bytes memory _value = new bytes(2); + _value[0] = _bytes[1]; + _value[1] = _bytes[0]; + return CompactInt(uint16(bytes2(_value)) >> 2, 2); + } else if (mode == 0x02) { + bytes memory _value = new bytes(4); + _value[0] = _bytes[3]; + _value[1] = _bytes[2]; + _value[2] = _bytes[1]; + _value[3] = _bytes[0]; + return CompactInt(uint32(bytes4(_value)) >> 2, 4); + } else { + uint8 bytes_len = (uint8(_bytes[0]) >> 2) + 4; + + bytes memory _value = new bytes(bytes_len + 1); + + for (uint256 i = 0; i < bytes_len + 1; i++) { + _value[i] = _bytes[i]; + } + + if (bytes_len <= 8) { + bytes_len = 8; + } else if (bytes_len <= 16) { + bytes_len = 16; + } else if (bytes_len <= 32) { + bytes_len = 32; + } else { + bytes_len = 64; + } + + bytes memory _result = new bytes(bytes_len); + + for (uint256 i = 0; i < bytes_len - _value.length; i++) { + _result[i] = bytes1(0); + } + + for (uint256 i = bytes_len - _value.length + 1; i < bytes_len; i++) { + _result[i] = _value[bytes_len - i]; + } + + if (bytes_len == 8) { + return CompactInt(uint64(bytes8(_result)), 8); + } else if (bytes_len == 16) { + return CompactInt(uint128(bytes16(_result)), 16); + } else { + return CompactInt(uint256(bytes32(_result)), 32); + } + } + } + + function encodeString(string memory value) public pure returns (bytes memory) { + bytes memory result = bytes(value); + bytes memory len = encodeCompactInt(result.length); + + bytes memory res = new bytes(len.length + result.length); + + for (uint256 i = 0; i < len.length; i++) { + res[i] = len[i]; + } + + for (uint256 i = 0; i < result.length; i++) { + res[i + len.length] = result[i]; + } + + return res; + } + + function decodeString(bytes memory _bytes) public pure returns (DecodedString memory) { + CompactInt memory len = decodeCompactInt(_bytes); + bytes memory result = new bytes(len.value); + + for (uint256 i = 0; i < len.value; i++) { + result[i] = _bytes[i + 1]; + } + + return DecodedString(string(result), len.offset + result.length); + } + + function encodeVec(bytes[] memory value) public pure returns (bytes memory) { + bytes memory len = encodeCompactInt(value.length); + uint256 total_len = len.length; + + for (uint256 i = 0; i < value.length; i++) { + total_len += value[i].length; + } + + bytes memory res = new bytes(total_len); + + for (uint256 i = 0; i < len.length; i++) { + res[i] = len[i]; + } + + uint256 offset = len.length; + + for (uint256 i = 0; i < value.length; i++) { + for (uint256 j = 0; j < value[i].length; j++) { + res[offset + j] = value[i][j]; + } + offset += value[i].length; + } + + return res; + } + + function decodeVec(bytes memory _bytes, uint256 item_len, bool unknown_len) public pure returns (bytes[] memory) { + CompactInt memory prefix = decodeCompactInt(_bytes); + bytes[] memory result = new bytes[](prefix.value); + + uint256 offset = prefix.offset; + + bytes memory _value = sliceBytes(_bytes, offset, _bytes.length); + + for (uint256 i = 0; i < prefix.value; i++) { + uint256 item_prefix_len = 0; + if (unknown_len) { + // item_len = decodeCompactInt(value[offset]).len; + CompactInt memory item_prefix = decodeCompactInt(_value); + item_len = item_prefix.value; + item_prefix_len = item_prefix.offset; + } + + bytes memory item = new bytes(item_len + item_prefix_len); + + for (uint256 j = 0; j < item_len + item_prefix_len; j++) { + item[j] = _bytes[offset + j]; + } + + result[i] = item; + offset += item_len + item_prefix_len; + + if (offset >= _bytes.length) { + break; + } + _value = sliceBytes(_bytes, offset, _bytes.length - 1); + } + + return result; + } + + function encodeOptional(Optional memory value) public pure returns (bytes memory) { + if (value.isSome) { + bytes memory result = new bytes(value.value.length + 1); + result[0] = 0x01; + + for (uint256 i = 0; i < value.value.length; i++) { + result[i + 1] = value.value[i]; + } + + return result; + } else { + bytes memory result = new bytes(1); + result[0] = 0x00; + + return result; + } + } + + function decodeOptional(bytes memory _bytes) public pure returns (Optional memory) { + if (_bytes[0] == 0x00) { + return Optional(false, new bytes(0)); + } else { + return Optional(true, sliceBytes(_bytes, 1, _bytes.length)); + } + } + + function encodeResult(Result memory value) public pure returns (bytes memory) { + bytes[] memory result = new bytes[](2); + result[0] = new bytes(1); + result[1] = value.value; + if (value.isOk) { + result[0][0] = 0x00; + } else { + result[0][0] = 0x01; + } + return concatBytes(result); + } + + function decodeResult(bytes memory _bytes) public pure returns (Result memory) { + bytes memory value = sliceBytes(_bytes, 1, _bytes.length); + if (_bytes[0] == 0x00) { + return Result(true, false, value); + } else { + return Result(false, true, value); + } + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Array.t.sol b/ethexe/contracts/test/ScaleCodec/Array.t.sol new file mode 100644 index 00000000000..7254d50f686 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Array.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestArrayScaleCodec is Test { + function encodeArray(string[5] memory _value) internal pure returns (bytes memory) { + bytes[] memory result = new bytes[](5); + for (uint256 i = 0; i < 5; i++) { + result[i] = ScaleCodec.encodeString(_value[i]); + } + + return ScaleCodec.concatBytes(result); + } + + function decodeArray(bytes memory _value) internal pure returns (string[5] memory) { + string[] memory result = new string[](5); + + uint256 offset = 0; + + for (uint256 i = 0; i < 5; i++) { + ScaleCodec.DecodedString memory item = + ScaleCodec.decodeString(ScaleCodec.sliceBytes(_value, offset, _value.length)); + result[i] = item.value; + offset += item.offset; + } + + return [result[0], result[1], result[2], result[3], result[4]]; + } + + function test_arrayEncode() public pure { + string[5] memory _array = ["Gear", "is", "awesome", "and", "cool"]; + + bytes memory encoded = hex"10476561720869731c617765736f6d650c616e6410636f6f6c"; + + assertEq(encodeArray(_array), encoded); + } + + function test_arrayDecode() public pure { + string[5] memory _array = ["Gear", "is", "awesome", "and", "cool"]; + + bytes memory encoded = hex"10476561720869731c617765736f6d650c616e6410636f6f6c"; + + string[5] memory decoded = decodeArray(encoded); + + assertEq(decoded[0], _array[0]); + assertEq(decoded[1], _array[1]); + assertEq(decoded[2], _array[2]); + assertEq(decoded[3], _array[3]); + assertEq(decoded[4], _array[4]); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Bool.t.sol b/ethexe/contracts/test/ScaleCodec/Bool.t.sol new file mode 100644 index 00000000000..90cc4cc9a7a --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Bool.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestBoolScaleCodec is Test { + function test_boolEncodeTrue() public pure { + assertEq(ScaleCodec.encodeBool(true), hex"01"); + } + + function test_boolEncodeFalse() public pure { + assertEq(ScaleCodec.encodeBool(false), hex"00"); + } + + function test_boolDecodeTrue() public pure { + assertEq(ScaleCodec.decodeBool(hex"01"), true); + } + + function test_boolDecodeFalse() public pure { + assertEq(ScaleCodec.decodeBool(hex"00"), false); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol new file mode 100644 index 00000000000..377c91ad680 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestBytesScaleCodec is Test { + function test_bytesToBytes32() public pure { + assertEq( + ScaleCodec.bytesToBytes32(hex"00000000000000000000000000000000000000000000000000000000000000000000"), + hex"0000000000000000000000000000000000000000000000000000000000000000" + ); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Int.t.sol b/ethexe/contracts/test/ScaleCodec/Int.t.sol new file mode 100644 index 00000000000..c64bbc98d24 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestIntScaleCodec is Test { + function test_int8EncodeDecode() public pure { + assertEq(ScaleCodec.encodeInt8(int8(69)), hex"45"); + assertEq(ScaleCodec.decodeInt8(hex"45"), int8(69)); + + assertEq(ScaleCodec.encodeInt8(int8(-69)), hex"bb"); + assertEq(ScaleCodec.decodeInt8(hex"bb"), int8(-69)); + } + + function test_int16EncodeDecode() public pure { + assertEq(ScaleCodec.encodeInt16(int16(42)), hex"2a00"); + assertEq(ScaleCodec.decodeInt16(hex"2a00"), int16(42)); + + assertEq(ScaleCodec.encodeInt16(int16(-42)), hex"d6ff"); + assertEq(ScaleCodec.decodeInt16(hex"d6ff"), int16(-42)); + } + + function test_int32EncodeDecode() public pure { + assertEq(ScaleCodec.encodeInt32(int32(16777215)), hex"ffffff00"); + assertEq(ScaleCodec.decodeInt32(hex"ffffff00"), int32(16777215)); + + assertEq(ScaleCodec.encodeInt32(int32(-16777215)), hex"010000ff"); + assertEq(ScaleCodec.decodeInt32(hex"010000ff"), int32(-16777215)); + } + + function test_int64EncodeDecode18446744073709() public pure { + assertEq(ScaleCodec.encodeInt64(int64(18446744073709)), hex"edb5a0f7c6100000"); + assertEq(ScaleCodec.decodeInt64(hex"edb5a0f7c6100000"), int64(18446744073709)); + + assertEq(ScaleCodec.encodeInt64(int64(-18446744073709)), hex"134a5f0839efffff"); + assertEq(ScaleCodec.decodeInt64(hex"134a5f0839efffff"), int64(-18446744073709)); + } + + function test_int128EncodeDecode() public pure { + assertEq(ScaleCodec.encodeInt128(int128(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b04000000"); + assertEq(ScaleCodec.decodeInt128(hex"4754408bb92ca5b509fa824b04000000"), int128(340282366920938463463374607431)); + + assertEq( + ScaleCodec.encodeInt128(int128(-340282366920938463463374607431)), hex"b9abbf7446d35a4af6057db4fbffffff" + ); + assertEq( + ScaleCodec.decodeInt128(hex"b9abbf7446d35a4af6057db4fbffffff"), int128(-340282366920938463463374607431) + ); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Optional.t.sol b/ethexe/contracts/test/ScaleCodec/Optional.t.sol new file mode 100644 index 00000000000..3f58b605491 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Optional.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestOptionalScaleCodec is Test { + struct OptionalString { + bool isSome; + string value; + } + + function encodeOptionalString(OptionalString memory _value) internal pure returns (bytes memory) { + return ScaleCodec.encodeOptional( + ScaleCodec.Optional({ + isSome: _value.isSome, + value: _value.isSome ? ScaleCodec.encodeString(_value.value) : new bytes(0) + }) + ); + } + + function decodeOptionalString(bytes memory _bytes) internal pure returns (OptionalString memory) { + ScaleCodec.Optional memory decoded = ScaleCodec.decodeOptional(_bytes); + + return OptionalString({ + isSome: decoded.isSome, + value: decoded.isSome ? ScaleCodec.decodeString(decoded.value).value : "" + }); + } + + function test_OptionalNoneEncodeDecode() public pure { + OptionalString memory _optional = OptionalString({isSome: false, value: ""}); + + assertEq(encodeOptionalString(_optional), hex"00"); + + OptionalString memory _decoded = decodeOptionalString(hex"00"); + + assertEq(_decoded.isSome, false); + } + + function test_OptionalSomeEncodeDecode() public pure { + OptionalString memory _optional = OptionalString({isSome: true, value: "Gear"}); + + assertEq(encodeOptionalString(_optional), hex"011047656172"); + + OptionalString memory _decoded = decodeOptionalString(hex"011047656172"); + + assertEq(_decoded.isSome, true); + assertEq(_decoded.value, "Gear"); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Result.t.sol b/ethexe/contracts/test/ScaleCodec/Result.t.sol new file mode 100644 index 00000000000..2d463985fa4 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Result.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestResultScaleCodec is Test { + struct ResultStringU8 { + bool isOk; + bool isErr; + string ok; + uint8 err; + } + + function encodeResultStringU8(ResultStringU8 memory _value) internal pure returns (bytes memory) { + if (_value.isOk) { + return ScaleCodec.encodeResult( + ScaleCodec.Result({isOk: true, isErr: false, value: ScaleCodec.encodeString(_value.ok)}) + ); + } else { + return ScaleCodec.encodeResult( + ScaleCodec.Result({isOk: false, isErr: true, value: ScaleCodec.encodeUint8(_value.err)}) + ); + } + } + + function decodeResultStringU8(bytes memory _value) public pure returns (ResultStringU8 memory) { + ScaleCodec.Result memory decoded = ScaleCodec.decodeResult(_value); + + if (decoded.isOk) { + return ResultStringU8({isOk: true, isErr: false, ok: ScaleCodec.decodeString(decoded.value).value, err: 0}); + } else { + return ResultStringU8({isOk: false, isErr: true, ok: "", err: ScaleCodec.decodeUint8(decoded.value)}); + } + } + + function test_ResultOkEncodeDecode() public pure { + ResultStringU8 memory _result = ResultStringU8({isOk: true, isErr: false, ok: "Gear", err: 0}); + + assertEq(encodeResultStringU8(_result), hex"001047656172"); + + ResultStringU8 memory _decoded = decodeResultStringU8(hex"001047656172"); + + assertEq(_decoded.isOk, true); + assertEq(_decoded.ok, "Gear"); + } + + function test_ResultErrEncodeDecode() public pure { + ResultStringU8 memory _result = ResultStringU8({isOk: false, isErr: true, ok: "", err: 1}); + + assertEq(encodeResultStringU8(_result), hex"0101"); + + ResultStringU8 memory _decoded = decodeResultStringU8(hex"0101"); + + assertEq(_decoded.isErr, true); + assertEq(_decoded.err, 1); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/String.t.sol b/ethexe/contracts/test/ScaleCodec/String.t.sol new file mode 100644 index 00000000000..84e1bf197da --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/String.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestStringScaleCodec is Test { + function test_stringEncode() public pure { + assertEq(ScaleCodec.encodeString("hello"), hex"1468656c6c6f"); + } + + function test_stringDecode() public pure { + assertEq(ScaleCodec.decodeString(hex"1468656c6c6f").value, "hello"); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Struct.t.sol b/ethexe/contracts/test/ScaleCodec/Struct.t.sol new file mode 100644 index 00000000000..57a1683f7e2 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestStructScaleCodec is Test { + struct MyStruct { + string name; + uint8 age; + } + + function encodeMyStruct(string memory _name, uint8 _age) internal pure returns (bytes memory) { + MyStruct memory myStruct = MyStruct(_name, _age); + + bytes[] memory encoded_items = new bytes[](2); + + encoded_items[0] = ScaleCodec.encodeString(myStruct.name); + encoded_items[1] = ScaleCodec.encodeUint8(myStruct.age); + + return ScaleCodec.concatBytes(encoded_items); + } + + function decodeMyStruct(bytes memory _value) internal pure returns (MyStruct memory) { + ScaleCodec.DecodedString memory name = ScaleCodec.decodeString(_value); + uint8 age = ScaleCodec.decodeUint8(ScaleCodec.sliceBytes(_value, name.offset, _value.length)); + + return MyStruct(name.value, age); + } + + function test_MyStructEncode() public pure { + MyStruct memory _myStruct = MyStruct({name: "Gear", age: 3}); + + assertEq(encodeMyStruct(_myStruct.name, _myStruct.age), hex"104765617203"); + } + + function test_MyStructEncodeDecode() public pure { + MyStruct memory _decoded = decodeMyStruct(hex"104765617203"); + assertEq(_decoded.name, "Gear"); + assertEq(_decoded.age, 3); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Uint.t.sol b/ethexe/contracts/test/ScaleCodec/Uint.t.sol new file mode 100644 index 00000000000..c4f064a2448 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestUintScaleCodec is Test { + function test_uint8EncodeDecode() public pure { + assertEq(ScaleCodec.encodeUint8(uint8(69)), hex"45"); + assertEq(ScaleCodec.decodeUint8(hex"45"), uint8(69)); + } + + function test_uint16EncodeDecode() public pure { + assertEq(ScaleCodec.encodeUint16(uint16(42)), hex"2a00"); + assertEq(ScaleCodec.decodeUint16(hex"2a00"), uint16(42)); + } + + function test_uint32EncodeDecode() public pure { + assertEq(ScaleCodec.encodeUint32(uint32(16777215)), hex"ffffff00"); + assertEq(ScaleCodec.decodeUint32(hex"ffffff00"), uint32(16777215)); + } + + function test_uint64EncodeDecode() public pure { + assertEq(ScaleCodec.encodeUint64(uint64(18446744073709)), hex"edb5a0f7c6100000"); + assertEq(ScaleCodec.decodeUint64(hex"edb5a0f7c6100000"), uint64(18446744073709)); + } + + function test_uint128EncodeDecode() public pure { + assertEq( + ScaleCodec.encodeUint128(uint128(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b04000000" + ); + assertEq( + ScaleCodec.decodeUint128(hex"4754408bb92ca5b509fa824b04000000"), uint128(340282366920938463463374607431) + ); + } + + function tedt_Uint256EncodeDecode() public pure { + assertEq( + ScaleCodec.encodeUint256(uint256(340282366920938463463374607431)), + hex"0000000000000000000000000000000000000000000000000000000000000080" + ); + assertEq( + ScaleCodec.decodeUint256(hex"0000000000000000000000000000000000000000000000000000000000000080"), + uint256(340282366920938463463374607431) + ); + } + + function test_CompactInt() public pure { + assertEq(ScaleCodec.encodeCompactInt(0), hex"00"); + assertEq(ScaleCodec.encodeCompactInt(1), hex"04"); + assertEq(ScaleCodec.encodeCompactInt(42), hex"a8"); + assertEq(ScaleCodec.encodeCompactInt(69), hex"1501"); + assertEq(ScaleCodec.encodeCompactInt(65535), hex"feff0300"); + assertEq(ScaleCodec.encodeCompactInt(100000000000000), hex"0b00407a10f35a"); + + assertEq(ScaleCodec.decodeCompactInt(hex"00").value, 0); + assertEq(ScaleCodec.decodeCompactInt(hex"04").value, 1); + assertEq(ScaleCodec.decodeCompactInt(hex"a8").value, 42); + assertEq(ScaleCodec.decodeCompactInt(hex"1501").value, 69); + assertEq(ScaleCodec.decodeCompactInt(hex"feff0300").value, 65535); + assertEq(ScaleCodec.decodeCompactInt(hex"0b00407a10f35a").value, 100000000000000); + } +} diff --git a/ethexe/contracts/test/ScaleCodec/Vec.t.sol b/ethexe/contracts/test/ScaleCodec/Vec.t.sol new file mode 100644 index 00000000000..1ba0ddcc349 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.19; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestVecScaleCodec is Test { + function encodeVecU8(uint8[] memory _value) internal pure returns (bytes memory) { + bytes[] memory vec = new bytes[](_value.length); + + for (uint256 i = 0; i < _value.length; i++) { + vec[i] = ScaleCodec.encodeUint8(_value[i]); + } + + return ScaleCodec.encodeVec(vec); + } + + function decodeVecU8(bytes memory _value) internal pure returns (uint8[] memory) { + bytes[] memory vec = ScaleCodec.decodeVec(_value, 1, false); + + uint8[] memory result = new uint8[](vec.length); + + for (uint256 i = 0; i < vec.length; i++) { + result[i] = ScaleCodec.decodeUint8(vec[i]); + } + + return result; + } + + function encodeVecString(string[] memory _value) internal pure returns (bytes memory) { + bytes[] memory vec = new bytes[](_value.length); + + for (uint256 i = 0; i < _value.length; i++) { + vec[i] = ScaleCodec.encodeString(_value[i]); + } + + return ScaleCodec.encodeVec(vec); + } + + function decodeVecString(bytes memory _value) internal pure returns (string[] memory) { + bytes[] memory vec = ScaleCodec.decodeVec(_value, 0, true); + + string[] memory result = new string[](vec.length); + + for (uint256 i = 0; i < vec.length; i++) { + result[i] = ScaleCodec.decodeString(vec[i]).value; + } + + return result; + } + + function encodeVecVecU16(uint16[][] memory _value) public pure returns (bytes memory) { + bytes[] memory vec = new bytes[](_value.length); + + for (uint256 i = 0; i < _value.length; i++) { + bytes[] memory inner_vec = new bytes[](_value[i].length); + for (uint256 j = 0; j < _value[i].length; j++) { + inner_vec[j] = ScaleCodec.encodeUint16(_value[i][j]); + } + vec[i] = ScaleCodec.encodeVec(inner_vec); + } + + return ScaleCodec.encodeVec(vec); + } + + function test_encodeVec() public pure { + uint8[] memory _vecUint8 = new uint8[](3); + _vecUint8[0] = 1; + _vecUint8[1] = 2; + _vecUint8[2] = 3; + assertEq(encodeVecU8(_vecUint8), hex"0c010203"); + + string[] memory _vecString = new string[](2); + _vecString[0] = "hello"; + _vecString[1] = "world"; + assertEq(encodeVecString(_vecString), hex"081468656c6c6f14776f726c64"); + + uint16[][] memory _vecVecUint16 = new uint16[][](2); + _vecVecUint16[0] = new uint16[](3); + _vecVecUint16[0][0] = 1; + _vecVecUint16[0][1] = 2; + _vecVecUint16[0][2] = 3; + _vecVecUint16[1] = new uint16[](3); + _vecVecUint16[1][0] = 100; + _vecVecUint16[1][1] = 200; + _vecVecUint16[1][2] = 300; + assertEq(encodeVecVecU16(_vecVecUint16), hex"080c0100020003000c6400c8002c01"); + } + + function test_decodeVec() public pure { + uint8[] memory _decodedUint8 = decodeVecU8(hex"0c010203"); + assertEq(_decodedUint8[0], 1); + assertEq(_decodedUint8[1], 2); + assertEq(_decodedUint8[2], 3); + + string[] memory _decodedString = decodeVecString(hex"081468656c6c6f14776f726c64"); + assertEq(_decodedString[0], "hello"); + assertEq(_decodedString[1], "world"); + } +} From ce1ef2b9ef170aeb37bb56c8e1dafc0f9fca0464 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Wed, 18 Sep 2024 19:27:11 +0400 Subject: [PATCH 02/12] adjust encoding --- ethexe/contracts/src/ScaleCodec.sol | 374 ++++++++++-------- ethexe/contracts/test/ScaleCodec/Array.t.sol | 5 +- ethexe/contracts/test/ScaleCodec/Bool.t.sol | 4 +- ethexe/contracts/test/ScaleCodec/Int.t.sol | 22 +- .../contracts/test/ScaleCodec/Optional.t.sol | 4 +- ethexe/contracts/test/ScaleCodec/Result.t.sol | 7 +- ethexe/contracts/test/ScaleCodec/String.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Struct.t.sol | 4 +- ethexe/contracts/test/ScaleCodec/Uint.t.sol | 35 +- ethexe/contracts/test/ScaleCodec/Vec.t.sol | 8 +- 10 files changed, 268 insertions(+), 197 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index e8b9ae9d592..8d26ef78e45 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -1,11 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -function bytesToUint(bytes memory data, uint256 byte_length) pure returns (uint256) { +function bytesToUint(bytes memory data, uint256 byte_length, uint256 offset) pure returns (uint256) { uint256 result = 0; - for (uint256 i = 0; i < byte_length; i++) { - result = result | (uint256(uint8(data[i])) << (i * 8)); + assembly { + let data_ptr := add(data, 0x20) + for { let i := offset } lt(i, byte_length) { i := add(i, 1) } { + let byte_value := byte(0, mload(add(add(data_ptr, i), offset))) + result := or(result, shl(mul(i, 8), byte_value)) + } } return result; @@ -116,224 +120,294 @@ library ScaleCodec { return result; } - function decodeBool(bytes memory _bytes) public pure returns (bool) { - return _bytes[0] == 0x01; + function decodeBool(bytes memory _bytes, uint256 offset) public pure returns (bool) { + return _bytes[offset] == 0x01; } function encodeUint8(uint8 value) public pure returns (bytes memory) { bytes memory result = new bytes(1); - result[0] = bytes1(value); + encodeUint8To(value, result, 0); return result; } - function decodeUint8(bytes memory _bytes) public pure returns (uint8) { - return uint8(_bytes[0]); + function encodeUint8To(uint8 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, value) + } + } + + function decodeUint8(bytes memory _bytes, uint256 offset) public pure returns (uint8) { + return uint8(_bytes[offset]); } function encodeInt8(int8 value) public pure returns (bytes memory) { return encodeUint8(uint8(value)); } - function decodeInt8(bytes memory _bytes) public pure returns (int8) { - return int8(uint8(_bytes[0])); + function encodeInt8To(int8 value, bytes memory destination, uint256 offset) public pure { + encodeUint8To(uint8(value), destination, offset); + } + + function decodeInt8(bytes memory _bytes, uint256 offset) public pure returns (int8) { + return int8(uint8(_bytes[offset])); } function encodeUint16(uint16 value) public pure returns (bytes memory) { bytes memory result = new bytes(2); - result[0] = bytes2(value)[1]; - result[1] = bytes2(value)[0]; + encodeUint16To(value, result, 0); return result; } - function decodeUint16(bytes memory _bytes) public pure returns (uint16) { - return uint16(bytesToUint(_bytes, 2)); + function encodeUint16To(uint16 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, and(value, 0xff)) + mstore8(add(dest, 1), shr(0x08, value)) + } + } + + function decodeUint16(bytes memory _bytes, uint256 offset) public pure returns (uint16) { + return uint16(bytesToUint(_bytes, 2, offset)); } function encodeInt16(int16 value) public pure returns (bytes memory) { return encodeUint16(uint16(value)); } - function decodeInt16(bytes memory _bytes) public pure returns (int16) { - return int16(decodeUint16(_bytes)); + function encodeInt16To(int16 value, bytes memory destination, uint256 offset) public pure { + encodeUint16To(uint16(value), destination, offset); + } + + function decodeInt16(bytes memory _bytes, uint256 offset) public pure returns (int16) { + return int16(decodeUint16(_bytes, offset)); } function encodeUint32(uint32 value) public pure returns (bytes memory) { bytes memory result = new bytes(4); + encodeUint32To(value, result, 0); + return result; + } - bytes4 _value = bytes4(value); - - for (uint8 i = 0; i < 4; i++) { - result[i] = _value[3 - i]; + function encodeUint32To(uint32 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, and(value, 0xff)) + mstore8(add(dest, 1), shr(0x08, value)) + mstore8(add(dest, 2), shr(0x10, value)) + mstore8(add(dest, 3), shr(0x18, value)) } - return result; } - function decodeUint32(bytes memory _bytes) public pure returns (uint32) { - return uint32(bytesToUint(_bytes, 4)); + function decodeUint32(bytes memory _bytes, uint256 offset) public pure returns (uint32) { + return uint32(bytesToUint(_bytes, 4, offset)); } function encodeInt32(int32 value) public pure returns (bytes memory) { return encodeUint32(uint32(value)); } - function decodeInt32(bytes memory _bytes) public pure returns (int32) { - return int32(decodeUint32(_bytes)); + function encodeInt32To(int32 value, bytes memory destination, uint256 offset) public pure { + encodeUint32To(uint32(value), destination, offset); + } + + function decodeInt32(bytes memory _bytes, uint256 offset) public pure returns (int32) { + return int32(decodeUint32(_bytes, offset)); } function encodeUint64(uint64 value) public pure returns (bytes memory) { bytes memory result = new bytes(8); - bytes8 _value = bytes8(value); + encodeUint64To(value, result, 0); + return result; + } - for (uint8 i = 0; i < 8; i++) { - result[i] = _value[7 - i]; + function encodeUint64To(uint64 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, and(value, 0xff)) + for { let i := 1 } lt(i, 8) { i := add(i, 1) } { mstore8(add(dest, i), shr(mul(i, 8), value)) } } - return result; } - function decodeUint64(bytes memory _bytes) public pure returns (uint64) { - return uint64(bytesToUint(_bytes, 8)); + function decodeUint64(bytes memory _bytes, uint256 offset) public pure returns (uint64) { + return uint64(bytesToUint(_bytes, 8, offset)); } function encodeInt64(int64 value) public pure returns (bytes memory) { return encodeUint64(uint64(value)); } - function decodeInt64(bytes memory _bytes) public pure returns (int64) { - return int64(decodeUint64(_bytes)); + function encodeInt64To(int64 value, bytes memory destination, uint256 offset) public pure { + encodeUint64To(uint64(value), destination, offset); + } + + function decodeInt64(bytes memory _bytes, uint256 offset) public pure returns (int64) { + return int64(decodeUint64(_bytes, offset)); } function encodeUint128(uint128 value) public pure returns (bytes memory) { bytes memory result = new bytes(16); - bytes16 _value = bytes16(value); - for (uint8 i = 0; i < 16; i++) { - result[i] = _value[15 - i]; - } + encodeUint128To(value, result, 0); return result; } - function decodeUint128(bytes memory _bytes) public pure returns (uint128) { - return uint128(bytesToUint(_bytes, 16)); + function encodeUint128To(uint128 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, and(value, 0xff)) + for { let i := 1 } lt(i, 16) { i := add(i, 1) } { mstore8(add(dest, i), shr(mul(i, 8), value)) } + } + } + + function decodeUint128(bytes memory _bytes, uint256 offset) public pure returns (uint128) { + return uint128(bytesToUint(_bytes, 16, offset)); } function encodeInt128(int128 value) public pure returns (bytes memory) { return encodeUint128(uint128(value)); } - function decodeInt128(bytes memory _bytes) public pure returns (int128) { - return int128(decodeUint128(_bytes)); + function encodeInt128To(int128 value, bytes memory destination, uint256 offset) public pure { + encodeUint128To(uint128(value), destination, offset); + } + + function decodeInt128(bytes memory _bytes, uint256 offset) public pure returns (int128) { + return int128(decodeUint128(_bytes, offset)); } function encodeUint256(uint256 value) public pure returns (bytes memory) { bytes memory result = new bytes(32); - bytes32 _value = bytes32(value); - for (uint8 i = 0; i < 32; i++) { - result[i] = _value[31 - i]; - } + encodeUint256To(value, result, 0); return result; } - function decodeUint256(bytes memory _bytes) public pure returns (uint256) { - return bytesToUint(_bytes, 32); + function encodeUint256To(uint256 value, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + mstore8(dest, and(value, 0xff)) + for { let i := 1 } lt(i, 32) { i := add(i, 1) } { mstore8(add(dest, i), shr(mul(i, 8), value)) } + } + } + + function decodeUint256(bytes memory _bytes, uint256 offset) public pure returns (uint256) { + return bytesToUint(_bytes, 32, offset); } function encodeCompactInt(uint256 value) public pure returns (bytes memory) { + uint8 bytesLen = compactIntLen(value); + bytes memory result = new bytes(bytesLen); + encodeCompactIntTo(value, bytesLen, result, 0); + return result; + } + + function compactIntLen(uint256 value) public pure returns (uint8) { if (value < 1 << 6) { - uint8 v = uint8(value << 2); - bytes memory result = new bytes(1); - result[0] = bytes1(v); - return result; + return 1; } else if (value < 1 << 14) { - uint16 v = uint16((value << 2) + 1); - bytes memory result = new bytes(2); - result[0] = bytes2(v)[1]; - result[1] = bytes2(v)[0]; - return result; + return 2; } else if (value < 1 << 30) { - uint32 v = uint32((value << 2) + 2); - bytes memory result = new bytes(4); - result[0] = bytes4(v)[3]; - result[1] = bytes4(v)[2]; - result[2] = bytes4(v)[1]; - result[3] = bytes4(v)[0]; - return result; + return 4; } else { - bytes memory _value = new bytes(32); - - bytes32 v = bytes32(uint256(value)); - - for (uint256 i = 0; i < 32; i++) { - _value[i] = v[31 - i]; + uint8 bytes_len = 1; + assembly { + let v := value + for {} gt(v, 0) { v := shr(8, v) } { bytes_len := add(bytes_len, 1) } + if gt(bytes_len, 32) { revert(0, 0) } } + return bytes_len; + } + } - uint8 bytes_len = uint8(_value.length); - - while (_value[bytes_len - 1] == 0) { - bytes_len--; + function encodeCompactIntTo(uint256 value, uint8 bytesLen, bytes memory destination, uint256 offset) public pure { + assembly { + let dest := add(add(destination, 0x20), offset) + if lt(value, shl(6, 1)) { mstore8(dest, shl(2, value)) } + if and(lt(value, shl(14, 1)), iszero(lt(value, shl(6, 1)))) { + let v := add(shl(2, value), 1) + mstore8(dest, v) + mstore8(add(dest, 1), shr(8, v)) } - - bytes1 _len = bytes1(((bytes_len - 4) << 2) + 3); - - bytes memory result = new bytes(bytes_len + 1); - - result[0] = _len; - - for (uint256 i = 0; i < bytes_len; i++) { - result[i + 1] = _value[i]; + if and(lt(value, shl(30, 1)), iszero(lt(value, shl(14, 1)))) { + let v := add(shl(2, value), 2) + mstore8(dest, v) + mstore8(add(dest, 1), shr(8, v)) + mstore8(add(dest, 2), shr(16, v)) + mstore8(add(dest, 3), shr(24, v)) + } + if iszero(lt(value, shl(30, 1))) { + let bytes_len := sub(bytesLen, 1) + let first_byte := add(shl(2, sub(bytes_len, 4)), 3) + mstore8(dest, first_byte) + for { let i := 0 } lt(i, bytes_len) { i := add(i, 1) } { + mstore8(add(dest, add(i, 1)), shr(mul(i, 8), value)) + } } - - return result; } } - function decodeCompactInt(bytes memory _bytes) public pure returns (CompactInt memory) { - uint8 mode = uint8(_bytes[0]) & 0x03; + function decodeCompactInt(bytes memory _bytes, uint256 offset) public pure returns (CompactInt memory) { + uint8 mode = uint8(_bytes[offset]) & 0x03; if (mode == 0x00) { - return CompactInt(uint8(_bytes[0]) >> 2, 1); + return CompactInt(uint8(_bytes[offset]) >> 2, 1); } else if (mode == 0x01) { - bytes memory _value = new bytes(2); - _value[0] = _bytes[1]; - _value[1] = _bytes[0]; - return CompactInt(uint16(bytes2(_value)) >> 2, 2); + uint16 _value; + assembly { + let src_ptr := add(add(_bytes, 0x20), offset) + let v := byte(0, mload(add(src_ptr, 1))) + _value := or(_value, shl(8, v)) + v := byte(0, mload(src_ptr)) + _value := or(_value, v) + } + return CompactInt(_value >> 2, 2); } else if (mode == 0x02) { - bytes memory _value = new bytes(4); - _value[0] = _bytes[3]; - _value[1] = _bytes[2]; - _value[2] = _bytes[1]; - _value[3] = _bytes[0]; - return CompactInt(uint32(bytes4(_value)) >> 2, 4); - } else { - uint8 bytes_len = (uint8(_bytes[0]) >> 2) + 4; + uint32 _value; + assembly { + let src_ptr := add(add(_bytes, 0x20), offset) + for { let i := 3 } gt(i, 0) { i := sub(i, 1) } { + let v := byte(0, mload(add(src_ptr, i))) + _value := or(_value, shl(mul(i, 8), v)) + } + let v := byte(0, mload(src_ptr)) + _value := or(_value, v) - bytes memory _value = new bytes(bytes_len + 1); - - for (uint256 i = 0; i < bytes_len + 1; i++) { - _value[i] = _bytes[i]; } + return CompactInt(_value >> 2, 4); + } else { + uint8 bytes_len = (uint8(_bytes[offset]) >> 2) + 4; + + uint8 size = 0; if (bytes_len <= 8) { - bytes_len = 8; + size = 8; } else if (bytes_len <= 16) { - bytes_len = 16; + size = 16; } else if (bytes_len <= 32) { - bytes_len = 32; + size = 32; } else { - bytes_len = 64; + size = 64; } - bytes memory _result = new bytes(bytes_len); - - for (uint256 i = 0; i < bytes_len - _value.length; i++) { - _result[i] = bytes1(0); + bytes memory _result = new bytes(size); + + assembly { + let res := add(_result, 0x20) + let src_ptr := add(add(_bytes, 0x20), offset) + + for { let i := 0 } lt(i, size) { i := add(i, 1) } { + if lt(bytes_len, sub(size, i)) { + mstore8(add(res, i), 0x00) + } + if iszero(lt(bytes_len, sub(size, i))) { + let v := byte(0, mload(add(src_ptr, sub(size, i)))) + mstore8(add(res, i), v) + } + } } - for (uint256 i = bytes_len - _value.length + 1; i < bytes_len; i++) { - _result[i] = _value[bytes_len - i]; - } - - if (bytes_len == 8) { + if (size == 8) { return CompactInt(uint64(bytes8(_result)), 8); - } else if (bytes_len == 16) { + } else if (size == 16) { return CompactInt(uint128(bytes16(_result)), 16); } else { return CompactInt(uint256(bytes32(_result)), 32); @@ -345,28 +419,22 @@ library ScaleCodec { bytes memory result = bytes(value); bytes memory len = encodeCompactInt(result.length); - bytes memory res = new bytes(len.length + result.length); - - for (uint256 i = 0; i < len.length; i++) { - res[i] = len[i]; - } + return bytes.concat(len, result); + } - for (uint256 i = 0; i < result.length; i++) { - res[i + len.length] = result[i]; - } + function decodeString(bytes memory _bytes, uint256 offset) public pure returns (DecodedString memory) { + CompactInt memory len = decodeCompactInt(_bytes, offset); - return res; - } + offset += len.offset; - function decodeString(bytes memory _bytes) public pure returns (DecodedString memory) { - CompactInt memory len = decodeCompactInt(_bytes); bytes memory result = new bytes(len.value); - for (uint256 i = 0; i < len.value; i++) { - result[i] = _bytes[i + 1]; + result[i] = _bytes[i + offset]; } - return DecodedString(string(result), len.offset + result.length); + offset += len.value; + + return DecodedString(string(result), offset); } function encodeVec(bytes[] memory value) public pure returns (bytes memory) { @@ -395,19 +463,20 @@ library ScaleCodec { return res; } - function decodeVec(bytes memory _bytes, uint256 item_len, bool unknown_len) public pure returns (bytes[] memory) { - CompactInt memory prefix = decodeCompactInt(_bytes); + function decodeVec(bytes memory _bytes, uint256 item_len, bool unknown_len, uint256 offset) + public + pure + returns (bytes[] memory) + { + CompactInt memory prefix = decodeCompactInt(_bytes, offset); bytes[] memory result = new bytes[](prefix.value); - uint256 offset = prefix.offset; - - bytes memory _value = sliceBytes(_bytes, offset, _bytes.length); + uint256 _offset = offset + prefix.offset; for (uint256 i = 0; i < prefix.value; i++) { uint256 item_prefix_len = 0; if (unknown_len) { - // item_len = decodeCompactInt(value[offset]).len; - CompactInt memory item_prefix = decodeCompactInt(_value); + CompactInt memory item_prefix = decodeCompactInt(_bytes, _offset); item_len = item_prefix.value; item_prefix_len = item_prefix.offset; } @@ -415,16 +484,15 @@ library ScaleCodec { bytes memory item = new bytes(item_len + item_prefix_len); for (uint256 j = 0; j < item_len + item_prefix_len; j++) { - item[j] = _bytes[offset + j]; + item[j] = _bytes[_offset + j]; } result[i] = item; - offset += item_len + item_prefix_len; + _offset += item_len + item_prefix_len; - if (offset >= _bytes.length) { + if (_offset >= _bytes.length) { break; } - _value = sliceBytes(_bytes, offset, _bytes.length - 1); } return result; @@ -448,29 +516,25 @@ library ScaleCodec { } } - function decodeOptional(bytes memory _bytes) public pure returns (Optional memory) { - if (_bytes[0] == 0x00) { + function decodeOptional(bytes memory _bytes, uint256 offset) public pure returns (Optional memory) { + if (_bytes[offset] == 0x00) { return Optional(false, new bytes(0)); } else { - return Optional(true, sliceBytes(_bytes, 1, _bytes.length)); + return Optional(true, sliceBytes(_bytes, 1 + offset, _bytes.length)); } } function encodeResult(Result memory value) public pure returns (bytes memory) { - bytes[] memory result = new bytes[](2); - result[0] = new bytes(1); - result[1] = value.value; if (value.isOk) { - result[0][0] = 0x00; + return bytes.concat(hex"00", value.value); } else { - result[0][0] = 0x01; + return bytes.concat(hex"01", value.value); } - return concatBytes(result); } - function decodeResult(bytes memory _bytes) public pure returns (Result memory) { - bytes memory value = sliceBytes(_bytes, 1, _bytes.length); - if (_bytes[0] == 0x00) { + function decodeResult(bytes memory _bytes, uint256 offset) public pure returns (Result memory) { + bytes memory value = sliceBytes(_bytes, 1 + offset, _bytes.length); + if (_bytes[offset] == 0x00) { return Result(true, false, value); } else { return Result(false, true, value); diff --git a/ethexe/contracts/test/ScaleCodec/Array.t.sol b/ethexe/contracts/test/ScaleCodec/Array.t.sol index 7254d50f686..e95a179095c 100644 --- a/ethexe/contracts/test/ScaleCodec/Array.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Array.t.sol @@ -20,10 +20,9 @@ contract TestArrayScaleCodec is Test { uint256 offset = 0; for (uint256 i = 0; i < 5; i++) { - ScaleCodec.DecodedString memory item = - ScaleCodec.decodeString(ScaleCodec.sliceBytes(_value, offset, _value.length)); + ScaleCodec.DecodedString memory item = ScaleCodec.decodeString(_value, offset); result[i] = item.value; - offset += item.offset; + offset = item.offset; } return [result[0], result[1], result[2], result[3], result[4]]; diff --git a/ethexe/contracts/test/ScaleCodec/Bool.t.sol b/ethexe/contracts/test/ScaleCodec/Bool.t.sol index 90cc4cc9a7a..07bb535353c 100644 --- a/ethexe/contracts/test/ScaleCodec/Bool.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bool.t.sol @@ -14,10 +14,10 @@ contract TestBoolScaleCodec is Test { } function test_boolDecodeTrue() public pure { - assertEq(ScaleCodec.decodeBool(hex"01"), true); + assertEq(ScaleCodec.decodeBool(hex"01", 0), true); } function test_boolDecodeFalse() public pure { - assertEq(ScaleCodec.decodeBool(hex"00"), false); + assertEq(ScaleCodec.decodeBool(hex"00", 0), false); } } diff --git a/ethexe/contracts/test/ScaleCodec/Int.t.sol b/ethexe/contracts/test/ScaleCodec/Int.t.sol index c64bbc98d24..2286cba499c 100644 --- a/ethexe/contracts/test/ScaleCodec/Int.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -7,45 +7,47 @@ import "forge-std/Test.sol"; contract TestIntScaleCodec is Test { function test_int8EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt8(int8(69)), hex"45"); - assertEq(ScaleCodec.decodeInt8(hex"45"), int8(69)); + assertEq(ScaleCodec.decodeInt8(hex"45", 0), int8(69)); assertEq(ScaleCodec.encodeInt8(int8(-69)), hex"bb"); - assertEq(ScaleCodec.decodeInt8(hex"bb"), int8(-69)); + assertEq(ScaleCodec.decodeInt8(hex"bb", 0), int8(-69)); } function test_int16EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt16(int16(42)), hex"2a00"); - assertEq(ScaleCodec.decodeInt16(hex"2a00"), int16(42)); + assertEq(ScaleCodec.decodeInt16(hex"2a00", 0), int16(42)); assertEq(ScaleCodec.encodeInt16(int16(-42)), hex"d6ff"); - assertEq(ScaleCodec.decodeInt16(hex"d6ff"), int16(-42)); + assertEq(ScaleCodec.decodeInt16(hex"d6ff", 0), int16(-42)); } function test_int32EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt32(int32(16777215)), hex"ffffff00"); - assertEq(ScaleCodec.decodeInt32(hex"ffffff00"), int32(16777215)); + assertEq(ScaleCodec.decodeInt32(hex"ffffff00", 0), int32(16777215)); assertEq(ScaleCodec.encodeInt32(int32(-16777215)), hex"010000ff"); - assertEq(ScaleCodec.decodeInt32(hex"010000ff"), int32(-16777215)); + assertEq(ScaleCodec.decodeInt32(hex"010000ff", 0), int32(-16777215)); } function test_int64EncodeDecode18446744073709() public pure { assertEq(ScaleCodec.encodeInt64(int64(18446744073709)), hex"edb5a0f7c6100000"); - assertEq(ScaleCodec.decodeInt64(hex"edb5a0f7c6100000"), int64(18446744073709)); + assertEq(ScaleCodec.decodeInt64(hex"edb5a0f7c6100000", 0), int64(18446744073709)); assertEq(ScaleCodec.encodeInt64(int64(-18446744073709)), hex"134a5f0839efffff"); - assertEq(ScaleCodec.decodeInt64(hex"134a5f0839efffff"), int64(-18446744073709)); + assertEq(ScaleCodec.decodeInt64(hex"134a5f0839efffff", 0), int64(-18446744073709)); } function test_int128EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt128(int128(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b04000000"); - assertEq(ScaleCodec.decodeInt128(hex"4754408bb92ca5b509fa824b04000000"), int128(340282366920938463463374607431)); + assertEq( + ScaleCodec.decodeInt128(hex"4754408bb92ca5b509fa824b04000000", 0), int128(340282366920938463463374607431) + ); assertEq( ScaleCodec.encodeInt128(int128(-340282366920938463463374607431)), hex"b9abbf7446d35a4af6057db4fbffffff" ); assertEq( - ScaleCodec.decodeInt128(hex"b9abbf7446d35a4af6057db4fbffffff"), int128(-340282366920938463463374607431) + ScaleCodec.decodeInt128(hex"b9abbf7446d35a4af6057db4fbffffff", 0), int128(-340282366920938463463374607431) ); } } diff --git a/ethexe/contracts/test/ScaleCodec/Optional.t.sol b/ethexe/contracts/test/ScaleCodec/Optional.t.sol index 3f58b605491..dee8e9a85bf 100644 --- a/ethexe/contracts/test/ScaleCodec/Optional.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Optional.t.sol @@ -20,11 +20,11 @@ contract TestOptionalScaleCodec is Test { } function decodeOptionalString(bytes memory _bytes) internal pure returns (OptionalString memory) { - ScaleCodec.Optional memory decoded = ScaleCodec.decodeOptional(_bytes); + ScaleCodec.Optional memory decoded = ScaleCodec.decodeOptional(_bytes, 0); return OptionalString({ isSome: decoded.isSome, - value: decoded.isSome ? ScaleCodec.decodeString(decoded.value).value : "" + value: decoded.isSome ? ScaleCodec.decodeString(decoded.value, 0).value : "" }); } diff --git a/ethexe/contracts/test/ScaleCodec/Result.t.sol b/ethexe/contracts/test/ScaleCodec/Result.t.sol index 2d463985fa4..2f2cd43e34f 100644 --- a/ethexe/contracts/test/ScaleCodec/Result.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Result.t.sol @@ -25,12 +25,13 @@ contract TestResultScaleCodec is Test { } function decodeResultStringU8(bytes memory _value) public pure returns (ResultStringU8 memory) { - ScaleCodec.Result memory decoded = ScaleCodec.decodeResult(_value); + ScaleCodec.Result memory decoded = ScaleCodec.decodeResult(_value, 0); if (decoded.isOk) { - return ResultStringU8({isOk: true, isErr: false, ok: ScaleCodec.decodeString(decoded.value).value, err: 0}); + return + ResultStringU8({isOk: true, isErr: false, ok: ScaleCodec.decodeString(decoded.value, 0).value, err: 0}); } else { - return ResultStringU8({isOk: false, isErr: true, ok: "", err: ScaleCodec.decodeUint8(decoded.value)}); + return ResultStringU8({isOk: false, isErr: true, ok: "", err: ScaleCodec.decodeUint8(decoded.value, 0)}); } } diff --git a/ethexe/contracts/test/ScaleCodec/String.t.sol b/ethexe/contracts/test/ScaleCodec/String.t.sol index 84e1bf197da..7041cb47547 100644 --- a/ethexe/contracts/test/ScaleCodec/String.t.sol +++ b/ethexe/contracts/test/ScaleCodec/String.t.sol @@ -10,6 +10,6 @@ contract TestStringScaleCodec is Test { } function test_stringDecode() public pure { - assertEq(ScaleCodec.decodeString(hex"1468656c6c6f").value, "hello"); + assertEq(ScaleCodec.decodeString(hex"1468656c6c6f", 0).value, "hello"); } } diff --git a/ethexe/contracts/test/ScaleCodec/Struct.t.sol b/ethexe/contracts/test/ScaleCodec/Struct.t.sol index 57a1683f7e2..ff9fc34ee77 100644 --- a/ethexe/contracts/test/ScaleCodec/Struct.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -22,8 +22,8 @@ contract TestStructScaleCodec is Test { } function decodeMyStruct(bytes memory _value) internal pure returns (MyStruct memory) { - ScaleCodec.DecodedString memory name = ScaleCodec.decodeString(_value); - uint8 age = ScaleCodec.decodeUint8(ScaleCodec.sliceBytes(_value, name.offset, _value.length)); + ScaleCodec.DecodedString memory name = ScaleCodec.decodeString(_value, 0); + uint8 age = ScaleCodec.decodeUint8(_value, name.offset); return MyStruct(name.value, age); } diff --git a/ethexe/contracts/test/ScaleCodec/Uint.t.sol b/ethexe/contracts/test/ScaleCodec/Uint.t.sol index c4f064a2448..d2ace71489a 100644 --- a/ethexe/contracts/test/ScaleCodec/Uint.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -7,22 +7,22 @@ import "forge-std/Test.sol"; contract TestUintScaleCodec is Test { function test_uint8EncodeDecode() public pure { assertEq(ScaleCodec.encodeUint8(uint8(69)), hex"45"); - assertEq(ScaleCodec.decodeUint8(hex"45"), uint8(69)); + assertEq(ScaleCodec.decodeUint8(hex"45", 0), uint8(69)); } function test_uint16EncodeDecode() public pure { assertEq(ScaleCodec.encodeUint16(uint16(42)), hex"2a00"); - assertEq(ScaleCodec.decodeUint16(hex"2a00"), uint16(42)); + assertEq(ScaleCodec.decodeUint16(hex"2a00", 0), uint16(42)); } function test_uint32EncodeDecode() public pure { assertEq(ScaleCodec.encodeUint32(uint32(16777215)), hex"ffffff00"); - assertEq(ScaleCodec.decodeUint32(hex"ffffff00"), uint32(16777215)); + assertEq(ScaleCodec.decodeUint32(hex"ffffff00", 0), uint32(16777215)); } function test_uint64EncodeDecode() public pure { assertEq(ScaleCodec.encodeUint64(uint64(18446744073709)), hex"edb5a0f7c6100000"); - assertEq(ScaleCodec.decodeUint64(hex"edb5a0f7c6100000"), uint64(18446744073709)); + assertEq(ScaleCodec.decodeUint64(hex"edb5a0f7c6100000", 0), uint64(18446744073709)); } function test_uint128EncodeDecode() public pure { @@ -30,34 +30,39 @@ contract TestUintScaleCodec is Test { ScaleCodec.encodeUint128(uint128(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b04000000" ); assertEq( - ScaleCodec.decodeUint128(hex"4754408bb92ca5b509fa824b04000000"), uint128(340282366920938463463374607431) + ScaleCodec.decodeUint128(hex"4754408bb92ca5b509fa824b04000000", 0), uint128(340282366920938463463374607431) ); } - function tedt_Uint256EncodeDecode() public pure { + function test_Uint256Encode() public pure { assertEq( ScaleCodec.encodeUint256(uint256(340282366920938463463374607431)), - hex"0000000000000000000000000000000000000000000000000000000000000080" + hex"4754408bb92ca5b509fa824b0400000000000000000000000000000000000000" ); + } + + function test_Uint256Decode() public pure { assertEq( - ScaleCodec.decodeUint256(hex"0000000000000000000000000000000000000000000000000000000000000080"), + ScaleCodec.decodeUint256(hex"4754408bb92ca5b509fa824b0400000000000000000000000000000000000000", 0), uint256(340282366920938463463374607431) ); } - function test_CompactInt() public pure { + function test_CompactIntEncode() public pure { assertEq(ScaleCodec.encodeCompactInt(0), hex"00"); assertEq(ScaleCodec.encodeCompactInt(1), hex"04"); assertEq(ScaleCodec.encodeCompactInt(42), hex"a8"); assertEq(ScaleCodec.encodeCompactInt(69), hex"1501"); assertEq(ScaleCodec.encodeCompactInt(65535), hex"feff0300"); assertEq(ScaleCodec.encodeCompactInt(100000000000000), hex"0b00407a10f35a"); + } - assertEq(ScaleCodec.decodeCompactInt(hex"00").value, 0); - assertEq(ScaleCodec.decodeCompactInt(hex"04").value, 1); - assertEq(ScaleCodec.decodeCompactInt(hex"a8").value, 42); - assertEq(ScaleCodec.decodeCompactInt(hex"1501").value, 69); - assertEq(ScaleCodec.decodeCompactInt(hex"feff0300").value, 65535); - assertEq(ScaleCodec.decodeCompactInt(hex"0b00407a10f35a").value, 100000000000000); + function test_CompactIntDecode() public pure { + assertEq(ScaleCodec.decodeCompactInt(hex"00", 0).value, 0); + assertEq(ScaleCodec.decodeCompactInt(hex"04", 0).value, 1); + assertEq(ScaleCodec.decodeCompactInt(hex"a8", 0).value, 42); + assertEq(ScaleCodec.decodeCompactInt(hex"1501", 0).value, 69); + assertEq(ScaleCodec.decodeCompactInt(hex"feff0300", 0).value, 65535); + assertEq(ScaleCodec.decodeCompactInt(hex"0b00407a10f35a", 0).value, 100000000000000); } } diff --git a/ethexe/contracts/test/ScaleCodec/Vec.t.sol b/ethexe/contracts/test/ScaleCodec/Vec.t.sol index 1ba0ddcc349..08d8c950878 100644 --- a/ethexe/contracts/test/ScaleCodec/Vec.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -16,12 +16,12 @@ contract TestVecScaleCodec is Test { } function decodeVecU8(bytes memory _value) internal pure returns (uint8[] memory) { - bytes[] memory vec = ScaleCodec.decodeVec(_value, 1, false); + bytes[] memory vec = ScaleCodec.decodeVec(_value, 1, false, 0); uint8[] memory result = new uint8[](vec.length); for (uint256 i = 0; i < vec.length; i++) { - result[i] = ScaleCodec.decodeUint8(vec[i]); + result[i] = ScaleCodec.decodeUint8(vec[i], 0); } return result; @@ -38,12 +38,12 @@ contract TestVecScaleCodec is Test { } function decodeVecString(bytes memory _value) internal pure returns (string[] memory) { - bytes[] memory vec = ScaleCodec.decodeVec(_value, 0, true); + bytes[] memory vec = ScaleCodec.decodeVec(_value, 0, true, 0); string[] memory result = new string[](vec.length); for (uint256 i = 0; i < vec.length; i++) { - result[i] = ScaleCodec.decodeString(vec[i]).value; + result[i] = ScaleCodec.decodeString(vec[i], 0).value; } return result; From c8b509347ce015e085a63d3c4b136078e82f32eb Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 23 Sep 2024 11:41:49 +0400 Subject: [PATCH 03/12] update --- ethexe/contracts/src/ScaleCodec.sol | 109 +++++++----------- ethexe/contracts/test/ScaleCodec/Array.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Bool.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Bytes.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Int.t.sol | 2 +- .../contracts/test/ScaleCodec/Optional.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Result.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/String.t.sol | 13 ++- ethexe/contracts/test/ScaleCodec/Struct.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Uint.t.sol | 104 +++++++++++++++-- ethexe/contracts/test/ScaleCodec/Vec.t.sol | 2 +- 11 files changed, 161 insertions(+), 81 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 8d26ef78e45..0047713c8a2 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -function bytesToUint(bytes memory data, uint256 byte_length, uint256 offset) pure returns (uint256) { +function bytesToUint(bytes memory data, uint256 byteLength, uint256 offset) pure returns (uint256) { uint256 result = 0; assembly { - let data_ptr := add(data, 0x20) - for { let i := offset } lt(i, byte_length) { i := add(i, 1) } { - let byte_value := byte(0, mload(add(add(data_ptr, i), offset))) + let src_ptr := add(add(data, 0x20), offset) + for { let i := 0 } lt(i, byteLength) { i := add(i, 1) } { + let byte_value := byte(0, mload(add(src_ptr, i))) result := or(result, shl(mul(i, 8), byte_value)) } } @@ -271,7 +271,8 @@ library ScaleCodec { encodeUint128To(uint128(value), destination, offset); } - function decodeInt128(bytes memory _bytes, uint256 offset) public pure returns (int128) { + function decodeInt128(bytes memory + _bytes, uint256 offset) public pure returns (int128) { return int128(decodeUint128(_bytes, offset)); } @@ -308,13 +309,13 @@ library ScaleCodec { } else if (value < 1 << 30) { return 4; } else { - uint8 bytes_len = 1; + uint8 bytesLen = 1; assembly { let v := value - for {} gt(v, 0) { v := shr(8, v) } { bytes_len := add(bytes_len, 1) } - if gt(bytes_len, 32) { revert(0, 0) } + for {} gt(v, 0) { v := shr(8, v) } { bytesLen := add(bytesLen, 1) } + if gt(bytesLen, 32) { revert(0, 0) } } - return bytes_len; + return bytesLen; } } @@ -351,70 +352,40 @@ library ScaleCodec { if (mode == 0x00) { return CompactInt(uint8(_bytes[offset]) >> 2, 1); } else if (mode == 0x01) { - uint16 _value; + uint16 value; assembly { let src_ptr := add(add(_bytes, 0x20), offset) let v := byte(0, mload(add(src_ptr, 1))) - _value := or(_value, shl(8, v)) + value := or(value, shl(8, v)) v := byte(0, mload(src_ptr)) - _value := or(_value, v) + value := or(value, v) } - return CompactInt(_value >> 2, 2); + return CompactInt(value >> 2, 2); } else if (mode == 0x02) { - uint32 _value; + uint32 value; assembly { let src_ptr := add(add(_bytes, 0x20), offset) for { let i := 3 } gt(i, 0) { i := sub(i, 1) } { let v := byte(0, mload(add(src_ptr, i))) - _value := or(_value, shl(mul(i, 8), v)) + value := or(value, shl(mul(i, 8), v)) } let v := byte(0, mload(src_ptr)) - _value := or(_value, v) - + value := or(value, v) } - return CompactInt(_value >> 2, 4); + return CompactInt(value >> 2, 4); } else { - uint8 bytes_len = (uint8(_bytes[offset]) >> 2) + 4; - - uint8 size = 0; - - if (bytes_len <= 8) { - size = 8; - } else if (bytes_len <= 16) { - size = 16; - } else if (bytes_len <= 32) { - size = 32; - } else { - size = 64; - } - - bytes memory _result = new bytes(size); - - assembly { - let res := add(_result, 0x20) - let src_ptr := add(add(_bytes, 0x20), offset) + uint8 bytesLen = (uint8(_bytes[offset]) >> 2) + 4; - for { let i := 0 } lt(i, size) { i := add(i, 1) } { - if lt(bytes_len, sub(size, i)) { - mstore8(add(res, i), 0x00) - } - if iszero(lt(bytes_len, sub(size, i))) { - let v := byte(0, mload(add(src_ptr, sub(size, i)))) - mstore8(add(res, i), v) - } - } - } + uint256 value = bytesToUint(_bytes, bytesLen, offset + 1); - if (size == 8) { - return CompactInt(uint64(bytes8(_result)), 8); - } else if (size == 16) { - return CompactInt(uint128(bytes16(_result)), 16); - } else { - return CompactInt(uint256(bytes32(_result)), 32); - } + return CompactInt(value, bytesLen + 1); } } + function stringLen(string memory value) public pure returns (uint256) { + return bytes(value).length; + } + function encodeString(string memory value) public pure returns (bytes memory) { bytes memory result = bytes(value); bytes memory len = encodeCompactInt(result.length); @@ -422,6 +393,14 @@ library ScaleCodec { return bytes.concat(len, result); } + function encodeStringTo(string memory value, bytes memory destination, uint256 offset) public pure { + bytes memory _value = bytes(value); + + for (uint256 i = 0; i < _value.length; i++) { + destination[i + offset] = _value[i]; + } + } + function decodeString(bytes memory _bytes, uint256 offset) public pure returns (DecodedString memory) { CompactInt memory len = decodeCompactInt(_bytes, offset); @@ -439,13 +418,13 @@ library ScaleCodec { function encodeVec(bytes[] memory value) public pure returns (bytes memory) { bytes memory len = encodeCompactInt(value.length); - uint256 total_len = len.length; + uint256 totalLen = len.length; for (uint256 i = 0; i < value.length; i++) { - total_len += value[i].length; + totalLen += value[i].length; } - bytes memory res = new bytes(total_len); + bytes memory res = new bytes(totalLen); for (uint256 i = 0; i < len.length; i++) { res[i] = len[i]; @@ -463,7 +442,7 @@ library ScaleCodec { return res; } - function decodeVec(bytes memory _bytes, uint256 item_len, bool unknown_len, uint256 offset) + function decodeVec(bytes memory _bytes, uint256 itemLen, bool unknownLen, uint256 offset) public pure returns (bytes[] memory) @@ -474,21 +453,21 @@ library ScaleCodec { uint256 _offset = offset + prefix.offset; for (uint256 i = 0; i < prefix.value; i++) { - uint256 item_prefix_len = 0; - if (unknown_len) { + uint256 itemPrefixLen = 0; + if (unknownLen) { CompactInt memory item_prefix = decodeCompactInt(_bytes, _offset); - item_len = item_prefix.value; - item_prefix_len = item_prefix.offset; + itemLen = item_prefix.value; + itemPrefixLen = item_prefix.offset; } - bytes memory item = new bytes(item_len + item_prefix_len); + bytes memory item = new bytes(itemLen + itemPrefixLen); - for (uint256 j = 0; j < item_len + item_prefix_len; j++) { + for (uint256 j = 0; j < itemLen + itemPrefixLen; j++) { item[j] = _bytes[_offset + j]; } result[i] = item; - _offset += item_len + item_prefix_len; + _offset += itemLen + itemPrefixLen; if (_offset >= _bytes.length) { break; diff --git a/ethexe/contracts/test/ScaleCodec/Array.t.sol b/ethexe/contracts/test/ScaleCodec/Array.t.sol index e95a179095c..a1528cac4de 100644 --- a/ethexe/contracts/test/ScaleCodec/Array.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Array.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.25; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Bool.t.sol b/ethexe/contracts/test/ScaleCodec/Bool.t.sol index 07bb535353c..e0300656820 100644 --- a/ethexe/contracts/test/ScaleCodec/Bool.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bool.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol index 377c91ad680..f34014e17d3 100644 --- a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Int.t.sol b/ethexe/contracts/test/ScaleCodec/Int.t.sol index 2286cba499c..42188bd8704 100644 --- a/ethexe/contracts/test/ScaleCodec/Int.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Optional.t.sol b/ethexe/contracts/test/ScaleCodec/Optional.t.sol index dee8e9a85bf..9b45cdbb35f 100644 --- a/ethexe/contracts/test/ScaleCodec/Optional.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Optional.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Result.t.sol b/ethexe/contracts/test/ScaleCodec/Result.t.sol index 2f2cd43e34f..179d3d606e0 100644 --- a/ethexe/contracts/test/ScaleCodec/Result.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Result.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/String.t.sol b/ethexe/contracts/test/ScaleCodec/String.t.sol index 7041cb47547..df24ba692f9 100644 --- a/ethexe/contracts/test/ScaleCodec/String.t.sol +++ b/ethexe/contracts/test/ScaleCodec/String.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; @@ -9,7 +9,18 @@ contract TestStringScaleCodec is Test { assertEq(ScaleCodec.encodeString("hello"), hex"1468656c6c6f"); } + function test_stringEncodeTo() public pure { + bytes memory _bytes = new bytes(6); + _bytes[0] = 0x01; + ScaleCodec.encodeStringTo("hello", _bytes, 1); + assertEq(_bytes, hex"011468656c6c6f"); + } + function test_stringDecode() public pure { assertEq(ScaleCodec.decodeString(hex"1468656c6c6f", 0).value, "hello"); } + + function test_strLen() public pure { + assertEq(ScaleCodec.stringLen("hello"), 5); + } } diff --git a/ethexe/contracts/test/ScaleCodec/Struct.t.sol b/ethexe/contracts/test/ScaleCodec/Struct.t.sol index ff9fc34ee77..d728e837aed 100644 --- a/ethexe/contracts/test/ScaleCodec/Struct.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; diff --git a/ethexe/contracts/test/ScaleCodec/Uint.t.sol b/ethexe/contracts/test/ScaleCodec/Uint.t.sol index d2ace71489a..bba7d51c3b8 100644 --- a/ethexe/contracts/test/ScaleCodec/Uint.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -1,37 +1,89 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; contract TestUintScaleCodec is Test { - function test_uint8EncodeDecode() public pure { + function test_uint8Encode() public pure { assertEq(ScaleCodec.encodeUint8(uint8(69)), hex"45"); + + // Encode to + bytes memory _bytes = new bytes(2); + _bytes[0] = 0x01; + ScaleCodec.encodeUint8To(uint8(69), _bytes, 1); + assertEq(_bytes, hex"0145"); + } + + function test_uint8Decode() public pure { assertEq(ScaleCodec.decodeUint8(hex"45", 0), uint8(69)); + assertEq(ScaleCodec.decodeUint8(hex"0145", 1), uint8(69)); } - function test_uint16EncodeDecode() public pure { + function test_uint16Encode() public pure { assertEq(ScaleCodec.encodeUint16(uint16(42)), hex"2a00"); + + // Encode to + bytes memory _bytes = new bytes(3); + _bytes[0] = 0x01; + ScaleCodec.encodeUint16To(uint16(42), _bytes, 1); + assertEq(_bytes, hex"012a00"); + } + + function test_uint16Decode() public pure { assertEq(ScaleCodec.decodeUint16(hex"2a00", 0), uint16(42)); + assertEq(ScaleCodec.decodeUint16(hex"002a00", 1), uint16(42)); } - function test_uint32EncodeDecode() public pure { + function test_uint32Encode() public pure { assertEq(ScaleCodec.encodeUint32(uint32(16777215)), hex"ffffff00"); + + // Encode to + bytes memory _bytes = new bytes(5); + _bytes[0] = 0x01; + ScaleCodec.encodeUint32To(uint32(16777215), _bytes, 1); + assertEq(_bytes, hex"01ffffff00"); + } + + function test_uint32Decode() public pure { assertEq(ScaleCodec.decodeUint32(hex"ffffff00", 0), uint32(16777215)); + assertEq(ScaleCodec.decodeUint32(hex"00ffffff00", 1), uint32(16777215)); } - function test_uint64EncodeDecode() public pure { + function test_uint64Encode() public pure { assertEq(ScaleCodec.encodeUint64(uint64(18446744073709)), hex"edb5a0f7c6100000"); + + // Encode to + bytes memory _bytes = new bytes(9); + _bytes[0] = 0x01; + ScaleCodec.encodeUint64To(uint64(18446744073709), _bytes, 1); + assertEq(_bytes, hex"01edb5a0f7c6100000"); + } + + function test_uint64Decode() public pure { assertEq(ScaleCodec.decodeUint64(hex"edb5a0f7c6100000", 0), uint64(18446744073709)); + assertEq(ScaleCodec.decodeUint64(hex"02edb5a0f7c6100000", 1), uint64(18446744073709)); } - function test_uint128EncodeDecode() public pure { + function test_uint128Encode() public pure { assertEq( ScaleCodec.encodeUint128(uint128(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b04000000" ); + + // Encode to + bytes memory _bytes = new bytes(17); + _bytes[0] = 0x01; + ScaleCodec.encodeUint128To(uint128(340282366920938463463374607431), _bytes, 1); + assertEq(_bytes, hex"014754408bb92ca5b509fa824b04000000"); + } + + function test_uint128Decode() public pure { assertEq( ScaleCodec.decodeUint128(hex"4754408bb92ca5b509fa824b04000000", 0), uint128(340282366920938463463374607431) ); + assertEq( + ScaleCodec.decodeUint128(hex"014754408bb92ca5b509fa824b04000000", 1), uint128(340282366920938463463374607431) + ); } function test_Uint256Encode() public pure { @@ -39,6 +91,12 @@ contract TestUintScaleCodec is Test { ScaleCodec.encodeUint256(uint256(340282366920938463463374607431)), hex"4754408bb92ca5b509fa824b0400000000000000000000000000000000000000" ); + + // Encode to + bytes memory _bytes = new bytes(33); + _bytes[0] = 0x01; + ScaleCodec.encodeUint256To(uint256(340282366920938463463374607431), _bytes, 1); + assertEq(_bytes, hex"014754408bb92ca5b509fa824b0400000000000000000000000000000000000000"); } function test_Uint256Decode() public pure { @@ -46,6 +104,10 @@ contract TestUintScaleCodec is Test { ScaleCodec.decodeUint256(hex"4754408bb92ca5b509fa824b0400000000000000000000000000000000000000", 0), uint256(340282366920938463463374607431) ); + assertEq( + ScaleCodec.decodeUint256(hex"014754408bb92ca5b509fa824b0400000000000000000000000000000000000000", 1), + uint256(340282366920938463463374607431) + ); } function test_CompactIntEncode() public pure { @@ -54,15 +116,43 @@ contract TestUintScaleCodec is Test { assertEq(ScaleCodec.encodeCompactInt(42), hex"a8"); assertEq(ScaleCodec.encodeCompactInt(69), hex"1501"); assertEq(ScaleCodec.encodeCompactInt(65535), hex"feff0300"); + assertEq(ScaleCodec.encodeCompactInt(1073741824), hex"0300000040"); + assertEq(ScaleCodec.encodeCompactInt(1000000000000), hex"070010a5d4e8"); assertEq(ScaleCodec.encodeCompactInt(100000000000000), hex"0b00407a10f35a"); } + function test_CompactIntEncodeTo() public pure { + bytes memory result = new bytes(2); + result[0] = 0x01; + ScaleCodec.encodeCompactIntTo(1, 1, result, 1); + assertEq(result, hex"0104"); + } + function test_CompactIntDecode() public pure { assertEq(ScaleCodec.decodeCompactInt(hex"00", 0).value, 0); assertEq(ScaleCodec.decodeCompactInt(hex"04", 0).value, 1); - assertEq(ScaleCodec.decodeCompactInt(hex"a8", 0).value, 42); + assertEq(ScaleCodec.decodeCompactInt(hex"01a8", 1).value, 42); assertEq(ScaleCodec.decodeCompactInt(hex"1501", 0).value, 69); assertEq(ScaleCodec.decodeCompactInt(hex"feff0300", 0).value, 65535); assertEq(ScaleCodec.decodeCompactInt(hex"0b00407a10f35a", 0).value, 100000000000000); + + ScaleCodec.CompactInt memory value = ScaleCodec.decodeCompactInt(hex"010b00407a10f35a", 1); + assertEq(value.value, 100000000000000); + assertEq(value.offset, 7); + + value = ScaleCodec.decodeCompactInt(hex"01070010a5d4e8", 1); + assertEq(value.value, 1000000000000); + assertEq(value.offset, 6); + + value = ScaleCodec.decodeCompactInt(hex"010300000040", 1); + assertEq(value.value, 1073741824); + assertEq(value.offset, 5); + } + + function test_CompactLen() public pure { + assertEq(ScaleCodec.compactIntLen(0), 1); + assertEq(ScaleCodec.compactIntLen(69), 2); + assertEq(ScaleCodec.compactIntLen(665535), 4); + assertEq(ScaleCodec.compactIntLen(100000000000000), 7); } } diff --git a/ethexe/contracts/test/ScaleCodec/Vec.t.sol b/ethexe/contracts/test/ScaleCodec/Vec.t.sol index 08d8c950878..3ff508545c6 100644 --- a/ethexe/contracts/test/ScaleCodec/Vec.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.19; +pragma solidity ^0.8.26; import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; From 60eadbc1554810e5bcfa6561ef004c70665c8892 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 23 Sep 2024 17:37:23 +0400 Subject: [PATCH 04/12] remove encode/decode vectors func --- ethexe/contracts/src/ScaleCodec.sol | 156 +++++++----------- ethexe/contracts/test/ScaleCodec/Int.t.sol | 14 +- ethexe/contracts/test/ScaleCodec/String.t.sol | 10 +- ethexe/contracts/test/ScaleCodec/Uint.t.sol | 5 +- ethexe/contracts/test/ScaleCodec/Vec.t.sol | 132 +++++++-------- 5 files changed, 146 insertions(+), 171 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 0047713c8a2..e8ec7fe99a2 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -112,12 +112,16 @@ library ScaleCodec { function encodeBool(bool value) public pure returns (bytes memory) { bytes memory result = new bytes(1); + encodeBoolTo(value, result, 0); + return result; + } + + function encodeBoolTo(bool value, bytes memory destination, uint256 offset) internal pure { if (value) { - result[0] = 0x01; + destination[offset] = 0x01; } else { - result[0] = 0x00; + destination[offset] = 0x00; } - return result; } function decodeBool(bytes memory _bytes, uint256 offset) public pure returns (bool) { @@ -125,12 +129,12 @@ library ScaleCodec { } function encodeUint8(uint8 value) public pure returns (bytes memory) { - bytes memory result = new bytes(1); - encodeUint8To(value, result, 0); - return result; + bytes memory destination = new bytes(1); + encodeUint8To(value, destination, 0); + return destination; } - function encodeUint8To(uint8 value, bytes memory destination, uint256 offset) public pure { + function encodeUint8To(uint8 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, value) @@ -145,7 +149,7 @@ library ScaleCodec { return encodeUint8(uint8(value)); } - function encodeInt8To(int8 value, bytes memory destination, uint256 offset) public pure { + function encodeInt8To(int8 value, bytes memory destination, uint256 offset) internal pure { encodeUint8To(uint8(value), destination, offset); } @@ -159,7 +163,7 @@ library ScaleCodec { return result; } - function encodeUint16To(uint16 value, bytes memory destination, uint256 offset) public pure { + function encodeUint16To(uint16 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, and(value, 0xff)) @@ -175,7 +179,7 @@ library ScaleCodec { return encodeUint16(uint16(value)); } - function encodeInt16To(int16 value, bytes memory destination, uint256 offset) public pure { + function encodeInt16To(int16 value, bytes memory destination, uint256 offset) internal pure { encodeUint16To(uint16(value), destination, offset); } @@ -184,12 +188,12 @@ library ScaleCodec { } function encodeUint32(uint32 value) public pure returns (bytes memory) { - bytes memory result = new bytes(4); - encodeUint32To(value, result, 0); - return result; + bytes memory destination = new bytes(4); + encodeUint32To(value, destination, 0); + return destination; } - function encodeUint32To(uint32 value, bytes memory destination, uint256 offset) public pure { + function encodeUint32To(uint32 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, and(value, 0xff)) @@ -207,7 +211,7 @@ library ScaleCodec { return encodeUint32(uint32(value)); } - function encodeInt32To(int32 value, bytes memory destination, uint256 offset) public pure { + function encodeInt32To(int32 value, bytes memory destination, uint256 offset) internal pure { encodeUint32To(uint32(value), destination, offset); } @@ -221,7 +225,7 @@ library ScaleCodec { return result; } - function encodeUint64To(uint64 value, bytes memory destination, uint256 offset) public pure { + function encodeUint64To(uint64 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, and(value, 0xff)) @@ -237,7 +241,7 @@ library ScaleCodec { return encodeUint64(uint64(value)); } - function encodeInt64To(int64 value, bytes memory destination, uint256 offset) public pure { + function encodeInt64To(int64 value, bytes memory destination, uint256 offset) internal pure { encodeUint64To(uint64(value), destination, offset); } @@ -251,7 +255,7 @@ library ScaleCodec { return result; } - function encodeUint128To(uint128 value, bytes memory destination, uint256 offset) public pure { + function encodeUint128To(uint128 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, and(value, 0xff)) @@ -267,12 +271,11 @@ library ScaleCodec { return encodeUint128(uint128(value)); } - function encodeInt128To(int128 value, bytes memory destination, uint256 offset) public pure { + function encodeInt128To(int128 value, bytes memory destination, uint256 offset) internal pure { encodeUint128To(uint128(value), destination, offset); } - function decodeInt128(bytes memory - _bytes, uint256 offset) public pure returns (int128) { + function decodeInt128(bytes memory _bytes, uint256 offset) public pure returns (int128) { return int128(decodeUint128(_bytes, offset)); } @@ -282,7 +285,7 @@ library ScaleCodec { return result; } - function encodeUint256To(uint256 value, bytes memory destination, uint256 offset) public pure { + function encodeUint256To(uint256 value, bytes memory destination, uint256 offset) internal pure { assembly { let dest := add(add(destination, 0x20), offset) mstore8(dest, and(value, 0xff)) @@ -301,7 +304,7 @@ library ScaleCodec { return result; } - function compactIntLen(uint256 value) public pure returns (uint8) { + function compactIntLen(uint256 value) public pure returns (uint8 length) { if (value < 1 << 6) { return 1; } else if (value < 1 << 14) { @@ -319,7 +322,10 @@ library ScaleCodec { } } - function encodeCompactIntTo(uint256 value, uint8 bytesLen, bytes memory destination, uint256 offset) public pure { + function encodeCompactIntTo(uint256 value, uint8 bytesLen, bytes memory destination, uint256 offset) + internal + pure + { assembly { let dest := add(add(destination, 0x20), offset) if lt(value, shl(6, 1)) { mstore8(dest, shl(2, value)) } @@ -358,9 +364,9 @@ library ScaleCodec { let v := byte(0, mload(add(src_ptr, 1))) value := or(value, shl(8, v)) v := byte(0, mload(src_ptr)) - value := or(value, v) + value := shr(2, or(value, v)) } - return CompactInt(value >> 2, 2); + return CompactInt(value, 2); } else if (mode == 0x02) { uint32 value; assembly { @@ -370,9 +376,9 @@ library ScaleCodec { value := or(value, shl(mul(i, 8), v)) } let v := byte(0, mload(src_ptr)) - value := or(value, v) + value := shr(2, or(value, v)) } - return CompactInt(value >> 2, 4); + return CompactInt(value, 4); } else { uint8 bytesLen = (uint8(_bytes[offset]) >> 2) + 4; @@ -382,8 +388,10 @@ library ScaleCodec { } } - function stringLen(string memory value) public pure returns (uint256) { - return bytes(value).length; + function stringLen(string memory value) public pure returns (uint256 length) { + assembly { + length := mload(value) + } } function encodeString(string memory value) public pure returns (bytes memory) { @@ -393,11 +401,17 @@ library ScaleCodec { return bytes.concat(len, result); } - function encodeStringTo(string memory value, bytes memory destination, uint256 offset) public pure { - bytes memory _value = bytes(value); - - for (uint256 i = 0; i < _value.length; i++) { - destination[i + offset] = _value[i]; + function encodeStringTo(string memory value, uint256 strLen, bytes memory destination, uint256 offset) + internal + pure + { + assembly { + let src_ptr := add(value, 0x20) + let dest_ptr := add(add(destination, 0x20), offset) + for { let i := 0 } lt(i, strLen) { i := add(i, 1) } { + let v := mload(add(src_ptr, i)) + mstore(add(dest_ptr, i), v) + } } } @@ -407,74 +421,20 @@ library ScaleCodec { offset += len.offset; bytes memory result = new bytes(len.value); - for (uint256 i = 0; i < len.value; i++) { - result[i] = _bytes[i + offset]; - } - offset += len.value; - - return DecodedString(string(result), offset); - } - - function encodeVec(bytes[] memory value) public pure returns (bytes memory) { - bytes memory len = encodeCompactInt(value.length); - uint256 totalLen = len.length; - - for (uint256 i = 0; i < value.length; i++) { - totalLen += value[i].length; - } - - bytes memory res = new bytes(totalLen); - - for (uint256 i = 0; i < len.length; i++) { - res[i] = len[i]; - } - - uint256 offset = len.length; - - for (uint256 i = 0; i < value.length; i++) { - for (uint256 j = 0; j < value[i].length; j++) { - res[offset + j] = value[i][j]; + assembly { + let src_ptr := add(add(_bytes, 0x20), offset) + let dest_ptr := add(result, 0x20) + let len_bytes := mload(len) + for { let i := 0 } lt(i, len_bytes) { i := add(i, 1) } { + let v := mload(add(src_ptr, i)) + mstore(add(dest_ptr, i), v) } - offset += value[i].length; } - return res; - } - - function decodeVec(bytes memory _bytes, uint256 itemLen, bool unknownLen, uint256 offset) - public - pure - returns (bytes[] memory) - { - CompactInt memory prefix = decodeCompactInt(_bytes, offset); - bytes[] memory result = new bytes[](prefix.value); - - uint256 _offset = offset + prefix.offset; - - for (uint256 i = 0; i < prefix.value; i++) { - uint256 itemPrefixLen = 0; - if (unknownLen) { - CompactInt memory item_prefix = decodeCompactInt(_bytes, _offset); - itemLen = item_prefix.value; - itemPrefixLen = item_prefix.offset; - } - - bytes memory item = new bytes(itemLen + itemPrefixLen); - - for (uint256 j = 0; j < itemLen + itemPrefixLen; j++) { - item[j] = _bytes[_offset + j]; - } - - result[i] = item; - _offset += itemLen + itemPrefixLen; - - if (_offset >= _bytes.length) { - break; - } - } + offset += len.value; - return result; + return DecodedString(string(result), offset); } function encodeOptional(Optional memory value) public pure returns (bytes memory) { diff --git a/ethexe/contracts/test/ScaleCodec/Int.t.sol b/ethexe/contracts/test/ScaleCodec/Int.t.sol index 42188bd8704..10c462c8f2b 100644 --- a/ethexe/contracts/test/ScaleCodec/Int.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -7,13 +7,19 @@ import "forge-std/Test.sol"; contract TestIntScaleCodec is Test { function test_int8EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt8(int8(69)), hex"45"); - assertEq(ScaleCodec.decodeInt8(hex"45", 0), int8(69)); - assertEq(ScaleCodec.encodeInt8(int8(-69)), hex"bb"); - assertEq(ScaleCodec.decodeInt8(hex"bb", 0), int8(-69)); + bytes memory _bytes = new bytes(2); + _bytes[0] = 0x01; + ScaleCodec.encodeInt8To(int8(-69), _bytes, 1); + assertEq(_bytes, hex"01bb"); + } + + function test_int8Decode() public pure { + assertEq(ScaleCodec.decodeInt8(hex"45", 0), int8(69)); + assertEq(ScaleCodec.decodeInt8(hex"01bb", 1), int8(-69)); } - function test_int16EncodeDecode() public pure { + function test_int16EncodeDecode() public { assertEq(ScaleCodec.encodeInt16(int16(42)), hex"2a00"); assertEq(ScaleCodec.decodeInt16(hex"2a00", 0), int16(42)); diff --git a/ethexe/contracts/test/ScaleCodec/String.t.sol b/ethexe/contracts/test/ScaleCodec/String.t.sol index df24ba692f9..a1d8ad2a65e 100644 --- a/ethexe/contracts/test/ScaleCodec/String.t.sol +++ b/ethexe/contracts/test/ScaleCodec/String.t.sol @@ -10,9 +10,15 @@ contract TestStringScaleCodec is Test { } function test_stringEncodeTo() public pure { - bytes memory _bytes = new bytes(6); + bytes memory _bytes = new bytes(7); _bytes[0] = 0x01; - ScaleCodec.encodeStringTo("hello", _bytes, 1); + + string memory _str = "hello"; + uint256 strLen = ScaleCodec.stringLen(_str); + uint8 strLenPrefixLen = ScaleCodec.compactIntLen(strLen); + ScaleCodec.encodeCompactIntTo(strLen, strLenPrefixLen, _bytes, 1); + + ScaleCodec.encodeStringTo("hello", strLen, _bytes, 1 + strLenPrefixLen); assertEq(_bytes, hex"011468656c6c6f"); } diff --git a/ethexe/contracts/test/ScaleCodec/Uint.t.sol b/ethexe/contracts/test/ScaleCodec/Uint.t.sol index bba7d51c3b8..631792ff1d8 100644 --- a/ethexe/contracts/test/ScaleCodec/Uint.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -20,7 +20,7 @@ contract TestUintScaleCodec is Test { assertEq(ScaleCodec.decodeUint8(hex"0145", 1), uint8(69)); } - function test_uint16Encode() public pure { + function test_uint16Encode() public { assertEq(ScaleCodec.encodeUint16(uint16(42)), hex"2a00"); // Encode to @@ -82,7 +82,8 @@ contract TestUintScaleCodec is Test { ScaleCodec.decodeUint128(hex"4754408bb92ca5b509fa824b04000000", 0), uint128(340282366920938463463374607431) ); assertEq( - ScaleCodec.decodeUint128(hex"014754408bb92ca5b509fa824b04000000", 1), uint128(340282366920938463463374607431) + ScaleCodec.decodeUint128(hex"014754408bb92ca5b509fa824b04000000", 1), + uint128(340282366920938463463374607431) ); } diff --git a/ethexe/contracts/test/ScaleCodec/Vec.t.sol b/ethexe/contracts/test/ScaleCodec/Vec.t.sol index 3ff508545c6..255f71c17af 100644 --- a/ethexe/contracts/test/ScaleCodec/Vec.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -5,96 +5,98 @@ import {ScaleCodec} from "src/ScaleCodec.sol"; import "forge-std/Test.sol"; contract TestVecScaleCodec is Test { - function encodeVecU8(uint8[] memory _value) internal pure returns (bytes memory) { - bytes[] memory vec = new bytes[](_value.length); + function test_encodeVecUint8() public pure { + uint8[] memory data = new uint8[](3); + data[0] = 1; + data[1] = 2; + data[2] = 3; - for (uint256 i = 0; i < _value.length; i++) { - vec[i] = ScaleCodec.encodeUint8(_value[i]); - } + uint8 vecPrefixLen = ScaleCodec.compactIntLen(data.length); + uint256 totalLen = data.length + vecPrefixLen; - return ScaleCodec.encodeVec(vec); - } + bytes memory _bytes = new bytes(totalLen); - function decodeVecU8(bytes memory _value) internal pure returns (uint8[] memory) { - bytes[] memory vec = ScaleCodec.decodeVec(_value, 1, false, 0); + uint256 offset = 0; - uint8[] memory result = new uint8[](vec.length); + ScaleCodec.encodeCompactIntTo(data.length, vecPrefixLen, _bytes, offset); - for (uint256 i = 0; i < vec.length; i++) { - result[i] = ScaleCodec.decodeUint8(vec[i], 0); + offset += vecPrefixLen; + + for (uint256 i = 0; i < data.length; i++) { + ScaleCodec.encodeUint8To(data[i], _bytes, offset); + offset += 1; } - return result; + assertEq(_bytes, hex"0c010203"); } - function encodeVecString(string[] memory _value) internal pure returns (bytes memory) { - bytes[] memory vec = new bytes[](_value.length); + function test_encodeVecString() public pure { + string[] memory data = new string[](2); + data[0] = "hello"; + data[1] = "world"; - for (uint256 i = 0; i < _value.length; i++) { - vec[i] = ScaleCodec.encodeString(_value[i]); + uint256 bytesLen = 0; + + uint8 vecPrefixLen = ScaleCodec.compactIntLen(data.length); + + for (uint256 i = 0; i < data.length; i++) { + uint256 strLen = ScaleCodec.stringLen(data[i]); + bytesLen += strLen; + bytesLen += ScaleCodec.compactIntLen(strLen); } - return ScaleCodec.encodeVec(vec); - } + bytes memory _bytes = new bytes(bytesLen + vecPrefixLen); - function decodeVecString(bytes memory _value) internal pure returns (string[] memory) { - bytes[] memory vec = ScaleCodec.decodeVec(_value, 0, true, 0); + uint256 offset = 0; - string[] memory result = new string[](vec.length); + ScaleCodec.encodeCompactIntTo(data.length, vecPrefixLen, _bytes, offset); + offset += vecPrefixLen; - for (uint256 i = 0; i < vec.length; i++) { - result[i] = ScaleCodec.decodeString(vec[i], 0).value; + for (uint256 i = 0; i < data.length; i++) { + uint256 strLen = ScaleCodec.stringLen(data[i]); + uint256 prefixLen = ScaleCodec.compactIntLen(strLen); + ScaleCodec.encodeCompactIntTo(strLen, vecPrefixLen, _bytes, offset); + offset += prefixLen; + ScaleCodec.encodeStringTo(data[i], strLen, _bytes, offset); + offset += strLen; } - return result; + assertEq(_bytes, hex"081468656c6c6f14776f726c64"); } - function encodeVecVecU16(uint16[][] memory _value) public pure returns (bytes memory) { - bytes[] memory vec = new bytes[](_value.length); + function test_decodeVecUint8() public pure { + bytes memory data = hex"0c010203"; - for (uint256 i = 0; i < _value.length; i++) { - bytes[] memory inner_vec = new bytes[](_value[i].length); - for (uint256 j = 0; j < _value[i].length; j++) { - inner_vec[j] = ScaleCodec.encodeUint16(_value[i][j]); - } - vec[i] = ScaleCodec.encodeVec(inner_vec); + ScaleCodec.CompactInt memory vecLen = ScaleCodec.decodeCompactInt(data, 0); + uint256 offset = vecLen.offset; + + uint8[] memory vec = new uint8[](vecLen.value); + + for (uint256 i = 0; i < vec.length; i++) { + vec[i] = ScaleCodec.decodeUint8(data, offset); + offset++; } - return ScaleCodec.encodeVec(vec); + assertEq(vec[0], 1); + assertEq(vec[1], 2); + assertEq(vec[2], 3); } - function test_encodeVec() public pure { - uint8[] memory _vecUint8 = new uint8[](3); - _vecUint8[0] = 1; - _vecUint8[1] = 2; - _vecUint8[2] = 3; - assertEq(encodeVecU8(_vecUint8), hex"0c010203"); - - string[] memory _vecString = new string[](2); - _vecString[0] = "hello"; - _vecString[1] = "world"; - assertEq(encodeVecString(_vecString), hex"081468656c6c6f14776f726c64"); - - uint16[][] memory _vecVecUint16 = new uint16[][](2); - _vecVecUint16[0] = new uint16[](3); - _vecVecUint16[0][0] = 1; - _vecVecUint16[0][1] = 2; - _vecVecUint16[0][2] = 3; - _vecVecUint16[1] = new uint16[](3); - _vecVecUint16[1][0] = 100; - _vecVecUint16[1][1] = 200; - _vecVecUint16[1][2] = 300; - assertEq(encodeVecVecU16(_vecVecUint16), hex"080c0100020003000c6400c8002c01"); - } + function test_decodeVecString() public pure { + bytes memory data = hex"081468656c6c6f14776f726c64"; - function test_decodeVec() public pure { - uint8[] memory _decodedUint8 = decodeVecU8(hex"0c010203"); - assertEq(_decodedUint8[0], 1); - assertEq(_decodedUint8[1], 2); - assertEq(_decodedUint8[2], 3); + ScaleCodec.CompactInt memory vecLen = ScaleCodec.decodeCompactInt(data, 0); + uint256 offset = vecLen.offset; + + string[] memory vec = new string[](vecLen.value); + + for (uint256 i = 0; i < vec.length; i++) { + ScaleCodec.DecodedString memory str = ScaleCodec.decodeString(data, offset); + offset = str.offset; + vec[i] = str.value; + } - string[] memory _decodedString = decodeVecString(hex"081468656c6c6f14776f726c64"); - assertEq(_decodedString[0], "hello"); - assertEq(_decodedString[1], "world"); + assertEq(vec[0], "hello"); + assertEq(vec[1], "world"); } } From cd1c113f79e257fb1a7f25583f179e8ae766ad0b Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Tue, 24 Sep 2024 14:35:23 +0400 Subject: [PATCH 05/12] insertBytes fn --- ethexe/contracts/src/ScaleCodec.sol | 24 +++++++++++++++++++ ethexe/contracts/test/ScaleCodec/Bytes.t.sol | 14 +++++++++++ ethexe/contracts/test/ScaleCodec/Struct.t.sol | 20 ++++++++++++---- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index e8ec7fe99a2..7925dae1ebd 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -110,6 +110,30 @@ library ScaleCodec { return bytes32(value); } + function insertBytes20To(bytes20 data, bytes memory destination, uint256 offset) internal pure { + assembly { + mstore(add(add(destination, 0x20), offset), data) + } + } + + function insertBytes32To(bytes32 data, bytes memory destination, uint256 offset) internal pure { + assembly { + mstore(add(add(destination, 0x20), offset), data) + } + } + + function insertBytesTo(bytes memory data, bytes memory destination, uint256 offset) internal pure { + assembly { + let data_len := mload(data) + let dest_ptr := add(add(destination, 0x20), offset) + let data_ptr := add(data, 0x20) + for { let i := 0 } lt(i, data_len) { i := add(i, 1) } { + let v := mload(add(data_ptr, i)) + mstore8(add(dest_ptr, i), v) + } + } + } + function encodeBool(bool value) public pure returns (bytes memory) { bytes memory result = new bytes(1); encodeBoolTo(value, result, 0); diff --git a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol index f34014e17d3..b936d9d0f7f 100644 --- a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -11,4 +11,18 @@ contract TestBytesScaleCodec is Test { hex"0000000000000000000000000000000000000000000000000000000000000000" ); } + + function test_insertBytes32() public pure { + bytes memory _bytes = new bytes(33); + _bytes[0] = 0x05; + ScaleCodec.insertBytes32To(hex"0102030401020304010203040102030401020304010203040102030401020304", _bytes, 1); + assertEq(_bytes, hex"050102030401020304010203040102030401020304010203040102030401020304"); + } + + function test_insertBytes20() public pure { + bytes memory _bytes = new bytes(21); + _bytes[0] = 0x05; + ScaleCodec.insertBytes32To(hex"0102030401020304010203040102030401020304", _bytes, 1); + assertEq(_bytes, hex"050102030401020304010203040102030401020304"); + } } diff --git a/ethexe/contracts/test/ScaleCodec/Struct.t.sol b/ethexe/contracts/test/ScaleCodec/Struct.t.sol index d728e837aed..bc7ed5905f6 100644 --- a/ethexe/contracts/test/ScaleCodec/Struct.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -13,12 +13,24 @@ contract TestStructScaleCodec is Test { function encodeMyStruct(string memory _name, uint8 _age) internal pure returns (bytes memory) { MyStruct memory myStruct = MyStruct(_name, _age); - bytes[] memory encoded_items = new bytes[](2); + uint totalLen = 0; - encoded_items[0] = ScaleCodec.encodeString(myStruct.name); - encoded_items[1] = ScaleCodec.encodeUint8(myStruct.age); + uint256 __nameLen = ScaleCodec.stringLen(myStruct.name); + uint8 __namePrefixLen = ScaleCodec.compactIntLen(__nameLen); + totalLen += __nameLen + __namePrefixLen; - return ScaleCodec.concatBytes(encoded_items); + totalLen += 1; + + bytes memory _bytes = new bytes(totalLen); + + uint offset = 0; + ScaleCodec.encodeCompactIntTo(__nameLen, __namePrefixLen, _bytes, 0); + offset += __namePrefixLen; + ScaleCodec.encodeStringTo(myStruct.name, __nameLen, _bytes, offset); + offset += __nameLen; + ScaleCodec.encodeUint8To(myStruct.age, _bytes, offset); + + return _bytes; } function decodeMyStruct(bytes memory _value) internal pure returns (MyStruct memory) { From f76fa17d06ccddc9602ea6bcc5195a06614b5308 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Tue, 24 Sep 2024 15:19:30 +0400 Subject: [PATCH 06/12] fix: bytes to bytes32 --- ethexe/contracts/src/ScaleCodec.sol | 6 ++++-- ethexe/contracts/test/ScaleCodec/Bytes.t.sol | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 7925dae1ebd..cf5ab0063a4 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -106,8 +106,10 @@ library ScaleCodec { return result; } - function bytesToBytes32(bytes memory value) public pure returns (bytes32) { - return bytes32(value); + function bytesToBytes32(bytes memory value, uint256 offset) public pure returns (bytes32 result) { + assembly { + result := mload(add(add(value, 0x20), offset)) + } } function insertBytes20To(bytes20 data, bytes memory destination, uint256 offset) internal pure { diff --git a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol index b936d9d0f7f..f56507eba04 100644 --- a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -7,8 +7,8 @@ import "forge-std/Test.sol"; contract TestBytesScaleCodec is Test { function test_bytesToBytes32() public pure { assertEq( - ScaleCodec.bytesToBytes32(hex"00000000000000000000000000000000000000000000000000000000000000000000"), - hex"0000000000000000000000000000000000000000000000000000000000000000" + ScaleCodec.bytesToBytes32(hex"050102030401020304010203040102030401020304010203040102030401020304", 1), + hex"0102030401020304010203040102030401020304010203040102030401020304" ); } From 176ccdd0416d171c3c34959f3a3a7ae1efadaaa6 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Fri, 27 Sep 2024 12:55:48 +0400 Subject: [PATCH 07/12] fix: insert bytes --- ethexe/contracts/src/ScaleCodec.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index cf5ab0063a4..31afde3e5ff 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -131,7 +131,7 @@ library ScaleCodec { let data_ptr := add(data, 0x20) for { let i := 0 } lt(i, data_len) { i := add(i, 1) } { let v := mload(add(data_ptr, i)) - mstore8(add(dest_ptr, i), v) + mstore(add(dest_ptr, i), v) } } } From d7f0fe6e2d23157b169c622b486861263dfeb705 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 4 Nov 2024 11:11:11 +0100 Subject: [PATCH 08/12] make forge fmt check passed --- ethexe/contracts/test/ScaleCodec/Struct.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ethexe/contracts/test/ScaleCodec/Struct.t.sol b/ethexe/contracts/test/ScaleCodec/Struct.t.sol index bc7ed5905f6..ea2860b7b52 100644 --- a/ethexe/contracts/test/ScaleCodec/Struct.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -13,7 +13,7 @@ contract TestStructScaleCodec is Test { function encodeMyStruct(string memory _name, uint8 _age) internal pure returns (bytes memory) { MyStruct memory myStruct = MyStruct(_name, _age); - uint totalLen = 0; + uint256 totalLen = 0; uint256 __nameLen = ScaleCodec.stringLen(myStruct.name); uint8 __namePrefixLen = ScaleCodec.compactIntLen(__nameLen); @@ -23,7 +23,7 @@ contract TestStructScaleCodec is Test { bytes memory _bytes = new bytes(totalLen); - uint offset = 0; + uint256 offset = 0; ScaleCodec.encodeCompactIntTo(__nameLen, __namePrefixLen, _bytes, 0); offset += __namePrefixLen; ScaleCodec.encodeStringTo(myStruct.name, __nameLen, _bytes, offset); From fada29cc928dbe7dbf482aec443b8c36db2aa7da Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 4 Nov 2024 12:06:31 +0100 Subject: [PATCH 09/12] resolve comments --- ethexe/contracts/src/ScaleCodec.sol | 76 ++++--------------- ethexe/contracts/test/ScaleCodec/Bytes.t.sol | 2 +- ethexe/contracts/test/ScaleCodec/Int.t.sol | 2 +- .../contracts/test/ScaleCodec/Optional.t.sol | 6 +- ethexe/contracts/test/ScaleCodec/Result.t.sol | 20 ++--- ethexe/contracts/test/ScaleCodec/Uint.t.sol | 4 +- ethexe/contracts/test/ScaleCodec/Vec.t.sol | 4 +- 7 files changed, 31 insertions(+), 83 deletions(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 31afde3e5ff..48d9923ccec 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -16,9 +16,9 @@ function bytesToUint(bytes memory data, uint256 byteLength, uint256 offset) pure } library ScaleCodec { - struct CompactInt { + struct CompactUint256 { uint256 value; - uint256 offset; + uint8 offset; } struct DecodedString { @@ -26,14 +26,13 @@ library ScaleCodec { uint256 offset; } - struct Optional { + struct Option { bool isSome; bytes value; } struct Result { bool isOk; - bool isErr; bytes value; } @@ -61,51 +60,6 @@ library ScaleCodec { return res; } - function bytes1Tobytes(bytes1 value) public pure returns (bytes memory) { - bytes memory result = new bytes(1); - result[0] = value; - return result; - } - - function bytes2Tobytes(bytes2 value) public pure returns (bytes memory) { - bytes memory result = new bytes(2); - result[0] = value[0]; - result[1] = value[1]; - return result; - } - - function bytes4Tobytes(bytes4 value) public pure returns (bytes memory) { - bytes memory result = new bytes(4); - for (uint256 i = 0; i < 4; i++) { - result[i] = value[i]; - } - return result; - } - - function bytes8Tobytes(bytes8 value) public pure returns (bytes memory) { - bytes memory result = new bytes(8); - for (uint256 i = 0; i < 8; i++) { - result[i] = value[i]; - } - return result; - } - - function bytes16Tobytes(bytes16 value) public pure returns (bytes memory) { - bytes memory result = new bytes(16); - for (uint256 i = 0; i < 16; i++) { - result[i] = value[i]; - } - return result; - } - - function bytes32Tobytes(bytes32 value) public pure returns (bytes memory) { - bytes memory result = new bytes(32); - for (uint256 i = 0; i < 32; i++) { - result[i] = value[i]; - } - return result; - } - function bytesToBytes32(bytes memory value, uint256 offset) public pure returns (bytes32 result) { assembly { result := mload(add(add(value, 0x20), offset)) @@ -378,11 +332,11 @@ library ScaleCodec { } } - function decodeCompactInt(bytes memory _bytes, uint256 offset) public pure returns (CompactInt memory) { + function decodeCompactInt(bytes memory _bytes, uint256 offset) public pure returns (CompactUint256 memory) { uint8 mode = uint8(_bytes[offset]) & 0x03; if (mode == 0x00) { - return CompactInt(uint8(_bytes[offset]) >> 2, 1); + return CompactUint256(uint8(_bytes[offset]) >> 2, 1); } else if (mode == 0x01) { uint16 value; assembly { @@ -392,7 +346,7 @@ library ScaleCodec { v := byte(0, mload(src_ptr)) value := shr(2, or(value, v)) } - return CompactInt(value, 2); + return CompactUint256(value, 2); } else if (mode == 0x02) { uint32 value; assembly { @@ -404,13 +358,13 @@ library ScaleCodec { let v := byte(0, mload(src_ptr)) value := shr(2, or(value, v)) } - return CompactInt(value, 4); + return CompactUint256(value, 4); } else { uint8 bytesLen = (uint8(_bytes[offset]) >> 2) + 4; uint256 value = bytesToUint(_bytes, bytesLen, offset + 1); - return CompactInt(value, bytesLen + 1); + return CompactUint256(value, bytesLen + 1); } } @@ -442,7 +396,7 @@ library ScaleCodec { } function decodeString(bytes memory _bytes, uint256 offset) public pure returns (DecodedString memory) { - CompactInt memory len = decodeCompactInt(_bytes, offset); + CompactUint256 memory len = decodeCompactInt(_bytes, offset); offset += len.offset; @@ -463,7 +417,7 @@ library ScaleCodec { return DecodedString(string(result), offset); } - function encodeOptional(Optional memory value) public pure returns (bytes memory) { + function encodeOption(Option memory value) public pure returns (bytes memory) { if (value.isSome) { bytes memory result = new bytes(value.value.length + 1); result[0] = 0x01; @@ -481,11 +435,11 @@ library ScaleCodec { } } - function decodeOptional(bytes memory _bytes, uint256 offset) public pure returns (Optional memory) { + function decodeOption(bytes memory _bytes, uint256 offset) public pure returns (Option memory) { if (_bytes[offset] == 0x00) { - return Optional(false, new bytes(0)); + return Option(false, new bytes(0)); } else { - return Optional(true, sliceBytes(_bytes, 1 + offset, _bytes.length)); + return Option(true, sliceBytes(_bytes, 1 + offset, _bytes.length)); } } @@ -500,9 +454,9 @@ library ScaleCodec { function decodeResult(bytes memory _bytes, uint256 offset) public pure returns (Result memory) { bytes memory value = sliceBytes(_bytes, 1 + offset, _bytes.length); if (_bytes[offset] == 0x00) { - return Result(true, false, value); + return Result(true, value); } else { - return Result(false, true, value); + return Result(false, value); } } } diff --git a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol index f56507eba04..2ce14bdde81 100644 --- a/ethexe/contracts/test/ScaleCodec/Bytes.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -22,7 +22,7 @@ contract TestBytesScaleCodec is Test { function test_insertBytes20() public pure { bytes memory _bytes = new bytes(21); _bytes[0] = 0x05; - ScaleCodec.insertBytes32To(hex"0102030401020304010203040102030401020304", _bytes, 1); + ScaleCodec.insertBytes20To(hex"0102030401020304010203040102030401020304", _bytes, 1); assertEq(_bytes, hex"050102030401020304010203040102030401020304"); } } diff --git a/ethexe/contracts/test/ScaleCodec/Int.t.sol b/ethexe/contracts/test/ScaleCodec/Int.t.sol index 10c462c8f2b..03a774271eb 100644 --- a/ethexe/contracts/test/ScaleCodec/Int.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -19,7 +19,7 @@ contract TestIntScaleCodec is Test { assertEq(ScaleCodec.decodeInt8(hex"01bb", 1), int8(-69)); } - function test_int16EncodeDecode() public { + function test_int16EncodeDecode() public pure { assertEq(ScaleCodec.encodeInt16(int16(42)), hex"2a00"); assertEq(ScaleCodec.decodeInt16(hex"2a00", 0), int16(42)); diff --git a/ethexe/contracts/test/ScaleCodec/Optional.t.sol b/ethexe/contracts/test/ScaleCodec/Optional.t.sol index 9b45cdbb35f..8804c9612ca 100644 --- a/ethexe/contracts/test/ScaleCodec/Optional.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Optional.t.sol @@ -11,8 +11,8 @@ contract TestOptionalScaleCodec is Test { } function encodeOptionalString(OptionalString memory _value) internal pure returns (bytes memory) { - return ScaleCodec.encodeOptional( - ScaleCodec.Optional({ + return ScaleCodec.encodeOption( + ScaleCodec.Option({ isSome: _value.isSome, value: _value.isSome ? ScaleCodec.encodeString(_value.value) : new bytes(0) }) @@ -20,7 +20,7 @@ contract TestOptionalScaleCodec is Test { } function decodeOptionalString(bytes memory _bytes) internal pure returns (OptionalString memory) { - ScaleCodec.Optional memory decoded = ScaleCodec.decodeOptional(_bytes, 0); + ScaleCodec.Option memory decoded = ScaleCodec.decodeOption(_bytes, 0); return OptionalString({ isSome: decoded.isSome, diff --git a/ethexe/contracts/test/ScaleCodec/Result.t.sol b/ethexe/contracts/test/ScaleCodec/Result.t.sol index 179d3d606e0..3cebefdf788 100644 --- a/ethexe/contracts/test/ScaleCodec/Result.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Result.t.sol @@ -7,20 +7,15 @@ import "forge-std/Test.sol"; contract TestResultScaleCodec is Test { struct ResultStringU8 { bool isOk; - bool isErr; string ok; uint8 err; } function encodeResultStringU8(ResultStringU8 memory _value) internal pure returns (bytes memory) { if (_value.isOk) { - return ScaleCodec.encodeResult( - ScaleCodec.Result({isOk: true, isErr: false, value: ScaleCodec.encodeString(_value.ok)}) - ); + return ScaleCodec.encodeResult(ScaleCodec.Result({isOk: true, value: ScaleCodec.encodeString(_value.ok)})); } else { - return ScaleCodec.encodeResult( - ScaleCodec.Result({isOk: false, isErr: true, value: ScaleCodec.encodeUint8(_value.err)}) - ); + return ScaleCodec.encodeResult(ScaleCodec.Result({isOk: false, value: ScaleCodec.encodeUint8(_value.err)})); } } @@ -28,15 +23,14 @@ contract TestResultScaleCodec is Test { ScaleCodec.Result memory decoded = ScaleCodec.decodeResult(_value, 0); if (decoded.isOk) { - return - ResultStringU8({isOk: true, isErr: false, ok: ScaleCodec.decodeString(decoded.value, 0).value, err: 0}); + return ResultStringU8({isOk: true, ok: ScaleCodec.decodeString(decoded.value, 0).value, err: 0}); } else { - return ResultStringU8({isOk: false, isErr: true, ok: "", err: ScaleCodec.decodeUint8(decoded.value, 0)}); + return ResultStringU8({isOk: false, ok: "", err: ScaleCodec.decodeUint8(decoded.value, 0)}); } } function test_ResultOkEncodeDecode() public pure { - ResultStringU8 memory _result = ResultStringU8({isOk: true, isErr: false, ok: "Gear", err: 0}); + ResultStringU8 memory _result = ResultStringU8({isOk: true, ok: "Gear", err: 0}); assertEq(encodeResultStringU8(_result), hex"001047656172"); @@ -47,13 +41,13 @@ contract TestResultScaleCodec is Test { } function test_ResultErrEncodeDecode() public pure { - ResultStringU8 memory _result = ResultStringU8({isOk: false, isErr: true, ok: "", err: 1}); + ResultStringU8 memory _result = ResultStringU8({isOk: false, ok: "", err: 1}); assertEq(encodeResultStringU8(_result), hex"0101"); ResultStringU8 memory _decoded = decodeResultStringU8(hex"0101"); - assertEq(_decoded.isErr, true); + assertEq(_decoded.isOk, false); assertEq(_decoded.err, 1); } } diff --git a/ethexe/contracts/test/ScaleCodec/Uint.t.sol b/ethexe/contracts/test/ScaleCodec/Uint.t.sol index 631792ff1d8..5bbabb51ae5 100644 --- a/ethexe/contracts/test/ScaleCodec/Uint.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -20,7 +20,7 @@ contract TestUintScaleCodec is Test { assertEq(ScaleCodec.decodeUint8(hex"0145", 1), uint8(69)); } - function test_uint16Encode() public { + function test_uint16Encode() public pure { assertEq(ScaleCodec.encodeUint16(uint16(42)), hex"2a00"); // Encode to @@ -137,7 +137,7 @@ contract TestUintScaleCodec is Test { assertEq(ScaleCodec.decodeCompactInt(hex"feff0300", 0).value, 65535); assertEq(ScaleCodec.decodeCompactInt(hex"0b00407a10f35a", 0).value, 100000000000000); - ScaleCodec.CompactInt memory value = ScaleCodec.decodeCompactInt(hex"010b00407a10f35a", 1); + ScaleCodec.CompactUint256 memory value = ScaleCodec.decodeCompactInt(hex"010b00407a10f35a", 1); assertEq(value.value, 100000000000000); assertEq(value.offset, 7); diff --git a/ethexe/contracts/test/ScaleCodec/Vec.t.sol b/ethexe/contracts/test/ScaleCodec/Vec.t.sol index 255f71c17af..6743a0af511 100644 --- a/ethexe/contracts/test/ScaleCodec/Vec.t.sol +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -67,7 +67,7 @@ contract TestVecScaleCodec is Test { function test_decodeVecUint8() public pure { bytes memory data = hex"0c010203"; - ScaleCodec.CompactInt memory vecLen = ScaleCodec.decodeCompactInt(data, 0); + ScaleCodec.CompactUint256 memory vecLen = ScaleCodec.decodeCompactInt(data, 0); uint256 offset = vecLen.offset; uint8[] memory vec = new uint8[](vecLen.value); @@ -85,7 +85,7 @@ contract TestVecScaleCodec is Test { function test_decodeVecString() public pure { bytes memory data = hex"081468656c6c6f14776f726c64"; - ScaleCodec.CompactInt memory vecLen = ScaleCodec.decodeCompactInt(data, 0); + ScaleCodec.CompactUint256 memory vecLen = ScaleCodec.decodeCompactInt(data, 0); uint256 offset = vecLen.offset; string[] memory vec = new string[](vecLen.value); From 60a0ddfabe9944082f7f50747e461faeb4b6df66 Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Fri, 8 Nov 2024 13:14:24 +0100 Subject: [PATCH 10/12] add isBytesPrefixedWith fn --- ethexe/contracts/src/ScaleCodec.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 48d9923ccec..b8641b73434 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -60,6 +60,16 @@ library ScaleCodec { return res; } + // This function is supposed to be used for checking if the data is prefixed with the given prefix. + function isBytesPrefixedWith(bytes memory prefix, bytes memory data, uint256 dataOffset) public pure returns (bool) { + for (uint256 i = 0; i < prefix.length; i++) { + if (prefix[i] != data[i + dataOffset]) { + return false; + } + } + return true; + } + function bytesToBytes32(bytes memory value, uint256 offset) public pure returns (bytes32 result) { assembly { result := mload(add(add(value, 0x20), offset)) From 77c9ec93ab09ba6efd60986a306e9b2fa63687bd Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 11 Nov 2024 16:40:10 +0100 Subject: [PATCH 11/12] fmt --- ethexe/contracts/src/ScaleCodec.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index b8641b73434..1754f587881 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -61,7 +61,11 @@ library ScaleCodec { } // This function is supposed to be used for checking if the data is prefixed with the given prefix. - function isBytesPrefixedWith(bytes memory prefix, bytes memory data, uint256 dataOffset) public pure returns (bool) { + function isBytesPrefixedWith(bytes memory prefix, bytes memory data, uint256 dataOffset) + public + pure + returns (bool) + { for (uint256 i = 0; i < prefix.length; i++) { if (prefix[i] != data[i + dataOffset]) { return false; From 66395b780387e30c7fb1d716f12acb48916e04bd Mon Sep 17 00:00:00 2001 From: Dmitry Osipov Date: Mon, 11 Nov 2024 17:49:44 +0100 Subject: [PATCH 12/12] check bytes before decoding --- ethexe/contracts/src/ScaleCodec.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol index 1754f587881..ca7e7d84940 100644 --- a/ethexe/contracts/src/ScaleCodec.sol +++ b/ethexe/contracts/src/ScaleCodec.sol @@ -119,6 +119,7 @@ library ScaleCodec { } function decodeBool(bytes memory _bytes, uint256 offset) public pure returns (bool) { + require(_bytes[offset] == 0x01 || _bytes[offset] == 0x00, "Invalid bool value"); return _bytes[offset] == 0x01; } @@ -450,6 +451,7 @@ library ScaleCodec { } function decodeOption(bytes memory _bytes, uint256 offset) public pure returns (Option memory) { + require(_bytes[offset] == 0x00 || _bytes[offset] == 0x01, "Invalid Option value"); if (_bytes[offset] == 0x00) { return Option(false, new bytes(0)); } else { @@ -466,6 +468,7 @@ library ScaleCodec { } function decodeResult(bytes memory _bytes, uint256 offset) public pure returns (Result memory) { + require(_bytes[offset] == 0x00 || _bytes[offset] == 0x01, "Invalid Result value"); bytes memory value = sliceBytes(_bytes, 1 + offset, _bytes.length); if (_bytes[offset] == 0x00) { return Result(true, value);