-
Notifications
You must be signed in to change notification settings - Fork 130
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
📝 Creating a draft for upcoming cross-contract tutorial #857
base: main
Are you sure you want to change the base?
Changes from all commits
7aa4b14
0976297
1519183
d099633
fb44ec5
3a1ce85
89090e2
e1ca418
321394b
4ca59b3
74d5b3e
0b32bae
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,223 @@ | ||
--- | ||
title: 'Tutorial 12: Cross Contract Calls' | ||
hide_title: true | ||
sidebar_label: 'Tutorial 12: Cross Contract Calls' | ||
description: Guided steps to learn about the ways to interact with a smart contract from another smart contract. | ||
keywords: | ||
- smart contracts | ||
- zkapps | ||
- zero knowledge proof programming | ||
- zk proof | ||
- zk | ||
- cross contract call | ||
- mina | ||
--- | ||
|
||
:::info | ||
|
||
Please note that zkApp programmability is not yet available on Mina Mainnet, but zkApps can now be deployed to Berkeley Testnet. | ||
|
||
::: | ||
|
||
|
||
# Tutorial 12: Cross Contract Calls | ||
|
||
In this tutorial, you learn how smart contracts on a blockchain can interact by calling functions in each other's code, enabling building modular and complex decentralized applications. | ||
|
||
Cross contract calls allow smart contracts on a blockchain to interact with each other. This enables the building of complex decentralized applications (Dapps) from multiple modular components. In a cross-contract call, a function in one smart contract can call a function in another smart contract to leverage existing code and functionality. | ||
|
||
This tutorial demonstrates passing data between contracts, handling events, and returning values when contracts call each other. | ||
|
||
The full example code is provided in the [12-cross-contract-calls/src/](https://github.com/o1-labs/docs2/tree/main/examples/zkapps/12-cross-contract-calls/src) example files. | ||
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. where are the example files? are they in this PR? 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. The example files are added in initial commit in example folder. 7aa4b14#diff-40a308f55dcc34f6c479b6f2954062c4ef5bac47ad2928204dc9951e78eea6bc 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. Once this PR get's merges the link will start working. 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. I didn't recognize all three smart contracts as being present, since in a single file. Thanks @iregina for testing the code! |
||
|
||
## Prerequisites | ||
|
||
- Make sure you have the latest version of the zkApp CLI installed: | ||
|
||
```sh | ||
$ npm install -g zkapp-cli | ||
``` | ||
|
||
- Ensure your environment meets the [Prerequisites](/zkapps/tutorials#prerequisites) for zkApp Developer Tutorials. | ||
|
||
This tutorial has been tested with: | ||
|
||
- [zkApp CLI](https://github.com/o1-labs/zkapp-cli) version `0.16.0` | ||
- [o1js](https://www.npmjs.com/package/o1js) version `0.16.2` | ||
|
||
## Create a new project | ||
|
||
Now that you have the tooling installed, you can start building your application. | ||
|
||
1. Create or change to a directory where you have write privileges. | ||
1. Now, create a project using the `zk project` command: | ||
|
||
```sh | ||
$ zk project 12-cross-contract-calls | ||
``` | ||
|
||
As you learned in earlier tutorials, the `zk project` command creates the `12-cross-contract-calls` directory that contains the scaffolding for your project. | ||
|
||
1. Change into the `12-cross-contract-calls` directory. | ||
|
||
Like all projects, you run `zk` commands from the root of the `12-cross-contract-calls` directory as you work in the `src` directory on files that contain the TypeScript code for the smart contract. | ||
|
||
Each time you make updates, then build or deploy, the TypeScript code is compiled into JavaScript in the `build` directory. | ||
|
||
### Prepare the project | ||
|
||
Like earlier tutorials, you can prepare your project by deleting the default files that come with the new project and creating a smart contract called `Composability`. | ||
|
||
## Write the ZkProgram | ||
|
||
Now, the fun part! Write your smart contract in the `src/Composability.ts` file. | ||
|
||
A final version of the smart contract is provided in the [Composability.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/12-cross-contract-calls/src/Composability.ts) example file. | ||
|
||
|
||
### Copy the example | ||
|
||
Use the existing code in the [Composability.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/12-cross-contract-calls/src/Composability.ts) example file. | ||
|
||
1. First, open the [Composability.ts](https://github.com/o1-labs/docs2/blob/main/examples/zkapps/12-cross-contract-calls/src/Composability.ts) example file. | ||
|
||
1. Copy the file's entire contents into your project `src/Composability.ts` file. | ||
|
||
### Imports and Incrementer smart contract | ||
|
||
First, bring in imports and set up the first smart contract `Incrementer`. | ||
|
||
```typescript | ||
import { | ||
Field, | ||
method, | ||
Mina, | ||
AccountUpdate, | ||
PrivateKey, | ||
SmartContract, | ||
state, | ||
State, | ||
} from 'o1js'; | ||
|
||
// Contract which adds 1 to a number | ||
class Incrementer extends SmartContract { | ||
@method increment(x: Field): Field { | ||
return x.add(1); | ||
} | ||
} | ||
|
||
``` | ||
This Incrementer contract adds `1` to the `Field` argument, which is passed. | ||
|
||
### Adder smart contract | ||
|
||
Now bring in the second contract `Adder` that returns the addition of two numbers and adds `1` to the result. | ||
The addition of `1` to the result is outsourced to the `Incrementer` smart contract by creating a new object by passing its address. | ||
|
||
```typescript | ||
// Contract which add two numbers and plus 1 to their sum and return the result | ||
// Incrementing by one is outsourced to Incrementer contract | ||
class Adder extends SmartContract { | ||
@method addPlus1(x: Field, y: Field): Field { | ||
let sum = x.add(y); | ||
let incrementer = new Incrementer(incrementerAddress); | ||
return incrementer.increment(sum); | ||
} | ||
} | ||
|
||
``` | ||
|
||
### Caller smart contract | ||
|
||
The final smart contract `Caller` calls the `addPlus1()` method of the `Adder` smart contract and emits the stored result that is returned. | ||
|
||
```typescript | ||
// Contract which calls the Adder contract, stores the result on chain & emits an event | ||
class Caller extends SmartContract { | ||
@state(Field) sum = State<Field>(); | ||
events = { sum: Field }; | ||
|
||
@method callAddAndEmit(x: Field, y: Field) { | ||
let adder = new Adder(adderAddress); | ||
let sum = adder.addPlus1(x, y); | ||
this.emitEvent('sum', sum); | ||
this.sum.set(sum); | ||
} | ||
} | ||
|
||
``` | ||
|
||
The code to interact with the smart contract: | ||
|
||
```typescript | ||
const doProofs = true; | ||
|
||
// Deploy and interact with smart contract locally | ||
let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); | ||
Mina.setActiveInstance(Local); | ||
|
||
// Test account that pays all the fees, and puts additional funds into the zkapp | ||
let feePayerKey = Local.testAccounts[0].privateKey; | ||
let feePayer = Local.testAccounts[0].publicKey; | ||
|
||
// The Incrementer contract's address | ||
let incrementerKey = PrivateKey.random(); | ||
let incrementerAddress = incrementerKey.toPublicKey(); | ||
|
||
// The Adder contract's address | ||
let adderKey = PrivateKey.random(); | ||
let adderAddress = adderKey.toPublicKey(); | ||
|
||
// The Caller contract's address | ||
let callerKey = PrivateKey.random(); | ||
let callerAddress = callerKey.toPublicKey(); | ||
|
||
let callerZkapp = new Caller(callerAddress); | ||
let adderZkapp = new Adder(adderAddress); | ||
let incrementerZkapp = new Incrementer(incrementerAddress); | ||
|
||
// When doProofs is true, compile contracts to generate prover and verifier keys | ||
if (doProofs) { | ||
console.log('compile (incrementer)'); | ||
await Incrementer.compile(); | ||
console.log('compile (adder)'); | ||
await Adder.compile(); | ||
console.log('compile (caller)'); | ||
await Caller.compile(); | ||
} | ||
|
||
// Create transaction to deploy contracts | ||
console.log('deploy'); | ||
let tx = await Mina.transaction(feePayer, () => { | ||
AccountUpdate.fundNewAccount(feePayer, 3); | ||
callerZkapp.deploy(); | ||
adderZkapp.deploy(); | ||
incrementerZkapp.deploy(); | ||
}); | ||
// Sign all four account updates by | ||
// passing the corresponding private key for the mentioned public addresses | ||
await tx.sign([feePayerKey, callerKey, adderKey, incrementerKey]).send(); | ||
|
||
// Create transaction to interact with the callAddAndEmit method of Caller contract | ||
console.log('call interaction'); | ||
tx = await Mina.transaction(feePayer, () => { | ||
zkapp.callAddAndEmit(Field(5), Field(6)); | ||
}); | ||
console.log('proving (3 proofs.. can take a bit!)'); | ||
await tx.prove(); | ||
console.log(tx.toPretty()); | ||
await tx.sign([feePayerKey]).send(); | ||
|
||
console.log('state: ' + zkapp.sum.get()); | ||
|
||
``` | ||
|
||
In this example, you spin up the Mina chain and deploy all three smart contracts locally. | ||
|
||
Then you call the `callAddAndEmit` method from the `Caller` smart contract, which takes two numbers as arguments, then call the `Adder` smart contract, which adds these two numbers and passes the result in the `Incrementer` smart contract, which increments the result by 1. | ||
|
||
When run successfully, the state is equal to 12. | ||
|
||
## Conclusion | ||
|
||
Congratulations! You have learned how to implement cross contract calls, allowing smart contracts to interact and unlocking new possibilities for modular blockchain applications. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
module.exports = { | ||
root: true, | ||
env: { | ||
browser: true, | ||
node: true, | ||
jest: true, | ||
}, | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/eslint-recommended', | ||
'plugin:@typescript-eslint/recommended', | ||
'plugin:o1js/recommended', | ||
], | ||
parser: '@typescript-eslint/parser', | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
}, | ||
plugins: ['@typescript-eslint', 'o1js'], | ||
rules: { | ||
'no-constant-condition': 'off', | ||
'prefer-const': 'off', | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Use line endings appropriate for the system. This prevents Git from | ||
# complaining about project template line endings when committing on Windows. | ||
* text=auto eol=lf |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# | ||
# ci.yml | ||
# | ||
# Run tests for all pushed commits and opened pull requests on Github. | ||
# | ||
|
||
name: CI | ||
on: [push, pull_request] | ||
jobs: | ||
test: | ||
runs-on: ubuntu-latest | ||
timeout-minutes: 10 | ||
steps: | ||
- name: Set up NodeJS | ||
uses: actions/setup-node@v4 | ||
with: | ||
node-version: '16' | ||
- name: Git checkout | ||
uses: actions/checkout@v4 | ||
- name: NPM ci, build, & test | ||
run: | | ||
npm install | ||
npm run build --if-present | ||
npm test | ||
env: | ||
CI: true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# NodeJS | ||
node_modules | ||
build | ||
coverage | ||
|
||
# Editor | ||
.vscode | ||
|
||
# System | ||
.DS_Store | ||
|
||
# Never commit keys to Git! | ||
keys |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
#!/bin/sh | ||
. "$(dirname "$0")/_/husky.sh" | ||
|
||
npx lint-staged |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# TypeScript files | ||
src | ||
|
||
# Editor | ||
.vscode | ||
|
||
# System | ||
.DS_Store | ||
|
||
# Never reveal your keys! | ||
keys |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# NodeJS | ||
node_modules | ||
build | ||
coverage | ||
.husky | ||
|
||
# Editor | ||
.vscode | ||
|
||
# System | ||
.DS_Store | ||
|
||
# Misc | ||
LICENSE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"semi": true, | ||
"singleQuote": true, | ||
"tabWidth": 2, | ||
"trailingComma": "es5" | ||
} |
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.
let's add an example of a complex stack that will benefit from this pattern (can we describe a use case?) @LuffySama-Dev
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.
I was thinking the same !!
I was thinking of adding a image but an use case example sounds awesome.
Will check and add it in couple of days.
Thank You 🙌🏻