1#![deny(rustdoc::all)]
22#![allow(rustdoc::missing_doc_code_examples)]
23#![deny(clippy::unwrap_used)]
24
25use anchor_lang::prelude::*;
26use anchor_lang::solana_program;
27use vipers::prelude::*;
28
29mod events;
30mod instructions;
31mod state;
32mod validators;
33
34pub use events::*;
35pub use instructions::*;
36pub use state::*;
37
38pub const SECONDS_PER_DAY: i64 = 60 * 60 * 24;
40
41pub const MAX_DELAY_SECONDS: i64 = 365 * SECONDS_PER_DAY;
43
44pub const DEFAULT_GRACE_PERIOD: i64 = 14 * SECONDS_PER_DAY;
46
47pub const NO_ETA: i64 = -1;
49
50declare_id!("GokivDYuQXPZCWRkwMhdH2h91KpDQXBEmpgBgs55bnpH");
51
52#[program]
53#[deny(missing_docs)]
54pub mod smart_wallet {
56 use super::*;
57
58 #[access_control(ctx.accounts.validate())]
60 pub fn create_smart_wallet(
61 ctx: Context<CreateSmartWallet>,
62 _bump: u8,
63 max_owners: u8,
64 owners: Vec<Pubkey>,
65 threshold: u64,
66 minimum_delay: i64,
67 ) -> Result<()> {
68 invariant!(minimum_delay >= 0, "delay must be positive");
69 invariant!(minimum_delay < MAX_DELAY_SECONDS, DelayTooHigh);
70
71 invariant!((max_owners as usize) >= owners.len(), "max_owners");
72
73 let smart_wallet = &mut ctx.accounts.smart_wallet;
74 smart_wallet.base = ctx.accounts.base.key();
75 smart_wallet.bump = *unwrap_int!(ctx.bumps.get("smart_wallet"));
76
77 smart_wallet.threshold = threshold;
78 smart_wallet.minimum_delay = minimum_delay;
79 smart_wallet.grace_period = DEFAULT_GRACE_PERIOD;
80
81 smart_wallet.owner_set_seqno = 0;
82 smart_wallet.num_transactions = 0;
83
84 smart_wallet.owners = owners.clone();
85
86 emit!(WalletCreateEvent {
87 smart_wallet: ctx.accounts.smart_wallet.key(),
88 owners,
89 threshold,
90 minimum_delay,
91 timestamp: Clock::get()?.unix_timestamp
92 });
93 Ok(())
94 }
95
96 #[access_control(ctx.accounts.validate())]
99 pub fn set_owners(ctx: Context<Auth>, owners: Vec<Pubkey>) -> Result<()> {
100 let smart_wallet = &mut ctx.accounts.smart_wallet;
101 if (owners.len() as u64) < smart_wallet.threshold {
102 smart_wallet.threshold = owners.len() as u64;
103 }
104
105 smart_wallet.owners = owners.clone();
106 smart_wallet.owner_set_seqno = unwrap_int!(smart_wallet.owner_set_seqno.checked_add(1));
107
108 emit!(WalletSetOwnersEvent {
109 smart_wallet: ctx.accounts.smart_wallet.key(),
110 owners,
111 timestamp: Clock::get()?.unix_timestamp
112 });
113 Ok(())
114 }
115
116 #[access_control(ctx.accounts.validate())]
120 pub fn change_threshold(ctx: Context<Auth>, threshold: u64) -> Result<()> {
121 invariant!(
122 threshold <= ctx.accounts.smart_wallet.owners.len() as u64,
123 InvalidThreshold
124 );
125 let smart_wallet = &mut ctx.accounts.smart_wallet;
126 smart_wallet.threshold = threshold;
127
128 emit!(WalletChangeThresholdEvent {
129 smart_wallet: ctx.accounts.smart_wallet.key(),
130 threshold,
131 timestamp: Clock::get()?.unix_timestamp
132 });
133 Ok(())
134 }
135
136 pub fn create_transaction(
139 ctx: Context<CreateTransaction>,
140 bump: u8,
141 instructions: Vec<TXInstruction>,
142 ) -> Result<()> {
143 create_transaction_with_timelock(ctx, bump, instructions, NO_ETA)
144 }
145
146 #[access_control(ctx.accounts.validate())]
148 pub fn create_transaction_with_timelock(
149 ctx: Context<CreateTransaction>,
150 _bump: u8,
151 instructions: Vec<TXInstruction>,
152 eta: i64,
153 ) -> Result<()> {
154 let smart_wallet = &ctx.accounts.smart_wallet;
155 let owner_index = smart_wallet.try_owner_index(ctx.accounts.proposer.key())?;
156
157 let clock = Clock::get()?;
158 let current_ts = clock.unix_timestamp;
159 if smart_wallet.minimum_delay != 0 {
160 invariant!(
161 eta >= unwrap_int!(current_ts.checked_add(smart_wallet.minimum_delay as i64)),
162 InvalidETA
163 );
164 }
165 if eta != NO_ETA {
166 invariant!(eta >= 0, "ETA must be positive");
167 let delay = unwrap_int!(eta.checked_sub(current_ts));
168 invariant!(delay >= 0, "ETA must be in the future");
169 invariant!(delay <= MAX_DELAY_SECONDS, DelayTooHigh);
170 }
171
172 let owners = &smart_wallet.owners;
174 let mut signers = Vec::new();
175 signers.resize(owners.len(), false);
176 signers[owner_index] = true;
177
178 let index = smart_wallet.num_transactions;
179 let smart_wallet = &mut ctx.accounts.smart_wallet;
180 smart_wallet.num_transactions = unwrap_int!(smart_wallet.num_transactions.checked_add(1));
181
182 let tx = &mut ctx.accounts.transaction;
184 tx.smart_wallet = smart_wallet.key();
185 tx.index = index;
186 tx.bump = *unwrap_int!(ctx.bumps.get("transaction"));
187
188 tx.proposer = ctx.accounts.proposer.key();
189 tx.instructions = instructions.clone();
190 tx.signers = signers;
191 tx.owner_set_seqno = smart_wallet.owner_set_seqno;
192 tx.eta = eta;
193
194 tx.executor = Pubkey::default();
195 tx.executed_at = -1;
196
197 emit!(TransactionCreateEvent {
198 smart_wallet: ctx.accounts.smart_wallet.key(),
199 transaction: ctx.accounts.transaction.key(),
200 proposer: ctx.accounts.proposer.key(),
201 instructions,
202 eta,
203 timestamp: Clock::get()?.unix_timestamp
204 });
205 Ok(())
206 }
207
208 #[access_control(ctx.accounts.validate())]
210 pub fn approve(ctx: Context<Approve>) -> Result<()> {
211 instructions::approve::handler(ctx)
212 }
213
214 #[access_control(ctx.accounts.validate())]
216 pub fn unapprove(ctx: Context<Approve>) -> Result<()> {
217 instructions::unapprove::handler(ctx)
218 }
219
220 #[access_control(ctx.accounts.validate())]
222 pub fn execute_transaction(ctx: Context<ExecuteTransaction>) -> Result<()> {
223 let smart_wallet = &ctx.accounts.smart_wallet;
224 let wallet_seeds: &[&[&[u8]]] = &[&[
225 b"GokiSmartWallet" as &[u8],
226 &smart_wallet.base.to_bytes(),
227 &[smart_wallet.bump],
228 ]];
229 do_execute_transaction(ctx, wallet_seeds)
230 }
231
232 #[access_control(ctx.accounts.validate())]
236 pub fn execute_transaction_derived(
237 ctx: Context<ExecuteTransaction>,
238 index: u64,
239 bump: u8,
240 ) -> Result<()> {
241 let smart_wallet = &ctx.accounts.smart_wallet;
242 let wallet_seeds: &[&[&[u8]]] = &[&[
244 b"GokiSmartWalletDerived" as &[u8],
245 &smart_wallet.key().to_bytes(),
246 &index.to_le_bytes(),
247 &[bump],
248 ]];
249 do_execute_transaction(ctx, wallet_seeds)
250 }
251
252 #[access_control(ctx.accounts.validate())]
258 pub fn owner_invoke_instruction(
259 ctx: Context<OwnerInvokeInstruction>,
260 index: u64,
261 bump: u8,
262 ix: TXInstruction,
263 ) -> Result<()> {
264 let smart_wallet = &ctx.accounts.smart_wallet;
265 let invoker_seeds: &[&[&[u8]]] = &[&[
267 b"GokiSmartWalletOwnerInvoker" as &[u8],
268 &smart_wallet.key().to_bytes(),
269 &index.to_le_bytes(),
270 &[bump],
271 ]];
272
273 solana_program::program::invoke_signed(
274 &(&ix).into(),
275 ctx.remaining_accounts,
276 invoker_seeds,
277 )?;
278
279 Ok(())
280 }
281
282 #[access_control(ctx.accounts.validate())]
294 pub fn owner_invoke_instruction_v2(
295 ctx: Context<OwnerInvokeInstruction>,
296 index: u64,
297 bump: u8,
298 invoker: Pubkey,
299 data: Vec<u8>,
300 ) -> Result<()> {
301 let smart_wallet = &ctx.accounts.smart_wallet;
302 let invoker_seeds: &[&[&[u8]]] = &[&[
304 b"GokiSmartWalletOwnerInvoker" as &[u8],
305 &smart_wallet.key().to_bytes(),
306 &index.to_le_bytes(),
307 &[bump],
308 ]];
309
310 let program_id = ctx.remaining_accounts[0].key();
311 let accounts: Vec<AccountMeta> = ctx.remaining_accounts[1..]
312 .iter()
313 .map(|v| AccountMeta {
314 pubkey: *v.key,
315 is_signer: if v.key == &invoker { true } else { v.is_signer },
316 is_writable: v.is_writable,
317 })
318 .collect();
319 let ix = &solana_program::instruction::Instruction {
320 program_id,
321 accounts,
322 data,
323 };
324
325 solana_program::program::invoke_signed(ix, ctx.remaining_accounts, invoker_seeds)?;
326 Ok(())
327 }
328
329 #[access_control(ctx.accounts.validate())]
332 pub fn create_subaccount_info(
333 ctx: Context<CreateSubaccountInfo>,
334 _bump: u8,
335 subaccount: Pubkey,
336 smart_wallet: Pubkey,
337 index: u64,
338 subaccount_type: SubaccountType,
339 ) -> Result<()> {
340 let (address, _derived_bump) = match subaccount_type {
341 SubaccountType::Derived => Pubkey::find_program_address(
342 &[
343 b"GokiSmartWalletDerived" as &[u8],
344 &smart_wallet.to_bytes(),
345 &index.to_le_bytes(),
346 ],
347 &crate::ID,
348 ),
349 SubaccountType::OwnerInvoker => Pubkey::find_program_address(
350 &[
351 b"GokiSmartWalletOwnerInvoker" as &[u8],
352 &smart_wallet.to_bytes(),
353 &index.to_le_bytes(),
354 ],
355 &crate::ID,
356 ),
357 };
358
359 invariant!(address == subaccount, SubaccountOwnerMismatch);
360
361 let info = &mut ctx.accounts.subaccount_info;
362 info.smart_wallet = smart_wallet;
363 info.subaccount_type = subaccount_type;
364 info.index = index;
365
366 Ok(())
367 }
368}
369
370#[derive(Accounts)]
372#[instruction(bump: u8, max_owners: u8)]
373pub struct CreateSmartWallet<'info> {
374 pub base: Signer<'info>,
376
377 #[account(
379 init,
380 seeds = [
381 b"GokiSmartWallet".as_ref(),
382 base.key().to_bytes().as_ref()
383 ],
384 bump,
385 payer = payer,
386 space = SmartWallet::space(max_owners),
387 )]
388 pub smart_wallet: Account<'info, SmartWallet>,
389
390 #[account(mut)]
392 pub payer: Signer<'info>,
393
394 pub system_program: Program<'info, System>,
396}
397
398#[derive(Accounts)]
400pub struct Auth<'info> {
401 #[account(mut, signer)]
403 pub smart_wallet: Account<'info, SmartWallet>,
404}
405
406#[derive(Accounts)]
408#[instruction(bump: u8, instructions: Vec<TXInstruction>)]
409pub struct CreateTransaction<'info> {
410 #[account(mut)]
412 pub smart_wallet: Account<'info, SmartWallet>,
413 #[account(
415 init,
416 seeds = [
417 b"GokiTransaction".as_ref(),
418 smart_wallet.key().to_bytes().as_ref(),
419 smart_wallet.num_transactions.to_le_bytes().as_ref()
420 ],
421 bump,
422 payer = payer,
423 space = Transaction::space(instructions),
424 )]
425 pub transaction: Account<'info, Transaction>,
426 pub proposer: Signer<'info>,
428 #[account(mut)]
430 pub payer: Signer<'info>,
431 pub system_program: Program<'info, System>,
433}
434
435#[derive(Accounts)]
437pub struct ExecuteTransaction<'info> {
438 pub smart_wallet: Account<'info, SmartWallet>,
440 #[account(mut)]
442 pub transaction: Account<'info, Transaction>,
443 pub owner: Signer<'info>,
445}
446
447#[derive(Accounts)]
449pub struct OwnerInvokeInstruction<'info> {
450 pub smart_wallet: Account<'info, SmartWallet>,
452 pub owner: Signer<'info>,
454}
455
456#[derive(Accounts)]
458#[instruction(bump: u8, subaccount: Pubkey)]
459pub struct CreateSubaccountInfo<'info> {
460 #[account(
462 init,
463 seeds = [
464 b"GokiSubaccountInfo".as_ref(),
465 &subaccount.to_bytes()
466 ],
467 bump,
468 payer = payer,
469 space = 8 + SubaccountInfo::LEN
470 )]
471 pub subaccount_info: Account<'info, SubaccountInfo>,
472 #[account(mut)]
474 pub payer: Signer<'info>,
475 pub system_program: Program<'info, System>,
477}
478
479fn do_execute_transaction(ctx: Context<ExecuteTransaction>, seeds: &[&[&[u8]]]) -> Result<()> {
480 for ix in ctx.accounts.transaction.instructions.iter() {
481 solana_program::program::invoke_signed(&(ix).into(), ctx.remaining_accounts, seeds)?;
482 }
483
484 let tx = &mut ctx.accounts.transaction;
486 tx.executor = ctx.accounts.owner.key();
487 tx.executed_at = Clock::get()?.unix_timestamp;
488
489 emit!(TransactionExecuteEvent {
490 smart_wallet: ctx.accounts.smart_wallet.key(),
491 transaction: ctx.accounts.transaction.key(),
492 executor: ctx.accounts.owner.key(),
493 timestamp: Clock::get()?.unix_timestamp
494 });
495 Ok(())
496}
497
498#[error_code]
500pub enum ErrorCode {
501 #[msg("The given owner is not part of this smart wallet.")]
502 InvalidOwner,
503 #[msg("Estimated execution block must satisfy delay.")]
504 InvalidETA,
505 #[msg("Delay greater than the maximum.")]
506 DelayTooHigh,
507 #[msg("Not enough owners signed this transaction.")]
508 NotEnoughSigners,
509 #[msg("Transaction is past the grace period.")]
510 TransactionIsStale,
511 #[msg("Transaction hasn't surpassed time lock.")]
512 TransactionNotReady,
513 #[msg("The given transaction has already been executed.")]
514 AlreadyExecuted,
515 #[msg("Threshold must be less than or equal to the number of owners.")]
516 InvalidThreshold,
517 #[msg("Owner set has changed since the creation of the transaction.")]
518 OwnerSetChanged,
519 #[msg("Subaccount does not belong to smart wallet.")]
520 SubaccountOwnerMismatch,
521 #[msg("Buffer already finalized.")]
522 BufferFinalized,
523 #[msg("Buffer bundle not found.")]
524 BufferBundleNotFound,
525 #[msg("Buffer index specified is out of range.")]
526 BufferBundleOutOfRange,
527 #[msg("Buffer has not been finalized.")]
528 BufferBundleNotFinalized,
529 #[msg("Buffer bundle has already been executed.")]
530 BufferBundleExecuted,
531}