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

Feature: clear pending offchain #117

Merged
merged 3 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions contracts/programs/ocr2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ pub mod ocr2 {
offchain_config.len() < config.pending_offchain_config.remaining_capacity(),
InvalidInput
);
require!(config.pending_offchain_config.version != 0, InvalidInput);
config.pending_offchain_config.extend(&offchain_config);
Ok(())
}
Expand Down Expand Up @@ -147,6 +148,24 @@ pub mod ocr2 {
Ok(())
}

#[access_control(owner(&ctx.accounts.state, &ctx.accounts.authority))]
pub fn reset_pending_offchain_config(ctx: Context<SetConfig>) -> ProgramResult {
let state = &mut *ctx.accounts.state.load_mut()?;
let config = &mut state.config;

// Require that at least some data was written
require!(
config.pending_offchain_config.version > 0
|| !config.pending_offchain_config.is_empty(),
InvalidInput
);

// reset staging area
config.pending_offchain_config.clear();
config.pending_offchain_config.version = 0;
Ok(())
}

#[access_control(owner(&ctx.accounts.state, &ctx.accounts.authority))]
pub fn set_config(
ctx: Context<SetConfig>,
Expand Down
73 changes: 71 additions & 2 deletions contracts/tests/ocr2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,19 +436,88 @@ describe('ocr2', async () => {
authority: owner.publicKey,
},
});
assert.fail("beginOffchainConfig shouldn't have succeeded!")
} catch {
// beginOffchainConfig should fail
return
}
assert.fail("beginOffchainConfig shouldn't have succeeded!")
});

it("Can't write offchain config if begin has not been called", async () => {
try {
await program.rpc.writeOffchainConfig(
Buffer.from([4, 5, 6]),
{
accounts: {
state: state.publicKey,
authority: owner.publicKey,
},
});
} catch {
// writeOffchainConfig should fail
return
}
assert.fail("writeOffchainConfig shouldn't have succeeded!")
});

it("ResetPendingOffchainConfig clears pending state", async () => {

await program.rpc.beginOffchainConfig(
new BN(2),
{
accounts: {
state: state.publicKey,
authority: owner.publicKey,
},
});
await program.rpc.writeOffchainConfig(
Buffer.from([4, 5, 6]),
{
accounts: {
state: state.publicKey,
authority: owner.publicKey,
},
});
let account = await program.account.state.fetch(state.publicKey);
assert.ok(account.config.pendingOffchainConfig.version != 0);
assert.ok(account.config.pendingOffchainConfig.len != 0);

await program.rpc.resetPendingOffchainConfig(
{
accounts: {
state: state.publicKey,
authority: owner.publicKey,
},
});
account = await program.account.state.fetch(state.publicKey);
assert.ok(account.config.pendingOffchainConfig.version == 0);
assert.ok(account.config.pendingOffchainConfig.len == 0);
})

it("Can't reset pending config if already in new state", async () => {
try {
await program.rpc.resetPendingOffchainConfig(
{
accounts: {
state: state.publicKey,
authority: owner.publicKey,
},
});
} catch {
// resetPendingOffchainConfig should fail
return
}
assert.fail("resetPendingOffchainConfig shouldn't have succeeded!")
});

it("Can't transmit a round if not the writer", async () => {
try {
await transmit(1, 1, new BN(1));
assert.fail("transmit() shouldn't have succeeded!");
} catch {
// transmit should fail
return
}
assert.fail("transmit() shouldn't have succeeded!");
});

it('Sets the cluster as the feed writer', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,22 @@
],
"args": []
},
{
"name": "resetPendingOffchainConfig",
"accounts": [
{
"name": "state",
"isMut": true,
"isSigner": false
},
{
"name": "authority",
"isMut": false,
"isSigner": true
}
],
"args": []
},
{
"name": "setConfig",
"accounts": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BeginOffchainConfig from './offchainConfig/begin'
import CommitOffchainConfig from './offchainConfig/commit'
import SetOffchainConfigFlow from './offchainConfig/setOffchainConfig.flow'
import WriteOffchainConfig from './offchainConfig/write'
import ResetPendingOffchainConfig from './offchainConfig/resetPending'
import PayRemaining from './payRemaining'
import ReadState from './read'
import SetBillingAccessController from './setBillingAccessController'
Expand All @@ -28,6 +29,7 @@ export default [
BeginOffchainConfig,
WriteOffchainConfig,
CommitOffchainConfig,
ResetPendingOffchainConfig,
SetBillingAccessController,
SetRequesterAccessController,
// Inspection
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Result } from '@chainlink/gauntlet-core'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana'
import { PublicKey } from '@solana/web3.js'
import { CONTRACT_LIST, getContract } from '../../../../lib/contracts'

export default class ResetPendingOffchainConfig extends SolanaCommand {
static id = 'ocr2:reset_pending_offchain_config'
static category = CONTRACT_LIST.OCR_2

static examples = ['yarn gauntlet ocr2:reset_pending_offchain_config --network=devnet --state=[OCR2_STATE]']
constructor(flags, args) {
super(flags, args)

this.require(!!this.flags.state, 'Please provide flags with "state"')
}

execute = async () => {
const ocr2 = getContract(CONTRACT_LIST.OCR_2, '')
const address = ocr2.programId.toString()
const program = this.loadProgram(ocr2.idl, address)

const state = new PublicKey(this.flags.state)
const owner = this.wallet.payer

const info = await program.account.state.fetch(state)
console.log(info.config.pendingOffchainConfig)
this.require(
info.config.pendingOffchainConfig.version != 0 || info.config.pendingOffchainConfig.len != 0,
'pending offchain config version is already in reset state',
)

await prompt(`Reset pending offchain config?`)

const tx = await program.rpc.resetPendingOffchainConfig({
accounts: {
state: state,
authority: owner.publicKey,
},
})

logger.success(`Reset pending offchain config on tx ${tx}`)

return {
responses: [
{
tx: this.wrapResponse(tx, state.toString(), { state: state.toString() }),
contract: state.toString(),
},
],
} as Result<TransactionResponse>
}
}