Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .kiro/specs/invalid-parameters-validation/.config.kiro
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"specId": "695ef77b-6e75-4bf1-905a-d174b32fbfc1", "workflowType": "requirements-first", "specType": "feature"}
209 changes: 209 additions & 0 deletions .kiro/specs/invalid-parameters-validation/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Design Document

## Overview

This feature adds input validation to the `create_token` entry point of the `TokenFactory` Soroban smart contract. Currently `Error::InvalidParameters` is defined but never used, causing a dead-code warning and allowing callers to pass nonsensical values. The change is minimal: a validation block is inserted at the top of `create_token` that checks each caller-supplied parameter and returns `Error::InvalidParameters` early if any check fails.

No new types, storage keys, or external dependencies are introduced. The fix is entirely contained within `contracts/token-factory/src/lib.rs` and its companion test file `contracts/token-factory/src/test.rs`.

## Architecture

The contract follows a single-file, flat architecture typical of Soroban contracts. All logic lives in `lib.rs`; tests live in `test.rs` (included via `mod test`).

```mermaid
flowchart TD
Caller -->|create_token| CT[create_token]
CT --> V{Validate params}
V -- invalid --> E[Err InvalidParameters]
V -- valid --> P[require_not_paused]
P --> F[fee check & transfer]
F --> D[deploy token]
D --> S[store TokenInfo]
S --> OK[Ok address]
```

The validation block is the first thing that runs inside `create_token`, before the pause check and before any auth or fee logic. This is the cheapest possible rejection path.

## Components and Interfaces

### Validation block (new code in `create_token`)

```rust
// Parameter validation — runs before any auth, fee, or storage access
if name.len() == 0 {
return Err(Error::InvalidParameters);
}
if symbol.len() == 0 {
return Err(Error::InvalidParameters);
}
if decimals > 18 {
return Err(Error::InvalidParameters);
}
if initial_supply < 0 {
return Err(Error::InvalidParameters);
}
```

`soroban_sdk::String` exposes a `.len() -> u32` method, so the empty-string check is a direct SDK call with no allocation. The `decimals > 18` check is a plain `u32` comparison. The `initial_supply < 0` check is a plain `i128` comparison.

### Error enum (unchanged)

`Error::InvalidParameters = 3` already exists. No changes to the enum are needed.

### Test additions (`test.rs`)

Five new `#[test]` functions are added to the existing test module, one per acceptance criterion in Requirement 6:

| Test name | Parameter under test | Input | Expected result |
|---|---|---|---|
| `test_create_token_rejects_empty_name` | `name` | `""` | `Err(Ok(Error::InvalidParameters))` |
| `test_create_token_rejects_empty_symbol` | `symbol` | `""` | `Err(Ok(Error::InvalidParameters))` |
| `test_create_token_rejects_decimals_19` | `decimals` | `19` | `Err(Ok(Error::InvalidParameters))` |
| `test_create_token_accepts_decimals_18` | `decimals` | `18` | result ≠ `Err(Ok(Error::InvalidParameters))` |
| `test_create_token_rejects_negative_supply` | `initial_supply` | `-1` | `Err(Ok(Error::InvalidParameters))` |

## Data Models

No new data models are introduced. The existing `TokenInfo`, `FactoryState`, and `Error` types are unchanged.

The only observable state change is that `create_token` now returns `Err(Error::InvalidParameters)` for previously-accepted (but semantically invalid) inputs. No new storage keys are written.

## Correctness Properties

*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*


### Property 1: Empty name is always rejected

*For any* call to `create_token` where `name` is an empty string (and all other parameters are otherwise valid), the contract SHALL return `Err(Error::InvalidParameters)` and no token SHALL be deployed.

**Validates: Requirements 1.1**

### Property 2: Empty symbol is always rejected

*For any* call to `create_token` where `symbol` is an empty string (and all other parameters are otherwise valid), the contract SHALL return `Err(Error::InvalidParameters)` and no token SHALL be deployed.

**Validates: Requirements 2.1**

### Property 3: Decimals above 18 are always rejected

*For any* `decimals` value greater than 18 (i.e., any `u32` in the range 19..=u32::MAX), a call to `create_token` SHALL return `Err(Error::InvalidParameters)` and no token SHALL be deployed.

**Validates: Requirements 3.1**

### Property 4: Negative initial supply is always rejected

*For any* `initial_supply` value less than 0 (i.e., any negative `i128`), a call to `create_token` SHALL return `Err(Error::InvalidParameters)` and no token SHALL be deployed.

**Validates: Requirements 4.1**

### Property 5: All-valid parameters are not rejected by validation

*For any* call to `create_token` where `name` is non-empty, `symbol` is non-empty, `decimals` is in 0..=18, and `initial_supply` is ≥ 0, the contract SHALL NOT return `Err(Error::InvalidParameters)`. (The call may still fail for other reasons such as insufficient fee, but not due to parameter validation.)

**Validates: Requirements 1.2, 2.2, 3.2, 4.2, 4.3**

## Error Handling

All validation errors return `Error::InvalidParameters` (discriminant 3). The contract returns early before any auth check, fee transfer, or storage write, so invalid calls are cheap and leave no side effects.

| Condition | Error returned |
|---|---|
| `name.len() == 0` | `Error::InvalidParameters` |
| `symbol.len() == 0` | `Error::InvalidParameters` |
| `decimals > 18` | `Error::InvalidParameters` |
| `initial_supply < 0` | `Error::InvalidParameters` |

No new error variants are introduced. All other existing error paths (`InsufficientFee`, `ContractPaused`, etc.) are unaffected.

## Testing Strategy

### Dual Testing Approach

Both unit tests and property-based tests are used. Unit tests cover the specific examples mandated by Requirement 6. Property-based tests verify the universal properties above across a wide range of generated inputs.

### Unit Tests (in `contracts/token-factory/src/test.rs`)

Five unit tests are added, one per acceptance criterion in Requirement 6:

```rust
#[test]
fn test_create_token_rejects_empty_name() { ... }

#[test]
fn test_create_token_rejects_empty_symbol() { ... }

#[test]
fn test_create_token_rejects_decimals_19() { ... }

#[test]
fn test_create_token_accepts_decimals_18() { ... }

#[test]
fn test_create_token_rejects_negative_supply() { ... }
```

Unit tests focus on the exact boundary values specified in the requirements (empty string, `decimals = 19`, `decimals = 18`, `initial_supply = -1`).

### Property-Based Tests

The Rust property-based testing library used is **`proptest`** (crate `proptest = "1"`), which integrates cleanly with `no_std`-compatible test harnesses via its `std` feature. Each property test runs a minimum of **100 iterations**.

Each test is tagged with a comment referencing the design property it validates:

```
// Feature: invalid-parameters-validation, Property N: <property text>
```

Property test outline:

```rust
// Feature: invalid-parameters-validation, Property 1: Empty name is always rejected
proptest! {
#[test]
fn prop_empty_name_rejected(symbol in "[a-zA-Z]{1,12}", decimals in 0u32..=18, supply in 0i128..=1_000_000) {
let (env, client, ..) = setup_env();
let result = client.try_create_token(
&Address::generate(&env),
&String::from_str(&env, ""), // empty name
&String::from_str(&env, &symbol),
&decimals,
&supply,
&1000,
);
prop_assert_eq!(result, Err(Ok(Error::InvalidParameters)));
}
}

// Feature: invalid-parameters-validation, Property 2: Empty symbol is always rejected
proptest! { ... }

// Feature: invalid-parameters-validation, Property 3: Decimals above 18 are always rejected
proptest! {
#[test]
fn prop_decimals_above_18_rejected(decimals in 19u32..=u32::MAX) { ... }
}

// Feature: invalid-parameters-validation, Property 4: Negative initial supply is always rejected
proptest! {
#[test]
fn prop_negative_supply_rejected(supply in i128::MIN..=-1i128) { ... }
}

// Feature: invalid-parameters-validation, Property 5: All-valid parameters are not rejected by validation
proptest! {
#[test]
fn prop_valid_params_not_rejected_by_validation(
name in "[a-zA-Z]{1,32}",
symbol in "[a-zA-Z]{1,12}",
decimals in 0u32..=18,
supply in 0i128..=1_000_000,
) {
// result may be Err for other reasons (fee, etc.) but NOT InvalidParameters
...
prop_assert_ne!(result, Err(Ok(Error::InvalidParameters)));
}
}
```

`proptest` is added to `[dev-dependencies]` in `contracts/token-factory/Cargo.toml`. Tests are run with `cargo test -p token-factory`.
83 changes: 83 additions & 0 deletions .kiro/specs/invalid-parameters-validation/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Requirements Document

## Introduction

The `TokenFactory` smart contract exposes a `create_token` entry point that accepts several parameters (name, symbol, decimals, initial_supply). Currently the `Error::InvalidParameters` variant is defined but never used, meaning callers can pass nonsensical values (empty strings, decimals above the ERC-20 maximum of 18) without receiving a meaningful error. This feature adds input validation to `create_token` so that every invalid parameter is caught early and returns `Error::InvalidParameters`, eliminating the dead-code warning and improving contract safety.

