1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
use anchor_lang::prelude::*;
use std::mem::size_of;

declare_id!("CrkGQLM8mnWxUV2bGXacvFtnk3oVyeP6grRyFgu6XJ9G");

#[program]
pub mod solrandhypn {
    use super::*;

    const ORACLE_FEE: u64 = 495000; //  Approximately $0.09 = 0.000000001 * $175 * 495,000

    pub fn initialize(
        ctx: Context<Initialize>,
        request_bump: u8,
        vault_bump: u8,
    ) -> ProgramResult {
        // Set the vault account, used to pay the oracle
        ctx.accounts.vault.requester = *ctx.accounts.requester.to_account_info().key;
        ctx.accounts.vault.bump = vault_bump;

        let requester = &mut ctx.accounts.requester.load_init()?;
        let clock: Clock = Clock::get().unwrap();

        // The requester is ZeroCopy and stores the random number
        requester.authority = *ctx.accounts.authority.key;
        requester.oracle = *ctx.accounts.oracle.key;
        requester.created_at = clock.unix_timestamp;
        requester.count = 0;
        requester.active_request = false;
        requester.last_updated = clock.unix_timestamp;
        requester.bump = request_bump;

        Ok(())
    }

    pub fn request_random(
        ctx: Context<RequestRandom>,
    ) -> ProgramResult {
        // Some checks to ensure proper account ownership
        {
            let requester_key = ctx.accounts.requester.to_account_info().key();

            if requester_key != ctx.accounts.vault.requester {
                return Err(ErrorCode::RequesterAndVaultMismatch.into());
            }

            let requester = &mut ctx.accounts.requester.load()?;
            let authority = ctx.accounts.authority.key();
    
            if requester.authority != authority {
                return Err(ErrorCode::Unauthorized.into());
            }
    
            if requester.oracle != ctx.accounts.oracle.key() {
                return Err(ErrorCode::WrongOracle.into());
            }
    
            if requester.active_request {
                return Err(ErrorCode::InflightRequest.into());
            }
        }

        // Transfer fee to Oracle
        {
            let vault = ctx.accounts.vault.to_account_info();

            **vault.try_borrow_mut_lamports()? = vault.lamports()
                .checked_sub(ORACLE_FEE)
                .ok_or(ProgramError::InvalidArgument)?;

            **ctx.accounts.oracle.try_borrow_mut_lamports()? = ctx.accounts.oracle.lamports()
                .checked_add(ORACLE_FEE)
                .ok_or(ProgramError::InvalidArgument)?;
            
        }

        // Once the requester has active_request set, it's frozen until the Oracle responds
        {
            let requester = &mut ctx.accounts.requester.load_mut()?;
            let clock: Clock = Clock::get().unwrap();

            requester.last_updated = clock.unix_timestamp;
            requester.active_request = true;
            requester.count += 1;
        }
        
        Ok(())
    }

    pub fn publish_random(
        ctx: Context<PublishRandom>,
        random: [u8; 64],
        pkt_id: [u8; 32],
        tls_id: [u8; 32],
    ) -> ProgramResult {
        // Have to load the account this way to avoid automated ownership checks
        let loader: Loader<Requester> = Loader::try_from_unchecked(ctx.program_id, &ctx.remaining_accounts[0]).unwrap();
        let mut requester = loader.load_mut()?;

        if requester.oracle != ctx.accounts.oracle.key() {
            return Err(ErrorCode::Unauthorized.into());
        }

        if !requester.active_request {
            return Err(ErrorCode::AlreadyCompleted.into())
        }
        let clock: Clock = Clock::get().unwrap();

        requester.last_updated = clock.unix_timestamp;
        requester.active_request = false;
        requester.random = random;
        requester.pkt_id = pkt_id;
        requester.tls_id = tls_id;

        Ok(())
    }

    /**
     * Used by PDAs in CPIs to lock an Oracle request
     */
    pub fn transfer_authority(
        ctx: Context<TransferAuthority>
    ) -> ProgramResult {
        let requester = &mut ctx.accounts.requester.load_mut()?;

        if requester.authority != ctx.accounts.authority.key() {
            return Err(ErrorCode::RequesterAndContextAccountsMismatch.into());
        }

        if requester.active_request {
            return Err(ErrorCode::RequesterLocked.into());
        }

        requester.authority = ctx.accounts.new_authority.key();

        Ok(())
    }
}


#[derive(Accounts)]
#[instruction(request_bump: u8, vault_bump: u8)]
pub struct Initialize<'info> {
    #[account(
        init, 
        seeds = [b"r-seed".as_ref(), authority.key().as_ref()],
        bump = request_bump,
        payer = authority,
        space = 8 + size_of::<Requester>()
    )]
    pub requester: AccountLoader<'info, Requester>,
    #[account(
        init,
        seeds = [b"v-seed".as_ref(), authority.key().as_ref()],
        bump = vault_bump,
        payer = authority,
        space = 8 + size_of::<Vault>()
    )]
    pub vault: Account<'info, Vault>,
    #[account(signer, mut)]
    pub authority: AccountInfo<'info>,
    pub oracle: AccountInfo<'info>,
    pub rent: Sysvar<'info, Rent>,
    pub system_program: Program<'info, System>,
}

#[account(zero_copy)]
pub struct Requester {
    pub authority: Pubkey,
    pub oracle: Pubkey,
    pub created_at: i64,
    pub count: u64,
    pub last_updated: i64,
    pub random: [u8; 64],
    pub pkt_id: [u8; 32],
    pub tls_id: [u8; 32],
    pub active_request: bool,
    pub bump: u8,
}

#[account]
pub struct Vault {
    pub requester: Pubkey,
    pub bump: u8,
}

#[derive(Accounts)]
pub struct RequestRandom<'info> {
    #[account(mut)]
    pub requester: AccountLoader<'info, Requester>,
    #[account(mut)]
    pub vault: Account<'info, Vault>,
    #[account(signer, mut)]
    pub authority: AccountInfo<'info>,
    #[account(mut)]
    pub oracle: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct PublishRandom<'info> {
    pub oracle: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct TransferAuthority<'info> {
    #[account(mut)]
    pub requester: AccountLoader<'info, Requester>,
    #[account(signer, mut)]
    pub authority: AccountInfo<'info>,
    #[account(mut)]
    pub new_authority: AccountInfo<'info>,
    pub system_program: Program<'info, System>,
}

#[error]
pub enum ErrorCode {
    #[msg("You are not authorized to complete this transaction")]
    Unauthorized,
    #[msg("You have already completed this transaction")]
    AlreadyCompleted,
    #[msg("A request is already in progress. Only one request may be made at a time")]
    InflightRequest,
    #[msg("The Oracle you make the request with must be the same as initialization")]
    WrongOracle,
    #[msg("You cannot change authority of a request awaiting a response")]
    RequesterLocked,
    #[msg("Not authorized: requester and vault keys mismatch")]
    RequesterAndVaultMismatch,
    #[msg("Not authorized: requester authority and context accounts authority keys mismatch")]
    RequesterAndContextAccountsMismatch,
}