diff --git a/docs/gauntlet/README.md b/docs/gauntlet/README.md new file mode 100644 index 000000000..70801b607 --- /dev/null +++ b/docs/gauntlet/README.md @@ -0,0 +1,4 @@ +# Gauntlet Solana + +- [Setup](./gauntlet-setup.md) +- [Withdraw Payments](./withdraw.md) diff --git a/docs/gauntlet/gauntlet-setup.md b/docs/gauntlet/gauntlet-setup.md index 3a7b969c4..eebbd45a5 100644 --- a/docs/gauntlet/gauntlet-setup.md +++ b/docs/gauntlet/gauntlet-setup.md @@ -1,4 +1,4 @@ -# Gauntlet Solana +# Gauntlet Solana Setup Install node version with asdf. Which can be done by running this from the base of the chainlink-solana repository: diff --git a/docs/gauntlet/withdraw.md b/docs/gauntlet/withdraw.md new file mode 100644 index 000000000..134fdd73b --- /dev/null +++ b/docs/gauntlet/withdraw.md @@ -0,0 +1,19 @@ +# Withdraw Payments using Gauntlet + +When Node Operators report data to a feed, they are compensated with LINK thats owned by the feed. Node operators can then withdraw the funds owed to them via gauntlet using this section below + +## Sample Command Usage + +Make sure to set up gauntlet using the steps described in [Gauntlet Setup](gauntlet-setup.md) before attempting to run the following command. + +The `recipient` address is the payee address set in the contract configuration. This should be the Token derived address from the transaction signer address. + +```bash +yarn gauntlet ocr2:withdraw_payment --network=mainnet --recipient= +``` + +If you are using a Ledger, include the `--withLedger` flag. Gauntlet will ask you to sign the transaction using your Ledger. + +```bash +yarn gauntlet ocr2:withdraw_payment --network=mainnet --recipient= --withLedger +``` diff --git a/gauntlet/packages/gauntlet-serum-multisig/src/commands/multisig.ts b/gauntlet/packages/gauntlet-serum-multisig/src/commands/multisig.ts index a454031c4..40516f470 100644 --- a/gauntlet/packages/gauntlet-serum-multisig/src/commands/multisig.ts +++ b/gauntlet/packages/gauntlet-serum-multisig/src/commands/multisig.ts @@ -35,7 +35,7 @@ type ProposalState = { export const wrapCommand = (command) => { return class Multisig extends SolanaCommand { command: SolanaCommand - program: Program + program: Program multisigAddress: PublicKey static id = `${command.id}:multisig` diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts index e9faf0112..c0f5059b8 100644 --- a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/index.ts @@ -21,6 +21,7 @@ import ProposePayees from './proposePayees' import FinalizeProposal from './proposal/finalizeProposal' import Close from './close' import WithdrawFunds from './withdrawFunds' +import WithdrawPayment from './withdrawPayment' const getOwner = async (program, state) => { const contractState = await program.account.state.fetch(state) @@ -49,6 +50,7 @@ export default [ makeInspectOwnershipCommand(CONTRACT_LIST.OCR_2, getOwner), makeUpgradeProgramCommand(CONTRACT_LIST.OCR_2), WithdrawFunds, + WithdrawPayment, // Inspection ...Inspection, // ONLY DEV diff --git a/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/withdrawPayment.ts b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/withdrawPayment.ts new file mode 100644 index 000000000..60d165b13 --- /dev/null +++ b/gauntlet/packages/gauntlet-solana-contracts/src/commands/contracts/ocr2/withdrawPayment.ts @@ -0,0 +1,97 @@ +import { Result } from '@chainlink/gauntlet-core' +import { SolanaCommand, TransactionResponse } from '@chainlink/gauntlet-solana' +import { PublicKey } from '@solana/web3.js' +import { TOKEN_PROGRAM_ID } from '@solana/spl-token' +import { utils } from '@project-serum/anchor' +import { logger, prompt } from '@chainlink/gauntlet-core/dist/utils' +import { CONTRACT_LIST, getContract } from '../../../lib/contracts' + +type Input = { + recipient: string +} + +export default class WithdrawPayment extends SolanaCommand { + static id = 'ocr2:withdraw_payment' + static category = CONTRACT_LIST.OCR_2 + + static examples = [ + 'yarn gauntlet ocr2:withdraw_payment --network=devnet --recipient=YOUR_LINK_ACCOUNT ', + 'yarn gauntlet ocr2:withdraw_payment --network=devnet --recipient=FTH1Kqvr5BhiAA786DdQVBQYJ1bs5XhKwTEETKCqYwMh 9hBz81AnfoeGgqVqQHKBiAXGJ2hKAs7A2KYFxn5yGgat', + ] + + input: Input + + makeInput = (userInput: any): Input => { + if (userInput) return userInput as Input + + if (!this.flags.recipient) { + throw Error('Please specify a valid LINK --recipient for withdrawal') + } + + return { + recipient: this.flags.recipient, + } + } + + constructor(flags, args) { + super(flags, args) + } + + buildCommand = async (flags, args) => { + const ocr2 = getContract(CONTRACT_LIST.OCR_2, '') + this.program = this.loadProgram(ocr2.idl, ocr2.programId.toString()) + this.input = this.makeInput(flags.input) + return this + } + + beforeExecute = async () => { + logger.loading(`Executing ${WithdrawPayment.id} from contract ${this.args[0]}`) + await prompt(`Continue?`) + } + + makeRawTransaction = async (signer: PublicKey) => { + const state = new PublicKey(this.args[0]) + + const info = (await this.program.account.state.fetch(state)) as any + const tokenVault = new PublicKey(info.config.tokenVault) + const [vaultAuthority] = await PublicKey.findProgramAddress( + [Buffer.from(utils.bytes.utf8.encode('vault')), state.toBuffer()], + this.program.programId, + ) + + const data = this.program.instruction.withdrawPayment({ + accounts: { + state, + authority: signer, + tokenVault: tokenVault, + vaultAuthority: vaultAuthority, + tokenProgram: TOKEN_PROGRAM_ID, + payee: new PublicKey(this.input.recipient), + }, + }) + + return [data] + } + + execute = async () => { + await this.buildCommand(this.flags, this.args) + // use local wallet as signer + const signer = this.wallet.publicKey + + const rawTx = await this.makeRawTransaction(signer) + await this.simulateTx(signer, rawTx) + + await this.beforeExecute() + const txhash = await this.signAndSendRawTx(rawTx) + logger.success(`Payment withdrew on tx hash: ${txhash}`) + + return { + responses: [ + { + tx: this.wrapResponse(txhash, this.args[0]), + contract: this.args[0], + }, + ], + } as Result + } +}