@@ -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+ / 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