-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add Smart Contract documentation
- Loading branch information
Showing
16 changed files
with
4,399 additions
and
11,541 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
# Interacting with MAS Tokens in Massa smart contracts | ||
In Massa smart contracts, you can interact with the MAS token to perform various financial operations, such as sending and receiving MAS tokens, checking balances, and transferring tokens between addresses. | ||
The Mas token is represented as a u64 value. | ||
Here’s a guide on how to handle these interactions within a contract. | ||
|
||
:::caution | ||
In Massa, all contract exported functions are payable par default. This means that all functions can receive MAS tokens even if the called function does not write anything onchain. | ||
::: | ||
|
||
## Including MAS Tokens when calling a smart contract | ||
When calling a smart contract, you can specify an amount of MAS tokens to send along with the call. This is done by setting the amount in the operation from the calling account to the contract. | ||
|
||
For example, if you are calling a function on a deployed smart contract from your client application, you would include the MAS amount in the operation parameters. | ||
Using [massa-web3](https://github.com/massalabs/massa-web3), the SmartContract class allows you to specify the `coins` parameter. | ||
|
||
```ts | ||
import { SmartContract, Mas } from '@massalabs/massa-web3'; | ||
|
||
const contract = new SmartContract(provider, "<deployed_contract_address>"); | ||
const amount = Mas.fromString('10'); // 10 MAS | ||
|
||
// Call the smart contract with 10 MAS | ||
await contract.call( | ||
'receiveTokens', | ||
// here the parameters of the function can be left undefined if the function does not require any | ||
undefined, | ||
{ coins: amount } | ||
); | ||
``` | ||
|
||
## Retrieving the sent value in the contract | ||
|
||
Inside the contract, you can retrieve the amount of MAS tokens sent with the call by using the `transferredCoins()` function. This returns the amount of MAS as a `u64` value. | ||
|
||
```ts | ||
import { transferredCoins } from '@massalabs/massa-as-sdk'; | ||
|
||
export function receiveTokens(): void { | ||
const receivedAmount = transferredCoins(); | ||
assert(receivedAmount > 0, 'No MAS tokens were sent.'); | ||
// Further logic for handling the received amount | ||
} | ||
``` | ||
|
||
## Checking the contract balance and the balance of another address | ||
You can check the MAS token balance of the current contract or any other address using the `balance()` function or the `balanceOf(address: string)` function. | ||
|
||
1. Getting the Contract's balance: | ||
|
||
```ts | ||
import { balance } from '@massalabs/massa-as-sdk'; | ||
|
||
export function getContractBalance(): u64 { | ||
return Context.balance(); | ||
} | ||
``` | ||
|
||
2. Getting the Balance of Another Address: | ||
|
||
```ts | ||
import { balanceOf } from '@massalabs/massa-as-sdk'; | ||
|
||
export function getAddressBalance(address: string): u64 { | ||
return balanceOf(address); | ||
} | ||
``` | ||
|
||
## Sending MAS tokens to another address | ||
To send MAS tokens from the contract to another address, you can use the `transferCoins()` function. This function takes the recipient address and the amount of MAS tokens to send. | ||
Ensure that the contract has sufficient balance to cover the transfer. | ||
|
||
```ts | ||
import { transferCoins, balance, Address } from '@massalabs/massa-as-sdk'; | ||
export function sendTokens(toAddress: Address, amount: u64): void { | ||
const contractBalance = balance(); | ||
assert(contractBalance >= amount, 'Insufficient balance to send MAS tokens'); | ||
|
||
transferCoins(toAddress, amount); | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# Constructor function | ||
|
||
In Massa smart contracts, the constructor function is responsible for initializing the contract's state during deployment. This function is called only once, during the deployment step, to set up any required initial values or configurations. | ||
|
||
1. **Deployment-Only Execution**: | ||
- To ensure that the constructor function is only executed during deployment, include the line `assert(Context.isDeployingContract());` at the start of the function. This assertion checks that the contract is currently in the deployment phase and will throw an error if the function is called after deployment. | ||
|
||
2. **Passing Parameters to the Constructor**: | ||
- Just like other functions, parameters can be passed to the constructor using serialized data. This allows for the flexible initialization of the contract, enabling the deployment process to be customized with specific data. | ||
The Args utility can be used to handle serialization and deserialization of these parameters, ensuring consistency with other functions in the contract. | ||
|
||
|
||
### Example constructor implementation | ||
Below is an example of a constructor function that takes a string parameter for initialization. This example demonstrates how to set up the constructor to accept parameters, ensuring it is only called during deployment, and store the initialized data: | ||
|
||
```ts | ||
import { Args, Storage, Context } from "@massalabs/as-types"; | ||
|
||
export function constructor(argsData: StaticArray<u8>): void { | ||
// Ensure the function is only called during deployment | ||
assert(Context.isDeployingContract(), "Constructor can only be called during deployment"); | ||
|
||
// Deserialize the initialization parameter | ||
const initialMessage = new Args(argsData).nextString().expect("Initialization parameter missing or invalid"); | ||
|
||
// Store the initial message in contract storage | ||
Storage.set("greeting", initialMessage); | ||
} | ||
``` | ||
|
||
### Deploying with Constructor Parameters | ||
When deploying this contract, the serialized parameter(s) should be included in the deployment transaction. For example, in a TypeScript deployment script, you might serialize the initial message and include it in the deployment call: | ||
|
||
```ts | ||
import { | ||
Account, | ||
Args, | ||
Mas, | ||
SmartContract, | ||
Web3Provider, | ||
} from '@massalabs/massa-web3'; | ||
import { getScByteCode } from './utils'; | ||
|
||
const account = await Account.fromEnv(); | ||
const provider = Web3Provider.buildnet(account); | ||
|
||
// Retrieve the compiled smart contract bytecode | ||
const byteCode = getScByteCode('build', 'main.wasm'); | ||
|
||
const constructorArgs = new Args().addString("Welcome to Massa!"); | ||
|
||
const contract = await SmartContract.deploy( | ||
provider, | ||
byteCode, | ||
constructorArgs, | ||
{ coins: Mas.fromString('0.01') }, | ||
); | ||
``` | ||
|
||
#### Explanation of the Deployment Code | ||
1. **Imports**: | ||
|
||
- Key modules are imported from @massalabs/massa-web3, including Account, Args, Mas, SmartContract, and Web3Provider, which are essential for deploying a smart contract. | ||
|
||
2. **Account Setup**: | ||
|
||
- An Account instance is created using Account.fromEnv(), which retrieves the necessary credentials from environment variables. This ensures the deployment uses a valid account on the Massa network. | ||
|
||
3. **Provider Initialization**: | ||
|
||
- Web3Provider.buildnet(account) sets up a provider instance connected to the Massa buildnet, allowing the account to interact with the network. | ||
|
||
4. **Bytecode Retrieval**: | ||
|
||
- The compiled smart contract bytecode is loaded using `getScByteCode('build', 'main.wasm')`. This function reads the compiled .wasm file from the specified directory `build`. | ||
It assume your contract file is `main.ts`. The getScByteCode implementation can be found [here](https://github.com/massalabs/massa-sc-toolkit/blob/a987baa4ad0f5b9a3aea1cc1107d1e90892c5fee/packages/sc-project-initializer/commands/init/src/utils.ts#L8) | ||
|
||
5. **Constructor Arguments**: | ||
|
||
- The constructor arguments are prepared using Args, where addString("Welcome to Massa!") serializes the initialization message. This message will be passed to the constructor function in the smart contract. | ||
|
||
6. **Contract Deployment**: | ||
|
||
- SmartContract.deploy is called with the provider, bytecode, and constructor arguments to deploy the contract. | ||
The coins option specifies an initial allocation of 0.01 MAS to the contract. | ||
|
||
:::caution | ||
Deploying a smart contract on Massa incurs a cost, which is deducted from the caller's wallet in MAS. The deployment cost is proportional to the size of the compiled contract bytecode. This means that larger smart contracts, with more complex logic and features, will generally require more MAS to deploy. Learn more about storage costs [here](https://docs.massa.net/docs/learn/storage-costs) | ||
::: | ||
|
||
By following this approach, you can initialize your Massa smart contract with parameters at deployment, ensuring the constructor sets up the contract's initial state with custom values. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
# Smart Contract Data Types | ||
|
||
When developing smart contracts on Massa, it is essential to understand the data types available in AssemblyScript, as they directly influence how you handle and manipulate data within the contract. AssemblyScript offers a range of data types that align closely with WebAssembly's capabilities, providing efficient and predictable performance. | ||
|
||
## AssemblyScript native types | ||
|
||
| Data Type | Description | | ||
|---------------|----------------------------------------| | ||
| i8 / u8 | 8-bit signed/unsigned integers | | ||
| i16 / u16 | 16-bit signed/unsigned integers | | ||
| i32 / u32 | 32-bit signed/unsigned integers | | ||
| i64 / u64 | 64-bit signed/unsigned integers | | ||
| f32 / f64 | 32-bit and 64-bit floating-point numbers | | ||
| bool | Represents a Boolean value (`true` or `false`) | | ||
| string | Represents UTF-16 encoded text | | ||
|
||
### Array: | ||
`Array<T>`: Represents a dynamically-sized collection of elements of type T. | ||
Arrays are suitable for storing lists of data, such as user addresses or transaction histories. You can define arrays with specific types, such as `Array<i32>` or `Array<string>`, to ensure data consistency. | ||
|
||
### Byte array: | ||
Massa use AssemblyScript `StaticArray<u8>` native type to represent data bytes. This type is used internally in storage and is also used to serialize public function parameters and returned data. | ||
|
||
## Handling Large Numbers in Massa Smart Contracts | ||
|
||
In some smart contracts, you may need to work with numbers that exceed the size limitations of standard 64-bit integers. For example, financial applications involving token balances or computations with very large numbers require higher precision and larger ranges that i64 or u64 can't provide. To handle such cases, Massa smart contracts can use the [as-bignum](https://github.com/MaxGraey/as-bignum) library, which supports fixed length big number arithmetic. | ||
|
||
| Data Type | Description | | ||
|---------------|----------------------------------------| | ||
| i128 / u128 | 128-bit signed/unsigned integers | | ||
| i256 / u256 | 256-bit signed/unsigned integers | | ||
|
||
### Example Import Statement | ||
```ts | ||
import { u256 } from "as-bignum/assembly" | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
# Events | ||
|
||
In Massa's smart contracts, events provide a way to emit runtime messages that can be useful for monitoring, debugging, and logging activities within the contract. Events can be triggered by errors, such as assertion failures or insufficient coin balances, as well as custom, user-defined events. | ||
|
||
## Types of events | ||
|
||
1. **Error Events**: | ||
|
||
- Error events occur when the contract encounters runtime issues, such as: | ||
- Failed assertions, where the assert statement fails. | ||
- Errors during argument deserialization | ||
- Insufficient coins to write to storage or to call another contract that requires a fee. | ||
- These events help identify issues and diagnose why a transaction or contract call may have failed. | ||
|
||
2. **User-Defined Events**: | ||
|
||
- In addition to system-generated error events, developers can define custom events to log specific actions or important milestones within the contract. | ||
- User-defined events are typically used to provide visibility into contract behavior, for example during a transfer or token minting. | ||
|
||
## Important considerations for events | ||
In Massa, events are not stored in the blockchain ledger, meaning they are ephemeral. Therefore, events should not be used as a source of persistent data. Instead, they are suitable for temporary tasks such as monitoring, logging, or providing real-time feedback. | ||
For use cases that require persistent data, storage should be used. | ||
|
||
## Emitting a custom event | ||
To emit a custom event in a smart contract, you can use the generateEvent function (assuming this utility is available in your environment): | ||
|
||
```ts | ||
import { generateEvent } from "@massalabs/massa-as-sdk"; | ||
|
||
export function transferTokens(to: string, amount: u64): void { | ||
// Transfer logic here | ||
|
||
// Emit a custom event after transfer | ||
generateEvent(`Transferred ${amount.toString()} tokens to ${to}`); | ||
} | ||
``` | ||
In this example, the generateEvent function is used to emit a custom message after a token transfer operation. This message can be retrieved as shown in the example below. | ||
|
||
## Retrieving events | ||
Although events are not permanently stored on the blockchain, they can still be retrieved for monitoring purposes after being emitted. Here's an example of how to retrieve events for a specific smart contract using the [@massalabs/massa-web3](https://github.com/massalabs/massa-web3) provider: | ||
```ts | ||
// typescript | ||
const events = await provider.getEvents({ | ||
smartContractAddress: "<deployed_contract_address>", | ||
}); | ||
|
||
for (const event of events) { | ||
console.log('Event message:', event.data); | ||
} | ||
``` | ||
|
||
## Retrieving events with filters | ||
When retrieving events from a Massa smart contract, you can apply various filters to specify which events to retrieve. These filters allow you to narrow down the results based on specific criteria, such as the contract address, caller address, event time range, and whether the events are final or error-related. This flexibility helps you target the most relevant events for monitoring, debugging, or analysis. | ||
|
||
### Available event Filters | ||
|
||
Here are the different optional filters you can use when retrieving events: | ||
|
||
1. `start` and `end` (Slot): | ||
|
||
- Specifies the time range for events to retrieve, based on slots. | ||
- `start`: The slot at which to begin retrieving events. | ||
- `end`: The slot at which to stop retrieving events. | ||
- These filters are useful for fetching events that occurred within a specific timeframe. | ||
|
||
2. `smartContractAddress`: | ||
|
||
- Filters events by the specific smart contract address that emitted them. | ||
- This is essential for retrieving events from a particular contract, especially in environments with multiple contracts. | ||
|
||
3. `callerAddress`: | ||
|
||
- Filters events by the address that called the smart contract. | ||
- Use this filter to track interactions made by a specific user or another smart contract, providing insights into usage patterns or access control. | ||
|
||
4. `operationId`: | ||
|
||
- Filters events by a specific operation ID, allowing you to trace events related to a particular operation. | ||
- This is useful for tracking the full lifecycle of an operation, including any events it triggered. | ||
|
||
5. `isFinal`: | ||
|
||
- Filters events based on whether they are final or not. | ||
- Set to true to retrieve only final events, which are confirmed and immutable, or false to get non-final events that are still subject to change. | ||
|
||
6. `isError`: | ||
|
||
- Filters events by error status. | ||
- Set to true to retrieve only error events, which can help identify failed operations, or false to retrieve only successful events. | ||
|
||
## Event poller | ||
The [@massalabs/massa-web3](https://github.com/massalabs/massa-web3) library includes an event poller, that facilitates continuous monitoring of smart contract events by polling for updates at regular intervals. This feature is particularly useful for applications that require real-time event tracking or need to respond dynamically to contract activities. | ||
|
||
### How the event poller works | ||
The event poller retrieves events from the Massa blockchain using specified filters and invokes callback functions for handling incoming data and errors. You can configure the poller with various parameters, such as the smart contract address and the polling interval, to tailor its behavior to your needs. | ||
|
||
### Example: Setting up an event poller | ||
Below is an example of how to use the EventPoller from [@massalabs/massa-web3](https://github.com/massalabs/massa-web3 to continuously fetch and log events for a specific smart contract. This code sets up a poller that checks for new events every 5 seconds, processes each event, and stops if an error occurs: | ||
|
||
```ts | ||
// typescript | ||
let stop = false; | ||
|
||
// Callback function for handling incoming events | ||
const onData = async (events: SCEvent[]) => { | ||
for (const event of events) { | ||
console.log( | ||
`Event period: ${event.context.slot.period} thread: ${event.context.slot.thread} -`, | ||
event.data, | ||
); | ||
} | ||
}; | ||
|
||
// Callback function for handling errors | ||
const onError = (error: Error) => { | ||
console.error('Error:', error); | ||
stop = true; // Stop polling in case of an error | ||
}; | ||
|
||
// Start the event poller with a 5-second interval | ||
const { stopPolling } = EventPoller.start( | ||
provider, | ||
{ | ||
smartContractAddress: CONTRACT_ADDR, // Replace with your contract address | ||
}, | ||
onData, | ||
onError, | ||
5000, // Polling interval in milliseconds | ||
); | ||
|
||
// Continue polling until stopped | ||
while (!stop) { | ||
await scheduler.wait(5000); | ||
} | ||
stopPolling(); // Stop polling once the loop terminates | ||
``` |
Oops, something went wrong.