Skip to content

Commit

Permalink
Merge branch 'dev' into naynay/file-restructure
Browse files Browse the repository at this point in the history
  • Loading branch information
rh0delta committed Sep 16, 2024
2 parents 67045d4 + 3a3130f commit 16eb051
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 59 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/cla.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
uses: contributor-assistant/github-action@v2.4.0
uses: entropyxyz/contributor-assistant-github-action@c5f4628ffe1edb97724edb64e0dd4795394d33e5 # exemptRepoOrgMembers
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Required, so that the bot in this repository has `write` permissions to Contents of remote repo.
Expand All @@ -47,5 +47,6 @@ jobs:
path-to-document: 'https://github.com/entropyxyz/.github/blob/main/legal/cla/v1/cla.md'
branch: 'main'
allowlist: dependabot[bot]
exemptRepoOrgMembers: true
remote-organization-name: entropyxyz
remote-repository-name: .github
11 changes: 11 additions & 0 deletions dev/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,14 @@ git push origin main --tags
```

go create a release on github if possible.


## Deploying new faucet
<!-- TO-DO: Cleanup requirements -->
#### Requirements
- faucet program
- build from repo or use binary in tests/programs/faucet_program.wasm
- configuration and aux data schema
- program mod account with funds to deploy
- child funded accounts to be used as issuers of funds for faucet
- child accounts must be registered with deployed faucet program
12 changes: 6 additions & 6 deletions src/common/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,27 +89,27 @@ export class EntropyLogger {
}

// maps to winston:error
public error (description: string, error: Error): void {
this.writeLogMsg('error', error?.message || error, this.context, description, error.stack);
error (description: string, error: Error, context?: string): void {
this.writeLogMsg('error', error?.message || error, context, description, error.stack);
}

// maps to winston:info
public log (message: any, context?: string): void {
log (message: any, context?: string): void {
this.writeLogMsg('info', message, context);
}

// maps to winston:warn
public warn (message: any, context?: string): void {
warn (message: any, context?: string): void {
this.writeLogMsg('warn', message, context);
}

// maps to winston:debug
public debug (message: any, context?: string): void {
debug (message: any, context?: string): void {
this.writeLogMsg('debug', message, context);
}

// maps to winston:verbose
public verbose (message: any, context?: string): void {
verbose (message: any, context?: string): void {
this.writeLogMsg('verbose', message, context);
}

Expand Down
10 changes: 10 additions & 0 deletions src/flows/entropyFaucet/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Testnet address used to deploy program on chain
// Used to derive various accounts registered to faucet program in order to be used for
// issuing Faucet Funds
export const FAUCET_PROGRAM_MOD_KEY = '5GWamxgW4XWcwGsrUynqnFq2oNZPqNXQhMDfgNH9xNsg2Yj7'
// Faucet program pointer
// To-DO: Look into deriving this program from owned programs of Faucet Program Mod Acct
// this is differnt from tests because the fauce that is live now was lazily deployed without schemas
// TO-DO: update this when faucet is deployed properly
export const TESTNET_PROGRAM_HASH = '0x12af0bd1f2d91f12e34aeb07ea622c315dbc3c2bdc1e25ff98c23f1e61106c77'
export const LOCAL_PROGRAM_HASH = '0x5fa0536818acaa380b0c349c8e887bf269d593a47e30c8e31de53a75d327f7b1'
96 changes: 96 additions & 0 deletions src/flows/entropyFaucet/faucet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// check verifying key has the balance and proper program hash

import Entropy from "@entropyxyz/sdk";
import { blake2AsHex, encodeAddress } from "@polkadot/util-crypto";
import { viewPrograms } from "../programs/view";
import FaucetSigner from "./signer";
import { FAUCET_PROGRAM_MOD_KEY, TESTNET_PROGRAM_HASH } from "./constants";
import { EntropyBalance } from "src/balance/main";

// only the faucet program should be on the key
async function faucetSignAndSend (call: any, entropy: Entropy, amount: number, senderAddress: string, chosenVerifyingKey: any): Promise<any> {
const api = entropy.substrate
const faucetSigner = new FaucetSigner(api.registry, entropy, amount, chosenVerifyingKey)

const sig = await call.signAsync(senderAddress, {
signer: faucetSigner,
});
return new Promise((resolve, reject) => {
sig.send(({ status, dispatchError }: any) => {
// status would still be set, but in the case of error we can shortcut
// to just check it (so an error would indicate InBlock or Finalized)
if (dispatchError) {
let msg: string
if (dispatchError.isModule) {
// for module errors, we have the section indexed, lookup
const decoded = api.registry.findMetaError(dispatchError.asModule);
// @ts-ignore
const { documentation, method, section } = decoded;

msg = `${section}.${method}: ${documentation.join(' ')}`
} else {
// Other, CannotLookup, BadOrigin, no extra info
msg = dispatchError.toString()
}
return reject(Error(msg))
}
if (status.isFinalized) resolve(status)
})
})
}

export async function getRandomFaucet (entropy: Entropy, previousVerifyingKeys: string[] = [], programModKey = FAUCET_PROGRAM_MOD_KEY) {
const modifiableKeys = await entropy.substrate.query.registry.modifiableKeys(programModKey)
const verifyingKeys = JSON.parse(JSON.stringify(modifiableKeys.toJSON()))

// Choosing one of the 5 verifiying keys at random to be used as the faucet sender
if (verifyingKeys.length === previousVerifyingKeys.length) {
throw new Error('FaucetError: There are no more faucets to choose from')
}
let chosenVerifyingKey = verifyingKeys[Math.floor(Math.random() * verifyingKeys.length)]
if (previousVerifyingKeys.length && previousVerifyingKeys.includes(chosenVerifyingKey)) {
const filteredVerifyingKeys = verifyingKeys.filter((key: string) => !previousVerifyingKeys.includes(key))
chosenVerifyingKey = filteredVerifyingKeys[Math.floor(Math.random() * filteredVerifyingKeys.length)]
}
const hashedKey = blake2AsHex(chosenVerifyingKey)
const faucetAddress = encodeAddress(hashedKey, 42).toString()

return { chosenVerifyingKey, faucetAddress, verifyingKeys }
}

export async function sendMoney (
entropy: Entropy,
endpoint: string,
{
amount,
addressToSendTo,
faucetAddress,
chosenVerifyingKey,
faucetProgramPointer = TESTNET_PROGRAM_HASH
}: {
amount: string,
addressToSendTo: string,
faucetAddress: string,
chosenVerifyingKey: string,
faucetProgramPointer: string
}
): Promise<any> {
const BalanceService = new EntropyBalance(entropy, endpoint)
// check balance of faucet address
const balance = await BalanceService.getBalance(faucetAddress)
if (balance <= 0) throw new Error('FundsError: Faucet Account does not have funds')
// check verifying key for only one program matching the program hash
const programs = await viewPrograms(entropy, { verifyingKey: chosenVerifyingKey })
if (programs.length) {
if (programs.length > 1) throw new Error('ProgramsError: Faucet Account has too many programs attached, expected less')
if (programs.length === 1 && programs[0].program_pointer !== faucetProgramPointer) {
throw new Error('ProgramsError: Faucet Account does not possess Faucet program')
}
} else {
throw new Error('ProgramsError: Faucet Account has no programs attached')
}

const transfer = entropy.substrate.tx.balances.transferAllowDeath(addressToSendTo, BigInt(amount));
const transferStatus = await faucetSignAndSend(transfer, entropy, parseInt(amount), faucetAddress, chosenVerifyingKey )
if (transferStatus.isFinalized) return transferStatus
}
85 changes: 41 additions & 44 deletions src/flows/entropyFaucet/index.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,45 @@
import inquirer from "inquirer"
import { print, accountChoices } from "../../common/utils"
import Entropy from "@entropyxyz/sdk"
import { getSelectedAccount, print } from "../../common/utils"
import { initializeEntropy } from "../../common/initializeEntropy"

export async function entropyFaucet ({ accounts }, options) {
import { EntropyLogger } from '../../common/logger'
import { getRandomFaucet, sendMoney } from "./faucet"
import { TESTNET_PROGRAM_HASH } from "./constants"

let chosenVerifyingKeys = []
export async function entropyFaucet ({ accounts, selectedAccount: selectedAccountAddress }, options, logger: EntropyLogger) {
const FLOW_CONTEXT = 'ENTROPY_FAUCET'
let faucetAddress
let chosenVerifyingKey
let entropy: Entropy
let verifyingKeys: string[] = []
const amount = "10000000000"
const { endpoint } = options
const selectedAccount = getSelectedAccount(accounts, selectedAccountAddress)
logger.log(`selectedAccount::`, FLOW_CONTEXT)
logger.log(selectedAccount, FLOW_CONTEXT)
try {
// @ts-ignore (see TODO on aliceAccount)
entropy = await initializeEntropy({ keyMaterial: selectedAccount.data, endpoint })

if (!entropy.registrationManager.signer.pair) {
throw new Error("Keys are undefined")
}

const accountQuestion = {
type: "list",
name: "selectedAccount",
message: "Choose account:",
choices: accountChoices(accounts),
}

const answers = await inquirer.prompt([accountQuestion])
const selectedAccount = answers.selectedAccount

const recipientAddress = selectedAccount.address
const aliceAccount = {
data: {
// type: "seed",
seed: "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a",
// admin TODO: missing this field
},
}

// @ts-ignore (see TODO on aliceAccount)
const entropy = await initializeEntropy({ keyMaterial: aliceAccount.data, endpoint })

if (!entropy.registrationManager.signer.pair) {
throw new Error("Keys are undefined")
}

const amount = "10000000000000000"
const tx = entropy.substrate.tx.balances.transferAllowDeath(
recipientAddress,
amount
)

await tx.signAndSend(
entropy.registrationManager.signer.pair,
async ({ status }) => {
if (status.isInBlock || status.isFinalized) {
print(recipientAddress, "funded")
}
({ chosenVerifyingKey, faucetAddress, verifyingKeys } = await getRandomFaucet(entropy, chosenVerifyingKeys))

await sendMoney(entropy, options.endpoint, { amount, addressToSendTo: selectedAccountAddress, faucetAddress, chosenVerifyingKey, faucetProgramPointer: TESTNET_PROGRAM_HASH })
// reset chosen keys after successful transfer
chosenVerifyingKeys = []
print(`Account: ${selectedAccountAddress} has been successfully funded with ${parseInt(amount).toLocaleString('en-US')} BITS`)
} catch (error) {
logger.error('Error issuing funds through faucet', error, FLOW_CONTEXT)
chosenVerifyingKeys.push(chosenVerifyingKey)
if (error.message.includes('FaucetError') || chosenVerifyingKeys.length === verifyingKeys.length) {
console.error('ERR::', error.message)
return
} else {
// Check for non faucet errors (FaucetError) and retry faucet
await entropyFaucet({ accounts, selectedAccount: selectedAccountAddress }, options, logger)
}
)
}
}
}
70 changes: 70 additions & 0 deletions src/flows/entropyFaucet/signer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import Entropy from "@entropyxyz/sdk";
import type { Signer, SignerResult } from "@polkadot/api/types";
import { Registry, SignerPayloadJSON } from "@polkadot/types/types";
import { u8aToHex } from "@polkadot/util";
import { stripHexPrefix } from "../../common/utils";
import { blake2AsHex, decodeAddress, encodeAddress, signatureVerify } from "@polkadot/util-crypto";

let id = 0
export default class FaucetSigner implements Signer {
readonly #registry: Registry
readonly #entropy: Entropy
readonly amount: number
readonly chosenVerifyingKey: any
readonly globalTest: any

constructor (
registry: Registry,
entropy: Entropy,
amount: number,
chosenVerifyingKey: any,
) {
this.#registry = registry
this.#entropy = entropy
this.amount = amount
this.chosenVerifyingKey = chosenVerifyingKey
}

async signPayload (payload: SignerPayloadJSON): Promise<SignerResult> {
// toU8a(true) is important as it strips the scale encoding length prefix from the payload
// without it transactions will fail
// ref: https://github.com/polkadot-js/api/issues/4446#issuecomment-1013213962
const raw = this.#registry.createType('ExtrinsicPayload', payload, {
version: payload.version,
}).toU8a(true);

const auxData = {
spec_version: 100,
transaction_version: 6,
string_account_id: this.#entropy.keyring.accounts.registration.address,
amount: this.amount
}

const signature = await this.#entropy.sign({
sigRequestHash: u8aToHex(raw),
// @ts-ignore
hash: {custom: 0},
auxiliaryData: [auxData],
signatureVerifyingKey: this.chosenVerifyingKey
})

let sigHex = u8aToHex(signature);
// the 02 prefix is needed for signature type edcsa (00 = ed25519, 01 = sr25519, 02 = ecdsa)
// ref: https://github.com/polkadot-js/tools/issues/175#issuecomment-767496439
sigHex = `0x02${stripHexPrefix(sigHex)}`

const hashedKey = blake2AsHex(this.chosenVerifyingKey)
const faucetAddress = encodeAddress(hashedKey)
const publicKey = decodeAddress(faucetAddress);

const hexPublicKey = u8aToHex(publicKey);

const signatureValidation = signatureVerify(u8aToHex(raw), sigHex, hexPublicKey)

if (signatureValidation.isValid) {
return { id: id++, signature: sigHex }
} else {
throw new Error('FaucetSignerError: Signature is not valid')
}
}
}
4 changes: 2 additions & 2 deletions src/flows/manage-accounts/new-account.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import inquirer from 'inquirer'
import { randomAsHex } from '@polkadot/util-crypto'
import { importQuestions } from './helpers/import-account'
import { importQuestions } from './utils/import-account'
// import * as passwordFlow from '../password'
import { print } from '../../common/utils'
import { createAccount } from './helpers/create-account'
import { createAccount } from './utils/create-account'
import { EntropyLogger } from 'src/common/logger'

export async function newAccount ({ accounts }, logger: EntropyLogger) {
Expand Down
2 changes: 1 addition & 1 deletion src/flows/programs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export async function userPrograms ({ accounts, selectedAccount: selectedAccount
validate: (input) => (input ? true : "Program pointer is required!"),
}])
logger.debug(`program pointer: ${programPointer}`, `${FLOW_CONTEXT}::PROGRAM_PRESENCE_CHECK`);
const program = await entropy.programs.dev.get(programPointer);
const program = await entropy.programs.dev.getProgramInfo(programPointer);
print(program);
} catch (error) {
console.error(error.message);
Expand Down
2 changes: 1 addition & 1 deletion src/tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function tui (entropy: Entropy, options: EntropyTuiOptions) {
// TODO: design programs in TUI (merge deploy+user programs)
'Deploy Program': flows.devPrograms,
'User Programs': flows.userPrograms,
// 'Construct an Ethereum Tx': flows.ethTransaction,
'Entropy Faucet': flows.entropyFaucet,
}

// const devChoices = {
Expand Down
Loading

0 comments on commit 16eb051

Please sign in to comment.