@@ -624,6 +624,94 @@ Since float casts create thunks, we simplify this pattern directly to `PRODUCT *
624624 => Integer(PRODUCT *Int 2, 64, false)
625625```
626626
627+ ## Linking identical accounts
628+
629+ When the ` AccountInfo ` are provided to the program from the solana runtime,
630+ we restrict that if they have the same ` key ` then the rest of their fields are
631+ the same. This cheatcode should only be on two ` AccountInfo ` after ` cheatcode_is_account `
632+ is called on those ` AccountInfo ` to set up the symbolic state. Furthermore this should
633+ be called prior to capturing initial state and prior to executing the implementation.
634+
635+ ``` {.k .symbolic}
636+ // Path to account key: &AccountInfo -> AccountInfo -> key -> &Pubkey -> Pubkey
637+ syntax ProjectionElems ::= "KEY_PROJS" [alias]
638+ rule KEY_PROJS => projectionElemDeref // deref &AccountInfo
639+ projectionElemField(fieldIdx(0), #hack()) // .key (&Pubkey)
640+ projectionElemDeref // deref to Pubkey
641+ .ProjectionElems
642+
643+ // Cheatcode to link two accounts if they have the same key
644+ // Usage: cheatcode_maybe_same_account(&account1, &account2)
645+ // Effect: If account1.key == account2.key, then all SPL data fields are constrained equal
646+ rule [cheatcode-maybe-same-account]:
647+ <k> #execTerminatorCall(_, FUNC,
648+ operandCopy(place(LOCAL1, PROJS1))
649+ operandCopy(place(LOCAL2, PROJS2))
650+ .Operands, _DEST, TARGET, _UNWIND) ~> _CONT
651+ => #maybeLinkAccounts(
652+ operandCopy(place(LOCAL1, appendP(PROJS1, KEY_PROJS))),
653+ operandCopy(place(LOCAL2, appendP(PROJS2, KEY_PROJS))),
654+ operandCopy(place(LOCAL1, appendP(PROJS1, DATA_BUFFER_PROJS))),
655+ operandCopy(place(LOCAL2, appendP(PROJS2, DATA_BUFFER_PROJS)))
656+ ) ~> #continueAt(TARGET)
657+ </k>
658+ requires #functionName(FUNC) ==String "cheatcode_maybe_same_account"
659+ orBool #functionName(FUNC) ==String "spl_token::entrypoint::cheatcode_maybe_same_account"
660+ [priority(30), preserves-definedness]
661+
662+ // Helper to evaluate keys and data, then apply constraint
663+ syntax KItem ::= #maybeLinkAccounts(Evaluation, Evaluation, Evaluation, Evaluation) [seqstrict]
664+
665+ // Case: keys are equal - add ensures clause to constrain SPL data equality
666+ // The ensures clause adds the constraint that all SPL fields must be equal
667+ rule <k> #maybeLinkAccounts(
668+ Aggregate(variantIdx(0), ListItem(Range(KEY1))),
669+ Aggregate(variantIdx(0), ListItem(Range(KEY2))),
670+ SPLDataBuffer(Aggregate(variantIdx(0),
671+ ListItem(Aggregate(variantIdx(0), ListItem(Range(MINT1))))
672+ ListItem(Aggregate(variantIdx(0), ListItem(Range(OWNER1))))
673+ ListItem(Integer(AMOUNT1, 64, false))
674+ ListItem(Aggregate(variantIdx(HAS_DELEG1), ListItem(Aggregate(variantIdx(0), ListItem(Range(DELEG1))))))
675+ ListItem(Aggregate(variantIdx(STATE1), .List))
676+ ListItem(Aggregate(variantIdx(HAS_NATIVE1), ListItem(Integer(NATIVE1, 64, false))))
677+ ListItem(Integer(DELEG_AMT1, 64, false))
678+ ListItem(Aggregate(variantIdx(HAS_CLOSE1), ListItem(Aggregate(variantIdx(0), ListItem(Range(CLOSE1))))))
679+ )),
680+ SPLDataBuffer(Aggregate(variantIdx(0),
681+ ListItem(Aggregate(variantIdx(0), ListItem(Range(MINT2))))
682+ ListItem(Aggregate(variantIdx(0), ListItem(Range(OWNER2))))
683+ ListItem(Integer(AMOUNT2, 64, false))
684+ ListItem(Aggregate(variantIdx(HAS_DELEG2), ListItem(Aggregate(variantIdx(0), ListItem(Range(DELEG2))))))
685+ ListItem(Aggregate(variantIdx(STATE2), .List))
686+ ListItem(Aggregate(variantIdx(HAS_NATIVE2), ListItem(Integer(NATIVE2, 64, false))))
687+ ListItem(Integer(DELEG_AMT2, 64, false))
688+ ListItem(Aggregate(variantIdx(HAS_CLOSE2), ListItem(Aggregate(variantIdx(0), ListItem(Range(CLOSE2))))))
689+ ))
690+ ) => .K ... </k>
691+ requires KEY1 ==K KEY2
692+ ensures MINT1 ==K MINT2
693+ andBool OWNER1 ==K OWNER2
694+ andBool AMOUNT1 ==Int AMOUNT2
695+ andBool HAS_DELEG1 ==Int HAS_DELEG2
696+ andBool DELEG1 ==K DELEG2
697+ andBool STATE1 ==Int STATE2
698+ andBool HAS_NATIVE1 ==Int HAS_NATIVE2
699+ andBool NATIVE1 ==Int NATIVE2
700+ andBool DELEG_AMT1 ==Int DELEG_AMT2
701+ andBool HAS_CLOSE1 ==Int HAS_CLOSE2
702+ andBool CLOSE1 ==K CLOSE2
703+ [priority(30)]
704+
705+ // Case: keys are different - no constraint needed
706+ rule <k> #maybeLinkAccounts(
707+ Aggregate(variantIdx(0), ListItem(Range(KEY1))),
708+ Aggregate(variantIdx(0), ListItem(Range(KEY2))),
709+ _, _
710+ ) => .K ... </k>
711+ requires notBool (KEY1 ==K KEY2)
712+ [priority(30)]
713+ ```
714+
627715``` k
628716endmodule
629717```
0 commit comments