Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(ethexe): introduce ScaleCodec library for Solidity #4237

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
479 changes: 479 additions & 0 deletions ethexe/contracts/src/ScaleCodec.sol

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Array.t.sol
Original file line number Diff line number Diff line change
@@ -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]);
}
}
23 changes: 23 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Bool.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
28 changes: 28 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Bytes.t.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
59 changes: 59 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Int.t.sol
Original file line number Diff line number Diff line change
@@ -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)
);
}
}
51 changes: 51 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Optional.t.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
53 changes: 53 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Result.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
32 changes: 32 additions & 0 deletions ethexe/contracts/test/ScaleCodec/String.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
54 changes: 54 additions & 0 deletions ethexe/contracts/test/ScaleCodec/Struct.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Loading
Loading