Skip to content
Closed
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/sdk_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: SDK Tests

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]

jobs:
sdk-tests:
runs-on: ubuntu-latest
defaults:
run:
working-directory: zk/sdk

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: npm install
Comment on lines +25 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Use deterministic dependency installation in CI.

npm install is non-deterministic for CI and can introduce lockfile drift behavior between runs. Switch to npm ci (optionally with the same fallback pattern used in .github/workflows/zk_circuits.yml, Line 52).

Suggested change
       - name: Install dependencies
-        run: npm install
+        run: npm ci --legacy-peer-deps || npm ci
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Install dependencies
run: npm install
- name: Install dependencies
run: npm ci --legacy-peer-deps || npm ci
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/sdk_tests.yml around lines 25 - 26, Replace the
non-deterministic "npm install" invocation in the "Install dependencies" step
with a deterministic CI install: use "npm ci" (or the same "npm ci || npm
install --prefer-offline" fallback pattern used in the other workflow) so
installs use the lockfile and avoid lockfile drift; update the step that
currently runs "npm install" (step name "Install dependencies") to run the
deterministic command instead.


- name: Run SDK tests
run: npm test
329 changes: 329 additions & 0 deletions gateway-contract/contracts/auction_contract/auction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
# Auction Contract Specification

The Auction contract implements two auction flows that coexist in the same contract:

- A singleton username auction flow keyed by instance storage (`close_auction`, `claim_username`).
- An ID-indexed auction flow keyed by persistent storage (`create_auction`, `place_bid`, `close_auction_by_id`, `claim`).

Both flows use Soroban auth (`require_auth`) and ledger timestamp checks to enforce access and timing constraints.

## Public Entry Points

### Function: `create_auction`

Creates a new auction identified by `id`.

#### Interface

```rust
pub fn create_auction(
env: Env,
id: u32,
seller: Address,
asset: Address,
min_bid: i128,
end_time: u64,
)
```

#### Authorization

- `seller.require_auth()` must succeed.

#### Requirements & Validation

- Auction ID must not already exist (`storage::auction_exists(&env, id) == false`).
- If ID already exists, function aborts with `AuctionError::AuctionNotOpen`.

#### State Transitions

- Writes `seller` to `AuctionKey::Seller(id)`.
- Writes bidding token `asset` to `AuctionKey::Asset(id)`.
- Writes `min_bid` to `AuctionKey::MinBid(id)`.
- Writes `end_time` to `AuctionKey::EndTime(id)`.
- Sets status to `AuctionStatus::Open` in `AuctionKey::Status(id)`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotOpen` when `id` already exists.
- Host auth failure if `seller` does not authorize.

#### Edge Cases

- **Duplicate auction ID**: explicitly rejected (panic with `AuctionNotOpen`).
- **No min/end validation**: contract currently does not enforce `min_bid > 0` or `end_time > now` at creation.

Comment on lines +12 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Document the risk of negative min_bid values.

The specification correctly notes that "contract currently does not enforce min_bid > 0" (line 58), but this deserves more prominence. Since min_bid is i128, negative values are technically valid but likely nonsensical for an auction. Consider explicitly documenting this as a design decision or potential validation gap.

📝 Suggested addition to Edge Cases section
 #### Edge Cases
 
 - **Duplicate auction ID**: explicitly rejected (panic with `AuctionNotOpen`).
 - **No min/end validation**: contract currently does not enforce `min_bid > 0` or `end_time > now` at creation.
+- **Negative min_bid**: `i128` type permits negative values; no validation prevents this scenario.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Function: `create_auction`
Creates a new auction identified by `id`.
#### Interface
```rust
pub fn create_auction(
env: Env,
id: u32,
seller: Address,
asset: Address,
min_bid: i128,
end_time: u64,
)
```
#### Authorization
- `seller.require_auth()` must succeed.
#### Requirements & Validation
- Auction ID must not already exist (`storage::auction_exists(&env, id) == false`).
- If ID already exists, function aborts with `AuctionError::AuctionNotOpen`.
#### State Transitions
- Writes `seller` to `AuctionKey::Seller(id)`.
- Writes bidding token `asset` to `AuctionKey::Asset(id)`.
- Writes `min_bid` to `AuctionKey::MinBid(id)`.
- Writes `end_time` to `AuctionKey::EndTime(id)`.
- Sets status to `AuctionStatus::Open` in `AuctionKey::Status(id)`.
#### Events Emitted
- None in current implementation.
#### Errors
- `AuctionError::AuctionNotOpen` when `id` already exists.
- Host auth failure if `seller` does not authorize.
#### Edge Cases
- **Duplicate auction ID**: explicitly rejected (panic with `AuctionNotOpen`).
- **No min/end validation**: contract currently does not enforce `min_bid > 0` or `end_time > now` at creation.
### Function: `create_auction`
Creates a new auction identified by `id`.
#### Interface
🧰 Tools
🪛 LanguageTool

[style] ~42-~42: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...n asset to AuctionKey::Asset(id). - Writes min_bid to AuctionKey::MinBid(id). ...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)


[style] ~43-~43: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...min_bidtoAuctionKey::MinBid(id). - Writes end_timetoAuctionKey::EndTime(id)`...

(ENGLISH_WORD_REPEAT_BEGINNING_RULE)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/auction_contract/auction.md` around lines 12 - 59,
Update the Edge Cases for create_auction to explicitly call out that min_bid is
an i128 and therefore can be negative (a likely nonsensical value), mention that
the current implementation writes min_bid to AuctionKey::MinBid(id) without
validation, and either document this as an accepted design decision or recommend
adding a validation (e.g., require min_bid > 0) and where that would fail
(AuctionError::AuctionNotOpen is unrelated) so reviewers know the gap and
suggested remediation.

### Function: `place_bid`

Places a bid on an existing auction and refunds the previously highest bidder.

#### Interface

```rust
pub fn place_bid(env: Env, id: u32, bidder: Address, amount: i128)
```

#### Authorization

- `bidder.require_auth()` must succeed.

#### Requirements & Validation

- Auction must still be open by time: `env.ledger().timestamp() < auction_end_time`.
- Bid must satisfy both:
- `amount >= min_bid`
- `amount > highest_bid`

If timing check fails, function aborts with `AuctionError::AuctionNotOpen`.
If bid floor/outbid check fails, function aborts with `AuctionError::BidTooLow`.

#### State Transitions

1. Transfers `amount` of auction asset token from `bidder` to contract.
2. If previous highest bidder exists, transfers prior `highest_bid` from contract back to that bidder.
3. Updates `AuctionKey::HighestBidder(id)` to current `bidder`.
4. Updates `AuctionKey::HighestBid(id)` to `amount`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotOpen` when auction time window is closed.
- `AuctionError::BidTooLow` when bid is below min or not strictly above current highest.
- Host auth failure if `bidder` does not authorize.
- Token transfer failure if token contract transfer preconditions are not met.

#### Edge Cases

- **Zero-bid history**: first bid is accepted if it meets `min_bid` and auction is still open.
- **Equal-to-highest bid**: rejected (`amount <= highest_bid` path).
- **Late bid at exact end timestamp**: rejected because condition is `timestamp >= end_time`.

Comment on lines +60 to +107
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Flag the absence of negative bid validation.

The amount parameter is i128, allowing negative values. While the implementation checks amount < min_bid and amount <= highest_bid, a negative amount combined with a negative min_bid could theoretically pass validation. This edge case is not documented.

📝 Suggested addition to Edge Cases section
 #### Edge Cases
 
 - **Zero-bid history**: first bid is accepted if it meets `min_bid` and auction is still open.
 - **Equal-to-highest bid**: rejected (`amount <= highest_bid` path).
 - **Late bid at exact end timestamp**: rejected because condition is `timestamp >= end_time`.
+- **Negative bid amounts**: `i128` type permits negative values; validation relies on comparison with `min_bid` and `highest_bid` without explicit non-negativity check.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/auction_contract/auction.md` around lines 60 -
107, The place_bid implementation and docs miss validation against negative bid
amounts: add an explicit check in place_bid (before token transfers and
comparisons with AuctionKey::HighestBid(id) and min_bid) that rejects any amount
<= 0 (or < 0 depending on desired policy) and aborts with a clear error (e.g.,
AuctionError::InvalidBidAmount or reuse AuctionError::BidTooLow) to prevent
negative/invalid bids, and update the Edge Cases section to document rejection
of negative bids and the requirement that min_bid must be non-negative when
auctions are created.

### Function: `close_auction_by_id`

Closes an ID-indexed auction once its end time has passed.

#### Interface

```rust
pub fn close_auction_by_id(env: Env, id: u32)
```

#### Authorization

- No explicit caller auth in current implementation.

#### Requirements & Validation

- Current ledger timestamp must be at least the auction end time.
- If `timestamp < end_time`, function aborts with `AuctionError::AuctionNotClosed`.

#### State Transitions

- Sets `AuctionKey::Status(id)` to `AuctionStatus::Closed`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::AuctionNotClosed` when called before end time.

#### Edge Cases

- **Early close attempt**: rejected with `AuctionNotClosed`.
- **No bids placed**: still closes successfully; later claim semantics determine payout/ownership behavior.

### Function: `close_auction`

Closes the singleton username auction flow and emits closure metadata.

#### Interface

```rust
pub fn close_auction(
env: Env,
username_hash: BytesN<32>,
) -> Result<(), AuctionError>
```

#### Authorization

- No explicit caller auth in current implementation.

#### Requirements & Validation

- Current instance `status` must be `AuctionStatus::Open`.
- Current ledger timestamp must be at least instance `end_time`.

Returns:

- `Err(AuctionError::AuctionNotOpen)` if status is not `Open`.
- `Err(AuctionError::AuctionNotClosed)` if called before end time.

#### State Transitions

- Sets instance `DataKey::Status` to `AuctionStatus::Closed`.
- Reads instance `DataKey::HighestBidder` and `DataKey::HighestBid` for event payload.

#### Events Emitted

- Emits `AuctionClosedEvent` via `emit_auction_closed` with:
- `username_hash`
- `winner: Option<Address>`
- `winning_bid: u128`

#### Errors

- `AuctionError::AuctionNotOpen`
- `AuctionError::AuctionNotClosed`

#### Edge Cases

- **Zero bids**: event emits `winner = None`, `winning_bid = 0`.
- **Repeated close**: second close call fails with `AuctionNotOpen` because status is no longer `Open`.

Comment on lines +144 to +192
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the type inconsistency between auction flows

echo "=== Singleton username auction (instance storage) ==="
rg -A3 'pub fn get_highest_bid' gateway-contract/contracts/auction_contract/src/storage.rs

echo ""
echo "=== ID-indexed auction (persistent storage) ==="
rg -A3 'pub fn auction_get_highest_bid' gateway-contract/contracts/auction_contract/src/storage.rs

echo ""
echo "=== Event emission in close_auction ==="
rg -B2 -A2 'emit_auction_closed' gateway-contract/contracts/auction_contract/src/lib.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 930


🏁 Script executed:

#!/bin/bash
# Check for min_bid and place_bid type usage in both flows
echo "=== Checking min_bid type in ID-indexed flow ==="
rg -B2 -A2 'fn auction_get_min_bid|min_bid.*i128|min_bid.*u128' gateway-contract/contracts/auction_contract/src/

echo ""
echo "=== Checking place_bid function signature ==="
rg -B2 -A4 'pub fn place_bid' gateway-contract/contracts/auction_contract/src/lib.rs

echo ""
echo "=== Checking auction.md overview section ==="
head -50 gateway-contract/contracts/auction_contract/auction.md

Repository: Alien-Protocol/Alien-Gateway

Length of output: 4240


Document the type inconsistency for bid amounts across auction flows.

The singleton username auction uses u128 for highest bids (line 181: winning_bid: u128), while the ID-indexed auction uses i128 throughout: min_bid, highest bids, and bid amounts in place_bid. This type difference between the two coexisting flows should be explicitly documented in the overview section to prevent confusion.

The inconsistency appears systematically across the ID-indexed flow: min_bid: i128 in function signatures and storage functions like auction_get_min_bid() returns i128, while singleton flow storage returns u128.

Add a note to the overview section clarifying this difference:

Suggested documentation addition
 The Auction contract implements two auction flows that coexist in the same contract:
 
 - A singleton username auction flow keyed by instance storage (`close_auction`, `claim_username`).
 - An ID-indexed auction flow keyed by persistent storage (`create_auction`, `place_bid`, `close_auction_by_id`, `claim`).
+
+**Note**: The two flows use different numeric types for bid amounts:
+- Singleton username auction: `u128` (unsigned) for highest_bid
+- ID-indexed auction: `i128` (signed) for min_bid, highest_bid, and bid amounts
 
 Both flows use Soroban auth (`require_auth`) and ledger timestamp checks to enforce access and timing constraints.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/auction_contract/auction.md` around lines 144 -
192, Add a short note to the contract overview stating that the singleton
username auction uses unsigned 128-bit amounts (u128) for bids and events (e.g.,
close_auction's AuctionClosedEvent winning_bid: u128 and storage for
HighestBid), while the ID-indexed auction flow uses signed 128-bit amounts
(i128) for min_bid, bids and helpers (e.g., place_bid signatures and
auction_get_min_bid returns i128); mention this intentional difference so
readers know to convert/handle types when interacting between flows.

### Function: `claim_username`

Allows winner of singleton username auction to deploy/claim the username via factory contract.

#### Interface

```rust
pub fn claim_username(
env: Env,
username_hash: BytesN<32>,
claimer: Address,
) -> Result<(), AuctionError>
```

#### Authorization

- `claimer.require_auth()` must succeed.

#### Requirements & Validation

- Instance status must not already be `Claimed`.
- Instance status must be `Closed`.
- `claimer` must equal stored highest bidder.
- Factory contract address must exist in `DataKey::FactoryContract`.

Returns:

- `Err(AuctionError::AlreadyClaimed)` if already claimed.
- `Err(AuctionError::NotClosed)` if not closed.
- `Err(AuctionError::NotWinner)` if caller is not winner.
- `Err(AuctionError::NoFactoryContract)` if factory address is missing.

#### State Transitions

1. Sets instance `DataKey::Status` to `AuctionStatus::Claimed`.
2. Invokes factory contract method `deploy_username(username_hash, claimer)`.

#### Events Emitted

- Emits `UsernameClaimedEvent` via `emit_username_claimed` with:
- `username_hash`
- `claimer`

#### Errors

- `AuctionError::AlreadyClaimed`
- `AuctionError::NotClosed`
- `AuctionError::NotWinner`
- `AuctionError::NoFactoryContract`
- Host auth failure if `claimer` does not authorize.

#### Edge Cases

- **No bids**: no highest bidder exists, so claim fails with `NotWinner`.
- **Claim race**: first valid claim sets status to `Claimed`; subsequent claims fail with `AlreadyClaimed`.

### Function: `claim`

Finalizes an ID-indexed auction by allowing the winner to release funds to seller.

#### Interface

```rust
pub fn claim(env: Env, id: u32, claimant: Address)
```

#### Authorization

- `claimant.require_auth()` must succeed.

#### Requirements & Validation

- Auction status for `id` must be `AuctionStatus::Closed`.
- Auction must not already be claimed (`auction_is_claimed == false`).
- `claimant` must equal current highest bidder.

Function aborts with:

- `AuctionError::NotClosed` when status is not closed.
- `AuctionError::AlreadyClaimed` when already claimed.
- `AuctionError::NotWinner` when claimant is not highest bidder.

#### State Transitions

1. Reads token `asset`, `winning_bid`, and `seller` for auction `id`.
2. Transfers `winning_bid` from contract to `seller`.
3. Marks `AuctionKey::Claimed(id)` as `true`.

#### Events Emitted

- None in current implementation.

#### Errors

- `AuctionError::NotClosed`
- `AuctionError::AlreadyClaimed`
- `AuctionError::NotWinner`
- Host auth failure if `claimant` does not authorize.
- Token transfer failure if transfer cannot be completed.

#### Edge Cases

- **No bids**: highest bidder is `None`; all claim attempts fail with `NotWinner`.
- **Double claim**: second successful claimant attempt is blocked by `AlreadyClaimed`.

## Error Variants (Contract-Wide)

Defined in `errors.rs`:

- `NotWinner`
- `AlreadyClaimed`
- `NotClosed`
- `NoFactoryContract`
- `Unauthorized`
- `InvalidState`
- `BidTooLow`
- `AuctionNotOpen`
- `AuctionNotClosed`

Note: `Unauthorized` and `InvalidState` are defined but not currently emitted by the public entry points above; authorization failures are enforced primarily through host-level `require_auth`.

## Event Types (Contract-Wide)

Defined in `events.rs`:

- `AuctionCreatedEvent`
- `BidPlacedEvent`
- `AuctionClosedEvent`
- `UsernameClaimedEvent`
- `BidRefundedEvent`

Current emission in public entry points:

- `close_auction` emits `AuctionClosedEvent`.
- `claim_username` emits `UsernameClaimedEvent`.
- Other listed entry points currently emit no events.

15 changes: 15 additions & 0 deletions gateway-contract/contracts/escrow_contract/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,19 @@ impl Events {
}
.publish(env);
}

/// Emits a DEPOSIT event with topics (symbol!("DEPOSIT"), commitment)
/// and data (owner, amount, new_balance).
pub fn deposit(
env: &Env,
commitment: BytesN<32>,
owner: Address,
amount: i128,
new_balance: i128,
) {
env.events().publish(
(symbol_short!("DEPOSIT"), commitment),
(owner, amount, new_balance),
);
Comment on lines +169 to +181
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify deprecated publish usage and typed deposit event presence.
rg -n 'env\.events\(\)\.publish\(' gateway-contract/contracts/escrow_contract/src/events.rs gateway-contract/contracts/escrow_contract/src/lib.rs
rg -n 'struct\s+DepositEvent|fn\s+deposit\s*\(' gateway-contract/contracts/escrow_contract/src/events.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 186


🏁 Script executed:

# Check soroban-sdk version and examine event patterns
cat gateway-contract/Cargo.toml | grep -A 5 "soroban-sdk"

Repository: Alien-Protocol/Alien-Gateway

Length of output: 158


🏁 Script executed:

# View the full events.rs file to understand event patterns
wc -l gateway-contract/contracts/escrow_contract/src/events.rs

Repository: Alien-Protocol/Alien-Gateway

Length of output: 132


🏁 Script executed:

# Check other event functions to see if they use the old or new pattern
rg -n 'pub fn|env\.events\(\)\.publish|#\[contractevent\]' gateway-contract/contracts/escrow_contract/src/events.rs -B 2 -A 5

Repository: Alien-Protocol/Alien-Gateway

Length of output: 3430


Replace deprecated deposit event emission API (CI blocker).

At Line 178, env.events().publish(...) is deprecated and fails clippy with -D warnings. All other events in the file use the typed #[contractevent] pattern with .publish(env). Replace with a DepositEvent struct to match the pattern used by VaultCancelEvent, PayExecEvent, and other events.

Proposed fix
@@
 #[contractevent]
 #[derive(Clone, Debug, Eq, PartialEq)]
+pub struct DepositEvent {
+    /// The commitment identifier of the vault.
+    #[topic]
+    pub commitment: BytesN<32>,
+    /// The owner funding the deposit.
+    pub owner: Address,
+    /// Amount deposited.
+    pub amount: i128,
+    /// Resulting vault balance.
+    pub new_balance: i128,
+}
+
+#[contractevent]
+#[derive(Clone, Debug, Eq, PartialEq)]
 pub struct VaultCancelEvent {
@@
     pub fn deposit(
         env: &Env,
         commitment: BytesN<32>,
         owner: Address,
         amount: i128,
         new_balance: i128,
     ) {
-        env.events().publish(
-            (symbol_short!("DEPOSIT"), commitment),
-            (owner, amount, new_balance),
-        );
+        DepositEvent {
+            commitment,
+            owner,
+            amount,
+            new_balance,
+        }
+        .publish(env);
     }
🧰 Tools
🪛 GitHub Actions: Checks

[error] 178-178: Clippy (with -D warnings) failed: use of deprecated method soroban_sdk::events::Events::publish—use the #[contractevent] macro on a contract event type. (in env.events().publish(...))

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gateway-contract/contracts/escrow_contract/src/events.rs` around lines 169 -
181, The deposit function currently uses the deprecated
env.events().publish(...) API; replace it by defining a typed event struct
(e.g., DepositEvent with fields commitment: BytesN<32>, owner: Address, amount:
i128, new_balance: i128) and use the same pattern as
VaultCancelEvent/PayExecEvent: construct DepositEvent { ... } and call
.publish(env) to emit it from the deposit function, ensuring the struct is
annotated with #[contractevent] to match other events in the file.

}
}
Loading
Loading