1+ use code_vm_api:: prelude:: * ;
2+ use steel:: * ;
3+
4+ use crate :: ExecContext ;
5+
6+ /*
7+ This instruction is used to transfer tokens from *one* virtual account to a
8+ number of virtual accounts. The signature of the source account is required
9+ to authorize the transfer.
10+
11+ Extra accounts required by this instruction:
12+
13+ | # | R/W | Type | Req | PDA | Name | Description |
14+ |---|-----|------------- |-----|-----|--------|--------------|
15+ |...| The same as the vm_exec instruction. |
16+ |---|-----|------------- |-----|-----|--------|--------------|
17+ | 6 | | <None> | | | | |
18+ | 7 | | <None> | | | | |
19+ | 8 | | <None> | | | | |
20+ | 9 | | <None> | | | | |
21+ |10 | | <None> | | | | |
22+
23+
24+ Instruction data:
25+
26+ 0. signature: [u8;64] - The opcode to execute.
27+ 1. amount: [u64] - The account_indicies of the virtual accounts to use.
28+ 2. count: [u8] - The number of destinations.
29+ */
30+ pub fn process_airdrop (
31+ ctx : & ExecContext ,
32+ data : & ExecIxData ,
33+ ) -> ProgramResult {
34+
35+ let vm = load_vm ( ctx. vm_info ) ?;
36+ let args = AirdropOp :: try_from_bytes ( & data. data ) ?. to_struct ( ) ?;
37+
38+ let mem_indicies = & data. mem_indicies ;
39+ let mem_banks = & data. mem_banks ;
40+ let num_accounts = 2 + ( args. count as usize ) ;
41+
42+ check_condition (
43+ mem_indicies. len ( ) == num_accounts,
44+ "invalid number of memory indicies" ,
45+ ) ?;
46+
47+ check_condition (
48+ mem_banks. len ( ) == num_accounts,
49+ "invalid number of memory banks" ,
50+ ) ?;
51+
52+ let nonce_index = mem_indicies[ 0 ] ;
53+ let nonce_mem = mem_banks[ 0 ] ;
54+
55+ let src_index = mem_indicies[ 1 ] ;
56+ let src_mem = mem_banks[ 1 ] ;
57+
58+ let vm_mem = ctx. get_banks ( ) ;
59+
60+ check_condition (
61+ vm_mem[ nonce_mem as usize ] . is_some ( ) ,
62+ "the nonce memory account must be provided" ,
63+ ) ?;
64+
65+ check_condition (
66+ vm_mem[ src_mem as usize ] . is_some ( ) ,
67+ "the source memory account must be provided" ,
68+ ) ?;
69+
70+ let nonce_mem_info = vm_mem[ nonce_mem as usize ] . unwrap ( ) ;
71+ let src_mem_info = vm_mem[ src_mem as usize ] . unwrap ( ) ;
72+
73+ let va = try_read ( & nonce_mem_info, nonce_index) ?;
74+ let mut vdn = va. into_inner_nonce ( ) . unwrap ( ) ;
75+
76+ let va = try_read ( & src_mem_info, src_index) ?;
77+ let mut src_vta = va. into_inner_timelock ( ) . unwrap ( ) ;
78+
79+ let total_amount = args. amount
80+ . checked_mul ( args. count as u64 )
81+ . ok_or ( ProgramError :: ArithmeticOverflow ) ?;
82+
83+ if src_vta. balance < total_amount {
84+ return Err ( ProgramError :: InsufficientFunds ) ;
85+ }
86+
87+ src_vta. balance = src_vta. balance
88+ . checked_sub ( total_amount)
89+ . ok_or ( ProgramError :: ArithmeticOverflow ) ?;
90+
91+ let mut dst_pubkeys = Vec :: new ( ) ;
92+ for i in 0 ..args. count as usize {
93+ let dst_index = mem_indicies[ 2 + i] ;
94+ let dst_mem = mem_banks[ 2 + i] ;
95+
96+ check_condition (
97+ vm_mem[ dst_mem as usize ] . is_some ( ) ,
98+ "a destination memory account must be provided" ,
99+ ) ?;
100+
101+ let dst_mem_info = vm_mem[ dst_mem as usize ] . unwrap ( ) ;
102+
103+ let va = try_read ( & dst_mem_info, dst_index) ?;
104+ let mut dst_vta = va. into_inner_timelock ( ) . unwrap ( ) ;
105+
106+ // Check if this destination is actually the source.
107+ let is_same_account = ( src_mem == dst_mem) && ( src_index == dst_index) ;
108+ if is_same_account {
109+ // If the source is also in the destinations list, it receives the airdrop as well.
110+ src_vta. balance = src_vta. balance
111+ . checked_add ( args. amount )
112+ . ok_or ( ProgramError :: ArithmeticOverflow ) ?;
113+
114+ } else {
115+ // Normal destination: add the airdrop to its balance
116+ dst_vta. balance = dst_vta. balance
117+ . checked_add ( args. amount )
118+ . ok_or ( ProgramError :: ArithmeticOverflow ) ?;
119+
120+ // Write the updated destination back
121+ try_write (
122+ dst_mem_info,
123+ dst_index,
124+ & VirtualAccount :: Timelock ( dst_vta)
125+ ) ?;
126+ }
127+
128+ dst_pubkeys. push ( dst_vta. owner ) ;
129+ }
130+
131+ let hash = create_airdrop_message (
132+ & vm,
133+ & src_vta,
134+ & dst_pubkeys,
135+ args. amount ,
136+ & vdn,
137+ ) ;
138+
139+ sig_verify (
140+ src_vta. owner . as_ref ( ) ,
141+ args. signature . as_ref ( ) ,
142+ hash. as_ref ( ) ,
143+ ) ?;
144+
145+ vdn. value = vm. get_current_poh ( ) ;
146+
147+ // Finally, write back the updated source (which now includes
148+ // any airdrop shares if the source was also in the destination list).
149+ try_write (
150+ src_mem_info,
151+ src_index,
152+ & VirtualAccount :: Timelock ( src_vta)
153+ ) ?;
154+
155+ try_write (
156+ nonce_mem_info,
157+ nonce_index,
158+ & VirtualAccount :: Nonce ( vdn)
159+ ) ?;
160+
161+ Ok ( ( ) )
162+ }
0 commit comments