Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamping contract basics docs by splitting contracts and enhancing readability #1104

Merged
11 commits merged into from
Jun 5, 2024
12 changes: 12 additions & 0 deletions main/.vitepress/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,18 @@ export default defineConfig({
link: '/guides/zoe/contract-basics',
collapsed: true,
items: [
{
text: 'Greetings Smart Contract',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
text: 'Greetings Smart Contract',
text: 'Hello World Smart Contract',

link: '/guides/zoe/contract-hello',
},
{
text: 'State Smart Contract',
link: '/guides/zoe/contract-state',
},
{
text: 'Access Control Smart Contract',
link: '/guides/zoe/contract-access-control',
},
{
text: 'Complete Contract Walk-Through',
link: '/guides/zoe/contract-walkthru',
Expand Down
18 changes: 18 additions & 0 deletions main/guides/zoe/contract-access-control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@

# Access Control with Objects

In our third smart contract, we will demostrate how to control access to different functions of a smart contract. So far, we have only used `publicFacet` to expose all functions. There is an other facet, called `creatorFacet` that is provided only to the caller who creates the contract instance.
In this smart contract, we
limit the `publicFacet` API to a read-only function `get()`, and use `creatorFacet` API to expose the `set()` method to the caller who creates the contract instanace.

Here is the complete code for `03-access.js` smart contract:

<<< @/../snippets/zoe/src/03-access.js#access-contract

We can write a simple test as below to make sure that trying to `set` using the `publicFacet` throws an exception, but using the `creatorFacet` works:

<<< @/../snippets/zoe/contracts/test-zoe-hello.js#test-access

Note that the `set()` method has no access check inside it. Access control is based on separation of powers between the `publicFacet`, which is expected to be shared widely, and the `creatorFacet`, which is closely held. _We'll discuss this [object capabilities](../js-programming/hardened-js#object-capabilities-ocaps) approach more later._If you're having trouble, check out the [`tut-03-access`](https://github.com/Agoric/dapp-offer-up/tree/tut-03-access) branch in the example repo.

Next, let's look at minting and trading assets with [Zoe](../zoe/).
85 changes: 10 additions & 75 deletions main/guides/zoe/contract-basics.md
Original file line number Diff line number Diff line change
@@ -1,90 +1,25 @@
# Smart Contract Basics

Before we look at how to make a contract such as the one in [the
basic dapp](../getting-started/) in the previous section, let's cover some basics by writing a simple contract that returns a greetings message. We will simply call it _hello-world smart contract_.
This guide is designed to help developers understand how to write smart contracts efficiently and securely. Here, you will find detailed examples that demonstrate different functionalities and use-cases within the context of smart contracts starting from the very basics. To follow along, verify your work, or to get help if you get stuck, you can use the https://github.com/Agoric/dapp-offer-up/ repo as a reference. Each section of the tutorial has an associated branch in the repo to act as a checkpoint.

A contract is defined by a JavaScript module that exports a `start` function. For our hello-world smart contract, the declaration of `start` function looks like this:
## Examples

<<< @/../snippets/zoe/src/01-hello.js#start
We provide three core examples to illustrate how to implement various smart contract functionalities:

For the hello-world smart contract, we will have a simple `greet` function apart from `start` function. The `greet` function takes a string as a parameter (for example, name of the person calling the function) and returns a customized greeting message.
### 1. Greetings Contract
The **Greetings Contract** demonstrates how to create a simple contract that greets the caller. This example is perfect for understanding the basic structure and syntax of smart contracts. The relevant code is available in [`tut-01-hello` branch](https://github.com/Agoric/dapp-offer-up/tree/tut-01-hello).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### 1. Greetings Contract
The **Greetings Contract** demonstrates how to create a simple contract that greets the caller. This example is perfect for understanding the basic structure and syntax of smart contracts. The relevant code is available in [`tut-01-hello` branch](https://github.com/Agoric/dapp-offer-up/tree/tut-01-hello).
### 1. Hello World Contract
The **Hello World Contract** demonstrates how to create a simple contract that greets the caller. This example is perfect for understanding the basic structure and syntax of smart contracts. The relevant code is available in [`tut-01-hello` branch](https://github.com/Agoric/dapp-offer-up/tree/tut-01-hello).


<<< @/../snippets/zoe/src/01-hello.js#greet
### 2. State Contract
The **State Contract** shows how to maintain a state or a variable within a smart contract. This is useful for contracts that need to store data or state across transactions. The relevant code is available in [`tut-02-state` branch](https://github.com/Agoric/dapp-offer-up/tree/tut-02-state).

The `greet` function, along with any other public function, must be made accessible through the `publicFacet` of the contract. The `start` function returns an object with a `publicFacet` property. In the hello-world contract, the `start` function exposes the `greet` function by defining it as a method of the contract's `publicFacet`, as shown below:
### 3. Access-Control Contract
The **Access-Control Contract** provides an example of how to implement access control, ensuring that only authorized users can call certain functions within the smart contract. The relevant code is available in [`tut-03-access` branch](https://github.com/Agoric/dapp-offer-up/tree/tut-03-access).

<<< @/../snippets/zoe/src/01-hello.js#publicFacet

We wrap the value of the `publicFacet` property in a `Far(...)` call to safely expose it as a remote object, accessible from outside the contract. This also gives it a suggestive interface name `Hello` for debugging.
_We'll discuss [Far in more detail](../js-programming/far) later._

Putting it all together:

<<< @/../snippets/zoe/src/01-hello.js#contract

Let us save this code to a file named `01-hello.js` inside `src` directory.
## Using, testing a contract

Agoric contracts are typically tested using the [ava](https://github.com/avajs/ava) framework. The test file begins with an `import @endo/init` to establish a [Hardened JavaScript](../js-programming/hardened-js) environment. We also import `E()` in order to make asynchronous method calls and `test` function from `ava`. _We'll talk more about [using `E()` for async method calls](../js-programming/eventual-send) later._ Following these `import` statements, we write a simple test that validates that the `greet` method works as expected.

Putting it all together:

<<< @/../snippets/zoe/contracts/test-zoe-01-hello.js#test-01-hello

Let's save this code in a file named `test-01-hello.js` in a `test` directory. Both `src` and `test` directories should lie in the same `contract` directory. Let us run the following command to execute the test:

```sh
yarn ava --match="contract greets by name"
```
You should see the following line towards the end of the output:
```
1 test passed
```
Congratulations! You have written and tested your first smart contract. Our next goal is to learn about the state of a smart contract.
Let us get started!

See also:

- [\$LOCKDOWN_OPTIONS for better diagnositcs](https://github.com/Agoric/agoric-sdk/wiki/Developing-with-better-error-diagnostics)
- [\$DEBUG](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#debug)
- [\$TRACK_TURNS](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#track_turns)

## State

Contracts can use ordinary variables and data structures for state.

<<< @/../snippets/zoe/src/02-state.js#startfn

Using `makeRoom` changes the results of the following call to `getRoomCount`:

<<< @/../snippets/zoe/contracts/test-zoe-hello.js#test-state

::: tip Heap state is persistent

Ordinary heap state persists between contract invocations.

We'll discuss more explicit state management for
large numbers of objects (_virtual objects_) and
objects that last across upgrades ([durable objects](./contract-upgrade#durability)) later.

:::

## Access Control with Objects

We can limit the `publicFacet` API to read-only by omitting the `set()` method.

The `creatorFacet` is provided only to the caller who creates the contract instance.

<<< @/../snippets/zoe/src/03-access.js

Trying to `set` using the `publicFacet` throws, but
using the `creatorFacet` works:

<<< @/../snippets/zoe/contracts/test-zoe-hello.js#test-access

Note that the `set()` method has no access check inside it.
Access control is based on separation of powers between
the `publicFacet`, which is expected to be shared widely,
and the `creatorFacet`, which is closely held.
_We'll discuss this [object capabilities](../js-programming/hardened-js#object-capabilities-ocaps) approach more later._

Next, let's look at minting and trading assets with [Zoe](../zoe/).
52 changes: 52 additions & 0 deletions main/guides/zoe/contract-hello.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Greetings Smart Contract
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Greetings Smart Contract
# Hello World Smart Contract


Before we look at how to make a contract such as the one in [the
basic dapp](../getting-started/) in the previous section, let's cover some basics by writing a simple contract that returns a greetings message. We will simply call it _hello-world smart contract_.

A contract is defined by a JavaScript module that exports a `start` function. For our hello-world smart contract, the declaration of `start` function looks like this:

<<< @/../snippets/zoe/src/01-hello.js#start

For the hello-world smart contract, we will have a simple `greet` function apart from `start` function. The `greet` function takes a string as a parameter (for example, name of the person calling the function) and returns a customized greeting message.

<<< @/../snippets/zoe/src/01-hello.js#greet

The `greet` function, along with any other public function, must be made accessible through the `publicFacet` of the contract. The `start` function returns an object with a `publicFacet` property. In the hello-world contract, the `start` function exposes the `greet` function by defining it as a method of the contract's `publicFacet`, as shown below:

<<< @/../snippets/zoe/src/01-hello.js#publicFacet

We wrap the value of the `publicFacet` property in a `Far(...)` call to safely expose it as a remote object, accessible from outside the contract. This also gives it a suggestive interface name `Hello` for debugging.
_We'll discuss [Far in more detail](../js-programming/far) later._

Putting it all together:

<<< @/../snippets/zoe/src/01-hello.js#contract

Let us save this code to a file named `01-hello.js` inside `src` directory.

## Using, testing a contract

Agoric contracts are typically tested using the [ava](https://github.com/avajs/ava) framework. The test file begins with an `import @endo/init` to establish a [Hardened JavaScript](../js-programming/hardened-js) environment. We also import `E()` in order to make asynchronous method calls and `test` function from `ava`. _We'll talk more about [using `E()` for async method calls](../js-programming/eventual-send) later._ Following these `import` statements, we write a simple test that validates that the `greet` method works as expected.

Putting it all together:

<<< @/../snippets/zoe/contracts/test-zoe-01-hello.js#test-01-hello

Let's save this code in a file named `test-01-hello.js` in a `test` directory. Both `src` and `test` directories should lie in the same `contract` directory. Let us run the following command to execute the test:

```sh
yarn ava --match="contract greets by name"
```
You should see the following line towards the end of the output:
```
1 test passed
```
Congratulations! You have written and tested your first smart contract. Our next goal is to learn about the state of a smart contract.

If you're having trouble, check out the [`tut-01-hello`](https://github.com/Agoric/dapp-offer-up/tree/tut-01-hello) branch in the example repo.

See also:

- [\$LOCKDOWN_OPTIONS for better diagnositcs](https://github.com/Agoric/agoric-sdk/wiki/Developing-with-better-error-diagnostics)
- [\$DEBUG](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#debug)
- [\$TRACK_TURNS](https://github.com/Agoric/agoric-sdk/blob/master/docs/env.md#track_turns)
35 changes: 35 additions & 0 deletions main/guides/zoe/contract-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# State Smart Contract

In our first `greetings` smart contract, we created a `greet` function and exposed it using `publicFacet` so that it can be remotely called. However, if you notice, there is no state in our smart contract that is preserved between calls. Contracts can use ordinary variables and data structures for state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In our first `greetings` smart contract, we created a `greet` function and exposed it using `publicFacet` so that it can be remotely called. However, if you notice, there is no state in our smart contract that is preserved between calls. Contracts can use ordinary variables and data structures for state.
In our first `hello-world` smart contract, we created a `greet` function and exposed it using `publicFacet` so that it can be remotely called. However, if you notice, there is no state in our smart contract that is preserved between calls. Contracts can use ordinary variables and data structures for state.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.


In our second example smart contract, we will manage a list of rooms. We want everyone with access to `publicFacet` to be able to create a new room, and also get current count of rooms. We maintain state using `Map` data structure as below:

<<< @/../snippets/zoe/src/02-state.js#rooms-map

Anyone can add new rooms by making a call to `makeRoom` which is defined as:

<<< @/../snippets/zoe/src/02-state.js#makeRoom

Using `makeRoom` creates a new room, exposing these functions to be invoked on the newly added room, `getId`, `incr`, and `decr`. As you can see this pattern follows the `Object Capability model`, as whoever receives the room by invoking `makeRoom`, will now have access to these three methods. Following this, `rooms.set(id, room)` adds the newly created room, into the contract's map state variable. A call to `getRoomCount` function returns the number of rooms in this map.

<<< @/../snippets/zoe/src/02-state.js#getRoomCount

Putting it all together:

<<< @/../snippets/zoe/src/02-state.js#state-contract

Let us save this contract as `02-state.js` and creating a simple test to validate its functionality:

<<< @/../snippets/zoe/contracts/test-zoe-hello.js#test-state

This test asserts that in the beginning the number of rooms is zero and after a call to `makeRoom`, the number of rooms changes to one. If you're having trouble, check out the [`tut-02-state`](https://github.com/Agoric/dapp-offer-up/tree/tut-02-state) branch in the example repo.

::: tip Heap state is persistent

Ordinary heap state persists between contract invocations.

We'll discuss more explicit state management for
large numbers of objects (_virtual objects_) and
objects that last across upgrades ([durable objects](./contract-upgrade#durability)) later.

:::
8 changes: 8 additions & 0 deletions snippets/zoe/src/02-state.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// #region state-contract
import { Far } from '@endo/far';

// #region startfn
// #region heap-state
export const start = () => {
// #region rooms-map
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good. Just a thought here, if we're going to add comments here, maybe we should simply explain the purpose of the map here? i think the contracts can use some basic comments for the map. Super basic, but it may not be clear to the absolute beginner here.

const rooms = new Map();
// #endregion rooms-map

// #region getRoomCount
const getRoomCount = () => rooms.size;
// #endregion getRoomCount
// #region makeRoom
const makeRoom = id => {
let count = 0;
const room = Far('Room', {
Expand All @@ -16,10 +22,12 @@ export const start = () => {
rooms.set(id, room);
return room;
};
// #endregion makeRoom
// #endregion heap-state

return {
publicFacet: Far('RoomMaker', { getRoomCount, makeRoom }),
};
};
// #endregion startfn
// #endregion state-contract
2 changes: 2 additions & 0 deletions snippets/zoe/src/03-access.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// #region access-contract
import { Far } from '@endo/far';

export const start = () => {
Expand All @@ -10,3 +11,4 @@ export const start = () => {
creatorFacet: Far('ValueCell', { get, set }),
};
};
// #endregion access-contract
Loading