Some more tips for developing secure programs on Solana.
If I had to give one piece of advice to a new Solana dev, it would be to internalize the following type alias.

```
type UnsafeAccount = AccountInfo;
```

Accounts given to a program can't be trusted, and can be a major source of problems if not handled correctly.
First, let's consider what I call account type substitution attacks.
Suppose you have a program that takes in two accounts.

1. Token Account.
2. Token Account.
3. Mint Account.

Where account 1 pays account 2 in a way that's data dependent on account 3.
For example, 1 pays 2 if the mint shares an authority with the token and the supply is greater than 10.

Easy enough.

Just make sure to authorize the accounts by deserializing in the program and then check the authority fields and supply match what is expected, right?
But what happens if you pass in for account 3) a Token account instead of a Mint account?

With the SPL token program, nothing bad. But you can imagine a program that passes these checks, while still be vulnerable to account type substitution.
So what's the solution? Prefix your account data with a unique identifier for each account "type" and check that the type is as expected whenever you deserialize, so that it's impossible to deserialize a Token account when your program expects a mint.
With Anchor ⚓️ this is done transparently for all `#[account]` types, where an account discriminator is generated and inserted into the first 8 bytes of account data.

```
sha256("account:<MyAccountName>")[..8] || borsh(account_struct)
```
Another common problem I expect to see is sharing PDAs across authority domains.

If you have a program controlled, user-specific vault holding a deposit, then ensure the PDA authority for that vault is unique to that user.
You might be tempted to use a global PDA because it seems to just represent the program. Don't.

What if a malicious user calls your instruction with his account, your vault, and the global PDA? Account validation checks might all pass, but your funds might be toast.
Account closing.

Account closing is done by transferring lamports out of a program owned account to trigger the runtime to garbage collect it, resetting the owner from the program to the system program immediately after the transaction completed successfully.
What happens if in a single transaction you have two instructions:

1. close account
2. send lamports to newly closed account

The account won't be garbage collected by the runtime. So the account isn't closed.

Oops.
Instead, one can zero out the instruction data to prevent the account data from being used again.

Great all done.

Nope.
What if you have a program-derived-address (PDA) that's being used in a stateless way by a client?

Often, a PDA maps 1-1 to an account for a specific user. So the existence of an initialized account constitutes a proof that the data is correct, e.g., associated token accounts.
In the above scenario, what happens if I close the account *and* zero the data *and* the subequent instruction refunds the rent?

The account is still around. The data is wiped. But there's a subtle flaw.

One can re-initialize the same account with different data.
This means we can have an account with an address that's a deterministic function of one user but actually belongs to another, violating the invariant posed above.
With Anchor ⚓️, we solve this by replacing the account discriminator with a special "is closed" discriminator, causing all subsequent deserializations to fail.

All done via 🦀 codegen, of course.
As the famous philosopher mvines once said. Belt and suspenders.
Fun fact.

Trying to wrangle the complexity of account validation on Solana is where the oh so creative name "Anchor" comes from.
Sea level accounts are an ocean of flowing, untrusted data that can cause your program to do drift in uncertain directions. You need an Anchor to lock it down.
How do you create an Anchor?

Use the `#[derive(Accounts)]` macro and associated eDSL to simplify and isolate account validation into a single focused area of your program, separated from the business logic of your instruction handler.
If you got all the way to the end of the thread. Bless your soul.
Bonus content?

It's Saturday night and I'm on the other end of the Balmer's peak, so why not.
A useful property to have, if you can get away with it, is "balance isolation". Never mix funds belonging to one user with funds belonging to another user in the same account.

Why? If there's a bug in calculating withdrawals, it's less likely it will affect entire pool deposit.
Another useful property to have is "gulping".

Instead of tracking balances with separate bookkeeping variables, just use token accounts and mints to transfer back and forth between vaults.

It's more expensive but more robust. The token account is the source of truth.

• • •

Missing some Tweet in this thread? You can try to force a refresh
 

Keep Current with Armani Ferrante

Armani Ferrante Profile picture

Stay in touch and get notified when new unrolls are available from this author!

Read all threads

This Thread may be Removed Anytime!

PDF

Twitter may remove this content at anytime! Save it as PDF for later use!

Try unrolling a thread yourself!

how to unroll video
  1. Follow @ThreadReaderApp to mention us!

  2. From a Twitter thread mention us with a keyword "unroll"
@threadreaderapp unroll

Practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just two indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3/month or $30/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!

Follow Us on Twitter!

:(