The Superform Protocol is a suite of non-upgradeable, non-custodial smart contracts that act as a central repository for yield and a router for users. It is modular, permissionless to list vaults, and enables intent-based transactions across chains that allow users to execute into an arbitrary number of tokens, chains, and vaults at once.
For DeFi protocols, it acts as an instant out-of-the-box distribution platform for ERC4626-compliant vaults. For users, it allows access to any vault listed on the platform from the chain and asset of their choice in a single transaction.
Core capabilities for protocols include:
- Permissionlessly list your vaults on Superform by adding your ERC4626 vault to the proper 'Form' (a vault adapter within Superform).
- Create a profile page with embeddable data sources for users to find more information about your protocol
- Manage metadata for yield opportunities
- Users can deposit into your vaults from any chain without the need to deploy your vaults on that chain
Core capabilities for users include:
- Deposit or withdraw into any vault using any asset from any chain
- Batch desired actions across multiple vaults and multiple chains in a single transaction
- Automate and manage your yield portfolio from any chain
- Automatically compound your yield position
- Make cross-chain transactions using multiple AMBs
This repository includes all Superform contracts and can be split into two categories: Core and Periphery.
Corecontracts contain logic to move liquidity and data across chains along with maintaining roles in the protocolPeripherycontracts contain the main touch-points for protocols and users to interface with and include helper contracts to ease 3rd party integrations
.
├── script
├── security-review
├── src
├── crosschain-data
├── crosschain-liquidity
├── forms
├── interfaces
├── libraries
├── payments
├── settings
├── types
├── vendor
├── test
├── foundry.toml
└── README.md
scriptcontains deployment and utility scripts and outputs/scriptsecurity-reviewcontains information relevant to prior security reviews and the scope of bug bounties/security-reviewsrcis the source folder for all smart contract code/srccrosschain-dataimplements the sending of messages from chain to chain via various AMBs/src/crosschain-datacrosschain-liquidityimplements the movement of tokens from chain to chain via bridge aggregators/src/crosschain-liquidityformsimplements types of yield that can be supported on Superform and introduces a queue when they are paused/src/formsinterfacesdefine interactions with other contracts/src/interfaceslibrariesdefine functions used across other contracts and error states in the protocol/src/librariespaymentsimplements the handling and processing of payments for cross-chain actions/src/paymentssettingsdefine, set, and manage roles in the Superform ecosystem/src/settingstypesdefine core data structures used in the protocol/src/typesvendoris where all externally written interfaces reside/src/vendor
testcontains tests for contracts in src/test
We recommend visiting technical documentation at https://docs.superform.xyz.
- All external actions, except Superform creation, start in
SuperformRouter.sol. For each deposit or withdraw function the user has to provide the appropriate "StateRequest" found inDataTypes.sol - All deposit and withdrawal actions can be to single or multiple destinations, single or multi vaults, and same-chain or cross-chain. Any token can be deposited from any chain into a vault with swapping and bridging handled in a single call. Sometimes it is also needed to perform another action on the destination chain for tokens with low bridge liquidity, through the usage of
DstSwapper.sol. Similarly for withdraw actions, users can choose to receive a different token than the one redeemed for from the vault, but funds must go back directly to the user (i.e. no use ofDstSwapper.sol). - Any individual tx must be of a specific kind, either all deposits or all withdraws, for all vaults and destinations
- Vaults themselves can be added permissionlessly to Forms in
SuperformFactory.solby callingcreateSuperform(). Forms are code implementations that adapt to the needs of a given vault, currently all around the ERC-4626 Standard. Any user can wrap a vault into a Superform using the SuperformFactory but only the protocol may add new Form implementations. - This wrapping action leads to the creation of Superforms which are assigned a unique id, made up of the superForm address, formId, and chainId.
- Users are minted SuperPositions on successful deposits, a type of ERC1155 modified called ERC1155A. On withdrawals these are burned. Users may also within each "StateRequest" deposit choose whether to retain4626 which sends the vault share directly to the user instead of holding in the appropriate Superform, but only SuperPositions can be withdrawn through SuperformRouter.
In this section we will run through examples where users deposit and withdraw into vault(s) using Superform.
- Validation of the input data in
SuperformRouter.sol. - Process swap transaction data if provided to allow SuperformRouter to move tokens from the user to the Superform and call
directDepositIntoVaultto move tokens from the Superform into the vault. - Store ERC-4626 shares in the Superform and mint the appropriate amount of SuperPositions back to the user.
- Validation of the input data in
SuperformRouter.sol. - Dispatch the input token to the liquidity bridge using an implementation of a
BridgeValidator.solandLiquidityHandler.sol. - Create an
AMBMessagewith the information about what is going to be deposited and by whom. - Message the information about the deposits to the vaults using
CoreStateRegistry.sol. This is done with the combination of a main AMB and a configurable number of proof AMBs for added security, a measure set viasetRequiredMessagingQuoruminSuperRegistry.sol. - Forward remaining payment to
PayMaster.solto cover the costs of cross-chain transactions and relayer payments. - Receive the information on the destination chain's
CoreStateRegistry.sol. Assuming no swap was required inDstSwapper.sol, at this step, assuming both the payload and proof have arrived, a keeper updates the messaged amounts to-be deposited with the actual amounts received through the liquidity bridge usingupdateDepositPayload. The maximum number it can be updated to is what the user specified in StateReq.amount. If the end number of tokens received is below the minimum bound of what the user specified, calculated by StateReq.amount*(10000-StateReq.maxSlippage), the deposit is marked as failed and must be rescued through therescueFailedDepositfunction to return funds back to the user through an optimistic dispute process. - The keeper can then process the received message using
processPayload. Here the deposit action is try-catched for errors. Should the action pass, a message is sent back to source acknowledging the action and mints SuperPositions to the user. If the action fails, no message is sent back, no SuperPositions are minted, and therescueFailedDepositfunction must be used.
- Validation of the input data in
SuperformRouter.sol. - Burn the corresponding SuperPositions owned by the user and call
directWithdrawFromVaultin the Superform, which redeems funds from the vault. - Process transaction data (either a swap or a bridge) if provided and send funds back to the user.
- Validation of the input data in
SuperformRouter.sol. - Burn the corresponding SuperPositions owned by the user in accordance to the input data.
- Create the
AMBMessagewith the information about what is going to be withdrawn and by whom. - Message the information about the withdrawals from the vaults using
CoreStateRegistry.sol. This is done with the combination of a main AMB and a configurable number of proof AMBs for added security, a measure set viasetRequiredMessagingQuoruminSuperRegistry.sol. - Forward remaining payment to
PayMaster.solto cover the costs of cross-chain transactions and relayer payments. - If no transaction data was provided with the transaction, but the user defined an intended token and chain to receive assets back on, assuming both the payload and proof have arrived, a keeper can call
updateWithdrawPayloadto update the payload with transaction data. This can be done to reduce the chance of transaction data failure due to latency. - The keeper can then process the received message using
processPayload. Here the withdraw action is try-catched for errors. Should the action pass, the underlying obtained is bridged back to the user in the form of the desired tokens to be received. If the action fails, a message is sent back indicating that SuperPositions need to be re-minted for the user according to the original amounts that were burned. No rescue methods are implemented given the re-minting behavior on withdrawals.
Step by step instructions on setting up the project and running it
- Make sure Foundry is installed with the following temporary workarounds (see: foundry-rs/foundry#8014)
- For minimal ram usage, do
foundryup -v nightly-f625d0fa7c51e65b4bf1e8f7931cd1c6e2e285e9 - For compatibility with safe signing operations do
foundryup -v nightly-ea2eff95b5c17edd3ffbdfc6daab5ce5cc80afc0
- Set the rpc variables in the makefile using your own nodes and disable any instances that run off 1password
POLYGON_RPC_URL=
AVALANCHE_RPC_URL=
FANTOM_RPC_URL=
BSC_RPC_URL=
ARBITRUM_RPC_URL=
OPTIMISM_RPC_URL=
ETHEREUM_RPC_URL=
BASE_RPC_URL=
FANTOM_RPC_URL=
SEPOLIA_RPC_URL=
- Install submodule dependencies:
forge install- Run
make ftestto run tests against the contracts
$ make ftest