diff --git a/clients/js/test/externalPlugins/oracle.test.ts b/clients/js/test/externalPlugins/oracle.test.ts index 55a554af..47a55354 100644 --- a/clients/js/test/externalPlugins/oracle.test.ts +++ b/clients/js/test/externalPlugins/oracle.test.ts @@ -2388,3 +2388,140 @@ test('it can update oracle to different size external plugin', async (t) => { ], }); }); + +test('create does not panic when oracle account does not exist', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + + const asset = generateSigner(umi); + const result = create(umi, { + asset, + name: 'Test name', + uri: 'https://example.com', + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + create: [CheckResult.CAN_REJECT], + update: [CheckResult.CAN_REJECT], + transfer: [CheckResult.CAN_REJECT], + burn: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); + +test('transferring an asset does not panic when oracle account does not exist', async (t) => { + const umi = await createUmi(); + const oracleSigner = generateSigner(umi); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: oracleSigner.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'Anchor', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: oracleSigner.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + pda: undefined, + }, + ], + }); + + const newOwner = generateSigner(umi); + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); + +test.skip('transferring an asset does not panic when oracle account is too small', async (t) => { + const umi = await createUmi(); + const newAccount = generateSigner(umi); + + // Create an invalid oracle account that is an account with 3 bytes. + await createAccount(umi, { + newAccount, + lamports: sol(0.1), + space: 3, + programId: umi.programs.get('mplCore').publicKey, + }).sendAndConfirm(umi); + + const asset = await createAsset(umi, { + plugins: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + baseAddress: newAccount.publicKey, + }, + ], + }); + + await assertAsset(t, umi, { + ...DEFAULT_ASSET, + asset: asset.publicKey, + owner: umi.identity.publicKey, + oracles: [ + { + type: 'Oracle', + resultsOffset: { + type: 'NoOffset', + }, + authority: { + type: 'UpdateAuthority', + }, + baseAddress: newAccount.publicKey, + lifecycleChecks: { + transfer: [CheckResult.CAN_REJECT], + }, + pda: undefined, + }, + ], + }); + + const newOwner = generateSigner(umi); + const result = transfer(umi, { + asset, + newOwner: newOwner.publicKey, + }).sendAndConfirm(umi); + + await t.throwsAsync(result, { name: 'InvalidOracleAccountData' }); +}); diff --git a/programs/mpl-core/src/plugins/oracle.rs b/programs/mpl-core/src/plugins/oracle.rs index 1a27274b..441dfedc 100644 --- a/programs/mpl-core/src/plugins/oracle.rs +++ b/programs/mpl-core/src/plugins/oracle.rs @@ -89,9 +89,18 @@ impl Oracle { .ok_or(MplCoreError::MissingExternalAccount)?; let offset = self.results_offset.to_offset_usize(); - let validation_result = - OracleValidation::deserialize(&mut &(*oracle_account.data).borrow()[offset..]) - .map_err(|_| MplCoreError::InvalidOracleAccountData)?; + + let oracle_data = (*oracle_account.data).borrow(); + let mut oracle_data_slice = oracle_data + .get(offset..) + .ok_or(MplCoreError::InvalidOracleAccountData)?; + + if oracle_data_slice.len() < OracleValidation::serialized_size() { + return Err(MplCoreError::InvalidOracleAccountData.into()); + } + + let validation_result = OracleValidation::deserialize(&mut oracle_data_slice) + .map_err(|_| MplCoreError::InvalidOracleAccountData)?; match validation_result { OracleValidation::V1 { @@ -188,3 +197,10 @@ pub enum OracleValidation { update: ExternalValidationResult, }, } + +impl OracleValidation { + /// Borsh- and Anchor-serialized size of the `OracleValidation` struct. + pub fn serialized_size() -> usize { + 5 + } +}