Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oracle uninitialized variant #116

Merged
merged 5 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions clients/js/src/generated/errors/mplCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,22 @@ export class InvalidOracleAccountDataError extends ProgramError {
codeToErrorMap.set(0x26, InvalidOracleAccountDataError);
nameToErrorMap.set('InvalidOracleAccountData', InvalidOracleAccountDataError);

/** UninitializedOracleAccount: Oracle account is uninitialized */
export class UninitializedOracleAccountError extends ProgramError {
override readonly name: string = 'UninitializedOracleAccount';

readonly code: number = 0x27; // 39

constructor(program: Program, cause?: Error) {
super('Oracle account is uninitialized', program, cause);
}
}
codeToErrorMap.set(0x27, UninitializedOracleAccountError);
nameToErrorMap.set(
'UninitializedOracleAccount',
UninitializedOracleAccountError
);

/**
* Attempts to resolve a custom program error from the provided error code.
* @category Errors
Expand Down
37 changes: 23 additions & 14 deletions clients/js/src/generated/types/oracleValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,41 @@ import {
Serializer,
dataEnum,
struct,
unit,
} from '@metaplex-foundation/umi/serializers';
import {
ExternalValidationResult,
ExternalValidationResultArgs,
getExternalValidationResultSerializer,
} from '.';

export type OracleValidation = {
__kind: 'V1';
create: ExternalValidationResult;
transfer: ExternalValidationResult;
burn: ExternalValidationResult;
update: ExternalValidationResult;
};
export type OracleValidation =
| { __kind: 'Uninitialized' }
| {
__kind: 'V1';
create: ExternalValidationResult;
transfer: ExternalValidationResult;
burn: ExternalValidationResult;
update: ExternalValidationResult;
};

export type OracleValidationArgs = {
__kind: 'V1';
create: ExternalValidationResultArgs;
transfer: ExternalValidationResultArgs;
burn: ExternalValidationResultArgs;
update: ExternalValidationResultArgs;
};
export type OracleValidationArgs =
| { __kind: 'Uninitialized' }
| {
__kind: 'V1';
create: ExternalValidationResultArgs;
transfer: ExternalValidationResultArgs;
burn: ExternalValidationResultArgs;
update: ExternalValidationResultArgs;
};

export function getOracleValidationSerializer(): Serializer<
OracleValidationArgs,
OracleValidation
> {
return dataEnum<OracleValidation>(
[
['Uninitialized', unit()],
[
'V1',
struct<GetDataEnumKindContent<OracleValidation, 'V1'>>([
Expand All @@ -56,6 +62,9 @@ export function getOracleValidationSerializer(): Serializer<
}

// Data Enum Helpers.
export function oracleValidation(
kind: 'Uninitialized'
): GetDataEnumKind<OracleValidationArgs, 'Uninitialized'>;
export function oracleValidation(
kind: 'V1',
data: GetDataEnumKindContent<OracleValidationArgs, 'V1'>
Expand Down
27 changes: 18 additions & 9 deletions clients/js/src/helpers/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,12 @@ export async function validateTransfer(
return null;
});

const oraclePass = oracleValidations.every(
(v) => v?.transfer === ExternalValidationResult.Pass
);
const oraclePass = oracleValidations.every((v) => {
if (v?.__kind === 'Uninitialized') {
return false;
}
return v?.transfer === ExternalValidationResult.Pass;
});
if (!oraclePass) {
return LifecycleValidationError.OracleValidationFailed;
}
Expand Down Expand Up @@ -284,9 +287,12 @@ export async function validateBurn(
return null;
});

const oraclePass = oracleValidations.every(
(v) => v?.burn === ExternalValidationResult.Pass
);
const oraclePass = oracleValidations.every((v) => {
if (v?.__kind === 'Uninitialized') {
return false;
}
return v?.burn === ExternalValidationResult.Pass;
});
if (!oraclePass) {
return LifecycleValidationError.OracleValidationFailed;
}
Expand Down Expand Up @@ -373,9 +379,12 @@ export async function validateUpdate(
return null;
});

const oraclePass = oracleValidations.every(
(v) => v?.update === ExternalValidationResult.Pass
);
const oraclePass = oracleValidations.every((v) => {
if (v?.__kind === 'Uninitialized') {
return false;
}
return v?.update === ExternalValidationResult.Pass;
});
if (!oraclePass) {
return LifecycleValidationError.OracleValidationFailed;
}
Expand Down
64 changes: 61 additions & 3 deletions clients/js/test/externalPlugins/oracle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2390,7 +2390,7 @@ test('it can update oracle to different size external plugin', async (t) => {
});
});

test('create fails but does not panic when oracle account does not exist', async (t) => {
test('it create fails but does not panic when oracle account does not exist', async (t) => {
const umi = await createUmi();
const oracleSigner = generateSigner(umi);

Expand Down Expand Up @@ -2419,7 +2419,7 @@ test('create fails but does not panic when oracle account does not exist', async
await t.throwsAsync(result, { name: 'InvalidOracleAccountData' });
});

test('transfer fails but does not panic when oracle account does not exist', async (t) => {
test('it transfer fails but does not panic when oracle account does not exist', async (t) => {
const umi = await createUmi();
const oracleSigner = generateSigner(umi);

Expand Down Expand Up @@ -2469,7 +2469,7 @@ test('transfer fails but does not panic when oracle account does not exist', asy
await t.throwsAsync(result, { name: 'InvalidOracleAccountData' });
});

test('transfer fails but does not panic when oracle account is too small', async (t) => {
test('it transfer fails but does not panic when oracle account is too small', async (t) => {
const umi = await createUmi();
const newAccount = generateSigner(umi);

Expand Down Expand Up @@ -2526,3 +2526,61 @@ test('transfer fails but does not panic when oracle account is too small', async

await t.throwsAsync(result, { name: 'InvalidOracleAccountData' });
});

test('it empty account does not default to valid oracle', async (t) => {
const umi = await createUmi();
const newAccount = generateSigner(umi);

// Create an invalid oracle account that is an account with 42 bytes.
await createAccount(umi, {
newAccount,
lamports: sol(0.1),
space: 42,
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: 'UninitializedOracleAccount' });
});
3 changes: 3 additions & 0 deletions clients/rust/src/generated/errors/mpl_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,9 @@ pub enum MplCoreError {
/// 38 (0x26) - Could not read from oracle account
#[error("Could not read from oracle account")]
InvalidOracleAccountData,
/// 39 (0x27) - Oracle account is uninitialized
#[error("Oracle account is uninitialized")]
UninitializedOracleAccount,
}

impl solana_program::program_error::PrintProgramError for MplCoreError {
Expand Down
1 change: 1 addition & 0 deletions clients/rust/src/generated/types/oracle_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use borsh::{BorshDeserialize, BorshSerialize};
#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum OracleValidation {
Uninitialized,
V1 {
create: ExternalValidationResult,
transfer: ExternalValidationResult,
Expand Down
8 changes: 8 additions & 0 deletions idls/mpl_core.json
Original file line number Diff line number Diff line change
Expand Up @@ -3871,6 +3871,9 @@
"type": {
"kind": "enum",
"variants": [
{
"name": "Uninitialized"
},
{
"name": "V1",
"fields": [
Expand Down Expand Up @@ -4215,6 +4218,11 @@
"code": 38,
"name": "InvalidOracleAccountData",
"msg": "Could not read from oracle account"
},
{
"code": 39,
"name": "UninitializedOracleAccount",
"msg": "Oracle account is uninitialized"
}
],
"metadata": {
Expand Down
4 changes: 4 additions & 0 deletions programs/mpl-core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ pub enum MplCoreError {
/// 38 - Could not read from oracle account
#[error("Could not read from oracle account")]
InvalidOracleAccountData,

/// 39 - Oracle account is uninitialized
#[error("Oracle account is uninitialized")]
UninitializedOracleAccount,
}

impl PrintProgramError for MplCoreError {
Expand Down
3 changes: 3 additions & 0 deletions programs/mpl-core/src/plugins/oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ impl Oracle {
.map_err(|_| MplCoreError::InvalidOracleAccountData)?;

match validation_result {
OracleValidation::Uninitialized => Err(MplCoreError::UninitializedOracleAccount.into()),
OracleValidation::V1 {
create,
transfer,
Expand Down Expand Up @@ -185,6 +186,8 @@ impl ValidationResultsOffset {
/// Validation results struct for an Oracle account.
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, Eq, PartialEq)]
pub enum OracleValidation {
/// Uninitialized data. This is intended to prevent leaving an account zeroed out by mistake.
Uninitialized,
/// Version 1 of the format.
V1 {
/// Validation for the the create lifecycle action.
Expand Down