# Coding Standard

> What IPOR requires from the code of a third-party fuse family: structure, statelessness, safety checks, errors, events, NatSpec, compiler settings and formatting, plus recommended tests.

This page lists what the IPOR team requires when it reviews the deployed code of your fuses. In this section "IPOR requires" and "IPOR expects" both mean a rule that the review applies: a fuse that does not meet it receives a request for changes in the pull request. "Recommended" marks what is optional. Each rule is marked as an IPOR requirement or shown in the code of the reference fuses in [`IPOR-Labs/ipor-fusion`](https://github.com/IPOR-Labs/ipor-fusion), read at commit `632350d`. Where a reference fuse does not follow a rule, the page says so. For a walkthrough of a working fuse, see [Developing a Fuse](https://ipor.io/docs/build-on-fusion/developer-guide/developing-a-fuse.md). For the safety rules behind several of the items below, see [Security Requirements](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/security-requirements.md).

> **Note:** The review is based on what is on-chain: the source code verified in the block explorer of each chain. Write the code so that it can be read and checked in that form.

## Naming and file layout

An action fuse written for a new market is typically named `<Venue><Action>Fuse`, for example `AaveV4SupplyFuse`, `AaveV4BorrowFuse` and `AaveV4CollateralFuse`. Use one contract per file and name the file after the contract. The reference families follow this layout: see [`AaveV4SupplyFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/aave_v4/AaveV4SupplyFuse.sol) and [`Erc4626SupplyFuse`](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/erc4626/Erc4626SupplyFuse.sol).

Every fuse exposes `MARKET_ID()`. The `IFuseCommon` interface has exactly this one function ([source](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/IFuseCommon.sol#L5-L9)). When a fuse is added to the Fuse Whitelist Registry, the registry reads `MARKET_ID()` and lists the fuse under that market id; a fuse without `MARKET_ID()` is silently skipped for the per-market list, so IPOR requires it ([source](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/whitelist/FuseWhitelistLib.sol#L550-L564)). The parts of a market integration are described in [What a Market Integration Contains](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/market-components.md).

## Statelessness and delegatecall

A fuse runs by `delegatecall` in the vault context. Inside a fuse function `address(this)` is the vault, and reads of storage see the vault's storage (`PlasmaVault.execute` delegatecalls the encoded call). For this reason IPOR requires fuses to be stateless:

- The only variables are immutables: `VERSION`, `MARKET_ID` and, where the venue is fixed, venue addresses. There are no mutable storage variables.
- Any other state lives in ERC-7201 namespaced storage slots, accessed through a library. Fusion uses this pattern for the `AsyncExecutor` address and for the transient storage slot.
- `VERSION` is set to `address(this)` in the constructor, so it is the address of the fuse contract itself and not of the vault. The fuse emits it in events and uses it as the transient storage key.

The reference declaration and constructor of the ERC4626 fuse are in [lines 81-87](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L81-L87).

## Reject market id zero

IPOR requires the constructor to revert when `marketId_ == 0`. The vault engine uses market id 0 as the empty slot when it lists and refreshes markets, so a fuse with market id 0 would be skipped. `AaveV4SupplyFuse` shows the guard with the custom error `AaveV4SupplyFuseInvalidMarketId`, and a unit test asserts the revert ([constructor](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/aave_v4/AaveV4SupplyFuse.sol#L123-L131)).

> **Warning:** `Erc4626SupplyFuse` and `Erc4626BalanceFuse` do not have this guard at `632350d`. Do not copy their constructors as they are; add the check yourself.

## Check substrates before moving funds

A substrate is a `bytes32` value that an atomist grants to a market. The fuse checks it before it moves any funds: the grant check comes before every approve, deposit, supply or withdraw. Plain address substrates are checked with `isSubstrateAsAssetGranted`; other encodings with `isMarketSubstrateGranted`. Both are in `PlasmaVaultConfigLib`.

When a caller supplies an asset together with a venue position, also check that the asset matches the asset of the granted position before moving funds. `AaveV4SupplyFuse` compares the reserve underlying with the asset ([lines 145-148](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/aave_v4/AaveV4SupplyFuse.sol#L145-L148)). The ERC4626 fuse checks the grant at the start of `enter` ([lines 94-96](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/erc4626/Erc4626SupplyFuse.sol#L94-L96)).

If a substrate carries more than an address, put its encoding and decoding in a substrate library, as `AaveV4SubstrateLib` does: a type flag in the top byte, then the fields, with reserved bits that must be zero.

## Token transfers and approvals

- Use `SafeERC20` and approve the exact amount with `forceApprove`.
- IPOR requires the allowance to be set back to zero after the venue call, so that no residual allowance remains. `AguaSupplyFuse` shows the pattern `forceApprove(vault, amount)`, deposit, `forceApprove(vault, 0)` ([lines 83-87](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/contracts/fuses/agua/AguaSupplyFuse.sol#L83-L87)).

> **Warning:** The allowance reset is a requirement of the standard, not a feature of the ERC4626 and Aave V4 examples. `Erc4626SupplyFuse.enter`, `AaveV4SupplyFuse.enter` and `AaveV4BorrowFuse.exit` approve the exact amount but do not reset it at `632350d`.

## Errors and events

- **Errors** are custom errors named with the fuse name as a prefix, for example `AaveV4SupplyFuseInvalidMarketId`. Do not use revert strings.
- **Events** are named with the fuse name as a prefix and carry the fuse `VERSION` as the first parameter.
- **Events report what actually happened.** Emit the amount after clamping to the balance or limit, not only what was requested. The requirement is that events show the outcome; it is not that every event has an amount field. `AaveV4SupplyFuseEnter`, for example, reports the received shares.
- **No `indexed` parameters.** IPOR requires that event parameters are not indexed; all parameters go into the data section. The reference fuses declare their events without `indexed`. Some other Fusion contracts, for example `FusionFactory`, still use it, so this is a rule for new fuses and not a description of all existing code.

## NatSpec

Write NatSpec in English: `@title`, `@notice`, `@param`, `@return` and `@custom:revert` on external and public functions, and `@param` on events and errors. The Aave V4 fuses show this level of documentation. The `enter`, `exit`, `enterTransient` and `exitTransient` functions of `Erc4626SupplyFuse` have no NatSpec at `632350d`.

A fuse README in your repository should follow the sections of the [Aave V4 README](https://github.com/IPOR-Labs/ipor-fusion/blob/main/contracts/fuses/aave_v4/README.md): overview, market structure, key components, substrate configuration with the validation matrix, operations, balance calculation, security considerations, price oracle setup and tests.

## Compiler settings and size

- **Pragma.** IPOR sets no requirement on the pragma version of your code; you choose it. Fusion interfaces and libraries such as `IFuseCommon` or `PlasmaVaultConfigLib` declare `pragma solidity 0.8.30`, so a file that imports them must be compiled with solc 0.8.30.
- **Reproducible bytecode.** The deployed code is read from the chain, so the compiler settings you used for the deployment must reproduce the deployed bytecode. List them in the pull request: the solc version, the EVM version, the optimizer setting and runs, and any per-chain profile.
- **Same source on every chain.** Use the same source code and the same compiler settings on every chain. The runtime bytecode can still differ because immutable values (`VERSION`, `MARKET_ID`, venue addresses) are written into it. List these expected differences, and any other difference, in the pull request.
- **No `via_ir`.** IPOR does not accept `via_ir` when compiling fuses. At commit `632350d`, Fusion's own [`foundry.toml`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/foundry.toml#L1-L28) sets solc 0.8.30, `optimizer_runs` of 10000000 and `evm_version` cancun, with an `arbitrum` profile on paris, and no `via_ir`.
- **EIP-170.** The deployed runtime bytecode of every fuse must fit the limit of 24,576 bytes. If the code is too large, reduce it in the code, for example by splitting logic into libraries, and not with compiler flags.

## Formatting and linting

Fusion formats Solidity with Prettier and `prettier-plugin-solidity` ([`.prettierrc.js`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/.prettierrc.js#L1-L19): tab width 4, print width 120, double quotes, no bracket spacing, the slang parser). It lints with solhint, extending `solhint:recommended` ([`.solhint.json`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/.solhint.json#L1-L13)). Following both is a recommendation. Code that follows these files is easier to read.

## Recommended: tests

> **Note:** Recommended, not required.

Tests are not a condition of the review. They are the quickest way to show that your fuses behave as this page describes. Fusion's own tests use Foundry (`forge test`). Good tests prove the following:

- **Every branch** of every fuse function runs, including the early returns for zero amounts.
- **Every custom error** is asserted by its selector with `vm.expectRevert`, for example the market id zero guard in the constructor.
- **Every event** is asserted with `vm.expectEmit`, including the amounts after clamping.
- **Fork tests against the real venue.** Run them on a pinned block so results are reproducible. The Aave V4 fork tests pin an Ethereum mainnet block with `vm.createSelectFork` ([`AaveV4ForkTestBase.sol`](https://github.com/IPOR-Labs/ipor-fusion/blob/632350d6a31afbe7a306f0e6458bb4794c18c624/test/fuses/aave_v4/AaveV4ForkTestBase.sol#L94)).
- **The balance fuse against the vault.** After `enter` and `exit`, the balance fuse and the vault's `totalAssets()` agree.
- **Edge cases.** Instant withdraw, slippage limits, zero and dust amounts, tokens with different decimals, and a substrate that was not granted.

A public repository with the tests and the fork-test results is recommended, and the pull request description may link to it. See [Whitelist Review](https://ipor.io/docs/build-on-fusion/integrating-a-new-market/whitelist-review.md) for what the pull request contains.
