Skip to content

Commit 91a49f4

Browse files
Merge pull request #1174 from kadena-community/feat/docs/voting-dapp-update
feat(docs): refactor voting dapp tutorial gas station implementation
2 parents 1f0c0ce + f4b866e commit 91a49f4

File tree

2 files changed

+75
-38
lines changed

2 files changed

+75
-38
lines changed

.changeset/spotty-suits-explode.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@kadena/docs': patch
3+
---
4+
5+
refactor voting dapp tutorial gas station implementation

packages/apps/docs/src/pages/build/guides/election-dapp-tutorial/09-gas-station.md

+70-38
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ that can pay the gas fee for every voting transaction, allowing all citizens to
4747
## Recommended reading
4848

4949
* [The First Crypto Gas Station is Now on Kadena’s Blockchain](/blogchain/2020/the-first-crypto-gas-station-is-now-on-kadenas-blockchain-2020-08-06)
50+
* [Introducing Kadena Account Protocols (KIP-0012)](/blogchain/2021/introducing-kadena-account-protocols-kip-0012-2021-09-27)
5051

5152
## Get the code
5253

@@ -214,12 +215,9 @@ The test will now fail with
214215
`Error: found unimplemented member while resolving model constraints: create-gas-payer-guard`.
215216
Indeed, there is a function `create-gas-payer-guard` defined in the `gas-payer-v1` interface
216217
that still needs to be implemented. The documentation inside is a bit cryptic, but it suggests
217-
to require something like the `GAS_PAYER` capability without the parameters. You can use
218-
the `GAS` capability from the `coin` module here. After all, in Chainweaver's module explorer
219-
you can find that this capability is documented as `Magic capability to protect gas buy and redeem`.
220-
Sounds legit! Implement `create-gas-payer-guard` as follows, using the built-in functions
221-
`create-user-guard` and `require-capability`. You can use `GAS` directly if you load the
222-
`coin` module in the `election-gas-station` module.
218+
to require something like the `GAS_PAYER` capability without the parameters. To achieve this,
219+
you can leverage the built-in function `create-capability-guard` and pass the `ALLOW_GAS`
220+
capability into it. The function will return a guard that requires the respective capability.
223221

224222
```pact
225223
(namespace 'n_fd020525c953aa002f20fb81a920982b175cdf1a)
@@ -231,8 +229,6 @@ Sounds legit! Implement `create-gas-payer-guard` as follows, using the built-in
231229
232230
(implements gas-payer-v1)
233231
234-
(use coin)
235-
236232
(defcap GAS_PAYER:bool
237233
( user:string
238234
limit:integer
@@ -244,11 +240,7 @@ Sounds legit! Implement `create-gas-payer-guard` as follows, using the built-in
244240
(defcap ALLOW_GAS () true)
245241
246242
(defun create-gas-payer-guard:guard ()
247-
(create-user-guard (gas-payer-guard))
248-
)
249-
250-
(defun gas-payer-guard ()
251-
(require-capability (GAS))
243+
(create-capability-guard (ALLOW_GAS))
252244
)
253245
)
254246
```
@@ -300,31 +292,54 @@ function change the line `.addSigner(accountKey(account))` into the following.
300292
```pact
301293
.addSigner(accountKey(account), (withCapability) => [
302294
withCapability(`${NAMESPACE}.election-gas-station.GAS_PAYER`, account, { int: 0 }, { decimal: '0.0' }),
303-
withCapability('coin.GAS'),
304295
])
305296
```
306297

307-
This scopes the signature of the account that votes to two capabilities. The `coin.GAS` capability is used
308-
in the `create-gas-payer-guard` function of the `election-gas-station` module. The voter account name and
309-
zero (unlimited) limits for the amount of gas and the gas price are passed into the
310-
`${NAMESPACE}.election-gas-station.GAS_PAYER` capability.
311-
312-
Also, change the `senderAccount` in the transaction's metadata to `'election-gas-station'`.
298+
This scopes the signature of the account that votes to the `GAS_PAYER` capability. The voter account name and
299+
zero (unlimited) limits for the amount of gas and the gas price are passed as arguments. Also, change the
300+
`senderAccount` in the transaction's metadata to `'election-gas-station'`, to indicate that the election
301+
gas station account will pay the gas fee of the transaction instead of the voter account.
313302

