This workshop includes several activities:
- a scavenger hunt through several AssemblyScript projects to get you quickly oriented
- a debugging challenge to fix a few failing unit tests with broken contracts
- a development lifecycle challenge to guide you through NEAR platform tools for testing
- a design challenge to create new contracts and related models that satisfy a set of requirements
Prerequisites
If you're already comfortable with TypeScript then reading AssemblyScript should be a breeze. If you're coming from JavaScript, you'll have to get your head around static types
and code compilation (since JavaScript has dynamic types and is an interpreted language) but reading through the samples here should not be too difficult. If you have no programming experience then this workshop will be challenging for you -- find someone to pair with so you can stay motivated and productive.
Companion Presentation
This hands-on workshop is paired with a presentation called Hello AssemblyScript which helps set the context for this work and clarifies a few key mental models.
Before diving into this workshop, have a look at the slides linked above.
Orientation
If you're totally new to NEAR you can start here with a high level overview.
NEAR Protocol (aka "NEAR") is a public peer-to-peer key-value database. Public as in open to everyone for reading anything and writing what you are allowed to. Write permissions are defined by access keys so only the owner of the data can give permissions to modify data they own.
Manipulation of data is controlled by stored procedures (smart contracts) executing as WebAssembly (Wasm) which means they can be implemented in any programming language that compiles to Wasm (ie. Rust, AssemblyScript, Kotlin, C, C++, Nim, Zig, etc). Currently only the first two languages are supported for development on the NEAR platform.
We will not be building dApps around any of these contracts since our focus is on learning AssemblyScript. Almost all of the contract code presented in this workshop is also running on live examples where you will also find the frontend code that relies on these contracts.
- clone this repo locally
- run
yarn
to install dependencies
yarn build
builds all contractsyarn build <contract name>
builds a specific contractyarn clean
deletes theout
folder containing built contracts
yarn test
runs unit tests for all contracts
See package.json
for more detail about these and other scripts.
You will find the following folder structure in this repository under the assembly
folder.
assembly
│
├── A.sample-projects
│ ├── 01.greeting
│ ├── 02.wallet-example
│ ├── 03.counter
│ ├── 04.token-contract
│ ├── 05.guestbook
│ ├── 06.chat
│ └── 07.cross-contract-calls
│
├── B.debugging-challenge
│ ├── 01.broken-greeting
│ ├── 03.broken-counter
│ └── 05.broken-guestbook
│
└── C.design-challenge
├── 01.PinkyPromise
├── 02.OpenPetition
└── 03.BucketList
You can filter tests using the following syntax
yarn test -f <contract name>.unit
For example:
yarn test -f greeting.unit
or yarn test -f counter.unit
Note the projects are ordered by increasing complexity so lower numbers roughly implies "easier to understand".
Instructions
- Scan the items to scavenge (ie. to find) in the lists below
- For the most interesting ones, look for them in the folder called
A.sample-projects
All of them appear in contract files (
main.ts
andmodel.ts
) or their unit tests (*.unit.spec.ts
)Keep your own notes. Time permitting, we will share and discuss your findings and answer questions at the end of the activity.
First Steps
Note, some of these may only take you a few seconds to complete so don't overthink things. This activity is about massive exposure to several examples of smart contracts written using AssemblyScript for the NEAR platform.
Find examples of the following:
- a contract method that takes no parameters
- a contract method that takes one parameter
- a model passed to a contract method
Models Organize and Serialize Data
NEAR Protocol stores data in a key-value store called Storage
. For developer convenience when building more complex dApps, Storage
is also wrapped by a few other persistent collections including PersistentVector
, PersistentSet
, PersistentMap
and PersistentDeque
.
Reading and writing to Storage
requires specifying the type of data to store, whether string
, number
or binary
.
All custom data types (ie. custom data models) must be decorated with the @nearBindgen
decorator so that the system knows to serialize when storing and deserialize when retrieving them.
Find examples of the following:
- use of
Storage
to read and / or write data from blockchain storage - use of
PersistentVector
to store contract data in an array-like data structure - use of
PersistentMap
to store contract data in a map-like data structure - use of
PersistentDeque
to store contract data in a queue-like data structure - use of
PersistentSet
to store contract data in a set-like data structure - an example that includes the
@nearBindgen
decorator - use of the
getPrimitive<T>()
method on theStorage
class - use of the
getString()
method on theStorage
class - use of the
setString()
method on theStorage
class
Contracts Expose an Interface
NEAR Protocol accounts are initially created without an associated contract. Each account can have a maximum of 1 contract deployed to its storage (although a contract may be deployed to many accounts).
Each account maintains it's own copy of a contract code as well as any state storage consumed by the contract during normal operation. You can read more about accounts on the NEAR platform here.
Find examples of the following:
- use of
context.sender
which represents the account that signed the current transaction - an example of a unit test where the test explicitly sets the
signer_account_id
to controlcontext.sender
- use of
context.contractName
which represents the account on which the contract lives - an example of a unit test where the test explicitly sets the
current_account_id
to controlcontext.contractName
- use of
context.attachedDeposit
to capture the tokens attached to a contract function call - an example of a unit test where the test explicitly sets the
attached_deposit
to controlcontext.attachedDeposit
Validation
- use of
assert()
in a contract method to guarantee that some value meets the necessary criteria
Instructions
Debug as many of the following problems as you can.
- They are ordered by increasing difficulty.
- All of the related files appear in the folder called
B.debugging-challenge
- None of the tests were altered. Only the
main.ts
contract file and / or themodel.ts
model file were changed from the original to create the problems you see in these failing tests or failures to compile the code.- You know you're finished when the tests pass
Keep your own notes. Time permitting, we will share and discuss your findings and answer questions at the end of the activity.
- run
yarn test -f broken-greeting
and fix the failing unit tests
Reveal hints
- Run this command in the terminal to reveal the needed fixes
git diff --no-index assembly/B.debugging-challenge/01.broken-greeting/main.ts assembly/A.sample-projects/01.greeting/assembly/index.ts
You know you're finished when the unit tests are all passing and you see something like this:
[Describe]: 01. Greeting
[Success]: ✔ should respond to showYouKnow()
[Success]: ✔ should respond to sayHello()
[Success]: ✔ should respond to sayMyName()
[Success]: ✔ should respond to saveMyName()
[Success]: ✔ should respond to saveMyMessage()
[Success]: ✔ should respond to getAllMessages()
[File]: B.debugging-challenge/01.broken-greeting/__tests__/greeting.spec.ts
[Groups]: 2 pass, 2 total
[Result]: ✔ PASS
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
[Summary]: 6 pass, 0 fail, 6 total
[Time]: 13.597ms
- run
yarn test -f broken-counter
and fix the failing unit tests
Reveal hints
- One error is preventing the code from compiling so none of the other tests are running. solve the compiler error first so you can see the failing tests
- Run this command in the terminal to reveal the needed fixes
git diff --no-index assembly/B.debugging-challenge/03.broken-counter/main.ts assembly/A.sample-projects/03.counter/assembly/index.ts
- run
yarn test -f broken-guestbook
and fix the failing unit tests
Note, in this challenge, some of the issues are preventing the code from the compiling in the first place, so the tests aren't even running.
Reveal hints
@nearBindgen
is a decorator added to custom models so they can be serialized and stored on chainPersistentVector
require a type parameter which will often be the model you are trying to store on chaincontext.sender
git diff --no-index assembly/A.sample-projects/05.guestbook/main.ts assembly/B.debugging-challenge/05.broken-guestbook/main.ts
git diff --no-index assembly/B.debugging-challenge/05.broken-guestbook/model.ts assembly/A.sample-projects/05.guestbook/assembly/model.ts
Instructions
Open the challenge linked in this section
- All related code is located in
A.sample-projects/01.greeting
Keep your own notes. Time permitting, we will share and discuss your findings and answer questions at the end of the activity.
Let's explore the contract development lifecycle on NEAR Protocol.
We will start with a simple but instructive contract design and explore the contract interface (hint: you've seen it already), build the contract (with a quick peek at the WebAssembly text format), and finally test the contract using unit tests, simulation tests and integration tests.
As we move from end-to-in in this process, focus on the parts that are most interesting to you and feel free to skip the parts that are boring or maybe overwhelming. Come back anytime.
Open the Development Lifecycle challenge
Instructions
- Choose one of the following projects and write the model(s) and contract(s) that satisfy the following requirements.
- Write unit tests for all models and contracts.
Keep your own notes. Time permitting, we will share and discuss your findings and answer questions at the end of the activity.
Important Note: The design guidelines below are almost certaginly incomplete. They are intended to inspire you to consider the design challenge on your own or with your pair or team. Feel free to run with these ideas and do not be constrained by what you see here.
(inspired by a 2019 hackathon project)
PinkyPromise is a system for recording promises on the blockchain for all to see, forever and ever. A promise is a piece of text that is made from
someone to
someone (possibly themselves). A promise may eventually be marked as kept
or broken
by the owner of the to
account.
Models
PinkyPromise
- Collects a commitment (as string) between two accounts (as strings). Consider whether to use
Storage
directly (our on-chain key-value store) or one of the persistent collections that wrapsStorage
to mimic a Vector, Map, Queue or Set.
- Collects a commitment (as string) between two accounts (as strings). Consider whether to use
Contracts
main
makePromise(to: string, statement: string)
(inspired by Covid-19)
BucketList is a system that records things we wish we all could do as soon as it's safe to go back outside.
Models
Activity
represents something we want to dodescription
asstring
cost
asu8
(let's keep it small since these are frugal times)friends
asPersistentVector<string>
of account names of our friends, if we have any
Contracts
main
add(item: string, friends: string[], cost: u8): bool
list(): Activity[]
(inspired by an internal hackathon project)
OpenPetition is a system for managing the creation and support of petitions (ie. Change.org for blockchain). Models
Petition
- Collects signatures (
context.sender
) in aPersistentVector<string>
for anyone that calls the main contract'ssign
method, passing in the petition identifier. - The Petition model should include Petition metadata like
title
asstring
body
asstring
andfunding
asu128
- The Petition model should include methods like
sign(): bool
signWithFunds(amount: u128 = 0): bool
- Collects signatures (
Contracts
main
sign(petitionId: string): bool
allows thecontext.sender
to sign the petitionlist(): Array<string>
returns a list of petition identifiersshow(petitionId: string): Petition
returns the details of a petitioncontract.petitions
could be the collection of petitions stored as aPersistentMap<string, Petition>
where the key is petition identifier and the value is the petition instance
Stretch Goals
- Consider how you would structure this project if each petition were its own contract instead of a model on a single contract. What could the benefits of this be?
If you find yourself stuck with any of this, feel free to reach out to us via the following links: