Skip to main content

sbpf_runtime/syscalls/
pda.rs

1use {
2    crate::config::ExecutionCost,
3    sbpf_vm::{
4        compute::ComputeMeter,
5        errors::{SbpfVmError, SbpfVmResult},
6        memory::Memory,
7    },
8    solana_address::Address,
9};
10
11const MAX_SEED_LEN: usize = 32;
12const MAX_SEEDS: usize = 16;
13
14fn read_seeds(memory: &Memory, seeds_addr: u64, seeds_len: u64) -> SbpfVmResult<Vec<Vec<u8>>> {
15    if seeds_len as usize > MAX_SEEDS {
16        return Err(SbpfVmError::MaxSeedLengthExceeded);
17    }
18
19    let mut seeds = Vec::with_capacity(seeds_len as usize);
20    for i in 0..seeds_len {
21        let slice_addr = seeds_addr.saturating_add(i.saturating_mul(16));
22        let ptr = memory.read_u64(slice_addr)?;
23        let len = memory.read_u64(slice_addr.saturating_add(8))?;
24
25        if len as usize > MAX_SEED_LEN {
26            return Err(SbpfVmError::MaxSeedLengthExceeded);
27        }
28
29        seeds.push(memory.read_bytes(ptr, len as usize)?.to_vec());
30    }
31    Ok(seeds)
32}
33
34pub fn sol_create_program_address(
35    registers: [u64; 5],
36    memory: &mut Memory,
37    compute: &ComputeMeter,
38    costs: &ExecutionCost,
39) -> SbpfVmResult<u64> {
40    let seeds_addr = registers[0];
41    let seeds_len = registers[1];
42    let program_id_addr = registers[2];
43    let address_addr = registers[3];
44
45    compute.consume(costs.create_program_address_units)?;
46
47    let seeds = read_seeds(memory, seeds_addr, seeds_len)?;
48    let program_id = Address::from(
49        <[u8; 32]>::try_from(memory.read_bytes(program_id_addr, 32)?)
50            .map_err(|_| SbpfVmError::InvalidSliceConversion)?,
51    );
52
53    let seed_slices: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect();
54    match Address::create_program_address(&seed_slices, &program_id) {
55        Ok(addr) => {
56            memory.write_bytes(address_addr, addr.as_ref())?;
57            Ok(0)
58        }
59        Err(_) => Ok(1),
60    }
61}
62
63pub fn sol_try_find_program_address(
64    registers: [u64; 5],
65    memory: &mut Memory,
66    compute: &ComputeMeter,
67    costs: &ExecutionCost,
68) -> SbpfVmResult<u64> {
69    let seeds_addr = registers[0];
70    let seeds_len = registers[1];
71    let program_id_addr = registers[2];
72    let address_addr = registers[3];
73    let bump_seed_addr = registers[4];
74
75    // Consume once at start.
76    let cost = costs.create_program_address_units;
77    compute.consume(cost)?;
78
79    let seeds = read_seeds(memory, seeds_addr, seeds_len)?;
80    let program_id = Address::from(
81        <[u8; 32]>::try_from(memory.read_bytes(program_id_addr, 32)?)
82            .map_err(|_| SbpfVmError::InvalidSliceConversion)?,
83    );
84
85    let mut bump_seed = [u8::MAX];
86    for _ in 0..u8::MAX {
87        let mut seeds_with_bump: Vec<&[u8]> = seeds.iter().map(|s| s.as_slice()).collect();
88        seeds_with_bump.push(&bump_seed);
89
90        if let Ok(addr) = Address::create_program_address(&seeds_with_bump, &program_id) {
91            memory.write_u8(bump_seed_addr, bump_seed[0])?;
92            memory.write_bytes(address_addr, addr.as_ref())?;
93            return Ok(0);
94        }
95
96        bump_seed[0] = bump_seed[0].checked_sub(1).unwrap();
97        // Consume after each failed attempt
98        compute.consume(cost)?;
99    }
100    Ok(1)
101}
102
103#[cfg(test)]
104mod tests {
105    use {
106        super::*,
107        crate::syscalls::tests::test_helpers::{costs, make_memory, meter},
108        sbpf_vm::{errors::SbpfVmError, memory::Memory},
109        solana_address::Address,
110    };
111
112    fn setup_seeds(
113        memory: &mut Memory,
114        seeds: &[&[u8]],
115        program_id: &Address,
116    ) -> (u64, u64, u64, u64) {
117        // Write each seed's bytes starting at offset 0, spaced 64 bytes apart
118        let mut seed_data_addrs = Vec::new();
119        for (i, seed) in seeds.iter().enumerate() {
120            let addr = Memory::HEAP_START + (i as u64 * 64);
121            memory.write_bytes(addr, seed).unwrap();
122            seed_data_addrs.push((addr, seed.len() as u64));
123        }
124
125        // Write the slice-descriptor array after all seed data
126        let seeds_addr = Memory::HEAP_START + 1024;
127        for (i, (ptr, len)) in seed_data_addrs.iter().enumerate() {
128            let desc = seeds_addr + (i as u64 * 16);
129            memory.write_u64(desc, *ptr).unwrap();
130            memory.write_u64(desc + 8, *len).unwrap();
131        }
132
133        let program_id_addr = Memory::HEAP_START + 2048;
134        memory
135            .write_bytes(program_id_addr, program_id.as_ref())
136            .unwrap();
137
138        let address_out_addr = Memory::HEAP_START + 2096;
139
140        (
141            seeds_addr,
142            seeds.len() as u64,
143            program_id_addr,
144            address_out_addr,
145        )
146    }
147
148    #[test]
149    fn test_create_program_address_valid_pda() {
150        let mut memory = make_memory();
151        let program_id = Address::from([2u8; 32]);
152
153        // find_program_address gives us seeds+bump that produce a valid PDA
154        let (expected_addr, bump) = Address::find_program_address(&[b"test"], &program_id);
155        let bump_bytes = [bump];
156
157        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
158            setup_seeds(&mut memory, &[b"test", &bump_bytes], &program_id);
159
160        let registers = [seeds_addr, seeds_len, program_id_addr, address_out_addr, 0];
161        let ret = sol_create_program_address(registers, &mut memory, &meter(1_000_000), &costs())
162            .unwrap();
163
164        assert_eq!(ret, 0, "should return 0 for a valid PDA");
165        let written_addr: [u8; 32] = memory
166            .read_bytes(address_out_addr, 32)
167            .unwrap()
168            .try_into()
169            .unwrap();
170        assert_eq!(Address::from(written_addr), expected_addr);
171    }
172
173    #[test]
174    fn test_create_program_address_too_many_seeds() {
175        let mut memory = make_memory();
176        let program_id = Address::from([1u8; 32]);
177        // 17 seeds exceeds MAX_SEEDS=16
178        let seeds: Vec<&[u8]> = (0..17).map(|_| b"s".as_ref()).collect();
179        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
180            setup_seeds(&mut memory, &seeds, &program_id);
181
182        let registers = [seeds_addr, seeds_len, program_id_addr, address_out_addr, 0];
183        assert!(matches!(
184            sol_create_program_address(registers, &mut memory, &meter(1_000_000), &costs()),
185            Err(SbpfVmError::MaxSeedLengthExceeded)
186        ));
187    }
188
189    #[test]
190    fn test_create_program_address_seed_too_long() {
191        let mut memory = make_memory();
192        let program_id = Address::from([1u8; 32]);
193        // 33-byte seed exceeds MAX_SEED_LEN=32
194        let long_seed = [0u8; 33];
195        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
196            setup_seeds(&mut memory, &[&long_seed], &program_id);
197
198        let registers = [seeds_addr, seeds_len, program_id_addr, address_out_addr, 0];
199        assert!(matches!(
200            sol_create_program_address(registers, &mut memory, &meter(1_000_000), &costs()),
201            Err(SbpfVmError::MaxSeedLengthExceeded)
202        ));
203    }
204
205    #[test]
206    fn test_create_program_address_compute_exhausted() {
207        let mut memory = make_memory();
208        let program_id = Address::from([1u8; 32]);
209        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
210            setup_seeds(&mut memory, &[b"seed"], &program_id);
211
212        let registers = [seeds_addr, seeds_len, program_id_addr, address_out_addr, 0];
213        // create_program_address_units = 1500; budget=1499 fails
214        assert!(matches!(
215            sol_create_program_address(registers, &mut memory, &meter(1499), &costs()),
216            Err(SbpfVmError::ComputeBudgetExceeded { .. })
217        ));
218    }
219
220    #[test]
221    fn test_try_find_program_address_matches_library() {
222        let mut memory = make_memory();
223        let program_id = Address::from([3u8; 32]);
224
225        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
226            setup_seeds(&mut memory, &[b"find-me"], &program_id);
227
228        let bump_out_addr = Memory::HEAP_START + 2200;
229
230        let registers = [
231            seeds_addr,
232            seeds_len,
233            program_id_addr,
234            address_out_addr,
235            bump_out_addr,
236        ];
237        let ret = sol_try_find_program_address(registers, &mut memory, &meter(1_000_000), &costs())
238            .unwrap();
239
240        assert_eq!(ret, 0, "should find a valid PDA");
241
242        let written_bump = memory.read_u8(bump_out_addr).unwrap();
243        let written_addr: [u8; 32] = memory
244            .read_bytes(address_out_addr, 32)
245            .unwrap()
246            .try_into()
247            .unwrap();
248
249        let (expected_addr, expected_bump) =
250            Address::find_program_address(&[b"find-me"], &program_id);
251
252        assert_eq!(written_bump, expected_bump);
253        assert_eq!(Address::from(written_addr), expected_addr);
254    }
255
256    #[test]
257    fn test_try_find_program_address_too_many_seeds() {
258        let mut memory = make_memory();
259        let program_id = Address::from([1u8; 32]);
260        let seeds: Vec<&[u8]> = (0..17).map(|_| b"x".as_ref()).collect();
261        let (seeds_addr, seeds_len, program_id_addr, address_out_addr) =
262            setup_seeds(&mut memory, &seeds, &program_id);
263
264        let bump_out_addr = Memory::HEAP_START + 2200;
265        let registers = [
266            seeds_addr,
267            seeds_len,
268            program_id_addr,
269            address_out_addr,
270            bump_out_addr,
271        ];
272        assert!(matches!(
273            sol_try_find_program_address(registers, &mut memory, &meter(1_000_000), &costs()),
274            Err(SbpfVmError::MaxSeedLengthExceeded)
275        ));
276    }
277}