-
Notifications
You must be signed in to change notification settings - Fork 38
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
Changes from 10 commits
b7d6a6e
c9dbe86
ebd667b
f478e51
c74392d
4d07adf
90875df
d38412c
72ddf33
1e26ba3
ca32303
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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/). |
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). | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
|
||||||||||
<<< @/../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/). |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,52 @@ | ||||||
# Greetings Smart Contract | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
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) |
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||||||
|
||||||
::: |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', { | ||
|
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.