-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multiplication Overflow #64
Comments
Thanks Felix. I was trying to use tenderly again. How do you know the block number corresponding to this transaction? |
We don't know for sure (as we are using the "latest" block at the time). The error was thrown at 10:57:45 UTC, the latest blocknumber at that time according to etherscan was probably https://etherscan.io/block/9973513 |
Using that block number in tenderly fails on:
Does this mean that I should try lower numbers? |
From https://etherscan.io/address/0x6f400810b62df8e13fded51be75ff5393eaa841f?fromaddress=0x453ad119f26128034d3b5c2b6179b8b7f63ae1c7 (filtered on the from address above), seems there is a failed transaction about 4.5 hours ago. That would be block 9973323, but it also fails with the error message above. |
Sorry, I messed up the timestamps (they were already in UTC), so we are looking at 12:57 which is block https://etherscan.io/block/9974043 Note that "Solutions are no longer accepted for this batch" can also mean "Solutions are not yet accepted for this batch" |
That worked, thanks. Unfortunately I ran out of free simulations (10). They are now asking me to upgrade to tenderly pro :( EDIT: Apparently I still have a trial phase of 15 days, yay! |
After investigation using tenderly, it seems the multiplication overflow happens when computing the disregarded utility of an order. I double checked and it matches perfectly what I'm doing in python (except python has no limitations on multiplication). The contract throws when doing the multiplication on this line:
With:
If you multiply them together and take the log2 you get 256.24, and that's why it fail. Now, how should we handle this on the solver side? Should we send the trivial solution whenever some multiplication/division can't fit in 256 bits? |
That's very unfortunate. It basically means the user has too much balance/remaining amount left. So his order can only be fulfilled fully - not partially (at least at these prices). Is this a very esotheric example or can this easily happen again?
We should be able to multiply to a product of up to 10**77, which should allow in sum 23 digits (77 - 18 (price_a) - 18 (price_b) - 18 (amount) |
Here is the order details:
|
So the problem is the huge priceDenominator/Numerator. cc @josojo for the discussion. This seems to be an OWL liquidity provision order. Because it has unlimited amount the numerator and denominator are extremely large (128 bit) which might limit their ability to be use in trades. |
Given the open solver can touch more then one order per side, can we just exclude the offending orders and try to solve again? |
I see. I supose in the smart contract we could have an alternative association in case it doesn't fit the current way? For example this gives a pretty close value:
|
Yes that is always possible. It would be a bit inefficient though since we would have to remove one at a time, interleaving with solving, until everything fits. Unless we come up with a good heuristic to remove these orders at preprocessing. |
Yes, I agree that this is an issue with the smart contract, which we should have thought of before. Hence, I made an issue in dex-contracts: gnosis/dex-contracts#708. We should tackle it in the next version.
For right now, I also can not come up with a better solution. Probably, we should go this way for the open-solver, although it is very inefficient. But most likely the standard solver would also run into the same problem? We should also think about deploying the bracket-strategy without this huge magic numbers. |
Just in case anyone would like more information regarding the offending order:
with balances
Since up until now we have been missing the It is probably also worth pointing out here that this offending order is not "selling" OWL, but "buying" it. Something that has not yet been made clear in our discussions. Also, although I don't suspect it is immediately relevant to the overflow, it might be worth mentioning that this trader was about to trade with themselves;
|
Some other observations made about the "geometry" of this trade was that the trade occurred between two tokens ( |
Trying to reply to:
Attached is the chart corresponding to the market where the overflow occurred. The green dashed lines are the limit prices of the 14 orders buying fee with non zero account balance. These orders, sorted by execution order are:
The orange dashed lines are the limit prices of the 11 orders selling fee with non zero account balance. These orders, sorted by execution order are:
I think I'm likely missing the point, because everything seems normal to me: The first two orders selling fee are two very large market orders, and they make the price (the value of the objective function is optimal there). |
I think it's really important to see the volumes that we left untouched as well.
In that case I would have expected all orders buying the fee to be executed completely. However, we apparently left an order with >300$ remaining to be traded (the "high" remainingSellBalance caused the overflow). If the first two orders selling fee are smaller than that 300$ order I would instead expect the 300$ order to be making the price. |
This is fraction filled of every order buying fee, sorted by execution order:
Note that is fraction is the executed sell amount over the max_sell_amount after capping by balance (i.e. the effective max sell amount). The fact that sometimes it is not 1 exactly is because of rounding errors. ---- [EDITED] --- The two less-priority orders buying fee were indeed not executed. Investigating. |
Clarifying, after taking the balance into account there are 16 orders buying fee with max_sell_amount > 10000 being considered. However, there are 2 whose limit price is below the 0.3355053471704794, which is the minimum price to be matched by any sell order. That is why you only see 14 green dashed vertical lines in the graph. All those 14 buy orders are fully filled. |
I see. So a major factor in this example is also that we are trading against ourselves. This makes it that despite our order being completely filled, we end up with remaining balance coming from the proceeds of trading on the other side at the same time. To clarify, the solver thinks we have exhausted the orders to 100% and there is no more balance left. In the smart contract however we evaluate disregarded utility based on the final balance after settling all trades. My claim is that since the order size was unlimited, they could have theoretically been executed with much larger buy/sell amounts (leading to temporarily negative balances before the reverse trade is applied in the same batch). The executed amount should only be capped by the amount of fee we have to pay (bringing our balance to 0). Is that correct? Is the punishing self-trade behaviour something that could be implemented? |
Maybe here is a good place to include some of my construction attempts and failures: Recall, we are trying to construct an example of a touched, unlimited order between two non-fee tokens that is fractionally filled by a very small percentage of the available balance. I am beginning to think that we cannot make this happen between non-fee tokens. At least not with "market orders". This is because when a market order is filled, it is often filled completely. Conjecture: There are no optimal solutions containing two touched, unlimited order between two non-fee tokens that is fractionally filled by a very small percentage of the available balance. I am not sure that I have proven this yet, but I below is a sketch proof that neither of the two orders can be "market orders" (i.e. willing to sell for whatever price). Essentially, I would say, if they are willing to sell at whatever price, then there is no reason they would be partially filled. Fact; When there exist overlapping market orders, both orders should/could/would be fully executed and it does not matter at which price (within the overlapping price interval). Thus, overlapping market orders between non-fee tokens would/should/could not cause this overflow. Assume now that there is an unlimited market order selling DAI for ETH with a “large” balance. The owner of the ETH has an order selling a (a) small or balance at a fixed appropriate limit price (since we have already considered market orders) Note first, that large balance here is relative to the DAI owner’s balance (i.e. a balance of ETH that exceeds the DAI balance of the other trader according to the ETH seller’s valuation of their ETH) while small balance is the opposite of this (e.g. ETH owner has 1 ETH and DAI owner has 100K DAI at a valuation of 1 ETH = 100 DAI) In both cases (b-i) and (b-ii) it would be feasible (optimal?) sell the entire small balance of ETH for all the DAI. This is undesired for other reasons, but shouldn’t overflow because In both (a-i) and (a-ii), if the relative ETH balance is much larger than that of DAI, then the price will be forced to the limit price specified by the seller of ETH and the market order selling DAI will be liquidated entirely and we are again in a scenario where |
That makes sense. The standard solver used to correctly handle this case. It was disabled at some point to solve some other problems that may happen when you can use balance that you don't have initially (@twalth3r should know). Currently it caps the max sell amounts to the initial balances, which is a very efficient way of satisfying the account balance constraint. This is what is done in the open solver as well. |
I am not sure than when you say "balance" you are making the distinction between "max sell amount" and "account balance". Something I'm convinced of is that, there is no optimal solution for the token pair matching problem where there is more than one order that is partially filled. |
But this single partially filled order does not necessarily have to be matched close to its limit price? As in the price that at which it is matched could be significantly better than requested? |
Something that was mentioned above but maybe not interpreted from the same angle is this fact that the offending order traded with themselves. This actually implies that the disregarded utility was calculated "incorrectly" for this account because of the fact that the contract adds all the balances of tokens obtained in the trade before subtracting. Since the disregarded utility is computed after the fact, the adjusted balances have both already been accounted for, so the individual disregarded utility for the trade itself is incorrect (i.e. using a different balance than it should). Of course it has no other way to know this. Furthermore, this would also go undetected by the solvers (in their current state) because they would not be computing I believe we have now started to isolate the exact scenario in which this overflow is expected to occur. |
The partial-filled order doesn't have to be matchd at its limit price. Here's an example with 1 order and 1 counter-order:
These orders get matched at:
The optimal exchange rate is more or less in the middle of both limit prices. |
That's a good point. If I understood it correctly, the solvers are doing something similar: the du used in the objective function is computed using the initial token balance, and not the final (I believe that would be possible to do in the standard solver, but not sure how it would impact efficiency). Additionally, as said above, the solvers are also capping the max sell amounts to that initial token balance, which may also have an impact in this issue. |
One reason for this is that we can't have these huge numbers coming from the standing orders in the model for numerical reasons, so we have to cap sell amounts somewhere. We decided to cap them at the available balance, because otherwise it might screw people that accidentally trade with themselves into paying a lot of fees. On the downside, this prevents uncovered orders entirely. |
@marcovc I would have expected the following settlement: order1: {execSellAmount: 4, execBuyAmount: 2 } Considering u = (execBuyAmount - (execSellAmount * buyAmount / sellAmount)) * price_buy In the solution you described I compute And some dU (left out here) In the solution I expected I compute thus more than in the first solution. I'm probably messing up the prices somehow. Do you happen to have this computation in code somewhere? |
Lets call token1 the token bought by order1, and token2 the token bought by order2. Also lets consider that
For the solution I describe we have It seems above you assume that Therefore: In the solution you suggested we have I haven't checked what the disregarded utility would be, but perhaps this explains it? You can use the code in this repo (although I believe we better try to sort this out independently since the code reflects my, possibly flawed, reasoning):
The relevant part are the lines starting with
You can see that root 1, which corresponds to your suggestion, has lower objective value than the winning root (root 4). Also when you're looking at the output above take into consideration that it also factors in the fees, both in the constraints and in the objective function. |
Thanks, that cleared things up for me. The dU of o2 is only ~0.23 and so the overall objective value in my proposed solution is less than in the one you found. This is a nice example of how our objective function does not favour a Walrasian Equilibrium even if we considered dU of all orders (I'm not sure if we already had an example for this cc @koeppelmann) |
Closing as this is a bug of the smart contract. |
The following solution (batch 5294170) caused a
in the smart contract: https://gnosis-dev-dfusion.s3.amazonaws.com/data/mainnet/open-solver/results/2020-04-30/instance_5294170_2020-04-30T12%3A57%3A18.258176793%2B00%3A00/06_solution_int_valid.json
The payload sent to the contract is:
The text was updated successfully, but these errors were encountered: