Canonical page: <https://www.guvenkaya.co/reports/spin>

[Work & reports](https://www.guvenkaya.co/work) Report

# Spin Finance Onchain Orderbook and Perpetual Trading Security Review

Guvenkaya reviewed the Spin spot, perpetual and options vault smart contracts on NEAR.

[View PDF on GitHub](https://github.com/Guvenkaya/public-reports/blob/master/Spin-NEAR-Rust-Smart-Contract-Security-Assessment.pdf)

![Spin Finance logo](https://www.guvenkaya.co/reports/assets/clients/spin.svg)

Date of engagement: 15th April 2024 - 28th June 2024

- **Timur Güvenkaya** Principal advisor

## Engagement Dashboard

### Audit results

Guvenkaya conducted a security assessment of the Spin spot, perp, and options-vault contracts from 15th of April 2024 to 28th June 2024. During this engagement, a total of 22 findings were reported. 3 of the findings were critical, 3 high, 5 medium, and the remaining were low or informational severity. By the final report, the Spin team had fixed 9 findings, including all critical ones, and acknowledged the remaining 13.

### Engagement record

- Client

  Spin

- System

  Spin spot, perp and options vault contracts on NEAR

- Review period

  15th April 2024 - 28th June 2024

- Paths in scope

  120

- Principal advisor

  Timur Güvenkaya

### Timeline

1. Review start 15th April 2024
2. Draft report 4th July 2024
3. Final report 3rd September 2024

### Findings by severity

- Critical **3**
- High **3**
- Medium **5**
- Low **4**
- Informational **7**

### Finding status at final report

**22** Raised

**9** Fixed

**13** Acknowledged

### Remediation complexity

**15** Simple

**7** Moderate

### Impact

**6** Severe

**5** Moderate

**11** Negligible

### Likelihood

**5** Likely

**8** Possible

**9** Rare

## Advisory and Client

The advisory

![Guvenkaya](https://www.guvenkaya.co/logo.svg)

Guvenkaya specializes in security for digital assets and financial infrastructure, from smart contracts and protocols to applications, infrastructure, custody and cryptography.

Our team, including the sitting Heads of Security at Gauntlet and Agora, brings experience from Kraken, OpenZeppelin, Binance, ZKsync, Nillion, Invicti and EY. Across the team: 200+ security reviews, $30B+ in volume secured, 13 published cryptography research papers, and credentials including OSCP, OSWE and CISM.

- Every reviewer has a public bio and is named before you sign. We match expertise to your system, with a principal leading scoping and checking findings.

The client

![Spin](https://www.guvenkaya.co/reports/assets/clients/spin.svg)

Founded in June 2021, Spin was the first DEX Orderbook in the NEAR ecosystem to release Perpetual Futures and DeFi Option Vaults. Spin’s mission is extending DeFi services offered on NEAR Protocol also attracting & onboarding the CeFi audience to on-chain trading and investments.

## Scope and Review Boundaries

### Review depths

- Deep

  Line-by-line review with explicit attack hypotheses, adversarial reasoning about state transitions, and reproduction attempts for each hypothesis that survived reading.

- Supporting

  The code was read for its effect on a reviewed flow. Behaviour outside that effect was not assessed.

- Targeted

  Entry points were exercised or triaged against the flow's security properties rather than read in full.

This is the code we read, and how closely. 120 paths are in scope. The original report counts 21,770 lines of code reviewed. Each path links to the file at the reviewed commit.

| Path                                                                                                                                                                                 | Review depth | Scope note             |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | ---------------------- |
| [`marketplace/src/marketplace.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/marketplace.rs)                               | Deep         | Spot Contract          |
| [`marketplace/src/market.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/market.rs)                                         | Deep         | Spot Contract          |
| [`spot/contract/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/spot/contract/src/lib.rs)                                           | Deep         | Spot Contract          |
| [`marketplace/src/event.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/event.rs)                                           | Deep         | Spot Contract          |
| [`marketplace/src/order_book/list.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/order_book/list.rs)                       | Deep         | Spot Contract          |
| [`marketplace/src/order_book/book.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/order_book/book.rs)                       | Deep         | Spot Contract          |
| [`marketplace/src/accounts.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/accounts.rs)                                     | Deep         | Spot Contract          |
| [`marketplace/src/types/buffer.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/buffer.rs)                             | Deep         | Spot Contract          |
| [`marketplace/src/currency.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/currency.rs)                                     | Deep         | Spot Contract          |
| [`marketplace/src/types/map.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/map.rs)                                   | Deep         | Spot Contract          |
| [`marketplace/src/limits.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/limits.rs)                                         | Deep         | Spot Contract          |
| [`marketplace/src/order_book/order.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/order_book/order.rs)                     | Deep         | Spot Contract          |
| [`marketplace/src/order_book/collector.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/order_book/collector.rs)             | Deep         | Spot Contract          |
| [`marketplace/src/types/reverse.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/reverse.rs)                           | Deep         | Spot Contract          |
| [`marketplace/src/types/mod.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/mod.rs)                                   | Deep         | Spot Contract          |
| [`marketplace/src/types/heap.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/heap.rs)                                 | Deep         | Spot Contract          |
| [`spot/contract/src/platform.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/spot/contract/src/platform.rs)                                 | Deep         | Spot Contract          |
| [`marketplace/src/types/set.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/types/set.rs)                                   | Deep         | Spot Contract          |
| [`marketplace/src/keys.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/keys.rs)                                             | Deep         | Spot Contract          |
| [`spot/contract/src/ft.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/spot/contract/src/ft.rs)                                             | Deep         | Spot Contract          |
| [`marketplace/src/order_book/mod.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/order_book/mod.rs)                         | Deep         | Spot Contract          |
| [`marketplace/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/4596971fefe1646512bd66116b725cee2a14aeac/marketplace/src/lib.rs)                                               | Deep         | Spot Contract          |
| [`perp/contract/src/contract.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/contract.rs)                                 | Deep         | Perp Contract          |
| [`perp/contract/src/markets/market.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/markets/market.rs)                     | Deep         | Perp Contract          |
| [`perp/contract/src/risk/liquidation.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/liquidation.rs)                 | Deep         | Perp Contract          |
| [`perp/contract/src/perp/position.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/perp/position.rs)                       | Deep         | Perp Contract          |
| [`perp/contract/src/events/log.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/events/log.rs)                             | Deep         | Perp Contract          |
| [`perp/contract/src/risk/position.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/position.rs)                       | Deep         | Perp Contract          |
| [`core/types/src/buffer.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/buffer.rs)                                           | Deep         | Perp Contract          |
| [`perp/contract/src/events/event.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/events/event.rs)                         | Deep         | Perp Contract          |
| [`core/contract_api/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/contract_api/src/lib.rs)                                   | Deep         | Perp Contract          |
| [`perp/contract/src/perp/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/perp/mod.rs)                                 | Deep         | Perp Contract          |
| [`core/types/src/map.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/map.rs)                                                 | Deep         | Perp Contract          |
| [`perp/contract/src/accounts/account.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/accounts/account.rs)                 | Deep         | Perp Contract          |
| [`perp/contract/src/risk/funding.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/funding.rs)                         | Deep         | Perp Contract          |
| [`perp/contract/src/perp/orders.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/perp/orders.rs)                           | Deep         | Perp Contract          |
| [`perp/contract/src/perp/markets.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/perp/markets.rs)                         | Deep         | Perp Contract          |
| [`perp/contract/src/api.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/api.rs)                                           | Deep         | Perp Contract          |
| [`perp/contract/src/markets/storage.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/markets/storage.rs)                   | Deep         | Perp Contract          |
| [`perp/contract/src/risk/margin.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/margin.rs)                           | Deep         | Perp Contract          |
| [`perp/contract/src/risk/epoch.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/epoch.rs)                             | Deep         | Perp Contract          |
| [`core/types/src/set.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/set.rs)                                                 | Deep         | Perp Contract          |
| [`perp/contract/src/accounts/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/accounts/mod.rs)                         | Deep         | Perp Contract          |
| [`perp/contract/src/risk/times.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/times.rs)                             | Deep         | Perp Contract          |
| [`perp/contract/src/currency.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/currency.rs)                                 | Deep         | Perp Contract          |
| [`perp/contract/src/perp/currency.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/perp/currency.rs)                       | Deep         | Perp Contract          |
| [`core/platform/src/o_chain.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/platform/src/o_chain.rs)                                   | Deep         | Perp Contract          |
| [`perp/contract/src/risk/positions.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/positions.rs)                     | Deep         | Perp Contract          |
| [`core/types/src/cache.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/cache.rs)                                             | Deep         | Perp Contract          |
| [`core/types/src/reverse.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/reverse.rs)                                         | Deep         | Perp Contract          |
| [`perp/contract/src/accounts/holder.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/accounts/holder.rs)                   | Deep         | Perp Contract          |
| [`core/types/src/heap.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/heap.rs)                                               | Deep         | Perp Contract          |
| [`core/platform/src/near.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/platform/src/near.rs)                                         | Deep         | Perp Contract          |
| [`perp/contract/src/flush.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/flush.rs)                                       | Deep         | Perp Contract          |
| [`core/platform/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/platform/src/lib.rs)                                           | Deep         | Perp Contract          |
| [`core/ft/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/ft/src/lib.rs)                                                       | Deep         | Perp Contract          |
| [`perp/contract/src/risk/state.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/state.rs)                             | Deep         | Perp Contract          |
| [`perp/contract/src/markets/info.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/markets/info.rs)                         | Deep         | Perp Contract          |
| [`core/types/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/lib.rs)                                                 | Deep         | Perp Contract          |
| [`perp/contract/src/keys.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/keys.rs)                                         | Deep         | Perp Contract          |
| [`core/platform/src/zlib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/platform/src/zlib.rs)                                         | Deep         | Perp Contract          |
| [`perp/contract/src/markets/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/markets/mod.rs)                           | Deep         | Perp Contract          |
| [`perp/common/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/common/src/lib.rs)                                               | Deep         | Perp Contract          |
| [`perp/contract/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/lib.rs)                                           | Deep         | Perp Contract          |
| [`perp/contract/src/risk/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/risk/mod.rs)                                 | Deep         | Perp Contract          |
| [`core/types/src/number/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/number/mod.rs)                                   | Deep         | Perp Contract          |
| [`core/types/src/number/zero.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/core/types/src/number/zero.rs)                                 | Deep         | Perp Contract          |
| [`perp/contract/src/events/mod.rs`](https://github.com/spin-fi/near-dapps/tree/e558c0ed54b376ab07dd6a7f677b0ea36950e4f4/perp/contract/src/events/mod.rs)                             | Deep         | Perp Contract          |
| [`option-vault/src/bindgen.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/bindgen.rs)                                     | Deep         | Options Vault Contract |
| [`spot/auction/src/auction.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/spot/auction/src/auction.rs)                                     | Deep         | Options Vault Contract |
| [`option-vault/src/vault.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/vault.rs)                                         | Deep         | Options Vault Contract |
| [`spot/auction/src/market.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/spot/auction/src/market.rs)                                       | Deep         | Options Vault Contract |
| [`order_book/src/order.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/order.rs)                                             | Deep         | Options Vault Contract |
| [`events/vault/src/v1.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/vault/src/v1.rs)                                               | Deep         | Options Vault Contract |
| [`order_book/src/list.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/list.rs)                                               | Deep         | Options Vault Contract |
| [`option-vault/src/log.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/log.rs)                                             | Deep         | Options Vault Contract |
| [`order_book/src/book.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/book.rs)                                               | Deep         | Options Vault Contract |
| [`option-vault/src/ofts/methods.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/ofts/methods.rs)                           | Deep         | Options Vault Contract |
| [`components/feed_manager/src/price_feed/mod.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/feed_manager/src/price_feed/mod.rs) | Deep         | Options Vault Contract |
| [`spot/auction/src/log.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/spot/auction/src/log.rs)                                             | Deep         | Options Vault Contract |
| [`option-vault/src/methods/state_mashine.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/methods/state_mashine.rs)         | Deep         | Options Vault Contract |
| [`components/vault/src/users.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/vault/src/users.rs)                                 | Deep         | Options Vault Contract |
| [`utils/serializers/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/utils/serializers/src/lib.rs)                                   | Deep         | Options Vault Contract |
| [`option-vault/src/api.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/api.rs)                                             | Deep         | Options Vault Contract |
| [`events/system/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/system/src/lib.rs)                                           | Deep         | Options Vault Contract |
| [`components/transfer/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/transfer/src/lib.rs)                               | Deep         | Options Vault Contract |
| [`option-vault/src/vaults/mod.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/vaults/mod.rs)                               | Deep         | Options Vault Contract |
| [`order_book/src/collector.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/collector.rs)                                     | Deep         | Options Vault Contract |
| [`utils/drop_map/src/zero_copy.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/utils/drop_map/src/zero_copy.rs)                             | Deep         | Options Vault Contract |
| [`option-vault/src/vaults/management.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/vaults/management.rs)                 | Deep         | Options Vault Contract |
| [`option-vault/src/vaults/accounts.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/vaults/accounts.rs)                     | Deep         | Options Vault Contract |
| [`option-vault/src/methods/mft.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/methods/mft.rs)                             | Deep         | Options Vault Contract |
| [`components/vault/src/epochs/mod.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/vault/src/epochs/mod.rs)                       | Deep         | Options Vault Contract |
| [`components/vault/src/fees.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/vault/src/fees.rs)                                   | Deep         | Options Vault Contract |
| [`events/spot/src/v1.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/spot/src/v1.rs)                                                 | Deep         | Options Vault Contract |
| [`components/feed_manager/src/feed.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/feed_manager/src/feed.rs)                     | Deep         | Options Vault Contract |
| [`option-vault/src/ofts/mod.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/ofts/mod.rs)                                   | Deep         | Options Vault Contract |
| [`components/storage_manager/src/storage.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/storage_manager/src/storage.rs)         | Deep         | Options Vault Contract |
| [`spot/auction/src/api.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/spot/auction/src/api.rs)                                             | Deep         | Options Vault Contract |
| [`components/vault/src/management.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/vault/src/management.rs)                       | Deep         | Options Vault Contract |
| [`events/primitives/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/primitives/src/lib.rs)                                   | Deep         | Options Vault Contract |
| [`components/vault/src/epochs/epoch.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/vault/src/epochs/epoch.rs)                   | Deep         | Options Vault Contract |
| [`events/data/src/events.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/data/src/events.rs)                                         | Deep         | Options Vault Contract |
| [`spot/auction/src/views.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/spot/auction/src/views.rs)                                         | Deep         | Options Vault Contract |
| [`utils/drop_map/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/utils/drop_map/src/lib.rs)                                         | Deep         | Options Vault Contract |
| [`option-vault/src/id_parser.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/id_parser.rs)                                 | Deep         | Options Vault Contract |
| [`events/vault/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/vault/src/lib.rs)                                             | Deep         | Options Vault Contract |
| [`utils/counter/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/utils/counter/src/lib.rs)                                           | Deep         | Options Vault Contract |
| [`events/spot/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/spot/src/lib.rs)                                               | Deep         | Options Vault Contract |
| [`order_book/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/lib.rs)                                                 | Deep         | Options Vault Contract |
| [`events/data/src/zlib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/data/src/zlib.rs)                                             | Deep         | Options Vault Contract |
| [`components/storage_manager/src/keys.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/storage_manager/src/keys.rs)               | Deep         | Options Vault Contract |
| [`order_book/src/keys.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/order_book/src/keys.rs)                                               | Deep         | Options Vault Contract |
| [`utils/pause/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/utils/pause/src/lib.rs)                                               | Deep         | Options Vault Contract |
| [`components/oft/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/oft/src/lib.rs)                                         | Deep         | Options Vault Contract |
| [`events/data/src/log.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/data/src/log.rs)                                               | Deep         | Options Vault Contract |
| [`components/storage_manager/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/components/storage_manager/src/lib.rs)                 | Deep         | Options Vault Contract |
| [`dependencies/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/dependencies/src/lib.rs)                                             | Deep         | Options Vault Contract |
| [`events/data/src/lib.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/events/data/src/lib.rs)                                               | Deep         | Options Vault Contract |
| [`option-vault/src/methods/mod.rs`](https://github.com/spin-fi/near-dapps/tree/85c985f95656437f0bef0273f86b6d55e10b194c/option-vault/src/methods/mod.rs)                             | Deep         | Options Vault Contract |

### Out of scope

The audit will include, but is not limited to, reviewing the code for security vulnerabilities, coding practices, and architecture. The audit does not include a review of the dependencies.

## Assessment Framework

Guvenkaya rates findings by likelihood and impact so remediation can be prioritized by technical risk, business context, and the decision the engagement is meant to support.

### Rating chain applied to every finding

1. 01 **Likelihood** Likely, possible or rare
2. 02 **Impact** Severe, moderate or negligible
3. 03 **Severity** Derived from the matrix below
4. 04 **Remediation complexity** Simple, moderate or difficult
5. 05 **Status** Not fixed, fixed, acknowledged or scheduled

### Impact

| Impact     | What it means here                                                                                                                                    |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Severe     | Exploitation could result in critical loss or compromise, such as full system control, substantial financial loss, or severe reputational damage.     |
| Moderate   | Exploitation may lead to limited data loss, partial compromise, moderate financial impact, or noticeable degradation of services.                     |
| Negligible | Exploitation has minimal impact, such as minor data exposure without significant consequences or slight inconvenience without substantial disruption. |

### Likelihood

| Likelihood | What it means here                                                                                 |
| ---------- | -------------------------------------------------------------------------------------------------- |
| Likely     | The vulnerability is easily discoverable and not overly complex to exploit.                        |
| Possible   | The vulnerability presents some challenges either in discovery or in the complexity of the attack. |
| Rare       | The vulnerability is either very difficult to discover or complex to exploit, or both.             |

This matrix provides a nuanced view, taking into account both the ease of discovering a vulnerability and the complexity involved in exploiting it.

| Attack Complexity \ Discovery Ease | Obvious  | Concealed | Hidden   |
| ---------------------------------- | -------- | --------- | -------- |
| Complex                            | Possible | Rare      | Rare     |
| Moderate                           | Likely   | Possible  | Rare     |
| Straightforward                    | Likely   | Possible  | Possible |

### Severity

| Severity      | What it means here                                                                                                                                            |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Critical      | Assigned to vulnerabilities with severe impact and a likely likelihood of exploitation.                                                                       |
| High          | For vulnerabilities with either severe impact but only a possible likelihood, or moderate impact with a likely likelihood.                                    |
| Medium        | Used for vulnerabilities with severe impact but a rare likelihood, moderate impact with a possible likelihood, or negligible impact with a likely likelihood. |
| Low           | For vulnerabilities with moderate impact and rare likelihood, or negligible impact with a possible likelihood.                                                |
| Informational | The lowest severity rating, typically for vulnerabilities with negligible impact and a rare likelihood of exploitation.                                       |

| Likelihood \ Impact | Severe   | Moderate | Negligible    |
| ------------------- | -------- | -------- | ------------- |
| Likely              | Critical | High     | Medium        |
| Possible            | High     | Medium   | Low           |
| Rare                | Medium   | Low      | Informational |

### Remediation complexity

This measures how difficult it is to remediate a finding once it has been identified.

| Remediation complexity | What it means here                                                                                                 |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Simple                 | Patches or fixes are readily available and easily implemented.                                                     |
| Moderate               | Requires some time and resources to remediate, but well within the capabilities of most organizations.             |
| Difficult              | Remediation requires significant resources, specialized skills, or substantial changes to systems or architecture. |

### Finding status

This tracks the client's response to each finding and whether remediation has been completed, accepted, or scheduled.

| Status       | What it means here                                                                                                                                                                                                                                                              |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Not Fixed    | Indicates that the vulnerability has been identified but no remedial action has been taken yet. This status is crucial for newly discovered vulnerabilities or those awaiting prioritization.                                                                                   |
| Fixed        | This status is applied when the vulnerability has been successfully remediated. It implies that appropriate measures, such as patching, configuration changes, or architectural modifications, have been implemented to resolve the issue.                                      |
| Acknowledged | This status is used for vulnerabilities that have been recognized, but for various reasons (such as risk acceptance, cost, or other business decisions), have not been fixed. It indicates that the risk posed by the vulnerability is known and has been consciously accepted. |
| Scheduled    | This status indicates that the vulnerability has been acknowledged and a plan is in place to fix it in the future. It signifies that while remediation hasn't yet occurred, the issue has been prioritized and is part of the planned development roadmap.                      |

## Findings Summary

**22** All findings

**3** Critical

**3** High

**5** Medium

**4** Low

**7** Informational

### Findings register

| Finding                                                                                                                                    | Impact     | Likelihood | Severity      | Remediation complexity | Status       |
| ------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ---------- | ------------- | ---------------------- | ------------ |
| [GUV-1: Market-wide Denial of Service via Log Limit Exceeding](#guv-1)                                                                     | Severe     | Likely     | Critical      | Simple                 | Fixed        |
| [GUV-2: Any account can unregister another account](#guv-2)                                                                                | Severe     | Likely     | Critical      | Simple                 | Fixed        |
| [GUV-3: Order Placement with Negative/Zero Margin Ratio Is Possible](#guv-3)                                                               | Severe     | Likely     | Critical      | Simple                 | Fixed        |
| [GUV-4: Wrapping casting overflow leads to the invalid storage deposit](#guv-4)                                                            | Moderate   | Likely     | High          | Simple                 | Fixed        |
| [GUV-5: Storage Withdraw Does Not Refund User Tokens](#guv-5)                                                                              | Moderate   | Likely     | High          | Simple                 | Fixed        |
| [GUV-6: Storage Bloating](#guv-6)                                                                                                          | Severe     | Possible   | High          | Moderate               | Acknowledged |
| [GUV-7: Intention of the function does not match with implementation](#guv-7)                                                              | Moderate   | Possible   | Medium        | Simple                 | Acknowledged |
| [GUV-8: Usage of Non-Maintained Oracle](#guv-8)                                                                                            | Moderate   | Possible   | Medium        | Moderate               | Acknowledged |
| [GUV-9: Orders are unprocessable due to log limit during effects processing](#guv-9)                                                       | Severe     | Rare       | Medium        | Moderate               | Fixed        |
| [GUV-10: Vault can be set to incorrect values via update\_settings](#guv-10)                                                               | Severe     | Rare       | Medium        | Moderate               | Acknowledged |
| [GUV-11: Vault Min Capacity Is Not Asserted](#guv-11)                                                                                      | Moderate   | Possible   | Medium        | Simple                 | Fixed        |
| [GUV-12: Management Fee Is Not Asserted](#guv-12)                                                                                          | Negligible | Possible   | Low           | Simple                 | Acknowledged |
| [GUV-13: Token id Collision Leads To Two Different Vaults Using The Same OFT](#guv-13)                                                     | Negligible | Possible   | Low           | Simple                 | Acknowledged |
| [GUV-14: Metadata Is Not Validated During Vault Creation](#guv-14)                                                                         | Negligible | Possible   | Low           | Simple                 | Acknowledged |
| [GUV-15: Storage Withdraw Is Not Protected With 2fa](#guv-15)                                                                              | Negligible | Possible   | Low           | Simple                 | Fixed        |
| [GUV-16: Better Contract Upgrade Mechanism Can Be Implemented](#guv-16)                                                                    | Negligible | Rare       | Informational | Moderate               | Acknowledged |
| [GUV-17: Casting overflow via as\_decimals() when the account cannot be liquidated](#guv-17)                                               | Negligible | Rare       | Informational | Moderate               | Acknowledged |
| [GUV-18: .view() method leads to casting overflow in get\_positions due to as\_decimals usage in Fraction Display implementation](#guv-18) | Negligible | Rare       | Informational | Moderate               | Acknowledged |
| [GUV-19: Suboptimal Assertion](#guv-19)                                                                                                    | Negligible | Rare       | Informational | Simple                 | Acknowledged |
| [GUV-20: Redundant Prepaid Gas Check](#guv-20)                                                                                             | Negligible | Rare       | Informational | Simple                 | Fixed        |
| [GUV-21: Contract Storage Can Be Optimized](#guv-21)                                                                                       | Negligible | Rare       | Informational | Simple                 | Acknowledged |
| [GUV-22: Not Resolved TODOs In The Codebase](#guv-22)                                                                                      | Negligible | Rare       | Informational | Simple                 | Acknowledged |

## Detailed Findings

### GUV-1: Market-wide Denial of Service via Log Limit Exceeding

- Severity

  Critical

- Impact

  Severe

- Likelihood

  Likely

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

We observed that it is possible to cause DoS in a market by placing a certain number of limit orders with a short expiry time. When expired orders are handled, we push several events to the event buffer, such as balance\_update & order\_update. After a certain number of expired orders, we will exceed the limit of 16kb per event. This will cause panic, and no one will be able to place an opposite order in a market (ask or bid) since expired orders cannot be processed Cost of an attack varies depending on the market settings. Also, it depends on market activity since any order can trigger processing of expired orders before an attack has reached a critical number.

We refund the users and send order event

spot:marketplace:handle\_expired\_orders:marketplace/​src/​market.rs

```
fn handle_expired_orders(         &mut self,         balances: &mut Balances<P>,         orders: &[Order],     ) {         for order in orders {             self.refund(balances, order);             Accounts::<P>::remove_order(&order.acc, self.id, order.id);             self.send_order_event(order, OrderStatus::Expired);         }     }
```

In `self.refund` we do balances.deposit, where we push balance\_update event

spot:marketplace:deposit:marketplace/​src/​accounts.rs

```
pub fn deposit(         &mut self,         account_id: &AccountId,         token: u16,         amount: Balance,         event: BalanceChangeInfo,     ) -> Balance {         assert!(             amount > U128(0),             "Expected positive amount. User: {}, token: {}, amount: {}",             account_id,             token,             amount.0         );           // Loading balance and adding amount.         let balance = self.load_balance_mut(account_id, token);         *balance += amount;           // Emitting event.         event::balance_update(             account_id.to_string(),             token,             amount,             true,             event,         );         *balance     }
```

POC: [`guv_1.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_1-rs)

#### Recommendation

Instead of accumulating all events into one big event, you could split them into several. NEAR has a limit of 100 events per transaction, so you can determine which events should go into a big one and which should be immediately emitted.

#### Remediation

The Spin team has fixed the issue in the next implementation of the spot contract.

### GUV-2: Any account can unregister another account

- Severity

  Critical

- Impact

  Severe

- Likelihood

  Likely

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that the `storage_withdraw` has exposed `account_id` parameter, which allows any account to withdraw the storage of another account.

vault:storage\_withdraw:option-vault/​src/​bindgen.rs

```
fn storage_withdraw(     &mut self,     account_id: &AccountId,     amount: Option<u64>,     unregister: Option<bool>, ) -> u64 {     self.call(|vault| vault.storage_withdraw(account_id, amount, unregister)) }
```

POC: [`guv_2.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_2-rs)

#### Recommendation

We propose removing the `account_id` parameter and use `env::predecessor_account_id` instead

#### Remediation

The Spin team has fixed the issue by removing the account\_id parameter

### GUV-3: Order Placement with Negative/Zero Margin Ratio Is Possible

- Severity

  Critical

- Impact

  Severe

- Likelihood

  Likely

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that it is possible to continue placing orders even if the margin ratio is negative. This issue occurs because the `can_place_order` function only checks if the current margin ratio is greater than or equal to the previous margin ratio, or if it is at least 1.0. This logic fails to prevent order placement when both the current and previous ratios are negative, allowing users with negative margins to continue trading, which can lead to further losses and increased risk for the system

perp:can\_place\_order:perp/​contract/​src/​risk/​margin.rs

```
pub fn can_place_order(&self, previous: MarginInfo) -> bool {     (!previous.ratio.is_nan() && self.ratio >= previous.ratio) || self.ratio >= Fraction::one() }
```

`previous_ratio` is set inside of `before_place_order` by via margin\_ratio.

perp:before\_place\_order:perp/​contract/​src/​markets/​market.rs

```
fn before_place_order(     &mut self,     context: &mut (MarginInfo, State<P>),     acc_data: &mut Accounts<P>,     order: &Order, ) {     let prelaunch_markets = Set::<MarketID, P>::new(StorageKeyEnum::PrelaunchMarkets);     if !prelaunch_markets.contains(&self.id) {         self.limits.validate_order_price(order, self.index_price());     }     context.0 = self.margin_ratio(acc_data, &order.acc, &context.1);}
```

**Scenario**

`1`. Alice and Bob each deposit 55k tokens.

`2`. The index price is set to 45k.

`3`. Alice places a bid order at 45k for 10 units.

`4`. Bob places an ask order at 45k for 10 units.

`5`. The price moves up to 55k, causing Bob to face liquidation.

`6`. Despite Bob having a negative margin, he can still place new orders and match them with Alice's

- orders, further increasing his losses.

POC: [`guv_3.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_3-rs)

#### Recommendation

We propose verifying whether the current margin ratio is greater than Fraction::zero() before allowing the order to be placed.

#### Remediation

The Spin team has fixed the issue by verifying whether the ratio is larger than Fraction::zero() in the `can_place_order` function

### GUV-4: Wrapping casting overflow leads to the invalid storage deposit

- Severity

  High

- Impact

  Moderate

- Likelihood

  Likely

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that in the `storage_deposit` `env::attached_deposit()` is casted to the u64. Since attached\_deposit is u128 casting it to u64 via "as" keyword will lead to the wrapping casting overflow if the attached deposit is larger than the u64::MAX. It is easily possible since u64::MAX in NEAR terms is **000018446744073709554N** (u64::MAX / 10\*\*24). It leads to the invalid value put as a storage deposit

vault:storage\_deposit:option-vault/​src/​bindgen.rs

```
#[payable] fn storage_deposit(&mut self, to: Option<AccountId>) {     let caller = env::predecessor_account_id();     let deposit = env::attached_deposit();       self.call(|vault| vault.storage_deposit(&caller, deposit as u64, to.as_ref())) }
```

POC: [`guv_4.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_4-rs)

#### Recommendation

Make sure to check whether the attached deposit is not larger than u64::MAX before casting it to u64 or utilize panicking casting methods.

#### Remediation

The Spin team has fixed the by casting to the u64 via `try_into()`.

### GUV-5: Storage Withdraw Does Not Refund User Tokens

- Severity

  High

- Impact

  Moderate

- Likelihood

  Likely

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that the storage\_withdraw only changes the internal state but does not refund NEAR back to the user

vault:storage\_withdraw:components/​storage\_manager/​src/​storage.rs

```
fn storage_withdraw(&mut self, storage_amount: Option<u64>, unregister: Option<bool>) -> u64 {     let storage = self.get().expect("Account is not registered");       let available_amount = storage.available_amount();     let should_unregister = unregister.unwrap_or(false);       if should_unregister {         self.new_value = None;         available_amount     } else {         let withdraw_amount = storage_amount.unwrap_or(available_amount);         let account_size = self.map.borrow().measured_storage_size;         assert!(             withdraw_amount <= available_amount + account_size,             "Not enough prepayed storage"         );         storage.sub(withdraw_amount);         self.new_value = Some(storage);         withdraw_amount     } }
```

#### Recommendation

It is advised to refund user's NEAR when the storage was withdrawn.

#### Remediation

The Spin team has fixed the issue by disabling storage withdraw via `unimplemented!()`

### GUV-6: Storage Bloating

- Severity

  High

- Impact

  Severe

- Likelihood

  Possible

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

It was observed that the perp contract does not delete the user data, when the user balance hits 0. When user does not hold any position, user can simply deposit certain amount and immediately withdraw, which will still leave a user entry in the contract. By constantly repeating this action, a malicious actor can bloat the storage, which eventually will lead to the lack of funds for a state error in the contract. Every execution storage increases by `0.00329N` if user account length is 64

POC: [`guv_6.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_6-rs)

#### Recommendation

It is advised to delete user accounts from storage once their balance hits zero.

#### Remediation

The Spin team has acknowledged the issue saying that there are much functionality depends on this behaivor and also that It makes sense to review this again, look at the limitations of Near on storage and calculate the potential throughput of users of our contract and also check whether any functions depend on the number of users on storage

### GUV-7: Intention of the function does not match with implementation

- Severity

  Medium

- Impact

  Moderate

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

It was observed that round method in Decimals, according to the comment, supposed to **round up** the result. However, due to the nature of division it actually rounds it down.

vault:round\_up\_quantity:marketplace/​src/​market/​limits.rs

```
/// Round up the passed quantity. pub fn round_up_quantity(&self, quantity: Decimal) -> Decimal {     quantity.round(self.step_size) }
```

vault:round:core/​types/​src/​number/​decimal.rs

```
/// Round up self value by using the given base. /// self / base * base pub fn round(&self, base: Decimal) -> Decimal {     if base.is_zero() {         return *self;     }       Decimal {         val: self.val / base.val * base.val,     } }
```

#### Recommendation

It is recommended to use `div_ceil` to round the decimals up. Alternatively, the comment has to be changed

#### Remediation

The Spin team has acknowledged the issue.

### GUV-8: Usage of Non-Maintained Oracle

- Severity

  Medium

- Impact

  Moderate

- Likelihood

  Possible

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

It was observed that the perp contract uses First-Party Flux oracle, which is no longer maintained.

#### Recommendation

It is advised to migrate over `priceoracle.near`.

More details: https\://docs.near.org/build/primitives/oracles

Price feeds: https\://github.com/NearDeFi/near-price-oracle-bot/tree/main/feeds

#### Remediation

The Spin team has acknowledged the issue.

### GUV-9: Orders are unprocessable due to log limit during effects processing

- Severity

  Medium

- Impact

  Severe

- Likelihood

  Rare

- Remediation Complexity

  Moderate

- Status

  Fixed

#### Description

We observed that due to the log limit, orders which cause the effect to apply to > 10 other orders will fail. It is important to note that it does not include processing expired orders. If the processing was also handling expired orders, the apply\_to vector could be even smaller to cause failure

On each apply\_to element, we handle\_execution effects and then send the order event to be added to the emitted log.

spot:place\_limit\_order:marketplace/​src/​market.rs

```
if !res.effects.apply_to.is_empty() {     self.handle_execution_effects(balances, &res.effects, false);       self.send_order_event(         &res.effects.applied,         if res.effects.is_filled {             OrderStatus::Filled         } else {             OrderStatus::PartiallyFilled         },     ); } else {     self.send_order_event(&res.effects.applied, OrderStatus::New); }
```

Also inside of handle\_execution\_effects we push the trade\_update event

spot:handle\_execution\_effects:marketplace/​src/​market.rs

```
let (taker_fee, taker_fee_token_id) = match fees_info.taker_fee_currency {     FeeCurrency::Base => (         fees_info.taker_fee.scale(self.base.decimals).value(),         self.base.id,     ),     FeeCurrency::Quote => (         fees_info.taker_fee.scale(self.quote.decimals).value(),         self.quote.id,     ) };   event::trade_update(     self.id,     order.id,     effects.applied.id,     maker_fee,     maker_fee_token_id,     taker_fee,     taker_fee_token_id,     fees_info.is_rebate,     price.value(),     quantity.value(),     effects.applied.o_type, );   self.send_release_funds_event(order, quantity, price);
```

POC: [`guv_9.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_9-rs) **Possible Exploitation**

`1`. This case could be exploited if we put many limit orders with a small price & quantity. This could cause some opposite orders to fail since there is a high chance that they will reach critical apply\_to count (not counting if there are any expired orders)

`2`. This case could be exploited by front running certain transactions to influence the orderbook and prevent certain users from executing certain orders.

- `a`. Alice puts an ask order with quantity of 100 and price as 1N `b`. Bob sees this transaction in transaction pool and sends required number of orders, which will cause the log limit. `c`. Alice order fails

#### Recommendation

It is advised to set a limit on the number of orders that can be matched.

#### Remediation

The Spin team has fixed the issue and mentioned that:

On the spot there is a problem with the size of the logs, yes. However there shouldn’t be any problems on the perp, since we use some optimization mechanisms (archiving, using an separated account map, ...). In theory, orders can still get stuck in the order book, but in practice the following mechanism is used to mitigate this:

1. Parameter `max_match_count` - due to which a match of orders will occur for a certain number of

- orders, and the rest of the order will be canceled

1. `min/max_base/quote_quantity` parameters ensure minimum costs for creating a huge number

- of orders in the orderbook

1. There is also an admin method for force cancellation of ALL ORDERS one by one - this is just a

- last resort

### GUV-10: Vault can be set to incorrect values via update\_settings

- Severity

  Medium

- Impact

  Severe

- Likelihood

  Rare

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

It was observed that the update\_settings function lacks provided parameter checks, which are present in the create function. Thus, it is possible to update the vault with incorrect values such as **min\_deposit > max\_deposit** and so on.

#### Recommendation

It is recommended to incorporate parameter validation into update\_settings from the create function to ensure that the vault is always operating with sane parameters

#### Remediation

The Spin team has acknowledged the issue.

### GUV-11: Vault Min Capacity Is Not Asserted

- Severity

  Medium

- Impact

  Moderate

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

During vault creation, we can specify the minimum capacity of the vault. However, it is not asserted anywhere in the code. Currently, it is possible to forward an epoch even if the minimum capacity is not met.

#### Recommendation

Consider asserting whether the vault reached its minimum capacity before letting anyone forward epoch

#### Remediation

The Spin team has fixed the issue in the next version of the vault.

### GUV-12: Management Fee Is Not Asserted

- Severity

  Low

- Impact

  Negligible

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

It was observed that the `assert_valid` method of the VaultFee contains duplicate validation logic. Based on the logic, it is possible the management fee is supposed to be asserted there

vault:assert\_valid:components/​vault/​src/​fees.rs

```
fn assert_valid(&self) { if let Some(performance_fee_numerator) = self.performance_fee_numerator {           assert!(self.fee_denominator > performance_fee_numerator);       }       if let Some(performance_fee_numerator) = self.performance_fee_numerator {           assert!(self.fee_denominator > performance_fee_numerator);       }   }
```

#### Recommendation

It was advised to assert the management\_fee in one of the conditional blocks.

#### Remediation

The Spin team has acknowledged the issue.

### GUV-13: Token id Collision Leads To Two Different Vaults Using The Same OFT

- Severity

  Low

- Impact

  Negligible

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

It was observed that it is possible for two different vaults to use the same OFT due to the token\_id collision.

In the forward\_epoch function, self.ofts.create is called to create a new oft per vault. Internally, it uses construct\_token\_id method, which calculates the token\_id as follows:

vault:construct\_token\_id:components/​oft/​src/​oft/​mod.rs

```
pub fn construct_token_id(     metadata: &FungibleTokenMetadata,     option_data: &OptionData,     authority: &AccountId, ) -> String {     format!(         "o{}-{:?}-{}-{}-{}",         metadata.symbol,         option_data.option_type,         cast_integer_to_float(option_data.strike_price, PRICE_FEED_DECIMALS as usize),         option_data.execution_ts,         authority,     ) }
```

Token id collision can happen if two vaults have the same:

- underlying asset
- strike price
- option type
- option expiration time
- auction start

Then, a malicious actor can initiate a batch transaction to synchronize env::block\_timestamp to cause option\_expiration\_ts, which in return gets assigned as option\_data.execution\_ts, to be the same for both vaults. This will lead to the token\_id collision for both vaults.

option\_data.execution\_ts is calculated in calculate\_params through option\_expiration\_ts and depends on the start\_of\_epoch, which depends on the current block timestamp.

vault:calculate\_params:option-vault/​src/​strategy.rs (Part 1)

```
impl VaultInvariant {     pub fn calculate_params(         &self,         pricing: &PricingParams,         time: &TimeIntervalParams,         current_ts: u64,     ) -> BootstrappingParams {         let mut start_of_epoch = current_ts / time.period * time.period + time.period_offset;         if start_of_epoch + time.auction_start < current_ts {             start_of_epoch += time.period;         }         let auction_start_ts = start_of_epoch + time.auction_start;         let auction_end_ts = auction_start_ts + time.auction_duration;
```

vault:calculate\_params:option-vault/​src/​strategy.rs

```
        BootstrappingParams {             epoch_start_ts: start_of_epoch,             epoch_end_ts: start_of_epoch + time.period,             auction_start_ts,             auction_end_ts,             option_expiration_ts: start_of_epoch + time.option_expiration,             auction_min_price: pricing.auction_min_price,             strike_price: pricing.strike_price,             option_type: self.option_type.clone(),             execution_asset: self.execution_asset.clone(),             underlying_asset: self.underlying_asset.clone(),             price_feed_id: pricing.price_feed_id,         }     } }
```

Then it gets assigned to OptionData:

vault:forward\_epoch:option-vault/​src/​methods/​state\_mashine.rs

```
VaultStateMashine::NotStarted => {     let quantity = vault.current_epoch().start_amount;     let params = settings.invariant.calculate_params(         &settings.pricing,         &settings.time,         current_ts,     );       let option_data = OptionData {         option_type: params.option_type.clone(),         execution_asset: params.execution_asset.clone(),         underlying_asset: params.underlying_asset.clone(),         strike_price: params.strike_price,         execution_ts: params.option_expiration_ts,     };         let oft_token_id = self.ofts.create(           vault_address,           storage_counter,           underlying_asset_metadata.clone(),           option_data,           params.price_feed_id,           execution_decimals,       );
```

**Scenario**

1. ALICE created two vaults (1, 2) in the beginning with the same underlying asset but different

- execution asset

1. Same option type and option expiration time 3. When auction time has approached BOB creates a batch transaction to call forward\_epoch with

- vault\_id as 1 and vault\_id as 2

1. Calling via batch transaction will make env::block\_timestamp to be the same for 2 calls 5. Epoch is forwarded 6. First call creates oft 7. Second call returns previous oft token id since there is a check to prevent creation of new oft if it

- already exists

1. Both vaults will get the same oft token\_id even though the execution assets and other parameters

- are different

POC: [`guv_13.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_13-rs)

#### Recommendation

If we truly want to have a different oft per vault, we should also include vault\_id into the token\_id generation

#### Remediation

The Spin team has acknowledged the issue by saying that even if provided scenario is used and identical OFTs were created, it won't cause any problems.

### GUV-14: Metadata Is Not Validated During Vault Creation

- Severity

  Low

- Impact

  Negligible

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

It was observed that neither vault\_metadata nor asset metadata is validated despite FungibleTokenMetadata having the `assert_valid` method.

vault:assert\_valid:near\_contract\_standards

```
impl FungibleTokenMetadata {     pub fn assert_valid(&self) {         require!(self.spec == FT_METADATA_SPEC);         require!(self.reference.is_some() == self.reference_hash.is_some());         if let Some(reference_hash) = &self.reference_hash {             require!(reference_hash.0.len() == 32, "Hash has to be 32 bytes");         }     } }
```

vault:create\_ft\_metadata\_callback:option-vault/​src/​bindgen.rs

```
#[private] pub fn create_ft_metadata_callback(     &mut self,     #[callback_result] execution_asset_metadata: Result<FungibleTokenMetadata, PromiseError>,     #[callback_result] underlying_asset_metadata: Result<FungibleTokenMetadata, PromiseError>,     caller: &AccountId,     settings: VaultSettings,     capacity: VaultCapacity,     fee: VaultFeeParams,     vault_metadata: FungibleTokenMetadata, ...
```

#### Recommendation

Consider utilizing the assert\_valid method to validate passed metadata before processing

#### Remediation

The Spin team has acknowledged the issue by saying that they had experienced issues with validation of one of the production tokens, so they decided not to revalidate it and there should be no issues.

### GUV-15: Storage Withdraw Is Not Protected With 2fa

- Severity

  Low

- Impact

  Negligible

- Likelihood

  Possible

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that the `storage_withdraw` does not have 2fa protection through asserting one yocto. This is mentioned in the near-contract-standards: https\://github.com/near/near-sdk-rs/blob/master/near-contract-standards/src/storage\_management/mod.rs#L97

#### Recommendation

In general, It is advised for all privileged functions, which deal with user balances or crucial contract states, to have assert\_one\_yocto to prevent malicious calls if a function call key (commonly used on frontends) is compromised.

Alternatively, all the function call keys generated should specify exactly, which methods are allowed to be called so that the most crucial ones should be called only via full key

#### Remediation

The Spin team has fixed the issue by disabling storage withdraw via `unimplemented!()`.

### GUV-16: Better Contract Upgrade Mechanism Can Be Implemented

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

Currently state is read via two functions with a callback

Current Implementation

```
/// Used to change the state of the contract when updating the contract. pub fn set_root_state(&mut self, state: String) {     let signer = env::signer_account_id();     self.marketplace.ensure_root(signer.as_str());     self.marketplace.ensure_markets_are_stopped();     Promise::new(current_account_id()).function_call(         "set_root_state_callback".to_string(),         json!({ "state": state }).to_string().into_bytes(),         0,         SINGLE_CALL_GAS,     ); }   #[private] pub fn set_root_state_callback(state: String) {     env::storage_write(b"STATE", &base64::decode(state).expect("Invalid state")); }
```

This pattern is error-prone since we directly write the state to the contract. There are no guarantees that the state you written is correct. The better approach to avoid the "Cannot deserialize the contract state." error on data structure changes is explicitly having an update and migration mechanisms inside the contract. So we know exactly what was an old and new data structure and whether we need to migrate user data

#### Recommendation

It is better to include:

- Update Contract Function (for self upgrade)
- Migrate function

Examples (taken from public sources)

Update Contract Function Example

```
pub fn update_contract(&self) -> Promise {     // Check the caller is authorized to update the code     assert!(         env::predecessor_account_id() == self.manager,         "Only the manager can update the code"     );     // Receive the code directly from the input to avoid the     // GAS overhead of deserializing parameters     let code = env::input().expect("Error: No input").to_vec();       // Deploy the contract on self     Promise::new(env::current_account_id())         .deploy_contract(code)         .function_call(             "migrate".to_string(),             NO_ARGS,             NearToken::from_near(0),             CALL_GAS,         ).as_return()}
```

Migrate Function Example 1

```
#[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] pub struct OldState {     token_account_id: AccountId,     oracles: UnorderedSet<AccountId>,     claim_period: Duration,     burn_period: Duration,     accruals: UnorderedMap<UnixTimestamp, (Vector<TokensAmount>, TokensAmount)>,     accounts: LookupMap<AccountId, AccountRecordLegacy>,     is_service_call_running: bool, }   #[near_bindgen] impl Contract {     #[private]     #[init(ignore_state)]     pub fn migrate() -> Self {         let old_state: OldState = env::state_read().expect("Failed to read old state");           Self {             token_account_id: old_state.token_account_id,             oracles: old_state.oracles,             claim_period: old_state.claim_period,             burn_period: old_state.burn_period,             accruals: old_state.accruals,             accounts_legacy: old_state.accounts,             accounts: LookupMap::new(StorageKey::Accounts),             is_service_call_running: old_state.is_service_call_running,             balance_to_burn: 0,         }     }
```

Migrate Function Example 2 (Part 1: Old State Structures)

Old State Structures

```
#[near(serializers=[borsh])] pub struct OldPostedMessage {     pub premium: bool,     pub sender: AccountId,     pub text: String, }   #[near(serializers=[borsh])] pub struct OldState {     messages: Vector<OldPostedMessage>,     payments: Vector<NearToken>, }
```

Migrate Function Example 2 (Part 2: Migrate Function)

Migrate Function

```
#[near] impl GuestBook {     #[private]     #[init(ignore_state)]     pub fn migrate() -> Self {         // retrieve the current state from the contract         let old_state: OldState = env::state_read().expect("failed");           // iterate through the state migrating it to the new version         let mut new_messages: Vector<PostedMessage> = Vector::new(b"p");           for (idx, posted) in old_state.messages.iter().enumerate() {             let payment = old_state                 .payments                 .get(idx as u64)                 .unwrap_or(NearToken::from_near(0));               new_messages.push(&PostedMessage {                 payment,                 premium: posted.premium,                 sender: posted.sender,                 text: posted.text,             })         }           // return the new state         Self {             messages: new_messages,         }     } }
```

#### Remediation

The Spin team has acknowledged the issue by saying: "For the perp, the set\_root\_state method no longer exists; for the spot it remains as a legacy method, but can be removed. We can add a method migrate in case of emergency and necessity."

### GUV-17: Casting overflow via as\_decimals() when the account cannot be liquidated

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

It was observed that the check\_liquidation as\_decimals() is used to format the margin\_ratio from Fractional to the Decimal. Usage of as\_decimals causes casting overflow in some scenarios since when performing division if numerator to denominator we first multiply the numerator by 10\*\*24

perp:check\_liquidation:perp/​contract/​src/​risk/​liquidation.rs

```
fn check_liquidation(&self, positions_collateral: Decimal) -> Result<bool, LiquidationError> {     let margin_ratio = MarginInfo::from_parts(self.total_margin, positions_collateral);       if !margin_ratio.can_be_liquidated() {         Err(LiquidationError::CanNotBeLiquidated(             margin_ratio.ratio().as_decimal(),         ))     } else {         Ok(margin_ratio.can_completely_liquidated())     } }
```

This can lead to situations when the transaction failure cannot be correctly interpreted, especially if some offchain components rely on the correct error message returned.

#### Recommendation

It is advised to add additional checks to ensure that the value cannot be larger than u128::MAX

#### Remediation

The Spin team has acknowledged the issue by saying: "As we understand, the problem should not arise on real values, taking into account the restrictions min/max\_base/quote\_quantity but we will check separately and probably make some threshold, starting from which the margin ratio will be considered infinity / NaN"

### GUV-18: .view() method leads to casting overflow in get\_positions due to as\_decimals usage in Fraction Display implementation

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Moderate

- Status

  Acknowledged

#### Description

It was observed that when `get_positions()` is called, `.view()` method is invoked on margin\_ration, which internally uses Display implementation for **Fraction**. Since Display implementation for Fraction uses `as_decimals()`, it sometimes leads to casting overflow on division

perp:get\_positions:perp/​contract/​src/​perp/​position.rs

```
pub fn get_positions(&self, account_id: AccountId) -> PositionsView {     let accounts = self.accounts_holder.instance();     let acc = accounts.get(&account_id);     let positions = acc.get_positions().view(&self.markets);     let margin_ratio = self.margin_ratio(&acc).view();     PositionsView {         margin_ratio,         positions,     } }
```

perp:fmt:core/​types/​src/​number/​fraction.rs

```
impl Display for Fraction {     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {         if self.is_nan() {             write!(f, "NaN")         } else {             write!(f, "{}", self.as_decimal())         }     } }
```

perp:div:core/​types/​src/​number/​decimal.rs

```
impl Div<Self> for Decimal {     type Output = Decimal;       fn div(self, rhs: Self) -> Self::Output {         let self_scaled_with_overflow = U256::from(self.val) * U256::from(DECIMAL);         let division = self_scaled_with_overflow / U256::from(rhs.val);         Self {             val: division.as_u128(),         }     } }
```

POC: [`guv_19.rs`](https://gist.github.com/guvenkaya-security-team/8efc5ec244a4774539f59e319ad40f60#file-guv_19-rs)

#### Recommendation

It is advised to add additional checks to ensure that the value cannot be larger than u128::MAX

#### Remediation

The Spin team has acknowledged the issue by saying: "As we understand, the problem should not arise on real values, taking into account the restrictions min/max\_base/quote\_quantity but we will check separately and probably make some threshold, starting from which the margin ratio will be considered infinity / NaN"

### GUV-19: Suboptimal Assertion

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

When you have > 0 and operate within unsigned integer space, a more logical check is != 0. By default, unsigned integer space checks that the value is >= 0. So the only thing you need to check is val != 0 since the value cannot be smaller than 0

deposit

```
pub fn deposit(     &mut self,     account_id: &AccountId,     token: u16,     amount: Balance,     event: BalanceChangeInfo, ) -> Balance {     assert!(         amount > U128(0),         "Expected positive amount. User: {}, token: {}, amount: {}",         account_id,         token,         amount.0     );
```

#### Recommendation

It is advised to change > operator to != 0

#### Remediation

The Spin team has acknowledged the issue.

### GUV-20: Redundant Prepaid Gas Check

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Simple

- Status

  Fixed

#### Description

It was observed that there is a redundant prepaid gas check in the function

withdraw function

```
#[payable] pub fn withdraw(&mut self, token: AccountId, amount: Balance) -> Promise {     let _session = self.make_session();     let signer = env::signer_account_id();     assert!(         env::prepaid_gas() >= SINGLE_CALL_GAS * 5,         "Expected prepaid gas {}.",         SINGLE_CALL_GAS.0 * 5     );     self.marketplace.withdraw(         signer.to_string(),         token.to_string(),         amount,     );     assert!(         env::prepaid_gas() >= SINGLE_CALL_GAS * 5,         "Expected prepaid gas {}.",         SINGLE_CALL_GAS.0 * 5     );
```

#### Recommendation

It is advised to only keep one

#### Remediation

The Spin team has fixed the issue in the next version of the contract"

### GUV-21: Contract Storage Can Be Optimized

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

We can decrease the total size of the spot and other contracts by following these steps:

**Steps Performed**

Get initial Size

We got the total size of the binary by using twiggy.

Initial Size Analysis

```
        Shallow Bytes │ Shallow % │ Item   ───────────────┼───────────┼──────────────────────────────────         610946 ┊    29.03% ┊ custom section '.debug_str'         379733 ┊    18.04% ┊ custom section '.debug_info'         240083 ┊    11.41% ┊ custom section '.debug_line'         197639 ┊     9.39% ┊ "function names" subsection         164224 ┊     7.80% ┊ custom section '.debug_ranges'         60562 ┊     2.88% ┊ data[0]           6298 ┊     0.30% ┊ core::num::flt2dec::strategy::dragon::format_shortest::h5d521f4a27ed81b4           5663 ┊     0.27% ┊ base64::decode::decode_config_buf::h6817914728a71c2f           5318 ┊     0.25% ┊ core::num::flt2dec::strategy::dragon::format_exact::h9b3f09aaeac78ab3           4449 ┊     0.21% ┊ custom section '.debug_abbrev'         406891 ┊    19.33% ┊ ... and 2245 more.         2104765 ┊   100.00% ┊ Σ [2265 Total Rows]
```

Total Size: 2104765 bytes = 2104.765kb

Cost to store: \~21.047649999999997N (100kb = \~1N) = $132.60019499999999 (at 6.30 price)

Stripping Debug Info

If we do not include these flags in .cargo/config.toml:

Cargo Config

```
[target.wasm32-unknown-unknown] rustflags = ["-C", "link-arg=-s"]
```

Or pass them directly during cargo build, our generated WASM binary will contain debug info. We can decrease binary size by stripping them:

Debug Info Size

```
610946 ┊    29.03% ┊ custom section '.debug_str' 379733 ┊    18.04% ┊ custom section '.debug_info' 240083 ┊    11.41% ┊ custom section '.debug_line' 197639 ┊     9.39% ┊ "function names" subsection
```

Removing rlib

We noticed that the crate type is set as both cdylib and rlib. However, since NEAR smart contracts are compiled to WASM, rlib is unnecessary. Eliminating rlib can significantly reduce the size of the generated WASM binary.

Cargo.toml

```
[lib] crate-type = ["cdylib"]
```

Optimizing Via Wasm-Opt

We used wasm-opt to further optimize generated WASM binary by performing several iterations of optimization:

- 1\. wasm-opt -Oz -o swap\_optimized.wasm ./target/wasm32-unknown-

```
unknown/release/swap.wasm
```

- 2\. wasm-opt -Oz -o swap\_optimized.wasm swap\_optimized.wasm

Diff To See Result

We ran twiggy-diff between old binary and optimized one:

Optimization Diff

```
        Delta Bytes │ Item   ─────────────┼────────────────────────────────────────────────         -610946 ┊ custom section '.debug_str'         -379733 ┊ custom section '.debug_info'         -240083 ┊ custom section '.debug_line'         -164224 ┊ custom section '.debug_ranges'         -59852 ┊ data[0]         +19939 ┊ data[4]           +9668 ┊ code[199]           +7026 ┊ data[1]           +6664 ┊ code[662]           +6379 ┊ data[28]           -6298 ┊ core::num::flt2dec::strategy::dragon::format_shortest::h5d521f4a27ed81b4           -5663 ┊ base64::decode::decode_config_buf::hfb20bcc6885b3c7c           +5577 ┊ code[452]           +5466 ┊ data[5]           -5318 ┊ core::num::flt2dec::strategy::dragon::format_exact::h9b3f09aaeac78ab3           +4986 ┊ code[576]           +4825 ┊ code[121]           +4742 ┊ code[644]           +4473 ┊ data[2]           -4449 ┊ custom section '.debug_abbrev'         -151797 ┊ ... and 3032 more.         -1754694 ┊ Σ [3052 Total Rows]
```

**Result**

Here are details of a new binary size after various optimizations without changing the actual code:

```
Total Saved: 1754694 bytes = 1754.694 kb Saved in percentage: 83.36769188009113% Total Cost Saved: ~17.54694N = ~$110.545722 (6.30 price) Current Size: 350071 bytes = 350.071kb Current Cost: 3.50071N = ~$22.054473 (6.30 price)
```

#### Recommendation

It is advised to follow the stepts above to decrease the binary size.

#### Remediation

The Spin team has acknowledged the issue.

### GUV-22: Not Resolved TODOs In The Codebase

- Severity

  Informational

- Impact

  Negligible

- Likelihood

  Rare

- Remediation Complexity

  Simple

- Status

  Acknowledged

#### Description

It was observed that the codebase contains several TODOs that are not resolved. Some of them describe certain vulnerabilities and suboptimal mathematical operations.

vault:mul\_div:types/​num/​src/​uint256.rs

```
//TODO: remove this muldiv and change everywhere to be muldiv256 with chaning it's name to this pub fn mul_div<A: Into<U256>, B: Into<U256>, C: Into<U256>>(a: A, b: B, c: C) -> Option<u128> {     let a = a.into();     let b = b.into();     let c = c.into();       if c == U256::from(0) {         Some(0)     } else {         a.checked_mul(b)             .and_then(|v| v.checked_div(c))             .map(TryInto::try_into)             .transpose()             .ok()             .flatten()     } }
```

vault:mft\_resolve\_transfer:core/​mft/​src/​lib.rs

```
#[private] fn mft_resolve_transfer(     &mut self,     token_id: String,     sender_id: AccountId,     receiver_id: &AccountId,     amount: U128, ) -> U128 {     let unused_amount = match env::promise_result(0) {         PromiseResult::NotReady => unreachable!(),         PromiseResult::Successful(value) => {             if let Ok(unused_amount) = near_sdk::serde_json::from_slice::<U128>(&value) {                 std::cmp::min(amount.0, unused_amount.0)             } else {                 amount.0             }         }         PromiseResult::Failed => amount.0,     };       if unused_amount > 0 {         // TODO: vulnerability with account deleting would be available,         // there should be a check if sender account was not deleted before resolve         let receiver_balance = self.internal_balance_of(&token_id, receiver_id);         if receiver_balance > 0 {             let refund_amount = std::cmp::min(receiver_balance, unused_amount);             self.internal_mft_transfer(token_id, &sender_id, receiver_id, refund_amount, None);         }     }       unused_amount.into() }
```

#### Recommendation

It is advised to resolve these TODOs.

#### Remediation

The Spin team has acknowledged the issue.

The web version and the [PDF](https://www.guvenkaya.co/reports/pdfs/spin.pdf) (43 pages) are generated from the same source. The [report as first delivered](https://github.com/Guvenkaya/public-reports/blob/master/legacy-design/Spin-NEAR-Rust-Smart-Contract-Security-Assessment.pdf) is kept in the public reports archive.

## Table of Contents

- [Engagement Dashboard](#engagement-dashboard)
- [Advisory and Client](#advisory-and-client)
- [Scope and Review Boundaries](#scope-and-review-boundaries)
- [Assessment Framework](#assessment-framework)
- [Findings Summary](#findings-summary)
- [Detailed Findings](#detailed-findings)

## Share

[Share on X](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.guvenkaya.co%2Freports%2Fspin\&text=Spin%20Finance%3A%20Onchain%20Orderbook%20and%20Perpetual%20Trading%20Security%20Review) [Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.guvenkaya.co%2Freports%2Fspin) [Share on Threads](https://www.threads.net/intent/post?text=Spin%20Finance%3A%20Onchain%20Orderbook%20and%20Perpetual%20Trading%20Security%20Review%20https%3A%2F%2Fwww.guvenkaya.co%2Freports%2Fspin)

[Discuss a security review](https://www.guvenkaya.co/contact?source=report) [View PDF on GitHub](https://github.com/Guvenkaya/public-reports/blob/master/Spin-NEAR-Rust-Smart-Contract-Security-Assessment.pdf)

## Tell us what you need to secure.

Describe your system, main concern, and deadline. We’ll reply with scoping questions and a proposed next step.

[Discuss your scope](https://www.guvenkaya.co/contact?source=report)
