What happens inside Solana when you deploy a smart contract to the Solana Mainnet? Can a Solana program be modified or closed? How to upgrade a Solana program? Who is authorized to change a Solana program?
This article focuses on the upgradability of Solana programs and highlights some intricacies.
Here is a list of take-away notes:
Every user-deployed smart contract on Solana is associated with a Solana program account, which has a number of important attributes: program_id, owner , program_data , authority , etc.
Note that these information attributes are not stored in a single executable program. The program data and authority are actually stored in a separate account (programdata) derived from the program_id. See this article for further details (credit: starry.sol and tmpjail)
The program_id is the address of the Solana program. We use jet-v1 (program_id JPv1rCqrhagNNmJVM5J1he7msQ5ybtvE1nNuHpDHMNU) as an example. Figure 1 shows a screen shot from explorer.solana.com.
There are several things to note:
The executable_data account contains the actual BPF bytecode of jet-v1, and its data length is 1827341 bytes (>1.8MB), as shown in Figure 2 below.
To show detailed info of jet-v1’s Solana program account in the terminal, run:
To show detailed info of jet-v1’s Solana executable_data account in the terminal, run:
According to Solana documentation, to deploy a Solana program, e.g., jet-v1, simply run the following command, which uploads the compiled BPF bytecode (i.e., an ELF shared object jet.so) to the Solana cluster:
However, behind solana program deploy , deploying a Solana program is fairly complicated, and it can take many transactions:
Step 1 is done by submitting a transaction with a system_instruction::create_account instruction:
The owner of a program account can also be BPFLoaderUpgradeab1e . In fact, by default all user-deployed Solana programs are deployed with BPFLoaderUpgradeab1e and hence are upgradable.
Note: in this context loader_id cannot be BPFLoaderUpgradeab1e. The deployment steps for BPFLoaderUpgradeab1e are different from BPFLoader2 and BPFLoader, in that all steps happen in a single transaction. See more detail in Sec. “Deploying an upgradeable Solana program”. (credit: BlockBandit suggested discussing BPFLoaderUpgradeab1e here to avoid confusion)
Step 2 is done by first verifying the BPF bytecode (off-chain)
and then submitting one or more transactions to upload the bytecode to the data buffer account via the LoaderInstruction::Write instruction:
Note that a transaction on Solana has a maximal size: solana_sdk::packet:: PACKET_DATA_SIZE(less than 1280 bytes, determined by IPv6 packet limit).
For BPF bytecode with data length larger than PACKET_DATA_SIZE , it has to be split into multiple small chunks and submit a transaction for each chunk.
The parameters offset and bytes to loader_instruction::write specify the offset of a chunk and the maximal chunk size, respectively.
For a typical Solana program, this step can take hundreds or more thousands. For instance, deploying jet-v1 takes ~1500 transactions (less than a second on Solana on average).
This step submits a transaction with the LoaderInstruction::Finalize instruction :
The instruction will invoke the bpf_loader (with loader_id) in the Solana runtime, which sets the program’s executable flag to true:
By default, all user-deployed Solana programs are deployed with the BPFLoaderUpgradeab1e (i.e., bpf_loader_upgradeable::id()) loader.
The deployment submits a single transaction with the UpgradeableLoaderInstruction::DeployWithMaxDataLen instruction:
The instruction will invoke the BPFLoaderUpgradeab1e loader in the Solana runtime, which creates a ProgramData account to store the buffer data, and finally sets the program executable.
To allocate space for future upgrade, the max_data_len of the ProgramData account is set to twice the size of the BPF bytecode.
Solana programs can be upgraded by default. That is, it is possible to redeploy a new shared object (BPF bytecode) to the same Solana program account.
This can be done by the program’s upgrade authority, which can be specified during the original deployment by --upgrade-authority <UPGRADE_AUTHORITY_SIGNER>, otherwise it is set to be the default configured keypair.
The program’s upgrade authority can also be changed to a new_authority by the UpgradeableLoaderInstruction::SetAuthority instruction (in a transaction signed by the current upgrade authority).
When a Solana program is redeployed by the upgrade authority, it first creates a new data buffer account for the new BPF bytecode, and then invokes the UpgradeableLoaderInstruction::Upgrade instruction to update the ProgramData account to store the new buffer data.
Solana also provides an option --final to use BPFLoader2 at the deployment time (when --final is provided and the program will not be upgradeable).
If any changes are required to the finalized program (features, patches, etc…) the new program must be deployed to a new program ID.
Both Solana program and buffer accounts can be closed by their upgrade authority, and their lamport balances will be transferred to a recipient’s account.
To close a program account:
Internally, it invokes the UpgradeableLoaderInstruction::Close instruction, which updates the account lamports and sets the state of close_account to UpgradeableLoaderState::Uninitialized
The upgradability of smart contracts is a distinctive feature of Solana compared to Ethereum. This design makes Solana applications easier to incorporate new features. However, there are a few caveats:
The upgrade authority must be securely managed. If the upgrade authority becomes evil or the private key is obtained by an attacker, then the Solana program can be close directly or changed at anytime to lock or steal users’ fund.
If you are using an upgradable program, find a way to be notified whenever the program is upgraded to avoid malicious behaviors such as Rug pull.
Even tiny incremental program changes can introduce new security vulnerabilities, and must be carefully tested and audited.
As an example, recently, a critical vulnerability was discovered in jet-v1 due to an ad hoc upgrade to include a new feature. Luckily, the vulnerability was first found and reported by a white hat. See detail in this tweet.
sec3 is a security research firm that prepares Solana projects for millions of users. sec3’s Launch Audit is a rigorous, researcher-led code examination that investigates and certifies mainnet-grade smart contracts; sec3’s continuous auditing software platform, X-ray, integrates with GitHub to progressively scan pull requests, helping projects fortify code before deployment; and sec3’s post-deployment security solution, WatchTower, ensures funds stay safe. sec3 is building technology-based scalable solutions for Web3 projects to ensure protocols stay safe as they scale.
To learn more about sec3, please visit https://www.sec3.dev