Skip to main content
Version: Next

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:

  1. Build the smart contract in release mode. You will find an example here

    cargo build --release --target wasm32-unknown-unknown
  2. Run wasm-strip on the compiled code (see WABT). You will find an example here

    wasm-strip target/wasm32-unknown-unknown/release/contract.wasm
  3. Don't enable the std feature when linking to the casper-contract or casper-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]
  4. Build the contract with codegen-units set to 1 by adding codegen-units = 1 to the Cargo.toml under [profile.release]). You will find an example here

  5. 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: