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

Gauntlet sec improvements #166

Merged
merged 6 commits into from
Mar 4, 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
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import AbstractCommand, { makeAbstractCommand } from '.'
import { Result } from '@chainlink/gauntlet-core'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
import { AccAddress } from '@terra-money/terra.js'
import { AccAddress, LCDClient } from '@terra-money/terra.js'
import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils'

export type BeforeExecutionContext<Input, ContractInput> = {
export type ExecutionContext<Input, ContractInput> = {
input: Input
contractInput: ContractInput
id: string
contract: string
provider: LCDClient
flags: any
}

export type BeforeExecute<Input, ContractInput> = (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a function, returning a function, returning a promise?

Makes more sense for the BeforeExecute type to be a simple function returning a promise?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The instruction command will provide the context in what the execution will happen. Then is responsibility of the command executor to actually use the hook.
IE, multisig command don't have the context of what's happening in the underlying commands, just wants to trigger the hook

context: ExecutionContext<Input, ContractInput>,
) => (signer: AccAddress) => Promise<void>

export interface AbstractInstruction<Input, ContractInput> {
examples?: string[]
instruction: {
Expand All @@ -21,10 +27,16 @@ export interface AbstractInstruction<Input, ContractInput> {
makeInput: (flags: any, args: string[]) => Promise<Input>
validateInput: (input: Input) => boolean
makeContractInput: (input: Input) => Promise<ContractInput>
beforeExecute?: (context: BeforeExecutionContext<Input, ContractInput>) => () => Promise<void>
beforeExecute?: BeforeExecute<Input, ContractInput>
afterExecute?: (response: Result<TransactionResponse>) => any
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feel free to also type this function, something like:

export type AfterExecute<Input, ContractInput> = (
  context: ExecutionContext<Input, ContractInput>,
  response: Result<TransactionResponse>,
) => Promise<any>

Additionally, not too fond of these <Input, ContractInput> types overall but no suggestions yet.

}

const defaultBeforeExecute = <Input, ContractInput>(context: ExecutionContext<Input, ContractInput>) => async () => {
logger.loading(`Executing ${context.id} from contract ${context.contract}`)
logger.log('Input Params:', context.contractInput)
await prompt(`Continue?`)
}

export const instructionToCommand = <Input, ContractInput>(instruction: AbstractInstruction<Input, ContractInput>) => {
const id = `${instruction.instruction.contract}:${instruction.instruction.function}`
const category = `${instruction.instruction.category}`
Expand All @@ -40,12 +52,6 @@ export const instructionToCommand = <Input, ContractInput>(instruction: Abstract
super(flags, args)
}

defaultBeforeExecute = (context: BeforeExecutionContext<Input, ContractInput>) => async () => {
logger.loading(`Executing ${context.id} from contract ${context.contract}`)
logger.log('Input Params:', context.contractInput)
await prompt(`Continue?`)
}

afterExecute = instruction.afterExecute || this.afterExecute

buildCommand = async (flags, args): Promise<TerraCommand> => {
Expand All @@ -54,15 +60,17 @@ export const instructionToCommand = <Input, ContractInput>(instruction: Abstract
throw new Error(`Invalid input params: ${JSON.stringify(input)}`)
}
const contractInput = await instruction.makeContractInput(input)
const beforeExecutionContext: BeforeExecutionContext<Input, ContractInput> = {
const executionContext: ExecutionContext<Input, ContractInput> = {
input,
contractInput,
id,
contract: this.args[0],
provider: this.provider,
flags,
}
this.beforeExecute = instruction.beforeExecute
? instruction.beforeExecute(beforeExecutionContext)
: this.defaultBeforeExecute(beforeExecutionContext)
? instruction.beforeExecute(executionContext)
: defaultBeforeExecute(executionContext)

const abstractCommand = await makeAbstractCommand(id, this.flags, this.args, contractInput)
await abstractCommand.invokeMiddlewares(abstractCommand, abstractCommand.middlewares)
Expand All @@ -79,7 +87,7 @@ export const instructionToCommand = <Input, ContractInput>(instruction: Abstract
// TODO: Command should be built from gauntet-core
await this.buildCommand(this.flags, this.args)
//
await this.beforeExecute()
await this.beforeExecute(this.wallet.key.accAddress)
let response = await this.command.execute()
const data = this.afterExecute(response)
if (data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export default class AbstractCommand extends TerraCommand {
}

abstractQuery: AbstractExecute = async (params: any, address: string) => {
logger.loading(`Calling ${this.opts.function} from contract ${this.opts.contract.id} at ${address}`)
logger.debug(`Calling ${this.opts.function} from contract ${this.opts.contract.id} at ${address}`)
const result = await this.query(address, params)
logger.debug(`Query finished with result: ${JSON.stringify(result)}`)
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import AbstractCommand, { makeAbstractCommand } from '.'
import { makeAbstractCommand } from '.'
import { Result } from '@chainlink/gauntlet-core'
import { TerraCommand, TransactionResponse } from '@chainlink/gauntlet-terra'
import { logger } from '@chainlink/gauntlet-core/dist/utils'
import { CATEGORIES } from '../../lib/constants'
import { CONTRACT_LIST } from '../../lib/contracts'
import { APIParams } from '@terra-money/terra.js/dist/client/lcd/APIRequester'

export type Query = (contractAddress: string, query: any, params?: APIParams) => Promise<any>
import { LCDClient } from '@terra-money/terra.js'

/**
* Inspection commands need to match this interface
Expand All @@ -31,9 +29,9 @@ export interface InspectInstruction<CommandInput, ContractExpectedInfo> {
function: string
}[]
makeInput: (flags: any, args: string[]) => Promise<CommandInput>
makeInspectionData: (query: Query) => (input: CommandInput) => Promise<ContractExpectedInfo>
makeInspectionData: (provider: LCDClient) => (input: CommandInput) => Promise<ContractExpectedInfo>
makeOnchainData: (
query: Query,
provider: LCDClient,
) => (instructionsData: any[], input: CommandInput, contractAddress: string) => Promise<ContractExpectedInfo>
inspect: (expected: ContractExpectedInfo, data: ContractExpectedInfo) => boolean
}
Expand Down Expand Up @@ -70,9 +68,8 @@ export const instructionToInspectCommand = <CommandInput, Expected>(
}),
)

const query: Query = this.provider.wasm.contractQuery.bind(this.provider.wasm)
const onchainData = await inspectInstruction.makeOnchainData(query)(data, input, this.args[0])
const inspectData = await inspectInstruction.makeInspectionData(query)(input)
const onchainData = await inspectInstruction.makeOnchainData(this.provider)(data, input, this.args[0])
const inspectData = await inspectInstruction.makeInspectionData(this.provider)(input)
const inspection = inspectInstruction.inspect(inspectData, onchainData)
return {
data: inspection,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BN, logger, prompt } from '@chainlink/gauntlet-core/dist/utils'
import { CATEGORIES, TOKEN_DECIMALS } from '../../../lib/constants'
import { AbstractInstruction, BeforeExecutionContext, instructionToCommand } from '../../abstract/executionWrapper'
import { AbstractInstruction, ExecutionContext, instructionToCommand } from '../../abstract/executionWrapper'
import { AccAddress } from '@terra-money/terra.js'

type CommandInput = {
Expand Down Expand Up @@ -35,7 +35,7 @@ const makeContractInput = async (input: CommandInput): Promise<ContractInput> =>
}
}

const beforeExecute = (context: BeforeExecutionContext<CommandInput, ContractInput>) => async (): Promise<void> => {
const beforeExecute = (context: ExecutionContext<CommandInput, ContractInput>) => async (): Promise<void> => {
logger.info(
`Transferring ${context.contractInput.amount} (${context.input.amount}) Tokens to ${context.contractInput.recipient}`,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { getRDD } from '../../../lib/rdd'
import { RDD } from '@chainlink/gauntlet-terra'
import { instructionToCommand, AbstractInstruction } from '../../abstract/executionWrapper'
import { CATEGORIES } from '../../../lib/constants'
import { CONTRACT_LIST } from '../../../lib/contracts'

type CommandInput = {
billingAccessController: string
Expand All @@ -25,7 +24,7 @@ type ContractInput = {

const makeCommandInput = async (flags: any, args: string[]): Promise<CommandInput> => {
if (flags.input) return flags.input as CommandInput
const rdd = getRDD(flags.rdd)
const rdd = RDD.getRDD(flags.rdd)
const contract = args[0]
const aggregator = rdd.contracts[contract]
return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { BN, inspection, logger } from '@chainlink/gauntlet-core/dist/utils'
import { providerUtils, RDD } from '@chainlink/gauntlet-terra'
import { CONTRACT_LIST } from '../../../../lib/contracts'
import { CATEGORIES, TOKEN_UNIT } from '../../../../lib/constants'
import { getRDD } from '../../../../lib/rdd'
import { InspectInstruction, instructionToInspectCommand, Query } from '../../../abstract/inspectionWrapper'
import { InspectInstruction, instructionToInspectCommand } from '../../../abstract/inspectionWrapper'
import { deserializeConfig } from '../../../../lib/encoding'
import { getOffchainConfigInput, OffchainConfig } from '../proposeOffchainConfig'
import { toComparableNumber, wrappedComparableLongNumber } from '../../../../lib/inspection'
import { LCDClient } from '@terra-money/terra.js'

// Command input and expected info is the same here
type ContractExpectedInfo = {
digest: string
description: string
decimals: string | number
transmitters: string[]
Expand All @@ -17,13 +21,15 @@ type ContractExpectedInfo = {
recommendedGasPriceMicro: string
transmissionPaymentGjuels: string
}
offchainConfig: OffchainConfig
totalOwed?: string
linkAvailable?: string
owner?: string
}

const makeInput = async (flags: any, args: string[]): Promise<ContractExpectedInfo> => {
if (flags.input) return flags.input as ContractExpectedInfo
const rdd = getRDD(flags.rdd)
const rdd = RDD.getRDD(flags.rdd)
const contract = args[0]
const info = rdd.contracts[contract]
const aggregatorOperators: string[] = info.oracles.map((o) => o.operator)
Expand All @@ -33,7 +39,6 @@ const makeInput = async (flags: any, args: string[]): Promise<ContractExpectedIn
const link = flags.link || process.env.LINK

return {
digest: flags.digest,
description: info.name,
decimals: info.decimals,
transmitters,
Expand All @@ -45,12 +50,13 @@ const makeInput = async (flags: any, args: string[]): Promise<ContractExpectedIn
recommendedGasPriceMicro: info.billing.recommendedGasPriceMicro,
transmissionPaymentGjuels: info.billing.transmissionPaymentGjuels,
},
offchainConfig: getOffchainConfigInput(rdd, contract),
}
}

const makeInspectionData = () => async (input: ContractExpectedInfo): Promise<ContractExpectedInfo> => input

const makeOnchainData = (query: Query) => async (
const makeOnchainData = (provider: LCDClient) => async (
instructionsData: any[],
input: ContractExpectedInfo,
aggregator: string,
Expand All @@ -64,17 +70,28 @@ const makeOnchainData = (query: Query) => async (
const requesterAC = instructionsData[6]
const link = instructionsData[7]
const linkAvailable = instructionsData[8]
const owner = instructionsData[9]

const owedPerTransmitter: string[] = await Promise.all(
transmitters.addresses.map((t) => {
return query(aggregator, {
return provider.wasm.contractQuery(aggregator, {
owed_payment: {
transmitter: t,
},
})
}),
)

// The contract only stores the block where the config was accepted. The tx log contains the config
const setConfigTx = providerUtils.filterTxsByEvent(
await providerUtils.getBlockTxs(provider, latestConfigDetails.block_number),
'wasm-set_config',
)
const event = setConfigTx?.logs?.[0].eventsByType['wasm-set_config']
const offchainConfig = event?.offchain_config
? await deserializeConfig(Buffer.from(event.offchain_config[0], 'base64'))
: ({} as OffchainConfig)

return {
description,
decimals,
Expand All @@ -83,21 +100,21 @@ const makeOnchainData = (query: Query) => async (
requesterAccessController: requesterAC,
link,
linkAvailable: linkAvailable.amount,
digest: Buffer.from(latestConfigDetails.config_digest).toString('hex'),
billing: {
observationPaymentGjuels: billing.observation_payment_gjuels,
transmissionPaymentGjuels: billing.transmission_payment_gjuels,
recommendedGasPriceMicro: billing.recommended_gas_price_micro,
},
totalOwed: owedPerTransmitter.reduce((agg: BN, v) => agg.add(new BN(v)), new BN(0)).toString(),
owner,
offchainConfig,
}
}

const inspect = (expected: ContractExpectedInfo, onchainData: ContractExpectedInfo): boolean => {
const inspections: inspection.Inspection[] = [
let inspections: inspection.Inspection[] = [
inspection.makeInspection(onchainData.description, expected.description, 'Description'),
inspection.makeInspection(onchainData.decimals, expected.decimals, 'Decimals'),
inspection.makeInspection(onchainData.digest, expected.digest, 'Offchain config digest'),
inspection.makeInspection(onchainData.transmitters, expected.transmitters, 'Transmitters'),
inspection.makeInspection(
onchainData.billingAccessController,
Expand Down Expand Up @@ -126,18 +143,97 @@ const inspect = (expected: ContractExpectedInfo, onchainData: ContractExpectedIn
'Transmission Payment',
),
]

if (!!onchainData.offchainConfig.s) {
const offchainConfigInspections: inspection.Inspection[] = [
inspection.makeInspection(onchainData.offchainConfig.s, expected.offchainConfig.s, 'Offchain Config "s"'),
inspection.makeInspection(
onchainData.offchainConfig.peerIds,
expected.offchainConfig.peerIds,
'Offchain Config "peerIds"',
),
inspection.makeInspection(
toComparableNumber(onchainData.offchainConfig.rMax),
toComparableNumber(expected.offchainConfig.rMax),
'Offchain Config "rMax"',
),
inspection.makeInspection(
onchainData.offchainConfig.offchainPublicKeys.map((k) => Buffer.from(k).toString('hex')),
expected.offchainConfig.offchainPublicKeys,
`Offchain Config "offchainPublicKeys"`,
),
inspection.makeInspection(
onchainData.offchainConfig.reportingPluginConfig.alphaReportInfinite,
expected.offchainConfig.reportingPluginConfig.alphaReportInfinite,
'Offchain Config "reportingPluginConfig.alphaReportInfinite"',
),
inspection.makeInspection(
onchainData.offchainConfig.reportingPluginConfig.alphaAcceptInfinite,
expected.offchainConfig.reportingPluginConfig.alphaAcceptInfinite,
'Offchain Config "reportingPluginConfig.alphaAcceptInfinite"',
),
inspection.makeInspection(
wrappedComparableLongNumber(onchainData.offchainConfig.reportingPluginConfig.alphaReportPpb),
toComparableNumber(expected.offchainConfig.reportingPluginConfig.alphaReportPpb),
`Offchain Config "reportingPluginConfig.alphaReportPpb"`,
),
inspection.makeInspection(
wrappedComparableLongNumber(onchainData.offchainConfig.reportingPluginConfig.alphaAcceptPpb),
toComparableNumber(expected.offchainConfig.reportingPluginConfig.alphaAcceptPpb),
`Offchain Config "reportingPluginConfig.alphaAcceptPpb"`,
),
inspection.makeInspection(
wrappedComparableLongNumber(onchainData.offchainConfig.reportingPluginConfig.deltaCNanoseconds),
toComparableNumber(expected.offchainConfig.reportingPluginConfig.deltaCNanoseconds),
`Offchain Config "reportingPluginConfig.deltaCNanoseconds"`,
),
]

const longNumberInspections = [
'deltaProgressNanoseconds',
'deltaResendNanoseconds',
'deltaRoundNanoseconds',
'deltaGraceNanoseconds',
'deltaStageNanoseconds',
'maxDurationQueryNanoseconds',
'maxDurationObservationNanoseconds',
'maxDurationReportNanoseconds',
'maxDurationShouldAcceptFinalizedReportNanoseconds',
'maxDurationShouldTransmitAcceptedReportNanoseconds',
].map((prop) =>
inspection.makeInspection(
wrappedComparableLongNumber(onchainData.offchainConfig[prop]),
toComparableNumber(expected.offchainConfig[prop]),
`Offchain Config "${prop}"`,
),
)

inspections = inspections.concat(offchainConfigInspections).concat(longNumberInspections)
} else {
logger.error('Could not get offchain config information from the contract. Skipping offchain config inspection')
}

logger.line()
logger.info('Inspection results:')
logger.info(`LINK Available: ${onchainData.linkAvailable}`)
logger.info(`Total LINK Owed: ${onchainData.totalOwed}`)
logger.info(`Ownership:
- Owner: ${onchainData.owner}
`)
logger.info(`Funding:
- LINK Available: ${onchainData.linkAvailable}
- Total LINK Owed: ${onchainData.totalOwed}
`)

const owedDiff = new BN(onchainData.linkAvailable).sub(new BN(onchainData.totalOwed)).div(new BN(TOKEN_UNIT))
if (owedDiff.lt(new BN(0))) {
logger.warn(`Total LINK Owed is higher than balance. Amount to fund: ${owedDiff.mul(new BN(-1)).toString()}`)
} else {
logger.success(`LINK Balance can cover debt. LINK after payment: ${owedDiff.toString()}`)
}
return inspection.inspect(inspections)

const result = inspection.inspect(inspections)
logger.line()

return result
}

const instruction: InspectInstruction<any, ContractExpectedInfo> = {
Expand Down Expand Up @@ -183,6 +279,10 @@ const instruction: InspectInstruction<any, ContractExpectedInfo> = {
contract: 'ocr2',
function: 'link_available_for_payment',
},
{
contract: 'ocr2',
function: 'owner',
},
],
makeInput,
makeInspectionData,
Expand Down
Loading