Skip to content

🧩 Kwenta's proprietary margin engine for interacting with and trading on-chain derivatives

License

Notifications You must be signed in to change notification settings

Kwenta/smart-margin

Repository files navigation

Kwenta Smart Margin

Github Actions Foundry License: MIT

High-Level Overview

Contracts to manage account abstractions and features on top of Synthetix Perps V2.

System Diagram

System-Diagram

Contracts Overview

See ./deploy-addresses/ for deployed contract addresses

The Smart Margin codebase consists of the Factory and Account contracts, and all of the associated dependencies. The purpose of the Factory is to create/deploy trading accounts (Account contracts) for users that support features ranging from cross-margin, conditional orders, copy trading, etc.. Once a smart margin account has been created, the main point of entry is the Account.execute function. Account.execute allows users to execute a set of commands describing the actions/trades they want executed by their account.

User Entry: MarginBase Command Execution

Calls to Account.execute, the entrypoint to the smart margin account, require 2 main parameters:

IAccount.Command commands: An array of enum. Each enum represents 1 command that the transaction will execute. bytes[] inputs: An array of bytes strings. Each element in the array is the encoded parameters for a command.

commands[i] is the command that will use inputs[i] as its encoded input parameters.

The supported commands can be found in the wiki and code.

How the input bytes are structured

Each input bytes string is merely the abi encoding of a set of parameters. Depending on the command chosen, the input bytes string will be different. For example:

The inputs for PERPS_V2_SUBMIT_OFFCHAIN_DELAYED_ORDER is the encoding of 3 parameters:

address: The Synthetix PerpsV2 Market address int256: The size delta of the order to be submitted uint256: The desired fill price of the order

Whereas in contrast PERPS_V2_CANCEL_DELAYED_ORDER has just 1 parameter encoded:

address: The Synthetix PerpsV2 Market address which has an active delayed order submitted by this account

Encoding parameters in a bytes string in this way gives us maximum flexiblity to be able to support many commands which require different datatypes in a gas-efficient way.

For a more detailed breakdown of which parameters you should provide for each command take a look at the Account.dispatch function.

Developer documentation to give a detailed explanation of the inputs for every command can also be found in the wiki

Diagram

Execution-Flow

Reference

The command execution design was inspired by Uniswap's Universal Router.

Events

Certain actions performed by the smart margin account emit events (such as depositing margin, or placing a conditional order). These events can be used to track the state of the account and to monitor the account's activity. To avoid monitoring a large number of accounts for events, we consolidate all events into a single Events' contract. Smart margin accounts make external calls to the Events contract to emit events. This costs more gas, but significantly reduces the load on our event monitoring infrastructure.

Orders

The term "order" is used often in this codebase. Smart margin accounts natively define conditional orders, which include limit orders, stop-loss orders, and redcue-only flavors of the former two. It also supports a variety of other Synthetix PerpsV2 orders which are explicity defined by IAccount.Command commands.

Upgradability

Smart margin accounts are upgradable. This is achieved by using a proxy pattern, where the Account contract is the implementation and the AccountProxy contract is the proxy. The AccountProxy contract is the contract that is deployed by the Factory and is the contract that users interact with. The AccountProxy contract delegates all calls to the Account contract. The Account contract can be upgraded by the Factory contract due to the Factory acting as a Beacon contract for the proxy. See further details on Beacons here. One important difference between the standard Beacon implementation and our own, is that the Beacon (i.e. the Factory) is not upgradeable. This is to prevent the Beacon from being upgraded and the proxy implementation being changed to a malicious contract.

Finally, all associated functionality related to upgradability can be disabled by the Factory contract owner.

Folder Structure

to run: tree src/

src/
β”œβ”€β”€ Account.sol
β”œβ”€β”€ AccountProxy.sol
β”œβ”€β”€ Events.sol
β”œβ”€β”€ Factory.sol
β”œβ”€β”€ Settings.sol
β”œβ”€β”€ interfaces
β”‚   β”œβ”€β”€ IAccount.sol
β”‚   β”œβ”€β”€ IAccountProxy.sol
β”‚   β”œβ”€β”€ IERC20.sol
β”‚   β”œβ”€β”€ IEvents.sol
β”‚   β”œβ”€β”€ IFactory.sol
β”‚   β”œβ”€β”€ ISettings.sol
β”‚   β”œβ”€β”€ gelato
β”‚   β”‚   └── IOps.sol
β”‚   β”œβ”€β”€ synthetix
β”‚   β”‚   β”œβ”€β”€ IFuturesMarketManager.sol
β”‚   β”‚   β”œβ”€β”€ IPerpsV2ExchangeRate.sol
β”‚   β”‚   β”œβ”€β”€ IPerpsV2MarketConsolidated.sol
β”‚   β”‚   └── ISystemStatus.sol
β”‚   β”œβ”€β”€ token
β”‚   β”‚   └── IERC20.sol
β”‚   └── uniswap
β”‚       β”œβ”€β”€ IPermit2.sol
β”‚       └── IUniversalRouter.sol
└── utils
    β”œβ”€β”€ Auth.sol
    β”œβ”€β”€ Owned.sol
    β”œβ”€β”€ executors
    β”‚   └── OrderExecution.sol
    β”œβ”€β”€ gelato
    β”‚   └── OpsReady.sol
    └── uniswap
        β”œβ”€β”€ BytesLib.sol
        β”œβ”€β”€ Constants.sol
        β”œβ”€β”€ SafeCast160.sol
        └── V3Path.sol

