|
| 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