# Elrond dev cheat sheet ## Smart contracts in Rust All contracts need to begin with the following imports ```rust #![no_std] #![no_main] #![allow(unused_attributes)] imports!(); ``` The imports!() macro comes from the elrond-wasm crate and automatically imports everything you need. It includes the following ```rust use elrond_wasm::{Box, Vec, String, VarArgs, SCError, BorrowedMutStorage}; use elrond_wasm::{H256, Address, StorageKey, ErrorMessage}; use elrond_wasm::{ContractHookApi, ContractIOApi, BigIntApi, BigUintApi, OtherContractHandle, AsyncCallResult, AsyncCallError}; use elrond_wasm::esd_light::{Encode, Decode, DecodeError}; use elrond_wasm::io::*; use elrond_wasm::err_msg; use core::ops::{Add, Sub, Mul, Div, Rem}; use core::ops::{AddAssign, SubAssign, MulAssign, DivAssign, RemAssign}; use core::ops::{BitAnd, BitOr, BitXor, Shr, Shl}; use core::ops::{BitAndAssign, BitOrAssign, BitXorAssign, ShrAssign, ShlAssign}; ``` The contract logic itself is contained within a trait annotated as follows: ```rust #[elrond_wasm_derive::contract(SimpleCoinImpl)] pub trait SimpleCoin { // snip } ``` All appropriately annotated functions within this trait form the public interface of the smart contract. ### #[init] ```rust #[init] fn init(&self, ...<optional other args>) { // snip } ``` There must be only one function annotated with `#[init]`. This called once only when the contract is deployed and can be used to set initial values in the storage. Like all contract functions it has access to `self` which exposes info and functions on the contract. This can be used to call other functions or some of the exposed functionality such as `self.get_caller()`. ### #[view] ```rust #[view] fn balance_of(&self, subject: Address) -> BigUint { // snip } ``` These are generic public exposed methods that cannot mutate the state, only provide a convenient way to read certain parts of the state. These do not cost gas to call. The attribute can take an optional argument to rename the exposed contract method ```rust #[view("balanceOf")] fn balance_of(&self, subject: Address) -> BigUint { // snip } ``` ### #[endpoint] ```rust #[endpoint] fn transfer(&self, to: Address, amount: BigUint) -> Result<(), &str> { // snip } ``` These are generic public exposed functions that can be called on the smart contract by making transactions. These are allowed to mutate the contract state and therefore cost gas. The attribute can also take an optional argument to change the name of the exposed contract method from that implemented in the Rust code e.g. `#[endpoint(transferFunds)]`. Endpoints can also be `payable` that is to say the transaction to call them can also include ERD which the contract can accept and possibly transfer. By default, any function in a smart contract is not payable, i.e. sending a sum of ERD to the contract using the function will cause the transaction to be rejected. Payable functions need to be annotated with ``#[payable]``. Notice the `#[payment] payment: &BigUint` argument. This is not a real argument, but just syntactic sugar to pass the payed sum to the function. ```rust #[payable] #[endpoint] fn topUp(&self, #[payment] payment: BigUint) { // snip } ``` ### #[storage_set("<storage-key>")] ```rust // example with a single value #[storage_set("owner")] fn set_owner(&self, address: &Address); // example of setting a key-value pair in the store #[storage_set("balance")] fn set_balance(&self, address: &Address, balance: &BigUint); ``` This is used to annotate a function without a body and convert it into a setter for a particular key in the contract storage. Providing multiple key arguments makes it possible to define a map in the storage. all but the last argument will become part of the storage key. Any number of such key arguments can be added. The resulting storage key will be a concatenation of the specified base key "<storage-key>" and the serialized argument delimited by a pipe ('|') character. ### #[storage_get("<storage-key>")] ```rust #[storage_get("owner")] fn get_owner(&self) -> Address; ``` Pretty self explanatory, can be used to read a value from the contract store. There is also the `#storage_get_mut` macro. This will return a mutable reference to a variable in the store. This variable can be modified and when it goes out of scope it will mutate the store. ```rust #[storage_get_mut("allowance")] fn get_mut_allowance(&self, owner: &Address, spender: &Address) -> mut_storage!(BigUint); ``` ### #[event] ```rust #[event("0x0000000000000000000000000000000000000000000000000000000000000001")] fn transfer_event(&self, sender: &Address, recipient: &Address, amount: &BigUint); ``` Contracts can trigger events which the node running them can listen for. The event macro takes a single argument which is the local address for the event (?). Events are function and can accept multiple arguments of accepted types which form the topics of the event. Events can be triggered from elsewhere in the contract by calling them on self ```rust // trigger an event self.transfer_event(&sender, &recipient, &amount); ``` ## Modules As contracts get larger it is nice to split them into multiple files. This is where modules come in. Declaring a module is similar to declering a contract. ```rust #[elrond_wasm_derive::module(PauseModuleImpl)] pub trait PauseModule { // snip } ``` This module can then be included in a contract by importing it in the rust .lib file and then using the `#module` attribute macro on a function within the contract trait. ```rust pub mod pause; use crate::pause::*; #[elrond_wasm_derive::contract(ContractImpl)] pub trait Contract { #[module(PauseModuleImpl)] fn pause(&self) -> PauseModuleImpl<T, BigInt, BigUint>; // snip - other contract logic } ``` Modules can be used inside other modules if their functionality is required. Presumably this will be de-duped at compile time by the contract macro. Functions in other modules can be called by first calling their module function. For example in the main contract the call functions can be called as ```rust self.pause().pause_contract() ``` ## The contract API All of the following methods are available on `self` during contract function execution. Most are pretty self explanatory ```rust fn get_sc_address(&self) -> Address; fn get_owner_address(&self) -> Address; fn get_caller(&self) -> Address; fn get_balance(&self, address: &Address) -> BigUint; fn get_sc_balance(&self) -> BigUint; fn storage_store(&self, key: &[u8], value: &[u8]); fn storage_load(&self, key: &[u8]) -> Vec<u8>; fn storage_load_len(&self, key: &[u8]) -> usize; fn storage_store_bytes32(&self, key: &[u8], value: &[u8; 32]); fn storage_load_bytes32(&self, key: &[u8]) -> [u8; 32]; fn storage_store_big_uint(&self, key: &[u8], value: &BigUint); fn storage_load_big_uint(&self, key: &[u8]) -> BigUint; fn storage_store_big_int(&self, key: &[u8], value: &BigInt); fn storage_load_big_int(&self, key: &[u8]) -> BigInt; fn storage_store_i64(&self, key: &[u8], value: i64); fn storage_load_i64(&self, key: &[u8]) -> Option<i64>; fn storage_load_cumulated_validator_reward(&self) -> BigUint fn get_call_value_big_uint(&self) -> BigUint; fn send_tx(&self, to: &Address, amount: &BigUint, message: &str); fn async_call(&self, to: &Address, amount: &BigUint, data: &[u8]); fn get_tx_hash(&self) -> H256; fn get_gas_left(&self) -> i64; fn get_block_timestamp(&self) -> u64; fn get_block_nonce(&self) -> u64; fn get_block_round(&self) -> u64; fn get_block_epoch(&self) -> u64; fn sha256(&self, data: &[u8]) -> [u8; 32]; fn keccak256(&self, data: &[u8]) -> [u8; 32]; ``` ## Calling other contracts Calling between contracts is a little more involved in Elrond compared with Ethereum. This is presumable due to the complexity of making calls between shards? (unconfirmed). In Elrond calls are made asynchronously so rather than blocking the current execution a callback must be provided to follow up on the results of a cross-contract call. Before you can make a call to another contract you must first define a new Rust trait called a 'proxy'. This is a type safe way to describe the functions you will be calling in another contract and also a way to wire up the callback. It is not nesseary to re-implement all of the public function in the other contract, just the ones you want to call. Say we had an ERC20 style contract and we wanted to call to transfer some tokens. The proxy we would need to define is as follows: ```rust #[elrond_wasm_derive::callable(TokenTransferProxy)] pub trait TokenTransfer { fn transfer( &self, to: &Address, token_amount: BigUint, // don't use 'amount' as a variable name it bugs out! ); } ``` Using this to call the function in a foreign contract requires we know its address. In our own contract code we can make the call by calling ```rust let token_contract = contract_proxy!(self, contract_address, TokenTransfer); token_contract.transfer( &to_address, amount, ); ``` ### Callbacks As mentioned this call will happen asynchronously. It will not block the current execution or yield a result. In this case we need to use callbacks to change our contract state based on the success/failure of a foreign contract call. Callbacks are registered on the proxy trait using the `#[callback(<callback_fn_name>)]` attrubute. To set up a callback to run after a token transfer ```rust #[elrond_wasm_derive::callable(TokenTransferProxy)] pub trait TokenTransfer { #[callback(token_transfer_callback)] fn transfer( &self, to: &Address, token_amount: BigUint, ); } ``` and inside our contract trait ```rust #[callback] fn token_transfer_callback( &self, call_result: AsyncCallResult<()> ){ if let AsyncCallResult::Ok(()) = call_result { // This will be executed on a successful transfer! } } ``` One problem with this configuration is it doesn't distinguish between contract calls. In this case we have no way of knowing who the token were transferred to in the callback code. To allow this the SDK includes callback args. These are special arguments that can be added to the proxy that will be passed directly to the callback when it is invoked. For example if we want the callback to know who the tokens were transferred to ```rust #[elrond_wasm_derive::callable(TokenTransferProxy)] pub trait TokenTransfer { #[callback(token_transfer_callback)] fn transfer( &self, #[callback_arg] cb_to_address: &Address, to: &Address, token_amount: BigUint, ); } ``` and inside our contract trait ```rust #[callback] fn token_transfer_callback( &self, call_result: AsyncCallResult<()>, #[callback_arg] cb_to_address: &Address, ){ if let AsyncCallResult::Ok(()) = call_result { // Now we know who the transfer was to! } } ``` Now we need to make the call as ```rust token_contract.transfer( &to_address, // cb_to_address passed to callback only &to_address, // to_address passed to contract call amount, // passed to contract call ); ``` ## Writing tests ### Scenario testing framework The Elrond SDK ships with a custom testing framework that executes the contract WASM on the Arwen VM to simulate a real network with multiple agents. The tests are defined as json files describing the steps to run in and checks to make. The general form for a `.scen.json` object is: ```javascript { "name": "<name of the test></name>", "steps": [ { "step": "<step type>", // other fields depending on step type } ] } ``` The following step types are supported. There may be more that I don't yet know about #### setState This is a powerful step that can fully define the state of the blockchain. It can create accounts or contracts, set the state of those accounts/contracts and probably more. It can contain the following sub-fields ##### accounts This is a map between addresses and accounts. Each account can be defined with initial nonce, balance, storage and code. This means contract or regular accounts can be defined. It is probably better practice to use `"scDeploy"` to deploy contracts so that the init function is run. ```javascript "accounts": { "<address></address>": { "nonce": "0", "balance": "0xe8d4a51000", "storage": {"<storage-key>": "0xvalue"}, "code": "path/to/wasm" } } ``` ##### newAddresses This is a helper to make tests easier to read and remove hard-coded addresses that need to change. ```javascript "newAddresses": [ { "creatorAddress": "<address making a call>", "creatorNonce": "0", // nonce of call "newAddress": "<address to yield when creator makes a call with the given nonce>" } ] ``` Basically it is setting up the VM to produce `newAddress` as the address of the transaction by `creatorAddress` with `creatorNonce`. The must useful application of this is to ensure contracts get deployed to a particular address which is then called later. ##### #### scDeploy Deploy a smart contract example: ```javascript { "step": "scDeploy", "tx": { "from": "<deploying agent address>", "contractCode": "file:path/to/contract.wasm", "value": "0", // if the init is payable "arguments": [ // passed to contract init "500,000,000,000", "123,000" ], "gasLimit": "1000000", "gasPrice": "0" }, "expect": { "status": "0", "gas": "*", "refund": "*" } } ``` #### scCall Call a function on a particular smart contract address example ```javascript { "step": "scCall", "txId": "fund-1", "tx": { "from": "''donor_1_________________________", "to": "''the_____crowdfunding____contract", "value": "250,000,000,000", "function": "fund", "arguments": [], "gasLimit": "100,000,000", "gasPrice": "0" }, "expect": { "status": "", "gas": "*", "refund": "*" } } ``` #### expect Not a step in itself but the `scCall` and `scDeploy` steps can also include an `expect` field with the following structure. The test will fail if these are not met. ```javscript "expect": { "out": [ "2" ], // return from call "status": "0", // 0 => ok, 4 => error "gas": "*", // "*" implies anything goes "refund": "*", "logs": [], // any events are listed here } ``` The Logs field lists the events triggered in the call with the form: ```javascript { "address": "<source contract/account>", "identifier": "0x0000000000000000000000000000000000000000000000000000000000000001", "topics": [ // pre-args in event "``account_1_____________________s1", "``account_1_____________________s1" ], "data": "0x17" // final arg in event } ``` #### checkState This is non state mutating step that only checks the state of any of the accounts/contracts. example: ```javascript { "step": "checkState", "accounts": { "''the_____crowdfunding____contract": { "nonce": "0", // the current account nonce "balance": "250,000,000,000", "storage": { "''deposit|''donor_1_________________________": "250,000,000,000" // note the "<key1>|<key2>|..." format for multi key storage }, "code": "file:../output/crowdfunding.wasm" } } } ``` #### externalSteps `"externalSteps"` allows us to import steps from another json file. This is very handy, because we can write test scenarios that branch out from each other without having to duplicate code. Here we will be reusing the deployment steps in all tests. These imported steps get executed again each time they are imported. ```javascript { "step": "externalSteps", "path": "other-steps.json" } ``` If the other steps are in a `.json` rather than a `.scen.json` they will not be run on their own by the test runner which is most likely the desired result. ### Rust tests It is also possible to write tests in Rust using the `elrond-wasm-debug` crate. These do not run the contract logic after it is compiled to WASM but rather natively compile the rust code with a test simulated blockchain. Because of this they are not as true-to-real test. Note: These are not practical at this stage as it is not possible to call function that accept BitUint as an argument. ## Encodings ### BigUint Elrond deals exclusively in raw bytes. For fixed length types such as `u32`, `bool` etc it is easy to write these when encoding parameters for a contract call. Things get a bit more interesting when passing variable length types such BigUint which are variable length. In this case the first 4 bytes encode the length (big endian). The remainder of the encoding should contain as many bytes as specified and these encode the number. In the Mandos tests these can be written using concatenation syntax. `0x00000003|0x112233` represents a BigUint that is three bytes long and those three bytes are 0x112233 (1,122,867 in decimal). ### Structured Data Rust structs can be used as parameters for contract calls and the macro will automatically deal with deserializing these from the raw calling bytes. In a struct the fields are effectively concatenated together to form the calling bytes. e.g. ```json { "sender_address": "''bob_____________________________", "amount": "0x01", "nonce": "0" } ``` is expressed as `''bob_____________________________|0x001|0`