Skip to content

Commit

Permalink
[api] Support upload_code for vouchers (#1472)
Browse files Browse the repository at this point in the history
  • Loading branch information
osipov-mit authored Jan 22, 2024
1 parent 1ec8804 commit aacbaf7
Show file tree
Hide file tree
Showing 19 changed files with 205 additions and 41 deletions.
1 change: 1 addition & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ test/wasm/examples/

.rollup.cache
.yarnrc.yaml
temp/
3 changes: 3 additions & 0 deletions api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ https://github.com/gear-tech/gear-js/pull/1467
- Support new vouchers according to https://github.com/gear-tech/gear/pull/3606
- Bump polkadot dependencies to `10.11.2`

https://github.com/gear-tech/gear-js/pull/1472
- Support `uploadCode` call for vouchers.

## 0.35.2

_10/31/2023_
Expand Down
14 changes: 13 additions & 1 deletion api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,9 @@ const programs = ['0x1234...', '0x5678...'];
const spenderAddress = '0x...';
const validForOneHour = (60 * 60) / 3; // number of blocks in one hour

const { voucherId, extrinsic } = await api.voucher.issue(spenderAddress, 100 * 10 ** 12, validForOneHour, programs);
const { voucherId, extrinsic } = await api.voucher.issue(spenderAddress, 100 * 10 ** 12, validForOneHour, programs, true);

// To allow the voucher to be used for code uploading, set the last argument of the `.issue` method to true

extrinsic.signAndSend(account, (events) => {
const voucherIssuedEvent = events.events.filter(({event: {method}}) => method === 'VoucherIssued') as VoucherIssued;
Expand Down Expand Up @@ -404,6 +406,16 @@ await voucherTx.signAndSend(account, (events) => {
});
```

#### Upload code with issued voucher
```javascript
const { extrinsic } = await api.code.upload(code);

const tx = api.voucher.call(voucherId, { UploadCode: extrinsic })
await tx.signAndSend(account, (events) => {
console.log(events.toHuman());
});
```

#### Update voucher
The `api.voucher.update` can be used to update the voucher. All parameters in the 3rd argument are optional, but at least one parameter must be specified.
```javascript
Expand Down
7 changes: 3 additions & 4 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@
"test": "jest --runInBand",
"test:only": "jest --testPathPattern",
"build": "rm -rf lib && rollup --config rollup.config.js",
"generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package sample-polkadotjs-typegen/interfaces --input ./src/interfaces --endpoint ./gear.json",
"generate:meta": "ts-node --skip-project node_modules/.bin/polkadot-types-from-chain --package sample-polkadotjs-typegen/interfaces --endpoint ./gear.json --output ./src/interfaces",
"generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package sample-polkadotjs-typegen/interfaces --input ./temp --endpoint ws://127.0.0.1:9944",
"generate:meta": "ts-node --skip-project node_modules/.bin/polkadot-types-from-chain --package sample-polkadotjs-typegen/interfaces --endpoint ws://127.0.0.1:9944 --output ./temp",
"lint": "eslint . --ext .ts --ignore-pattern lib/ --ignore-pattern node_modules/",
"lint:fix": "eslint --fix . --ext .ts --ignore-pattern lib/ --ignore-pattern node_modules/",
"get:metadata": "curl -H \"Content-Type: application/json\" -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9944 > gear.json"
"lint:fix": "eslint --fix . --ext .ts --ignore-pattern lib/ --ignore-pattern node_modules/"
},
"exports": {
"./cjs/package.json": "./cjs/package.json",
Expand Down
8 changes: 8 additions & 0 deletions api/programs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/programs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ members = [
"test-gas",
"test-waitlist",
"test-meta",
"empty"
]

[profile.release]
Expand Down
11 changes: 11 additions & 0 deletions api/programs/empty/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "empty"
version = "0.1.0"
authors = ["Gear Technologies"]
edition = "2021"

[dependencies]
gstd = { git = "https://github.com/gear-tech/gear.git" }

[build-dependencies]
gear-wasm-builder = { git = "https://github.com/gear-tech/gear.git" }
3 changes: 3 additions & 0 deletions api/programs/empty/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
fn main() {
gear_wasm_builder::build();
}
8 changes: 8 additions & 0 deletions api/programs/empty/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#![no_std]

use gstd::msg;

#[no_mangle]
unsafe extern "C" fn init() {
msg::reply("ok", 0).unwrap();
}
10 changes: 3 additions & 7 deletions api/src/Code.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { Bytes, Option } from '@polkadot/types';
import { GearCommonCodeMetadata, GearCoreCodeInstrumentedCode } from './types';
import { HexString } from '@polkadot/util/types';
import { ISubmittableResult } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { u8aToHex } from '@polkadot/util';

import { CodeUploadResult, GearCommonCodeMetadata, GearCoreCodeInstrumentedCode } from './types';
import { generateCodeHash, getIdsFromKeys, validateCodeId } from './utils';
import { CodeDoesNotExistError } from './errors';
import { GearTransaction } from './Transaction';
Expand All @@ -16,15 +14,13 @@ export class GearCode extends GearTransaction {
* @param code
* @returns Code hash
*/
async upload(
code: Buffer | Uint8Array,
): Promise<{ codeHash: HexString; submitted: SubmittableExtrinsic<'promise', ISubmittableResult> }> {
async upload(code: Buffer | Uint8Array): Promise<CodeUploadResult> {
const codeHash = generateCodeHash(code);
await validateCodeId(codeHash, this._api);

const codeBytes = this._api.createType('Bytes', Array.from(code)) as Bytes;
this.extrinsic = this._api.tx.gear.uploadCode(codeBytes);
return { codeHash, submitted: this.extrinsic };
return { codeHash, submitted: this.extrinsic, extrinsic: this.extrinsic };
}

/**
Expand Down
4 changes: 2 additions & 2 deletions api/src/Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class GearMessage extends GearTransaction {
* @param args Message parameters
* @param metaOrHexRegistry (optional) Registry in hex format or ProgramMetadata
* @param typeName payload type (one of the default rust types if metadata or registry don't specified)
* @returns Submitable result
* @returns Submitable extrinsic
*
* _Note that parameter `prepaid` is not supported starting from `1010` runtime version._
* @example
Expand Down Expand Up @@ -231,7 +231,7 @@ export class GearMessage extends GearTransaction {
* @param args Message parameters
* @param metaOrHexRegistry Metadata
* @param typeIndexOrTypeName type index in registry or type name
* @returns Submitted result
* @returns Submittable result
*/

async sendReply(
Expand Down
35 changes: 30 additions & 5 deletions api/src/Voucher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ export class GearVoucher extends GearTransaction {
* ### Issue a new voucher for a `user` to be used to pay for sending messages to `program_id` program.
* @param spender The voucher holder account id.
* @param value The voucher amount.
* @param validity The number of the block until which the voucher is valid.
* @param duration (optional) The number of the block until which the voucher is valid. If not specified, the voucher is valid in `api.voucher.minDuration` blocks.
* @param programs (optional) The list of programs that the voucher can be used for. If not specified, the voucher can be used for any program.
* @param codeUploading (optional) Whether the voucher can be used for uploading code.
* @returns The voucher id and the extrinsic to submit.
*
* @example
Expand All @@ -32,15 +33,22 @@ export class GearVoucher extends GearTransaction {
async issue(
spender: HexString,
value: number | bigint | BalanceOf,
validity: number,
duration?: number,
programs?: HexString[],
codeUploading = false,
): Promise<{ extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>; voucherId: HexString }> {
const nonce = await this._api.query.gearVoucher.issued();

const nextNonce = nonce.unwrapOrDefault().addn(1).toArray('le', 8);
const voucherId = generateVoucherId(nextNonce);

this.extrinsic = this._api.tx.gearVoucher.issue(spender, value, programs || null, validity);
this.extrinsic = this._api.tx.gearVoucher.issue(
spender,
value,
programs || null,
codeUploading,
duration || this.minDuration,
);
return { extrinsic: this.extrinsic, voucherId };
}

Expand Down Expand Up @@ -101,6 +109,16 @@ export class GearVoucher extends GearTransaction {
return this._api.tx.gearVoucher.call(voucherId, {
SendReply: { replyToId, payload, gasLimit, value, keepAlive },
});
} else if ('UploadCode' in params) {
if (params.UploadCode.method.method !== 'uploadCode') {
throw new Error(`Invalid method name. Expected 'UploadCode' but actual is ${params.UploadCode.method.method}`);
}

const [code] = params.UploadCode.args;

return this._api.tx.gearVoucher.call(voucherId, {
UploadCode: { code },
});
}

throw new Error('Invalid call params');
Expand Down Expand Up @@ -173,7 +191,13 @@ export class GearVoucher extends GearTransaction {
voucherId: string,
params: IUpdateVoucherParams,
): SubmittableExtrinsic<'promise', ISubmittableResult> {
if (!params.moveOwnership && !params.balanceTopUp && !params.appendPrograms && !params.prolongValidity) {
if (
!params.moveOwnership &&
!params.balanceTopUp &&
!params.appendPrograms &&
!params.prolongDuration &&
(params.codeUploading === undefined || params.codeUploading === null)
) {
throw new Error('At least one of the parameters must be specified');
}
return this._api.tx.gearVoucher.update(
Expand All @@ -182,7 +206,8 @@ export class GearVoucher extends GearTransaction {
params.moveOwnership || null,
params.balanceTopUp || null,
params.appendPrograms || null,
params.prolongValidity || null,
params.codeUploading || null,
params.prolongDuration || null,
);
}

Expand Down
8 changes: 8 additions & 0 deletions api/src/types/augment/augment-api-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,14 @@ declare module '@polkadot/api-base/types/errors' {
* Error trying transfer balance to/from voucher account.
**/
BalanceTransfer: AugmentedError<ApiType>;
/**
* Voucher is disabled for code uploading, but requested.
**/
CodeUploadingDisabled: AugmentedError<ApiType>;
/**
* Voucher update function tries to cut voucher ability of code upload.
**/
CodeUploadingEnabled: AugmentedError<ApiType>;
/**
* Voucher issue/prolongation duration out of [min; max] constants.
**/
Expand Down
54 changes: 42 additions & 12 deletions api/src/types/augment/augment-api-tx.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import '@polkadot/api-base/types/submittable';

import type { AnyNumber, ITuple } from '@polkadot/types-codec/types';
import type { AnyNumber, Codec, ITuple } from '@polkadot/types-codec/types';
import type { ApiTypes, AugmentedSubmittable } from '@polkadot/api-base/types';
import type { BTreeSet, Bytes, Option, Vec, bool, u128, u32, u64 } from '@polkadot/types-codec';
import {
Expand Down Expand Up @@ -270,7 +270,33 @@ declare module '@polkadot/api-base/types/submittable' {
call: AugmentedSubmittable<
(
voucherId: PalletGearVoucherInternalVoucherId | string | Uint8Array,
call: PalletGearVoucherInternalPrepaidCall | { SendMessage: any } | { SendReply: any } | string | Uint8Array,
call:
| PalletGearVoucherInternalPrepaidCall
| {
SendMessage: {
destination: string | Codec;
payload: string | Uint8Array | Codec;
gasLimit: number | bigint | Uint8Array | Codec;
value: number | bigint | Uint8Array | Codec;
keepAlive: boolean | Uint8Array | Codec;
};
}
| {
SendReply: {
replyToId: string | Codec;
payload: string | Uint8Array | Codec;
gasLimit: number | bigint | Uint8Array | Codec;
value: number | bigint | Uint8Array | Codec;
keepAlive: boolean | Uint8Array | Codec;
};
}
| {
UploadCode: {
code: string | Uint8Array | Codec;
};
}
| string
| Uint8Array,
) => SubmittableExtrinsic<ApiType>,
[PalletGearVoucherInternalVoucherId, PalletGearVoucherInternalPrepaidCall]
>;
Expand All @@ -288,14 +314,15 @@ declare module '@polkadot/api-base/types/submittable' {
spender: AccountId32 | string | Uint8Array,
balance: u128 | AnyNumber | Uint8Array,
programs:
| Option<Vec<GearCoreIdsProgramId>>
| Option<BTreeSet<GearCoreIdsProgramId>>
| null
| Uint8Array
| Vec<GearCoreIdsProgramId>
| (GearCoreIdsProgramId | string | Uint8Array)[],
validity: u32 | AnyNumber | Uint8Array,
| BTreeSet<GearCoreIdsProgramId>
| string[],
codeUploading: bool | boolean | Uint8Array,
duration: u32 | AnyNumber | Uint8Array,
) => SubmittableExtrinsic<ApiType>,
[AccountId32, u128, Option<Vec<GearCoreIdsProgramId>>, u32]
[AccountId32, u128, Option<BTreeSet<GearCoreIdsProgramId>>, bool, u32]
>;
revoke: AugmentedSubmittable<
(
Expand All @@ -311,19 +338,22 @@ declare module '@polkadot/api-base/types/submittable' {
moveOwnership: Option<AccountId32> | null | Uint8Array | AccountId32 | string,
balanceTopUp: Option<u128> | null | Uint8Array | u128 | AnyNumber,
appendPrograms:
| Option<Vec<GearCoreIdsProgramId>>
| Option<Option<BTreeSet<GearCoreIdsProgramId>>>
| null
| Uint8Array
| Vec<GearCoreIdsProgramId>
| (GearCoreIdsProgramId | string | Uint8Array)[],
prolongValidity: Option<u32> | null | Uint8Array | u32 | AnyNumber,
| Option<BTreeSet<GearCoreIdsProgramId>>
| BTreeSet<GearCoreIdsProgramId>
| (string | Uint8Array)[],
codeUploading: Option<bool> | null | Uint8Array | bool | boolean,
prolongDuration: Option<u32> | null | Uint8Array | u32 | AnyNumber,
) => SubmittableExtrinsic<ApiType>,
[
AccountId32,
PalletGearVoucherInternalVoucherId,
Option<AccountId32>,
Option<u128>,
Option<Vec<GearCoreIdsProgramId>>,
Option<Option<BTreeSet<GearCoreIdsProgramId>>>,
Option<bool>,
Option<u32>,
]
>;
Expand Down
19 changes: 19 additions & 0 deletions api/src/types/interfaces/program/code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ISubmittableResult } from '@polkadot/types/types';
import { SubmittableExtrinsic } from '@polkadot/api/types';

import { HexString } from '../../index';

export interface CodeUploadResult {
/**
* Code hash
*/
codeHash: HexString;
/**
* Submittable extrinsic
*/
extrinsic: SubmittableExtrinsic<'promise', ISubmittableResult>;
/**
* @deprecated will be removed in next major version
*/
submitted: SubmittableExtrinsic<'promise', ISubmittableResult>;
}
1 change: 1 addition & 0 deletions api/src/types/interfaces/program/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './pages';
export * from './extrinsic';
export * from './storage';
export * from './state';
export * from './code';
11 changes: 8 additions & 3 deletions api/src/types/interfaces/voucher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { SubmittableExtrinsic } from '@polkadot/api/types';

export type ICallOptions =
| { SendMessage: SubmittableExtrinsic<'promise', ISubmittableResult> }
| { SendReply: SubmittableExtrinsic<'promise', ISubmittableResult> };
| { SendReply: SubmittableExtrinsic<'promise', ISubmittableResult> }
| { UploadCode: SubmittableExtrinsic<'promise', ISubmittableResult> };

export interface IUpdateVoucherParams {
/**
Expand All @@ -21,9 +22,13 @@ export interface IUpdateVoucherParams {
*/
appendPrograms?: string[];
/**
* Prolong the voucher validity.
* Enable or disable code uploading.
*/
prolongValidity?: number;
codeUploading?: boolean;
/**
* Prolong the duration of th voucher validity.
*/
prolongDuration?: number;
}

export interface IVoucherDetails {
Expand Down
Loading

0 comments on commit aacbaf7

Please sign in to comment.