Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Security Considerations

Migrating from Solana to Stylus changes your threat model. The EVM environment exposes different attack surfaces and patterns. This chapter details the security checks and hardening steps for migrated contracts.

In stateless Solana programs, a potential attacker controls all of the input data that the program operates over. Extreme care has to be taken to verify keys, PDAs and accounts to ensure that an attacker cannot spoof their way into stealing program controlled funds or creating invalid account states.

By contrast, Stylus contracts solely control their own storage, which can be considered trusted. They must take care to verify sender access permissions and validate function parameters.

Contract reentrancy

Unlike EVM contracts, all Stylus contract entrypoints have reentrancy disabled by default. This is implemented in the code generated by the #[entrypoint] macro, which expands to:

#[no_mangle]
pub extern "C" fn user_entrypoint(len: usize) -> usize {
    let host = stylus_sdk::host::VM(stylus_sdk::host::WasmVM {});
    if host.msg_reentrant() {
        // Returning 1 indicates an error occured and the transaction will be reverted
        return 1;
    }
    host.pay_for_memory_grow(0);
    let input = host.read_args(len);
    let (data, status) = match __stylus_struct_entrypoint(input, host.clone()) {
        Ok(data) => (data, 0),
        Err(data) => (data, 1),
    };
    host.flush_cache(false);
    host.write_result(&data);
    status
}

Enabling the reentrant feature for stylus-sdk instructs the #[entrypoint] macro to not generate the blanket reentrancy check. In the rare occasion that reentrancy is required for functions, extreme care must be taken to ensure non-reentrant functions manually deny reentrant calls by using the MessageAccess::msg_reentrant check. Any reentrant function needs to ensure that any storage writes are performed and the storage cache is explicitly flushed before making the external call.

Integer Arithmetic Overflow

By far the most commonly discovered vulnerability in Stylus audits is the use of unchecked arithmetic functions (+, -, *, <<, >> and their associated trait methods in std::ops) on integer types, which silently wrap around in release builds.

It is best practice to used the explicitly checked variants of arithmetic functions, by convention named checked_* where * is a placeholder for the operation. The contract should then either explicitly panic if an overflow should be impossible given the business logic invariants or otherwise return an error, reverting the transaction in both cases.

Examples of Stylus audit findings concerning integer arithmetic overflow include:

Sender Authorization

When employing access control patterns, ensure that MessageAccess::msg_sender is used when checking that the caller matches the stored authority address.

The exception to this rule is when you are retrieving the creator address of a contract within the constructor function, in this case MessageAccess::tx_origin should be used because MessageAccess::msg_sender returns the Stylus contract factory address in production.