Skip to content

Commit

Permalink
Merge pull request #74 from shapeshift/hot-wallet-cli-part-1
Browse files Browse the repository at this point in the history
  • Loading branch information
kaladinlight authored Jul 15, 2024
2 parents bdcf1e8 + e3624bb commit bf6db8d
Show file tree
Hide file tree
Showing 17 changed files with 4,711 additions and 1 deletion.
1 change: 1 addition & 0 deletions .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ jobs:
id: test
- name: Generate coverage report
run: |
forge clean
forge coverage --report summary
id: coverage
9 changes: 9 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
{
"plugins": ["prettier-plugin-solidity"],
"printWidth": 120,
"endOfLine": "lf",
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"semi": false,
"arrowParens": "avoid",
"jsxSingleQuote": true,
"trailingComma": "all",
"overrides": [
{
"files": "*.sol",
Expand Down
2 changes: 1 addition & 1 deletion foundry/test/StakingTestUpgrades.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ contract FoxStakingTestUpgrades is Test {
// confrim still on old version
assertEq(foxStakingV1.version(), expectedCurrentVersion);

// Change the owner
// Change the owner
vm.startPrank(owner);
foxStakingV1.transferOwnership(newOwner);
vm.stopPrank();
Expand Down
66 changes: 66 additions & 0 deletions scripts/hotWalletCli/MultiSig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Prerequisites

- Install golang: https://go.dev/doc/install

## Clone and Build

```bash
git clone https://gitlab.com/thorchain/thornode.git
cd thornode/cmd/thornode
go build --tags cgo,ledger
```

## Create MultiSig

- Add your key:
```bash
./thornode keys add {person1} --ledger
```
- Export pubkey:
```bash
./thornode keys show {person1} --pubkey
```
- Import signer pubkeys:
```bash
./thornode keys add {person2} --pubkey {pubkey}
./thornode keys add {person3} --pubkey {pubkey}
```
- Add multisig key:
```bash
./thornode keys add multisig --multisig {person1},{person2},{person3} --multisig-threshold 2
```
- Validate multisig address:
```bash
./thornode keys show multisig --address
```

## Sign Transaction

- Person 1 signs:
```bash
./thornode tx sign --from {person1} --multisig multisig {unsignedTx_epoch-N.json} --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --from ledger --ledger --sign-mode amino-json > signedTx_{person1}.json
```
- Person 2 signs:
```bash
./thornode tx sign --from {person2} --multisig multisig {unsignedTx_epoch-N.json} --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --from ledger --ledger --sign-mode amino-json > signedTx_{person2}.json
```
- Multisign:
```bash
./thornode tx multisign {unsignedTx_epoch-N.json} multisig signedTx_{person1}.json signedTx_{person2}.json --from multisig --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc > signedTx_multisig.json
```

## Send Transaction

- Simulate transaction:

```bash
./thornode tx broadcast signedTx_multisig.json --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --gas auto --dry-run > simulatedTx.json
```

- Validate contents of `simulatedTx.json` for accuracy before broadcasting

- Broadcast transaction:
```bash
./thornode tx broadcast signedTx_multisig.json --chain-id thorchain-mainnet-v1 --node https://daemon.thorchain.shapeshift.com:443/rpc --gas auto > tx.json
```
- Copy the `txhash` value from `tx.json` to supply to the cli in order to continue
27 changes: 27 additions & 0 deletions scripts/hotWalletCli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Prerequisites

- NodeJS (v18+): https://nodejs.org/en/download/package-manager
- Yarn: https://classic.yarnpkg.com/lang/en/docs/install/#debian-stable

## Setup

- Install dependencies:

```bash
yarn
```

- Copy sample env file:

```bash
cp sample.env .env
```

- Request environment variables and update `.env` with the appropriate values

## Running

- Run script:
```bash
yarn start
```
4 changes: 4 additions & 0 deletions scripts/hotWalletCli/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import os from 'node:os'
import path from 'node:path'

export const RFOX_DIR = path.join(os.homedir(), 'rfox')
39 changes: 39 additions & 0 deletions scripts/hotWalletCli/file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import fs from 'node:fs'
import { RFOX_DIR } from './constants'
import { error, info, warn } from './logging'

const deleteIfExists = (file: string) => {
try {
fs.accessSync(file, fs.constants.F_OK)
fs.unlinkSync(file)
} catch {}
}

export const write = (file: string, data: string) => {
try {
deleteIfExists(file)
fs.writeFileSync(file, data, { mode: 0o400, encoding: 'utf8' })
} catch {
error(`Failed to write file ${file}, exiting.`)
warn('Manually save the contents at the specified file location if possible, or a temporary file for recovery!!!')
info(data)
process.exit(1)
}
}

export const read = (file: string): string | undefined => {
try {
return fs.readFileSync(file, 'utf8')
} catch {}
}

export const isEpochDistributionStarted = (epoch: number): boolean => {
const regex = new RegExp(`epoch-${epoch}`)

try {
const files = fs.readdirSync(RFOX_DIR)
return Boolean(files.filter(file => regex.test(file)).length)
} catch {
return false
}
}
120 changes: 120 additions & 0 deletions scripts/hotWalletCli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import 'dotenv/config'
import * as prompts from '@inquirer/prompts'
import fs from 'node:fs'
import path from 'node:path'
import { Epoch } from '../types'
import { RFOX_DIR } from './constants'
import { isEpochDistributionStarted } from './file'
import { IPFS } from './ipfs'
import { error, info, success, warn } from './logging'
import { create, recoverKeystore } from './mnemonic'
import { Wallet } from './wallet'

const run = async () => {
const ipfs = await IPFS.new()

const epoch = await ipfs.getEpoch()

if (isEpochDistributionStarted(epoch.number)) {
const cont = await prompts.confirm({
message: 'It looks like you have already started a distribution for this epoch. Do you want to continue? ',
})

if (cont) return recover(epoch)

info(`Please move or delete all existing files for epoch-${epoch.number} from ${RFOX_DIR} before re-running.`)
warn('This action should never be taken unless you are absolutely sure you know what you are doing!!!')

process.exit(0)
}

const mnemonic = await create(epoch.number)

const confirmed = await prompts.confirm({
message: 'Have you securely backed up your mnemonic? ',
})

if (!confirmed) {
error('Unable to proceed knowing you have not securely backed up your mnemonic, exiting.')
process.exit(1)
}

const wallet = await Wallet.new(mnemonic)

await processEpoch(epoch, wallet, ipfs)
}

const recover = async (epoch?: Epoch) => {
const ipfs = await IPFS.new()

if (!epoch) epoch = await ipfs.getEpoch()

const keystoreFile = path.join(RFOX_DIR, `keystore_epoch-${epoch.number}.txt`)
const mnemonic = await recoverKeystore(keystoreFile)

const wallet = await Wallet.new(mnemonic)

await processEpoch(epoch, wallet, ipfs)
}

const processEpoch = async (epoch: Epoch, wallet: Wallet, ipfs: IPFS) => {
await wallet.fund(epoch)
const processedEpoch = await wallet.distribute(epoch)

const processedEpochHash = await ipfs.addEpoch(processedEpoch)
await ipfs.updateMetadata({ number: processedEpoch.number, hash: processedEpochHash })

success(`rFOX reward distribution for Epoch #${processedEpoch.number} has been completed!`)

info(
'Please update the rFOX Wiki (https://github.com/shapeshift/rFOX/wiki/rFOX-Metadata) and notify the DAO accordingly. Thanks!',
)
}

const shutdown = () => {
console.log()
warn('Received shutdown signal, exiting.')
process.exit(0)
}

const main = async () => {
try {
fs.mkdirSync(RFOX_DIR)
} catch (err) {
if (err instanceof Error) {
const fsError = err as NodeJS.ErrnoException
if (fsError.code !== 'EEXIST') throw err
}
}

const choice = await prompts.select<'run' | 'recover'>({
message: 'What do you want to do?',
choices: [
{
name: 'Run rFox distribution',
value: 'run',
description: 'Start here to process a new rFox distribution epoch',
},
{
name: 'Recover rFox distribution',
value: 'recover',
description: 'Use this to recover from an error during an rFox distribution epoch',
},
],
})

switch (choice) {
case 'run':
return run()
case 'recover':
return recover()
default:
error(`Invalid choice: ${choice}, exiting.`)
process.exit(1)
}
}

process.on('SIGINT', shutdown)
process.on('SIGTERM', shutdown)

main()
Loading

0 comments on commit bf6db8d

Please sign in to comment.