Skip to content

Commit

Permalink
Convert api-contract usage of api.rpc.* to api.call.* (#5107)
Browse files Browse the repository at this point in the history
* Convert api-contract usage of api.rpc.* to api.call.*

* Cast call (augmentation not available in build)

* Revert tx check assert

* Call mock
  • Loading branch information
jacogr authored Jul 25, 2022
1 parent 6e65bdb commit 0c79e13
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 50 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

Changes:

- Convert `api-contract` usage of `api.rpc.*` to `api.call.*`
- Drop support for contract runtimes without `storageDepositLimit` (runtime `contractsApi` only has unversioned support for latest)
- Export `api.rx.call.*` for internal usage (derive, contracts)


Expand Down
2 changes: 1 addition & 1 deletion packages/api-contract/src/base/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export abstract class Base<ApiType extends ApiTypes> {

if (!api || !api.isConnected || !api.tx) {
throw new Error('Your API has not been initialized correctly and is not connected to a chain');
} else if (!api.tx.contracts || !Object.keys(api.tx.contracts).length) {
} else if (!api.tx.contracts || !Object.keys(api.tx.contracts).length || !api.call.contractsApi) {
throw new Error('You need to connect to a chain with a runtime that supports contracts');
} else if (!isFunction(api.tx.contracts.instantiateWithCode)) {
throw new Error('You need to connect to a chain with a runtime with a V3 contracts module. The runtime does not expose api.tx.contracts.instantiateWithCode');
Expand Down
90 changes: 41 additions & 49 deletions packages/api-contract/src/base/Contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,12 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
});
}

public get hasRpcContractsCall (): boolean {
return isFunction(this.api.rx.rpc.contracts?.call);
public get hasRpcContractsApi (): boolean {
return isFunction(this.api.rx.call.contractsApi?.call);
}

public get query (): MapMessageQuery<ApiType> {
if (!this.hasRpcContractsCall) {
if (!this.hasRpcContractsApi) {
throw new Error(ERROR_NO_CALL);
}

Expand All @@ -112,64 +112,56 @@ export class Contract<ApiType extends ApiTypes> extends Base<ApiType> {
};

#exec = (messageOrId: AbiMessage | string | number, { gasLimit = BN_ZERO, storageDepositLimit = null, value = BN_ZERO }: ContractOptions, params: unknown[]): SubmittableExtrinsic<ApiType> => {
const hasStorageDeposit = this.api.tx.contracts.call.meta.args.length === 5;
const gas = this.#getGas(gasLimit);
const encParams = this.abi.findMessage(messageOrId).toU8a(params);
const tx = hasStorageDeposit
? this.api.tx.contracts.call(this.address, value, gas, storageDepositLimit, encParams)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore old style without storage deposit
: this.api.tx.contracts.call(this.address, value, gas, encParams);

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)
))
);

return this.api.tx.contracts
.call(this.address, value, gas, storageDepositLimit, encParams)
.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, storageDepositLimit = null, value = BN_ZERO }: ContractOptions, params: unknown[]): ContractCallSend<ApiType> => {
if (!this.hasRpcContractsCall) {
if (!this.hasRpcContractsApi) {
throw new Error(ERROR_NO_CALL);
}

const message = this.abi.findMessage(messageOrId);

return {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
send: this._decorateMethod((origin: string | AccountId | Uint8Array) => {
const hasStorageDeposit = this.api.tx.contracts.call.meta.args.length === 5;
const inputData = message.toU8a(params);
const rpc = hasStorageDeposit
? this.api.rx.rpc.contracts.call({ dest: this.address, gasLimit: this.#getGas(gasLimit, true), inputData, origin, storageDepositLimit, value })
: this.api.rx.rpc.contracts.call({ dest: this.address, gasLimit: this.#getGas(gasLimit, true), inputData, origin, value });

const mapFn = ({ debugMessage, gasConsumed, gasRequired, result, storageDeposit }: 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,
storageDeposit
});

return rpc.pipe(map(mapFn));
})
send: this._decorateMethod((origin: string | AccountId | Uint8Array) =>
this.api.rx.call.contractsApi
.call<ContractExecResult>(origin, this.address, value, this.#getGas(gasLimit, true), storageDepositLimit, message.toU8a(params))
.pipe(
map(({ debugMessage, gasConsumed, gasRequired, result, storageDeposit }): 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,
storageDeposit
}))
)
)
};
};
}
Expand Down
7 changes: 7 additions & 0 deletions packages/api-contract/src/base/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ import { TypeRegistry } from '@polkadot/types';
const registry = new TypeRegistry();

export const mockApi = {
call: {
contractsApi: {
call: (): never => {
throw new Error('mock');
}
}
},
isConnected: true,
registry,
tx: {
Expand Down

0 comments on commit 0c79e13

Please sign in to comment.