From 9cfd8172fdc2a654cd23e7cfe368f1187e887da5 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 31 Oct 2020 10:43:08 +0100 Subject: [PATCH 1/2] New contract interface --- docs/api-contract/start/contract.md | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/docs/api-contract/start/contract.md b/docs/api-contract/start/contract.md index 52d0707cc9..9dc69ff00f 100644 --- a/docs/api-contract/start/contract.md +++ b/docs/api-contract/start/contract.md @@ -35,18 +35,15 @@ const callValue = await contract.query.get(alicePair.address, value, gasLimit); // The actual result from RPC as `ContractExecResult` console.log(callValue.result.toHuman()); -// check if the call was successful -if (callValue.result.isSuccess) { - // data from the enum - const success = callValue.result.asSuccess; +// gas consumed +console.log(callValue.gasConsumed.toHuman()); +// check if the call was successful +if (callValue.result.isOk) { // should output 123 as per our initial set (output here is an i32) - console.log(callValue.output.toHuman()); - - // the amount of gas consumed (naturally a u64 value() - console.log(success.gasConsumed.toHuman()); + console.log('Success', callValue.output.toHuman()); } else { - console.error('Call failed'); + console.error('Call failed', callValue.result.asErr); } ``` @@ -133,15 +130,11 @@ const value = 0; const incValue = 1; // Instead of sending we use the `call` interface via `.query` that will return -// the gas consoumed (the API aut-fill the max block tx weight when -1 is the gasLimit) -const { result } = await contract.query.inc(value, -1, incValue) +// the gas consumed (the API aut-fill the max block tx weight when -1 is the gasLimit) +const { gasConsumed, result } = await contract.query.inc(value, -1, incValue) -if (result.isSuccess) { - // extract the value from the Success portion of the enum - const gasConsumed = result.asSuccess.gasConsumed; - - console.log(`Call execution will consume ${gasConsumed.toString()}`); -} +console.log(`outcome: ${result.isOk ? 'Ok' : 'Error'}`); +console.log(`gasConsumed ${gasConsumed.toString()}`); ``` We can use the `gasConsumed` input (potentially with a buffer for various execution paths) in any calls to `contract.tx.inc(...)` with the same input parameters specified on the `query` where the estimation was done. From cde53badcb2cb8ca0f2cf1729da4ba7792d1ac03 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 1 Nov 2020 10:37:53 +0100 Subject: [PATCH 2/2] Split call/tx --- docs/api-contract/FAQ.md | 15 +++- docs/api-contract/start/blueprint.md | 2 +- .../start/{contract.md => contract.read.md} | 74 ++----------------- docs/api-contract/start/contract.tx.md | 73 ++++++++++++++++++ sidebars.js | 3 +- 5 files changed, 96 insertions(+), 71 deletions(-) rename docs/api-contract/start/{contract.md => contract.read.md} (51%) create mode 100644 docs/api-contract/start/contract.tx.md diff --git a/docs/api-contract/FAQ.md b/docs/api-contract/FAQ.md index 40ea556a61..a2508ed2f9 100644 --- a/docs/api-contract/FAQ.md +++ b/docs/api-contract/FAQ.md @@ -12,13 +12,24 @@ When passing an older pre ink! 3.0-rc1 version of the ABI, you will have an "Inv If you are using an older version you would need to use an older version of the API or upgrade your contracts to ink! 3.0. +## After upgrading I don't have isSuccess/isError + +In earlier versions of Substrate the call results via read had a slightly different interface to what it available now. Specifically on the `result` structure retrieved via read calls `isOk` was named `isSuccess` (and `isErr` was named `isError`). Since the `Contract` interface follows the Substrate convention these changes has been applied alongside the Substrate update to the `ContractExecResult` structure. + +In addition `asErr` (unlike the older `asError`) now also has a full error enum (mapping to `DispatchError`) containing failures, unlike the older interface where this was not available. On older chains due to lack of information this will always be `Other`, while on newer chains the result will be fully populated. + +The `Contract` interface, despite these underlying naming changes, transparently maps older responses (on older, not yet upgraded chains) to the newer structure, so while there is an change to the JS code use required to cater for this new structure, it can be used against both old and new chains with a transparent mapping between. + + ## Why is there a snake_case vs camelCase difference -The API always tries to use `camelCase` where available. This aligns with the de-facto standards that are generally (not always!) used in JS interfaces. This means that when decorating the ABIs into `contract..methodName` the `methodName` part would be in camelCase format. +The API always tries to use `camelCase` naming where available. This aligns with the de-facto standards that are generally (not always!) used in JS interfaces. This means that when decorating the ABIs into `contract..methodName` the `methodName` part would be in camelCase format. An example of this would be in the erc20 ink! ABI - the method in the above would be `balance_of` however the API (for consistency with the full quite of libraries), decorate this as `contract.query.balanceOf`. When calling the `.read` or `.exec` directly on the contract, you should still specify the original ABI identifier, e.g. `contract.read('balance_of', ...)` (In the next release this will also allow for camelCase lookups in addition to the original Rust/Solidity naming) + ## How do I subscribe to a contract query? + Subscriptions, and queries to the raw storage are on their way! Unfortunately until then there isn't a proper way to subscribe to a contract query. A temporary workaround is to subscribe to `api.query.contracts.contractInfoOf`. ```javascript @@ -30,4 +41,4 @@ const unsub = await api.query.contracts.contractInfoOf(contractAddress, async () }); ``` -But this workaround is not without drawbacks. Since the callback will be executed every time the contract's storage is affected you will ultimately end up calling your contract query more often than necessary. \ No newline at end of file +But this workaround is not without drawbacks. Since the callback will be executed every time the contract's storage is affected you will ultimately end up calling your contract query more often than necessary. diff --git a/docs/api-contract/start/blueprint.md b/docs/api-contract/start/blueprint.md index b30f004515..3fddf11d4e 100644 --- a/docs/api-contract/start/blueprint.md +++ b/docs/api-contract/start/blueprint.md @@ -66,4 +66,4 @@ const unsub = await blueprint ## Interact with contracts -We have made it this far. At this point you should be familiar with code deployments as well as contract instantiation, next up [we will read a contract and send transactions to it](contract.md). +We have made it this far. At this point you should be familiar with code deployments as well as contract instantiation, next up [we will read a contract](contract.read.md). diff --git a/docs/api-contract/start/contract.md b/docs/api-contract/start/contract.read.md similarity index 51% rename from docs/api-contract/start/contract.md rename to docs/api-contract/start/contract.read.md index 9dc69ff00f..87313265b4 100644 --- a/docs/api-contract/start/contract.md +++ b/docs/api-contract/start/contract.read.md @@ -30,20 +30,20 @@ const gasLimit = 3000n * 1000000n; // Perform the actual read (no params at the end, for the `get` message) // (We perform the send from an account, here using Alice's address) -const callValue = await contract.query.get(alicePair.address, value, gasLimit); +const { gasConsumed, result, outcome } = await contract.query.get(alicePair.address, value, gasLimit); // The actual result from RPC as `ContractExecResult` -console.log(callValue.result.toHuman()); +console.log(result.toHuman()); // gas consumed -console.log(callValue.gasConsumed.toHuman()); +console.log(gasConsumed.toHuman()); // check if the call was successful -if (callValue.result.isOk) { +if (result.isOk) { // should output 123 as per our initial set (output here is an i32) - console.log('Success', callValue.output.toHuman()); + console.log('Success', output.toHuman()); } else { - console.error('Call failed', callValue.result.asErr); + console.error('Error', result.asErr); } ``` @@ -81,65 +81,5 @@ In cases where the ABI messages have conflicting names, instead of the `'get'` s ## Sending a transaction -In addition to using the `.query.` on a contract, the `.tx.` method is provides to send an actual encoded transaction to the contract. Expanding on our above example, we can now execute and then retrieve the subsequent value - +Now that we understand the underlying call/read interfaces where a message is executed, but not part of a block, we will loo into [sending transaction messages](contract.tx.md) in our next section. -```javascript -// We will use these values for the execution -const value = 0; // only useful on isPayable messages -const gasLimit = 3000n * 1000000n; -const incValue = 1; - -// Send the transaction, like elsewhere this is a normal extrinsic -// with the same rules as applied in the API (As with the read example, -// additional paras, if required can follow - here only one is needed) -await contract.tx - .inc(value, gasLimit, incValue) - .signAndSend(alicePair, (result) => { - if (result.status.isInBlock) { - console.log('in a block'); - } else if (result.status.isFinalized) { - console.log('finalized'); - } - }); -``` - -If we perform the same `query.get` read on the value now, it would be `124`. For lower-level access, like we have in the `Blueprint` via `.createContract` you can also perform the execution via the `.exec` function, which would yield equivalent results - - -```javascript -// Send the transaction, like elsewhere this is a normal submittable -// extrinsic with the same rules as applied in the API -await contract - .exec('inc', value, gasLimit, incValue) - .signAndSend(alicePair, (result) => { - ... - }); -``` - -For the above interface we can specify the message as the string name, the index of the actual message as retrieved via the Abi. - - -## Weight estimation - -To estimate the gasLimit (which in the Substrate context refers to the weight used), we can use the `.query` (read) interfaces with a sufficiently large value to retrieve the actual gas consumed. The API makes this easy - with a `gasLimit` or `-1` passed to the query it will use the maximum gas limit available to transactions and the return value will have the actual gas used. - -To see this in practice - - -```js -// We will use these values for the execution -const value = 0; -const incValue = 1; - -// Instead of sending we use the `call` interface via `.query` that will return -// the gas consumed (the API aut-fill the max block tx weight when -1 is the gasLimit) -const { gasConsumed, result } = await contract.query.inc(value, -1, incValue) - -console.log(`outcome: ${result.isOk ? 'Ok' : 'Error'}`); -console.log(`gasConsumed ${gasConsumed.toString()}`); -``` - -We can use the `gasConsumed` input (potentially with a buffer for various execution paths) in any calls to `contract.tx.inc(...)` with the same input parameters specified on the `query` where the estimation was done. - - -## That is it... for now - -This was a whirl-wind tour of what the API provides in terms of the `@polkadot/api-contract` interface. It is not perfect yet, we would like to expand it to allow for greater type-checking on the contracts (instead of read/exec wit messages), but hopefully in the current state it already enhances the way you can interact with contracts. diff --git a/docs/api-contract/start/contract.tx.md b/docs/api-contract/start/contract.tx.md new file mode 100644 index 0000000000..b852f66fd0 --- /dev/null +++ b/docs/api-contract/start/contract.tx.md @@ -0,0 +1,73 @@ +--- +title: Contract Tx +--- + +In addition to using the `.query.` on a contract, the `.tx.` method is provides to send an actual encoded transaction to the contract, allow for execution and have this applied in a block. Expanding on our previous examples, we can now execute and then retrieve the subsequent value - + +```javascript +// We will use these values for the execution +const value = 0; // only useful on isPayable messages +const gasLimit = 3000n * 1000000n; +const incValue = 1; + +// Send the transaction, like elsewhere this is a normal extrinsic +// with the same rules as applied in the API (As with the read example, +// additional params, if required can follow - here only one is needed) +await contract.tx + .inc(value, gasLimit, incValue) + .signAndSend(alicePair, (result) => { + if (result.status.isInBlock) { + console.log('in a block'); + } else if (result.status.isFinalized) { + console.log('finalized'); + } + }); +``` + +If we perform the same `query.get` read on the value now, it would be `124`. For lower-level access, like we have in the `Blueprint` via `.createContract` you can also perform the execution via the `.exec` function, which would yield equivalent results - + +```javascript +// Send the transaction, like elsewhere this is a normal submittable +// extrinsic with the same rules as applied in the API +await contract + .exec('inc', value, gasLimit, incValue) + .signAndSend(alicePair, (result) => { + ... + }); +``` + +For the above interface we can specify the message as the string name, the index of the actual message as retrieved via the Abi. + + +## Weight estimation + +To estimate the gasLimit (which in the Substrate context refers to the weight used), we can use the `.query` (read) interfaces with a sufficiently large value to retrieve the actual gas consumed. The API makes this easy - with a `gasLimit` or `-1` passed to the query it will use the maximum gas limit available to transactions and the return value will have the actual gas used. + +To see this in practice - + +```js +// We will use these values for the execution +const value = 0; +const incValue = 1; + +// Instead of sending we use the `call` interface via `.query` that will return +// the gas consumed (the API aut-fill the max block tx weight when -1 is the gasLimit) +const { gasConsumed, result } = await contract.query.inc(value, -1, incValue) + +console.log(`outcome: ${result.isOk ? 'Ok' : 'Error'}`); +console.log(`gasConsumed ${gasConsumed.toString()}`); +``` + +We can use the `gasConsumed` input (potentially with a buffer for various execution paths) in any calls to `contract.tx.inc(...)` with the same input parameters specified on the `query` where the estimation was done. + + +## Events + +On current versions of the API, any events raised by the contract will be transparently decoded with the relevant ABI and will be made available on the `result` (from `.signAndSend(alicePair, (result) => {...}`) as `contractEvents`. + +Where no events were emitted this value would be `undefined`, however should events be emitted, the array will contain all the decoded values. + + +## That is it... for now + +This was a whirl-wind tour of what the API provides in terms of the `@polkadot/api-contract` interface. It is not perfect yet, we would like to expand it to allow for greater type-checking on the contracts (instead of read/exec wit messages), but hopefully in the current state it already enhances the way you can interact with contracts. diff --git a/sidebars.js b/sidebars.js index f2f6604e48..02ca631456 100644 --- a/sidebars.js +++ b/sidebars.js @@ -101,7 +101,8 @@ module.exports = { 'api-contract/start/basics', 'api-contract/start/code', 'api-contract/start/blueprint', - 'api-contract/start/contract' + 'api-contract/start/contract.read', + 'api-contract/start/contract.tx' ] }, 'api-contract/FAQ'