314303
Return to the election website and try to vote again with the voter account. The transaction will still fail
315304
with the error: `Failure: Tx Failed: Insufficient funds`. Apparently, the gas station does not work as it is
316305
supposed to, yet. The reason is that the gas station module attempts to pay for gas using the `senderAccount`,
317306
but this account does not exist. It has to be created first. It also needs to have a positive KDA balance.
318307
Otherwise, the transaction will still fail due to insufficient funds in the gas station account.
319308

320-
## Create and fund the gas station account
309+
## Create the gas station account
321310

322-
The `coin` module is already imported inside the `election-gas-station` module. You can use it to create the
323-
`election-gas-station` account in a function called `init`, as follows.
311+
Actually, `election-gas-station` is not the most ideal name for the gas station account. As explained in the
312+
recommended reading, it is more secure to use a principal account name. Whereas your admin and voter accounts
313+
are guarded by a keyset, the gas station account will be guarded by the `ALLOW_GAS` capability. The gas station
314+
account is thus an example of a capability guarded account. The built-in Pact function `create-pincipal` can
315+
automatically create an account name based on a capability guard for you if you pass the capability guard as
316+
the first and only argument into it. The resulting account name will be prefixed with the `c:` of `capability`.
317+
Define the gas station account name as a constant at the bottom of the `election-gas-station` module in the
318+
`./pact/election-gas-station.pact` file.
324319

325320
```pact
326-
(defconst GAS_STATION_ACCOUNT "election-gas-station")
321+
(defconst GAS_STATION_ACCOUNT (create-principal (create-gas-payer-guard)))
322+
```
323+
324+
Update the `./pact/election-gas-station.repl` file as follows to print out the capability guarded gas station
325+
account name when you run the file.
327326

327+
```pact
328+
(load "setup.repl")
329+
330+
(begin-tx "Load election gas station module")
331+
(load "root/gas-payer-v1.pact")
332+
(load "election-gas-station.pact")
333+
[GAS_STATION_ACCOUNT]
334+
(commit-tx)
335+
```
336+
337+
In the `./pact/election-gas-station.pact` file, you can use the `create-account` function of the
338+
`coin` module to create the gas station account in a function called `init` in the `election-gas-station`
339+
module, as follows. The first argument of the function is the account name you just defined and the second
340+
argument is the guard for the account.
341+
342+
```pact
328343
(defun init ()
329344
(coin.create-account GAS_STATION_ACCOUNT (create-gas-payer-guard))
330345
)
@@ -357,51 +372,68 @@ in the `./snippets` folder of your project. Replace `k:account` with your admin
357372
npm run deploy-gas-station:devnet -- k:account upgrade init
358373
```
359374

360-
Verify that the `election-gas-station` account now exists with a 0 KDA balance on Devnet by running the
361-
following script.
375+
Verify that the gas station account now exists with a 0 KDA balance on Devnet by running the
376+
following script. Replace `c:account` with the actual gas station account name that you printed by running
377+
`./pact/election-gas-station.repl`.
362378

363379
```bash
364-
npm run coin-details:devnet -- election-gas-station
380+
npm run coin-details:devnet -- c:account
365381
```
366382

367383
If everything went well, you should see output similar to this.
368384

369385
```bash
370386
{
371387
guard: {
372-
args: [],
373-
fun: 'n_fd020525c953aa002f20fb81a920982b175cdf1a.election-gas-station.gas-payer-guard'
388+
cgPactId: null,
389+
cgArgs: [],
390+
cgName: 'n_fd020525c953aa002f20fb81a920982b175cdf1a.election-gas-station.ALLOW_GAS'
374391
},
375392
balance: 0,
376-
account: 'election-gas-station'
393+
account: 'c:Jjn2uym_xGD32ojhWdPjB5mgIbDwgXRRvkWmFl5n4gg'
377394
}
378395
```
379396

