Contracts
Contracts
Each Alephium's contract has 3 forms of unique identification:
- Address: each contract has a unique address
- Contract ID: each contract has a unique contract ID
- Token ID: each contract can issue a token with the same ID as its own contract ID
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.
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.
// This is a comment, and currently Ralph only supports line comments.
// Contract should be named in upper camel case.
// Contract fields are permanently stored in the contract storage.
Contract MyToken(supply: U256, name: ByteVec) {
// Events should be named in upper camel case.
// Events allow for logging of activities on the blockchain.
// Applications can listen to these events through the REST API of an Alephium client.
event Transfer(to: Address, amount: U256)
// Constant variables should be named in upper camel case.
const VERSION = 0
// Enums can be used to create a finite set of constant values.
enum ErrorCodes {
// Enum constants should be named in upper camel case.
INVALID_CALLER = 0
}
// Functions, parameters, and local variables should be named in lower camel case.
pub fn transferTo(toAddress: Address) -> () {
let payloadId = #00
// ...
}
}
Fields
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.
// Contract `Foo` has two fields:
// `a`: immutable, it can not be changed by the contract code
// `b`: mutable, it can be changed by the contract code
Contract Foo(a: U256, mut b: Boolean) {
// ...
}
// Contract fields can also be other contract.
// It will store the contract id of `Bar` in the contract storage of `Foo`.
Contract Foo(bar: Bar) {
// ...
}
Contract Bar() {
// ...
}
Contract Built-In Functions
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.
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.
There is an example:
Contract Foo(a: U256, mut b: I256, c: ByteVec, mut d: Bool) {
pub fn update(value: I256) -> () {
b = value
}
}
Contract Bar() {
@using(preapprovedAssets = true)
fn createFoo(caller: Address, fooBytecode: ByteVec, a: U256, b: I256, c: ByteVec, d: Bool) -> (ByteVec) {
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c, d)
return createContract!{caller -> 1 alph}(fooBytecode, encodedImmFields, encodedMutFields)
}
}
In Ralph, you can read or write contract fields within the same transaction after deploying the contract. With the above example, you can call the foo.update after deploying the contract Foo:
@using(preapprovedAssets = true)
fn createFoo(caller: Address, fooBytecode: ByteVec, a: U256, b: I256, c: ByteVec, d: Bool) -> (ByteVec) {
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c, d)
let fooId = createContract!{caller -> 1 alph}(fooBytecode, encodedImmFields, encodedMutFields)
Foo(fooId).update(-1)
}
Note that after deploying the contract, you cannot utilize contract assets in the same transaction.
Events
Events are dispatched signals that contracts can fire. Applications can listen to these events through the REST API of an Alephium client.
Contract Token() {
// The number of event fields cannot be greater than 8
event Transfer(to: Address, amount: U256)
@using(assetsInContract = true)
pub fn transfer(to: Address) -> () {
transferTokenFromSelf!(selfTokenId!(), to, 1)
// Emit the event
emit Transfer(to, 1)
}
}
SubContract
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.
Contract Bar(value: U256) {
pub fn getValue() -> U256 {
return value
}
}
Contract Foo(barTemplateId: ByteVec) {
event SubContractCreated(key: U256, contractId: ByteVec)
@using(preapprovedAssets = true, checkExternalCaller = false)
pub fn set(caller: Address, key: U256, value: U256) -> () {
let path = toByteVec!(key)
let (encodedImmFields, encodedMutFields) = Bar.encodeFields!(value) // Contract `Bar` has only one field
// Create a sub contract from the given key and value.
// The sub contract id is `blake2b(blake2b(selfContractId!() ++ path))`.
// It will fail if the sub contract already exists.
let contractId = copyCreateSubContract!{caller -> ALPH: minimalContractDeposit!()}(
path,
barTemplateId,
encodedImmFields,
encodedMutFields
)
emit SubContractCreated(key, contractId)
}
pub fn get(key: U256) -> U256 {
let path = toByteVec!(key)
// Get the sub contract id by the `subContractId!` built-in function
let contractId = subContractId!(path)
return Bar(contractId).getValue()
}
}
Deploying a contract requires depositing a certain amount of ALPH in the contract(currently 0.1 alph), so creating a large number of sub-contracts is not practical.
Contract Creation inside a Contract
Ralph supports creating contracts programmatically within contracts, Ralph provides some builtin functions to create contracts, you can find more information at here.
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.
Contract Foo(a: ByteVec, b: Address, mut c: U256) {
// ...
}
// We want to create multiple instances of contract `Foo`.
// First we need to deploy a template contract of `Foo`, which contract id is `fooTemplateId`.
// Then we can use `copyCreateContract!` to create multiple instances.
TxScript CreateFoo(fooTemplateId: ByteVec, a: ByteVec, b: Address, c: U256) {
let (encodedImmFields, encodedMutFields) = Foo.encodeFields!(a, b, c)
copyCreateContract!(fooTemplateId, encodedImmFields, encodedMutFields)
}