@@ -5,21 +5,26 @@ import {
5
5
Account ,
6
6
KECCAK256_NULL ,
7
7
KECCAK256_RLP ,
8
+ Units ,
8
9
accountBodyFromSlim ,
9
10
accountBodyToRLP ,
10
11
accountBodyToSlim ,
12
+ bigIntToUnpaddedBytes ,
11
13
bytesToBigInt ,
12
14
bytesToHex ,
13
15
createAccount ,
14
16
createAccountFromBytesArray ,
15
17
createAccountFromRLP ,
18
+ createPartialAccount ,
19
+ createPartialAccountFromRLP ,
16
20
equalsBytes ,
17
21
generateAddress ,
18
22
generateAddress2 ,
19
23
hexToBytes ,
20
24
importPublic ,
21
25
intToBytes ,
22
26
intToHex ,
27
+ intToUnpaddedBytes ,
23
28
isValidAddress ,
24
29
isValidChecksumAddress ,
25
30
isValidPrivate ,
@@ -810,3 +815,361 @@ describe('Utility Functions', () => {
810
815
assert . equal ( JSON . stringify ( result ) , JSON . stringify ( RLP . encode ( body ) ) )
811
816
} )
812
817
} )
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
+ / I n v a l i d s e r i a l i z e d a c c o u n t i n p u t / ,
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 : / A l l p a r t i a l f i e l d s n u l l / ,
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 : / I n v a l i d p a r t i a l n o n c e e n c o d i n g / ,
1000
+ } ,
1001
+ {
1002
+ description : 'should throw: invalid partial balance encoding' ,
1003
+ data : [ [ ] , KECCAK256_RLP , [ ] , [ ] , [ ] , [ ] ] ,
1004
+ shouldThrow : true ,
1005
+ expected : null ,
1006
+ errorRegex : / I n v a l i d p a r t i a l b a l a n c e e n c o d i n g / ,
1007
+ } ,
1008
+ {
1009
+ description : 'should throw: invalid partial storageRoot encoding' ,
1010
+ data : [ [ ] , [ ] , KECCAK256_RLP , [ ] , [ ] , [ ] ] ,
1011
+ shouldThrow : true ,
1012
+ expected : null ,
1013
+ errorRegex : / I n v a l i d p a r t i a l s t o r a g e R o o t e n c o d i n g / ,
1014
+ } ,
1015
+ {
1016
+ description : 'should throw: invalid partial codeHash encoding' ,
1017
+ data : [ [ ] , [ ] , [ ] , KECCAK256_RLP , [ ] , [ ] ] ,
1018
+ shouldThrow : true ,
1019
+ expected : null ,
1020
+ errorRegex : / I n v a l i d p a r t i a l c o d e H a s h e n c o d i n g / ,
1021
+ } ,
1022
+ {
1023
+ description : 'should throw: invalid partial codeSize encoding' ,
1024
+ data : [ [ ] , [ ] , [ ] , [ ] , KECCAK256_RLP , [ ] ] ,
1025
+ shouldThrow : true ,
1026
+ expected : null ,
1027
+ errorRegex : / I n v a l i d p a r t i a l c o d e S i z e e n c o d i n g / ,
1028
+ } ,
1029
+ {
1030
+ description : 'should throw: invalid partial version encoding' ,
1031
+ data : [ [ ] , [ ] , [ ] , [ ] , [ ] , KECCAK256_RLP ] ,
1032
+ shouldThrow : true ,
1033
+ expected : null ,
1034
+ errorRegex : / I n v a l i d p a r t i a l v e r s i o n e n c o d i n g / ,
1035
+ } ,
1036
+ {
1037
+ description : 'should throw: invalid isNullIndicator=2 for nonce' ,
1038
+ data : [ [ toBytes ( 2 ) ] , [ ] , [ ] , [ ] , [ ] , [ ] ] ,
1039
+ shouldThrow : true ,
1040
+ expected : null ,
1041
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r n o n c e / ,
1042
+ } ,
1043
+ {
1044
+ description : 'should throw: invalid isNullIndicator=2 for balance' ,
1045
+ data : [ [ ] , [ toBytes ( 2 ) ] , [ ] , [ ] , [ ] , [ ] ] ,
1046
+ shouldThrow : true ,
1047
+ expected : null ,
1048
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r b a l a n c e / ,
1049
+ } ,
1050
+ {
1051
+ description : 'should throw: invalid isNullIndicator=2 for storageRoot' ,
1052
+ data : [ [ ] , [ ] , [ toBytes ( 2 ) ] , [ ] , [ ] , [ ] ] ,
1053
+ shouldThrow : true ,
1054
+ expected : null ,
1055
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r s t o r a g e R o o t / ,
1056
+ } ,
1057
+ {
1058
+ description : 'should throw: invalid isNullIndicator=2 for codeHash' ,
1059
+ data : [ [ ] , [ ] , [ ] , [ toBytes ( 2 ) ] , [ ] , [ ] ] ,
1060
+ shouldThrow : true ,
1061
+ expected : null ,
1062
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r c o d e H a s h / ,
1063
+ } ,
1064
+ {
1065
+ description : 'should throw: invalid isNullIndicator=2 for codeSize' ,
1066
+ data : [ [ ] , [ ] , [ ] , [ ] , [ toBytes ( 2 ) ] , [ ] ] ,
1067
+ shouldThrow : true ,
1068
+ expected : null ,
1069
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r c o d e S i z e / ,
1070
+ } ,
1071
+ {
1072
+ description : 'should throw: invalid isNullIndicator=2 for version' ,
1073
+ data : [ [ ] , [ ] , [ ] , [ ] , [ ] , [ toBytes ( 2 ) ] ] ,
1074
+ shouldThrow : true ,
1075
+ expected : null ,
1076
+ errorRegex : / I n v a l i d i s N u l l I n d i c a t o r = 2 f o r v e r s i o n / ,
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