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

ISSUE#405 dev programs rework #409

Merged
merged 18 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from 13 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
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,17 @@ Version header format: `[version] Name - year-month-day (entropy-core compatibil
## [UNRELEASED]

### Added
- `configurationSchema` & `auxiliaryDataSchema` to `ProgramInterface`


### Fixed

### Changed

- util function rename: `hex2buf` -> `hexStringToBuffer`
- massive changes to `entropy.programs.dev`: (Look at documentation for more details)
- `getProgramInfo` -> `get` and bytecode is returned as a buffer. not a Uint8buffer to match what was deployed
- you now get owned programs for an address using `getByDeployer`.
- interface name change: `ProgramInfo` -> `ProgramInterface`
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
### Broke

### Dev
Expand Down
62 changes: 47 additions & 15 deletions src/programs/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ import ExtrinsicBaseClass from '../extrinsic'
import { ApiPromise } from '@polkadot/api'
import { Signer } from '../keys/types/internal'
import { SubmittableExtrinsic } from '@polkadot/api/types'
import { hex2buf, stripHexPrefix } from '../utils'
import { hexStringToBuffer, stripHexPrefix, hexStringToJSON } from '../utils'
import * as util from '@polkadot/util'
import { HexString } from '../keys/types/json'

/**
* Represents program information.
*
* @interface ProgramInfo
* @interface ProgramInterface
* @property {ArrayBuffer} bytecode - The bytecode of the program.
* @property {unknown} [interfaceDescription] - Optional. The configuration interface of the program.
* @property {string} deployer - The address of the deployer of the program.
* @property {number} refCounter - The reference count for the program.
*/

// interfaceDescription needs better design and another type other than 'unknown'
export interface ProgramInfo {
export interface ProgramInterface {
Copy link
Collaborator

Choose a reason for hiding this comment

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

before release, lets see if we can get this exported for use in CLI / other apps

bytecode: ArrayBuffer
interfaceDescription?: unknown
configurationSchema: unknown
auxiliaryDataSchema: unknown
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
// not quite supported yet
// oracleDataPointer?: []
deployer: string
refCounter: number
}
Expand Down Expand Up @@ -55,7 +58,7 @@ export default class ProgramDev extends ExtrinsicBaseClass {
* @returns {Promise<string[]>} A promise that resolves to the list of program pointers
*/

async get (address: string): Promise<any> {
async getByDeployer (address: string): Promise<any> {
const programs = await this.substrate.query.programs.ownedPrograms(address);
return programs.toHuman()
}
Expand All @@ -64,16 +67,17 @@ export default class ProgramDev extends ExtrinsicBaseClass {
* Retrieves program information using a program pointer.
*
* @param {string} pointer - The program pointer to fetch the program bytecode.
* @returns {Promise<ProgramInfo>} A promise that resolves to the program information.
* @returns {Promise<ProgramInterface>} A promise that resolves to the program information.
*/

async getProgramInfo (pointer: string): Promise<ProgramInfo> {
async get (pointer: string): Promise<ProgramInterface> {
// fetch program bytecode using the program pointer at the specific block hash
if (pointer.length <= 48) throw new Error('pointer length is less then or equal to 48. are you using an address?')
const responseOption = await this.substrate.query.programs.programs(pointer)

const programInfo = responseOption.toJSON()

return this.#formatProgramInfo(programInfo)
return this.#formatProgramInterface(programInfo)
}

/**
Expand All @@ -95,12 +99,13 @@ export default class ProgramDev extends ExtrinsicBaseClass {
): Promise<HexString> {
// converts program and configurationInterface into a palatable format
const formatedConfig = JSON.stringify(configurationSchema)
const formatedAuxData = JSON.stringify(auxiliaryDataSchema)
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
// programModKey is the caller of the extrinsic
const tx: SubmittableExtrinsic<'promise'> =
this.substrate.tx.programs.setProgram(
util.u8aToHex(new Uint8Array(program)), // new program
formatedConfig, // config schema
auxiliaryDataSchema, // auxilary config schema
formatedAuxData, // auxilary config schema
[] // oracleDataPointer // oracle data pointer
)
const record = await this.sendAndWaitFor(tx, {
Expand All @@ -115,6 +120,8 @@ export default class ProgramDev extends ExtrinsicBaseClass {
/**
* Removes an existing program.
*
* (removing a program is currently unstable and may not remove the program from chain as intended.)
*
* @param {string | Uint8Array} programHash - The hash of the program to remove.
* @returns {Promise<void>} A promise that resolves when the program is removed.
*/
Expand All @@ -129,18 +136,43 @@ export default class ProgramDev extends ExtrinsicBaseClass {
})
}

/**
* @internal
*
* trys to parse schema as a json. If fails because it's not a json returns original schema. throws for any other reason
*
* @param {any} programInfo - The program information in JSON format.
* @returns {unknown} - The formatted program information.
*/
#tryParseSchema (schema: any): unknown {
try {
return hexStringToJSON(schema)
} catch (e) {
if (e.message.includes('is not valid JSON')) return schema
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
throw e
}
}

/**
* @internal
*
* Formats program information.
*
* @param {ProgramInfoJSON} programInfo - The program information in JSON format.
* @returns {ProgramInfo} - The formatted program information.
* @param {ProgramInterfaceJSON} programInfo - The program information in JSON format.
* @returns {ProgramInterface} - The formatted program information.
*/

#formatProgramInfo (programInfo): ProgramInfo {
const { interfaceDescription, deployer, refCounter } = programInfo
const bytecode = hex2buf(stripHexPrefix(programInfo.bytecode)) // Convert hex string to ArrayBuffer
return { interfaceDescription, deployer, refCounter, bytecode }
#formatProgramInterface (programInfo): ProgramInterface {
const { deployer, refCounter } = programInfo
const bytecode = hexStringToBuffer(stripHexPrefix(programInfo.bytecode)) // Convert hex string to ArrayBuffer
const configurationSchema = this.#tryParseSchema(programInfo.configurationSchema)// Convert hex string to ArrayBuffer
const auxiliaryDataSchema = this.#tryParseSchema(programInfo.auxiliaryDataSchema)// Convert hex string to ArrayBuffer
mixmix marked this conversation as resolved.
Show resolved Hide resolved
return {
configurationSchema,
auxiliaryDataSchema,
deployer,
refCounter,
bytecode,
}
}
}
17 changes: 9 additions & 8 deletions src/registration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,7 @@ export default class RegistrationManager extends ExtrinsicBaseClass {
const registerTx = this.substrate.tx.registry.register(
programDeployer,
keyVisibility,
programData.map((programInfo) => {
return {
program_pointer: programInfo.program_pointer,
program_config: Array.from(
Buffer.from(JSON.stringify(programInfo.program_config))
),
}
})
programData.map(this.#formatProgramInfo)
)
// @ts-ignore: next line
// Send the registration transaction and wait for the result.
Expand Down Expand Up @@ -160,4 +153,12 @@ export default class RegistrationManager extends ExtrinsicBaseClass {
})
})
}

#formatProgramInfo (programInfo): ProgramInstance {
const program: ProgramInstance = { program_pointer: programInfo.program_pointer }
if (programInfo.program_config) program.program_config = Array.from(
Buffer.from(JSON.stringify(programInfo.program_config))
)
return program
}
}
19 changes: 13 additions & 6 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,19 @@ export function toHex (str: any) {
* @returns {ArrayBuffer} The ArrayBuffer representation of the hexadecimal string.
*/

export function hex2buf (hex: string): ArrayBuffer {
const bytes = new Uint8Array(Math.ceil(hex.length / 2))
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16)
}
return bytes.buffer
export function hexStringToBuffer (hex: string): ArrayBuffer {
return Buffer.from(hex, 'hex')
}

/**
* Converts a hexadecimal string to a JSON object.
*
* @param {string} hex - The hexadecimal string to convert.
* @returns {unknown} The ArrayBuffer representation of the hexadecimal string.
*/

export function hexStringToJSON (hex: string): ArrayBuffer {
return JSON.parse(hexStringToBuffer(stripHexPrefix(hex)).toString())
mixmix marked this conversation as resolved.
Show resolved Hide resolved
}

export function hexStringToUint8Array (hex: string): Uint8Array {
Expand Down
113 changes: 113 additions & 0 deletions tests/programs-dev.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import test from 'tape'
import { readFileSync } from 'fs'
import Entropy, { wasmGlobalsReady } from '../src'
import Keyring from '../src/keys'

import {
promiseRunner,
spinNetworkUp,
charlieStashAddress,
spinNetworkDown,
createTestAccount,
} from './testing-utils'

const networkType = 'two-nodes'

test('Programs#dev: all methods', async (t) => {
const run = promiseRunner(t)
await run('network up', spinNetworkUp(networkType))

await run('wasm', wasmGlobalsReady())

const entropy = await createTestAccount()

t.teardown(async () => {
await entropy.close()
await spinNetworkDown(networkType)
})


// wait for entropy to be ready
await run(
'entropy ready',
entropy.ready
)
mixmix marked this conversation as resolved.
Show resolved Hide resolved


// deploy
const noopProgram: any = readFileSync(
'./tests/testing-utils/program_noop.wasm',

)

const configSchema = {
noop_param: 'string',
}
const auxDataSchema = {
noop_param: 'number'
}
frankiebee marked this conversation as resolved.
Show resolved Hide resolved
const newPointer = await run(
'deploy',
entropy.programs.dev.deploy(noopProgram, configSchema, auxDataSchema)
)
console.log('newPointer:', newPointer)
const programsDeployed = await run(
'get deployed programs',
entropy.programs.dev.getByDeployer(entropy.keyring.accounts.programDev.address)
)
try {
await entropy.programs.dev.get(entropy.keyring.accounts.programDev.address)
t.fail('entropy.programs.dev.get(entropy.keyring.accounts.programDev.address) should have failed')
} catch (e) {
t.ok(e.message.includes('pointer length is less then or equal to 48. are you using an address?'), 'should error when using an address')
}

const noopProgramOnChain = await run(
'get a specific program',
entropy.programs.dev.get(newPointer)
)
mixmix marked this conversation as resolved.
Show resolved Hide resolved
t.deepEqual(
programsDeployed,
[newPointer],
'charlie has 1 program deployed'
)
frankiebee marked this conversation as resolved.
Show resolved Hide resolved

t.deepEqual(
noopProgramOnChain.bytecode,
noopProgram,
'bytecode on chain should match what was deployed'
)
t.deepEqual(
noopProgramOnChain.configurationSchema,
configSchema,
'configurationSchema on chain should match what was deployed'
)
t.deepEqual(
noopProgramOnChain.auxiliaryDataSchema,
auxDataSchema,
'auxiliaryDataSchema on chain should match what was deployed'
)

run(
'remove noopProgram',
entropy.programs.dev.remove(newPointer)
)

const programsDeployedAfterRemove = await run(
'get deployed programs',
entropy.programs.dev.getByDeployer(entropy.keyring.accounts.programDev.address)
)
// the removal of a program has failed
// the removing of a program is questionable
// functionality to begin with so ive commented this out
// for now but this needs digging
// see issue https://github.com/entropyxyz/sdk/issues/414
// t.equal(
// programsDeployedAfterRemove.length,
// 0,
// 'charlie has no deployed programs'
// )


t.end()
})
Loading
Loading