Skip to content

Commit e8f0886

Browse files
committed
Refactor Ralph doc
1 parent 80207ac commit e8f0886

8 files changed

+1045
-1034
lines changed

docs/ralph/built-in-functions.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
sidebar_position: 20
2+
sidebar_position: 90
33
title: Built-in Functions
44
sidebar_label: Built-in functions
55
---

docs/ralph/contracts.md

+361
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
---
2+
sidebar_position: 30
3+
title: Contracts
4+
sidebar_label: Contracts
5+
---
6+
7+
import UntranslatedPageText from "@site/src/components/UntranslatedPageText";
8+
9+
<UntranslatedPageText />
10+
11+
12+
## Contracts
13+
14+
:::info
15+
Each Alephium's contract has 3 forms of unique identification:
16+
17+
1. **Address**: each contract has a unique address
18+
2. **Contract ID**: each contract has a unique contract ID
19+
3. **Token ID**: each contract can issue a token with the same ID as its own contract ID
20+
21+
In Ralph, the contract ID is used more frequently. Contract ids can be converted from/to other forms with Ralph's built-in functions or web3 SDK.
22+
:::
23+
24+
Contracts in Ralph are similar to classes in object-oriented languages. Each contract can contain declarations of contract fields, events, constants, enums, and functions. All these declarations must be inside a contract. Furthermore, contracts can inherit from other contracts.
25+
26+
```rust
27+
// This is a comment, and currently Ralph only supports line comments.
28+
// Contract should be named in upper camel case.
29+
// Contract fields are permanently stored in the contract storage.
30+
Contract MyToken(supply: U256, name: ByteVec) {
31+
32+
// Events should be named in upper camel case.
33+
// Events allow for logging of activities on the blockchain.
34+
// Applications can listen to these events through the REST API of an Alephium client.
35+
event Transfer(to: Address, amount: U256)
36+
37+
// Constant variables should be named in upper camel case.
38+
const Version = 0
39+
40+
// Enums can be used to create a finite set of constant values.
41+
enum ErrorCodes {
42+
// Enum constants should be named in upper camel case.
43+
InvalidCaller = 0
44+
}
45+
46+
// Functions, parameters, and local variables should be named in lower camel case.
47+
pub fn transferTo(toAddress: Address) -> () {
48+
let payloadId = #00
49+
// ...
50+
}
51+
}
52+
```
53+
54+
### Fields
55+
56+
Contract fields are permanently stored in the contract storage, and the fields can be changed by the contract code. Applications can get the contract fields through the REST API of an Alephium client.
57+
58+
```rust
59+
// Contract `Foo` has two fields:
60+
// `a`: immutable, it can not be changed by the contract code
61+
// `b`: mutable, it can be changed by the contract code
62+
Contract Foo(a: U256, mut b: Boolean) {
63+
// ...
64+
}
65+
66+
// Contract fields can also be other contract.
67+
// It will store the contract id of `Bar` in the contract storage of `Foo`.
68+
Contract Foo(bar: Bar) {
69+
// ...
70+
}
71+
72+
Contract Bar() {
73+
// ...
74+
}
75+
```
76+
77+
### Contract Built-In Functions
78+
79+
Sometimes we need to create a contract within a contract, and in such cases, we need to encode the contract fields into `ByteVec`. Ralph provides a built-in function called `encodeFields` that can be used to encode the contract fields into `ByteVec`.
80+
81+
The parameter type of the `encodeFields` function is a list of the types of the contract fields, arranged in the order of their definitions. And the function returns two `ByteVec` values, where the first one is the encoded immutable fields, and the second one is the encoded mutable fields.
82+
83+
There is an example:
84+
85+
```rust
86+
Contract Foo(a: U256, mut b: I256, c: ByteVec, mut d: Bool) {
87+
pub fn update(value: I256) -> () {
88+
b = value
89+
}
90+
}
91+
92+
Contract Bar() {
93+
@using(preapprovedAssets = true)
94+
fn createFoo(caller: Address, fooBytecode: ByteVec, a: U256, b: I256, c: ByteVec, d: Bool) -> (ByteVec) {
95+
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c, d)
96+
return createContract!{caller -> 1 alph}(fooBytecode, encodedImmFields, encodedMutFields)
97+
}
98+
}
99+
```
100+
101+
In the Rhone upgrade, we support utilizing contract fields after deploying the contract in the same transaction. With the above example, you can call the `foo.update` after deploying the contract `Foo`:
102+
103+
```rust
104+
@using(preapprovedAssets = true)
105+
fn createFoo(caller: Address, fooBytecode: ByteVec, a: U256, b: I256, c: ByteVec, d: Bool) -> (ByteVec) {
106+
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c, d)
107+
let fooId = createContract!{caller -> 1 alph}(fooBytecode, encodedImmFields, encodedMutFields)
108+
Foo(fooId).update(-1)
109+
}
110+
```
111+
112+
Note that after deploying the contract, you cannot utilize contract assets in the same transaction.
113+
114+
### Events
115+
116+
Events are dispatched signals that contracts can fire. Applications can listen to these events through the REST API of an Alephium client.
117+
118+
```rust
119+
Contract Token() {
120+
// The number of event fields cannot be greater than 8
121+
event Transfer(to: Address, amount: U256)
122+
123+
@using(assetsInContract = true)
124+
pub fn transfer(to: Address) -> () {
125+
transferTokenFromSelf!(selfTokenId!(), to, 1)
126+
// Emit the event
127+
emit Transfer(to, 1)
128+
}
129+
}
130+
```
131+
132+
### SubContract
133+
134+
Alephium's virtual machine supports subcontract. Subcontracts can be used as map-like data structure but they are less prone to the state bloat issue. A subcontract can be created by a parent contract with a unique subcontract path.
135+
136+
```rust
137+
Contract Bar(value: U256) {
138+
pub fn getValue() -> U256 {
139+
return value
140+
}
141+
}
142+
143+
Contract Foo(barTemplateId: ByteVec) {
144+
event SubContractCreated(key: U256, contractId: ByteVec)
145+
146+
@using(preapprovedAssets = true, checkExternalCaller = false)
147+
pub fn set(caller: Address, key: U256, value: U256) -> () {
148+
let path = toByteVec!(key)
149+
let (encodedImmFields, encodedMutFields) = Bar.encodeFields!(value) // Contract `Bar` has only one field
150+
// Create a sub contract from the given key and value.
151+
// The sub contract id is `blake2b(blake2b(selfContractId!() ++ path))`.
152+
// It will fail if the sub contract already exists.
153+
let contractId = copyCreateSubContract!{caller -> ALPH: 1 alph}(
154+
path,
155+
barTemplateId,
156+
encodedImmFields,
157+
encodedMutFields
158+
)
159+
emit SubContractCreated(key, contractId)
160+
}
161+
162+
pub fn get(key: U256) -> U256 {
163+
let path = toByteVec!(key)
164+
// Get the sub contract id by the `subContractId!` built-in function
165+
let contractId = subContractId!(path)
166+
return Bar(contractId).getValue()
167+
}
168+
}
169+
```
170+
171+
:::note
172+
Deploying a contract requires depositing a certain amount of ALPH in the contract(currently 1 alph, it will be reduced to 0.1 alph after the Rhone upgrade is activated), so creating a large number of sub-contracts is not practical.
173+
:::
174+
175+
### Contract Creation inside a Contract
176+
177+
Ralph supports creating contracts programmatically within contracts, Ralph provides some builtin functions to create contracts, you can find more information at [here](/ralph/built-in-functions#contract-functions).
178+
179+
If you want to create multiple instances of a contract, then you should use the `copyCreateContract!` builtin functions, which will reduce a lot of on-chain storage and transaction gas fee.
180+
181+
```rust
182+
Contract Foo(a: ByteVec, b: Address, mut c: U256) {
183+
// ...
184+
}
185+
186+
// We want to create multiple instances of contract `Foo`.
187+
// First we need to deploy a template contract of `Foo`, which contract id is `fooTemplateId`.
188+
// Then we can use `copyCreateContract!` to create multiple instances.
189+
TxScript CreateFoo(fooTemplateId: ByteVec, a: ByteVec, b: Address, c: U256) {
190+
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c)
191+
copyCreateContract!(fooTemplateId, encodedImmFields, encodedMutFields)
192+
}
193+
```
194+
195+
### Migration
196+
197+
Alephium's contracts can be upgraded with two migration functions: [migrate!](/ralph/built-in-functions#migrate) and [migrateWithFields!](/ralph/built-in-functions#migratewithfields). Here are the three typical ways to use them:
198+
199+
```Rust
200+
fn upgrade(newCode: ByteVec) -> () {
201+
checkOwner(...)
202+
migrate!(newCode)
203+
}
204+
205+
fn upgrade(newCode: ByteVec, newImmFieldsEncoded: ByteVec, newMutFieldsEncoded: ByteVec) -> () {
206+
checkOwner(...)
207+
migrateWithFields!(newCode, newImmFieldsEncoded, newMutFieldsEncoded)
208+
}
209+
210+
fn upgrade(newCode: ByteVec) -> () {
211+
checkOwner(...)
212+
let (newImmFieldsEncoded, newMutFieldsEncoded) = ContractName.encodeFields!(newFields...)
213+
migrateWithFields!(newCode, newImmFieldsEncoded, newMutFieldsEncoded)
214+
}
215+
```
216+
217+
## Inheritance
218+
219+
Ralph also supports multiple inheritance, when a contract inherits from other contracts, only a single contract is created on the blockchain, and the code from all the parent contracts is compiled into the created contract.
220+
221+
```rust
222+
Abstract Contract Foo(a: U256) {
223+
pub fn foo() -> () {
224+
// ...
225+
}
226+
}
227+
228+
Abstract Contract Bar(b: ByteVec) {
229+
pub fn bar() -> () {
230+
// ...
231+
}
232+
}
233+
234+
// The field name of the child contract must be the same as the field name of parnet contracts.
235+
Contract Baz(a: U256, b: ByteVec) extends Foo(a), Bar(b) {
236+
pub fn baz() -> () {
237+
foo()
238+
bar()
239+
}
240+
}
241+
```
242+
243+
:::note
244+
In Ralph, abstract contracts are not instantiable, which means the following code is invalid:
245+
246+
```rust
247+
let bazId = // The contract id of `Baz`
248+
Foo(bazId).foo() // ERROR
249+
```
250+
251+
:::
252+
253+
## Interface
254+
255+
Interfaces are similar to abstract contracts with the following restrictions:
256+
257+
- They cannot have any functions implemented.
258+
- They cannot inherit from other contracts, but they can inherit from other interfaces.
259+
- They cannot declare contract fields.
260+
261+
```rust
262+
Interface Foo {
263+
event E(a: U256)
264+
265+
@using(assetsInContract = true)
266+
pub fn foo() -> ()
267+
}
268+
269+
Interface Bar extends Foo {
270+
pub fn bar() -> U256
271+
}
272+
273+
Contract Baz() implements Bar {
274+
// The function signature must be the same as the function signature declared in the interface.
275+
@using(assetsInContract = true)
276+
pub fn foo() -> () {
277+
// Inherit the event from `Foo`
278+
emit E(0)
279+
// ...
280+
}
281+
282+
pub fn bar() -> U256 {
283+
// ...
284+
}
285+
}
286+
```
287+
288+
And you can instantiate a contract with interface:
289+
290+
```rust
291+
let bazId = // The contract id of `Baz`
292+
Foo(bazId).foo()
293+
let _ = Bar(bazId).bar()
294+
```
295+
296+
In the Rhone upgrade, we introduced the `@using(methodSelector = true/false)` annotation to support multiple interfaces inheritance. You can set the `methodSelector` to `true` if your contract needs to inherit from multiple interfaces:
297+
298+
```rust
299+
@using(methodSelector = true)
300+
Interface Foo {
301+
pub fn foo() -> ()
302+
}
303+
304+
@using(methodSelector = true)
305+
Interface Bar {
306+
pub fn bar() -> ()
307+
}
308+
309+
Contract Baz() implements Foo, Bar {
310+
pub fn foo() -> () {}
311+
pub fn bar() -> () {}
312+
}
313+
```
314+
315+
## TxScript
316+
317+
A transaction script is a piece of code to interact with contracts on the blockchain. Transaction scripts can use the input assets of transactions in general. A script is disposable and will only be executed once along with the holder transaction.
318+
319+
```rust
320+
Contract Foo() {
321+
pub fn foo(v: U256) -> () {
322+
// ...
323+
}
324+
}
325+
326+
// The `preapprovedAssets` is true by default for `TxScript`.
327+
// We set the `preapprovedAssets` to false because the script does not need assets.
328+
@using(preapprovedAssets = false)
329+
// `TxScript` fields are more like function parameters, and these
330+
// fields need to be specified every time the script is executed.
331+
TxScript Main(fooId: ByteVec) {
332+
// The body of `TxScript` consists of statements
333+
bar()
334+
Foo(fooId).foo(0)
335+
336+
// You can also define functions in `TxScript`
337+
fn bar() -> () {
338+
// ...
339+
}
340+
}
341+
```
342+
343+
## Gasless Transaction
344+
345+
In the Rhone upgrade, we introduced support for gasless transactions. We can use the built-in `payGasFee` to pay transaction gas fees on behalf of the user, for example:
346+
347+
```rust
348+
Contract Foo() {
349+
@using(assetsInContract = true)
350+
pub fn foo() -> () {
351+
payGasFee!(selfAddress!(), txGasFee!())
352+
}
353+
}
354+
```
355+
356+
The built-in `payGasFee` has two parameters:
357+
358+
1. The first parameter is the payer address, in the example above, the contract paid the gas fee. But the payer address can also be the user address.
359+
2. The second parameter is the amount of gas to be paid, in the above example, the contract paid all the gas fees. You can choose to pay part of the gas fees.
360+
361+
Note that gasless transactions do not mean that transactions do not require gas fees, but that others pay the gas fees on your behalf. You still need to have ALPH to send transactions.

0 commit comments

Comments
 (0)