If you notice some outdated information please let us know!
PASS
The final review score is indicated as a percentage. The percentage is calculated as Achieved Points due to MAX Possible Points. For each element the answer can be either Yes/No or a percentage. For a detailed breakdown of the individual weights of each question, please consult this document.
Very simply, the audit looks for the following declarations from the developer's site. With these declarations, it is reasonable to trust the smart contracts.
This report is for informational purposes only and does not constitute investment advice of any kind, nor does it constitute an offer to provide investment advisory or other services. Nothing in this report shall be considered a solicitation or offer to buy or sell any security, token, future, option or other financial instrument or to offer or provide any investment advice or service to any person in any jurisdiction. Nothing contained in this report constitutes investment advice or offers any opinion with respect to the suitability of any security, and the views expressed in this report should not be taken as advice to buy, sell or hold any security. The information in this report should not be relied upon for the purpose of investing. In preparing the information contained in this report, we have not taken into account the investment needs, objectives and financial circumstances of any particular investor. This information has no regard to the specific investment objectives, financial situation and particular needs of any specific recipient of this information and investments discussed may not be suitable for all investors.
Any views expressed in this report by us were prepared based upon the information available to us at the time such views were written. The views expressed within this report are limited to DeFiSafety and the author and do not reflect those of any additional or third party and are strictly based upon DeFiSafety, its authors, interpretations and evaluation of relevant data. Changed or additional information could cause such views to change. All information is subject to possible correction. Information may quickly become unreliable for various reasons, including changes in market conditions or economic circumstances.
This completed report is copyright (c) DeFiSafety 2021. Permission is given to copy in whole, retaining this copyright label.
This section looks at the code deployed on the relevant chain that gets reviewed and its corresponding software repository. The document explaining these questions is here.
1. Are the smart contract addresses easy to find? (%)
They can be found at https://docs.marinade.finance/developers/contract-addresses, as indicated in the Appendix.
2. How active is the primary contract? (%)
Contract MainState is used over 10 times a day, as indicated in the Appendix.
3. Does the protocol have a public software repository? (Y/N)
Location: https://github.com/marinade-finance
4. Is there a development history visible? (%)
At 7 commits in the main program repository, there is limited developer history documented.
5. Is the team public (not anonymous)?
Multiple public team members could be found on their Twitter, and they confirm their contributions to Marinade.
This section looks at the software documentation. The document explaining these questions is here.
6. Is there a whitepaper? (Y/N)
Location: https://docs.marinade.finance/
7. Is the protocol's software architecture documented? (Y/N)
This protocol's software architecture is documented in Marinade's GitBook.
8. Does the software documentation fully cover the deployed contracts' source code? (%)
There is full coverage of deployed contracts by software function documentation.
9. Is it possible to trace the documented software to its implementation in the protocol's source code? (%)
There is explicit traceability between software documentation and implemented code. Users can see exactly which contract is referenced in the documentation.
10. Has the protocol tested their deployed code? (%)
There is no testing documented in Marinade's GitHub repository.
11. How covered is the protocol's code? (%)
No testing for code coverage was documented in Marinade's repository.
12. Does the protocol provide scripts and instructions to run their tests? (Y/N)
Scripts/Instructions location: https://github.com/marinade-finance/liquid-staking-program/tree/main/scripts
13. Is there a detailed report of the protocol's test results?(%)
No test report is documented in Marinade's repository.
14. Has the protocol undergone Formal Verification? (Y/N)
Marinade has not undergone undergone formal verification.
15. Were the smart contracts deployed to a testnet? (Y/N)
This protocol has documented deployment to a testnet. Marinade uses Solana's Devnet.
This section looks at the 3rd party software audits done. It is explained in this document.
16. Is the protocol sufficiently audited? (%)
Marinade has undergone 2 formal audits and 1 code review. Each of these were performed post-deployment. This was conducted after deployment. Kudelski Security and Ackee Blockchain conducted these audits. Neodyme conducted a code review.
17. Is the bounty value acceptably high (%)
This protocol offers an active bug bounty of $250K
This section covers the documentation of special access controls for a DeFi protocol. The admin access controls are the contracts that allow updating contracts or coefficients in the protocol. Since these contracts can allow the protocol admins to "change the rules", complete disclosure of capabilities is vital for user's transparency. It is explained in this document.
18. Is the protocol's admin control information easy to find?
Admin control information was clearly documented at this location. This was quick to find.
19. Are relevant contracts clearly labelled as upgradeable or immutable? (%)
The relevant contracts are clearly identified as immutable / upgradeable, as identified here.
20. Is the type of smart contract ownership clearly indicated? (%)
Ownership is clearly indicated in this location. Various multisigs are in charge of Marinade's separate functions.
21. Are the protocol's smart contract change capabilities described? (%)
Smart contract change capabilities are well identified in relevant contracts. Their documentation clearly identifies what parameters can be adjusted and under what circumstances these changes can be triggered.
22. Is the protocol's admin control information easy to understand? (%)
This information is is in software specific language. Non-technical users may struggle to comprehend who is in control of the contracts.
23. Is there sufficient Pause Control documentation? (%)
Marinade's pause control is not documented or explained.
24. Is there sufficient Timelock documentation? (%)
Marinade has no timelock documentation.
25. Is the Timelock of an adequate length? (Y/N)
Marinade has no timelock documentation.
This section goes over the documentation that a protocol may or may not supply about their Oracle usage. Oracles are a fundamental part of DeFi as they are responsible for relaying tons of price data information to thousands of protocols using blockchain technology. Not only are they important for price feeds, but they are also an essential component of transaction verification and security. These questions are explained in this document.
26. Is the protocol's Oracle sufficiently documented? (%)
Marinade's oracle source is well documented at this location. The contracts dependent are identified. There is good relevant software function documentation. It uses Pyth.
27. Is front running mitigated by this protocol? (Y/N)
This protocol documents front running mitigation techniques at this location. This is primarily in the form of specifying an interest in any bug relating to MEV applicable to Marinade.
28. Can flashloan attacks be applied to the protocol, and if so, are those flashloan attack risks mitigated? (Y/N)
This protocol documents some flashloan countermeasures at this location. As with MEV, this is in the form of stated interest in flashloan exploits being disclosed via bug bounty.
1use crate::{checks::check_address, list::List, located::Located, State, ID};
2use anchor_lang::prelude::*;
3use anchor_lang::solana_program::clock::Epoch;
4
5pub mod deactivate_stake;
6pub mod deposit_stake_account;
7pub mod emergency_unstake;
8pub mod merge;
9pub mod stake_reserve;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, AnchorSerialize, AnchorDeserialize)]
12pub struct StakeRecord {
13 pub stake_account: Pubkey,
14 pub last_update_delegated_lamports: u64,
15 pub last_update_epoch: u64,
16 pub is_emergency_unstaking: u8, // 1 for cooling down after emergency unstake, 0 otherwise
17}
18
19impl StakeRecord {
20 pub const DISCRIMINATOR: &'static [u8; 8] = b"staker__";
21
22 pub fn new(stake_account: &Pubkey, delegated_lamports: u64, clock: &Clock) -> Self {
23 Self {
24 stake_account: *stake_account,
25 last_update_delegated_lamports: delegated_lamports,
26 last_update_epoch: clock.epoch,
27 is_emergency_unstaking: 0,
28 }
29 }
30}
31
32#[derive(Clone, AnchorSerialize, AnchorDeserialize, Debug)]
33pub struct StakeSystem {
34 pub stake_list: List,
35 //pub last_update_epoch: u64,
36 //pub updated_during_last_epoch: u32,
37 pub delayed_unstake_cooling_down: u64,
38 pub stake_deposit_bump_seed: u8,
39 pub stake_withdraw_bump_seed: u8,
40
41 /// set by admin, how much slots before the end of the epoch, stake-delta can start
42 pub slots_for_stake_delta: u64,
43 /// Marks the start of stake-delta operations, meaning that if somebody starts a delayed-unstake ticket
44 /// after this var is set with epoch_num the ticket will have epoch_created = current_epoch+1
45 /// (the user must wait one more epoch, because their unstake-delta will be execute in this epoch)
46 pub last_stake_delta_epoch: u64,
47 pub min_stake: u64, // Minimal stake account delegation
48 /// can be set by validator-manager-auth to allow a second run of stake-delta to stake late stakers in the last minute of the epoch
49 /// so we maximize user's rewards
50 pub extra_stake_delta_runs: u32,
51}
52
53impl StakeSystem {
54 pub const STAKE_WITHDRAW_SEED: &'static [u8] = b"withdraw";
55 pub const STAKE_DEPOSIT_SEED: &'static [u8] = b"deposit";
56
57 pub fn bytes_for_list(count: u32, additional_record_space: u32) -> u32 {
58 List::bytes_for(
59 StakeRecord::default().try_to_vec().unwrap().len() as u32 + additional_record_space,
60 count,
61 )
62 }
63
64 /*
65 pub fn list_capacity(account_len: usize) -> u32 {
66 List::<StakeDiscriminator, StakeRecord, u32>::capacity_of(
67 StakeRecord::default().try_to_vec().unwrap().len() as u32,
68 account_len,
69 )
70 }*/
71
72 pub fn find_stake_withdraw_authority(state: &Pubkey) -> (Pubkey, u8) {
73 Pubkey::find_program_address(&[&state.to_bytes()[..32], Self::STAKE_WITHDRAW_SEED], &ID)
74 }
75
76 pub fn find_stake_deposit_authority(state: &Pubkey) -> (Pubkey, u8) {
77 Pubkey::find_program_address(&[&state.to_bytes()[..32], Self::STAKE_DEPOSIT_SEED], &ID)
78 }
79
80 pub fn new(
81 state: &Pubkey,
82 stake_list_account: Pubkey,
83 stake_list_data: &mut [u8],
84 slots_for_stake_delta: u64,
85 min_stake: u64,
86 extra_stake_delta_runs: u32,
87 additional_record_space: u32,
88 ) -> Result<Self, ProgramError> {
89 let stake_list = List::new(
90 StakeRecord::DISCRIMINATOR,
91 StakeRecord::default().try_to_vec().unwrap().len() as u32 + additional_record_space,
92 stake_list_account,
93 stake_list_data,
94 "stake_list",
95 )?;
96
97 Ok(Self {
98 stake_list,
99 delayed_unstake_cooling_down: 0,
100 stake_deposit_bump_seed: Self::find_stake_deposit_authority(state).1,
101 stake_withdraw_bump_seed: Self::find_stake_withdraw_authority(state).1,
102 slots_for_stake_delta,
103 last_stake_delta_epoch: Epoch::MAX, // never
104 min_stake,
105 extra_stake_delta_runs,
106 })
107 }
108
109 pub fn stake_list_address(&self) -> &Pubkey {
110 &self.stake_list.account
111 }
112
113 pub fn stake_count(&self) -> u32 {
114 self.stake_list.len()
115 }
116
117 pub fn stake_list_capacity(&self, stake_list_len: usize) -> Result<u32, ProgramError> {
118 self.stake_list.capacity(stake_list_len)
119 }
120
121 pub fn stake_record_size(&self) -> u32 {
122 self.stake_list.item_size()
123 }
124
125 pub fn add(
126 &mut self,
127 stake_list_data: &mut [u8],
128 stake_account: &Pubkey,
129 delegated_lamports: u64,
130 clock: &Clock,
131 ) -> ProgramResult {
132 self.stake_list.push(
133 stake_list_data,
134 StakeRecord::new(stake_account, delegated_lamports, clock),
135 "stake_list",
136 )?;
137 Ok(())
138 }
139
140 pub fn get(&self, stake_list_data: &[u8], index: u32) -> Result<StakeRecord, ProgramError> {
141 self.stake_list.get(stake_list_data, index, "stake_list")
142 }
143
144 pub fn set(&self, stake_list_data: &mut [u8], index: u32, stake: StakeRecord) -> ProgramResult {
145 self.stake_list
146 .set(stake_list_data, index, stake, "stake_list")
147 }
148 pub fn remove(&mut self, stake_list_data: &mut [u8], index: u32) -> ProgramResult {
149 self.stake_list.remove(stake_list_data, index, "stake_list")
150 }
151
152 pub fn check_stake_list<'info>(&self, stake_list: &AccountInfo<'info>) -> ProgramResult {
153 check_address(stake_list.key, self.stake_list_address(), "stake_list")?;
154 if &stake_list.data.borrow().as_ref()[0..8] != StakeRecord::DISCRIMINATOR {
155 msg!("Wrong stake list account discriminator");
156 return Err(ProgramError::InvalidAccountData);
157 }
158 Ok(())
159 }
160}
161
162pub trait StakeSystemHelpers {
163 fn stake_withdraw_authority(&self) -> Pubkey;
164 fn with_stake_withdraw_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R;
165 fn check_stake_withdraw_authority(&self, stake_withdraw_authority: &Pubkey) -> ProgramResult;
166
167 fn stake_deposit_authority(&self) -> Pubkey;
168 fn with_stake_deposit_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R;
169 fn check_stake_deposit_authority(&self, stake_deposit_authority: &Pubkey) -> ProgramResult;
170}
171
172impl<T> StakeSystemHelpers for T
173where
174 T: Located<State>,
175{
176 fn stake_withdraw_authority(&self) -> Pubkey {
177 self.with_stake_withdraw_authority_seeds(|seeds| {
178 Pubkey::create_program_address(seeds, &ID).unwrap()
179 })
180 }
181
182 fn with_stake_withdraw_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R {
183 f(&[
184 &self.key().to_bytes()[..32],
185 StakeSystem::STAKE_WITHDRAW_SEED,
186 &[self.as_ref().stake_system.stake_withdraw_bump_seed],
187 ])
188 }
189
190 fn check_stake_withdraw_authority(&self, stake_withdraw_authority: &Pubkey) -> ProgramResult {
191 check_address(
192 stake_withdraw_authority,
193 &self.stake_withdraw_authority(),
194 "stake_withdraw_authority",
195 )
196 }
197
198 fn stake_deposit_authority(&self) -> Pubkey {
199 self.with_stake_deposit_authority_seeds(|seeds| {
200 Pubkey::create_program_address(seeds, &ID).unwrap()
201 })
202 }
203
204 fn with_stake_deposit_authority_seeds<R, F: FnOnce(&[&[u8]]) -> R>(&self, f: F) -> R {
205 f(&[
206 &self.key().to_bytes()[..32],
207 StakeSystem::STAKE_DEPOSIT_SEED,
208 &[self.as_ref().stake_system.stake_deposit_bump_seed],
209 ])
210 }
211
212 fn check_stake_deposit_authority(&self, stake_deposit_authority: &Pubkey) -> ProgramResult {
213 check_address(
214 stake_deposit_authority,
215 &self.stake_deposit_authority(),
216 "stake_deposit_authority",
217 )
218 }
219}