This article is a development report for the Dex223 decentralized exchange.
We’re building an exchange to speed up the adoption of the ERC-223 token standard. ERC-223 was created to address a security problem in the older ERC-20 standard. The security issue in ERC-20 has caused over $100M in losses on Ethereum in 2023. Take a look at other Dex223 development reports.
Browse other Dex223 development reports here.
The implementation of the core platform is completed. The final version of the core contracts can be found here: https://github.com/EthereumCommonwealth/Dex223-contracts/tree/d99da952dfb472762e4c28f62a80202549e46ea8/contracts/dex-core
The core contracts include: Factory, Pools, Auto-Listing registry and Auto-listing contracts.
This version of the contracts is deployed on Ethereum mainnet, verified and is ready for production use:
| Name | Address |
|---|---|
| WETH9 | 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| TOKEN_CONVERTER | 0xe7E969012557f25bECddB717A3aa2f4789ba9f9a |
| POOL_LIBRARY | 0xfA5930D2Ef1b6231e220aeDda88E28C4E8F0F3a0 |
| FACTORY | 0x8dae173016f65F61e90631Ee5f28C9E47b1ebc06 |
| SWAP_ROUTER | 0xbeBAB9Ab58f8099fbFEb15E14b663615D19304Fa |
| POSITION_MANAGER | 0xFd4cE11db9db9433286734304049526E9336139E |
| POOL_INIT_CODE_HASH | 0xa5fa1f34aae4b83ab2690d3f3df6f78e99959a1f2eb8aa4c11ba10586677338d |
| POOL_USDC_WETH | 0x82Cc735b39a3992be7b47bEb9AE7519aC92ed562 |
| CORE_AUTOLISTING | 0x029f10E06Dc7d6264f9432ACA3F52572543c48e0 |
| FREE_AUTOLISTING | 0xa7089d8cbcc47543388a346dd6ebf0b05106a477 |
| AUTOLISTINGS_REGISTRY | 0x105F43A70aFCEd0493545D04C1d5687DF4b3f48f |
The UI of the core platform is also finalized at this point, we've added the last fixes, finalized tooltip texts and it must be fully operational.
All these changes to the core platform were completed in the first days of June. We are currently waiting for the quality analysis team (mostly composed of former EOS Support department) to approve the platform for the coordinated launch.
We consider the platform to be technically complete and ready for production use since June.
The bulk of the development this month was done in the margin module domain. In June we had a working prototype of the module which was audited internally (mostly by myself) and tested.
The testing indicated no issues in the math of the margin module or its workflow but we detected a huge quantity of problems related to Oracles and potential vulnerabilities that could allow order owners to intentionally design their margin orders in such a way that would allow them to pull funds from the borrowers.
Oracles passed a huge number of update iterations (which can be tracked by commits history here and here and I'll not cover every potential problem that was fixed in this report). This is just the first "default" Oracle that governs prices for the Dex223 margin module. We are planning to have at least two different Oracles at the launch of this module: (1) "default" Oracle would pull prices from the actual liquidity reserves in Dex223 pools and (2) LINK Oracle would be an alternative version which uses prices provided by ChainLINK Oracles as the mark price for liquidations.
The "default" oracle we are currently working with has an intrinsic problem related to in-contract computations. Uniswap and Dex223 store "price" in a Q64.96 format.
For example 1:1 price in a USDT-USDC pool look like this 79237389755455049773827871756. In order to calculate the actual price we would need to divide this number by 2**96 and then the result needs to be squared. In EVM there are no fixed-point numbers so if the price in the pool is less than 2**96 then the result of all these computations will inevitably become zero. In order to bypass this computational limitation of EVM we are raising the price to the power of 2 first then dividing it by 2**192, however we hit another limitation of EVM such that if any of the results of this computations would overflow uint256 (which can potentially happen) - it would cause the Oracle to return an invalid price for a completely functional pair of tokens and a functional Pool with existing liquidity.
Another problem with this price and automated price reading is that the "price" is only valid for one token swap, if we have token1 and token2 then the "price" in the pool is the amount of token2 that we would receive for 1 token1. If we would need to swap token1 for token2 then the price needs to be actually reverted and we need to make sure that nothing will overflow or slash out extra digits once again thanks to EVM not being able to process the most basic math.
Therefore the decision was made to simplify the computations and simply slash the price to the first 6 digits within Oracle. This would guarantee the safety of auto-computations but in return it negatively impacts precision.
We are aware of a number of potential problems with the current implementation of the default Oracle:
- If one token is significantly more expensive than the other (1 token1 > 1,000,000 token2) then the slashing method of Oracle's safe computations is ruining the price precision to the point where it becomes unusable.
- If the Oracle under any circumstances would fail to return any actual price then it would completely freeze the workflow of the position that holds these tokens. This means - the owner of the position will not be able to close it, make any trades, withdraw the funds. This position can not be liquidated either so the funds will be effectively frozen there in the contract. We need to make sure that this will not happen but we can not guarantee that this will not happen because a user can assign a custom Oracle to their Orders. We certainly need a built-in backup price source that would be triggered in case we fail to get the price from the Oracle specified by the owner of the funds held in the position.
- Given how flexible the system is i.e. users can set up custom Oracles, allow tokens to be traded by token-lists and set up any deadlines for positions - we will most certainly have Orders with messed settings at some point. In order to make sure that the funds are not being lost due to this problems we will need to implement a method of manual control over the margin module at the beginning which would allow the developers to extract funds from these orders and positions.
Even though the margin module was completed quite some time ago it was highly unoptimized and when the contract-UI integration started we had to re-implement a lot of functions in a way that would allow making multiple actions in one transaction.
It would have taken 5 transactions just to set up an Order at first (create an order template, tell the Order which tokens are allowed for trading, tell the Order which tokens are allowed to be a collateral, deposit funds to the Order which can be borrowed, enable Order).
In the current implementation we managed to reduce the number of transactions where its possible to just approval + action, however when setting up the Order we couldn't separate the step where funds are being deposited to the Order from the initial Order setup.
Also, we've built the monitoring system that will pull data from the contracts (by tracking their state on-chain) and feed it onto UI. The Orders need to be tracked and displayed, positions also need to be tracked as well as their status and liquidation risks. This data needs to be displayed here: https://test-app.dex223.io/en/margin-trading
The backend part of the Orders/Positions state displaying is completed. The current prototype deployed on test-app is unreliable but positions will be properly tracked in the next update.
The margin module is especially hard-to-debug system as its workflow is not straightforward but requires a set of conditions to be met every time a particular scenario is being executed. Each Pool it interacts with must have a specific amount of liquidity, the Oracle must pull the price and respond correctly, transactions must be executed at a specific time because everything is time-sensitive in margin module since it calculates interest based on the timestamp of each last block.
In order to simplify the testing of the contract and allow UI developers to execute particular scenarios in a fixed environment a special contract that simulates a "trader" was introduced:
That contract does two things:
- It sets up a pair of tokens
- It makes orders, takes loans, trades and takes profit or liquidates itself depending on what the tester wants from it
Anyone can interact with the "trader contract". The contract is deployed on Sepolia here: https://sepolia.etherscan.io/address/0xBca81278ff25E0BA088fcdFd3E2D798CDC3D2358
It can set up a new Oracle but this one is the most up-to-date one we have at the moment: https://sepolia.etherscan.io/address/0xc569D09E6de44679A2a7f1a31CC82eEB5feA0dC3
Functions called x0_MakeTokens(..), x1_MakePool10000(), x2_Liquidity() set up tokens.
Functions called step1_, step2_, step3_ etc. execute actions.
The default scenario involves:
- Make a tokenlist for an Order
- Create an Order
- Deposit tokens in the Order that was just created
- Take a loan from the Order
- Make a test margin swap (optional and can be skipped)
- Make a normal swap (directly via the Pool, not using the margin module) to slightly alter the price (optional and can be skipped)
- Make a swap with significant amount of tokens that pushes the price in the chosen direction - either makes the last opened position profitable or prone to liquidation
- Position liquidation or position closing depending on the chosen scenario
- Confirmation of the liquidation or withdrawal of the funds
Steps 7-8-9 from different scenarios conflict with each other.
For any early testers we strongly recommend using the Utility contract for setting up your tokens (with functions x0-x2) and making an Order (functions step1-step3). Anyone would be able to take loans from this Orders but in case we will need to investigate a reported problem - we will at least know that the amount of liquidity and Order settings were correct at the beginning.
- Order creation and funds depositing can't be performed in one transaction.
- Lender can't specify a "liquidation tax" which would be allocated to cover the price difference in case liquidation does not precisely happen at liquidation price. This feature was planned from the beginning as we expect the lack of precision, especially at low-liquidity pairs.
- Oracles are far from being reliable. The method of reading data from Pools is heavily affected by the limitations of EVM that we have to combat.
- Perpetual positions are not yet supported.
- UI is not completed, most of the tooltips and status handlers are not set yet.
Revenue contracts were built but we don't have a UI that would allow a smooth interaction with them yet. The implementation of the UI will only begin once the margin module UI is ready.
The contract source codes are published here: https://gist.github.com/Dexaran/9601c5369000f20069bb0b54fe7c9357
This version is not properly tested.
Most of the UI updates were also related to the margin module implementation.
-
Design system was updated (affects the displayed interactive elements like buttons, dropdowns etc.).
-
The margin module contracts differed from the version we planned at the beginning as we were solving arising problems therefore UI needed to be updated.
-
Particularly Margin Swap UI was updated to display positions that the user is swapping from.
-
Token importing was significantly simplified.
-
Core UI was finalized, missing tooltips and minor bugs were fixed. Now it is production ready: app.dex223.io
-
A new page "Liquidation risks" was added.
-
Remove liquidity and Borrow Market UI templates were updated to account for the latest contracts/Oracles change.
-
Margin swap directly from an Order was added (more on this one in the next report as we are currently working on this feature).
-
Position and Order management was reworked.
-
"Position health status" was implemented as we investigated the design of our competitors and found out that most of the users must be familiar with this term and this method of information displaying.
-
Manual liquidation through UI was created.
The latest Figma templates can be found here: https://www.figma.com/design/8ogeGWWFTcmjUBDWXLlIMU/%F0%9F%94%84-DEX223-Exchange---All-mockups---Current-version--Copy-?node-id=2460-62488&t=uPJgy4R8eJIXHMq7-1
Margin module has its own Figma: https://www.figma.com/design/dcgcSLN3cQKtq3umsEsK0C/Margin-module--Copy-?node-id=0-1&t=BJiFJOPtSyIULLHT-1
In 2021 OWASP introduced a new category of vulnerabilities that ERC-20 flaw perfectly fits in - insecure by design.
In 2017 when the ERC-20 flaw was discovered there was no such category and advocating for adding ERC-20 to any common vulnerabilities lists was difficult since the flaw was not actually an "exploitable vulnerability that could lead someone into hacking a contract and directly causing damage" but rather "a way for users to damage themselves when using this system in a common way".
With this new addition we gained a strong argument to advocate for ERC-20 flaw being classified as an actual vulnerability.
The success of the project depends on our ability to make the problem of ERC-20 standard known and recognized. Therefore we will focus on giving this one some exposure outside of crypto-related resources this time.
At the other hand the severity of ERC-20 problems was highlighted in the official Ethereum documentation as the moderators finally approved the pull request into one of the ethereum.org releases ethereum/ethereum-org-website#13218 (comment)
It took about a year of bureaucratic fight to make this happen but finally this is happening.
Once we will have a working prototype of the DEX we are planning to launch a campaign of warning existing projects that use ERC-20 standard (well, almost all projects in the Ethereum ecosystem that should be) about the problems associated with it and the possibility of using ERC-7417 Converter to mitigate them.