1
use bonsol_interface::callback::{handle_callback, BonsolCallback};
2
use bonsol_interface::instructions::{execute_v1, CallbackConfig, ExecutionConfig, InputRef};
3
use solana_program::account_info::AccountInfo;
4
use solana_program::entrypoint::ProgramResult;
5
use solana_program::program_error::ProgramError;
6
use solana_program::pubkey;
7
use solana_program::pubkey::Pubkey;
8

            
9
use solana_program::clock::Clock;
10
use solana_program::instruction::AccountMeta;
11
use solana_program::program::invoke_signed;
12
use solana_program::program_memory::sol_memcmp;
13
use solana_program::rent::Rent;
14
use solana_program::system_instruction;
15
use solana_program::sysvar::Sysvar;
16
use solana_program::{declare_id, entrypoint, msg};
17
use std::str::from_utf8;
18

            
19
1
declare_id!("exay1T7QqsJPNcwzMiWubR6vZnqrgM16jZRraHgqBGG");
20
const SIMPLE_IMAGE_ID: &str = "68f4b0c5f9ce034aa60ceb264a18d6c410a3af68fafd931bcfd9ebe7c1e42960";
21

            
22
static EA1: Pubkey = pubkey!("3b6DR2gbTJwrrX27VLEZ2FJcHrDvTSLKEcTLVhdxCoaf");
23
static EA2: Pubkey = pubkey!("g7dD1FHSemkUQrX1Eak37wzvDjscgBW2pFCENwjLdMX");
24
static EA3: Pubkey = pubkey!("FHab8zDcP1DooZqXHWQowikqtXJb1eNHc46FEh1KejmX");
25

            
26
entrypoint!(main);
27

            
28
/// This program is used as a testbed for bonsol, to test various scenarios
29
/// and to test the callback functionality
30
fn main<'a>(
31
    _program_id: &Pubkey,
32
    accounts: &'a [AccountInfo<'a>],
33
    instruction_data: &[u8],
