Skip to content

Commit cac7cc4

Browse files
Add tests for partial account helper functions (#3745)
* Add tests for partial account helpers * Make comments and error message checks uniform * Increase test timeout * Dry up the test code * Reuse const variable in check --------- Co-authored-by: Gabriel Rocheleau <[email protected]>
1 parent 4ed3482 commit cac7cc4

File tree

2 files changed

+364
-1
lines changed

2 files changed

+364
-1
lines changed

packages/client/test/cli/cli.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ describe('[CLI]', () => {
5757
}
5858
}
5959
await clientRunHelper(cliArgs, onData)
60-
}, 5000)
60+
}, 10000)
6161
it('should successfully start client with custom inputs for PoA network', async () => {
6262
const cliArgs = [
6363
'--rpc',

packages/util/test/account.spec.ts

Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,26 @@ import {
55
Account,
66
KECCAK256_NULL,
77
KECCAK256_RLP,
8+
Units,
89
accountBodyFromSlim,
910
accountBodyToRLP,
1011
accountBodyToSlim,
12+
bigIntToUnpaddedBytes,
1113
bytesToBigInt,
1214
bytesToHex,
1315
createAccount,
1416
createAccountFromBytesArray,
1517
createAccountFromRLP,
18+
createPartialAccount,
19+
createPartialAccountFromRLP,
1620
equalsBytes,
1721
generateAddress,
1822
generateAddress2,
1923
hexToBytes,
2024
importPublic,
2125
intToBytes,
2226
intToHex,
27+
intToUnpaddedBytes,
2328
isValidAddress,
2429
isValidChecksumAddress,
2530
isValidPrivate,
@@ -810,3 +815,361 @@ describe('Utility Functions', () => {
810815
assert.equal(JSON.stringify(result), JSON.stringify(RLP.encode(body)))
811816
})
812817
})
818+
819+
describe('createPartialAccount', () => {
820+
it('should throw an error when all fields are null', () => {
821+
assert.throws(
822+
() =>
823+
createPartialAccount({
824+
nonce: null,
825+
balance: null,
826+
storageRoot: null,
827+
codeHash: null,
828+
codeSize: null,
829+
version: null,
830+
}),
831+
'All partial fields null',
832+
)
833+
})
834+
835+
it('should return Account with correct values when fields are provided', () => {
836+
const accountData = {
837+
nonce: 1n,
838+
balance: Units.ether(1),
839+
storageRoot: KECCAK256_RLP,
840+
codeHash: KECCAK256_RLP,
841+
codeSize: 10,
842+
version: 1,
843+
}
844+
const account = createPartialAccount(accountData)
845+
846+
assert.deepEqual(
847+
account,
848+
new Account(
849+
accountData.nonce,
850+
accountData.balance,
851+
accountData.storageRoot,
852+
accountData.codeHash,
853+
accountData.codeSize,
854+
accountData.version,
855+
),
856+
)
857+
})
858+
859+
it('should return Account with null and undefined value fields as given', () => {
860+
const accountData = {
861+
nonce: undefined,
862+
balance: Units.ether(1),
863+
storageRoot: undefined,
864+
codeHash: null,
865+
codeSize: 10,
866+
version: undefined,
867+
}
868+
const account = createPartialAccount(accountData)
869+
870+
assert.deepEqual(
871+
account,
872+
new Account(
873+
accountData.nonce,
874+
accountData.balance,
875+
accountData.storageRoot,
876+
accountData.codeHash,
877+
accountData.codeSize,
878+
accountData.version,
879+
),
880+
)
881+
})
882+
})
883+
884+
describe('createPartialAccountFromRLP', () => {
885+
it('should throw an error for invalid serialized account input (non-array)', () => {
886+
const invalidSerialized = toBytes(1n)
887+
assert.throws(
888+
() => createPartialAccountFromRLP(invalidSerialized),
889+
/Invalid serialized account input/,
890+
)
891+
})
892+
893+
const testCases = [
894+
{
895+
description: 'should handle a mix of null and non-null values correctly',
896+
data: [
897+
[toBytes(1), toBytes(1)], // Nonce: 1
898+
[toBytes(0)], // Balance: null
899+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
900+
[toBytes(0)], // CodeHash: null
901+
[toBytes(1), toBytes(10)], // CodeSize: 10
902+
[toBytes(0)], // Version: null
903+
],
904+
shouldThrow: false,
905+
expected: new Account(BigInt(1), null, KECCAK256_RLP, null, 10, null),
906+
errorRegex: null,
907+
},
908+
{
909+
description: 'should throw when all fields are null',
910+
data: [
911+
[toBytes(0)], // Nonce: null
912+
[toBytes(0)], // Balance: null
913+
[toBytes(0)], // StorageRoot: null
914+
[toBytes(0)], // CodeHash: null
915+
[toBytes(0)], // CodeSize: null
916+
[toBytes(0)], // Version: null
917+
],
918+
shouldThrow: true,
919+
expected: null,
920+
errorRegex: /All partial fields null/,
921+
},
922+
{
923+
description: 'should handle all non-null fields correctly',
924+
data: [
925+
[toBytes(1), toBytes(2)], // Nonce: 2
926+
[toBytes(1), toBytes(1000)], // Balance: 1000
927+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
928+
[toBytes(1), KECCAK256_RLP], // CodeHash: KECCAK256_RLP
929+
[toBytes(1), toBytes(50)], // CodeSize: 50
930+
[toBytes(1), toBytes(1)], // Version: 1
931+
],
932+
shouldThrow: false,
933+
expected: new Account(BigInt(2), BigInt(1000), KECCAK256_RLP, KECCAK256_RLP, 50, 1),
934+
errorRegex: null,
935+
},
936+
{
937+
description:
938+
'should return partial account with non-null fields when isNotNullIndicator is 1',
939+
data: [
940+
[toBytes(1), toBytes(2)], // Nonce: 2
941+
[toBytes(1), toBytes(1000)], // Balance: 1000
942+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
943+
[toBytes(1), KECCAK256_RLP], // CodeHash: KECCAK256_RLP
944+
[toBytes(1), toBytes(50)], // CodeSize: 50
945+
[toBytes(1), toBytes(1)], // Version: 1
946+
],
947+
shouldThrow: false,
948+
expected: new Account(BigInt(2), BigInt(1000), KECCAK256_RLP, KECCAK256_RLP, 50, 1),
949+
errorRegex: null,
950+
},
951+
{
952+
description: 'should return a mix of null and non-null fields based on isNotNullIndicator',
953+
data: [
954+
[toBytes(1), toBytes(2)], // Nonce: 2
955+
[toBytes(0)], // Balance: null
956+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
957+
[toBytes(0)], // CodeHash: null
958+
[toBytes(1), toBytes(50)], // CodeSize: 50
959+
[toBytes(0)], // Version: null
960+
],
961+
shouldThrow: false,
962+
expected: new Account(BigInt(2), null, KECCAK256_RLP, null, 50, null),
963+
errorRegex: null,
964+
},
965+
{
966+
description:
967+
'should handle cases where some fields are non-null and others are null correctly',
968+
data: [
969+
[toBytes(1), toBytes(2)], // Nonce: 2
970+
[toBytes(0)], // Balance: null
971+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
972+
[toBytes(1), KECCAK256_RLP], // CodeHash: KECCAK256_RLP
973+
[toBytes(0)], // CodeSize: null
974+
[toBytes(0)], // Version: null
975+
],
976+
shouldThrow: false,
977+
expected: new Account(BigInt(2), null, KECCAK256_RLP, KECCAK256_RLP, null, null),
978+
errorRegex: null,
979+
},
980+
{
981+
description: 'should handle fields with empty arrays (isNullIndicator=0) correctly',
982+
data: [
983+
[], // nonce -> empty array => null
984+
[toBytes(1), toBytes(1000)], // balance: 1000
985+
[], // storageRoot -> null
986+
[], // codeHash -> null
987+
[], // codeSize -> null
988+
[], // version -> null
989+
],
990+
shouldThrow: false,
991+
expected: new Account(null, BigInt(1000), null, null, null, null),
992+
errorRegex: null,
993+
},
994+
{
995+
description: 'should throw: invalid partial nonce encoding',
996+
data: [KECCAK256_RLP, [], [], [], [], []],
997+
shouldThrow: true,
998+
expected: null,
999+
errorRegex: /Invalid partial nonce encoding/,
1000+
},
1001+
{
1002+
description: 'should throw: invalid partial balance encoding',
1003+
data: [[], KECCAK256_RLP, [], [], [], []],
1004+
shouldThrow: true,
1005+
expected: null,
1006+
errorRegex: /Invalid partial balance encoding/,
1007+
},
1008+
{
1009+
description: 'should throw: invalid partial storageRoot encoding',
1010+
data: [[], [], KECCAK256_RLP, [], [], []],
1011+
shouldThrow: true,
1012+
expected: null,
1013+
errorRegex: /Invalid partial storageRoot encoding/,
1014+
},
1015+
{
1016+
description: 'should throw: invalid partial codeHash encoding',
1017+
data: [[], [], [], KECCAK256_RLP, [], []],
1018+
shouldThrow: true,
1019+
expected: null,
1020+
errorRegex: /Invalid partial codeHash encoding/,
1021+
},
1022+
{
1023+
description: 'should throw: invalid partial codeSize encoding',
1024+
data: [[], [], [], [], KECCAK256_RLP, []],
1025+
shouldThrow: true,
1026+
expected: null,
1027+
errorRegex: /Invalid partial codeSize encoding/,
1028+
},
1029+
{
1030+
description: 'should throw: invalid partial version encoding',
1031+
data: [[], [], [], [], [], KECCAK256_RLP],
1032+
shouldThrow: true,
1033+
expected: null,
1034+
errorRegex: /Invalid partial version encoding/,
1035+
},
1036+
{
1037+
description: 'should throw: invalid isNullIndicator=2 for nonce',
1038+
data: [[toBytes(2)], [], [], [], [], []],
1039+
shouldThrow: true,
1040+
expected: null,
1041+
errorRegex: /Invalid isNullIndicator=2 for nonce/,
1042+
},
1043+
{
1044+
description: 'should throw: invalid isNullIndicator=2 for balance',
1045+
data: [[], [toBytes(2)], [], [], [], []],
1046+
shouldThrow: true,
1047+
expected: null,
1048+
errorRegex: /Invalid isNullIndicator=2 for balance/,
1049+
},
1050+
{
1051+
description: 'should throw: invalid isNullIndicator=2 for storageRoot',
1052+
data: [[], [], [toBytes(2)], [], [], []],
1053+
shouldThrow: true,
1054+
expected: null,
1055+
errorRegex: /Invalid isNullIndicator=2 for storageRoot/,
1056+
},
1057+
{
1058+
description: 'should throw: invalid isNullIndicator=2 for codeHash',
1059+
data: [[], [], [], [toBytes(2)], [], []],
1060+
shouldThrow: true,
1061+
expected: null,
1062+
errorRegex: /Invalid isNullIndicator=2 for codeHash/,
1063+
},
1064+
{
1065+
description: 'should throw: invalid isNullIndicator=2 for codeSize',
1066+
data: [[], [], [], [], [toBytes(2)], []],
1067+
shouldThrow: true,
1068+
expected: null,
1069+
errorRegex: /Invalid isNullIndicator=2 for codeSize/,
1070+
},
1071+
{
1072+
description: 'should throw: invalid isNullIndicator=2 for version',
1073+
data: [[], [], [], [], [], [toBytes(2)]],
1074+
shouldThrow: true,
1075+
expected: null,
1076+
errorRegex: /Invalid isNullIndicator=2 for version/,
1077+
},
1078+
]
1079+
1080+
for (const { description, data, shouldThrow, expected, errorRegex } of testCases) {
1081+
it(description, () => {
1082+
const serialized = RLP.encode(data)
1083+
if (shouldThrow) {
1084+
assert.throws(() => createPartialAccountFromRLP(serialized), errorRegex as RegExp)
1085+
} else {
1086+
const account = createPartialAccountFromRLP(serialized)
1087+
assert.deepEqual(account, expected)
1088+
}
1089+
})
1090+
}
1091+
})
1092+
1093+
describe('serializeWithPartialInfo', () => {
1094+
const testCases = [
1095+
{
1096+
description: 'should serialize all fields as null (isNotNullIndicator=0)',
1097+
account: new Account(null, null, null, null, null, null),
1098+
expectedDecoded: [
1099+
[new Uint8Array()], // Nonce: null
1100+
[new Uint8Array()], // Balance: null
1101+
[new Uint8Array()], // StorageRoot: null
1102+
[new Uint8Array()], // CodeHash: null
1103+
[new Uint8Array()], // CodeSize: null
1104+
[new Uint8Array()], // Version: null
1105+
],
1106+
},
1107+
{
1108+
description: 'should serialize all fields as non-null (isNotNullIndicator=1)',
1109+
account: new Account(BigInt(2), Units.ether(1), KECCAK256_RLP, KECCAK256_RLP, 50, 1),
1110+
expectedDecoded: [
1111+
[toBytes(1), bigIntToUnpaddedBytes(BigInt(2))], // Nonce: 2
1112+
[toBytes(1), bigIntToUnpaddedBytes(Units.ether(1))], // Balance: 1000
1113+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
1114+
[toBytes(1), KECCAK256_RLP], // CodeHash: KECCAK256_RLP
1115+
[toBytes(1), intToUnpaddedBytes(50)], // CodeSize: 50
1116+
[toBytes(1), intToUnpaddedBytes(1)], // Version: 1
1117+
],
1118+
},
1119+
{
1120+
description: 'should serialize mixed null and non-null fields',
1121+
account: new Account(BigInt(2), null, KECCAK256_RLP, null, 50, null),
1122+
expectedDecoded: [
1123+
[toBytes(1), bigIntToUnpaddedBytes(BigInt(2))], // Nonce: 2
1124+
[new Uint8Array()], // Balance: null
1125+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
1126+
[new Uint8Array()], // CodeHash: null
1127+
[toBytes(1), intToUnpaddedBytes(50)], // CodeSize: 50
1128+
[new Uint8Array()], // Version: null
1129+
],
1130+
},
1131+
{
1132+
description:
1133+
'should correctly handle serialization of null hash for storageRoot and codeHash',
1134+
account: new Account(BigInt(2), Units.ether(1), KECCAK256_RLP, KECCAK256_RLP, 50, 1),
1135+
expectedDecoded: [
1136+
[toBytes(1), bigIntToUnpaddedBytes(BigInt(2))], // Nonce: 2
1137+
[toBytes(1), bigIntToUnpaddedBytes(Units.ether(1))], // Balance: 1000
1138+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
1139+
[toBytes(1), KECCAK256_RLP], // CodeHash: KECCAK256_RLP
1140+
[toBytes(1), intToUnpaddedBytes(50)], // CodeSize: 50
1141+
[toBytes(1), intToUnpaddedBytes(1)], // Version: 1
1142+
],
1143+
},
1144+
{
1145+
description: 'should correctly serialize when only some fields are provided',
1146+
account: new Account(BigInt(123), null, KECCAK256_RLP, null, null, 42),
1147+
expectedDecoded: [
1148+
[toBytes(1), bigIntToUnpaddedBytes(BigInt(123))], // Nonce: 123
1149+
[new Uint8Array()], // Balance: null
1150+
[toBytes(1), KECCAK256_RLP], // StorageRoot: KECCAK256_RLP
1151+
[new Uint8Array()], // CodeHash: null
1152+
[new Uint8Array()], // CodeSize: null
1153+
[toBytes(1), intToUnpaddedBytes(42)], // Version: 42
1154+
],
1155+
},
1156+
]
1157+
1158+
for (const { description, account, expectedDecoded } of testCases) {
1159+
it(description, () => {
1160+
const serialized = account.serializeWithPartialInfo()
1161+
const decoded = RLP.decode(serialized)
1162+
assert.deepEqual(decoded, expectedDecoded)
1163+
})
1164+
}
1165+
1166+
it('should serialize and then deserialize back to the original account object', () => {
1167+
const account = new Account(BigInt(2), Units.ether(1), KECCAK256_RLP, KECCAK256_RLP, 50, 1)
1168+
const serialized = account.serializeWithPartialInfo()
1169+
1170+
// Now deserialize the serialized data back into a partial account
1171+
const deserializedAccount = createPartialAccountFromRLP(serialized)
1172+
1173+
assert.deepEqual(deserializedAccount, account)
1174+
})
1175+
})

0 commit comments

Comments
 (0)