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

Add storage deposit support #4304

Merged
merged 12 commits into from
Dec 14, 2021
1 change: 1 addition & 0 deletions packages/api-contract/src/base/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export abstract class Base<ApiType extends ApiTypes> {
? abi
: new Abi(abi, api.registry.getChainProperties());
this.api = api;

this._decorateMethod = decorateMethod;

assert(!!(api && api.isConnected && api.tx), 'Your API has not been initialized correctly and is not connected to a chain');
Expand Down
17 changes: 9 additions & 8 deletions packages/api-contract/src/base/Blueprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ export class Blueprint<ApiType extends ApiTypes> extends Base<ApiType> {
}

#deploy = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: unknown[]): SubmittableExtrinsic<ApiType, BlueprintSubmittableResult<ApiType>> => {
return this.api.tx.contracts
.instantiate(
value,
gasLimit,
this.codeHash,
this.abi.findConstructor(constructorOrId).toU8a(params),
encodeSalt(salt)
)
const hasStorageDeposit = this.api.tx.contracts.instantiate.meta.args.length === 6;
const storageDepositLimit = null;
const tx = hasStorageDeposit
? this.api.tx.contracts.instantiate(value, gasLimit, storageDepositLimit, this.codeHash, this.abi.findConstructor(constructorOrId).toU8a(params), encodeSalt(salt))
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore old style without storage deposit
: this.api.tx.contracts.instantiate(value, gasLimit, this.codeHash, this.abi.findConstructor(constructorOrId).toU8a(params), encodeSalt(salt));

return tx
.withResultTransform((result: ISubmittableResult) =>
new BlueprintSubmittableResult(result, applyOnEvent(result, ['Instantiated'], ([record]: EventRecord[]) =>
new Contract<ApiType>(this.api, this.abi, record.event.data[1] as AccountId, this._decorateMethod)
Expand Down
38 changes: 19 additions & 19 deletions packages/api-contract/src/base/Code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,25 @@ export class Code<ApiType extends ApiTypes> extends Base<ApiType> {
}

#instantiate = (constructorOrId: AbiConstructor | string | number, { gasLimit = BN_ZERO, salt, value = BN_ZERO }: BlueprintOptions, params: unknown[]): SubmittableExtrinsic<ApiType, CodeSubmittableResult<ApiType>> => {
return this.api.tx.contracts
.instantiateWithCode(
value,
gasLimit,
compactAddLength(this.code),
this.abi.findConstructor(constructorOrId).toU8a(params),
encodeSalt(salt)
)
.withResultTransform((result: ISubmittableResult) =>
new CodeSubmittableResult(result, ...(applyOnEvent(result, ['CodeStored', 'Instantiated'], (records: EventRecord[]) =>
records.reduce<[Blueprint<ApiType>?, Contract<ApiType>?]>(([blueprint, contract], { event }) =>
this.api.events.contracts.Instantiated.is(event)
? [blueprint, new Contract<ApiType>(this.api, this.abi, event.data[1], this._decorateMethod)]
: this.api.events.contracts.CodeStored.is(event)
? [new Blueprint<ApiType>(this.api, this.abi, event.data[0], this._decorateMethod), contract]
: [blueprint, contract],
[])
) || []))
);
const hasStorageDeposit = this.api.tx.contracts.instantiateWithCode.meta.args.length === 6;
const storageDepositLimit = null;
const tx = hasStorageDeposit
? this.api.tx.contracts.instantiateWithCode(value, gasLimit, storageDepositLimit, compactAddLength(this.code), this.abi.findConstructor(constructorOrId).toU8a(params), encodeSalt(salt))
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore old style without storage deposit
: this.api.tx.contracts.instantiateWithCode(value, gasLimit, compactAddLength(this.code), this.abi.findConstructor(constructorOrId).toU8a(params), encodeSalt(salt));

return tx.withResultTransform((result: ISubmittableResult) =>
new CodeSubmittableResult(result, ...(applyOnEvent(result, ['CodeStored', 'Instantiated'], (records: EventRecord[]) =>
records.reduce<[Blueprint<ApiType>?, Contract<ApiType>?]>(([blueprint, contract], { event }) =>
this.api.events.contracts.Instantiated.is(event)
? [blueprint, new Contract<ApiType>(this.api, this.abi, event.data[1], this._decorateMethod)]
: this.api.events.contracts.CodeStored.is(event)
? [new Blueprint<ApiType>(this.api, this.abi, event.data[0], this._decorateMethod), contract]
: [blueprint, contract],
[])
) || []))
);
};
}

Expand Down
95 changes: 47 additions & 48 deletions packages/api-contract/src/base/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { SubmittableExtrinsic } from '@polkadot/api/submittable/types';
import type { ApiTypes, DecorateMethod } from '@polkadot/api/types';
import type { Bytes } from '@polkadot/types';
import type { AccountId, EventRecord, Weight } from '@polkadot/types/interfaces';
import type { AccountId, ContractExecResult, EventRecord, Weight } from '@polkadot/types/interfaces';
import type { ISubmittableResult } from '@polkadot/types/types';
import type { AbiMessage, ContractCallOutcome, ContractOptions, DecodedEvent } from '../types';
import type { ContractCallResult, ContractCallSend, ContractQuery, ContractTx, MapMessageQuery, MapMessageTx } from './types';
Expand Down Expand Up @@ -33,14 +33,14 @@ function createQuery <ApiType extends ApiTypes> (fn: (origin: string | AccountId
return (origin: string | AccountId | Uint8Array, options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): ContractCallResult<ApiType, ContractCallOutcome> =>
isOptions(options)
? fn(origin, options, params)
: fn(origin, ...extractOptions(options, params));
: fn(origin, ...extractOptions<ContractOptions>(options, params));
}

function createTx <ApiType extends ApiTypes> (fn: (options: ContractOptions, params: unknown[]) => SubmittableExtrinsic<ApiType>): ContractTx<ApiType> {
return (options: bigint | string | number | BN | ContractOptions, ...params: unknown[]): SubmittableExtrinsic<ApiType> =>
isOptions(options)
? fn(options, params)
: fn(...extractOptions(options, params));
: fn(...extractOptions<ContractOptions>(options, params));
}

export class ContractSubmittableResult extends SubmittableResult {
Expand Down Expand Up @@ -107,29 +107,30 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
};

#exec = (messageOrId: AbiMessage | string | number, { gasLimit = BN_ZERO, value = BN_ZERO }: ContractOptions, params: unknown[]): SubmittableExtrinsic<ApiType> => {
return this.api.tx.contracts
.call(
this.address,
value,
this.#getGas(gasLimit),
this.abi.findMessage(messageOrId).toU8a(params)
)
.withResultTransform((result: ISubmittableResult) =>
// ContractEmitted is the current generation, ContractExecution is the previous generation
new ContractSubmittableResult(result, applyOnEvent(result, ['ContractEmitted', 'ContractExecution'], (records: EventRecord[]) =>
records
.map(({ event: { data: [, data] } }): DecodedEvent | null => {
try {
return this.abi.decodeEvent(data as Bytes);
} catch (error) {
l.error(`Unable to decode contract event: ${(error as Error).message}`);

return null;
}
})
.filter((decoded): decoded is DecodedEvent => !!decoded)
))
);
const hasStorageDeposit = this.api.tx.contracts.call.meta.args.length === 5;
const storageDepositLimit = null;
const tx = hasStorageDeposit
? this.api.tx.contracts.call(this.address, value, this.#getGas(gasLimit), storageDepositLimit, this.abi.findMessage(messageOrId).toU8a(params))
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore old style without storage deposit
: this.api.tx.contracts.call(this.address, value, this.#getGas(gasLimit), this.abi.findMessage(messageOrId).toU8a(params));

return tx.withResultTransform((result: ISubmittableResult) =>
// ContractEmitted is the current generation, ContractExecution is the previous generation
new ContractSubmittableResult(result, applyOnEvent(result, ['ContractEmitted', 'ContractExecution'], (records: EventRecord[]) =>
records
.map(({ event: { data: [, data] } }): DecodedEvent | null => {
try {
return this.abi.decodeEvent(data as Bytes);
} catch (error) {
l.error(`Unable to decode contract event: ${(error as Error).message}`);

return null;
}
})
.filter((decoded): decoded is DecodedEvent => !!decoded)
))
);
};

#read = (messageOrId: AbiMessage | string | number, { gasLimit = BN_ZERO, value = BN_ZERO }: ContractOptions, params: unknown[]): ContractCallSend<ApiType> => {
Expand All @@ -139,28 +140,26 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {

return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((origin: string | AccountId | Uint8Array) =>
this.api.rx.rpc.contracts
.call({
dest: this.address,
gasLimit: this.#getGas(gasLimit, true),
inputData: message.toU8a(params),
origin,
value
})
.pipe(
map(({ debugMessage, gasConsumed, gasRequired, result }): ContractCallOutcome => ({
debugMessage,
gasConsumed,
gasRequired: gasRequired && !gasRequired.isZero()
? gasRequired
: gasConsumed,
output: result.isOk && message.returnType
? this.abi.registry.createTypeUnsafe(message.returnType.lookupName || message.returnType.type, [result.asOk.data.toU8a(true)], { isPedantic: true })
: null,
result
}))
)
send: this._decorateMethod((origin: string | AccountId | Uint8Array) => {
const hasStorageDeposit = this.api.tx.contracts.call.meta.args.length === 5;
const rpc = hasStorageDeposit
? this.api.rx.rpc.contracts.call({ dest: this.address, gasLimit: this.#getGas(gasLimit, true), inputData: message.toU8a(params), origin, value })
: this.api.rx.rpc.contracts.call({ dest: this.address, gasLimit: this.#getGas(gasLimit, true), inputData: message.toU8a(params), origin, value });

const mapFn = ({ debugMessage, gasConsumed, gasRequired, result }: ContractExecResult): ContractCallOutcome => ({
debugMessage,
gasConsumed,
gasRequired: gasRequired && !gasRequired.isZero()
? gasRequired
: gasConsumed,
output: result.isOk && message.returnType
? this.abi.registry.createTypeUnsafe(message.returnType.lookupName || message.returnType.type, [result.asOk.data.toU8a(true)], { isPedantic: true })
: null,
result
});

return rpc.pipe(map(mapFn));
}
)
};
};
Expand Down
4 changes: 2 additions & 2 deletions packages/api-contract/src/base/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ export function createBluePrintTx <ApiType extends ApiTypes, R extends Submittab
return (options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): SubmittableExtrinsic<ApiType, R> =>
isOptions(options)
? fn(options, params)
: fn(...extractOptions(options, params));
: fn(...extractOptions<BlueprintOptions>(options, params));
}

export function createBluePrintWithId <T> (fn: (constructorOrId: AbiConstructor | string | number, options: BlueprintOptions, params: unknown[]) => T): ContractGeneric<BlueprintOptions, T> {
return (constructorOrId: AbiConstructor | string | number, options: bigint | string | number | BN | BlueprintOptions, ...params: unknown[]): T =>
isOptions(options)
? fn(constructorOrId, options, params)
: fn(constructorOrId, ...extractOptions(options, params));
: fn(constructorOrId, ...extractOptions<BlueprintOptions>(options, params));
}

export function encodeSalt (salt: Uint8Array | string | null = randomAsU8a()): Uint8Array {
Expand Down
39 changes: 33 additions & 6 deletions packages/api/src/augment/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,14 +180,26 @@ declare module '@polkadot/api/types/consts' {
**/
[key: string]: Codec;
};
contracts: {
childBounties: {
/**
* Percentage of child-bounty value to be reserved as curator deposit
* when curator fee is zero.
**/
childBountyCuratorDepositBase: Permill & AugmentedConst<ApiType>;
/**
* Minimum value for a child-bounty.
**/
childBountyValueMinimum: u128 & AugmentedConst<ApiType>;
/**
* The deposit that must be placed into the contract's account to instantiate it.
* This is in **addition** to the [`Currency::minimum_balance`].
* The minimum balance for a contract's account can be queried using
* [`Pallet::subsistence_threshold`].
* Maximum number of child-bounties that can be added to a parent bounty.
**/
contractDeposit: u128 & AugmentedConst<ApiType>;
maxActiveChildBountyCount: u32 & AugmentedConst<ApiType>;
/**
* Generic const
**/
[key: string]: Codec;
};
contracts: {
/**
* The maximum number of tries that can be queued for deletion.
**/
Expand All @@ -196,6 +208,21 @@ declare module '@polkadot/api/types/consts' {
* The maximum amount of weight that can be consumed per block for lazy trie removal.
**/
deletionWeightLimit: u64 & AugmentedConst<ApiType>;
/**
* The amount of balance a caller has to pay for each byte of storage.
*
* # Note
*
* Changing this value for an existing chain might need a storage migration.
**/
depositPerByte: u128 & AugmentedConst<ApiType>;
/**
* The amount of balance a caller has to pay for each storage item.
* # Note
*
* Changing this value for an existing chain might need a storage migration.
**/
depositPerItem: u128 & AugmentedConst<ApiType>;
/**
* Cost schedule and limits.
**/
Expand Down
67 changes: 50 additions & 17 deletions packages/api/src/augment/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,24 @@ declare module '@polkadot/api/types/errors' {
**/
[key: string]: AugmentedError<ApiType>;
};
bagsList: {
/**
* Id not found in list.
**/
IdNotFound: AugmentedError<ApiType>;
/**
* An Id does not have a greater vote weight than another Id.
**/
NotHeavier: AugmentedError<ApiType>;
/**
* Attempted to place node in front of a node in another bag.
**/
NotInSameBag: AugmentedError<ApiType>;
/**
* Generic error
**/
[key: string]: AugmentedError<ApiType>;
};
balances: {
/**
* Beneficiary account must pre-exist
Expand Down Expand Up @@ -151,6 +169,10 @@ declare module '@polkadot/api/types/errors' {
[key: string]: AugmentedError<ApiType>;
};
bounties: {
/**
* The bounty cannot be closed because it has active child-bounties.
**/
HasActiveChildBounty: AugmentedError<ApiType>;
/**
* Proposer's balance is too low.
**/
Expand Down Expand Up @@ -193,13 +215,29 @@ declare module '@polkadot/api/types/errors' {
**/
[key: string]: AugmentedError<ApiType>;
};
childBounties: {
/**
* The bounty balance is not enough to add new child-bounty.
**/
InsufficientBountyBalance: AugmentedError<ApiType>;
/**
* The parent bounty is not in active state.
**/
ParentBountyNotActive: AugmentedError<ApiType>;
/**
* Number of child-bounties exceeds limit `MaxActiveChildBountyCount`.
**/
TooManyChildBounties: AugmentedError<ApiType>;
/**
* Generic error
**/
[key: string]: AugmentedError<ApiType>;
};
contracts: {
/**
* Performing the requested transfer would have brought the contract below
* the subsistence threshold. No transfer is allowed to do this. Use `seal_terminate`
* to recover a deposit.
* Code removal was denied because the code is still in use by at least one contract.
**/
BelowSubsistenceThreshold: AugmentedError<ApiType>;
CodeInUse: AugmentedError<ApiType>;
/**
* No code could be found at the supplied code hash.
**/
Expand Down Expand Up @@ -254,11 +292,6 @@ declare module '@polkadot/api/types/errors' {
* of what is specified in the schedule.
**/
MaxCallDepthReached: AugmentedError<ApiType>;
/**
* The newly created contract is below the subsistence threshold after executing
* its contructor. No contracts are allowed to exist below that threshold.
**/
NewContractNotFunded: AugmentedError<ApiType>;
/**
* The chain does not provide a chain extension. Calling the chain extension results
* in this error. Note that this usually shouldn't happen as deploying such contracts
Expand Down Expand Up @@ -286,12 +319,13 @@ declare module '@polkadot/api/types/errors' {
**/
ReentranceDenied: AugmentedError<ApiType>;
/**
* A storage modification exhausted the 32bit type that holds the storage size.
*
* This can either happen when the accumulated storage in bytes is too large or
* when number of storage items is too large.
* More storage was created than allowed by the storage deposit limit.
**/
StorageDepositLimitExhausted: AugmentedError<ApiType>;
/**
* Origin doesn't have enough balance to pay the required storage deposits.
**/
StorageExhausted: AugmentedError<ApiType>;
StorageDepositNotEnoughFunds: AugmentedError<ApiType>;
/**
* A contract self destructed in its constructor.
*
Expand All @@ -308,9 +342,8 @@ declare module '@polkadot/api/types/errors' {
**/
TooManyTopics: AugmentedError<ApiType>;
/**
* Performing the requested transfer failed for a reason originating in the
* chosen currency implementation of the runtime. Most probably the balance is
* too low or locks are placed on it.
* Performing the requested transfer failed. Probably because there isn't enough
* free balance in the sender's account.
**/
TransferFailed: AugmentedError<ApiType>;
/**
Expand Down
Loading