@@ -627,4 +627,329 @@ describe('CowShedHooks', () => {
627627 }
628628 } )
629629 } )
630+
631+ describe ( 'eip1271SignatureCache' , ( ) => {
632+ it ( 'should cache EIP1271 signature verification results' , async ( ) => {
633+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
634+
635+ for ( const adapterName of adapterNames ) {
636+ const adapter = adapters [ adapterName ]
637+ setGlobalAdapter ( adapter )
638+
639+ // Mock readContract to return EIP1271_MAGICVALUE
640+ const originalReadContract = adapter . readContract
641+ const readContractMock = jest . fn ( ) . mockResolvedValue ( '0x1626ba7e' )
642+ adapter . readContract = readContractMock
643+
644+ const cowShed = new CowShedHooks ( 1 , {
645+ factoryAddress : MOCK_COW_SHED_FACTORY ,
646+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
647+ proxyCreationCode : MOCK_INIT_CODE ,
648+ } )
649+
650+ const calls = createCallsForAdapter ( adapter )
651+ const nonce = adapter . utils . formatBytes32String ( '1' )
652+ const deadline = BigInt ( 1000000 )
653+ const proxy = cowShed . proxyOf ( USER_MOCK )
654+ const typedDataContext = cowShed . infoToSign ( calls , nonce , deadline , proxy )
655+ const signature = '0x1234abcd'
656+
657+ // First call - should hit the contract
658+ const result1 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature , typedDataContext )
659+ expect ( result1 ) . toBe ( true )
660+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 )
661+
662+ // Second call with same parameters - should use cache
663+ const result2 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature , typedDataContext )
664+ expect ( result2 ) . toBe ( true )
665+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1, not called again
666+
667+ // Third call with same parameters - should still use cache
668+ const result3 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature , typedDataContext )
669+ expect ( result3 ) . toBe ( true )
670+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1
671+
672+ // Restore original method
673+ adapter . readContract = originalReadContract
674+ }
675+ } )
676+
677+ it ( 'should cache negative EIP1271 signature verification results' , async ( ) => {
678+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
679+
680+ for ( const adapterName of adapterNames ) {
681+ const adapter = adapters [ adapterName ]
682+ setGlobalAdapter ( adapter )
683+
684+ // Mock readContract to return invalid magic value
685+ const originalReadContract = adapter . readContract
686+ const readContractMock = jest . fn ( ) . mockResolvedValue ( '0xffffffff' )
687+ adapter . readContract = readContractMock
688+
689+ const cowShed = new CowShedHooks ( 1 , {
690+ factoryAddress : MOCK_COW_SHED_FACTORY ,
691+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
692+ proxyCreationCode : MOCK_INIT_CODE ,
693+ } )
694+
695+ const calls = createCallsForAdapter ( adapter )
696+ const nonce = adapter . utils . formatBytes32String ( '1' )
697+ const deadline = BigInt ( 1000000 )
698+ const proxy = cowShed . proxyOf ( USER_MOCK )
699+ const typedDataContext = cowShed . infoToSign ( calls , nonce , deadline , proxy )
700+ const signature = '0x1234abcd'
701+
702+ // First call - should hit the contract
703+ const result1 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature , typedDataContext )
704+ expect ( result1 ) . toBe ( false )
705+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 )
706+
707+ // Second call - should use cache
708+ const result2 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature , typedDataContext )
709+ expect ( result2 ) . toBe ( false )
710+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1
711+
712+ // Restore original method
713+ adapter . readContract = originalReadContract
714+ }
715+ } )
716+
717+ it ( 'should not use cache for different signatures' , async ( ) => {
718+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
719+
720+ for ( const adapterName of adapterNames ) {
721+ const adapter = adapters [ adapterName ]
722+ setGlobalAdapter ( adapter )
723+
724+ // Mock readContract to return EIP1271_MAGICVALUE
725+ const originalReadContract = adapter . readContract
726+ const readContractMock = jest . fn ( ) . mockResolvedValue ( '0x1626ba7e' )
727+ adapter . readContract = readContractMock
728+
729+ const cowShed = new CowShedHooks ( 1 , {
730+ factoryAddress : MOCK_COW_SHED_FACTORY ,
731+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
732+ proxyCreationCode : MOCK_INIT_CODE ,
733+ } )
734+
735+ const calls = createCallsForAdapter ( adapter )
736+ const nonce = adapter . utils . formatBytes32String ( '1' )
737+ const deadline = BigInt ( 1000000 )
738+ const proxy = cowShed . proxyOf ( USER_MOCK )
739+ const typedDataContext = cowShed . infoToSign ( calls , nonce , deadline , proxy )
740+
741+ // First call with signature1
742+ const signature1 = '0x1234abcd'
743+ const result1 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature1 , typedDataContext )
744+ expect ( result1 ) . toBe ( true )
745+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 )
746+
747+ // Second call with different signature - should hit contract again
748+ const signature2 = '0xdeadbeef'
749+ const result2 = await cowShed . verifyEip1271Signature ( USER_MOCK , signature2 , typedDataContext )
750+ expect ( result2 ) . toBe ( true )
751+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 2 ) // Called again
752+
753+ // Restore original method
754+ adapter . readContract = originalReadContract
755+ }
756+ } )
757+
758+ it ( 'should not use cache for different accounts' , async ( ) => {
759+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
760+
761+ for ( const adapterName of adapterNames ) {
762+ const adapter = adapters [ adapterName ]
763+ setGlobalAdapter ( adapter )
764+
765+ // Mock readContract to return EIP1271_MAGICVALUE
766+ const originalReadContract = adapter . readContract
767+ const readContractMock = jest . fn ( ) . mockResolvedValue ( '0x1626ba7e' )
768+ adapter . readContract = readContractMock
769+
770+ const cowShed = new CowShedHooks ( 1 , {
771+ factoryAddress : MOCK_COW_SHED_FACTORY ,
772+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
773+ proxyCreationCode : MOCK_INIT_CODE ,
774+ } )
775+
776+ const calls = createCallsForAdapter ( adapter )
777+ const nonce = adapter . utils . formatBytes32String ( '1' )
778+ const deadline = BigInt ( 1000000 )
779+ const proxy = cowShed . proxyOf ( USER_MOCK )
780+ const typedDataContext = cowShed . infoToSign ( calls , nonce , deadline , proxy )
781+ const signature = '0x1234abcd'
782+
783+ // First call with account1
784+ const account1 = USER_MOCK
785+ const result1 = await cowShed . verifyEip1271Signature ( account1 , signature , typedDataContext )
786+ expect ( result1 ) . toBe ( true )
787+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 1 )
788+
789+ // Second call with different account - should hit contract again
790+ const account2 = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'
791+ const result2 = await cowShed . verifyEip1271Signature ( account2 , signature , typedDataContext )
792+ expect ( result2 ) . toBe ( true )
793+ expect ( readContractMock ) . toHaveBeenCalledTimes ( 2 ) // Called again
794+
795+ // Restore original method
796+ adapter . readContract = originalReadContract
797+ }
798+ } )
799+ } )
800+
801+ describe ( 'accountCodeCache' , ( ) => {
802+ it ( 'should cache account code check results for smart contracts' , async ( ) => {
803+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
804+
805+ for ( const adapterName of adapterNames ) {
806+ const adapter = adapters [ adapterName ]
807+ setGlobalAdapter ( adapter )
808+
809+ // Mock getCode to return bytecode
810+ const originalGetCode = adapter . getCode
811+ const getCodeMock = jest . fn ( ) . mockResolvedValue ( '0x608060405234801561001057600080fd5b50' )
812+ adapter . getCode = getCodeMock
813+
814+ const cowShed = new CowShedHooks ( 1 , {
815+ factoryAddress : MOCK_COW_SHED_FACTORY ,
816+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
817+ proxyCreationCode : MOCK_INIT_CODE ,
818+ } )
819+
820+ // First call - should hit getCode
821+ const result1 = await ( cowShed as any ) . doesAccountHaveCode ( USER_MOCK )
822+ expect ( result1 ) . toBe ( true )
823+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 )
824+ expect ( getCodeMock ) . toHaveBeenCalledWith ( USER_MOCK )
825+
826+ // Second call with same account - should use cache
827+ const result2 = await ( cowShed as any ) . doesAccountHaveCode ( USER_MOCK )
828+ expect ( result2 ) . toBe ( true )
829+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1, not called again
830+
831+ // Third call - should still use cache
832+ const result3 = await ( cowShed as any ) . doesAccountHaveCode ( USER_MOCK )
833+ expect ( result3 ) . toBe ( true )
834+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1
835+
836+ // Restore original method
837+ adapter . getCode = originalGetCode
838+ }
839+ } )
840+
841+ it ( 'should cache account code check results for EOA accounts' , async ( ) => {
842+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
843+
844+ for ( const adapterName of adapterNames ) {
845+ const adapter = adapters [ adapterName ]
846+ setGlobalAdapter ( adapter )
847+
848+ // Mock getCode to return '0x' for EOA
849+ const originalGetCode = adapter . getCode
850+ const getCodeMock = jest . fn ( ) . mockResolvedValue ( '0x' )
851+ adapter . getCode = getCodeMock
852+
853+ const cowShed = new CowShedHooks ( 1 , {
854+ factoryAddress : MOCK_COW_SHED_FACTORY ,
855+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
856+ proxyCreationCode : MOCK_INIT_CODE ,
857+ } )
858+
859+ // First call - should hit getCode
860+ const result1 = await ( cowShed as any ) . doesAccountHaveCode ( USER_MOCK )
861+ expect ( result1 ) . toBe ( false )
862+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 )
863+
864+ // Second call - should use cache
865+ const result2 = await ( cowShed as any ) . doesAccountHaveCode ( USER_MOCK )
866+ expect ( result2 ) . toBe ( false )
867+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1
868+
869+ // Restore original method
870+ adapter . getCode = originalGetCode
871+ }
872+ } )
873+
874+ it ( 'should not use cache for different accounts' , async ( ) => {
875+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
876+
877+ for ( const adapterName of adapterNames ) {
878+ const adapter = adapters [ adapterName ]
879+ setGlobalAdapter ( adapter )
880+
881+ // Mock getCode to return bytecode
882+ const originalGetCode = adapter . getCode
883+ const getCodeMock = jest . fn ( ) . mockResolvedValue ( '0x608060405234801561001057600080fd5b50' )
884+ adapter . getCode = getCodeMock
885+
886+ const cowShed = new CowShedHooks ( 1 , {
887+ factoryAddress : MOCK_COW_SHED_FACTORY ,
888+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
889+ proxyCreationCode : MOCK_INIT_CODE ,
890+ } )
891+
892+ // First call with account1
893+ const account1 = USER_MOCK
894+ const result1 = await ( cowShed as any ) . doesAccountHaveCode ( account1 )
895+ expect ( result1 ) . toBe ( true )
896+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 )
897+
898+ // Second call with different account - should hit getCode again
899+ const account2 = '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC'
900+ const result2 = await ( cowShed as any ) . doesAccountHaveCode ( account2 )
901+ expect ( result2 ) . toBe ( true )
902+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 2 ) // Called again
903+
904+ // Third call with account1 again - should use cache
905+ const result3 = await ( cowShed as any ) . doesAccountHaveCode ( account1 )
906+ expect ( result3 ) . toBe ( true )
907+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 2 ) // Still 2, used cache for account1
908+
909+ // Restore original method
910+ adapter . getCode = originalGetCode
911+ }
912+ } )
913+
914+ it ( 'should cache results used in signCalls method' , async ( ) => {
915+ const adapterNames = Object . keys ( adapters ) as Array < keyof typeof adapters >
916+
917+ for ( const adapterName of adapterNames ) {
918+ const adapter = adapters [ adapterName ]
919+ setGlobalAdapter ( adapter )
920+
921+ // Mock getCode to return '0x' for EOA
922+ const originalGetCode = adapter . getCode
923+ const getCodeMock = jest . fn ( ) . mockResolvedValue ( '0x' )
924+ adapter . getCode = getCodeMock
925+
926+ const cowShed = new CowShedHooks ( 1 , {
927+ factoryAddress : MOCK_COW_SHED_FACTORY ,
928+ implementationAddress : MOCK_COW_SHED_IMPLEMENTATION ,
929+ proxyCreationCode : MOCK_INIT_CODE ,
930+ } )
931+
932+ const mockNonce = adapter . utils . formatBytes32String ( '1' )
933+ const mockDeadline = BigInt ( 1000000 )
934+ const calls = createCallsForAdapter ( adapter )
935+
936+ // First signCalls - should call getCode once
937+ await cowShed . signCalls ( calls , mockNonce , mockDeadline , SigningScheme . EIP712 , adapter . signer )
938+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 )
939+
940+ // Second signCalls with different nonce - should use cached doesAccountHaveCode
941+ const mockNonce2 = adapter . utils . formatBytes32String ( '2' )
942+ await cowShed . signCalls ( calls , mockNonce2 , mockDeadline , SigningScheme . EIP712 , adapter . signer )
943+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1, cache was used
944+
945+ // Third signCalls with different deadline - should still use cache
946+ const mockDeadline2 = BigInt ( 2000000 )
947+ await cowShed . signCalls ( calls , mockNonce , mockDeadline2 , SigningScheme . EIP712 , adapter . signer )
948+ expect ( getCodeMock ) . toHaveBeenCalledTimes ( 1 ) // Still 1
949+
950+ // Restore original method
951+ adapter . getCode = originalGetCode
952+ }
953+ } )
954+ } )
630955} )
0 commit comments