Usage

Setup

  1. Make sure to create an .env file following the example given in .env.example

  2. Install Slither

Tests

Running Tests

  1. Follow the Foundry guide to working on an existing project

  2. Build project

npm run compile
  1. Execute both unit and integration tests (both run in forked environments)
npm run test
  1. Run specific test
forge test --fork-url $(grep ARCHIVE_NODE_URL_L2 .env | cut -d '=' -f2) --match-test TEST_NAME -vvv

tests will fail if you have not set up your .env (see .env.example)

Upgradability

Upgrades can be dangerous. Please ensure you have a good understanding of the implications of upgrading your contracts before proceeding. Storage collisions, Function signature collisions, and other issues can occur.

Update Account Implementation

note that updates to Account are reflected in all smart margin accounts, regardless of whether they were created before or after the Account upgrade.

  1. Update Account.sol contract

Make sure to update version number in Account.sol contract

  1. Add new directory to ./script/upgrades/ with the version number as the directory name (i.e. v2.6.9)
  2. Create new Upgrade.s.sol for the new version in that directory
  3. Add new directory to ./test/upgrades/ with the version number as the directory name (i.e. v2.6.9)
  4. Create new Upgrade.t.sol for the new version in that directory
  5. Test upgrade using new script (example: ./test/upgrades/v2.0.1/Upgrade.t.sol)
  6. Run script and deploy to Testnet
  7. Call Factory.upgradeAccountImplementation with new Account address (can be done on etherscan)

Only factory owner can do this

  1. Update ./deploy-addresses/optimism-goerli.json with new Account address
  2. Update utils/parameters/OptimismGoerliParameters.sol with new Account address
  3. Ensure testnet accounts are updated and functional (ensure state is correct)
  4. Run script and deploy to Mainnet
  5. Call Factory.upgradeAccountImplementation with new Account address (can be done on etherscan)

Only factory owner can do this (pDAO)

  1. Update ./deploy-addresses/optimism.json with new Account address
  2. Update utils/parameters/OptimismParameters.sol with new Account address
  3. Double-check and update any other fields necessary on Parameters constant file. (for example if there is a new deployer address)
  4. Ensure mainnet accounts are updated and functional (ensure state is correct)

External Conditional Order Executors

As of SM v2.1.0, public actors can execute conditional orders and receive a fee for doing so

  1. Navigate to src/utils/executors/OrderExecution.sol
  2. OrderExecution is a simplified contract which defines: (1) basic batch conditional order execution functionality, (2) a method to update onchain Pyth oracle price feed(s), (3) combined price feed update(s) and then conditional order execution functionality

OrderExecution is meant to serve as a starting point for developers to build their own conditional order executors and IS NOT production ready nor has it been audited

  1. See https://docs.pyth.network/evm/update-price-feeds for more information on updating Pyth oracle price feeds
  2. See IAccount.executeConditionalOrder for more information on conditional order execution
  3. Currently there are no scripts in this repository which deploy the OrderExecution contract
  4. See https://github.com/JaredBorders/KwentaOrderExecutor for a conditional order executor that includes deployment scripts

Project Tools

Static Analysis

  1. Slither
npm run analysis:slither
  1. Solsat
npm run analysis:solsat

Formatting

  1. Project uses Foundry's formatter:
npm run format

Code Coverage

  1. Project uses Foundry's code coverage tool:
npm run coverage

Gas Snapshot

Snapshots should be updated after every contract change

  1. Project uses Foundry's gas snapshot tool:
npm run gas-snapshot
  1. To view the gas snapshot, navigate to ./gas-snapshot

About

🧩 Kwenta's proprietary margin engine for interacting with and trading on-chain derivatives

Resources

License

Stars

Watchers

Forks

Packages

No packages published