Skip to content

Commit c307603

Browse files
Merge pull request #1271 from dfinity/feature/add-canister-factory-motoko-example
add example
2 parents ef8c073 + 1dc5bb9 commit c307603

File tree

9 files changed

+690
-0
lines changed

9 files changed

+690
-0
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
/motoko/basic_bitcoin/ @dfinity/cross-chain-team
1313
/motoko/backend_only/ @dfinity/ninja-devs
14+
/motoko/canister_factory/ @dfinity/growth
1415
/motoko/canister_logs/ @dfinity/execution
1516
/motoko/cert-var/ @dfinity/trust
1617
/motoko/classes/ @dfinity/languages
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Known failure: https://dfinity.atlassian.net/browse/EM-5
2+
name: motoko-canister-factory
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
paths:
9+
- motoko/canister_factory/**
10+
- .github/workflows/provision-darwin.sh
11+
- .github/workflows/provision-linux.sh
12+
- .github/workflows/motoko-canister-factory-example.yml
13+
- .ic-commit
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
jobs:
18+
motoko-canister-factory-darwin:
19+
runs-on: macos-15
20+
steps:
21+
- uses: actions/checkout@v1
22+
- name: Provision Darwin
23+
run: bash .github/workflows/provision-darwin.sh
24+
- name: Motoko Canister Factory Darwin
25+
run: |
26+
pushd motoko/canister_factory
27+
bash ./demo.sh
28+
popd
29+
motoko-canister-factory-linux:
30+
runs-on: ubuntu-22.04
31+
steps:
32+
- uses: actions/checkout@v1
33+
- name: Provision Linux
34+
run: bash .github/workflows/provision-linux.sh
35+
- name: Motoko Canister Factory Linux
36+
run: |
37+
pushd motoko/canister_factory
38+
bash ./demo.sh
39+
popd

motoko/canister_factory/README.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Motoko Canister Creation Examples
2+
3+
This project demonstrates various approaches to creating and managing canisters on the Internet Computer using Motoko. It showcases the differences between high-level actor class management and low-level management canister operations.
4+
5+
## Overview
6+
7+
The example includes implementations of:
8+
9+
- **Actor Class Management**: High-level canister creation using the `system` keyword
10+
- **Manual Canister Management**: Low-level creation using the management canister directly
11+
- **Canister Lifecycle Operations**: Upgrade and reinstall operations
12+
13+
## Key Differences
14+
15+
### Actor Class Management (High-level)
16+
- Simpler API with automatic WASM installation
17+
- Limited canister settings: `controllers`, `compute_allocation`, `memory_allocation`, `freezing_threshold`
18+
- Good for most common use cases
19+
- [Documentation](https://internetcomputer.org/docs/motoko/language-manual#actor-class-management)
20+
21+
### Management Canister (Low-level)
22+
- Full control over canister creation and settings
23+
- Access to all settings: `reserved_cycles_limit`, `wasm_memory_limit`, `log_visibility`, `wasm_memory_threshold`
24+
- Requires separate steps for creation and code installation
25+
- [Documentation](https://internetcomputer.org/docs/references/ic-interface-spec#ic-create_canister)
26+
27+
## Prerequisites
28+
29+
- [DFX](https://internetcomputer.org/docs/current/developer-docs/setup/install) 0.29.0 or later
30+
- [Mops](https://mops.one/) package manager
31+
32+
## Project Structure
33+
34+
```
35+
├── src/
36+
│ └── backend/
37+
│ ├── Main.mo # Main actor with canister creation examples
38+
│ ├── Child.mo # Simple actor class for demonstrations
39+
│ └── AnotherChild.mo # Alternative actor class for upgrades
40+
├── dfx.json # DFX configuration
41+
├── mops.toml # Mops package configuration
42+
└── README.md # This file
43+
```
44+
45+
## Getting Started
46+
47+
### 1. Start the local Internet Computer
48+
49+
```bash
50+
dfx start --background
51+
```
52+
53+
### 2. Deploy the canister
54+
55+
```bash
56+
dfx deploy
57+
```
58+
59+
This will deploy the main canister that contains all the canister creation examples.
60+
61+
## Available Functions
62+
63+
### 1. Actor Class Creation (High-level)
64+
65+
#### `newActorClass(cycles: Nat)`
66+
Creates a new canister using actor class with automatic installation.
67+
68+
```bash
69+
# Create a canister with 2 trillion cycles
70+
dfx canister call backend newActorClass '(2_000_000_000_000)'
71+
```
72+
73+
#### `installActorClass(cycles: Nat)`
74+
Creates a canister and installs an actor class using a two-step process.
75+
76+
```bash
77+
# Create and install actor class with 2 trillion cycles
78+
dfx canister call backend installActorClass '(2_000_000_000_000)'
79+
```
80+
81+
### 2. Canister Lifecycle Management
82+
83+
#### Understanding Upgrade vs Reinstall
84+
85+
This example demonstrates the critical difference between **upgrading** and **reinstalling** canisters:
86+
87+
**🔄 Upgrade (`#upgrade`)**
88+
- **State is PRESERVED**: All mutable variables keep their current values
89+
- **New functionality is added**: The `substractFromValue` endpoint becomes available
90+
- **Existing functionality remains**: All original endpoints continue working
91+
- **Use case**: Adding features, bug fixes, optimizations while keeping data
92+
93+
**🔥 Reinstall (`#reinstall`)**
94+
- **State is RESET**: All mutable variables return to their initial values
95+
- **New functionality is added**: The `substractFromValue` endpoint becomes available
96+
- **Existing functionality resets**: Original endpoints work but with fresh state
97+
- **Use case**: Breaking changes, schema migrations, fresh start scenarios
98+
99+
#### `upgradeActorClass(canisterId: Principal)`
100+
Upgrades an existing canister to use a different actor class (preserves state).
101+
102+
```bash
103+
# Upgrade a canister (replace with actual canister ID)
104+
dfx canister call backend upgradeActorClass '(principal "rdmx6-jaaaa-aaaaa-aaadq-cai")'
105+
```
106+
107+
#### `reinstallActorClass(canisterId: Principal)`
108+
Reinstalls an existing canister with a different actor class (destroys state).
109+
110+
```bash
111+
# Reinstall a canister (replace with actual canister ID)
112+
dfx canister call backend reinstallActorClass '(principal "rdmx6-jaaaa-aaaaa-aaadq-cai")'
113+
```
114+
115+
### 3. Manual Canister Management (Low-level)
116+
117+
#### `createAndInstallCanisterManually(cycles: Nat)`
118+
Creates a canister manually using the management canister with full control over settings.
119+
120+
```bash
121+
# Create canister manually with advanced settings
122+
dfx canister call backend createAndInstallCanisterManually '(2_000_000_000_000)'
123+
```
124+
125+
## Example Workflow
126+
127+
Here's a complete example demonstrating all canister creation approaches and the differences between upgrade vs reinstall:
128+
129+
### Part 1: Different Canister Creation Approaches
130+
131+
```bash
132+
# 1. Start local replica
133+
dfx start --background --clean
134+
135+
# 2. Deploy the main canister
136+
dfx deploy --with-cycles 30000000000000
137+
138+
# 3. Create a canister using actor class (high-level)
139+
CANISTER1=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
140+
echo "Created canister via actor class: $CANISTER1"
141+
142+
# 4. Create a canister using manual management (low-level)
143+
CANISTER2=$(dfx canister call backend createAndInstallCanisterManually '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
144+
echo "Created canister manually: $CANISTER2"
145+
146+
# 5. Create canister with two-step process
147+
CANISTER3=$(dfx canister call backend installActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
148+
echo "Created canister with install process: $CANISTER3"
149+
```
150+
151+
### Part 2: Upgrade vs Reinstall Demonstration
152+
153+
```bash
154+
# 6. Create additional canisters for testing upgrade vs reinstall
155+
CANISTER_UPGRADE=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
156+
CANISTER_REINSTALL=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
157+
echo "Created upgrade test canister: $CANISTER_UPGRADE"
158+
echo "Created reinstall test canister: $CANISTER_REINSTALL"
159+
160+
# 7. Test initial state of both canisters
161+
echo "=== Initial State ==="
162+
echo "Upgrade canister initial value:"
163+
dfx canister call $CANISTER_UPGRADE getValue
164+
echo "Reinstall canister initial value:"
165+
dfx canister call $CANISTER_REINSTALL getValue
166+
167+
# 8. Modify state by incrementing internal counters
168+
echo "=== Modifying State ==="
169+
echo "Adding 10 to upgrade canister..."
170+
dfx canister call $CANISTER_UPGRADE addToValue '(10)'
171+
echo "Adding 20 to reinstall canister..."
172+
dfx canister call $CANISTER_REINSTALL addToValue '(20)'
173+
174+
# 9. Upgrade the first canister (preserves state)
175+
echo "=== Performing Upgrade (State Preserved) ==="
176+
dfx canister call backend upgradeActorClass "(principal \"$CANISTER_UPGRADE\")"
177+
echo "Upgraded canister: $CANISTER_UPGRADE"
178+
179+
# 10. Test upgraded canister - should have preserved state AND new functionality
180+
echo "Upgraded canister value (should be preserved):"
181+
dfx canister call $CANISTER_UPGRADE getValue
182+
echo "Testing new substractFromValue endpoint:"
183+
dfx canister call $CANISTER_UPGRADE substractFromValue '(5)'
184+
185+
# 11. Reinstall the second canister (resets state)
186+
echo "=== Performing Reinstall (State Reset) ==="
187+
dfx canister call backend reinstallActorClass "(principal \"$CANISTER_REINSTALL\")"
188+
echo "Reinstalled canister: $CANISTER_REINSTALL"
189+
190+
# 12. Test reinstalled canister - should have reset state BUT new functionality
191+
echo "Reinstalled canister value (should be reset to initial):"
192+
dfx canister call $CANISTER_REINSTALL getValue
193+
echo "Adding 20 to reinstall canister..."
194+
dfx canister call $CANISTER_REINSTALL addToValue '(20)'
195+
echo "Testing new substractFromValue endpoint:"
196+
dfx canister call $CANISTER_REINSTALL substractFromValue '(5)'
197+
198+
echo "=== Summary ==="
199+
echo "✅ Different creation approaches: actor class, manual, two-step"
200+
echo "🔄 Upgrade: State preserved, new functionality added"
201+
echo "🔥 Reinstall: State reset, new functionality added"
202+
```
203+
204+
## Understanding the Code
205+
206+
### Actor Classes
207+
- `Child.mo`: A simple persistent actor class with mutable state for demonstrating initial installations and state behavior
208+
- `AnotherChild.mo`: An extended actor class with additional `substractFromValue` functionality used for upgrade/reinstall demonstrations
209+
210+
### State Behavior Demonstration
211+
The example shows how:
212+
- **Child**: Has basic functionality (`getValue`, `addToValue`) and mutable state
213+
- **AnotherChild**: Extends Child with new functionality (`substractFromValue`)
214+
- **Upgrade**: Migrates from Child to AnotherChild while preserving any modified state
215+
- **Reinstall**: Migrates from Child to AnotherChild but resets state to initial values
216+
217+
### Main Functions
218+
- **High-level functions** use Motoko's `system` keyword with actor classes
219+
- **Low-level functions** interact directly with the management canister
220+
- **Lifecycle functions** demonstrate upgrade and reinstall capabilities
221+
222+
## Cycles Management
223+
224+
All functions require cycles to create canisters. The examples use 2 trillion cycles (2_000_000_000_000), which is sufficient for most development purposes. In production, you'll want to calculate appropriate cycle amounts based on your canister's needs.
225+
226+
## Troubleshooting
227+
228+
### Common Issues
229+
230+
1. **Insufficient cycles**: Increase the cycle amount in function calls
231+
2. **Invalid canister ID**: Ensure you're using the correct Principal format
232+
3. **Deploy failures**: Check that dfx is running and properly configured
233+
234+
### Getting Help
235+
236+
- [Internet Computer Documentation](https://internetcomputer.org/docs)
237+
- [Motoko Documentation](https://internetcomputer.org/docs/motoko)
238+
- [DFX Command Reference](https://internetcomputer.org/docs/building-apps/developer-tools/dfx/)
239+
- [Developer Forum](https://forum.dfinity.org/)
240+
241+
## Related Examples
242+
243+
For more Motoko examples, visit the [official examples repository](https://github.com/dfinity/examples/tree/master/motoko).
244+
245+
## License
246+
247+
This project is licensed under the Apache 2.0 license. See LICENSE for more details.

motoko/canister_factory/demo.sh

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/bin/bash
2+
3+
echo "=== Motoko Canister Factory: Complete Demo ==="
4+
echo "This demo showcases:"
5+
echo "1. Different canister creation approaches"
6+
echo "2. Upgrade vs Reinstall behavior with state persistence"
7+
echo ""
8+
9+
echo "=== Part 1: Different Canister Creation Approaches ==="
10+
11+
# 1. Start local replica
12+
echo "1. Starting local replica..."
13+
dfx start --background --clean
14+
15+
# 2. Deploy the main canister
16+
echo "2. Deploying main canister..."
17+
dfx deploy --with-cycles 30000000000000
18+
19+
# 3. Create a canister using actor class (high-level)
20+
echo "3. Creating canister via actor class (high-level)..."
21+
CANISTER1=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
22+
echo "Created canister via actor class: $CANISTER1"
23+
24+
# 4. Create a canister using manual management (low-level)
25+
echo "4. Creating canister manually (low-level)..."
26+
CANISTER2=$(dfx canister call backend createAndInstallCanisterManually '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
27+
echo "Created canister manually: $CANISTER2"
28+
29+
# 5. Create canister with two-step process
30+
echo "5. Creating canister with two-step process..."
31+
CANISTER3=$(dfx canister call backend installActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
32+
echo "Created canister with install process: $CANISTER3"
33+
34+
echo ""
35+
echo "=== Part 2: Upgrade vs Reinstall Demonstration ==="
36+
37+
# 6. Create additional canisters for testing upgrade vs reinstall
38+
echo "6. Creating test canisters for upgrade/reinstall demo..."
39+
CANISTER_UPGRADE=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
40+
CANISTER_REINSTALL=$(dfx canister call backend newActorClass '(2_000_000_000_000)' | grep -o 'principal "[^"]*"' | cut -d'"' -f2)
41+
echo "Created upgrade test canister: $CANISTER_UPGRADE"
42+
echo "Created reinstall test canister: $CANISTER_REINSTALL"
43+
44+
# 7. Test initial state of both canisters
45+
echo ""
46+
echo "=== Initial State ==="
47+
echo "Upgrade canister initial value:"
48+
dfx canister call $CANISTER_UPGRADE getValue
49+
echo "Reinstall canister initial value:"
50+
dfx canister call $CANISTER_REINSTALL getValue
51+
52+
# 8. Modify state by incrementing internal counters
53+
echo ""
54+
echo "=== Modifying State ==="
55+
echo "Adding 10 to upgrade canister..."
56+
dfx canister call $CANISTER_UPGRADE addToValue '(10)'
57+
echo "Adding 20 to reinstall canister..."
58+
dfx canister call $CANISTER_REINSTALL addToValue '(20)'
59+
60+
# 9. Upgrade the first canister (preserves state)
61+
echo ""
62+
echo "=== Performing Upgrade (State Preserved) ==="
63+
dfx canister call backend upgradeActorClass "(principal \"$CANISTER_UPGRADE\")"
64+
echo "Upgraded canister: $CANISTER_UPGRADE"
65+
66+
# 10. Test upgraded canister - should have preserved state AND new functionality
67+
echo ""
68+
echo "Upgraded canister value (should be preserved):"
69+
dfx canister call $CANISTER_UPGRADE getValue
70+
echo "Testing new substractFromValue endpoint:"
71+
dfx canister call $CANISTER_UPGRADE substractFromValue '(5)'
72+
73+
# 11. Reinstall the second canister (resets state)
74+
echo ""
75+
echo "=== Performing Reinstall (State Reset) ==="
76+
dfx canister call backend reinstallActorClass "(principal \"$CANISTER_REINSTALL\")"
77+
echo "Reinstalled canister: $CANISTER_REINSTALL"
78+
79+
# 12. Test reinstalled canister - should have reset state BUT new functionality
80+
echo ""
81+
echo "Reinstalled canister value (should be reset to initial):"
82+
dfx canister call $CANISTER_REINSTALL getValue
83+
echo "Adding 20 to reinstall canister..."
84+
dfx canister call $CANISTER_REINSTALL addToValue '(20)'
85+
echo "Testing new substractFromValue endpoint:"
86+
dfx canister call $CANISTER_REINSTALL substractFromValue '(5)'
87+
88+
echo ""
89+
echo "=== Summary ==="
90+
echo "✅ Different creation approaches demonstrated:"
91+
echo " - Actor class (high-level): $CANISTER1"
92+
echo " - Manual creation (low-level): $CANISTER2"
93+
echo " - Two-step process: $CANISTER3"
94+
echo ""
95+
echo "🔄 Upgrade: State preserved, new functionality added"
96+
echo "🔥 Reinstall: State reset, new functionality added"
97+
echo ""
98+
echo "Demo completed! All approaches and behaviors demonstrated."

0 commit comments

Comments
 (0)