## Glossary

- **TokenFactory**: The Soroban smart contract defined in `contracts/token-factory/src/lib.rs` that deploys and manages tokens.
- **create_token**: The public entry point on `TokenFactory` that deploys a new token contract.
- **Error::InvalidParameters**: The existing error variant (discriminant 3) returned when caller-supplied parameters fail validation.
- **decimals**: The number of decimal places for a token, capped at 18 to match the ERC-20 / SEP-0041 convention.
- **initial_supply**: The number of tokens minted to the creator at deployment time; must be non-negative.

## Requirements

### Requirement 1: Validate Token Name

**User Story:** As a contract caller, I want `create_token` to reject an empty token name, so that every deployed token has a human-readable identifier.

#### Acceptance Criteria

1. WHEN `create_token` is called with an empty `name` string, THE `TokenFactory` SHALL return `Error::InvalidParameters` without deploying a token.
2. WHEN `create_token` is called with a non-empty `name` string, THE `TokenFactory` SHALL proceed with the remaining validation steps.

---

### Requirement 2: Validate Token Symbol

**User Story:** As a contract caller, I want `create_token` to reject an empty token symbol, so that every deployed token has a valid ticker.

#### Acceptance Criteria

1. WHEN `create_token` is called with an empty `symbol` string, THE `TokenFactory` SHALL return `Error::InvalidParameters` without deploying a token.
2. WHEN `create_token` is called with a non-empty `symbol` string, THE `TokenFactory` SHALL proceed with the remaining validation steps.

---

### Requirement 3: Validate Decimals

**User Story:** As a contract caller, I want `create_token` to reject a `decimals` value greater than 18, so that deployed tokens conform to the ERC-20 / SEP-0041 standard.

#### Acceptance Criteria

1. WHEN `create_token` is called with `decimals` greater than 18, THE `TokenFactory` SHALL return `Error::InvalidParameters` without deploying a token.
2. WHEN `create_token` is called with `decimals` less than or equal to 18, THE `TokenFactory` SHALL proceed with the remaining validation steps.

---

### Requirement 4: Validate Initial Supply

**User Story:** As a contract caller, I want `create_token` to reject a negative `initial_supply`, so that token accounting starts from a valid non-negative state.

#### Acceptance Criteria

1. WHEN `create_token` is called with `initial_supply` less than 0, THE `TokenFactory` SHALL return `Error::InvalidParameters` without deploying a token.
2. WHEN `create_token` is called with `initial_supply` equal to 0, THE `TokenFactory` SHALL proceed with the remaining validation steps.
3. WHEN `create_token` is called with `initial_supply` greater than 0, THE `TokenFactory` SHALL proceed with the remaining validation steps.

---

### Requirement 5: Eliminate Dead Code Warning for InvalidParameters

**User Story:** As a developer, I want `Error::InvalidParameters` to be actively used in `create_token` validation, so that the compiler no longer reports it as dead code.

#### Acceptance Criteria

1. THE `TokenFactory` SHALL use `Error::InvalidParameters` in at least one reachable code path within `create_token`.
2. WHEN the Rust compiler builds the contract, THE compiler SHALL NOT emit a dead-code warning for the `InvalidParameters` variant.

---

### Requirement 6: Test Coverage for Each Invalid Parameter

**User Story:** As a developer, I want a dedicated test for each invalid parameter case, so that regressions are caught automatically.

#### Acceptance Criteria

1. THE test suite SHALL contain a test that verifies an empty `name` causes `create_token` to return `Err(Ok(Error::InvalidParameters))`.
2. THE test suite SHALL contain a test that verifies an empty `symbol` causes `create_token` to return `Err(Ok(Error::InvalidParameters))`.
3. THE test suite SHALL contain a test that verifies `decimals` equal to 19 causes `create_token` to return `Err(Ok(Error::InvalidParameters))`.
4. THE test suite SHALL contain a test that verifies `decimals` equal to 18 does NOT cause `create_token` to return `Err(Ok(Error::InvalidParameters))`.
5. THE test suite SHALL contain a test that verifies a negative `initial_supply` causes `create_token` to return `Err(Ok(Error::InvalidParameters))`.
66 changes: 66 additions & 0 deletions .kiro/specs/invalid-parameters-validation/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Implementation Plan: Invalid Parameters Validation

## Overview

Add input validation to `create_token` in `contracts/token-factory/src/lib.rs`, then cover it with unit tests and property-based tests in `test.rs`. The change is self-contained: no new types, storage keys, or dependencies beyond adding `proptest` to `[dev-dependencies]`.

## Tasks

