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

Jawn/GetSetRemovePrograms #281

Merged
merged 12 commits into from
Jan 29, 2024
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
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.10.0
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ import Entropy from '@entropyxyz/entropy-js'

const entropyAccount: EntropyAccount = {
sigRequestKey: signer,
programModKey: signer
programModKey: signer,
programDeployKey: signer,
}

entropy = new Entropy({ account: entropyAccount})
Expand Down
2 changes: 1 addition & 1 deletion entropy-metadata.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
"typescript": "^4.9.5"
},
"dependencies": {
"@entropyxyz/x25519-chacha20poly1305-nodejs": "^0.2.0",
"@entropyxyz/x25519-chacha20poly1305-web": "^0.2.0",
"@entropyxyz/entropy-protocol-nodejs": "^0.1.0",
"@entropyxyz/entropy-protocol-web": "^0.1.0",
"@ethereumjs/rlp": "^5.0.1",
"@ethereumjs/tx": "^5.1.0",
"@ethereumjs/util": "^9.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/extrinsic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export default class ExtrinsicBaseClass {

async sendAndWaitFor (
call: SubmittableExtrinsic<'promise'>,
freeTx = true,
freeTx = false,
filter: EventFilter
): Promise<EventRecord> {
const newCall = freeTx ? await this.handleFreeTx(call) : call
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@

const entropyAccount: EntropyAccount = {
sigRequestKey: signer,
programModKey: signer
programModKey: signer,
programDeployKey: signer,
}

const entropy = new Entropy({ account: entropyAccount})
Expand Down Expand Up @@ -200,7 +201,7 @@
*/
async getVerifyingKey (address: Address): Promise<string> {
const registeredInfo = await this.substrate.query.relayer.registered(address)
// @ts-ignore: next line

Check warning on line 204 in src/index.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint

Do not use "@ts-ignore" because it alters compilation errors
return registeredInfo.toHuman().verifyingKey
}

Expand Down
71 changes: 70 additions & 1 deletion src/programs/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import ExtrinsicBaseClass from '../extrinsic'
import { ApiPromise } from '@polkadot/api'
import { Signer } from '../types'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { hex2buf } from '../utils'
import { hex2buf, stripHexPrefix } from '../utils'
import * as util from '@polkadot/util'

export interface ProgramInfoJSON {
bytecode: string
configurationInterface?: unknown
deployer: string
refCounter: number
}

export interface ProgramInfo {
bytecode: ArrayBuffer
configurationInterface?: unknown
deployer: string
refCounter: number
}

export default class ProgramDev extends ExtrinsicBaseClass {
constructor ({
Expand All @@ -16,4 +29,60 @@ export default class ProgramDev extends ExtrinsicBaseClass {
}) {
super({ substrate, signer })
}

async get (pointer: string): Promise<ProgramInfo> {
// fetch program bytecode using the program pointer at the specific block hash
const responseOption = await this.substrate.query.programs.programs(pointer)

if (responseOption.isNone) {
throw new Error(`No program defined for the given pointer: ${pointer}`)
}

const programInfo = responseOption.toJSON()

return this.#formatProgramInfo(programInfo)
}


async deploy (
program: ArrayBuffer,
configurationInterface?: unknown,
): Promise<string> {

// converts program and configurationInterface into a palatable format
const formatedConfig = JSON.stringify(configurationInterface)
// programModKey is the caller of the extrinsic
const tx: SubmittableExtrinsic<'promise'> = this.substrate.tx.programs.setProgram(
util.u8aToHex(new Uint8Array(program)),
formatedConfig
)

const record = await this.sendAndWaitFor(tx, false, {
section: 'programs',
name: 'ProgramCreated',
})
const programHash = record.event.data[1].toHex()

return programHash
}

async remove (
programHash: string | Uint8Array,
): Promise<void> {

const tx: SubmittableExtrinsic<'promise'> = this.substrate.tx.programs.removeProgram(
programHash
)

await this.sendAndWaitFor(tx, false, {
section: 'programs',
name: 'ProgramRemoved',
})
}

#formatProgramInfo (programInfo): ProgramInfo {
const { configurationInterface, deployer, refCounter } = programInfo
const bytecode = hex2buf(stripHexPrefix(programInfo.bytecode)) // Convert hex string to ArrayBuffer
return { configurationInterface, deployer, refCounter, bytecode }
}
}
148 changes: 81 additions & 67 deletions src/programs/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { ApiPromise } from '@polkadot/api'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import * as util from '@polkadot/util'

import ExtrinsicBaseClass from '../extrinsic'
import ProgramDevManager from './dev'
import ProgramDev from './dev'
import { Signer } from '../types'
import { hex2buf } from '../utils'
import { u8aToString, u8aToHex, stringToU8a } from '@polkadot/util'

export interface ProgramData {
pointer: string
config?: unknown
}

/**
* @remarks
Expand All @@ -16,13 +19,13 @@
/**
* Creates an instance of ProgramManager.
* @param {ApiPromise} substrate - Substrate API object
* @param {Signer} programModKey - The signer object for the user interfacing with Substrate
* @param {Signer} programDeployKey - The signer object for the user interfacing with Substrate
* @param {Signer} programModKey - The signer object for the user interfacing with Entropy
* @param {Signer} programDeployKey - The signer object for the user interfacing with Entropy
* @remarks
* The constructor initializes the Substrate api and the signer.
* @alpha
*/

dev: ProgramDev
constructor ({
substrate,
programModKey,
Expand All @@ -32,106 +35,117 @@
programModKey: Signer
programDeployKey?: Signer
}) {
super({ substrate, signer })
this.dev = new ProgramDev(programDeployKey)
super({ substrate, signer: programModKey })
this.dev = new ProgramDev({substrate, signer: programDeployKey})
}


/**
* Retrieves the program associated with a given sigReqAccount (account)
* @param {string} sigReqAccount - The account key, defaulting to the signer's wallet address if not provided.
* @returns {Promise<ArrayBuffer>} - The program as an ArrayBuffer.
* @throws {Error} If no program is defined for the given account.
* @remarks
* This method communicates with Substrate to fetch bytecode associated with an account.
* The response is then procesed and converted to an ArrayBuffer before being returned
* The response is then processed and converted to an ArrayBuffer before being returned
* @alpha
*/

async get (sigReqAccount = this.signer.wallet.address): Promise<ArrayBuffer> {
const responseHexOption = await this.substrate.query.programs.bytecode(
async get (sigReqAccount: string): Promise<ProgramData[]> {
const registeredOption = await this.substrate.query.relayer.registered(
sigReqAccount
)
if (responseHexOption.isEmpty) {
throw new Error('No program defined for the given account.')

if (registeredOption.isEmpty) {
throw new Error(`No programs found for account: ${sigReqAccount}`)
}
// @ts-ignore: next line
const responseHex = responseHexOption.unwrap().toHex() // Get the hex representation
const byteBuffer = hex2buf(responseHex) // Convert hex string to ArrayBuffer
return byteBuffer

const registeredInfo = registeredOption.unwrap()

Check failure on line 62 in src/programs/index.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint

Property 'unwrap' does not exist on type 'Codec'.
return registeredInfo.programsData.map((program) => ({
// pointer: program.pointer.toString(),
pointer: program.programPointer.toString(),
// double check on how we're passing config
config: JSON.parse(u8aToString(program.programConfig)),
}))
}

/**
* Sets or updates the program of a specified account on Substrate
* Sets or updates the program of a specified account on Entropy.
* This method allows the current signer or an authorized account to update the program associated with the signer's account or another specified account.
* @param {ArrayBuffer} program - The program to be set or updated, as an ArrayBuffer.
* @param {string} [programModKey] - Optional. An authorized account to modify the program, if different from the signer's account.
* @param {string} [sigReqAccount=this.signer.wallet.address] -The account for which the program will be set or updated. Defaults to the signer's account.
* @returns {Promise<void>} A promise that resolves when the transaction has been included in the block.
* @throws {Error} Throws an error if the account is unauthorized or if there's a problem setting the program.
* @remarks
* This method handles the conversion of a program from an ArrayBuffer to a hex string
* This method handles the conversion of a program from an ArrayBuffer to a hex string.
* It checks for authorization if the programModKey is provided, ensuring that only authorized accounts can update the bytecode.
* The transaction is created and sent to Substrate. This method then awaits the confirmation event 'ProgramUpdated' to ensure that the update was successful.
* The transaction is created and sent to Entropy. This method then awaits the confirmation event 'ProgramUpdated' to ensure that the update was successful.
* @alpha
*/


async set (
program: ArrayBuffer,
newList: ProgramData[],
sigReqAccount = this.signer.wallet.address,
programModKey?: string
): Promise<void> {
programModKey = programModKey || sigReqAccount

const isAuthorized = await this.checkAuthorization(programModKey, sigReqAccount)


const registeredInfoOption = await this.substrate.query.relayer.registered(
sigReqAccount
)

if (registeredInfoOption.isEmpty) {
throw new Error(`Account not registered: ${sigReqAccount}`)
}

const registeredInfo = registeredInfoOption.unwrap()

Check failure on line 102 in src/programs/index.ts

View workflow job for this annotation

GitHub Actions / Build, test, and lint

Property 'unwrap' does not exist on type 'Codec'.

const isAuthorized =
registeredInfo.programModificationAccount.toString() === programModKey

if (!isAuthorized) {
throw new Error('Program modification is not authorized for the given account.')
throw new Error(`Unauthorized modification attempt by ${programModKey}`)
}

const programHex = util.u8aToHex(new Uint8Array(program))

const tx: SubmittableExtrinsic<'promise'> = this.substrate.tx.programs.updateProgram(
programModKey,
programHex

const newProgramInstances = newList.map((data) => ({
programPointer: u8aToHex(stringToU8a(data.pointer)),
programConfig: stringToU8a(JSON.stringify(data.config)),
}))

const tx: SubmittableExtrinsic<'promise'> = this.substrate.tx.relayer.changeProgramInstance(
sigReqAccount,
newProgramInstances
)

await this.sendAndWaitFor(tx, false, {
section: 'programs',
name: 'ProgramUpdated',
section: 'relayer',
name: 'changeProgramInstance',
})
}
/**
* Checks if a given program modification account is authorized to modify the program associated with a specific signature request account.
*
* @param {string} sigReqAccount - The account for which the program modification is intended.
* @param {string} programModKey - The account whose authorization is to be verified.
* @returns {Promise<boolean>} - A promise that resolves if the `programModKey` is authorized to modify the program for `sigReqAccount`
* @remarks
* This method queries Substrate to determine if the `programModKey` is allowed to modify the program associated with the `sigReqAccount`.
* The method utilizes the `allowedToModifyProgram` quert, which returns an optional value. If the value is present (`isSome`), it indicates authorization.
* (I'm not sure about this as the blob that's returned is extremely long )
* The method unwraps the optional value
* @example
* ```typescript
* const isAuthorized = await checkAuthorization('5FHneW46...HgYb3fW', '5DAAnrj7...P5JT7zP')
* console.log(isAuthorized) // Outputs: true or false
* ```
*/

async checkAuthorization (
programModKey: string,
sigReqAccount: string
): Promise<boolean> {
try {
const result = await this.substrate.query.programs.allowedToModifyProgram(
programModKey,
sigReqAccount
)
return !result.isEmpty
} catch (error) {
console.error('Error in checkAuthorization:', error)
return false
}
async remove (
programHashToRemove: string,
sigReqAccount = this.signer.wallet.address,
programModKey?: string
): Promise<void> {
const currentPrograms = await this.get(sigReqAccount)
// creates new array that contains all of the currentPrograms except programHashToRemove
const updatedPrograms = currentPrograms.filter(
(program) => program.pointer !== programHashToRemove
)
await this.set(updatedPrograms, sigReqAccount, programModKey)
}

async add (
newProgram: ProgramData,
sigReqAccount = this.signer.wallet.address,
programModKey?: string
): Promise<void> {
const currentPrograms = await this.get(sigReqAccount)
await this.set(
[...currentPrograms, newProgram],
sigReqAccount,
programModKey
)
}
}
Loading
Loading