diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml index d12f2cb..b05d760 100644 --- a/.github/workflows/check-examples.yml +++ b/.github/workflows/check-examples.yml @@ -21,4 +21,6 @@ jobs: chmod +x scripts/check-examples.sh - name: Check Fe code examples + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: npm run check:examples:verbose diff --git a/.gitignore b/.gitignore index 3df343d..a4fe545 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # build output dist/ +bin/ # generated types .astro/ @@ -21,4 +22,4 @@ pnpm-debug.log* .DS_Store # Open Spec -openspec/changes/ \ No newline at end of file +openspec/changes/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ede0d08..7a89321 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -157,17 +157,27 @@ For language behavior not covered in docs, consult the [Fe compiler source](http ## Updating the Fe Binary -The Fe binary is stored in `bin/`. A platform-detecting wrapper is at `scripts/fe`. +The Fe compiler is resolved dynamically via `scripts/fe`. -To update the compiler: +Behavior: +- On first use, `scripts/fe` fetches the latest release from `argotorg/fe` and caches it in `bin/`. +- It stores cache metadata in `bin/.fe-version` and `bin/.fe-last-check`. +- By default it only re-checks for latest releases every 6 hours. -1. Build Fe from source or obtain a new binary -2. Copy to `bin/fe-linux-x86_64` (or appropriate platform) -3. Ensure it's executable: `chmod +x bin/fe-linux-x86_64` -4. Run `bash scripts/check-examples.sh` to verify compatibility -5. Commit the updated binary +Useful commands: -Note: Only Linux x86_64 is currently supported for local checking. CI runs on Linux. +```bash +# Validate all docs code examples using the wrapper +bash scripts/check-examples.sh + +# Force an immediate latest-release check +FE_FORCE_LATEST_CHECK=1 ./scripts/fe check path/to/file.fe +``` + +Environment variables: +- `GITHUB_TOKEN`: used for authenticated GitHub API requests (recommended in CI) +- `FE_LATEST_TTL_SECONDS`: override metadata freshness window (default: `21600`) +- `FE_FORCE_LATEST_CHECK=1`: bypass freshness and force an API latest check ## Site Customization diff --git a/bin/fe-linux-x86_64 b/bin/fe-linux-x86_64 deleted file mode 100755 index 3a7de97..0000000 Binary files a/bin/fe-linux-x86_64 and /dev/null differ diff --git a/openspec/project.md b/openspec/project.md index d49f9e9..72767d1 100644 --- a/openspec/project.md +++ b/openspec/project.md @@ -13,20 +13,25 @@ ## Fe Compiler -The Fe compiler binary is located in the `bin/` directory. A platform-detecting wrapper script is available at `scripts/fe`: +Use the wrapper script at `scripts/fe` for all compiler invocations. The wrapper downloads the latest Fe release on demand, caches it in `bin/`, and reuses cached metadata to avoid repeated GitHub API calls. ```bash # Type-check a Fe file using the wrapper (recommended) ./scripts/fe check path/to/file.fe -# Or use the binary directly (Linux x86_64 only) -./bin/fe-linux-x86_64 check path/to/file.fe - # Validate all documentation code snippets bash scripts/check-examples.sh + +# Optional: force a latest-release check immediately +FE_FORCE_LATEST_CHECK=1 ./scripts/fe check path/to/file.fe ``` -This local compiler should be used instead of any system-installed version to ensure consistency with the documented language features. +Environment variables: +- `GITHUB_TOKEN`: used for authenticated GitHub API requests (recommended in CI to avoid low unauthenticated rate limits) +- `FE_LATEST_TTL_SECONDS`: freshness window for latest-release checks (default: `21600`) +- `FE_FORCE_LATEST_CHECK=1`: bypass freshness window and force a latest check + +This local compiler wrapper should be used instead of any system-installed version to ensure consistency with the documented language features. ## Project Conventions diff --git a/scripts/boilerplate.fe b/scripts/boilerplate.fe index 0934e05..41e7a49 100644 --- a/scripts/boilerplate.fe +++ b/scripts/boilerplate.fe @@ -3,11 +3,12 @@ // Snippets can import what they need with: use _boilerplate::Map mod _boilerplate { - // Re-export StorageMap from core - pub use core::StorageMap - - // Re-export intrinsics from core - pub use core::{caller, revert, keccak, sload, sstore} + // Intrinsic compatibility shims for docs snippets. + pub fn caller() -> u256 { todo() } + pub fn revert(_ offset: u256, _ size: u256) { todo() } + pub fn keccak(_ data: T) -> u256 { todo() } + pub fn sload(_ slot: u256) -> u256 { todo() } + pub fn sstore(_ slot: u256, _ value: u256) { todo() } // Map type stub (for non-storage contexts) pub struct Map {} @@ -68,10 +69,24 @@ mod _boilerplate { pub fn block_hash(_ number: u256) -> u256 { todo() } // Address type stub - pub struct Address {} + pub struct Address { inner: u256 } impl Address { - pub fn zero() -> Self { Address {} } + pub fn zero() -> Self { Address { inner: 0 } } + pub fn encode(own self, _ e: mut E) { todo() } + pub fn decode(_ d: mut D) -> Self { todo() } + pub fn as_topic(self) -> u256 { self.inner } + } + impl core::abi::Encode for Address { + fn encode>(own self, _ e: mut E) { todo() } + } + impl core::abi::Decode for Address { + fn decode>(_ d: mut D) -> Self { todo() } + } + impl std::abi::SolCompat for Address { + type S = String<7> + const SOL_TYPE: Self::S = "address" } + impl Copy for Address {} impl core::ops::Eq for Address { fn eq(self, _ other: Self) -> bool { todo() } } diff --git a/scripts/check-examples.sh b/scripts/check-examples.sh index 2cb53de..3a872c2 100755 --- a/scripts/check-examples.sh +++ b/scripts/check-examples.sh @@ -6,6 +6,7 @@ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$SCRIPT_DIR/.." +BOILERPLATE_FILE="$SCRIPT_DIR/boilerplate.fe" # Configuration VERBOSE=false @@ -84,6 +85,8 @@ TOTAL_BLOCKS=$(wc -l < "$MAPPINGS_FILE") if [[ "$VERBOSE" == true ]]; then echo "Found $TOTAL_BLOCKS Fe code blocks to check" echo "" +else + echo "Checking $TOTAL_BLOCKS Fe code blocks (use --verbose for live progress)..." fi # Track errors @@ -92,6 +95,22 @@ CHECKED=0 PASSED=0 FAILED=0 +prepare_standalone_check_file() { + local source_file="$1" + local output_file="$2" + + if [[ -f "$BOILERPLATE_FILE" ]]; then + cat "$BOILERPLATE_FILE" > "$output_file" + echo "" >> "$output_file" + echo "// --- standalone source below ---" >> "$output_file" + echo "" >> "$output_file" + cat "$source_file" >> "$output_file" + else + cp "$source_file" "$output_file" + fi + +} + # Check each extracted file while IFS=: read -r fe_file md_file block_start_line; do : $((CHECKED++)) @@ -154,7 +173,9 @@ if [[ -d "$EXAMPLES_DIR" ]] && [[ ${#FILES[@]} -eq 0 ]]; then echo -n "Checking $rel_fe... " fi - FE_OUTPUT=$("$SCRIPT_DIR/fe" check "$fe_file" 2>&1) || true + temp_check_file="$TEMP_DIR/standalone_$(basename "$fe_file")" + prepare_standalone_check_file "$fe_file" "$temp_check_file" + FE_OUTPUT=$("$SCRIPT_DIR/fe" check "$temp_check_file" 2>&1) || true if [[ -z "$FE_OUTPUT" ]]; then : $((PASSED++)) diff --git a/scripts/fe b/scripts/fe index fc3e648..7f92162 100755 --- a/scripts/fe +++ b/scripts/fe @@ -1,37 +1,231 @@ -#!/bin/bash -# Fe compiler wrapper script with platform detection -# Currently only Linux x86_64 is supported +#!/usr/bin/env bash +# Fe compiler wrapper with dynamic release resolution and local caching. -set -e +set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BIN_DIR="$SCRIPT_DIR/../bin" -# Detect platform -OS="$(uname -s)" -ARCH="$(uname -m)" - -case "$OS-$ARCH" in - Linux-x86_64) - FE_BINARY="$BIN_DIR/fe-linux-x86_64" - ;; - *) - echo "Error: Fe binary not available for $OS-$ARCH" >&2 - echo "" >&2 - echo "Currently only Linux x86_64 is supported." >&2 - echo "Options:" >&2 - echo " 1. Use a Linux machine or VM" >&2 - echo " 2. Use GitHub Codespaces or similar cloud environment" >&2 - echo " 3. Push changes and let CI validate the examples" >&2 - echo "" >&2 - exit 1 - ;; -esac - -if [[ ! -x "$FE_BINARY" ]]; then - echo "Error: Fe binary not found at $FE_BINARY" >&2 - echo "Please ensure the binary exists and is executable." >&2 +REPO="argotorg/fe" +RELEASE_API_URL="https://api.github.com/repos/$REPO/releases" +RELEASE_DOWNLOAD_BASE_URL="https://github.com/$REPO/releases/download" + +FE_BINARY="$BIN_DIR/fe" +VERSION_FILE="$BIN_DIR/.fe-version" +LAST_CHECK_FILE="$BIN_DIR/.fe-last-check" + +DEFAULT_TTL_SECONDS=21600 +TTL_SECONDS="${FE_LATEST_TTL_SECONDS:-$DEFAULT_TTL_SECONDS}" +FORCE_LATEST_CHECK="${FE_FORCE_LATEST_CHECK:-0}" + +warn() { + echo "Warning: $*" >&2 +} + +die() { + echo "Error: $*" >&2 exit 1 -fi +} + +require_command() { + local cmd="$1" + if ! command -v "$cmd" >/dev/null 2>&1; then + die "Required command '$cmd' is not available." + fi +} + +detect_asset_name() { + local os arch + os="$(uname -s)" + arch="$(uname -m)" + + case "$os-$arch" in + Linux-x86_64) + FE_ASSET_NAME="fe_linux_amd64" + ;; + Linux-aarch64) + FE_ASSET_NAME="fe_linux_arm64" + ;; + Darwin-x86_64) + FE_ASSET_NAME="fe_mac_amd64" + ;; + Darwin-arm64) + FE_ASSET_NAME="fe_mac_arm64" + ;; + *) + die "Fe binary is not supported on $os-$arch. Supported platforms: Linux x86_64/arm64, macOS x86_64/arm64." + ;; + esac +} + +is_positive_integer() { + local value="$1" + [[ "$value" =~ ^[0-9]+$ ]] +} + +has_cached_binary() { + [[ -x "$FE_BINARY" ]] +} + +is_metadata_fresh() { + local now last_check + + if [[ ! -f "$LAST_CHECK_FILE" ]]; then + return 1 + fi + + last_check="$(tr -d '[:space:]' < "$LAST_CHECK_FILE")" + if ! is_positive_integer "$last_check"; then + return 1 + fi + + now="$(date +%s)" + (( now - last_check < TTL_SECONDS )) +} + +mark_last_check_now() { + date +%s > "$LAST_CHECK_FILE" +} + +current_cached_tag() { + if [[ -f "$VERSION_FILE" ]]; then + tr -d '[:space:]' < "$VERSION_FILE" + fi +} + +curl_headers() { + local -a headers + headers=( + -H "Accept: application/vnd.github+json" + -H "X-GitHub-Api-Version: 2022-11-28" + -H "User-Agent: fe-guide-wrapper" + ) + + if [[ -n "${GITHUB_TOKEN:-}" ]]; then + headers+=(-H "Authorization: token ${GITHUB_TOKEN}") + fi + + printf '%s\n' "${headers[@]}" +} + +fetch_latest_tag() { + local response tag + local -a headers + mapfile -t headers < <(curl_headers) + + # Fetch the first page of releases (includes pre-releases), limited to 1 result + if ! response="$(curl --silent --show-error --fail --location "${headers[@]}" "${RELEASE_API_URL}?per_page=1")"; then + return 1 + fi + + # The response is a JSON array; extract the tag_name from the first entry + tag="$(printf '%s\n' "$response" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]\+\)".*/\1/p' | head -n1)" + if [[ -z "$tag" ]]; then + return 1 + fi + + printf '%s\n' "$tag" +} + +download_binary_for_tag() { + local tag="$1" + local url + url="$RELEASE_DOWNLOAD_BASE_URL/$tag/$FE_ASSET_NAME" + download_binary_from_url "$url" +} + + +download_binary_from_url() { + local url="$1" + local tmp_file + local -a headers + mapfile -t headers < <(curl_headers) + + tmp_file="$(mktemp "$BIN_DIR/.fe-download.XXXXXX")" + + if ! curl --silent --show-error --fail --location "${headers[@]}" --output "$tmp_file" "$url"; then + rm -f "$tmp_file" + return 1 + fi + + chmod +x "$tmp_file" + mv "$tmp_file" "$FE_BINARY" +} + +stamp_fallback_metadata() { + if [[ ! -s "$VERSION_FILE" ]]; then + printf '%s\n' "__unknown__" > "$VERSION_FILE" + fi + mark_last_check_now +} + +should_check_latest() { + if [[ "$FORCE_LATEST_CHECK" == "1" ]]; then + return 0 + fi + + if ! has_cached_binary; then + return 0 + fi + + if [[ ! -s "$VERSION_FILE" ]]; then + return 0 + fi + + if ! is_metadata_fresh; then + return 0 + fi + + return 1 +} + +main() { + local latest_tag cached_tag + + require_command curl + require_command sed + detect_asset_name + + if ! is_positive_integer "$TTL_SECONDS"; then + warn "FE_LATEST_TTL_SECONDS='$TTL_SECONDS' is invalid. Falling back to $DEFAULT_TTL_SECONDS." + TTL_SECONDS="$DEFAULT_TTL_SECONDS" + fi + + mkdir -p "$BIN_DIR" + + if ! should_check_latest; then + exec "$FE_BINARY" "$@" + fi + + if ! latest_tag="$(fetch_latest_tag)"; then + if has_cached_binary; then + warn "Failed to resolve latest Fe release tag. Falling back to cached binary." + stamp_fallback_metadata + exec "$FE_BINARY" "$@" + fi + + die "Failed to resolve latest Fe release tag and no cached binary is available." + fi + + cached_tag="$(current_cached_tag || true)" + if [[ "$cached_tag" == "$latest_tag" ]] && has_cached_binary; then + mark_last_check_now + exec "$FE_BINARY" "$@" + fi + + if ! download_binary_for_tag "$latest_tag"; then + if has_cached_binary; then + warn "Failed to download Fe release '$latest_tag'. Falling back to cached binary." + stamp_fallback_metadata + exec "$FE_BINARY" "$@" + fi + die "Failed to download Fe release '$latest_tag' and no cached binary is available." + fi + + printf '%s\n' "$latest_tag" > "$VERSION_FILE" + mark_last_check_now + + exec "$FE_BINARY" "$@" +} -exec "$FE_BINARY" "$@" +main "$@" diff --git a/src/content/docs/appendix/from-rust.md b/src/content/docs/appendix/from-rust.md index 4a6ef46..7cffc99 100644 --- a/src/content/docs/appendix/from-rust.md +++ b/src/content/docs/appendix/from-rust.md @@ -76,7 +76,7 @@ impl Hashable for Point { Type parameters work similarly: ```fe -fn identity(value: T) -> T { +fn identity(value: own T) -> T { value } @@ -85,7 +85,7 @@ struct Wrapper { } impl Wrapper { - fn get(self) -> T { + fn get(own self) -> T { self.value } } @@ -223,7 +223,7 @@ use _boilerplate::Storage // // Rust: fn modify(data: &mut Storage) // Fe: Effect declaration -fn modify() uses (mut storage: Storage) { +fn modify() uses (storage: mut Storage) { // let _ = storage // @@ -356,6 +356,7 @@ struct Storage { Blockchain events for logging: ```fe ignore +#[event] struct Transfer { #[indexed] from: Address, @@ -371,7 +372,7 @@ Explicit capability tracking: ```fe ignore fn transfer(from: Address, to: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { // Function declares what it accesses } diff --git a/src/content/docs/appendix/from-solidity.md b/src/content/docs/appendix/from-solidity.md index 0b0ed18..f1e8a88 100644 --- a/src/content/docs/appendix/from-solidity.md +++ b/src/content/docs/appendix/from-solidity.md @@ -173,8 +173,8 @@ function transfer(...) { ```fe // use _boilerplate::{Address, Log} -fn transfer(from: Address, to: Address, amount: u256) uses mut log: Log { // +#[event] struct Transfer { #[indexed] from: Address, @@ -183,14 +183,9 @@ struct Transfer { value: u256, } -// -} -fn _transfer(from: Address, to: Address, amount: u256) uses mut log: Log { -// +fn transfer(from: Address, to: Address, amount: u256) uses (log: mut Log) { log.emit(Transfer { from, to, value: amount }) -// } -// ``` ### Error Handling diff --git a/src/content/docs/appendix/intrinsics.md b/src/content/docs/appendix/intrinsics.md index 99099ec..b9e5618 100644 --- a/src/content/docs/appendix/intrinsics.md +++ b/src/content/docs/appendix/intrinsics.md @@ -188,6 +188,7 @@ Event emission: // use _boilerplate::{Address, Log} // +#[event] struct TransferEvent { #[indexed] from: Address, @@ -196,7 +197,7 @@ struct TransferEvent { value: u256, } -fn emit_transfer(from: Address, to: Address, value: u256) uses mut log: Log { +fn emit_transfer(from: own Address, to: own Address, value: u256) uses (log: mut Log) { log.emit(TransferEvent { from, to, value }) } ``` diff --git a/src/content/docs/appendix/keywords.md b/src/content/docs/appendix/keywords.md index 409877d..a6e46ec 100644 --- a/src/content/docs/appendix/keywords.md +++ b/src/content/docs/appendix/keywords.md @@ -107,7 +107,7 @@ return value ### Effects ```fe ignore fn foo() uses (storage: Storage) { } -fn foo() uses (mut storage: Storage) { } +fn foo() uses (storage: mut Storage) { } with (storage: Storage = store) { } ``` diff --git a/src/content/docs/compound-types/maps.md b/src/content/docs/compound-types/maps.md index 1f7e4a2..13d7e99 100644 --- a/src/content/docs/compound-types/maps.md +++ b/src/content/docs/compound-types/maps.md @@ -14,8 +14,9 @@ The current `StorageMap` is a minimal implementation that will be replaced with `StorageMap` stores key-value pairs in contract storage: ```fe -use core::StorageMap - +// +use _boilerplate::Map as StorageMap +// pub struct MyContract { balances: StorageMap, } @@ -29,7 +30,7 @@ Declare maps as fields in contract structs: ```fe // -use core::StorageMap +use _boilerplate::Map as StorageMap // pub struct Token { @@ -48,8 +49,7 @@ Use `get(key)` to read a value: ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct Token { balances: StorageMap, } @@ -70,8 +70,7 @@ Use `set(key, value)` to store a value: ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct Token { balances: StorageMap, } @@ -142,7 +141,7 @@ This ensures: ```fe // -use core::StorageMap +use _boilerplate::Map as StorageMap // pub struct Token { diff --git a/src/content/docs/compound-types/structs.md b/src/content/docs/compound-types/structs.md index f595978..5201480 100644 --- a/src/content/docs/compound-types/structs.md +++ b/src/content/docs/compound-types/structs.md @@ -233,8 +233,7 @@ struct Point { fn example() { // let point = Point { x: 10, y: 20 } - -let Point { x, y } = point +let Point { x, y } = ref point // x is 10, y is 20 // let _ = (x, y) diff --git a/src/content/docs/compound-types/tuples.md b/src/content/docs/compound-types/tuples.md index c7f9571..f105cac 100644 --- a/src/content/docs/compound-types/tuples.md +++ b/src/content/docs/compound-types/tuples.md @@ -103,7 +103,7 @@ Extract all tuple elements at once with pattern matching: fn example() { // let point: (u256, u256) = (100, 200) -let (x, y) = point +let (x, y): ref (u256, u256) = ref point // x is 100, y is 200 // @@ -189,8 +189,8 @@ fn example() { // let nested: ((i32, i32), (i32, i32)) = ((0, 0), (100, 100)) -let start = nested.0 // (0, 0) -let start_x = nested.0.0 // 0 +let (start, _) = nested // (0, 0) +let start_x = start.0 // 0 // let _ = (start, start_x) } diff --git a/src/content/docs/contracts/composition.md b/src/content/docs/contracts/composition.md index ec310b2..a0c5161 100644 --- a/src/content/docs/contracts/composition.md +++ b/src/content/docs/contracts/composition.md @@ -18,7 +18,7 @@ pub struct BalanceStorage { pub total: u256 } pub struct OwnerStorage { pub owner: u256 } // Reusable functions -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut balances: BalanceStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (balances: mut BalanceStorage) { let _ = (from, to, amount) true } @@ -40,7 +40,7 @@ Extract business logic into functions that declare their effect dependencies: ```fe // -use core::StorageMap +use _boilerplate::Map as StorageMap // pub struct TokenStorage { @@ -54,12 +54,12 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { } // Mutating helper -fn add_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn add_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { let current = store.balances.get(account) store.balances.set(account, current + amount) } -fn sub_balance(account: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn sub_balance(account: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let current = store.balances.get(account) if current < amount { return false @@ -69,7 +69,7 @@ fn sub_balance(account: u256, amount: u256) -> bool uses (mut store: TokenStorag } // Higher-level helper composing lower-level ones -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { if !sub_balance(from, amount) { return false } @@ -84,8 +84,7 @@ Split storage into logical units: ```fe // -use core::{StorageMap, revert} - +use _boilerplate::{Map as StorageMap, revert} pub struct Ctx {} impl Ctx { pub fn caller(self) -> u256 { todo() } @@ -99,7 +98,7 @@ fn require_owner(expected: u256) uses (ctx: Ctx) { if ctx.caller() != expected { revert(0, 0) } } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut balances: BalanceStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (balances: mut BalanceStorage) { let current = balances.balances.get(from) if current < amount { return false } balances.balances.set(from, current - amount) @@ -107,7 +106,7 @@ fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut balances: Bala true } -fn set_paused(value: bool) uses (mut pause_state: PauseStorage) { +fn set_paused(value: bool) uses (pause_state: mut PauseStorage) { pause_state.paused = value } // @@ -168,8 +167,7 @@ Implement access control as a reusable module: ```fe // -use core::{StorageMap, revert} - +use _boilerplate::{Map as StorageMap, revert} pub struct TokenStorage { pub balances: StorageMap, } @@ -179,7 +177,7 @@ impl Ctx { pub fn caller(self) -> u256 { todo() } } -fn mint_tokens(to: u256, amount: u256) uses (mut store: TokenStorage) { +fn mint_tokens(to: u256, amount: u256) uses (store: mut TokenStorage) { store.balances.set(to, store.balances.get(to) + amount) } // @@ -198,7 +196,7 @@ fn require_owner() uses (ctx: Ctx, ownership: OwnerStorage) { } } -fn transfer_ownership(new_owner: u256) uses (ctx: Ctx, mut ownership: OwnerStorage) { +fn transfer_ownership(new_owner: u256) uses (ctx: Ctx, ownership: mut OwnerStorage) { require_owner() ownership.owner = new_owner } @@ -240,8 +238,7 @@ contract OwnableToken { ```fe // -use core::{StorageMap, revert} - +use _boilerplate::{Map as StorageMap, revert} pub struct TokenStorage { pub balances: StorageMap, } @@ -259,7 +256,7 @@ fn require_owner() uses (ctx: Ctx, ownership: OwnerStorage) { if ctx.caller() != ownership.owner { revert(0, 0) } } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let current = store.balances.get(from) if current < amount { return false } store.balances.set(from, current - amount) @@ -294,7 +291,7 @@ fn require_not_paused() uses (pause_state: PauseStorage) { } } -fn set_paused(paused: bool) uses (mut pause_state: PauseStorage) { +fn set_paused(paused: bool) uses (pause_state: mut PauseStorage) { pause_state.paused = paused } @@ -332,8 +329,7 @@ Functions can require multiple effects: ```fe // -use core::{StorageMap, revert} - +use _boilerplate::{Map as StorageMap, revert} pub struct TokenStorage { pub balances: StorageMap, } @@ -355,7 +351,7 @@ fn require_not_paused() uses (pause_state: PauseStorage) { if pause_state.paused { revert(0, 0) } } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let current = store.balances.get(from) if current < amount { return false } store.balances.set(from, current - amount) @@ -373,7 +369,7 @@ fn guarded_transfer( from: u256, to: u256, amount: u256 -) -> bool uses (mut store: TokenStorage, pause_state: PauseStorage) { +) -> bool uses (store: mut TokenStorage, pause_state: PauseStorage) { require_not_paused() transfer(from, to, amount) } diff --git a/src/content/docs/contracts/declaration.md b/src/content/docs/contracts/declaration.md index 93318fa..94e082f 100644 --- a/src/content/docs/contracts/declaration.md +++ b/src/content/docs/contracts/declaration.md @@ -56,7 +56,7 @@ Fields declare the contract's storage and effect dependencies: ```fe // -use core::StorageMap +use _boilerplate::Map as StorageMap // pub struct TokenStorage { @@ -94,8 +94,7 @@ Instead, use standalone functions with effects: ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct TokenStorage { pub balances: StorageMap, } @@ -128,8 +127,7 @@ The canonical structure of a Fe contract: ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct Ctx {} impl Ctx { pub fn caller(self) -> u256 { todo() } @@ -152,7 +150,7 @@ msg TokenMsg { } // 3. Helper functions with effects -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let from_bal = store.balances.get(from) if from_bal < amount { return false diff --git a/src/content/docs/contracts/effects.md b/src/content/docs/contracts/effects.md index 095ae3f..2fdaaf6 100644 --- a/src/content/docs/contracts/effects.md +++ b/src/content/docs/contracts/effects.md @@ -62,7 +62,7 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account) } -fn add_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn add_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { let current = store.balances.get(account) store.balances.set(account, current + amount) } @@ -129,15 +129,15 @@ Contracts can have multiple fields for different effects: ```fe // use _boilerplate::{Map, caller} -fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (mut tokens: TokenStorage) { +fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (tokens: mut TokenStorage) { let _ = (c, to, amount, tokens) true } -fn set_allowance(c: u256, spender: u256, amount: u256) uses (mut permits: AllowanceStorage) { +fn set_allowance(c: u256, spender: u256, amount: u256) uses (permits: mut AllowanceStorage) { let _ = (c, spender, amount, permits) } fn do_transfer_from(c: u256, from: u256, to: u256, amount: u256) -> bool - uses (mut tokens: TokenStorage, mut permits: AllowanceStorage) + uses (tokens: mut TokenStorage, permits: mut AllowanceStorage) { let _ = (c, from, to, amount, tokens, permits) true @@ -245,7 +245,7 @@ pub struct TokenStorage { pub balances: Map } // // Fe - explicit effect dependency -fn transfer(to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { // Clear that this function needs TokenStorage store.balances.set(to, amount) true diff --git a/src/content/docs/contracts/receive-blocks.md b/src/content/docs/contracts/receive-blocks.md index 092dfe0..20f55c3 100644 --- a/src/content/docs/contracts/receive-blocks.md +++ b/src/content/docs/contracts/receive-blocks.md @@ -12,7 +12,7 @@ In a contract, recv blocks access storage via `uses` clauses: ```fe // use _boilerplate::{Map, caller} -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let _ = (from, to, amount, store) true } @@ -97,7 +97,7 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account) } -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let from_bal = store.balances.get(from) if from_bal < amount { return false @@ -133,7 +133,7 @@ When handlers need multiple storage types: // use _boilerplate::{Map, caller} fn do_transfer_from(c: u256, from: u256, to: u256, amount: u256) -> bool - uses (mut balances: BalanceStorage, mut allowances: AllowanceStorage) + uses (balances: mut BalanceStorage, allowances: mut AllowanceStorage) { let _ = (c, from, to, amount, balances, allowances) true diff --git a/src/content/docs/contracts/storage.md b/src/content/docs/contracts/storage.md index 3a0e82d..e515194 100644 --- a/src/content/docs/contracts/storage.md +++ b/src/content/docs/contracts/storage.md @@ -96,7 +96,7 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account) } -fn set_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn set_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { store.balances.set(account, amount) } ``` @@ -151,7 +151,7 @@ Store a value: // use _boilerplate::Map pub struct TokenStorage { pub balances: Map } -fn example(account: u256, new_balance: u256) uses (mut store: TokenStorage) { +fn example(account: u256, new_balance: u256) uses (store: mut TokenStorage) { // store.balances.set(account, new_balance) // @@ -177,7 +177,7 @@ fn get_allowance(owner: u256, spender: u256) -> u256 uses (store: AllowanceStora store.allowances.get(owner).get(spender) } -fn set_allowance(owner: u256, spender: u256, amount: u256) uses (mut store: AllowanceStorage) { +fn set_allowance(owner: u256, spender: u256, amount: u256) uses (store: mut AllowanceStorage) { store.allowances.get(owner).set(spender, amount) } ``` @@ -189,11 +189,11 @@ Contracts can have multiple storage fields for logical separation: ```fe // use _boilerplate::{Map, caller} -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut tokens: BalanceStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (tokens: mut BalanceStorage) { let _ = (from, to, amount, tokens) true } -fn initiate_transfer(new_owner: u256) uses (mut ownership: OwnerStorage) { +fn initiate_transfer(new_owner: u256) uses (ownership: mut OwnerStorage) { let _ = (new_owner, ownership) } msg TokenMsg { diff --git a/src/content/docs/effects/built-in.md b/src/content/docs/effects/built-in.md index 271f95f..15a0689 100644 --- a/src/content/docs/effects/built-in.md +++ b/src/content/docs/effects/built-in.md @@ -16,7 +16,7 @@ pub struct Logger { } // Use it as an effect -fn log_message(value: u256) uses (mut logger: Logger) { +fn log_message(value: u256) uses (logger: mut Logger) { logger.entries = value } ``` @@ -72,7 +72,7 @@ fn balance_of(account: u256) -> u256 uses (store: TokenStore) { store.balances.get(account) } -fn mint(to: u256, amount: u256) uses (mut store: TokenStore) { +fn mint(to: u256, amount: u256) uses (store: mut TokenStore) { let current = store.balances.get(to) store.balances.set(to, current + amount) store.total_supply = store.total_supply + amount @@ -89,7 +89,7 @@ pub struct EventLog { pub data: u256, } -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: EventLog) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut EventLog) { // Emit transfer event // let _ = (from, to, amount, log) @@ -135,14 +135,14 @@ impl Map { pub fn set(mut self, key: K, value: V) { todo() } } pub struct TokenStore { pub balances: Map } -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: EventLog) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut EventLog) { let _ = (from, to, amount, log) } fn require(cond: bool) { if cond { } else { todo() } } // fn transfer(from: u256, to: u256, amount: u256) - uses (ctx: Ctx, mut store: TokenStore, config: Config, mut log: EventLog) + uses (ctx: Ctx, store: mut TokenStore, config: Config, log: mut EventLog) { // Check caller authorization require(ctx.caller == from) @@ -187,7 +187,7 @@ fn get_cached() -> Option uses (cache: Cache) { } } -fn set_cached(value: T) uses (mut cache: Cache) { +fn set_cached(value: own T) uses (cache: mut Cache) { cache.value = value cache.valid = true } @@ -259,7 +259,7 @@ fn has_balance(account: u256, amount: u256) -> bool uses (balances: Balances) { } // Only this needs mut -fn debit(account: u256, amount: u256) uses (mut balances: Balances) { +fn debit(account: u256, amount: u256) uses (balances: mut Balances) { let current = balances.get(account) balances.set(account, current - amount) } diff --git a/src/content/docs/effects/declaring-effects.md b/src/content/docs/effects/declaring-effects.md index 803512c..511ff08 100644 --- a/src/content/docs/effects/declaring-effects.md +++ b/src/content/docs/effects/declaring-effects.md @@ -45,7 +45,7 @@ fn read_data() -> u256 uses (storage: Storage) { storage.data } -fn write_data(value: u256) uses (mut storage: Storage) { +fn write_data(value: u256) uses (storage: mut Storage) { storage.data = value } ``` @@ -69,7 +69,7 @@ fn process() uses (store: Storage) { // } -fn update() uses (mut store: Storage) { +fn update() uses (store: mut Storage) { store.data = 100 } ``` @@ -107,7 +107,7 @@ pub struct Config { pub data: u256 } pub struct Logger { pub data: u256 } // -fn transfer() uses (mut balances: Balances, config: Config, mut log: Logger) { +fn transfer() uses (balances: mut Balances, config: Config, log: mut Logger) { // balances and log are mutable // config is read-only // @@ -122,11 +122,11 @@ Contracts can declare effects in their definition: ```fe // -pub struct Context { pub data: u256 } +pub struct Context {} pub struct TokenStorage { pub data: u256 } // -contract Token uses (mut ctx: Context) { +contract Token uses (ctx: mut Context) { store: TokenStorage, } ``` @@ -143,7 +143,7 @@ pub struct TokenStorage { } // Helper function with explicit effects -fn do_transfer(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) uses (store: mut TokenStorage) { let from_balance = store.balances.get(from) let to_balance = store.balances.get(to) @@ -186,9 +186,9 @@ fn get_cached() -> T uses (cache: Cache) { | Form | Example | |------|---------| | Single effect | `uses (name: Effect)` | -| Mutable effect | `uses (mut name: Effect)` | +| Mutable effect | `uses (name: mut Effect)` | | Multiple effects | `uses (a: A, b: B, c: C)` | -| Mixed mutability | `uses (mut a: A, b: B)` | +| Mixed mutability | `uses (a: mut A, b: B)` | | With return type | `fn foo() -> T uses (e: E)` | | On contract | `contract C uses (e: Effect) { }` | @@ -219,7 +219,7 @@ impl Balances { } // -fn set_balance(account: u256, amount: u256) uses (mut balances: Balances) { +fn set_balance(account: u256, amount: u256) uses (balances: mut Balances) { balances.set(account, amount) } ``` @@ -239,7 +239,7 @@ impl TransferLog { // fn logged_transfer(from: u256, to: u256, amount: u256) - uses (mut balances: Balances, mut log: TransferLog) + uses (balances: mut Balances, log: mut TransferLog) { balances.transfer(from, to, amount) log.record(from, to, amount) diff --git a/src/content/docs/effects/mutability.md b/src/content/docs/effects/mutability.md index b782f1f..9b67210 100644 --- a/src/content/docs/effects/mutability.md +++ b/src/content/docs/effects/mutability.md @@ -42,7 +42,7 @@ Add `mut` to allow modification: pub struct Config { pub value: u256 } // -fn set_value(new_value: u256) uses (mut config: Config) { +fn set_value(new_value: u256) uses (config: mut Config) { config.value = new_value // Can modify } ``` @@ -72,7 +72,7 @@ fn read_only() uses (data: Data) { // } -fn outer() uses (mut data: Data) { +fn outer() uses (data: mut Data) { read_only() // OK: mut Data satisfies Data // let _ = data @@ -86,7 +86,7 @@ An immutable effect cannot satisfy a mutable requirement: ```fe ignore // This would be a compile error: -fn needs_mut() uses (mut data: Data) { +fn needs_mut() uses (data: mut Data) { // modifies Data } @@ -102,7 +102,7 @@ When providing an effect with `with`, the binding's mutability determines the ef ```fe pub struct Counter { pub value: u256 } -fn needs_mut() uses (mut counter: Counter) { +fn needs_mut() uses (counter: mut Counter) { counter.value = counter.value + 1 } @@ -157,11 +157,11 @@ pub struct TokenStore { pub supply: u256 } pub struct Config { pub max_amount: u256 } // -fn mint(amount: u256) uses (mut store: TokenStore) { +fn mint(amount: u256) uses (store: mut TokenStore) { store.supply = store.supply + amount } -fn update_config(new_max: u256) uses (mut config: Config) { +fn update_config(new_max: u256) uses (config: mut Config) { config.max_amount = new_max } ``` @@ -179,7 +179,7 @@ impl Cache { } // -fn process() uses (data: Data, mut cache: Cache) { +fn process() uses (data: Data, cache: mut Cache) { // data is read-only // cache is mutable @@ -209,12 +209,12 @@ fn get_balance(account: u256) -> u256 uses (balances: Balances) { } // Mutable: changes state -fn set_balance(account: u256, amount: u256) uses (mut balances: Balances) { +fn set_balance(account: u256, amount: u256) uses (balances: mut Balances) { balances.data.set(account, amount) } // Mutable: combines read and write -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { let from_balance = get_balance(from) // Calls read-only function let to_balance = get_balance(to) @@ -228,7 +228,7 @@ fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { | Declaration | Can Read | Can Modify | |------------|----------|------------| | `uses (e: Effect)` | Yes | No | -| `uses (mut e: Effect)` | Yes | Yes | +| `uses (e: mut Effect)` | Yes | Yes | | Caller Has | Callee Needs | Result | |------------|--------------|--------| diff --git a/src/content/docs/effects/propagation.md b/src/content/docs/effects/propagation.md index e660dc6..c86f9bd 100644 --- a/src/content/docs/effects/propagation.md +++ b/src/content/docs/effects/propagation.md @@ -240,7 +240,7 @@ pub struct Config { pub data: u256 } pub struct Logger { pub data: u256 } // -fn helper() uses (config: Config, mut logger: Logger) { +fn helper() uses (config: Config, logger: mut Logger) { // Needs both effects // let _ = (config, logger) @@ -282,7 +282,7 @@ fn caller() { ```fe ignore // This would be a compile error: -fn needs_mut() uses (mut data: Data) { } +fn needs_mut() uses (data: mut Data) { } fn caller() uses (data: Data) { needs_mut() diff --git a/src/content/docs/effects/storage.md b/src/content/docs/effects/storage.md index 1af1cb0..0af9a6c 100644 --- a/src/content/docs/effects/storage.md +++ b/src/content/docs/effects/storage.md @@ -59,11 +59,11 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { } // Mutable access -fn set_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn set_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { store.balances.set(account, amount) } -fn mint(to: u256, amount: u256) uses (mut store: TokenStorage) { +fn mint(to: u256, amount: u256) uses (store: mut TokenStorage) { let current = store.balances.get(to) store.balances.set(to, current + amount) store.total_supply = store.total_supply + amount @@ -101,7 +101,7 @@ pub struct TokenStorage { // // Helper functions with explicit storage effects -fn do_transfer(from: u256, to: u256, amount: u256) uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) uses (store: mut TokenStorage) { let from_balance = store.balances.get(from) let to_balance = store.balances.get(to) @@ -134,7 +134,7 @@ pub struct Metadata { } // Only needs Balances -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { let from_balance = balances.data.get(from) let to_balance = balances.data.get(to) @@ -160,7 +160,7 @@ pub struct Balances { pub data: u256 } // // This signature tells you exactly what storage is accessed -fn do_transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn do_transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { // ... // let _ = (from, to, amount, balances) @@ -176,7 +176,7 @@ pub struct Balances { pub data: u256 } // // This function cannot accidentally modify Allowances -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { // Compiler error if you try to access Allowances here // let _ = (from, to, amount, balances) @@ -189,7 +189,7 @@ fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { ```fe // pub struct Balances { pub data: u256 } -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { let _ = (from, to, amount, balances) } // @@ -219,7 +219,7 @@ You don't need to manage storage layout manually. Just define your storage struc | Pattern | Description | |---------|-------------| | `uses (s: Storage)` | Read-only storage access | -| `uses (mut s: Storage)` | Mutable storage access | +| `uses (s: mut Storage)` | Mutable storage access | | Storage struct | Group related storage fields | | Multiple effects | Separate storage by concern | | `with (Storage = ...)` | Provide storage effect in scope | diff --git a/src/content/docs/effects/what-are-effects.md b/src/content/docs/effects/what-are-effects.md index 898d3af..d672ac1 100644 --- a/src/content/docs/effects/what-are-effects.md +++ b/src/content/docs/effects/what-are-effects.md @@ -35,7 +35,7 @@ pub struct Balances { } // -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { // This function can only access Balances // and can modify it (mut) // @@ -60,7 +60,7 @@ pub struct Counter { } // This function requires the Counter effect -fn increment() uses (mut counter: Counter) { +fn increment() uses (counter: mut Counter) { counter.value = counter.value + 1 } diff --git a/src/content/docs/effects/why-effects-matter.md b/src/content/docs/effects/why-effects-matter.md index 3ba5c5b..fde303f 100644 --- a/src/content/docs/effects/why-effects-matter.md +++ b/src/content/docs/effects/why-effects-matter.md @@ -16,7 +16,7 @@ Every function declares exactly what it can access: pub struct Balances {} // -fn transfer(from: u256, to: u256, amount: u256) uses (mut balances: Balances) { +fn transfer(from: u256, to: u256, amount: u256) uses (balances: mut Balances) { // Can ONLY modify Balances // Cannot access Allowances, Config, or anything else // @@ -43,7 +43,7 @@ fn get_balance(account: u256) -> u256 uses (balances: Balances) { } // This function can read AND write -fn set_balance(account: u256, amount: u256) uses (mut balances: Balances) { +fn set_balance(account: u256, amount: u256) uses (balances: mut Balances) { balances.data.set(account, amount) } ``` @@ -94,7 +94,7 @@ impl Logger { } } -fn process_payment(amount: u256) uses (config: Config, mut balances: Balances, mut logger: Logger) { +fn process_payment(amount: u256) uses (config: Config, balances: mut Balances, logger: mut Logger) { let fee = amount * config.fee_rate / 10000 balances.credit(amount - fee) @@ -203,7 +203,7 @@ pub struct EventLog {} // - It modifies balances // - It writes to event log fn transfer(to: u256, amount: u256) - uses (ctx: Ctx, tokens: TokenStore, mut balances: Balances, mut log: EventLog) + uses (ctx: Ctx, tokens: TokenStore, balances: mut Balances, log: mut EventLog) { // let _ = (to, amount, ctx, tokens, balances, log) @@ -278,7 +278,7 @@ All effect checking happens at compile time: pub struct CriticalState {} // -fn risky_operation() uses (mut state: CriticalState) { +fn risky_operation() uses (state: mut CriticalState) { // let _ = state // @@ -290,7 +290,7 @@ fn risky_operation() uses (mut state: CriticalState) { // } // Must declare the effect to call risky_operation -fn valid_caller() uses (mut state: CriticalState) { +fn valid_caller() uses (state: mut CriticalState) { risky_operation() } ``` diff --git a/src/content/docs/events/abi.md b/src/content/docs/events/abi.md index c5587b3..2579c42 100644 --- a/src/content/docs/events/abi.md +++ b/src/content/docs/events/abi.md @@ -26,6 +26,7 @@ Log Entry Topic 0 is always the keccak256 hash of the event signature: ```fe +#[event] struct Transfer { #[indexed] from: u256, @@ -46,6 +47,7 @@ This matches Solidity's event encoding, ensuring tools recognize your events. Each `#[indexed]` field becomes a topic: ```fe +#[event] struct Transfer { #[indexed] from: u256, // → topics[1] @@ -69,6 +71,7 @@ When emitting `Transfer { from: 0x123, to: 0x456, amount: 1000 }`: Fields without `#[indexed]` are ABI-encoded into the data section: ```fe +#[event] struct Swap { #[indexed] sender: u256, @@ -102,6 +105,7 @@ To emit ERC20-compatible events: ```fe // ERC20 Transfer event // Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +#[event] struct Transfer { #[indexed] from: u256, // address as u256 @@ -112,6 +116,7 @@ struct Transfer { // ERC20 Approval event // Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) +#[event] struct Approval { #[indexed] owner: u256, @@ -179,6 +184,7 @@ To emit events compatible with existing Solidity contracts: ```fe // Match Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +#[event] struct Transfer { #[indexed] from: u256, @@ -227,6 +233,7 @@ Field order affects the signature: ```fe // Order: indexed fields first, then data fields +#[event] struct Transfer { #[indexed] from: u256, // First in signature @@ -245,6 +252,7 @@ Include signatures in your documentation: /// Transfer event /// Signature: Transfer(uint256,uint256,uint256) /// Topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef +#[event] struct Transfer { #[indexed] from: u256, diff --git a/src/content/docs/events/emitting.md b/src/content/docs/events/emitting.md index 484e40c..79bcdfa 100644 --- a/src/content/docs/events/emitting.md +++ b/src/content/docs/events/emitting.md @@ -15,6 +15,7 @@ use _boilerplate::{Log} // +#[event] struct Transfer { #[indexed] from: u256, @@ -23,7 +24,7 @@ struct Transfer { amount: u256, } -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut Log) { log.emit(Transfer { from, to, amount }) } ``` @@ -43,6 +44,7 @@ pub struct TokenStorage { pub balances: Map, } +#[event] struct Transfer { #[indexed] from: u256, @@ -52,7 +54,7 @@ struct Transfer { } fn do_transfer(from: u256, to: u256, amount: u256) - -> bool uses (mut store: TokenStorage, mut log: Log) + -> bool uses (store: mut TokenStorage, log: mut Log) { let from_bal = store.balances.get(from) if from_bal < amount { @@ -78,6 +80,7 @@ A critical pattern: emit events after state changes succeed, not before: // use _boilerplate::{Map, Log} pub struct TokenStorage { pub balances: Map } +#[event] struct Transfer { #[indexed] from: u256, @@ -88,7 +91,7 @@ struct Transfer { // fn transfer(from: u256, to: u256, amount: u256) - -> bool uses (mut store: TokenStorage, mut log: Log) + -> bool uses (store: mut TokenStorage, log: mut Log) { // 1. Validate let from_bal = store.balances.get(from) @@ -116,7 +119,7 @@ In contracts, declare storage and log as contract fields, then access them via ` ```fe // use _boilerplate::{Map, Log, caller} -fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage, mut log: Log) { +fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage, log: mut Log) { let _ = (c, to, amount, store, log) true } @@ -126,6 +129,7 @@ pub struct TokenStorage { pub balances: Map, } +#[event] struct TransferEvent { #[indexed] from: u256, @@ -164,6 +168,7 @@ pub struct TokenStorage { } // +#[event] struct Transfer { #[indexed] from: u256, @@ -172,6 +177,7 @@ struct Transfer { amount: u256, } +#[event] struct Approval { #[indexed] owner: u256, @@ -181,7 +187,7 @@ struct Approval { } fn transfer_from(spender: u256, from: u256, to: u256, amount: u256) - -> bool uses (mut store: TokenStorage, mut log: Log) + -> bool uses (store: mut TokenStorage, log: mut Log) { // Check and update allowance let allowed = store.allowances.get(from).get(spender) @@ -220,6 +226,7 @@ pub struct TokenStorage { pub balances: Map, pub total_supply: u256, } +#[event] struct Transfer { #[indexed] from: u256, @@ -229,14 +236,14 @@ struct Transfer { } // -fn mint(to: u256, amount: u256) uses (mut store: TokenStorage, mut log: Log) { +fn mint(to: u256, amount: u256) uses (store: mut TokenStorage, log: mut Log) { store.balances.set(to, store.balances.get(to) + amount) store.total_supply = store.total_supply + amount log.emit(Transfer { from: 0, to, amount }) } -fn burn(from: u256, amount: u256) uses (mut store: TokenStorage, mut log: Log) { +fn burn(from: u256, amount: u256) uses (store: mut TokenStorage, log: mut Log) { store.balances.set(from, store.balances.get(from) - amount) store.total_supply = store.total_supply - amount @@ -254,6 +261,7 @@ use _boilerplate::Log pub struct AdminStorage { pub owner: u256 } // +#[event] struct OwnershipTransferred { #[indexed] previous_owner: u256, @@ -262,7 +270,7 @@ struct OwnershipTransferred { } fn transfer_ownership(new_owner: u256) - uses (mut admin: AdminStorage, mut log: Log) + uses (admin: mut AdminStorage, log: mut Log) { let previous = admin.owner admin.owner = new_owner @@ -279,6 +287,7 @@ fn transfer_ownership(new_owner: u256) Occasionally emit for important queries (use sparingly): ```fe +#[event] struct BalanceChecked { #[indexed] account: u256, @@ -296,6 +305,7 @@ Create helper functions for common events: // use _boilerplate::{Map, Log} pub struct TokenStorage { pub balances: Map } +#[event] struct Transfer { #[indexed] from: u256, @@ -303,6 +313,7 @@ struct Transfer { to: u256, amount: u256, } +#[event] struct Approval { #[indexed] owner: u256, @@ -312,16 +323,16 @@ struct Approval { } // -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut Log) { log.emit(Transfer { from, to, amount }) } -fn emit_approval(owner: u256, spender: u256, amount: u256) uses (mut log: Log) { +fn emit_approval(owner: u256, spender: u256, amount: u256) uses (log: mut Log) { log.emit(Approval { owner, spender, amount }) } fn transfer(from: u256, to: u256, amount: u256) - -> bool uses (mut store: TokenStorage, mut log: Log) + -> bool uses (store: mut TokenStorage, log: mut Log) { // ... transfer logic ... // @@ -341,6 +352,7 @@ Only emit when something meaningful happens: // use _boilerplate::{Map, Log} pub struct TokenStorage { pub allowances: Map> } +#[event] struct Approval { #[indexed] owner: u256, @@ -351,7 +363,7 @@ struct Approval { // fn set_approval(owner: u256, spender: u256, new_amount: u256) - uses (mut store: TokenStorage, mut log: Log) + uses (store: mut TokenStorage, log: mut Log) { let current = store.allowances.get(owner).get(spender) diff --git a/src/content/docs/events/event-structs.md b/src/content/docs/events/event-structs.md index 68c47f8..0fb02fc 100644 --- a/src/content/docs/events/event-structs.md +++ b/src/content/docs/events/event-structs.md @@ -30,6 +30,7 @@ These are regular structs that become events when emitted through the Log effect The `#[indexed]` attribute marks fields that should become EVM log topics, making them filterable: ```fe +#[event] struct Transfer { #[indexed] from: u256, @@ -49,6 +50,7 @@ With indexed fields: Indexed fields enable efficient queries: ```fe +#[event] struct Transfer { #[indexed] from: u256, // Filter: "all transfers FROM this address" @@ -73,6 +75,7 @@ This means you can have at most 3 indexed fields: ```fe // Valid: 3 indexed fields +#[event] struct ComplexEvent { #[indexed] field1: u256, @@ -86,6 +89,7 @@ struct ComplexEvent { ```fe ignore // Invalid: too many indexed fields +#[event] struct TooManyIndexed { #[indexed] a: u256, @@ -106,6 +110,7 @@ Standard token events follow ERC20/ERC721 conventions: ```fe // ERC20 Transfer +#[event] struct Transfer { #[indexed] from: u256, @@ -115,6 +120,7 @@ struct Transfer { } // ERC20 Approval +#[event] struct Approval { #[indexed] owner: u256, @@ -124,6 +130,7 @@ struct Approval { } // ERC721 Transfer +#[event] struct NftTransfer { #[indexed] from: u256, @@ -139,6 +146,7 @@ struct NftTransfer { Events for contract administration: ```fe +#[event] struct OwnershipTransferred { #[indexed] previous_owner: u256, @@ -160,12 +168,14 @@ struct Unpaused { Events recording state changes: ```fe +#[event] struct Deposit { #[indexed] account: u256, amount: u256, } +#[event] struct Withdrawal { #[indexed] account: u256, @@ -187,12 +197,14 @@ Each event should represent one logical occurrence: ```fe // Good: specific events +#[event] struct Minted { #[indexed] to: u256, amount: u256, } +#[event] struct Burned { #[indexed] from: u256, @@ -212,6 +224,7 @@ struct SupplyChanged { Index fields you'll filter by: ```fe +#[event] struct Trade { #[indexed] trader: u256, // Often filtered by trader @@ -229,6 +242,7 @@ struct Trade { Events should be self-contained for off-chain processing: ```fe +#[event] struct Swap { #[indexed] sender: u256, diff --git a/src/content/docs/events/log-effect.md b/src/content/docs/events/log-effect.md index 9662a6a..e2868f9 100644 --- a/src/content/docs/events/log-effect.md +++ b/src/content/docs/events/log-effect.md @@ -15,6 +15,7 @@ use _boilerplate::{Map, Log} pub struct TokenStorage { pub balances: Map } // +#[event] struct Transfer { #[indexed] from: u256, @@ -25,7 +26,7 @@ struct Transfer { // This function CAN emit events fn transfer_with_event(from: u256, to: u256, amount: u256) - uses (mut store: TokenStorage, mut log: Log) + uses (store: mut TokenStorage, log: mut Log) { // ... transfer logic ... // @@ -36,7 +37,7 @@ fn transfer_with_event(from: u256, to: u256, amount: u256) // This function CANNOT emit events fn transfer_silent(from: u256, to: u256, amount: u256) - uses (mut store: TokenStorage) + uses (store: mut TokenStorage) { // ... transfer logic only ... // log.emit(...) would be a compile error here @@ -51,6 +52,7 @@ Use it in function signatures: ```fe // use _boilerplate::Log +#[event] struct Transfer { #[indexed] from: u256, @@ -61,7 +63,7 @@ struct Transfer { // // Read-only logging isn't meaningful, so always use mut -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut Log) { log.emit(Transfer { from, to, amount }) } ``` @@ -84,7 +86,7 @@ pub struct Balances { pub data: u256 } // - It modifies Balances (mutable) // - It emits events (mutable Log) fn process_payment(amount: u256) - -> bool uses (config: Config, mut balances: Balances, mut log: Log) + -> bool uses (config: Config, balances: mut Balances, log: mut Log) { // let _ = (amount, config, balances, log) @@ -125,6 +127,7 @@ Compose functions while controlling which can log: // use _boilerplate::Log pub struct Balances { pub data: u256 } +#[event] struct Deposit { #[indexed] account: u256, @@ -133,7 +136,7 @@ struct Deposit { // // Internal helper - no logging -fn update_balance(account: u256, delta: u256) uses (mut balances: Balances) { +fn update_balance(account: u256, delta: u256) uses (balances: mut Balances) { // Pure state update, no events // let _ = (account, delta, balances) @@ -142,7 +145,7 @@ fn update_balance(account: u256, delta: u256) uses (mut balances: Balances) { // Public interface - with logging fn deposit(account: u256, amount: u256) - uses (mut balances: Balances, mut log: Log) + uses (balances: mut Balances, log: mut Log) { update_balance(account, amount) log.emit(Deposit { account, amount }) @@ -157,6 +160,7 @@ Functions calling logging functions must declare the effect: // use _boilerplate::{Map, Log} pub struct TokenStorage { pub balances: Map } +#[event] struct Transfer { #[indexed] from: u256, @@ -166,13 +170,13 @@ struct Transfer { } // -fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) { +fn emit_transfer(from: u256, to: u256, amount: u256) uses (log: mut Log) { log.emit(Transfer { from, to, amount }) } // Must declare Log because it calls emit_transfer fn do_transfer(from: u256, to: u256, amount: u256) - -> bool uses (mut store: TokenStorage, mut log: Log) + -> bool uses (store: mut TokenStorage, log: mut Log) { // ... transfer logic ... // @@ -186,7 +190,7 @@ fn do_transfer(from: u256, to: u256, amount: u256) ```fe ignore // Compile error: missing Log effect fn broken_transfer(from: u256, to: u256, amount: u256) - uses (mut store: TokenStorage) + uses (store: mut TokenStorage) -> bool { // ... transfer logic ... @@ -203,7 +207,7 @@ Contracts provide the Log effect via the `uses` clause on handlers: // use _boilerplate::{Map, Log, caller} pub struct TokenStorage { pub balances: Map } -fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage, mut log: Log) { +fn do_transfer(c: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage, log: mut Log) { let _ = (c, to, amount, store, log) true } @@ -246,6 +250,7 @@ impl AdminLog { pub fn emit(self, event: T) { todo() } } +#[event] struct Transfer { #[indexed] from: u256, @@ -254,6 +259,7 @@ struct Transfer { amount: u256, } +#[event] struct OwnershipTransferred { #[indexed] previous_owner: u256, @@ -262,7 +268,7 @@ struct OwnershipTransferred { } fn transfer(from: u256, to: u256, amount: u256) - uses (mut store: TokenStorage, mut log: TransferLog) + uses (store: mut TokenStorage, log: mut TransferLog) { // ... transfer logic ... // @@ -272,7 +278,7 @@ fn transfer(from: u256, to: u256, amount: u256) } fn transfer_ownership(new_owner: u256) - uses (mut admin: AdminStorage, mut log: AdminLog) + uses (admin: mut AdminStorage, log: mut AdminLog) { let previous = admin.owner admin.owner = new_owner @@ -291,6 +297,7 @@ Often storage and its events are paired: ```fe // use _boilerplate::Map +#[event] struct Transfer { #[indexed] from: u256, @@ -311,7 +318,7 @@ impl TokenEvents { } fn mint(to: u256, amount: u256) - uses (mut store: TokenStorage, mut log: TokenEvents) + uses (store: mut TokenStorage, log: mut TokenEvents) { store.balances.set(to, store.balances.get(to) + amount) store.total_supply = store.total_supply + amount @@ -333,7 +340,7 @@ struct DebugMessage { value: u256, } -fn log_debug(message: u256) uses (mut log: DebugLog) { +fn log_debug(message: u256) uses (log: mut DebugLog) { log.emit(DebugMessage { value: message }) } ``` @@ -360,7 +367,7 @@ fn compute_fee(amount: u256) -> u256 uses (config: Config) { // With logging wrapper fn compute_fee_logged(amount: u256) - -> u256 uses (config: Config, mut log: Log) + -> u256 uses (config: Config, log: mut Log) { let fee = compute_fee(amount) log.emit(FeeComputed { amount, fee }) @@ -372,7 +379,7 @@ fn compute_fee_logged(amount: u256) | Aspect | Fe (Explicit) | Implicit Logging | |--------|---------------|------------------| -| Signature | Shows `uses (mut log: Log)` | No indication | +| Signature | Shows `uses (log: mut Log)` | No indication | | Testing | Easy to mock | Harder to intercept | | Composition | Fine-grained control | All-or-nothing | | Refactoring | Compiler catches missing effects | Silent failures | @@ -382,7 +389,7 @@ fn compute_fee_logged(amount: u256) | Concept | Description | |---------|-------------| | `pub struct Log {}` | Define a log effect type | -| `uses (mut log: Log)` | Declare logging capability | +| `uses (log: mut Log)` | Declare logging capability | | `log.emit(...)` | Emit an event | | Effect propagation | Callers must declare effects of callees | | Handler `uses` | Bind effect in contract handlers | diff --git a/src/content/docs/examples/erc20.md b/src/content/docs/examples/erc20.md index e9e3fad..f3d167a 100644 --- a/src/content/docs/examples/erc20.md +++ b/src/content/docs/examples/erc20.md @@ -12,7 +12,7 @@ This chapter presents a complete ERC20 token implementation called CoolCoin. Thi const MINTER: u256 = 1 const BURNER: u256 = 2 -pub contract CoolCoin uses (mut ctx: Ctx, mut log: Log) { +pub contract CoolCoin uses (ctx: mut Ctx, log: mut Log) { // Storage fields. These act as effects within the contract. mut store: TokenStore, mut auth: AccessControl, @@ -105,7 +105,7 @@ pub contract CoolCoin uses (mut ctx: Ctx, mut log: Log) { } fn transfer(from: Address, to: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { assert(from != Address::zero(), "transfer from zero address") assert(to != Address::zero(), "transfer to zero address") @@ -120,7 +120,7 @@ fn transfer(from: Address, to: Address, amount: u256) } fn mint(to: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { assert(to != Address::zero(), "mint to zero address") @@ -131,7 +131,7 @@ fn mint(to: Address, amount: u256) } fn burn(from: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { assert(from != Address::zero(), "burn from zero address") @@ -145,7 +145,7 @@ fn burn(from: Address, amount: u256) } fn approve(owner: Address, spender: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { assert(owner != Address::zero(), "approve from zero address") assert(spender != Address::zero(), "approve to zero address") @@ -157,7 +157,7 @@ fn approve(owner: Address, spender: Address, amount: u256) // Internal function to spend allowance fn spend_allowance(owner: Address, spender: Address, amount: u256) - uses (mut store: TokenStore) + uses (store: mut TokenStore) { let current = store.allowances[(owner, spender)] // if current != u256::MAX { // TODO: define ::MAX constants @@ -184,11 +184,12 @@ impl AccessControl { } pub fn has_role(self, role: u256, account: Address) -> bool { - self.roles[(role, account)] + core::ops::Index<(u256, Address)>::index(self.roles, (role, account)) } pub fn require(self, role: u256) uses (ctx: Ctx) { - assert(self.roles[(role, ctx.caller())], "access denied: missing role") + let caller = ctx.caller() + assert(self.has_role(role, caller), "access denied: missing role") } pub fn grant(mut self, role: u256, to: Address) { @@ -249,6 +250,7 @@ msg Erc20Extended { } // ERC20 events +#[event] struct TransferEvent { #[indexed] from: Address, @@ -257,6 +259,7 @@ struct TransferEvent { value: u256, } +#[event] struct ApprovalEvent { #[indexed] owner: Address, @@ -265,23 +268,14 @@ struct ApprovalEvent { value: u256, } +// // stubs for missing std lib stuff +use _boilerplate::Address + extern { fn assert(_: bool, _: String<64>) } -pub struct Address { inner: u256 } -impl Address { - pub fn zero() -> Self { - Address { inner: 0 } - } -} -impl core::ops::Eq for Address { - fn eq(self, _ other: Address) -> bool { - self.inner == other.inner - } -} - pub struct Map {} impl Map { pub fn new() -> Self { @@ -308,6 +302,7 @@ impl Log { todo() } } +// ``` ## Walkthrough @@ -317,13 +312,13 @@ impl Log { The contract declares effects at the contract level: ```fe ignore -pub contract CoolCoin uses (mut ctx: Ctx, mut log: Log) { +pub contract CoolCoin uses (ctx: mut Ctx, log: mut Log) { mut store: TokenStore, mut auth: AccessControl, ``` Key points: -- `uses (mut ctx: Ctx, mut log: Log)` declares contract-wide effects +- `uses (ctx: mut Ctx, log: mut Log)` declares contract-wide effects - `Ctx` provides execution context (caller address, block info) - `Log` provides event emission capability - `store` and `auth` are storage fields that act as effects within the contract @@ -418,7 +413,7 @@ Core logic is extracted into standalone functions: ```fe ignore fn transfer(from: Address, to: Address, amount: u256) - uses (mut store: TokenStore, mut log: Log) + uses (store: mut TokenStore, log: mut Log) { assert(from != Address::zero(), "transfer from zero address") assert(to != Address::zero(), "transfer to zero address") @@ -446,6 +441,7 @@ The `mint` and `burn` functions follow the same pattern, using `Address::zero()` Events are structs with `#[indexed]` fields for filtering: ```fe ignore +#[event] struct TransferEvent { #[indexed] from: Address, @@ -454,6 +450,7 @@ struct TransferEvent { value: u256, } +#[event] struct ApprovalEvent { #[indexed] owner: Address, @@ -479,7 +476,8 @@ const BURNER: u256 = 2 impl AccessControl { pub fn require(self, role: u256) uses (ctx: Ctx) { - assert(self.roles[(role, ctx.caller())], "access denied: missing role") + let caller = ctx.caller() + assert(self.has_role(role, caller), "access denied: missing role") } pub fn grant(mut self, role: u256, to: Address) { @@ -504,10 +502,10 @@ The `require` method checks if the caller has the specified role, reverting if n | Pattern | Example | |---------|---------| -| Contract-level effects | `contract CoolCoin uses (mut ctx: Ctx, mut log: Log)` | +| Contract-level effects | `contract CoolCoin uses (ctx: mut Ctx, log: mut Log)` | | Storage as fields | `mut store: TokenStore` | | Handler-specific effects | `uses (ctx, mut store, mut log)` | -| Effect in helpers | `fn transfer(...) uses (mut store: TokenStore, mut log: Log)` | +| Effect in helpers | `fn transfer(...) uses (store: mut TokenStore, log: mut Log)` | | Event emission | `log.emit(TransferEvent { ... })` | | Role-based access | `auth.require(role: MINTER)` | | Zero address checks | `assert(to != Address::zero(), "...")` | diff --git a/src/content/docs/foundations/control-flow.md b/src/content/docs/foundations/control-flow.md index a9298ed..ab51aad 100644 --- a/src/content/docs/foundations/control-flow.md +++ b/src/content/docs/foundations/control-flow.md @@ -159,21 +159,21 @@ let _ = description Match on enum variants to handle different cases: ```fe -// -fn example() { -// enum Status { Pending, Approved, - Rejected(String<64>), + Rejected, } -let status = Status::Pending +// +fn example() { + let status = Status::Pending +// let message: String<64> = match status { Status::Pending => "Awaiting review" Status::Approved => "Request approved" - Status::Rejected(reason) => reason // Extract the reason + Status::Rejected => "Request rejected" } // let _ = message diff --git a/src/content/docs/foundations/functions.md b/src/content/docs/foundations/functions.md index 85b9e59..8e451b5 100644 --- a/src/content/docs/foundations/functions.md +++ b/src/content/docs/foundations/functions.md @@ -43,7 +43,7 @@ fn process(value: u256, flag: bool) { Fe supports **labeled parameters** for improved call-site clarity. A label is the name used when calling the function, while the parameter name is used inside the function body: ```fe -fn transfer(from sender: u256, to recipient: u256, amount: u256) { +fn transfer(sender: u256, recipient: u256, amount: u256) { // Inside the function, use: sender, recipient, amount // let _ = (sender, recipient, amount) @@ -56,7 +56,7 @@ fn example_transfer() { let bob: u256 = 2 // // At the call site, use the labels: -transfer(from: alice, to: bob, amount: 100) +transfer(sender: alice, recipient: bob, amount: 100) // } ``` @@ -115,7 +115,7 @@ create_point(10, 20, named: true) Use `mut` to declare a parameter that can be modified within the function: ```fe -fn increment(mut value: u256) -> u256 { +fn increment(mut value: own u256) -> u256 { value = value + 1 value } @@ -138,10 +138,9 @@ impl Counter { self.value } - // Method with mutable self - can read and modify - fn increment(mut self) -> Self { - self.value = self.value + 1 - self + // Method that returns an updated counter + fn increment(self) -> Self { + Counter { value: self.value + 1 } } } ``` @@ -159,9 +158,8 @@ impl Counter { self.value } - fn increment(mut self) -> Self { - self.value = self.value + 1 - self + fn increment(self) -> Self { + Counter { value: self.value + 1 } } } @@ -304,7 +302,7 @@ This separation keeps contracts focused on state and message handling, while log Functions can be generic over types using angle brackets: ```fe -fn identity(value: T) -> T { +fn identity(value: own T) -> T { value } ``` @@ -341,7 +339,7 @@ fn read_storage() uses (storage: Storage) { // } -fn write_storage() uses (mut storage: Storage) { +fn write_storage() uses (storage: mut Storage) { // can read and write to storage // let _ = storage diff --git a/src/content/docs/foundations/primitive-types.md b/src/content/docs/foundations/primitive-types.md index 94042e6..36753c1 100644 --- a/src/content/docs/foundations/primitive-types.md +++ b/src/content/docs/foundations/primitive-types.md @@ -276,8 +276,7 @@ Fe's primitive types cover the fundamentals, but EVM-specific types like `Addres Currently, EVM addresses are represented as `u256` values: ```fe -use core::intrinsic::caller - +use _boilerplate::caller fn get_sender() -> u256 { caller() // returns the sender's address as u256 } diff --git a/src/content/docs/foundations/variables.md b/src/content/docs/foundations/variables.md index 45cf660..f139304 100644 --- a/src/content/docs/foundations/variables.md +++ b/src/content/docs/foundations/variables.md @@ -276,7 +276,7 @@ The `let` statement supports pattern matching, allowing you to destructure value fn example() { // let point: (u256, u256) = (10, 20) -let (x, y) = point // x = 10, y = 20 +let (x, y) = ref point // x = 10, y = 20 // let _ = (x, y) } @@ -309,7 +309,7 @@ struct Point { fn example() { // let point = Point { x: 10, y: 20 } -let Point { x, y } = point // x = 10, y = 20 +let Point { x, y } = ref point // x = 10, y = 20 // let _ = (x, y) } @@ -326,9 +326,9 @@ struct Point { } fn example() { -let point = Point { x: 10, y: 20 } // -let Point { x: horizontal, y: vertical } = point +let point = Point { x: 10, y: 20 } +let Point { x: horizontal, y: vertical } = ref point // horizontal = 10, vertical = 20 // let _ = (horizontal, vertical) @@ -363,9 +363,9 @@ struct Point { } fn example() { -let point = Point { x: 10, y: 20 } // -let Point { mut x, y } = point +let point = Point { x: 10, y: 20 } +let Point { mut x, y } = Point { x: point.x, y: point.y } x = 100 // OK: x is mutable // let _ = (x, y) diff --git a/src/content/docs/messages/fields.md b/src/content/docs/messages/fields.md index 036e420..6e6397a 100644 --- a/src/content/docs/messages/fields.md +++ b/src/content/docs/messages/fields.md @@ -66,7 +66,7 @@ msg Query { TotalSupply -> u256, #[selector = 0x06fdde03] - Name -> String, + Name -> String<256>, } ``` diff --git a/src/content/docs/messages/handler-syntax.md b/src/content/docs/messages/handler-syntax.md index d224fda..69c036f 100644 --- a/src/content/docs/messages/handler-syntax.md +++ b/src/content/docs/messages/handler-syntax.md @@ -309,7 +309,7 @@ fn validate_transfer(to: u256, amount: u256) -> bool { to != 0 && amount > 0 } -fn execute_transfer(to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn execute_transfer(to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { // transfer logic using storage effect // let _ = store @@ -358,7 +358,7 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account) } -fn add_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn add_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { let current = store.balances.get(account) store.balances.set(account, current + amount) } diff --git a/src/content/docs/messages/multiple-types.md b/src/content/docs/messages/multiple-types.md index 0f25ea9..541f412 100644 --- a/src/content/docs/messages/multiple-types.md +++ b/src/content/docs/messages/multiple-types.md @@ -11,8 +11,7 @@ A contract can have multiple recv blocks, each handling a different message type ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct TokenStorage { pub balances: StorageMap, pub total_supply: u256, @@ -23,7 +22,7 @@ impl Ctx { pub fn caller(self) -> u256 { todo() } } -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let bal = store.balances.get(from) if bal < amount { return false } store.balances.set(from, bal - amount) @@ -118,14 +117,13 @@ Multiple recv blocks can share the same contract state: ```fe // -use core::StorageMap - +use _boilerplate::Map as StorageMap pub struct Ctx {} impl Ctx { pub fn caller(self) -> u256 { todo() } } -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let bal = store.balances.get(from) if bal < amount { return false } store.balances.set(from, bal - amount) @@ -261,11 +259,11 @@ For contracts with many message types, consider organizing handlers logically: ```fe ignore // Group related helper functions -fn transfer_tokens(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer_tokens(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { // ... } -fn update_allowance(owner: u256, spender: u256, amount: u256) uses (mut store: TokenStorage) { +fn update_allowance(owner: u256, spender: u256, amount: u256) uses (store: mut TokenStorage) { // ... } diff --git a/src/content/docs/messages/receive-blocks.md b/src/content/docs/messages/receive-blocks.md index 300c493..d317fe1 100644 --- a/src/content/docs/messages/receive-blocks.md +++ b/src/content/docs/messages/receive-blocks.md @@ -155,7 +155,7 @@ pub struct TokenStorage { pub balances: StorageMap, } -fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn do_transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let from_bal = store.balances.get(from) if from_bal < amount { return false diff --git a/src/content/docs/structs/definition.md b/src/content/docs/structs/definition.md index b058720..39fb10c 100644 --- a/src/content/docs/structs/definition.md +++ b/src/content/docs/structs/definition.md @@ -207,10 +207,8 @@ struct Point { // fn destructure_point() { // -let p = Point { x: 10, y: 20 } - // Destructure into variables -let Point { x, y } = p +let Point { x, y } = Point { x: 10, y: 20 } // let _ = (x, y) // diff --git a/src/content/docs/structs/helper-structs.md b/src/content/docs/structs/helper-structs.md index 2e237db..bf26a71 100644 --- a/src/content/docs/structs/helper-structs.md +++ b/src/content/docs/structs/helper-structs.md @@ -12,7 +12,7 @@ Group related values into a struct: ```fe // pub struct TokenStorage {} -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let _ = (from, to, amount, store) true } @@ -24,7 +24,7 @@ struct TransferParams { amount: u256, } -fn execute_transfer(params: TransferParams) -> bool uses (mut store: TokenStorage) { +fn execute_transfer(params: TransferParams) -> bool uses (store: mut TokenStorage) { // Use params.from, params.to, params.amount transfer(params.from, params.to, params.amount) } @@ -97,12 +97,12 @@ Create structs for validated data: ```fe // -use core::revert +use _boilerplate::revert use _boilerplate::{Map, caller} pub struct TokenStorage { pub balances: Map } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let _ = (from, to, amount, store) true } @@ -124,7 +124,7 @@ impl ValidatedAmount { } } -fn safe_transfer(to: u256, amount: ValidatedAmount) -> bool uses (mut store: TokenStorage) { +fn safe_transfer(to: u256, amount: ValidatedAmount) -> bool uses (store: mut TokenStorage) { // amount is guaranteed to be valid transfer(caller(), to, amount.get()) } @@ -150,7 +150,7 @@ fn transfer_with_result( from: u256, to: u256, amount: u256 -) -> TransferResult uses (mut store: TokenStorage) { +) -> TransferResult uses (store: mut TokenStorage) { let from_bal = store.balances.get(from) if from_bal < amount { @@ -240,24 +240,48 @@ impl TokenConfig { } } - fn with_decimals(mut self, d: u8) -> TokenConfig { - self.decimals = d - self + fn with_decimals(self, d: u8) -> TokenConfig { + TokenConfig { + name_hash: self.name_hash, + symbol_hash: self.symbol_hash, + decimals: d, + initial_supply: self.initial_supply, + mintable: self.mintable, + burnable: self.burnable, + } } - fn with_supply(mut self, supply: u256) -> TokenConfig { - self.initial_supply = supply - self + fn with_supply(self, supply: u256) -> TokenConfig { + TokenConfig { + name_hash: self.name_hash, + symbol_hash: self.symbol_hash, + decimals: self.decimals, + initial_supply: supply, + mintable: self.mintable, + burnable: self.burnable, + } } - fn mintable(mut self) -> TokenConfig { - self.mintable = true - self + fn mintable(self) -> TokenConfig { + TokenConfig { + name_hash: self.name_hash, + symbol_hash: self.symbol_hash, + decimals: self.decimals, + initial_supply: self.initial_supply, + mintable: true, + burnable: self.burnable, + } } - fn burnable(mut self) -> TokenConfig { - self.burnable = true - self + fn burnable(self) -> TokenConfig { + TokenConfig { + name_hash: self.name_hash, + symbol_hash: self.symbol_hash, + decimals: self.decimals, + initial_supply: self.initial_supply, + mintable: self.mintable, + burnable: true, + } } } @@ -321,7 +345,7 @@ Helper structs can work alongside effects: // use _boilerplate::{Map, caller} pub struct TokenStorage { pub balances: Map } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let _ = (from, to, amount, store) true } @@ -342,7 +366,7 @@ impl TransferRequest { } } - fn execute(self) -> bool uses (mut store: TokenStorage) { + fn execute(self) -> bool uses (store: mut TokenStorage) { transfer(self.from, self.to, self.amount) } diff --git a/src/content/docs/structs/impl-blocks.md b/src/content/docs/structs/impl-blocks.md index d1d27d7..4ce2b6e 100644 --- a/src/content/docs/structs/impl-blocks.md +++ b/src/content/docs/structs/impl-blocks.md @@ -142,19 +142,28 @@ struct Builder { } impl Builder { - fn with_width(mut self, w: u256) -> Builder { - self.width = w - self + fn with_width(self, w: u256) -> Builder { + Builder { + width: w, + height: self.height, + depth: self.depth, + } } - fn with_height(mut self, h: u256) -> Builder { - self.height = h - self + fn with_height(self, h: u256) -> Builder { + Builder { + width: self.width, + height: h, + depth: self.depth, + } } - fn with_depth(mut self, d: u256) -> Builder { - self.depth = d - self + fn with_depth(self, d: u256) -> Builder { + Builder { + width: self.width, + height: self.height, + depth: d, + } } } diff --git a/src/content/docs/structs/storage-structs.md b/src/content/docs/structs/storage-structs.md index 410b870..25df60d 100644 --- a/src/content/docs/structs/storage-structs.md +++ b/src/content/docs/structs/storage-structs.md @@ -47,7 +47,7 @@ fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { } // Function that writes to storage -fn set_balance(account: u256, amount: u256) uses (mut store: TokenStorage) { +fn set_balance(account: u256, amount: u256) uses (store: mut TokenStorage) { store.balances.set(account, amount) } ``` @@ -63,7 +63,7 @@ pub struct TokenStorage { pub balances: Map } fn get_balance(account: u256) -> u256 uses (store: TokenStorage) { store.balances.get(account) } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut store: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (store: mut TokenStorage) { let _ = (from, to, amount, store) true } @@ -107,7 +107,7 @@ fn get_value() -> u256 uses (store: CounterStorage) { store.value } -fn increment() uses (mut store: CounterStorage) { +fn increment() uses (store: mut CounterStorage) { store.value = store.value + 1 } ``` @@ -154,7 +154,7 @@ pub struct PauseStorage { pub paused: bool } // fn transfer(from: u256, to: u256, amount: u256) - -> bool uses (mut balances: BalanceStorage, pause: PauseStorage) + -> bool uses (balances: mut BalanceStorage, pause: PauseStorage) { if pause.paused { return false @@ -200,7 +200,7 @@ fn get_entry(key: u256) -> u256 uses (reg: Registry) { reg.entries.get(key) } -fn set_entry(key: u256, value: u256) uses (mut reg: Registry) { +fn set_entry(key: u256, value: u256) uses (reg: mut Registry) { reg.entries.set(key, value) } @@ -208,7 +208,7 @@ fn get_nested(outer: u256, inner: u256) -> u256 uses (reg: Registry) { reg.nested.get(outer).get(inner) } -fn set_nested(outer: u256, inner: u256, value: u256) uses (mut reg: Registry) { +fn set_nested(outer: u256, inner: u256, value: u256) uses (reg: mut Registry) { reg.nested.get(outer).set(inner, value) } ``` @@ -279,7 +279,7 @@ fn get_balance(account: u256) -> u256 uses (tokens: TokenStorage) { tokens.balances.get(account) } -fn transfer(from: u256, to: u256, amount: u256) -> bool uses (mut tokens: TokenStorage) { +fn transfer(from: u256, to: u256, amount: u256) -> bool uses (tokens: mut TokenStorage) { let from_bal = tokens.balances.get(from) if from_bal < amount { return false @@ -324,5 +324,5 @@ contract Token { | Effect type | Storage struct used in `uses` clause | | `Map` | Key-value storage field | | `uses (store: Storage)` | Read-only access | -| `uses (mut store: Storage)` | Read-write access | +| `uses (store: mut Storage)` | Read-write access | | Handler `uses (field)` | Bind contract field to effect in handlers | diff --git a/src/content/docs/traits/bounds.md b/src/content/docs/traits/bounds.md index f700275..e0fd41f 100644 --- a/src/content/docs/traits/bounds.md +++ b/src/content/docs/traits/bounds.md @@ -117,7 +117,7 @@ fn combine(a: A, b: B) -> String<256> { b.to_string() } -fn transform(input: T, mut output: U) { +fn transform(input: T, mut output: own U) { let value = input.read() output.write(value) } @@ -164,7 +164,7 @@ struct Wrapper { // Basic methods, no bounds needed impl Wrapper { - fn get(self) -> T { + fn get(own self) -> T { self.value } } @@ -224,7 +224,7 @@ trait Default { fn default() -> Self } -fn or_default(value: Option) -> T { +fn or_default(value: own Option) -> T { match value { Option::Some(v) => v, Option::None => T::default(), @@ -242,7 +242,7 @@ trait Clone { fn clone(self) -> Self } -fn duplicate(value: T) -> (T, T) { +fn duplicate(value: own T) -> (T, T) { (value.clone(), value) } ``` @@ -259,7 +259,7 @@ trait Storable { fn key(self) -> u256 } -fn save(item: T) uses (mut storage: Storage) { +fn save(item: T) uses (storage: mut Storage) { let key = item.key() storage.set(key, item) } diff --git a/src/content/docs/traits/generics.md b/src/content/docs/traits/generics.md index b066d61..57090b1 100644 --- a/src/content/docs/traits/generics.md +++ b/src/content/docs/traits/generics.md @@ -10,7 +10,7 @@ Generic functions work with multiple types using type parameters. Instead of wri Define a generic function with type parameters in angle brackets: ```fe -fn identity(value: T) -> T { +fn identity(value: own T) -> T { value } ``` @@ -19,7 +19,7 @@ fn identity(value: T) -> T { ```fe // -fn identity(value: T) -> T { +fn identity(value: own T) -> T { value } @@ -38,7 +38,7 @@ let _ = (x, y) Functions can have multiple type parameters: ```fe -fn pair(first: A, second: B) -> (A, B) { +fn pair(first: own A, second: own B) -> (A, B) { (first, second) } @@ -97,11 +97,11 @@ struct Wrapper { } impl Wrapper { - fn new(value: T) -> Wrapper { + fn new(value: own T) -> Wrapper { Wrapper { value } } - fn get(self) -> T { + fn get(own self) -> T { self.value } } @@ -127,7 +127,7 @@ struct Container { } impl Container { - fn get(self) -> T { + fn get(own self) -> T { self.item } @@ -196,7 +196,7 @@ let x = identity::(42) ### Swap Function ```fe -fn swap(a: T, b: T) -> (T, T) { +fn swap(a: own T, b: own T) -> (T, T) { (b, a) } @@ -216,7 +216,7 @@ let _ = (x, y) // use _boilerplate::{Option, Default} // -fn or_default(value: Option) -> T { +fn or_default(value: own Option) -> T { match value { Option::Some(v) => v, Option::None => T::default(), diff --git a/src/content/docs/traits/implementing.md b/src/content/docs/traits/implementing.md index 1a38d89..098e899 100644 --- a/src/content/docs/traits/implementing.md +++ b/src/content/docs/traits/implementing.md @@ -15,7 +15,7 @@ struct String {} // trait Greetable { - fn greet(self) -> String + fn greet(own self) -> String } struct Person { @@ -23,7 +23,7 @@ struct Person { } impl Greetable for Person { - fn greet(self) -> String { + fn greet(own self) -> String { self.name } } @@ -37,7 +37,7 @@ struct String {} trait Greetable { - fn greet(self) -> String + fn greet(own self) -> String } struct Person { @@ -45,7 +45,7 @@ struct Person { } impl Greetable for Person { - fn greet(self) -> String { + fn greet(own self) -> String { self.name } } @@ -257,9 +257,8 @@ impl Wallet { Wallet { balance: initial } } - fn deposit(mut self, amount: u256) -> Self { - self.balance += amount - self + fn deposit(self, amount: u256) -> Self { + Wallet { balance: self.balance + amount } } } @@ -291,9 +290,8 @@ impl Wallet { Wallet { balance: initial } } - fn deposit(mut self, amount: u256) -> Self { - self.balance += amount - self + fn deposit(self, amount: u256) -> Self { + Wallet { balance: self.balance + amount } } } diff --git a/src/examples/landing-page.fe b/src/examples/landing-page.fe index 33a765c..eb58197 100644 --- a/src/examples/landing-page.fe +++ b/src/examples/landing-page.fe @@ -1,8 +1,8 @@ // Events are structs with #[indexed] fields where needed +#[event] struct Signed { #[indexed] signer: Address, - book_msg: String<100>, } // Storage is defined as a struct @@ -20,7 +20,7 @@ msg GuestBookMsg { } // Contract with explicit effects from environment -pub contract GuestBook uses (ctx: Ctx, mut log: Log) { +pub contract GuestBook uses (ctx: Ctx, log: mut Log) { mut store: GuestBookStore, // Handle incoming messages with per-handler effects @@ -28,7 +28,7 @@ pub contract GuestBook uses (ctx: Ctx, mut log: Log) { Sign { book_msg } uses (ctx, mut store, mut log) { let signer = ctx.caller() store.messages[signer] = book_msg - log.emit(Signed { signer, book_msg }) + log.emit(Signed { signer }) } GetMsg { addr } -> String<100> uses (store) { @@ -38,7 +38,8 @@ pub contract GuestBook uses (ctx: Ctx, mut log: Log) { } // Stubs for type checking (not shown on landing page) -pub struct Address { inner: u256 } +use _boilerplate::Address + pub struct Ctx {} impl Ctx { pub fn caller(self) -> Address { todo() }