34
) -> ProgramResult {
35
    let (ix, data) = instruction_data.split_at(1);
36
    match ix[0] {
37
        0 => {
38
            let payer = &accounts[0]; //any feepayer
39
            if data.len() < 57 {
40
                return Err(ProgramError::InvalidInstructionData);
41
            }
42
            let execution_id =
43
                from_utf8(&data[0..16]).map_err(|_| ProgramError::InvalidInstructionData)?;
44
            let input_hash = &data[16..48];
45
            let expiration = u64::from_le_bytes(
46
                data[48..56]
47
                    .try_into()
48
                    .map_err(|_| ProgramError::InvalidInstructionData)?,
49
            );
50
            let bump = data[56];
51
            let private_input_url = &data[57..];
52
            let requester = &accounts[1]; //pda of this program
53
            let system = &accounts[2];
54
            let execution_account = &accounts[3]; //the pda of bonsol that represents the execution request
55
            create_program_account(
56
                requester,
57
                &[execution_id.as_bytes(), &[bump]],
58
                32,
59
                payer,
60
                system,
61
                None,
62
            )?;
63
            let tip = 1000;
64
            let expiration = Clock::get()?.slot + expiration; //high expiration since we run this on potatoes in CI
65
            let ix = execute_v1(
66
                requester.key,
67
                payer.key,
68
                SIMPLE_IMAGE_ID,
69
                execution_id,
70
                vec![
71
                    InputRef::public("{\"attestation\":\"test\"}".as_bytes()),
72
                    InputRef::private(private_input_url),
73
                ],
74
                tip,
75
                expiration,
76
                ExecutionConfig {
77
                    verify_input_hash: true,
78
                    input_hash: Some(input_hash),
79
                    forward_output: true,
80
                },
81
                Some(CallbackConfig {
82
                    program_id: crate::id(),
83
                    instruction_prefix: vec![1],
84
                    extra_accounts: vec![
85
                        AccountMeta::new(*requester.key, false),
86
                        AccountMeta::new(EA1, false),
87
                        AccountMeta::new_readonly(EA2, false),
88
                        AccountMeta::new_readonly(EA3, false),
89
                    ],
90
                }),
91
                None,
92
            )
93
            .map_err(|_| ProgramError::InvalidInstructionData)?;
94
            invoke_signed(&ix, accounts, &[&[execution_id.as_bytes(), &[bump]]])?;
95
            let mut data = requester.try_borrow_mut_data()?;
96
            data.copy_from_slice(&execution_account.key.to_bytes());
97
            Ok(())
98
        }
99
        1 => {
100
            let requester = &accounts[1];
101
            let requester_data = requester.try_borrow_data()?;
102
            let execution_account = Pubkey::try_from(&requester_data[0..32])
103
                .map_err(|_| ProgramError::InvalidInstructionData)?;
104
            let callback_output: BonsolCallback =
105
                handle_callback(SIMPLE_IMAGE_ID, &execution_account, accounts, data)?;
106
            if sol_memcmp(accounts[2].key.as_ref(), EA1.as_ref(), 32) != 0 {
107
                return Err(ProgramError::InvalidInstructionData);
108
            }
109
            if sol_memcmp(accounts[3].key.as_ref(), EA2.as_ref(), 32) != 0 {
110
                return Err(ProgramError::InvalidInstructionData);
111
            }
112
            if sol_memcmp(accounts[4].key.as_ref(), EA3.as_ref(), 32) != 0 {
113
                return Err(ProgramError::InvalidInstructionData);
114
            }
115
            if !accounts[2].is_writable {
116
                return Err(ProgramError::InvalidInstructionData);
117
            }
118
            if callback_output.committed_outputs.len() == 1
119
                && callback_output.committed_outputs[0] == 1
120
            {
121
                msg!("Correct Json Attestation");
122
            }
123
            Ok(())
124
        }
125
        //only callback test
126
        2 => {
127
            let callback_output: BonsolCallback =
128
                handle_callback(SIMPLE_IMAGE_ID, accounts[0].key, accounts, data)?;
129
            if sol_memcmp(accounts[1].key.as_ref(), EA1.as_ref(), 32) != 0 {
130
                return Err(ProgramError::InvalidInstructionData);
131
            }
132
            if sol_memcmp(accounts[2].key.as_ref(), EA2.as_ref(), 32) != 0 {
133
                return Err(ProgramError::InvalidInstructionData);
134
            }
135
            if sol_memcmp(accounts[3].key.as_ref(), EA3.as_ref(), 32) != 0 {
136
                return Err(ProgramError::InvalidInstructionData);
137
            }
138
            if !accounts[1].is_writable {
139
                return Err(ProgramError::InvalidInstructionData);
140
            }
141
            if callback_output.committed_outputs.len() == 1
142
                && callback_output.committed_outputs[0] == 1
143
            {
144
                msg!("Correct Json Attestation");
145
            }
146
            Ok(())
147
        }
148

            
149
        _ => Err(ProgramError::InvalidInstructionData),
150
    }
151
}
152

            
153
pub fn create_program_account<'a>(
154
    account: &'a AccountInfo<'a>,
155
    seeds: &[&[u8]],
156
    space: u64,
157
    payer: &'a AccountInfo<'a>,
158
    system: &'a AccountInfo<'a>,
159
    additional_lamports: Option<u64>,
160
) -> Result<(), ProgramError> {
161
    let lamports = Rent::get()?.minimum_balance(space as usize) + additional_lamports.unwrap_or(0);
162
    let create_pda_account_ix =
163
        system_instruction::create_account(payer.key, account.key, lamports, space, &crate::id());
164
    invoke_signed(
165
        &create_pda_account_ix,
166
        &[account.clone(), payer.clone(), system.clone()],
167
        &[seeds],
168
    )
169
    .map_err(|_e| ProgramError::Custom(0))
170
}