Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

Why Anchor Accounts Go Stale After CPI (and When to Reload)- 1860

Taichi AuditsPosted 2 Months Ago
  • When making a Cross Program Invocation (CPI) in Solana via invoke or invoke_signed, you provide a set of accounts to be used. In raw Solana, you pass in AccountInfo directly, which is a handle to the in-memory runtime state. In Anchor, you pass in Account<'info, T>., which is a deserialized version of T and acts as a cached value.
  • Native Solana programs do not operate on the ledger directly. Instead, accounts are loaded into the runtime as a working set. Instructions mutate this in-memory state. Many things, like lamports, are read directly from the runtime state every time. If you reborrow the data, then the underlying bytes will also be updated.
  • In Anchor, the type T on the AccountInfo is a deserialized snapshot of the account data bytes. At the start of the instruction, Anchor constructs the accounts by deserializing them in a generated handler from the info.data on the account. This means that the data is copied onto the stack/heap as a Rust value and is NOT a live reference to the runtime bytes. At the end of the instruction, Anchor will serialize the data structure and write it back to the runtime.
  • In practice, this has a strange consequence: if a CPI modifies an account, the cached version will have stale data. For instance, for balance on a token account, a token transfer would show the same balance before and after the CPI, regardless of whether the account balance changed.
  • To solve this problem, Anchor accounts have reload(). This will reload the data from storage via re-reading and deserializing the data within AccountInfo.data. The account data is now no longer stale.
  • The author gives some tips on when to call reload(). It's required when A) a CPI can be used to mutate account data, B) the account needs to be read/validated later and C) you are reading a cached struct. If lamports or native runtime fields are being read, then reloading isn't necessary.
  • Overall, a great post on Solana CPI reloading and why it must be done. I had always wondered why lamports didn't need to be reloaded but the data did; now I know!