Skip to content

Commit

Permalink
Add storage deposit support (#4304)
Browse files Browse the repository at this point in the history
* add storage deposit support

* Revert "add storage deposit support"

This reverts commit 10eea63.

* generate new metadata

* make it build

* add storage deposit in RPCs

* rename endowment to value

* support older nodes

* check the right things

* hide storage_deposit until better supported

* add the right defaults

* hardcode storageDepositLimit to null
  • Loading branch information
statictype authored Dec 14, 2021
1 parent 8487558 commit cffab69
Show file tree
Hide file tree
Showing 18 changed files with 3,673 additions and 2,039 deletions.
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

0 comments on commit cffab69

Please sign in to comment.