- [x] 1. Add validation block to `create_token` in `lib.rs`
- Insert the four guard checks (`name.len() == 0`, `symbol.len() == 0`, `decimals > 18`, `initial_supply < 0`) at the very top of `create_token`, before `Self::require_not_paused` and `creator.require_auth()`
- Each failing check returns `Err(Error::InvalidParameters)` immediately
- _Requirements: 1.1, 1.2, 2.1, 2.2, 3.1, 3.2, 4.1, 4.2, 4.3, 5.1, 5.2_

- [x] 2. Add unit tests to `test.rs`
- [x] 2.1 Write unit test `test_create_token_rejects_empty_name`
- Call `try_create_token` with `name = ""` and valid other params; assert `Err(Ok(Error::InvalidParameters))`
- _Requirements: 1.1, 6.1_
- [x] 2.2 Write unit test `test_create_token_rejects_empty_symbol`
- Call `try_create_token` with `symbol = ""` and valid other params; assert `Err(Ok(Error::InvalidParameters))`
- _Requirements: 2.1, 6.2_
- [x] 2.3 Write unit test `test_create_token_rejects_decimals_19`
- Call `try_create_token` with `decimals = 19`; assert `Err(Ok(Error::InvalidParameters))`
- _Requirements: 3.1, 6.3_
- [x] 2.4 Write unit test `test_create_token_accepts_decimals_18`
- Call `try_create_token` with `decimals = 18`; assert result ≠ `Err(Ok(Error::InvalidParameters))`
- _Requirements: 3.2, 6.4_
- [x] 2.5 Write unit test `test_create_token_rejects_negative_supply`
- Call `try_create_token` with `initial_supply = -1`; assert `Err(Ok(Error::InvalidParameters))`
- _Requirements: 4.1, 6.5_

- [x] 3. Checkpoint — Ensure all tests pass
- Run `cargo test -p token-factory` and confirm all existing and new unit tests pass. Ask the user if questions arise.

- [x] 4. Add `proptest` to dev-dependencies and write property-based tests
- [x] 4.1 Add `proptest = "1"` to `[dev-dependencies]` in `contracts/token-factory/Cargo.toml` (if not already present)
- _Requirements: 5.1, 5.2_
- [ ]* 4.2 Write property test for Property 1: Empty name is always rejected
- **Property 1: Empty name is always rejected**
- **Validates: Requirements 1.1**
- Use `proptest!` with arbitrary valid `symbol` (`[a-zA-Z]{1,12}`), `decimals` (0..=18), `supply` (0..=1_000_000); fix `name = ""`; assert `Err(Ok(Error::InvalidParameters))`
- [ ]* 4.3 Write property test for Property 2: Empty symbol is always rejected
- **Property 2: Empty symbol is always rejected**
- **Validates: Requirements 2.1**
- Use `proptest!` with arbitrary valid `name`, `decimals`, `supply`; fix `symbol = ""`; assert `Err(Ok(Error::InvalidParameters))`
- [ ]* 4.4 Write property test for Property 3: Decimals above 18 are always rejected
- **Property 3: Decimals above 18 are always rejected**
- **Validates: Requirements 3.1**
- Use `proptest!` with `decimals in 19u32..=u32::MAX`; assert `Err(Ok(Error::InvalidParameters))`
- [ ]* 4.5 Write property test for Property 4: Negative initial supply is always rejected
- **Property 4: Negative initial supply is always rejected**
- **Validates: Requirements 4.1**
- Use `proptest!` with `supply in i128::MIN..=-1i128`; assert `Err(Ok(Error::InvalidParameters))`
- [ ]* 4.6 Write property test for Property 5: All-valid parameters are not rejected by validation
- **Property 5: All-valid parameters are not rejected by validation**
- **Validates: Requirements 1.2, 2.2, 3.2, 4.2, 4.3**
- Use `proptest!` with non-empty `name`, non-empty `symbol`, `decimals in 0..=18`, `supply in 0..=1_000_000`; assert result ≠ `Err(Ok(Error::InvalidParameters))`

- [x] 5. Final checkpoint — Ensure all tests pass
- Run `cargo test -p token-factory` and confirm the full suite (unit + property) passes. Ask the user if questions arise.

## Notes

- Tasks marked with `*` are optional and can be skipped for a faster MVP
- Each task references specific requirements for traceability
- Property tests use `proptest = "1"` in `[dev-dependencies]`; note `proptest` is currently listed under `[dependencies]` in `Cargo.toml` — move it to `[dev-dependencies]` as part of task 4.1
- Run tests with: `cargo test -p token-factory`
Loading
Loading