Stylus Integration
Keystone’s no_std core makes it compatible with Arbitrum Stylus smart contracts. This guide covers building and deploying Stylus contracts that use Keystone for precision arithmetic.
Prerequisites
Install Rust and WASM target
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
Install cargo-stylus
cargo install cargo-stylus
Get testnet ETH
For deployment, you’ll need Arbitrum Sepolia ETH from a faucet.
Project Setup
Cargo.toml
[package]
name = "my-stylus-contract"
version = "0.1.0"
edition = "2021"
[workspace]
[dependencies]
precision-core = { version = "0.1", default-features = false }
stylus-sdk = "0.10"
alloy-primitives = "1.3"
[features]
default = ["export-abi"]
export-abi = ["stylus-sdk/export-abi"]
[lib]
crate-type = ["lib", "cdylib"]
[profile.release]
opt-level = "z"
lto = true
codegen-units = 1
panic = "abort"
strip = true
Contract Structure
#![allow(unused)]
#![cfg_attr(not(feature = "export-abi"), no_main, no_std)]
fn main() {
extern crate alloc;
use alloc::vec::Vec;
use alloy_primitives::U256;
use precision_core::{Decimal, RoundingMode};
use stylus_sdk::prelude::*;
sol_storage! {
#[entrypoint]
pub struct MyContract {
uint256 some_value;
}
}
#[public]
impl MyContract {
pub fn my_function(&self, input: U256) -> Result<U256, Vec<u8>> {
// Use Keystone precision arithmetic
let decimal = u256_to_decimal(input);
let result = decimal.checked_mul(Decimal::from(2i64))
.ok_or_else(|| b"overflow".to_vec())?;
Ok(decimal_to_u256(result))
}
}
}
U256 Conversion
Stylus uses U256 for numbers, while Keystone uses Decimal. Convert between them:
#![allow(unused)]
fn main() {
const SCALE: u64 = 1_000_000_000_000_000_000; // 1e18
fn u256_to_decimal(value: U256) -> Decimal {
let lo: u128 = value.as_limbs()[0] as u128
| ((value.as_limbs()[1] as u128) << 64);
let raw = Decimal::from(lo);
raw.checked_div(Decimal::from(SCALE))
.unwrap_or(Decimal::MAX)
}
fn decimal_to_u256(value: Decimal) -> U256 {
let scaled = value
.checked_mul(Decimal::from(SCALE))
.unwrap_or(Decimal::MAX)
.round(0, RoundingMode::TowardZero);
let (mantissa, _scale) = scaled.to_parts();
U256::from(mantissa.unsigned_abs())
}
}
Building
Check compilation
cargo stylus check
Build for release
cargo build --release --target wasm32-unknown-unknown
Export ABI
cargo stylus export-abi
This generates Solidity-compatible ABI for your contract.
Deployment
Deploy to Arbitrum Sepolia
cargo stylus deploy \
--private-key-path=./key.txt \
--endpoint=https://sepolia-rollup.arbitrum.io/rpc
The CLI will output your contract address after deployment.
Deploy to Arbitrum One (mainnet)
cargo stylus deploy \
--private-key-path=./key.txt \
--endpoint=https://arb1.arbitrum.io/rpc
Contract Interaction
Using Foundry Cast
Read-only call:
cast call <address> "myFunction(uint256)(uint256)" 1000000000000000000 \
--rpc-url https://sepolia-rollup.arbitrum.io/rpc
Transaction:
cast send <address> "myFunction(uint256)" 1000000000000000000 \
--private-key <key> \
--rpc-url https://sepolia-rollup.arbitrum.io/rpc
Example Contracts
Keystone provides three reference implementations:
stylus-lending
Health factor, liquidation price, and max borrow calculations for lending protocols.
cd examples/stylus-lending
cargo stylus check
stylus-amm
Constant product AMM calculations: swap output, price impact, liquidity math.
cd examples/stylus-amm
cargo stylus check
stylus-vault
ERC4626-style vault calculations: share price, compound yield, APY conversion.
cd examples/stylus-vault
cargo stylus check
Best Practices
Error Handling
Use checked arithmetic and return errors as Vec<u8>:
#![allow(unused)]
fn main() {
let result = a.checked_mul(b)
.ok_or_else(|| b"multiplication overflow".to_vec())?;
}
Precision
Keystone provides 28 significant digits. For most DeFi use cases, this exceeds requirements.
Gas Optimization
- Use
opt-level = "z"for smallest WASM size - Enable LTO and strip debug info
- The 24KB compressed size limit requires efficient code
Rounding
Choose rounding modes appropriate for financial context:
HalfUp: Traditional rounding (0.5 rounds up)HalfEven: Banker’s rounding (0.5 rounds to nearest even)TowardZero: Truncation (always toward zero)Down: Floor (always toward negative infinity)
Troubleshooting
Contract too large
If your contract exceeds 24KB:
- Remove unused dependencies
- Use
#[inline(never)]on rarely-called functions - Consider splitting into multiple contracts
Build failures
Ensure all dependencies support no_std:
precision-core = { version = "0.1", default-features = false }
Deployment fails
- Verify you have sufficient ETH for gas
- Check RPC endpoint is accessible
- Ensure private key file has correct format