397+
The account details show the capability guard that guards the gas station account and was used to generate
398+
the `c:` account name. Notice how the `ALLOW_GAS` capability is prefixed with the module name as well as your
399+
principal namespace. Since the principal namespace is based on your admin keyset, and the principal account
400+
of the gas station is based on a capability including that principal namespace, it can be concluded that the
401+
gas station account name you created is unique to your admin account. This makes it impossible for someone else
402+
with a different keyset to squat your gas station account on another chain. That is how principal accounts in
403+
principal namespaces provide better security than vanity account names in the `free` namespace.
404+
405+
## Fund the gas station account
406+
380407
Execute the `./transfer.ts` snippet by running the following command to transfer 1 KDA from your admin
381-
account to the gas station account. Replace `k:account` with your admin account. The transaction
408+
account to the gas station account. Replace `k:account` with your admin account and replace `c:account`
409+
with the actual account name of your gas station. The transaction
382410
inside this file is similar to `./transfer-create.ts`, except that it does not use the special
383411
`sender00` account, but your own election admin account to transfer KDA from. Therefore, the transaction
384412
needs to be signed with Chainweaver instead of a private key. Also, the `transfer` function of the
385413
`coin` module is used. This function requires that the receiving account already exists on the
386414
blockchain and will not create the account if it does not exist like `transfer-create` would.
387415

388416
```bash
389-
npm run transfer:devnet -- k:account election-gas-station 1
417+
npm run transfer:devnet -- k:account c:account 1
390418
```
391419

392-
Verify that the `election-gas-station` account now has a 1 KDA balance on Devnet by running the
393-
following script again.
420+
Verify that the election gasstation account now has a 1 KDA balance on Devnet by running the
421+
following script again. Replace `c:account` with the actual account name of your gas station.
394422

395423
```bash
396-
npm run coin-details:devnet -- election-gas-station
424+
npm run coin-details:devnet -- c:account
397425
```
398426

399427
Now, everything should be set to allow voters to vote for free, because the `election-gas-station`
400428
account can pay the gas fee charged for the voting transaction.
401429

402430
## Vote again
403431

404-
Return to the election website, set the account to your voter account and vote for one of the
432+
Open the file `frontend/src/repositories/vote/DevnetVoteRepository.ts` and in the `vote`
433+
function change the value of `senderAccount` from `election-gas-station` to the `c:account` of the gas
434+
station that you created.
435+
436+
Vist the election website in your browser, set the account to your voter account and vote for one of the
405437
candidates in the list. Unfortunately, the transaction still fails but this time with a
406438
different error: `Keyset failure`. This error occurs because the signature is not scoped to
407439
the `ACCOUNT-OWNER` capability used in `./pact/election.repl`. When you created this capability
@@ -430,10 +462,10 @@ The `caps` field in the signature passed to `env-sigs` is an empty array. As a c
430462
signature of the transaction is not scoped to any capability and the signer automatically
431463
approves all capabilities required for the function execution. In the `vote` function of
432464
`frontend/src/repositories/vote/DevnetVoteRepository.ts` you scoped the signature of the
433-
transaction to two gas related capabilities, but not to the `ACCOUNT-OWNER` capability. When
465+
transaction to the `GAS_PAYER` capability, but not to the `ACCOUNT-OWNER` capability. When
434466
you sign for some capabilities but not all capabilities required for execution of a transaction,
435467
the execution will fail at the point where a capability is required that you did not sign for.
436-
Therefore, you need to add a third capability to the array passed to `addSigners` in
468+
Therefore, you need to add a second capability to the array passed to `addSigners` in
437469
the `vote` function in `frontend/src/repositories/vote/DevnetVoteRepository.ts`.
438470

439471
```typescript

0 commit comments

Comments
 (0)