diff --git a/ethexe/contracts/src/ScaleCodec.sol b/ethexe/contracts/src/ScaleCodec.sol new file mode 100644 index 00000000000..ca7e7d84940 --- /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 byteLength, uint256 offset) pure returns (uint256) { + uint256 result = 0; + + assembly { + 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)) + } + } + + return result; +} + +library ScaleCodec { + struct CompactUint256 { + uint256 value; + uint8 offset; + } + + struct DecodedString { + string value; + uint256 offset; + } + + struct Option { + bool isSome; + bytes value; + } + + struct Result { + bool isOk; + 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; + } + + // 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)) + } + } + + 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)) + mstore(add(dest_ptr, i), v) + } + } + } + + 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) { + destination[offset] = 0x01; + } else { + destination[offset] = 0x00; + } + } + + 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; + } + + function encodeUint8(uint8 value) public pure returns (bytes memory) { + bytes memory destination = new bytes(1); + encodeUint8To(value, destination, 0); + return destination; + } + + function encodeUint8To(uint8 value, bytes memory destination, uint256 offset) internal 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 encodeInt8To(int8 value, bytes memory destination, uint256 offset) internal 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); + encodeUint16To(value, result, 0); + return result; + } + + function encodeUint16To(uint16 value, bytes memory destination, uint256 offset) internal 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 encodeInt16To(int16 value, bytes memory destination, uint256 offset) internal 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 destination = new bytes(4); + encodeUint32To(value, destination, 0); + return destination; + } + + function encodeUint32To(uint32 value, bytes memory destination, uint256 offset) internal 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)) + } + } + + 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 encodeInt32To(int32 value, bytes memory destination, uint256 offset) internal 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); + encodeUint64To(value, result, 0); + return result; + } + + function encodeUint64To(uint64 value, bytes memory destination, uint256 offset) internal 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)) } + } + } + + 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 encodeInt64To(int64 value, bytes memory destination, uint256 offset) internal 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); + encodeUint128To(value, result, 0); + return result; + } + + function encodeUint128To(uint128 value, bytes memory destination, uint256 offset) internal 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 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) { + return int128(decodeUint128(_bytes, offset)); + } + + function encodeUint256(uint256 value) public pure returns (bytes memory) { + bytes memory result = new bytes(32); + encodeUint256To(value, result, 0); + return result; + } + + function encodeUint256To(uint256 value, bytes memory destination, uint256 offset) internal 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 length) { + if (value < 1 << 6) { + return 1; + } else if (value < 1 << 14) { + return 2; + } else if (value < 1 << 30) { + return 4; + } else { + uint8 bytesLen = 1; + assembly { + let v := value + for {} gt(v, 0) { v := shr(8, v) } { bytesLen := add(bytesLen, 1) } + if gt(bytesLen, 32) { revert(0, 0) } + } + return bytesLen; + } + } + + 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)) } + 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)) + } + 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)) + } + } + } + } + + function decodeCompactInt(bytes memory _bytes, uint256 offset) public pure returns (CompactUint256 memory) { + uint8 mode = uint8(_bytes[offset]) & 0x03; + + if (mode == 0x00) { + return CompactUint256(uint8(_bytes[offset]) >> 2, 1); + } else if (mode == 0x01) { + 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 := shr(2, or(value, v)) + } + return CompactUint256(value, 2); + } else if (mode == 0x02) { + 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 := shr(2, or(value, v)) + } + return CompactUint256(value, 4); + } else { + uint8 bytesLen = (uint8(_bytes[offset]) >> 2) + 4; + + uint256 value = bytesToUint(_bytes, bytesLen, offset + 1); + + return CompactUint256(value, bytesLen + 1); + } + } + + function stringLen(string memory value) public pure returns (uint256 length) { + assembly { + length := mload(value) + } + } + + function encodeString(string memory value) public pure returns (bytes memory) { + bytes memory result = bytes(value); + bytes memory len = encodeCompactInt(result.length); + + return bytes.concat(len, result); + } + + 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) + } + } + } + + function decodeString(bytes memory _bytes, uint256 offset) public pure returns (DecodedString memory) { + CompactUint256 memory len = decodeCompactInt(_bytes, offset); + + offset += len.offset; + + bytes memory result = new bytes(len.value); + + 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 += len.value; + + return DecodedString(string(result), offset); + } + + 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; + + 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 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 { + return Option(true, sliceBytes(_bytes, 1 + offset, _bytes.length)); + } + } + + function encodeResult(Result memory value) public pure returns (bytes memory) { + if (value.isOk) { + return bytes.concat(hex"00", value.value); + } else { + return bytes.concat(hex"01", value.value); + } + } + + 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); + } else { + return Result(false, 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..a1528cac4de --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Array.t.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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(_value, offset); + 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..e0300656820 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Bool.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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", 0), true); + } + + function test_boolDecodeFalse() public pure { + assertEq(ScaleCodec.decodeBool(hex"00", 0), 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..2ce14bdde81 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Bytes.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestBytesScaleCodec is Test { + function test_bytesToBytes32() public pure { + assertEq( + ScaleCodec.bytesToBytes32(hex"050102030401020304010203040102030401020304010203040102030401020304", 1), + hex"0102030401020304010203040102030401020304010203040102030401020304" + ); + } + + 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.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 new file mode 100644 index 00000000000..03a774271eb --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Int.t.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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"); + + 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 { + assertEq(ScaleCodec.encodeInt16(int16(42)), hex"2a00"); + assertEq(ScaleCodec.decodeInt16(hex"2a00", 0), int16(42)); + + assertEq(ScaleCodec.encodeInt16(int16(-42)), hex"d6ff"); + 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", 0), int32(16777215)); + + assertEq(ScaleCodec.encodeInt32(int32(-16777215)), hex"010000ff"); + 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", 0), int64(18446744073709)); + + assertEq(ScaleCodec.encodeInt64(int64(-18446744073709)), hex"134a5f0839efffff"); + 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", 0), int128(340282366920938463463374607431) + ); + + assertEq( + ScaleCodec.encodeInt128(int128(-340282366920938463463374607431)), hex"b9abbf7446d35a4af6057db4fbffffff" + ); + assertEq( + 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 new file mode 100644 index 00000000000..8804c9612ca --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Optional.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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.encodeOption( + ScaleCodec.Option({ + isSome: _value.isSome, + value: _value.isSome ? ScaleCodec.encodeString(_value.value) : new bytes(0) + }) + ); + } + + function decodeOptionalString(bytes memory _bytes) internal pure returns (OptionalString memory) { + ScaleCodec.Option memory decoded = ScaleCodec.decodeOption(_bytes, 0); + + return OptionalString({ + isSome: decoded.isSome, + value: decoded.isSome ? ScaleCodec.decodeString(decoded.value, 0).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..3cebefdf788 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Result.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestResultScaleCodec is Test { + struct ResultStringU8 { + bool isOk; + string ok; + uint8 err; + } + + function encodeResultStringU8(ResultStringU8 memory _value) internal pure returns (bytes memory) { + if (_value.isOk) { + return ScaleCodec.encodeResult(ScaleCodec.Result({isOk: true, value: ScaleCodec.encodeString(_value.ok)})); + } else { + return ScaleCodec.encodeResult(ScaleCodec.Result({isOk: false, value: ScaleCodec.encodeUint8(_value.err)})); + } + } + + function decodeResultStringU8(bytes memory _value) public pure returns (ResultStringU8 memory) { + ScaleCodec.Result memory decoded = ScaleCodec.decodeResult(_value, 0); + + if (decoded.isOk) { + return ResultStringU8({isOk: true, ok: ScaleCodec.decodeString(decoded.value, 0).value, err: 0}); + } else { + return ResultStringU8({isOk: false, ok: "", err: ScaleCodec.decodeUint8(decoded.value, 0)}); + } + } + + function test_ResultOkEncodeDecode() public pure { + ResultStringU8 memory _result = ResultStringU8({isOk: true, 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, ok: "", err: 1}); + + assertEq(encodeResultStringU8(_result), hex"0101"); + + ResultStringU8 memory _decoded = decodeResultStringU8(hex"0101"); + + assertEq(_decoded.isOk, false); + 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..a1d8ad2a65e --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/String.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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_stringEncodeTo() public pure { + bytes memory _bytes = new bytes(7); + _bytes[0] = 0x01; + + 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"); + } + + 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 new file mode 100644 index 00000000000..ea2860b7b52 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Struct.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +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); + + uint256 totalLen = 0; + + uint256 __nameLen = ScaleCodec.stringLen(myStruct.name); + uint8 __namePrefixLen = ScaleCodec.compactIntLen(__nameLen); + totalLen += __nameLen + __namePrefixLen; + + totalLen += 1; + + bytes memory _bytes = new bytes(totalLen); + + uint256 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) { + ScaleCodec.DecodedString memory name = ScaleCodec.decodeString(_value, 0); + uint8 age = ScaleCodec.decodeUint8(_value, name.offset); + + 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..5bbabb51ae5 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Uint.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestUintScaleCodec is Test { + 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_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_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_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_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 { + assertEq( + 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 { + assertEq( + ScaleCodec.decodeUint256(hex"4754408bb92ca5b509fa824b0400000000000000000000000000000000000000", 0), + uint256(340282366920938463463374607431) + ); + assertEq( + ScaleCodec.decodeUint256(hex"014754408bb92ca5b509fa824b0400000000000000000000000000000000000000", 1), + uint256(340282366920938463463374607431) + ); + } + + 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(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"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.CompactUint256 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 new file mode 100644 index 00000000000..6743a0af511 --- /dev/null +++ b/ethexe/contracts/test/ScaleCodec/Vec.t.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.26; + +import {ScaleCodec} from "src/ScaleCodec.sol"; +import "forge-std/Test.sol"; + +contract TestVecScaleCodec is Test { + function test_encodeVecUint8() public pure { + uint8[] memory data = new uint8[](3); + data[0] = 1; + data[1] = 2; + data[2] = 3; + + uint8 vecPrefixLen = ScaleCodec.compactIntLen(data.length); + uint256 totalLen = data.length + vecPrefixLen; + + bytes memory _bytes = new bytes(totalLen); + + uint256 offset = 0; + + ScaleCodec.encodeCompactIntTo(data.length, vecPrefixLen, _bytes, offset); + + offset += vecPrefixLen; + + for (uint256 i = 0; i < data.length; i++) { + ScaleCodec.encodeUint8To(data[i], _bytes, offset); + offset += 1; + } + + assertEq(_bytes, hex"0c010203"); + } + + function test_encodeVecString() public pure { + string[] memory data = new string[](2); + data[0] = "hello"; + data[1] = "world"; + + 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); + } + + bytes memory _bytes = new bytes(bytesLen + vecPrefixLen); + + uint256 offset = 0; + + ScaleCodec.encodeCompactIntTo(data.length, vecPrefixLen, _bytes, offset); + offset += vecPrefixLen; + + 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; + } + + assertEq(_bytes, hex"081468656c6c6f14776f726c64"); + } + + function test_decodeVecUint8() public pure { + bytes memory data = hex"0c010203"; + + ScaleCodec.CompactUint256 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++; + } + + assertEq(vec[0], 1); + assertEq(vec[1], 2); + assertEq(vec[2], 3); + } + + function test_decodeVecString() public pure { + bytes memory data = hex"081468656c6c6f14776f726c64"; + + ScaleCodec.CompactUint256 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; + } + + assertEq(vec[0], "hello"); + assertEq(vec[1], "world"); + } +}