Best Practices for Casper Smart Contract Authors
At its core, the Casper platform is software, and best practices for general software development will apply. However, there are specific variables and situations that should be considered when developing for a Casper network. For example, a smart contract installed on global state cannot access file systems or open a connection to external resources.
Data Efficiency
When developing on Casper, a policy of efficient data usage will ensure the lowest possible cost for on-chain computation. To this end, minimizing the number of necessary transactions will drastically decrease the overall cost.
When creating smart contracts, including an explicit initialization entry point allows the contract to self-initialize without a subsequent transaction of session code. This entry point creates the internal structure of the contract and cannot be called after the initial transaction. Below is an example of a self-initalizing entry point that can be used within the call
function.
Example Self-initialization Entry Point
// This entry point initializes the donation system, setting up the fundraising purse
// and creating a dictionary to track the account hashes and the number of donations
// made.
#[no_mangle]
pub extern "C" fn init() {
let fundraising_purse = system::create_purse();
runtime::put_key(FUNDRAISING_PURSE, fundraising_purse.into());
// Create a dictionary to track the mapping of account hashes to number of donations made.
storage::new_dictionary(LEDGER).unwrap_or_revert();
}
pub extern "C" fn call() {
let init_entry_point = EntryPoint::new(
ENTRY_POINT_INIT,
vec![],
CLType::Unit,
EntryPointAccess::Public,
EntryPointType::Contract,
);
Bear in mind, the host node will not enforce this. The smart contract author must create the entry point and ensure it cannot be called after initial transaction.
Costs
Computations occurring on-chain come with associated gas costs. Efficient coding can help to minimize gas costs, through the reduction of overall Wasm sent to global state. Beginning with 1.5.0, even invalid Wasm will incur gas costs when sent to global state. As such, proper testing prior to sending a transaction is critical.
Further, there is a set cost of 2.5 CSPR to create a new purse. If possible, the reuse of purses should be considered to reduce this cost. If reusing purses, proper access management must be maintained to prevent lapses in security. Ultimately, any choices made in regards to security and contract safeguards rely on the smart contract author.
Tips to reduce WASM size
Transactions have a maxim size specified in each network chainspec as max_transaction_size = 1_048_576
. For example, networks running node version 2.0, have the following maximum transaction size in bytes:
max_transaction_size = 1_048_576
Here are a few tips to reduce the size of Wasm included in a transaction:
-
Build the smart contract in release mode. You will find an example here
cargo build --release --target wasm32-unknown-unknown
-
Run
wasm-strip
on the compiled code (see WABT). You will find an example herewasm-strip target/wasm32-unknown-unknown/release/contract.wasm
-
Don't enable the
std
feature when linking to thecasper-contract
orcasper-types
crates using the#![no_std]
attribute, which tells the program not to import the standard libraries. You will find an example here and further details here#![no_std]
-
Build the contract with
codegen-units
set to 1 by addingcodegen-units = 1
to the Cargo.toml under[profile.release])
. You will find an example here -
Build the contract with link-time optimizations enabled by adding
lto = true
to the Cargo.toml under[profile.release]
. You will find an example here
Inlining
As often as practicable, developers should inline functions by including the body of the function within their code rather than making call
or call_indirect
to the function. In the context of coding for Casper blockchain purposes, this reduces the overhead of executed Wasm and prevents unexpected errors due to exceeding resource tolerances.
Testing
Testing all transactions prior to committing them to Mainnet can assist authors in detecting bugs and inefficiencies prior to incurring gas fees. Casper provides several methods of testing, including unit testing, testing using NCTL and sending transactions to Testnet.
Information on these processes can be found at the following locations:
Additionally, the following two tutorials outline sending an example contract using both NCTL and Testnet: