Skip to content

Commit

Permalink
feat: yield-resume docs (#2230)
Browse files Browse the repository at this point in the history
* feat: yield-resume docs

* fix: links

* improved text
  • Loading branch information
gagdiez authored Sep 19, 2024
1 parent a6d8c7e commit 3eabb0b
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 240 deletions.
8 changes: 8 additions & 0 deletions blog/2024-05-30.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ hide_table_of_contents: true

<!-- truncate -->

:::tip 🎉 September Update 🎉

We now have an [example of how to use `yield` and `resume`](https://github.com/near-examples/yield-resume) in your contracts. Check it out!

There is also a new documentation page on [Yield and Resume](/build/smart-contracts/anatomy/yield-resume) that explains how to use this feature

:::

## The problem of waiting
Currently, smart contracts have no way to wait for an external event to happen. This can be a problem when the contract relies on an external service to provide a result.

Expand Down
105 changes: 105 additions & 0 deletions docs/2.build/2.smart-contracts/anatomy/yield-resume.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
---
id: yield-resume
title: Yield and Resume
---
import {CodeTabs, Language, Github} from '@site/src/components/codetabs'

NEAR smart contracts can **yield** execution, until an **external** service **resumes** them. In practice, the contract yields a **cross-contract call** to itself, until an external service executes a function and the contract decides to resume.

This is a powerful feature that allows contracts to wait for external events, such as a response from an oracle, before continuing execution (read our [blog post](/blog/yield-resume)!).

:::info

Contract can wait for 200 blocks - around 4 minutes - after which the yielded function will execute, receiving a "timeout error" as input

:::

---

## Yielding a Promise

Let's look at an example that takes a prompt from a user (e.g. "What is 2+2"), and yields the execution until an external service provides a response.

<CodeTabs>
<Language value="rust" language="rust">
<Github fname="lib.rs"
url="https://github.com/near-examples/yield-resume/blob/main/contract/src/lib.rs"
start="43" end="70" />
</Language>
</CodeTabs>

#### Creating a Yielded Promise
In the example above, we are creating a [`Promise`](./crosscontract.md#promises) to call the contract's function `return_external_response`.

Notice that we create the `Promise` using `env::promise_yield_create`, which will create an **identifier** for the yielded promise in the `YIELD_REGISTER`.

#### Retrieving the Yielded Promise ID
We read the `YIELD_REGISTER` to retrieve the `ID` of our yielded promise. We store the `yield_id` and the user's `prompt` so the external service query them (the contract exposes has a function to list all requests).

#### Returning the Promise
Finally, we return the `Promise`, which will **not execute immediately**, but will be **yielded** until the external service provides a response.

<details>

<summary> What is that `self.request_id` in the code? </summary>

The `self.request_id` is an internal unique identifier that we use to keep track of stored requests. This way, we can delete the request once the external service provides a response (or the waiting times out)

Since we only use it to simplify the process of keeping track of the requests, you can remove it if you have a different way of tracking requests (e.g. an indexer)

</details>

---

## Signaling the Resume

The `env::promise_yield_resume` function allows us to signal which yielded promise should execute, as well as which parameters to pass to the resumed function.

<CodeTabs>
<Language value="rust" language="rust">
<Github fname="lib.rs"
url="https://github.com/near-examples/yield-resume/blob/main/contract/src/lib.rs"
start="72" end="75" />
</Language>
</CodeTabs>

In the example above, the `respond` function would be called by an external service, passing which promise should be resume (`yield_id`), and the response to the prompt.

:::warning Gatekeeping the Resume

Since the function used to signal the resume is public, developers must make sure to guard it properly to avoid unwanted calls. This can be done by simply checking the caller of the function

:::

---

## The Function that Resumes

The function being resumed will have access to all parameters passed to it, including those passed during the yield creation, or the external service response.

<CodeTabs>
<Language value="rust" language="rust">
<Github fname="lib.rs"
url="https://github.com/near-examples/yield-resume/blob/main/contract/src/lib.rs"
start="77" end="89" />
</Language>
</CodeTabs>

In the example above, the `return_external_response` receives two parameters:

1. A `request_id` - passed on [creation](#creating-a-yielded-promise) - which is used to remove the request from the state
2. A `response` - passed when [signaling to resume](#signaling-the-resume) - which contains the external response, or a `PromiseError` if the contract timed out while waiting

:::tip There's plenty of time

The contract will be able to wait for 200 blocks - around 4 minutes - before timing out

:::

:::info

Notice that, in this particular example, we choose to return a value both if there is a response or a time out

The reason to not raise an error, is because we are changing the state (removing the request in line `#7`), and raising an error would revert this state change

:::
16 changes: 9 additions & 7 deletions docs/3.tutorials/auction/0-intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,17 @@ We will be using the tool [NEAR CLI](../../4.tools/cli.md) to interact with the
This series will touch on different level of the NEAR tech stack. Each section will be independent of the previous one, so feel free to jump into the section that interests you the most.

#### 1. Smart Contract
1. [The Auction Contract](./1-basic.md): We cover a simple auction smart contract
2. [Updating and Locking a Contract](./2-locking.md): Discover what it means to lock a contract
3. Giving an NFT to the Winner (soon) : Give the highest bidder an NFT to signal their win
4. Integrating Fungible Tokens (soon) : Allow people to use fungible tokens to bid (e.g. stable coins)
1. [The Auction Contract](./1.1-basic.md): We cover a simple auction smart contract
2. [Testing the Contract](./1.2-testing.md): Learn how to test your contract in a realistic environment
3. [Deploying the Contract](./1.3-deploy.md): Deploy your contract to the NEAR blockchain
4. Updating and Locking a Contract (soon): Discover what it means to lock a contract
5. Giving an NFT to the Winner (soon) : Give the highest bidder an NFT to signal their win
6. Integrating Fungible Tokens (soon) : Allow people to use fungible tokens to bid (e.g. stable coins)

#### 2. Frontend

1. Creating the frontend : Lets learn how to connect a frontend with your smart contract
2. Easily query on-chain data : Use open APIs to keep track of the users and their bidding price
1. Creating the frontend (soon): Lets learn how to connect a frontend with your smart contract
2. Easily query on-chain data (soon): Use open APIs to keep track of the users and their bidding price

#### 3. Factory
1. Creating a factory: Allow users to easily deploy and initialize their own auction contracts
Expand All @@ -101,7 +103,7 @@ This series will touch on different level of the NEAR tech stack. Each section w

## Next steps

Ready to start? Let's jump to the [The Auction Contract](./1-basic.md) and begin your learning journey!
Ready to start? Let's jump to the [The Auction Contract](./1.1-basic.md) and begin your learning journey!

:::note Versioning for this article

Expand Down
4 changes: 2 additions & 2 deletions website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ const config = {
themeConfig: {
image: 'docs/assets/welcome-pages/protocol.png',
announcementBar: {
id: 'id-0006',
id: 'id-0007',
content:
'New blog post: <a href="/blog/2024-08-13-pagoda-services">Future of Pagoda Services</a>',
'🎉 New Documentation on Smart Contracts: <a href="/build/smart-contracts/anatomy/yield-resume">Yield and Resume</a> 🎉',
backgroundColor: '#fafbfc',
textColor: '#333',
isCloseable: true,
Expand Down
14 changes: 7 additions & 7 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"crowdin:download": "crowdin download"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "^3.4.0",
"@docusaurus/types": "^3.4.0",
"@docusaurus/module-type-aliases": "^3.5.2",
"@docusaurus/types": "^3.5.2",
"@types/react": "^18.2.42",
"buffer": "^6.0.3",
"concurrently": "^5.3.0",
Expand All @@ -32,11 +32,11 @@
},
"dependencies": {
"@crowdin/cli": "^4.1.1",
"@docusaurus/core": "^3.4.0",
"@docusaurus/plugin-ideal-image": "^3.4.0",
"@docusaurus/plugin-sitemap": "^3.4.0",
"@docusaurus/preset-classic": "^3.4.0",
"@docusaurus/theme-mermaid": "^3.4.0",
"@docusaurus/core": "^3.5.2",
"@docusaurus/plugin-ideal-image": "^3.5.2",
"@docusaurus/plugin-sitemap": "^3.5.2",
"@docusaurus/preset-classic": "^3.5.2",
"@docusaurus/theme-mermaid": "^3.5.2",
"@feelback/react": "^0.3.4",
"@near-wallet-selector/core": "^8.5.1",
"@near-wallet-selector/here-wallet": "^8.5.1",
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ const sidebar = {
"build/smart-contracts/anatomy/environment",
"build/smart-contracts/anatomy/actions",
"build/smart-contracts/anatomy/crosscontract",
"build/smart-contracts/anatomy/yield-resume",
"build/smart-contracts/security/checklist",
{
"type": "html",
Expand Down
10 changes: 4 additions & 6 deletions website/src/theme/DocItem/Layout/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import clsx from 'clsx';
import { useWindowSize } from '@docusaurus/theme-common';
import { useDoc } from '@docusaurus/theme-common/internal';
import { useDoc } from '@docusaurus/plugin-content-docs/client';
import DocItemPaginator from '@theme/DocItem/Paginator';
import DocVersionBanner from '@theme/DocVersionBanner';
import DocVersionBadge from '@theme/DocVersionBadge';
Expand All @@ -10,7 +10,7 @@ import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
import DocItemContent from '@theme/DocItem/Content';
import DocBreadcrumbs from '@theme/DocBreadcrumbs';
import Unlisted from '@theme/Unlisted';
import ContentVisibility from '@theme/ContentVisibility';
import styles from './styles.module.css';

import { HelpComponent } from '../../../components/helpcomponent';
Expand All @@ -37,13 +37,11 @@ function useDocTOC() {
}
export default function DocItemLayout({ children }) {
const docTOC = useDocTOC();
const {
metadata: { unlisted },
} = useDoc();
const { metadata } = useDoc();
return (
<div className="row">
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
{unlisted && <Unlisted />}
<ContentVisibility metadata={metadata} />
<DocVersionBanner />
<div className={styles.docItemContainer}>
<article>
Expand Down
Loading

0 comments on commit 3eabb0b

Please sign in to comment.