@dedot/contracts 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cjs/errors.js CHANGED
@@ -3,6 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isContractInstantiateLangError = exports.isContractInstantiateDispatchError = exports.isContractInstantiateError = exports.isContractLangError = exports.isContractDispatchError = exports.isContractExecutionError = exports.ContractLangError = exports.ContractDispatchError = exports.ContractExecutionError = exports.ContractInstantiateLangError = exports.ContractInstantiateDispatchError = exports.ContractInstantiateError = void 0;
4
4
  const utils_1 = require("@dedot/utils");
5
5
  const index_js_1 = require("./utils/index.js");
6
+ const formatDispatchError = (err, moduleError) => {
7
+ if (moduleError) {
8
+ const { pallet, name, docs } = moduleError;
9
+ let message = `Dispatch error: ${pallet}::${name}`;
10
+ if (docs) {
11
+ message += ` - ${docs.join('\n ')}`;
12
+ }
13
+ return message;
14
+ }
15
+ else {
16
+ return `Dispatch error: ${JSON.stringify(err)}`;
17
+ }
18
+ };
6
19
  /**
7
20
  * Represents an error that occurred during the instantiation of a smart contract.
8
21
  * This class extends the base `DedotError` and includes a `raw` property of type `ContractInstantiateResult`.
@@ -42,16 +55,22 @@ class ContractInstantiateDispatchError extends ContractInstantiateError {
42
55
  * The error that occurred during the dispatch phase.
43
56
  */
44
57
  dispatchError;
58
+ /**
59
+ * The decoded module error, if any.
60
+ */
61
+ moduleError;
45
62
  /**
46
63
  * Constructs a new `ContractInstantiateDispatchError` instance.
47
64
  *
48
65
  * @param err - The `DispatchError` that occurred during the dispatch phase.
49
66
  * @param raw - The raw result of the contract instantiation.
67
+ * @param moduleError - The decoded module error, if any.
50
68
  */
51
- constructor(err, raw) {
69
+ constructor(err, raw, moduleError) {
52
70
  super(raw);
53
71
  this.dispatchError = err;
54
- this.message = `Dispatch error: ${JSON.stringify(err)}`;
72
+ this.moduleError = moduleError;
73
+ this.message = formatDispatchError(err, moduleError);
55
74
  }
56
75
  }
57
76
  exports.ContractInstantiateDispatchError = ContractInstantiateDispatchError;
@@ -86,7 +105,7 @@ class ContractInstantiateLangError extends ContractInstantiateError {
86
105
  super(raw);
87
106
  this.langError = err;
88
107
  this.flags = (0, index_js_1.toReturnFlags)(raw.result.value.result.flags.bits);
89
- this.message = `Lange error: ${JSON.stringify(err)}`;
108
+ this.message = `Lang error: ${JSON.stringify(err)}`;
90
109
  }
91
110
  }
92
111
  exports.ContractInstantiateLangError = ContractInstantiateLangError;
@@ -129,16 +148,22 @@ class ContractDispatchError extends ContractExecutionError {
129
148
  * The error that occurred during the dispatch phase.
130
149
  */
131
150
  dispatchError;
151
+ /**
152
+ * The decoded module error, if any.
153
+ */
154
+ moduleError;
132
155
  /**
133
156
  * Constructs a new `ContractDispatchError` instance.
134
157
  *
135
158
  * @param err - The `DispatchError` that occurred during the dispatch phase.
136
159
  * @param raw - The raw result of the contract call.
160
+ * @param moduleError - The decoded module error, if any.
137
161
  */
138
- constructor(err, raw) {
162
+ constructor(err, raw, moduleError) {
139
163
  super(raw);
140
164
  this.dispatchError = err;
141
- this.message = `Dispatch error: ${JSON.stringify(err)}`;
165
+ this.moduleError = moduleError;
166
+ this.message = formatDispatchError(err, moduleError);
142
167
  }
143
168
  }
144
169
  exports.ContractDispatchError = ContractDispatchError;
@@ -173,7 +198,7 @@ class ContractLangError extends ContractExecutionError {
173
198
  super(raw);
174
199
  this.langError = err;
175
200
  this.flags = (0, index_js_1.toReturnFlags)(raw.result.value.flags.bits);
176
- this.message = `Lange error: ${JSON.stringify(err)}`;
201
+ this.message = `Lang error: ${JSON.stringify(err)}`;
177
202
  }
178
203
  }
179
204
  exports.ContractLangError = ContractLangError;
@@ -11,8 +11,7 @@ class ConstructorQueryExecutor extends index_js_2.DeployerExecutor {
11
11
  (0, utils_1.assert)(meta, `Constructor message not found: ${constructor}`);
12
12
  const callFn = async (...params) => {
13
13
  const { args } = meta;
14
- (0, utils_1.assertFalse)(params.length < args.length, `Expected at least ${args.length} arguments, got ${params.length}`);
15
- (0, utils_1.assertFalse)(params.length > args.length + 1, `Expected at most ${args.length + 1} arguments, got ${params.length}`);
14
+ (0, index_js_1.ensureParamsLength)(args.length, params.length);
16
15
  const callOptions = (params[args.length] || {});
17
16
  const { caller = this.options.defaultCaller, // --
18
17
  value = 0n, gasLimit, storageDepositLimit, salt, } = callOptions;
@@ -64,7 +63,9 @@ class ConstructorQueryExecutor extends index_js_2.DeployerExecutor {
64
63
  }
65
64
  })();
66
65
  if (raw.result.isErr) {
67
- throw new errors_js_1.ContractInstantiateDispatchError(raw.result.err, raw);
66
+ const dispatchError = raw.result.err;
67
+ const moduleError = client.registry.findErrorMeta(dispatchError);
68
+ throw new errors_js_1.ContractInstantiateDispatchError(dispatchError, raw, moduleError);
68
69
  }
69
70
  const data = this.tryDecode(meta, raw.result.value.result.data);
