diff --git a/.gitignore b/.gitignore index cee20a5c..f6be50b5 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,10 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +# not pnpm lockfiles +package-lock.json +yarn.lock + # others .env*.local .vercel diff --git a/content/contracts-sui/1.x/access.mdx b/content/contracts-sui/1.x/access.mdx new file mode 100644 index 00000000..d2672cee --- /dev/null +++ b/content/contracts-sui/1.x/access.mdx @@ -0,0 +1,55 @@ +--- +title: Access +--- + +The `openzeppelin_access` package provides ownership-transfer wrappers for privileged Sui objects (`T: key + store`), such as admin and treasury capabilities. + +Use this package when direct object transfer is too permissive for your protocol. It gives you explicit transfer workflows that are easier to review, monitor, and constrain with policy. + + +This package is designed for single-owned objects. In `two_step_transfer`, `ctx.sender()` is stored as the owner-of-record for pending requests. Avoid using this policy directly in shared-object executor flows unless your design explicitly maps signer identity to cancel authority. + + +## Usage + +Add the dependency in `Move.toml`: + +```toml +[dependencies] +openzeppelin_access = { r.mvr = "@openzeppelin-move/access" } +``` + +Import the transfer policy module you want to use: + +```move +use openzeppelin_access::two_step_transfer; +``` + +## Examples + +```move +module my_sui_app::admin; + +use openzeppelin_access::two_step_transfer; + +public struct AdminCap has key, store { + id: object::UID, +} + +public fun wrap_admin_cap( + cap: AdminCap, + ctx: &mut TxContext, +): two_step_transfer::TwoStepTransferWrapper { + // Wrap the capability object to force a two-step transfer policy. + two_step_transfer::wrap(cap, ctx) +} +``` + +## Choosing a transfer policy + +- Use `two_step_transfer` when the signer triggering transfer initiation is the same principal that should retain cancel authority. +- Use `delayed_transfer` when protocol safety requires on-chain lead time before transfer or unwrap execution, and when initial wrapper custody should be assigned explicitly at wrap time. + +## API Reference + +Use the full function-level reference here: [Access API](/contracts-sui/1.x/api/access). diff --git a/content/contracts-sui/1.x/api/access.mdx b/content/contracts-sui/1.x/api/access.mdx new file mode 100644 index 00000000..fe819308 --- /dev/null +++ b/content/contracts-sui/1.x/api/access.mdx @@ -0,0 +1,599 @@ +--- +title: Access API Reference +--- + +This page documents the public API of `openzeppelin_access` for OpenZeppelin Contracts for Sui `v1.x`. + +### `two_step_transfer` [toc] [#two_step_transfer] + + + +```move +use openzeppelin_access::two_step_transfer; +``` + +Two-step ownership wrapper for `T: key + store` objects. Transfers require explicit initiation and acceptance, with cancellation authority bound to the recorded initiator (`ctx.sender()` at initiation time). + +This module is designed for flows with a single logical owner. Avoid using it directly in shared-object custody flows where arbitrary executors can trigger `initiate_transfer`, because the initiator becomes the recorded cancel authority. + +Types + +- [`WrappedKey`](#two_step_transfer-WrappedKey) +- [`TwoStepTransferWrapper`](#two_step_transfer-TwoStepTransferWrapper) +- [`PendingOwnershipTransfer`](#two_step_transfer-PendingOwnershipTransfer) +- [`Borrow`](#two_step_transfer-Borrow) +- [`RequestBorrow`](#two_step_transfer-RequestBorrow) + +Functions + +- [`wrap(obj, ctx)`](#two_step_transfer-wrap) +- [`borrow(self)`](#two_step_transfer-borrow) +- [`borrow_mut(self)`](#two_step_transfer-borrow_mut) +- [`borrow_val(self)`](#two_step_transfer-borrow_val) +- [`return_val(self, obj, borrow)`](#two_step_transfer-return_val) +- [`unwrap(self, ctx)`](#two_step_transfer-unwrap) +- [`initiate_transfer(self, new_owner, ctx)`](#two_step_transfer-initiate_transfer) +- [`accept_transfer(request, wrapper_ticket, ctx)`](#two_step_transfer-accept_transfer) +- [`cancel_transfer(request, wrapper_ticket, ctx)`](#two_step_transfer-cancel_transfer) +- [`request_borrow_val(request, wrapper_ticket, ctx)`](#two_step_transfer-request_borrow_val) +- [`request_return_val(request, wrapper, borrow)`](#two_step_transfer-request_return_val) + +Events + +- [`WrapExecuted`](#two_step_transfer-WrapExecuted) +- [`UnwrapExecuted`](#two_step_transfer-UnwrapExecuted) +- [`TransferInitiated`](#two_step_transfer-TransferInitiated) +- [`TransferAccepted`](#two_step_transfer-TransferAccepted) +- [`TransferCancelled`](#two_step_transfer-TransferCancelled) + +Errors + +- [`EInvalidTransferRequest`](#two_step_transfer-EInvalidTransferRequest) +- [`EWrongTwoStepTransferWrapper`](#two_step_transfer-EWrongTwoStepTransferWrapper) +- [`EWrongTwoStepTransferObject`](#two_step_transfer-EWrongTwoStepTransferObject) +- [`ENotOwner`](#two_step_transfer-ENotOwner) +- [`ENotNewOwner`](#two_step_transfer-ENotNewOwner) + +#### Types [!toc] [#two_step_transfer-Types] + + +Dynamic object-field key used to store the wrapped object under the wrapper. + + + +Wrapper object that custody-locks an underlying object and controls transfer flow. + +The wrapper intentionally omits `store` so transfer paths stay constrained to this module's logic. + + + +Shared pending-transfer object storing wrapper identity, current owner (`from`), and prospective owner (`to`). + +Wrapper custody is held by this request object through transfer-to-object (TTO) until accept/cancel resolution. + + + +Hot-potato guard proving that a value taken via `borrow_val` is returned to the same wrapper. + + + +Hot-potato guard proving a wrapper borrowed through a pending request is returned to that request. + + +#### Functions [!toc] [#two_step_transfer-Functions] + + +Wraps `obj` in a new transfer wrapper. +The wrapped object is stored as a dynamic object field so off-chain indexers can still discover the underlying object ID. + +Emits a `WrapExecuted` event. + + + +Returns immutable access to the wrapped object. + + + +Returns mutable access to the wrapped object without changing ownership state. + + + +Temporarily extracts the wrapped object and returns a `Borrow` guard that must be consumed by `return_val`. + + + +Returns a previously borrowed object to the wrapper. + +Aborts when wrapper/object identity checks fail. + + + +Destroys the wrapper and returns the underlying object directly to the caller. + +This bypasses pending-request flow and recovers direct ownership of the wrapped object. + +Emits an `UnwrapExecuted` event. + + + +Starts a transfer by creating a shared request and transferring wrapper custody to that request. + +Security note: cancellation authority is tied to the initiating signer recorded as `from`. +Only use this flow when the signer executing initiation is intentionally the logical owner/cancel authority. + +Emits a `TransferInitiated` event. + + + +Completes a pending transfer and sends the wrapper to the designated new owner. + +Aborts if caller is not `request.to` or wrapper identity does not match. + +Emits a `TransferAccepted` event. + + + +Cancels a pending transfer and returns the wrapper to `request.from`. + +Aborts if caller is not `request.from` or wrapper identity does not match. + +Emits a `TransferCancelled` event. + + + +Receives wrapper custody from a shared request so the recorded owner can operate on the wrapped object during a pending transfer. + +Aborts if caller is not `request.from` or if request/wrapper identity checks fail. + + + +Returns a wrapper borrowed via `request_borrow_val` back to the pending request. + +Aborts if either `wrapper` or `borrow` does not match the target request. + + +#### Events [!toc] [#two_step_transfer-Events] + + +Emitted when an object is wrapped. + + + +Emitted when an object is unwrapped. + + + +Emitted when a two-step transfer request is created. + + + +Emitted when a pending transfer is accepted and ownership moves to the new owner. + + + +Emitted when a pending transfer is cancelled. + + +#### Errors [!toc] [#two_step_transfer-Errors] + + +Raised when request and wrapper identities do not match. + + + +Raised when a `Borrow` guard is used against a different wrapper. + + + +Raised when returning an object different from the one originally borrowed. + + + +Raised when caller is not authorized as the current owner for the requested operation. + + + +Raised when caller is not the designated prospective owner in `accept_transfer`. + + +### `delayed_transfer` [toc] [#delayed_transfer] + + + +```move +use openzeppelin_access::delayed_transfer; +``` + +Time-locked ownership wrapper for `T: key + store`. The wrapped object is placed under a wrapper that is immediately transferred to a chosen recipient. After that, transfers and unwraps must be scheduled and can only execute after `min_delay_ms` elapses. + +Types + +- [`WrappedKey`](#delayed_transfer-WrappedKey) +- [`DelayedTransferWrapper`](#delayed_transfer-DelayedTransferWrapper) +- [`PendingTransfer`](#delayed_transfer-PendingTransfer) +- [`Borrow`](#delayed_transfer-Borrow) + +Functions + +- [`wrap(obj, min_delay_ms, recipient, ctx)`](#delayed_transfer-wrap) +- [`borrow(self)`](#delayed_transfer-borrow) +- [`borrow_mut(self)`](#delayed_transfer-borrow_mut) +- [`borrow_val(self)`](#delayed_transfer-borrow_val) +- [`return_val(self, obj, borrow)`](#delayed_transfer-return_val) +- [`schedule_transfer(self, new_owner, clock, ctx)`](#delayed_transfer-schedule_transfer) +- [`schedule_unwrap(self, clock, ctx)`](#delayed_transfer-schedule_unwrap) +- [`execute_transfer(self, clock, ctx)`](#delayed_transfer-execute_transfer) +- [`unwrap(self, clock, ctx)`](#delayed_transfer-unwrap) +- [`cancel_schedule(self)`](#delayed_transfer-cancel_schedule) + +Events + +- [`WrapExecuted`](#delayed_transfer-WrapExecuted) +- [`TransferScheduled`](#delayed_transfer-TransferScheduled) +- [`UnwrapScheduled`](#delayed_transfer-UnwrapScheduled) +- [`OwnershipTransferred`](#delayed_transfer-OwnershipTransferred) +- [`PendingTransferCancelled`](#delayed_transfer-PendingTransferCancelled) +- [`UnwrapExecuted`](#delayed_transfer-UnwrapExecuted) + +Errors + +- [`ETransferAlreadyScheduled`](#delayed_transfer-ETransferAlreadyScheduled) +- [`ENoPendingTransfer`](#delayed_transfer-ENoPendingTransfer) +- [`EDelayNotElapsed`](#delayed_transfer-EDelayNotElapsed) +- [`EWrongPendingAction`](#delayed_transfer-EWrongPendingAction) +- [`EWrongDelayedTransferWrapper`](#delayed_transfer-EWrongDelayedTransferWrapper) +- [`EWrongDelayedTransferObject`](#delayed_transfer-EWrongDelayedTransferObject) + +#### Types [!toc] [#delayed_transfer-Types] + + +Dynamic object-field key used to store the wrapped object under the wrapper. + + + +Wrapper object storing `min_delay_ms` and optional pending schedule state. + + + +Represents a scheduled transfer (`recipient = some`) or scheduled unwrap (`recipient = none`). + + + +Hot-potato guard proving a value extracted with `borrow_val` is returned to the same wrapper. + + +#### Functions [!toc] [#delayed_transfer-Functions] + + +Wraps `obj` in a delayed-transfer wrapper, records the minimum execution delay, and transfers initial wrapper custody to `recipient`. + +The wrapped object is stored under a dynamic object field so object ID discovery remains straightforward for indexers. + +Emits a `WrapExecuted` event. + + + +Returns immutable access to the wrapped object. + + + +Returns mutable access to the wrapped object. + + + +Temporarily extracts the wrapped object and returns a guard that must be consumed by `return_val`. + + + +Returns a previously borrowed object to its wrapper. + +Aborts when wrapper/object identity checks fail. + + + +Schedules a transfer to `new_owner` at `clock.timestamp_ms() + min_delay_ms`. + +Aborts when another action is already scheduled. + +Emits a `TransferScheduled` event. + + + +Schedules delayed self-recovery (unwrap) of the wrapped object. + +Aborts when another action is already scheduled. + +Emits an `UnwrapScheduled` event. + + + +Executes a scheduled transfer once delay has elapsed. + +Consumes the wrapper. + +Aborts when no transfer is scheduled, wrong action type is scheduled, or delay has not elapsed. + +Emits an `OwnershipTransferred` event on success. + + + +Executes a scheduled unwrap once delay has elapsed and returns the wrapped object. + +Aborts when no unwrap is scheduled, wrong action type is scheduled, or delay has not elapsed. + +Emits an `UnwrapExecuted` event. + + + +Cancels the currently scheduled transfer or unwrap action. + +Aborts when no action is pending. + +Emits a `PendingTransferCancelled` event. + + +#### Events [!toc] [#delayed_transfer-Events] + + +Emitted when an object is wrapped. + + + +Emitted when a delayed transfer is scheduled. + + + +Emitted when delayed unwrap is scheduled. + + + +Emitted when scheduled transfer execution completes. + + + +Emitted when pending schedule is cancelled. + + + +Emitted when delayed unwrap execution completes. + + +#### Errors [!toc] [#delayed_transfer-Errors] + + +Raised when trying to schedule while another action is already pending. + + + +Raised when execution/cancellation is requested without pending state. + + + +Raised when execution occurs before `execute_after_ms`. + + + +Raised when calling transfer execution on unwrap state (or vice versa). + + + +Raised when a `Borrow` guard is used against a different wrapper. + + + +Raised when returning an object different from the one originally borrowed. + diff --git a/content/contracts-sui/1.x/api/math.mdx b/content/contracts-sui/1.x/api/math.mdx new file mode 100644 index 00000000..3f9456e6 --- /dev/null +++ b/content/contracts-sui/1.x/api/math.mdx @@ -0,0 +1,422 @@ +--- +title: Integer Math API Reference +--- + +This page documents the public API of `openzeppelin_math` for OpenZeppelin Contracts for Sui `v1.x`. + +### `rounding` [toc] [#rounding] + + + +```move +use openzeppelin_math::rounding; +``` + +Rounding strategy helpers shared by arithmetic operations across width-specific modules. + +Types + +- [`RoundingMode`](#rounding-RoundingMode) + +Functions + +- [`down()`](#rounding-down) +- [`up()`](#rounding-up) +- [`nearest()`](#rounding-nearest) + +#### Types [!toc] [#rounding-Types] + + +`Down` always rounds toward zero, `Up` rounds toward ceiling, and `Nearest` rounds to the closest integer with ties rounded up. + + +#### Functions [!toc] [#rounding-Functions] + + +Returns `RoundingMode::Down`. + + + +Returns `RoundingMode::Up`. + + + +Returns `RoundingMode::Nearest` (round-half-up behavior). + + +### `decimal_scaling` [toc] [#decimal_scaling] + + + +```move +use openzeppelin_math::decimal_scaling; +``` + +Helpers for converting balances between decimal precisions while enforcing safe casts and explicit truncation semantics. + +Functions + +- [`safe_downcast_balance(raw_amount, source_decimals, target_decimals)`](#decimal_scaling-safe_downcast_balance) +- [`safe_upcast_balance(amount, source_decimals, target_decimals)`](#decimal_scaling-safe_upcast_balance) + +Errors + +- [`ESafeDowncastOverflowedInt`](#decimal_scaling-ESafeDowncastOverflowedInt) +- [`EInvalidDecimals`](#decimal_scaling-EInvalidDecimals) + +#### Functions [!toc] [#decimal_scaling-Functions] + + +Converts a `u256` amount to `u64` across decimal domains. When scaling down (`source_decimals > target_decimals`), fractional residue is truncated, not rounded. + +Aborts when decimals exceed 24 or when the scaled value cannot fit in `u64`. + + + +Converts a `u64` amount to `u256` across decimal domains. + +Scaling up preserves precision. Scaling down truncates fractional residue (no rounding). Aborts when decimal values exceed the supported range. + + +#### Errors [!toc] [#decimal_scaling-Errors] + + +Raised when a downcast path would require representing a value larger than `u64::MAX`. + + + +Raised when either decimal argument is greater than the package limit (`24`). + + +### Unsigned width modules [toc] [#unsigned-width-modules] + +These modules expose a consistent API surface across unsigned widths. + +Each module wraps shared arithmetic helpers with width-specific bit limits and representability checks. + +Source modules: + +- [`u8.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u8.move) +- [`u16.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u16.move) +- [`u32.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u32.move) +- [`u64.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u64.move) +- [`u128.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u128.move) +- [`u256.move`](https://github.com/OpenZeppelin/contracts-sui/blob/v1.0.0/math/core/sources/u256.move) + +Functions + +- [`average(a, b, rounding_mode)`](#width-average) +- [`checked_shl(value, shift)`](#width-checked_shl) +- [`checked_shr(value, shift)`](#width-checked_shr) +- [`mul_div(a, b, denominator, rounding_mode)`](#width-mul_div) +- [`mul_shr(a, b, shift, rounding_mode)`](#width-mul_shr) +- [`clz(value)`](#width-clz) +- [`msb(value)`](#width-msb) +- [`log2(value, rounding_mode)`](#width-log2) +- [`log256(value, rounding_mode)`](#width-log256) +- [`log10(value, rounding_mode)`](#width-log10) +- [`sqrt(value, rounding_mode)`](#width-sqrt) +- [`inv_mod(value, modulus)`](#width-inv_mod) +- [`mul_mod(a, b, modulus)`](#width-mul_mod) + +#### Functions [!toc] [#width-Functions] + + +Returns the arithmetic mean of `a` and `b` for `Int in {u8, u16, u32, u64, u128, u256}`, rounded according to `rounding_mode`. + + + +Performs a lossless left shift. Returns `none()` when shifting would consume non-zero bits. + + + +Performs a lossless right shift. Returns `none()` when shifting would consume non-zero bits. + + + +Computes `(a * b) / denominator` with configured rounding. Returns `none()` when the rounded value is not representable in the target width. + +Aborts when `denominator` is zero. + + + +Computes `(a * b) >> shift` with configured rounding and returns `none()` when the rounded result overflows the target width. + + + +Counts leading zero bits. For `u256`, the return type is `u16`; for other widths it is `u8`. + + + +Returns the zero-based index of the most significant set bit. By convention returns `0` for `value = 0`. + + + +Computes base-2 logarithm with configured rounding. For `u256`, the return type is `u16`; for other widths it is `u8`. + + + +Computes base-256 logarithm with configured rounding. + + + +Computes base-10 logarithm with configured rounding. + + + +Computes square root with configured rounding. + + + +Computes modular multiplicative inverse. + +Returns `none()` when inverse does not exist (including `modulus = 1`). + +Aborts when `modulus` is zero. + + + +Computes `(a * b) mod modulus`. + +Aborts when `modulus` is zero. + + +### `u512` [toc] [#u512] + + + +```move +use openzeppelin_math::u512; +``` + +Wide-integer helper module used by high-precision arithmetic paths where `u256` intermediates may overflow. + +Types + +- [`U512`](#u512-U512) + +Functions + +- [`new(hi, lo)`](#u512-new) +- [`zero()`](#u512-zero) +- [`from_u256(value)`](#u512-from_u256) +- [`hi(value)`](#u512-hi) +- [`lo(value)`](#u512-lo) +- [`ge(value, other)`](#u512-ge) +- [`mul_u256(a, b)`](#u512-mul_u256) +- [`div_rem_u256(numerator, divisor)`](#u512-div_rem_u256) + +Errors + +- [`ECarryOverflow`](#u512-ECarryOverflow) +- [`EUnderflow`](#u512-EUnderflow) +- [`EDivideByZero`](#u512-EDivideByZero) +- [`EInvalidRemainder`](#u512-EInvalidRemainder) + +#### Types [!toc] [#u512-Types] + + +Represents a 512-bit unsigned integer as two `u256` limbs. + + +#### Functions [!toc] [#u512-Functions] + + +Builds a `U512` value from explicit high and low limbs. + + + +Returns the all-zero `U512` value. + + + +Embeds a `u256` value as `U512 { hi: 0, lo: value }`. + + + +Returns the upper 256-bit limb. + + + +Returns the lower 256-bit limb. + + + +Lexicographic greater-or-equal comparison between two wide integers. + + + +Computes full-width product `a * b` as a 512-bit result. + +Aborts with `ECarryOverflow` if an internal carry invariant is violated. + + + +Divides a 512-bit numerator by a non-zero `u256` divisor. + +Returns `(overflow, quotient, remainder)`, where `overflow = true` indicates the exact quotient does not fit in `u256`. +In overflow cases, `quotient` is returned as `0` while `remainder` remains correct. + + +#### Errors [!toc] [#u512-Errors] + + +Raised when cross-limb accumulation produces an out-of-range final carry. + + + +Raised when an internal borrow operation would underflow the high limb. + + + +Raised when `div_rem_u256` is called with `divisor = 0`. + + + +Raised when internal division remainder invariants are violated. + diff --git a/content/contracts-sui/1.x/index.mdx b/content/contracts-sui/1.x/index.mdx new file mode 100644 index 00000000..6c035768 --- /dev/null +++ b/content/contracts-sui/1.x/index.mdx @@ -0,0 +1,75 @@ +--- +title: Contracts for Sui 1.x +--- + +**OpenZeppelin Contracts for Sui v1.x** ships two core packages: + +- `openzeppelin_math` for deterministic arithmetic, configurable rounding, and decimal scaling. +- `openzeppelin_access` for ownership-transfer wrappers around privileged `key + store` objects. + +## Quickstart + +### Prerequisites + +- Sui CLI installed. +- MVR CLI installed. +- A new or existing Move package. + +### 1. Create a Move Package + +```bash +sui move new my_sui_app +cd my_sui_app +``` + +### 2. Add OpenZeppelin Dependencies from MVR + +```bash +mvr add @openzeppelin-move/access +mvr add @openzeppelin-move/integer-math +``` + +### 3. Verify `Move.toml` + +`mvr add` updates `Move.toml` automatically. It should include: + +```toml +[dependencies] +openzeppelin_access = { r.mvr = "@openzeppelin-move/access" } +openzeppelin_math = { r.mvr = "@openzeppelin-move/integer-math" } +``` + +### 4. Add a Minimal Module + +Create `sources/quickstart.move`: + +```move +module my_sui_app::quickstart; + +use openzeppelin_math::{rounding, u64}; +use std::option; + +// === Functions === + +public fun quote_with_fee(amount: u64): u64 { + // 2.5% fee, rounded to nearest. + let quoted = amount.mul_div(1025u64, 1000u64, rounding::nearest()); + quoted.destroy_some() +} + +public fun sqrt_floor(value: u64): u64 { + value.sqrt(rounding::down()) +} +``` + +### 5. Build and Test + +```bash +sui move build +sui move test +``` + +## Next Steps + +- Read package guides: [Integer Math](/contracts-sui/1.x/math), [Access](/contracts-sui/1.x/access). +- Use API docs: [Integer Math](/contracts-sui/1.x/api/math), [Access](/contracts-sui/1.x/api/access). diff --git a/content/contracts-sui/1.x/math.mdx b/content/contracts-sui/1.x/math.mdx new file mode 100644 index 00000000..352b11b3 --- /dev/null +++ b/content/contracts-sui/1.x/math.mdx @@ -0,0 +1,80 @@ +--- +title: Integer Math +--- + +The `openzeppelin_math` package is the numeric foundation for OpenZeppelin Contracts for Sui. It provides deterministic arithmetic across unsigned integer widths, explicit rounding controls, and helpers for decimal normalization. + +Use this package when your app needs arithmetic behavior that is predictable, auditable, and safe around overflow and precision boundaries. Instead of hiding rounding and truncation inside implementation details, `openzeppelin_math` makes those decisions explicit so they can be part of your protocol rules. + +## Usage + +Add the dependency in `Move.toml`: + +```toml +[dependencies] +openzeppelin_math = { r.mvr = "@openzeppelin-move/integer-math" } +``` + +Import the modules you need: + +```move +use openzeppelin_math::{rounding, u64}; +``` + +## Examples + +### Fee quote with explicit rounding + +```move +module my_sui_app::pricing; + +use openzeppelin_math::{rounding, u64}; + +const EMathOverflow: u64 = 0; + +public fun quote_with_fee(amount: u64): u64 { + u64::mul_div(amount, 1025u64, 1000u64, rounding::nearest()) + .destroy_or!(abort EMathOverflow) +} +``` + +### Square root with deterministic rounding + +```move +module my_sui_app::analytics; + +use openzeppelin_math::{rounding, u64}; + +public fun sqrt_floor(value: u64): u64 { + u64::sqrt(value, rounding::down()) +} +``` + +### Decimal normalization between precisions + +```move +module my_sui_app::scaling; + +use openzeppelin_math::decimal_scaling; + +// === Functions === + +public fun scale_up_6_to_9(amount: u64): u256 { + decimal_scaling::safe_upcast_balance(amount, 6u8, 9u8) +} + +public fun scale_down_9_to_6(amount: u256): u64 { + decimal_scaling::safe_downcast_balance(amount, 9u8, 6u8) +} +``` + +## Picking the right primitives + +- `rounding`: shared rounding policy (`Down`, `Up`, `Nearest`) for value-sensitive paths. +- `u8`, `u16`, `u32`, `u64`, `u128`, `u256`: same API surface across widths for portability. +- `decimal_scaling`: decimal conversion between systems using different precision. +- `u512`: wide intermediate support for high-precision arithmetic paths. + +## API Reference + +Use the full function-level reference here: [Integer Math API](/contracts-sui/1.x/api/math). diff --git a/content/contracts-sui/AGENTS.md b/content/contracts-sui/AGENTS.md new file mode 100644 index 00000000..8883700e --- /dev/null +++ b/content/contracts-sui/AGENTS.md @@ -0,0 +1,102 @@ +# AGENTS.md - Contracts for Sui Docs Conventions + +## Scope +- Applies to files under `content/contracts-sui/`. +- Applies to Move snippets embedded in Markdown/MDX docs. + +## Move code quality checklist +- Apply Sui Move code quality best practices to all examples and snippets. +- Prefer `movefmt` in editor and CI to keep style consistent. + +## Package manifest +- Use Move 2024 edition in `Move.toml` (`edition = "2024"` or `edition = "2024.beta"`). +- Do not add explicit framework dependencies in modern Sui manifests unless strictly required. +- Prefix named addresses to avoid collisions (for example `my_protocol_math` instead of `math`). + +## Modules, imports, and constants +- Prefer module declarations with `;` (`module my_package::my_module;`) over brace-wrapped module declarations. +- Do not use `use x::{Self};`; use `use x;`. +- When importing a module and one of its members, prefer `use x::{Self, Member};`. +- Error constants use `E` + PascalCase (for example `ENotAuthorized`). +- Regular constants use `ALL_CAPS` (for example `MY_CONSTANT`). + +## Move Import Style +- Prefer combining symbols from the same module into one `use` statement. +- Avoid nested grouped imports. +- Keep grouped imports compact: + - One line when readable. + - If wrapped, use at most 3 lines. +- If a grouped import would require more than 3 lines, split it into multiple `use` statements. +- When splitting, group imports by semantic compatibility (similar concern/purpose), not arbitrarily. + +## Examples + +Preferred single line: +```move +use openzeppelin_math::{rounding, u64}; +``` + +Allowed wrapped form (max 3 lines): +```move +use openzeppelin_math::{ + rounding, u64, u128 +}; +``` + +If it exceeds 3 lines, split by semantics: +```move +use openzeppelin_math::{rounding, u64, u128}; +use openzeppelin_math::{decimal_scaling, u256}; +``` + +## Struct and event naming +- Capability types end with `Cap` (for example `AdminCap`). +- Do not use `Potato` suffix for hot-potato values. +- Event structs are past tense (for example `UserRegistered`). +- Dynamic field keys use positional structs with `Key` suffix (for example `DynamicFieldKey()`). + +## Function signatures and API shape +- Do not use `public entry`; use either `public` or `entry`. +- Prefer composable functions for PTBs; keep non-composable entry points explicit. +- Parameter ordering: + - Objects first (except `Clock`). + - Capability parameters second. + - Pure values after objects/caps. + - `ctx: &mut TxContext` last. +- Getter naming: + - Use field-based names (`name`), not `get_name`. + - Mutable getters end in `_mut` (`details_mut`). + +## API Reference entry order +- In API Reference function entries, place behavior description paragraphs first. +- Place `Aborts ...` text after the description. +- Place `Emits ...` text after `Aborts ...`. +- If both are present, the order is always: description, then `Aborts`, then `Emits`. +- After `Aborts`/`Emits`, only `NOTE`, `INFO`, or `WARNING` blocks can appear. + +## Function body idioms +- Prefer dot notation (`x.y(...)`) over function-call notation (`y(x, ...)`) whenever Move supports method syntax for the first argument. +- Prefer method-style calls when available (for example `id.delete()`, `ctx.sender()`, `payment.split(...).into_balance()`). +- Do not import `std::string::utf8`; use `b"...".to_string()` or `b"...".to_ascii_string()`. +- Prefer vector literal/index/method style over legacy `vector::empty/push_back/borrow/length`. +- Use index syntax for compatible collections (for example `&x[&10]`, `&mut x[&10]`). + +## Macro-first control flow +- Prefer macros over manual `while` loops where available: + - Option: `do!`, `destroy_or!`. + - Repetition: `N.do!`. + - Vector construction/iteration: `vector::tabulate!`, `do_ref!`, `destroy!`, `fold!`, `filter!`. + +## Testing +- Combine test attributes in one line (`#[test, expected_failure(...)]`). +- In `expected_failure` tests, do not add cleanup past the failure point. +- In `_tests` modules, do not prefix test function names with `test_`. +- Do not use `test_scenario` when only a context is needed; prefer `tx_context::dummy()`. +- Do not pass abort codes to `assert!`. +- Use `assert_eq!` whenever possible. +- Prefer `sui::test_utils::destroy` for cleanup in tests. + +## Pattern matching and comments +- Use 2024 unpack shorthand (`let MyStruct { id, .. } = value;`) when ignoring fields. +- Doc comments use `///` (not JavaDoc-style comments). +- Add short `//` comments around non-obvious logic, assumptions, and TODOs. diff --git a/content/contracts-sui/index.mdx b/content/contracts-sui/index.mdx new file mode 100644 index 00000000..850497c3 --- /dev/null +++ b/content/contracts-sui/index.mdx @@ -0,0 +1,37 @@ +--- +title: Contracts for Sui +--- + +import { latestStable } from "./latest-versions.js"; + +**OpenZeppelin Contracts for Sui** is a Move library for building secure applications on Sui with production-oriented access and math primitives. + +## Getting Started + + + + Install packages, set up a Move project, and run a first end-to-end integration. + + + +## Packages + + + + Package-level guide for math primitives, including intent, module map, and usage boundaries. + + + Package-level guide for access primitives, including transfer policy selection and safety models. + + + +## API Reference + + + + Explore the complete math API, including all functions and types. + + + Explore the complete access API reference, including module-level functions, core types, emitted events, and expected error conditions for integration. + + diff --git a/content/contracts-sui/latest-versions.js b/content/contracts-sui/latest-versions.js new file mode 100644 index 00000000..403f2bc4 --- /dev/null +++ b/content/contracts-sui/latest-versions.js @@ -0,0 +1 @@ +export const latestStable = "1.x"; diff --git a/source.config.ts b/source.config.ts index cf9c9c88..c1c81411 100644 --- a/source.config.ts +++ b/source.config.ts @@ -13,6 +13,8 @@ export const docs = defineDocs({ // Async mode - enables runtime compilation for faster dev server startup docs: { async: true, + // Restrict docs pages to MDX content files. + files: ["**/*.mdx"], }, // To switch back to sync mode (pre-compilation), comment out the docs config above and uncomment below: // (sync mode - pre-compiles all content at build time) diff --git a/src/app/page.tsx b/src/app/page.tsx index ebe274ac..57449272 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,6 +23,7 @@ import { RelayersIcon, StarknetIcon, StellarIcon, + SuiIcon, TransactionProposalIcon, UniswapIcon, ZamaIcon, @@ -102,6 +103,9 @@ export default function HomePage() {
+
+ +
@@ -113,8 +117,8 @@ export default function HomePage() {
- Contracts libraries are also available for Starknet, Stellar, - Zama FHEVM, and more blockchains + Contracts libraries are also available for Starknet, Sui, + Stellar, Zama FHEVM, and more blockchains
@@ -210,6 +214,14 @@ export default function HomePage() { glowColor="starknet" /> + } + title="Sui" + description="Build Move smart contracts on Sui with secure and efficient primitives" + glowColor="sui" + /> + } diff --git a/src/components/banners/version-banner.tsx b/src/components/banners/version-banner.tsx index c2231076..e8625f54 100644 --- a/src/components/banners/version-banner.tsx +++ b/src/components/banners/version-banner.tsx @@ -1,6 +1,7 @@ "use client"; import { latestStable } from "content/contracts-cairo/latest-versions"; +import { latestStable as suiLatestStable } from "content/contracts-sui/latest-versions"; import { Callout } from "fumadocs-ui/components/callout"; import Link from "next/link"; import { usePathname } from "next/navigation"; @@ -21,6 +22,10 @@ export function VersionBanner() { const currentVersions: Record = { contracts: { version: "5.x", path: "/contracts/5.x" }, "cairo-contracts": { version: latestStable, path: "/cairo-contracts" }, + "contracts-sui": { + version: suiLatestStable, + path: `/contracts-sui/${suiLatestStable}`, + }, "contracts-stylus": { version: "latest", path: "/contracts-stylus" }, "stellar-contracts": { version: "latest", path: "/stellar-contracts" }, "substrate-runtimes": { version: "latest", path: "/substrate-runtimes" }, diff --git a/src/components/home-cards.tsx b/src/components/home-cards.tsx index 82368bfe..1b6be5fa 100644 --- a/src/components/home-cards.tsx +++ b/src/components/home-cards.tsx @@ -115,6 +115,7 @@ function FeatureCard({ "before:bg-gradient-to-r before:from-[#8b5cf6] before:to-[#a78bfa]", polkadot: "before:bg-gradient-to-r before:from-[#e6007a] before:to-[#ff4081]", + sui: "before:bg-gradient-to-r before:from-[#7dd3fc] before:to-[#0284c7]", }; return ( @@ -158,6 +159,7 @@ function EcosystemCard({ "before:bg-gradient-to-r before:from-[#2600FE] before:to-[#2600FE]", polkadot: "before:bg-gradient-to-r before:from-[#e6007a] before:to-[#ff4081]", + sui: "before:bg-gradient-to-r before:from-[#7dd3fc] before:to-[#0284c7]", uniswap: "before:bg-gradient-to-r before:from-[#e6007a] before:to-[#FC75FE]", zama: "before:bg-gradient-to-r before:from-[#FFD205] before:to-[#FFD205]", diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index 83556dce..6581ba71 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -13,6 +13,7 @@ export { PolkadotIcon } from "./polkadot-icon"; export { RelayersIcon } from "./relayers-icon"; export { StarknetIcon } from "./starknet-icon"; export { StellarIcon } from "./stellar-icon"; +export { SuiIcon } from "./sui-icon"; export { TransactionProposalIcon } from "./transaction-proposal-icon"; export { UniswapIcon } from "./uniswap-icon"; export { ZamaIcon } from "./zama-icon"; diff --git a/src/components/icons/sui-icon.tsx b/src/components/icons/sui-icon.tsx new file mode 100644 index 00000000..d231e181 --- /dev/null +++ b/src/components/icons/sui-icon.tsx @@ -0,0 +1,25 @@ +export function SuiIcon({ + className, + color, +}: { + className: string; + color?: boolean; +}) { + return ( + + Sui Icon + + + ); +} diff --git a/src/components/layout/docs-layout-client.tsx b/src/components/layout/docs-layout-client.tsx index b2ba7cef..d654dff2 100644 --- a/src/components/layout/docs-layout-client.tsx +++ b/src/components/layout/docs-layout-client.tsx @@ -11,6 +11,7 @@ import { PolkadotIcon, StarknetIcon, StellarIcon, + SuiIcon, UniswapIcon, ZamaIcon, } from "@/components/icons"; @@ -87,6 +88,11 @@ export function DocsLayoutClient({ children }: DocsLayoutClientProps) { url: "/contracts-cairo", icon: , }, + { + title: "Sui", + url: "/contracts-sui", + icon: , + }, { title: "Stellar", url: "/stellar-contracts", diff --git a/src/hooks/use-navigation-tree.ts b/src/hooks/use-navigation-tree.ts index 18d9abc3..5c57bc00 100644 --- a/src/hooks/use-navigation-tree.ts +++ b/src/hooks/use-navigation-tree.ts @@ -10,6 +10,7 @@ import { polkadotTree, starknetTree, stellarTree, + suiTree, uniswapTree, zamaTree, } from "@/navigation"; @@ -25,6 +26,8 @@ export function useNavigationTree() { sessionStorage.setItem("lastEcosystem", "stellar"); } else if (pathname.startsWith("/substrate-runtimes")) { sessionStorage.setItem("lastEcosystem", "polkadot"); + } else if (pathname.startsWith("/contracts-sui")) { + sessionStorage.setItem("lastEcosystem", "sui"); } else if (pathname.startsWith("/contracts-stylus")) { sessionStorage.setItem("lastEcosystem", "contracts-stylus"); } else if ( @@ -49,6 +52,8 @@ export function useNavigationTree() { return arbitrumStylusTree; } else if (pathname.startsWith("/contracts-cairo")) { return starknetTree; + } else if (pathname.startsWith("/contracts-sui")) { + return suiTree; } else if (pathname.startsWith("/stellar-contracts")) { return stellarTree; } else if (pathname.startsWith("/contracts-compact")) { diff --git a/src/lib/export-search-indexes.ts b/src/lib/export-search-indexes.ts index 9d35f72e..bd66d9fb 100644 --- a/src/lib/export-search-indexes.ts +++ b/src/lib/export-search-indexes.ts @@ -1,4 +1,5 @@ import type { DocumentRecord } from "fumadocs-core/search/algolia"; +import type { Page } from "fumadocs-core/source"; import { source } from "@/lib/source"; export async function exportSearchIndexes() { @@ -8,7 +9,6 @@ export async function exportSearchIndexes() { const excludedVersions = [ "/3.x/", "/4.x/", - "/1.0.0/", "/0.1.0/", "/0.2.0/", "/2.0.0/", @@ -18,7 +18,8 @@ export async function exportSearchIndexes() { const filteredPages = source .getPages() .filter( - (page) => + (page: Page) => + page.url.startsWith("/contracts-sui/") || !excludedVersions.some((excluded) => page.url.includes(excluded)), ); diff --git a/src/navigation/index.ts b/src/navigation/index.ts index c81c3f39..eb8ac81b 100644 --- a/src/navigation/index.ts +++ b/src/navigation/index.ts @@ -5,6 +5,7 @@ import midnightData from "./midnight.json"; import polkadotData from "./polkadot.json"; import starknetData from "./starknet"; import stellarData from "./stellar.json"; +import suiData from "./sui"; import type { NavigationNode, NavigationTree } from "./types"; import uniswapData from "./uniswap.json"; import zamaData from "./zama.json"; @@ -15,6 +16,7 @@ const arbitrumStylus = arbitrumStylusData as NavigationNode[]; const stellar = stellarData as NavigationNode[]; const midnight = midnightData as NavigationNode[]; const starknet = starknetData as NavigationNode[]; +const sui = suiData as NavigationNode[]; const zama = zamaData as NavigationNode[]; const uniswap = uniswapData as NavigationNode[]; const polkadot = polkadotData as NavigationNode[]; @@ -46,6 +48,11 @@ export const starknetTree: NavigationTree = { children: starknet, }; +export const suiTree: NavigationTree = { + name: "Sui", + children: sui, +}; + export const zamaTree: NavigationTree = { name: "Zama FHEVM", children: zama, diff --git a/src/navigation/sui/current.json b/src/navigation/sui/current.json new file mode 100644 index 00000000..521f0e65 --- /dev/null +++ b/src/navigation/sui/current.json @@ -0,0 +1,49 @@ +[ + { + "type": "page", + "name": "Getting Started", + "url": "/contracts-sui" + }, + { + "type": "separator", + "name": "Sui Contracts" + }, + { + "type": "page", + "name": "Overview", + "url": "/contracts-sui/1.x" + }, + { + "type": "folder", + "name": "Packages", + "defaultOpen": true, + "children": [ + { + "type": "page", + "name": "Integer Math", + "url": "/contracts-sui/1.x/math" + }, + { + "type": "page", + "name": "Access", + "url": "/contracts-sui/1.x/access" + } + ] + }, + { + "type": "folder", + "name": "API Reference", + "children": [ + { + "type": "page", + "name": "Integer Math", + "url": "/contracts-sui/1.x/api/math" + }, + { + "type": "page", + "name": "Access", + "url": "/contracts-sui/1.x/api/access" + } + ] + } +] diff --git a/src/navigation/sui/index.ts b/src/navigation/sui/index.ts new file mode 100644 index 00000000..13bff028 --- /dev/null +++ b/src/navigation/sui/index.ts @@ -0,0 +1,8 @@ +import type { NavigationNode } from "../types"; +import currentData from "./current.json"; + +const current = currentData as NavigationNode[]; + +const suiNavigation: NavigationNode[] = [...current]; + +export default suiNavigation;