70
71
  if (data.isErr) {
@@ -2,38 +2,128 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ConstructorTxExecutor = void 0;
4
4
  const utils_1 = require("@dedot/utils");
5
- const index_js_1 = require("./abstract/index.js");
6
- class ConstructorTxExecutor extends index_js_1.DeployerExecutor {
5
+ const Contract_js_1 = require("../Contract.js");
6
+ const index_js_1 = require("../utils/index.js");
7
+ const ConstructorQueryExecutor_js_1 = require("./ConstructorQueryExecutor.js");
8
+ const index_js_2 = require("./abstract/index.js");
9
+ class ConstructorTxExecutor extends index_js_2.DeployerExecutor {
7
10
  doExecute(constructor) {
8
11
  const meta = this.findConstructorMeta(constructor);
9
12
  (0, utils_1.assert)(meta, `Constructor message not found: ${constructor}`);
13
+ // @ts-ignore
10
14
  const callFn = (...params) => {
11
15
  const { args } = meta;
12
- (0, utils_1.assert)(params.length === args.length + 1, `Expected ${args.length + 1} arguments, got ${params.length}`);
13
- const txCallOptions = params[args.length];
16
+ (0, index_js_1.ensureParamsLength)(args.length, params.length);
17
+ const txCallOptions = (params[args.length] || {});
14
18
  const { value = 0n, gasLimit, storageDepositLimit, salt } = txCallOptions;
15
- (0, utils_1.assert)(gasLimit, 'Expected a gas limit in ConstructorTxOptions');
16
19
  const formattedInputs = args.map((arg, index) => this.tryEncode(arg, params[index]));
17
20
  const bytes = (0, utils_1.u8aToHex)((0, utils_1.concatU8a)((0, utils_1.hexToU8a)(meta.selector), ...formattedInputs));
18
21
  const client = this.client;
19
- if (this.registry.isRevive()) {
20
- (0, utils_1.assert)((0, utils_1.isUndefined)(salt) || (0, utils_1.toU8a)(salt).byteLength == 32, 'Invalid salt provided in ConstructorCallOptions: expected a 32-byte value as a hex string or a Uint8Array');
21
- (0, utils_1.assert)(!(0, utils_1.isUndefined)(storageDepositLimit), 'Expected a storage deposit limit in ConstructorTxOptions');
22
- if ((0, utils_1.isPvm)(this.code)) {
23
- return client.tx.revive.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt ? (0, utils_1.toHex)(salt) : undefined);
22
+ const tx = (() => {
23
+ if (this.registry.isRevive()) {
24
+ (0, utils_1.assert)((0, utils_1.isUndefined)(salt) || (0, utils_1.toU8a)(salt).byteLength == 32, 'Invalid salt provided in ConstructorCallOptions: expected a 32-byte value as a hex string or a Uint8Array');
25
+ if ((0, utils_1.isPvm)(this.code)) {
26
+ return client.tx.revive.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt ? (0, utils_1.toHex)(salt) : undefined);
27
+ }
28
+ else {
29
+ return client.tx.revive.instantiate(value, gasLimit, storageDepositLimit, (0, utils_1.toHex)(this.code), bytes, salt ? (0, utils_1.toHex)(salt) : undefined);
30
+ }
24
31
  }
25
32
  else {
26
- return client.tx.revive.instantiate(value, gasLimit, storageDepositLimit, (0, utils_1.toHex)(this.code), bytes, salt ? (0, utils_1.toHex)(salt) : undefined);
33
+ if ((0, utils_1.isWasm)(this.code)) {
34
+ return client.tx.contracts.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt || '0x');
35
+ }
36
+ else {
37
+ return client.tx.contracts.instantiate(value, gasLimit, storageDepositLimit, (0, utils_1.toHex)(this.code), bytes, salt || '0x');
38
+ }
27
39
  }
28
- }
29
- else {
30
- if ((0, utils_1.isWasm)(this.code)) {
31
- return client.tx.contracts.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt || '0x');
40
+ })();
41
+ let deployerAddress, deployerNonce;
42
+ const calculateContractAddress = async (result) => {
43
+ (0, utils_1.assert)(deployerAddress, 'Deployer Address Not Found');
44
+ const { status, events, dispatchError } = result;
45
+ const onChain = status.type === 'BestChainBlockIncluded' || status.type === 'Finalized';
46
+ (0, utils_1.assert)(onChain, 'The deployment transaction has not yet been included in the best chain block or finalized.');
47
+ (0, utils_1.assert)(!dispatchError, 'The deployment transaction failed to execute. Refer to the dispatch error for more information.');
48
+ if (this.registry.isRevive()) {
49
+ if (salt) {
50
+ let code;
51
+ if ((0, utils_1.isPvm)(this.code)) {
52
+ code = this.code;
53
+ }
54
+ else {
55
+ // pull the raw code in case we're using a code hash here
56
+ code = await client.query.revive.pristineCode((0, utils_1.toHex)(this.code));
57
+ }
58
+ (0, utils_1.assert)(code, 'Contract Code Binary Not Found');
59
+ return (0, index_js_1.CREATE2)((0, index_js_1.toEvmAddress)(deployerAddress), // --
60
+ code, bytes, salt);
61
+ }
62
+ else {
63
+ return (0, index_js_1.CREATE1)((0, index_js_1.toEvmAddress)(deployerAddress), // --
64
+ deployerNonce);
65
+ }
32
66
  }
33
67
  else {
34
- return client.tx.contracts.instantiate(value, gasLimit, storageDepositLimit, (0, utils_1.toHex)(this.code), bytes, salt || '0x');
68
+ const event = client.events.contracts.Instantiated.find(events);
69
+ (0, utils_1.assert)(event, 'Contracts.Instantiated event not found');
70
+ return event.palletEvent.data.contract.address();
35
71
  }
36
- }
72
+ };
73
+ tx.withHooks({
74
+ beforeSign: async (tx, signerAddress) => {
75
+ deployerAddress = signerAddress;
76
+ if (this.registry.isRevive() && !salt) {
77
+ deployerNonce = await client.call.accountNonceApi.accountNonce(deployerAddress);
78
+ }
79
+ const callParams = { ...tx.call.palletCall.params };
80
+ const needsDryRun = !callParams.gasLimit || (this.registry.isRevive() && !callParams.storageDepositLimit);
81
+ if (!needsDryRun)
82
+ return;
83
+ const executor = new ConstructorQueryExecutor_js_1.ConstructorQueryExecutor(this.client, // --
84
+ this.registry, this.code, {
85
+ defaultCaller: signerAddress,
86
+ ...this.options,
87
+ });
88
+ const { raw } = await executor.doExecute(constructor)(...params);
89
+ const { gasRequired, storageDeposit } = raw;
90
+ if (!callParams.gasLimit) {
91
+ callParams.gasLimit = gasRequired;
92
+ }
93
+ if (this.registry.isRevive() && !callParams.storageDepositLimit) {
94
+ callParams.storageDepositLimit = storageDeposit.value;
95
+ }
96
+ const newCall = { ...tx.call };
97
+ newCall.palletCall.params = callParams;
98
+ tx.call = newCall;
99
+ },
100
+ transformResult: (result) => {
101
+ let cachedAddress;
102
+ const contractAddress = async () => {
103
+ if (cachedAddress)
104
+ return cachedAddress;
105
+ cachedAddress = await calculateContractAddress(result);
106
+ return cachedAddress;
107
+ };
108
+ const contract = async (overrideOptions) => {
109
+ const address = await contractAddress();
110
+ // Check if the contract is existed on chain or not!
111
+ await (0, index_js_1.ensureContractPresence)(client, this.registry, address);
112
+ return new Contract_js_1.Contract(client, // --
113
+ this.metadata, address, {
114
+ defaultCaller: deployerAddress,
115
+ ...this.options,
116
+ ...overrideOptions,
117
+ });
118
+ };
119
+ Object.assign(result, {
120
+ contractAddress,
121
+ contract,
122
+ });
123
+ return result;
124
+ },
125
+ });
126
+ return tx;
37
127
  };
38
128
  callFn.meta = meta;
39
129
  return callFn;
@@ -11,8 +11,7 @@ class QueryExecutor extends index_js_2.ContractExecutor {
11
11
  (0, utils_1.assert)(meta, `Query message not found: ${message}`);
12
12
  const callFn = async (...params) => {
13
13
  const { args } = meta;
14
- (0, utils_1.assertFalse)(params.length < args.length, `Expected at least ${args.length} arguments, got ${params.length}`);
15
- (0, utils_1.assertFalse)(params.length > args.length + 1, `Expected at most ${args.length + 1} arguments, got ${params.length}`);
14
+ (0, index_js_1.ensureParamsLength)(args.length, params.length);
16
15
  const callOptions = (params[args.length] || {});
17
16
  const { caller = this.options.defaultCaller, value = 0n, gasLimit, storageDepositLimit } = callOptions;
18
17
  (0, utils_1.assert)(caller, 'Expected a valid caller address in ContractCallOptions');
@@ -44,7 +43,9 @@ class QueryExecutor extends index_js_2.ContractExecutor {
44
43
  }
45
44
  })();
46
45
  if (raw.result.isErr) {
47
- throw new errors_js_1.ContractDispatchError(raw.result.err, raw);
46
+ const dispatchError = raw.result.err;
47
+ const moduleError = client.registry.findErrorMeta(dispatchError);
48
+ throw new errors_js_1.ContractDispatchError(dispatchError, raw, moduleError);
48
49
  }
49
50
  const data = this.tryDecode(meta, raw.result.value.data);
50
51
  if (data.isErr) {
@@ -2,29 +2,56 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TxExecutor = void 0;
4
4
  const utils_1 = require("@dedot/utils");
5
- const index_js_1 = require("./abstract/index.js");
6
- class TxExecutor extends index_js_1.ContractExecutor {
5
+ const index_js_1 = require("../utils/index.js");
6
+ const QueryExecutor_js_1 = require("./QueryExecutor.js");
7
+ const index_js_2 = require("./abstract/index.js");
8
+ class TxExecutor extends index_js_2.ContractExecutor {
7
9
  doExecute(message) {
8
10
  const meta = this.findTxMessage(message);
9
11
  (0, utils_1.assert)(meta, `Tx message not found: ${message}`);
10
12
  const callFn = (...params) => {
11
13
  const { args } = meta;
12
- (0, utils_1.assert)(params.length === args.length + 1, `Expected ${args.length + 1} arguments, got ${params.length}`);
13
- const txCallOptions = params[args.length];
14
+ (0, index_js_1.ensureParamsLength)(args.length, params.length);
15
+ const txCallOptions = (params[args.length] || {});
14
16
  const { value = 0n, gasLimit, storageDepositLimit } = txCallOptions;
15
- (0, utils_1.assert)(gasLimit, 'Expected a gas limit in ContractTxOptions');
16
17
  const formattedInputs = args.map((arg, index) => this.tryEncode(arg, params[index]));
17
18
  const bytes = (0, utils_1.u8aToHex)((0, utils_1.concatU8a)((0, utils_1.hexToU8a)(meta.selector), ...formattedInputs));
18
19
  const client = this.client;
19
- if (this.registry.isRevive()) {
20
- (0, utils_1.assert)(!(0, utils_1.isUndefined)(storageDepositLimit), 'Expected a storage deposit limit in ContractTxOptions');
21
- return client.tx.revive.call(this.address, // --
22
- value, gasLimit, storageDepositLimit || 0n, bytes);
23
- }
24
- else {
25
- return client.tx.contracts.call(this.address, // --
26
- value, gasLimit, storageDepositLimit, bytes);
27
- }
20
+ const tx = (() => {
21
+ if (this.registry.isRevive()) {
22
+ return client.tx.revive.call(this.address, // --
23
+ value, gasLimit, storageDepositLimit || 0n, bytes);
24
+ }
25
+ else {
26
+ return client.tx.contracts.call(this.address, // --
27
+ value, gasLimit, storageDepositLimit, bytes);
28
+ }
29
+ })();
30
+ tx.withHooks({
31
+ beforeSign: async (tx, signerAddress) => {
32
+ const callParams = { ...tx.call.palletCall.params };
33
+ const needsDryRun = !callParams.gasLimit || (this.registry.isRevive() && !callParams.storageDepositLimit);
34
+ if (!needsDryRun)
35
+ return;
36
+ const executor = new QueryExecutor_js_1.QueryExecutor(this.client, // --
37
+ this.registry, this.address, {
38
+ defaultCaller: signerAddress,
39
+ ...this.options,
40
+ });
41
+ const { raw } = await executor.doExecute(message)(...params);
42
+ const { gasRequired, storageDeposit } = raw;
43
+ if (!callParams.gasLimit) {
44
+ callParams.gasLimit = gasRequired;
45
+ }
46
+ if (this.registry.isRevive() && !callParams.storageDepositLimit) {
47
+ callParams.storageDepositLimit = storageDeposit.value;
48
+ }
49
+ const newCall = { ...tx.call };
50
+ newCall.palletCall.params = callParams;
51
+ tx.call = newCall;
52
+ },
53
+ });
54
+ return tx;
28
55
  };
29
56
  callFn.meta = meta;
30
57
  return callFn;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ensureSupportedContractMetadataVersion = exports.ensureValidCodeHashOrCode = exports.ensureValidContractAddress = exports.ensureContractPresence = exports.ensurePalletPresence = exports.ensureStorageApiSupports = void 0;
3
+ exports.ensureParamsLength = exports.ensureSupportedContractMetadataVersion = exports.ensureValidCodeHashOrCode = exports.ensureValidContractAddress = exports.ensureContractPresence = exports.ensurePalletPresence = exports.ensureStorageApiSupports = void 0;
4
4
  const codecs_1 = require("@dedot/codecs");
5
5
  const utils_1 = require("@dedot/utils");
6
6
  const ensureStorageApiSupports = (version) => {
@@ -66,3 +66,9 @@ function ensureSupportedContractMetadataVersion(metadata) {
66
66
  }
67
67
  }
68
68
  exports.ensureSupportedContractMetadataVersion = ensureSupportedContractMetadataVersion;
69
+ function ensureParamsLength(expectedArgsLength, actualParamsLength) {
70
+ (0, utils_1.assertFalse)(actualParamsLength < expectedArgsLength, `Expected at least ${expectedArgsLength} arguments, got ${actualParamsLength}`);
71
+ // One extra param for the options
72
+ (0, utils_1.assertFalse)(actualParamsLength > expectedArgsLength + 1, `Expected at most ${expectedArgsLength + 1} arguments, got ${actualParamsLength}`);
73
+ }
74
+ exports.ensureParamsLength = ensureParamsLength;
package/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DispatchError } from '@dedot/codecs';
1
+ import { DispatchError, PalletErrorMetadataLatest } from '@dedot/codecs';
2
2
  import { DedotError } from '@dedot/utils';
3
3
  import { ContractCallResult, ContractInstantiateResult, GenericContractApi, ReturnFlags } from './types/index.js';
4
4
  /**
@@ -36,13 +36,18 @@ export declare class ContractInstantiateDispatchError<ContractApi extends Generi
36
36
  * The error that occurred during the dispatch phase.
37
37
  */
38
38
  dispatchError: DispatchError;
39
+ /**
40
+ * The decoded module error, if any.
41
+ */
42
+ moduleError?: PalletErrorMetadataLatest;
39
43
  /**
40
44
  * Constructs a new `ContractInstantiateDispatchError` instance.
41
45
  *
42
46
  * @param err - The `DispatchError` that occurred during the dispatch phase.
43
47
  * @param raw - The raw result of the contract instantiation.
48
+ * @param moduleError - The decoded module error, if any.
44
49
  */
45
- constructor(err: DispatchError, raw: ContractInstantiateResult<ContractApi['types']['ChainApi']>);
50
+ constructor(err: DispatchError, raw: ContractInstantiateResult<ContractApi['types']['ChainApi']>, moduleError?: PalletErrorMetadataLatest);
46
51
  }
47
52
  /**
48
53
  * Represents an error that occurred during the instantiation of a smart contract due to a language-specific error.
@@ -107,13 +112,18 @@ export declare class ContractDispatchError<ContractApi extends GenericContractAp
107
112
  * The error that occurred during the dispatch phase.
108
113
  */
109
114
  dispatchError: DispatchError;
115
+ /**
116
+ * The decoded module error, if any.
117
+ */
118
+ moduleError?: PalletErrorMetadataLatest;
110
119
  /**
111
120
  * Constructs a new `ContractDispatchError` instance.
112
121
  *
113
122
  * @param err - The `DispatchError` that occurred during the dispatch phase.
114
123
  * @param raw - The raw result of the contract call.
124
+ * @param moduleError - The decoded module error, if any.
115
125
  */
116
- constructor(err: DispatchError, raw: ContractCallResult<ContractApi['types']['ChainApi']>);
126
+ constructor(err: DispatchError, raw: ContractCallResult<ContractApi['types']['ChainApi']>, moduleError?: PalletErrorMetadataLatest);
117
127
  }
118
128
  /**
119
129
  * Represents an error that occurred during the execution of a smart contract call due to a language-specific error.
package/errors.js CHANGED
@@ -1,5 +1,18 @@
1
1
  import { assert, DedotError } from '@dedot/utils';
2
2
  import { toReturnFlags } from './utils/index.js';
3
+ const formatDispatchError = (err, moduleError) => {
4
+ if (moduleError) {
5
+ const { pallet, name, docs } = moduleError;
6
+ let message = `Dispatch error: ${pallet}::${name}`;
7
+ if (docs) {
8
+ message += ` - ${docs.join('\n ')}`;
9
+ }
10
+ return message;
11
+ }
12
+ else {
13
+ return `Dispatch error: ${JSON.stringify(err)}`;
14
+ }
15
+ };
3
16
  /**
4
17
  * Represents an error that occurred during the instantiation of a smart contract.
5
18
  * This class extends the base `DedotError` and includes a `raw` property of type `ContractInstantiateResult`.
@@ -38,16 +51,22 @@ export class ContractInstantiateDispatchError extends ContractInstantiateError {
38
51
  * The error that occurred during the dispatch phase.
39
52
  */
40
53
  dispatchError;
54
+ /**
55
+ * The decoded module error, if any.
56
+ */
57
+ moduleError;
41
58
  /**
42
59
  * Constructs a new `ContractInstantiateDispatchError` instance.
43
60
  *
44
61
  * @param err - The `DispatchError` that occurred during the dispatch phase.
45
62
  * @param raw - The raw result of the contract instantiation.
63
+ * @param moduleError - The decoded module error, if any.
46
64
  */
47
- constructor(err, raw) {
65
+ constructor(err, raw, moduleError) {
48
66
  super(raw);
49
67
  this.dispatchError = err;
50
- this.message = `Dispatch error: ${JSON.stringify(err)}`;
68
+ this.moduleError = moduleError;
69
+ this.message = formatDispatchError(err, moduleError);
51
70
  }
52
71
  }
53
72
  /**
@@ -81,7 +100,7 @@ export class ContractInstantiateLangError extends ContractInstantiateError {
81
100
  super(raw);
82
101
  this.langError = err;
83
102
  this.flags = toReturnFlags(raw.result.value.result.flags.bits);
84
- this.message = `Lange error: ${JSON.stringify(err)}`;
103
+ this.message = `Lang error: ${JSON.stringify(err)}`;
85
104
  }
86
105
  }
87
106
  /**
@@ -122,16 +141,22 @@ export class ContractDispatchError extends ContractExecutionError {
122
141
  * The error that occurred during the dispatch phase.
123
142
  */
124
143
  dispatchError;
144
+ /**
145
+ * The decoded module error, if any.
146
+ */
147
+ moduleError;
125
148
  /**
126
149
  * Constructs a new `ContractDispatchError` instance.
127
150
  *
128
151
  * @param err - The `DispatchError` that occurred during the dispatch phase.
129
152
  * @param raw - The raw result of the contract call.
153
+ * @param moduleError - The decoded module error, if any.
130
154
  */
131
- constructor(err, raw) {
155
+ constructor(err, raw, moduleError) {
132
156
  super(raw);
133
157
  this.dispatchError = err;
134
- this.message = `Dispatch error: ${JSON.stringify(err)}`;
158
+ this.moduleError = moduleError;
159
+ this.message = formatDispatchError(err, moduleError);
135
160
  }
136
161
  }
137
162
  /**
@@ -165,7 +190,7 @@ export class ContractLangError extends ContractExecutionError {
165
190
  super(raw);
166
191
  this.langError = err;
167
192
  this.flags = toReturnFlags(raw.result.value.flags.bits);
168
- this.message = `Lange error: ${JSON.stringify(err)}`;
193
+ this.message = `Lang error: ${JSON.stringify(err)}`;
169
194
  }
170
195
  }
171
196
  /**
@@ -1,6 +1,6 @@
1
- import { assert, assertFalse, concatU8a, hexToU8a, isPvm, isUndefined, isWasm, toHex, toU8a, u8aToHex, } from '@dedot/utils';
1
+ import { assert, concatU8a, hexToU8a, isPvm, isUndefined, isWasm, toHex, toU8a, u8aToHex } from '@dedot/utils';
2
2
  import { ContractInstantiateDispatchError, ContractInstantiateLangError } from '../errors.js';
3
- import { toReturnFlags } from '../utils/index.js';
3
+ import { ensureParamsLength, toReturnFlags } from '../utils/index.js';
4
4
  import { DeployerExecutor } from './abstract/index.js';
5
5
  export class ConstructorQueryExecutor extends DeployerExecutor {
6
6
  doExecute(constructor) {
@@ -8,8 +8,7 @@ export class ConstructorQueryExecutor extends DeployerExecutor {
8
8
  assert(meta, `Constructor message not found: ${constructor}`);
9
9
  const callFn = async (...params) => {
10
10
  const { args } = meta;
11
- assertFalse(params.length < args.length, `Expected at least ${args.length} arguments, got ${params.length}`);
12
- assertFalse(params.length > args.length + 1, `Expected at most ${args.length + 1} arguments, got ${params.length}`);
11
+ ensureParamsLength(args.length, params.length);
13
12
  const callOptions = (params[args.length] || {});
14
13
  const { caller = this.options.defaultCaller, // --
15
14
  value = 0n, gasLimit, storageDepositLimit, salt, } = callOptions;
@@ -61,7 +60,9 @@ export class ConstructorQueryExecutor extends DeployerExecutor {
61
60
  }
62
61
  })();
63
62
  if (raw.result.isErr) {
64
- throw new ContractInstantiateDispatchError(raw.result.err, raw);
63
+ const dispatchError = raw.result.err;
64
+ const moduleError = client.registry.findErrorMeta(dispatchError);
65
+ throw new ContractInstantiateDispatchError(dispatchError, raw, moduleError);
65
66
  }
66
67
  const data = this.tryDecode(meta, raw.result.value.result.data);
67
68
  if (data.isErr) {
@@ -1,36 +1,126 @@
1
1
  import { assert, concatU8a, hexToU8a, isPvm, isUndefined, isWasm, toHex, toU8a, u8aToHex } from '@dedot/utils';
2
+ import { Contract } from '../Contract.js';
3
+ import { CREATE1, CREATE2, ensureContractPresence, ensureParamsLength, toEvmAddress } from '../utils/index.js';
4
+ import { ConstructorQueryExecutor } from './ConstructorQueryExecutor.js';
2
5
  import { DeployerExecutor } from './abstract/index.js';
3
6
  export class ConstructorTxExecutor extends DeployerExecutor {
4
7
  doExecute(constructor) {
5
8
  const meta = this.findConstructorMeta(constructor);
6
9
  assert(meta, `Constructor message not found: ${constructor}`);
10
+ // @ts-ignore
7
11
  const callFn = (...params) => {
8
12
  const { args } = meta;
9
- assert(params.length === args.length + 1, `Expected ${args.length + 1} arguments, got ${params.length}`);
10
- const txCallOptions = params[args.length];
13
+ ensureParamsLength(args.length, params.length);
14
+ const txCallOptions = (params[args.length] || {});
11
15
  const { value = 0n, gasLimit, storageDepositLimit, salt } = txCallOptions;
12
- assert(gasLimit, 'Expected a gas limit in ConstructorTxOptions');
13
16
  const formattedInputs = args.map((arg, index) => this.tryEncode(arg, params[index]));
14
17
  const bytes = u8aToHex(concatU8a(hexToU8a(meta.selector), ...formattedInputs));
15
18
  const client = this.client;
16
- if (this.registry.isRevive()) {
17
- assert(isUndefined(salt) || toU8a(salt).byteLength == 32, 'Invalid salt provided in ConstructorCallOptions: expected a 32-byte value as a hex string or a Uint8Array');
18
- assert(!isUndefined(storageDepositLimit), 'Expected a storage deposit limit in ConstructorTxOptions');
19
- if (isPvm(this.code)) {
20
- return client.tx.revive.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt ? toHex(salt) : undefined);
19
+ const tx = (() => {
20
+ if (this.registry.isRevive()) {
21
+ assert(isUndefined(salt) || toU8a(salt).byteLength == 32, 'Invalid salt provided in ConstructorCallOptions: expected a 32-byte value as a hex string or a Uint8Array');
22
+ if (isPvm(this.code)) {
23
+ return client.tx.revive.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt ? toHex(salt) : undefined);
24
+ }
25
+ else {
26
+ return client.tx.revive.instantiate(value, gasLimit, storageDepositLimit, toHex(this.code), bytes, salt ? toHex(salt) : undefined);
27
+ }
21
28
  }
22
29
  else {
23
- return client.tx.revive.instantiate(value, gasLimit, storageDepositLimit, toHex(this.code), bytes, salt ? toHex(salt) : undefined);
30
+ if (isWasm(this.code)) {
31
+ return client.tx.contracts.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt || '0x');
32
+ }
33
+ else {
34
+ return client.tx.contracts.instantiate(value, gasLimit, storageDepositLimit, toHex(this.code), bytes, salt || '0x');
35
+ }
24
36
  }
25
- }
26
- else {
27
- if (isWasm(this.code)) {
28
- return client.tx.contracts.instantiateWithCode(value, gasLimit, storageDepositLimit, this.code, bytes, salt || '0x');
37
+ })();
38
+ let deployerAddress, deployerNonce;
39
+ const calculateContractAddress = async (result) => {
40
+ assert(deployerAddress, 'Deployer Address Not Found');
41
+ const { status, events, dispatchError } = result;
42
+ const onChain = status.type === 'BestChainBlockIncluded' || status.type === 'Finalized';
43
+ assert(onChain, 'The deployment transaction has not yet been included in the best chain block or finalized.');
44
+ assert(!dispatchError, 'The deployment transaction failed to execute. Refer to the dispatch error for more information.');
45
+ if (this.registry.isRevive()) {
46
+ if (salt) {
47
+ let code;
48
+ if (isPvm(this.code)) {
49
+ code = this.code;
50
+ }
51
+ else {
52
+ // pull the raw code in case we're using a code hash here
53
+ code = await client.query.revive.pristineCode(toHex(this.code));
54
+ }
55
+ assert(code, 'Contract Code Binary Not Found');
56
+ return CREATE2(toEvmAddress(deployerAddress), // --
57
+ code, bytes, salt);
58
+ }
59
+ else {
60
+ return CREATE1(toEvmAddress(deployerAddress), // --
61
+ deployerNonce);
62
+ }
29
63
  }
30
64
  else {
31
- return client.tx.contracts.instantiate(value, gasLimit, storageDepositLimit, toHex(this.code), bytes, salt || '0x');
65
+ const event = client.events.contracts.Instantiated.find(events);
66
+ assert(event, 'Contracts.Instantiated event not found');
67
+ return event.palletEvent.data.contract.address();
32
68
  }
33
- }
69
+ };
70
+ tx.withHooks({
71
+ beforeSign: async (tx, signerAddress) => {
72
+ deployerAddress = signerAddress;
73
+ if (this.registry.isRevive() && !salt) {
74
+ deployerNonce = await client.call.accountNonceApi.accountNonce(deployerAddress);
75
+ }
76
+ const callParams = { ...tx.call.palletCall.params };
77
+ const needsDryRun = !callParams.gasLimit || (this.registry.isRevive() && !callParams.storageDepositLimit);
78
+ if (!needsDryRun)
79
+ return;
80
+ const executor = new ConstructorQueryExecutor(this.client, // --
81
+ this.registry, this.code, {
82
+ defaultCaller: signerAddress,
83
+ ...this.options,
84
+ });
85
+ const { raw } = await executor.doExecute(constructor)(...params);
86
+ const { gasRequired, storageDeposit } = raw;
87
+ if (!callParams.gasLimit) {
88
+ callParams.gasLimit = gasRequired;
89
+ }
90
+ if (this.registry.isRevive() && !callParams.storageDepositLimit) {
91
+ callParams.storageDepositLimit = storageDeposit.value;
92
+ }
93
+ const newCall = { ...tx.call };
94
+ newCall.palletCall.params = callParams;
95
+ tx.call = newCall;
96
+ },
97
+ transformResult: (result) => {
98
+ let cachedAddress;
99
+ const contractAddress = async () => {
100
+ if (cachedAddress)
101
+ return cachedAddress;
102
+ cachedAddress = await calculateContractAddress(result);
103
+ return cachedAddress;
104
+ };
105
+ const contract = async (overrideOptions) => {
106
+ const address = await contractAddress();
107
+ // Check if the contract is existed on chain or not!
108
+ await ensureContractPresence(client, this.registry, address);
109
+ return new Contract(client, // --
110
+ this.metadata, address, {
111
+ defaultCaller: deployerAddress,
112
+ ...this.options,
113
+ ...overrideOptions,
114
+ });
115
+ };
116
+ Object.assign(result, {
117
+ contractAddress,
118
+ contract,
119
+ });
120
+ return result;
121
+ },
122
+ });
123
+ return tx;
34
124
  };
35
125
  callFn.meta = meta;
36
126
  return callFn;
@@ -1,6 +1,6 @@
1
- import { assert, assertFalse, concatU8a, hexToU8a, u8aToHex } from '@dedot/utils';
1
+ import { assert, concatU8a, hexToU8a, u8aToHex } from '@dedot/utils';
2
2
  import { ContractDispatchError, ContractLangError } from '../errors.js';
3
- import { ensureContractPresence, toReturnFlags } from '../utils/index.js';
3
+ import { ensureContractPresence, ensureParamsLength, toReturnFlags } from '../utils/index.js';
4
4
  import { ContractExecutor } from './abstract/index.js';
5
5
  export class QueryExecutor extends ContractExecutor {
6
6
  doExecute(message) {
@@ -8,8 +8,7 @@ export class QueryExecutor extends ContractExecutor {
8
8
  assert(meta, `Query message not found: ${message}`);
9
9
  const callFn = async (...params) => {
10
10
  const { args } = meta;
11
- assertFalse(params.length < args.length, `Expected at least ${args.length} arguments, got ${params.length}`);
12
- assertFalse(params.length > args.length + 1, `Expected at most ${args.length + 1} arguments, got ${params.length}`);
11
+ ensureParamsLength(args.length, params.length);
13
12
  const callOptions = (params[args.length] || {});
14
13
  const { caller = this.options.defaultCaller, value = 0n, gasLimit, storageDepositLimit } = callOptions;
15
14
  assert(caller, 'Expected a valid caller address in ContractCallOptions');
@@ -41,7 +40,9 @@ export class QueryExecutor extends ContractExecutor {
41
40
  }
42
41
  })();
43
42
  if (raw.result.isErr) {
44
- throw new ContractDispatchError(raw.result.err, raw);
43
+ const dispatchError = raw.result.err;
44
+ const moduleError = client.registry.findErrorMeta(dispatchError);
45
+ throw new ContractDispatchError(dispatchError, raw, moduleError);
45
46
  }
46
47
  const data = this.tryDecode(meta, raw.result.value.data);
47
48
  if (data.isErr) {
@@ -1,4 +1,6 @@
1
- import { assert, concatU8a, hexToU8a, isUndefined, u8aToHex } from '@dedot/utils';
1
+ import { assert, concatU8a, hexToU8a, u8aToHex } from '@dedot/utils';
2
+ import { ensureParamsLength } from '../utils/index.js';
3
+ import { QueryExecutor } from './QueryExecutor.js';
2
4
  import { ContractExecutor } from './abstract/index.js';
3
5
  export class TxExecutor extends ContractExecutor {
4
6
  doExecute(message) {
@@ -6,22 +8,47 @@ export class TxExecutor extends ContractExecutor {
6
8
  assert(meta, `Tx message not found: ${message}`);
7
9
  const callFn = (...params) => {
8
10
  const { args } = meta;
9
- assert(params.length === args.length + 1, `Expected ${args.length + 1} arguments, got ${params.length}`);
10
- const txCallOptions = params[args.length];
11
+ ensureParamsLength(args.length, params.length);
12
+ const txCallOptions = (params[args.length] || {});
11
13
  const { value = 0n, gasLimit, storageDepositLimit } = txCallOptions;
12
- assert(gasLimit, 'Expected a gas limit in ContractTxOptions');
13
14
  const formattedInputs = args.map((arg, index) => this.tryEncode(arg, params[index]));
14
15
  const bytes = u8aToHex(concatU8a(hexToU8a(meta.selector), ...formattedInputs));
15
16
  const client = this.client;
16
- if (this.registry.isRevive()) {
17
- assert(!isUndefined(storageDepositLimit), 'Expected a storage deposit limit in ContractTxOptions');
18
- return client.tx.revive.call(this.address, // --
19
- value, gasLimit, storageDepositLimit || 0n, bytes);
20
- }
21
- else {
22
- return client.tx.contracts.call(this.address, // --
23
- value, gasLimit, storageDepositLimit, bytes);
24
- }
17
+ const tx = (() => {
18
+ if (this.registry.isRevive()) {
19
+ return client.tx.revive.call(this.address, // --
20
+ value, gasLimit, storageDepositLimit || 0n, bytes);
21
+ }
22
+ else {
23
+ return client.tx.contracts.call(this.address, // --
24
+ value, gasLimit, storageDepositLimit, bytes);
25
+ }
26
+ })();
27
+ tx.withHooks({
28
+ beforeSign: async (tx, signerAddress) => {
29
+ const callParams = { ...tx.call.palletCall.params };
30
+ const needsDryRun = !callParams.gasLimit || (this.registry.isRevive() && !callParams.storageDepositLimit);
31
+ if (!needsDryRun)
32
+ return;
33
+ const executor = new QueryExecutor(this.client, // --
34
+ this.registry, this.address, {
35
+ defaultCaller: signerAddress,
36
+ ...this.options,
37
+ });
38
+ const { raw } = await executor.doExecute(message)(...params);
39
+ const { gasRequired, storageDeposit } = raw;
40
+ if (!callParams.gasLimit) {
41
+ callParams.gasLimit = gasRequired;
42
+ }
43
+ if (this.registry.isRevive() && !callParams.storageDepositLimit) {
44
+ callParams.storageDepositLimit = storageDeposit.value;
45
+ }
46
+ const newCall = { ...tx.call };
47
+ newCall.palletCall.params = callParams;
48
+ tx.call = newCall;
49
+ },
50
+ });
51
+ return tx;
25
52
  };
26
53
  callFn.meta = meta;
27
54
  return callFn;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dedot/contracts",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Delightful ink! Contract APIs",
5
5
  "author": "Tung Vu <tung@dedot.dev>",
6
6
  "homepage": "https://dedot.dev",
@@ -18,10 +18,10 @@
18
18
  "test": "npx vitest --watch=false"
19
19
  },
20
20
  "dependencies": {
21
- "@dedot/api": "0.13.2",
22
- "@dedot/codecs": "0.13.2",
23
- "@dedot/types": "0.13.2",
24
- "@dedot/utils": "0.13.2",
21
+ "@dedot/api": "0.14.0",
22
+ "@dedot/codecs": "0.14.0",
23
+ "@dedot/types": "0.14.0",
24
+ "@dedot/utils": "0.14.0",
25
25
  "@ethereumjs/rlp": "^10.0.0"
26
26
  },
27
27
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "node": ">=18"
33
33
  },
34
34
  "license": "Apache-2.0",
35
- "gitHead": "2835a0995d758ac3bcdfe1f880eb6e8d9b1e5605",
35
+ "gitHead": "cde1e77566038990fcdd69b291a4231013c16528",
36
36
  "module": "./index.js",
37
37
  "types": "./index.d.ts",
38
38
  "exports": {
package/types/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { SubstrateApi } from '@dedot/api/chaintypes';
2
2
  import { AccountId32Like, type Bytes, BytesLike, type DispatchError, Extrinsic, type H256, type Result, Weight } from '@dedot/codecs';
3
- import { AnyFunc, AsyncMethod, GenericSubstrateApi, IEventRecord, ISubmittableExtrinsic, RpcVersion, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
3
+ import { AnyFunc, AsyncMethod, GenericSubstrateApi, IEventRecord, ISubmittableExtrinsic, ISubmittableResult, RpcVersion, Unsub, VersionedGenericSubstrateApi } from '@dedot/types';
4
+ import { Contract } from '../Contract.js';
4
5
  import { ContractAddress, ContractCallMessage, ContractConstructorMessage } from './shared.js';
5
6
  import { ContractEventV4, ContractMetadataV4 } from './v4.js';
6
7
  import { ContractEventV5, ContractMetadataV5 } from './v5.js';
@@ -69,9 +70,19 @@ export type ContractInstantiateResult<_ extends GenericSubstrateApi> = {
69
70
  debugMessage?: Bytes;
70
71
  result: Result<InstantiateReturnValue, DispatchError>;
71
72
  };
72
- type SubmittableExtrinsic = ISubmittableExtrinsic & Extrinsic;
73
- export type ContractSubmittableExtrinsic<_ extends GenericSubstrateApi> = SubmittableExtrinsic;
74
- export type GenericInstantiateSubmittableExtrinsic<_ extends GenericSubstrateApi> = SubmittableExtrinsic;
73
+ interface IContractInstantiateSubmittableResult<ContractApi extends GenericContractApi = GenericContractApi> extends ISubmittableResult {
74
+ /**
75
+ * Get deployed contract address
76
+ */
77
+ contractAddress(): Promise<ContractAddress>;
78
+ /**
79
+ * Get deployed contract instance
80
+ */
81
+ contract(options?: ExecutionOptions): Promise<Contract<ContractApi>>;
82
+ }
83
+ type SubmittableExtrinsic<R extends ISubmittableResult> = ISubmittableExtrinsic<R> & Extrinsic;
84
+ export type ContractSubmittableExtrinsic<_ extends GenericSubstrateApi> = SubmittableExtrinsic<ISubmittableResult>;
85
+ export type GenericInstantiateSubmittableExtrinsic<_ extends GenericSubstrateApi, ContractApi extends GenericContractApi = GenericContractApi> = SubmittableExtrinsic<IContractInstantiateSubmittableResult<ContractApi>>;
75
86
  export type ContractMetadata = ContractMetadataV4 | ContractMetadataV5 | ContractMetadataV6;
76
87
  export interface LooseContractMetadata {
77
88
  version: number | string;
@@ -88,14 +99,11 @@ export type ConstructorCallOptions = CallOptions & {
88
99
  };
89
100
  export type ConstructorTxOptions = CallOptions & {
90
101
  salt?: BytesLike;
91
- gasLimit: Weight;
92
102
  };
93
103
  export type ContractCallOptions = CallOptions & {
94
104
  caller?: AccountId32Like;
95
105
  };
96
- export type ContractTxOptions = CallOptions & {
97
- gasLimit: Weight;
98
- };
106
+ export type ContractTxOptions = CallOptions;
99
107
  export type GenericContractQueryCall<ChainApi extends GenericSubstrateApi, F extends AsyncMethod = (...args: any[]) => Promise<GenericContractCallResult<any, ContractCallResult<ChainApi>>>> = F & {
100
108
  meta: ContractCallMessage;
101
109
  };
package/types/v6.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ContractInformation, ContractSource, ContractType } from './shared';
1
+ import { ContractInformation, ContractSource, ContractType } from './shared.js';
2
2
  import { ContractSpecV5, ContractStorageV5 } from './v5.js';
3
3
  export interface ContractSourceV6 extends ContractSource {
4
4
  contract_binary?: string;
package/utils/ensure.d.ts CHANGED
@@ -10,3 +10,4 @@ export declare function ensureContractPresence(client: ISubstrateClient<Substrat
10
10
  export declare function ensureValidContractAddress(address: ContractAddress, registry: TypinkRegistry): void;
11
11
  export declare function ensureValidCodeHashOrCode(codeHashOrCode: Hash | Uint8Array | string, registry: TypinkRegistry): void;
12
12
  export declare function ensureSupportedContractMetadataVersion(metadata: LooseContractMetadata): void;
13
+ export declare function ensureParamsLength(expectedArgsLength: number, actualParamsLength: number): void;
package/utils/ensure.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { accountId32ToHex } from '@dedot/codecs';
2
- import { assert, DedotError, ensurePresence, hexToU8a, isEvmAddress, isPvm, isWasm, toU8a, } from '@dedot/utils';
2
+ import { assert, assertFalse, DedotError, ensurePresence, hexToU8a, isEvmAddress, isPvm, isWasm, toU8a, } from '@dedot/utils';
3
3
  export const ensureStorageApiSupports = (version) => {
4
4
  const numberedVersion = typeof version === 'number' ? version : parseInt(version);
5
5
  assert(numberedVersion >= 5, `Contract Storage Api Only Available for metadata version >= 5, current version: ${version}`);
@@ -57,3 +57,8 @@ export function ensureSupportedContractMetadataVersion(metadata) {
57
57
  throw new DedotError(`Unsupported metadata version: ${metadata.version}`);
58
58
  }
59
59
  }
60
+ export function ensureParamsLength(expectedArgsLength, actualParamsLength) {
61
+ assertFalse(actualParamsLength < expectedArgsLength, `Expected at least ${expectedArgsLength} arguments, got ${actualParamsLength}`);
62
+ // One extra param for the options
63
+ assertFalse(actualParamsLength > expectedArgsLength + 1, `Expected at most ${expectedArgsLength + 1} arguments, got ${actualParamsLength}`);
64
+ }