├── grinding.pdf ├── tgraph.png ├── gamegraph.png ├── README.md ├── grinding.tex └── example.ipynb /grinding.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livnev/auction-grinding/HEAD/grinding.pdf -------------------------------------------------------------------------------- /tgraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livnev/auction-grinding/HEAD/tgraph.png -------------------------------------------------------------------------------- /gamegraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livnev/auction-grinding/HEAD/gamegraph.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## The Auction Grinding attack 2 | and a case for a liquidation penalty in dai 3 | -------------------------------------------------------------------------------- /grinding.tex: -------------------------------------------------------------------------------- 1 | \documentclass[a4paper,10pt]{article} 2 | 3 | \usepackage[hmargin={25mm,25mm},vmargin={25mm,25mm}]{geometry} 4 | \usepackage{amsmath} 5 | \usepackage{amsfonts} 6 | \usepackage{graphicx} 7 | 8 | \begin{document} 9 | 10 | \begin{center}{\LARGE\bf 11 | The Auction Grinding Attack:} \\ 12 | and a case for a liquidation penalty in dai 13 | 14 | \end{center} 15 | 16 | \section{Introduction} 17 | This paper describes a potential economic attack vector on the automatic auction mechanisms in the multi-collateral dai system, in particular, the reversing collateral liquidation auction. In summary: an attacker could open a Collateralised Debt Position (CDP), take out a substantial amount of debt against their collateral, and intentionally allow the position to go unsafe, causing the collateral to go to the liquidation auction. Upon liquidation the attacker can bid on their own collateral's liquidation auction, following a deterministic (pure) bidding strategy, which I describe. Following this strategy leads to meaningful profits in the event that the auction's capital participation rate (CPR) turns out to be low. In the case with no CDP liquidation penalty, the attack is practically risk free, since the worst possible outcome for the attacker is their gas expense, which is essentially bounded and easily quantifiable in advance. 18 | \par The key fact that permits this attack is that when a liquidation auction finishes ``in adequacy'', the excess collateral is returned to the owner of the CDP, meaning that when the CDP owner is bidding on their own auction in the surplus phase, the marginal cost of a new bid at any price is zero. 19 | \par To make matters worse, provided the other bidders suspect that a liquidation might be an auction grinder's, the Nash equilibrium in the auction game (with the bidders as players) might be in favour of the attacker, since bidders may be deterred from participating, knowing that the grinder's strategy will preclude them from winning the auction. 20 | \par In the presence of a non-zero liquidation penalty, the attacker will have a guaranteed up-front cost, meaning that the worst-case outcome is negative and the attack is no longer risk-free. I will provide a heuristic for setting the liquidation penalty that will deter a profit-seeking rational attacker; the heuristic will rely on a model of auction participation. 21 | 22 | \section{The Collateral Auction} 23 | The \emph{collateral auction} (also known as the {\tt flip}-auction), is one of the three auction types in the dai system. The other two are the \emph{debt auction} (the {\tt flop}-auction), and the \emph{surplus auction} (the {\tt flap}-auction). The collateral auction is used to sell off collateral from risky CDP positions in exchange for dai, to protect the system from excessive risk. When a CDP's status becomes unsafe, the system permits any agent to initiate a collateral auction, sending the collateral to the {\tt Flipper} contract, where it may receive bids. The mechanism used is a \emph{reversing auction}, which I now explain (details of the auction mechanism not relevant to the auction grinding attack are omitted). 24 | \par Like the other two dai auction types, the collateral auction has two termination conditions. The first is that the auction will terminate after {\tt tau} seconds have passed from the moment the auction was initiated. The second is that {\tt ttl} seconds have passed from the moment of the last bid. When an auction has terminated, no more bids are accepted, and the winning bidder may claim their collateral from the contract. Both {\tt tau} and {\tt ttl} are risk parameters set by governance. 25 | \par The goal of the collateral auction is to cover the outstanding dai debt on the liquidated CDP. Let {\tt tab} refer to the amount of dai debt outstanding on a CDP, and let {\tt ink} refer to the amount of collateral in the CDP, both at the moment of liquidation. Then, the first priority of the auction is to try to raise {\tt tab} dai, in exchange for any amount of the collateral, and if this much cannot be raised, to raise as much dai as possible. The second priority of the auction is to treat the CDP owner fairly, by not liquidating more of their collateral than is necessary to cover the debt. These two priorities correspond to the two phases of the auction: called the {\tt tend} phase and the {\tt dent} phase. 26 | \par In the {\tt tend} phase, the auction receives increasing bids (denoted {\tt bid}) of dai for the fixed amount {\tt ink} of collateral, with ${\tt bid} \leq {\tt tab}$. When a new, higher {\tt bid} is received, the difference between the new {\tt bid} and the previous {\tt bid} goes to the auction contract, while the rest goes to refund the previous bidder. If the auction terminates in the {\tt tend} phase (due to one of the two termination conditions above being met), all collateral goes to the winning bidder, and if in the end ${\tt bid} < {\tt tab}$ then we say the auction has \emph{finished in deficit}, and the deficit will need to be covered by an MKR-diluting debt auction. 27 | \par The auction goes from the {\tt tend} phase to the {\tt dent} phase when it receives a {\tt bid} equal to {\tt tab}. In the {\tt dent} phase, the auction receives bids in the form of decreasing amounts of collateral (denoted {\tt lot}), with ${\tt lot} < {\tt ink}$. When a lower {\tt lot} is offered, {\tt tab} dai is transferred from the bidder to refund the previous bidder. When the auction terminates in the {\tt dent} phase, the bidder with the lowest {\tt lot} receives {\tt lot} collateral, and the CDP owner has ${\tt ink}-{\tt lot}$ collateral returned to them. When an auction finishes in the {\tt dent} phase, we say that the auction has \emph{finished in adequacy}, so no debt auction will be needed. 28 | 29 | \par There are two key features of collateral auctions that create the possibility for the auction grinding attack: one concerning auctions that finish in deficit, and one concerning auctions that finish in surplus. 30 | \par An auction that finishes in deficit will trigger an MKR dilution to cover the difference between the debt owed by the liquidated CDP and the dai raised in the collateral auction. Assuming the existence of a true value for the collateral, either the winner of the collateral auction has managed to buy collateral at a discount to the fair price, or at the time of liquidation the CDP's debt must have exceeded the value of its collateral (negative equity), or some combination of the two\footnote{There is also a possibility that the CDP has positive equity at the time of liquidation, as judged by the fair market value of the collateral, but due to a decline in the price of the collateral over the course of the auction, it could still finish in deficit. This additional ``basis risk'' that MKR capital is exposed to is not considered here.}. Hence, viewing this event as a ``value flow'', we see that the value of MKR capital flows to the auction winner (whose collateral purchase price is effectively subsidised), or the CDP debtor (whose excess debt over collateral value is forgiven), or both. The auction grinding attack will seek to exploit this value flow in the case that the CDP owner and the winning auction bidder are the same person. 31 | 32 | \par An auction that finishes in surplus will have its excess collateral returned to the owner of the liquidated CDP. 33 | This means that (in the absence of a liquidation penalty) the marginal cost of a CDP owner placing a bid on the collateral auction of their own CDP is zero. The attack will exploit this fact in order to ensure that they can always win the auction. 34 | 35 | \section{The Attack} 36 | \par For the purposes of this section, we will assume there is no liquidation penalty. We will also neglect any risks associated with collateral price volatility during the course of the auction: the reason for this is that the attacker can choose to attempt this attack during a period when collateral price volatility is low. Moreover, they are free to hedge their exposure to the collateral externally to reduce their risk. It is also important that the attacker has extra dai available to them before they start the attack. 37 | \par I now describe the attack: first the attacker must choose the collateral type that they wish to use for the attack. As we shall see, they may select the collateral type where they expect the least auction participation, or simply try the attack on all collateral types repeatedly. Let {\tt mat } be the liquidation ratio of the chosen collateral type, and we will call the collateral tokens of this collateral type gems. Let {\tt tab} and {\tt ink} be the amounts of dai debt and collateral gems, respectively, of the CDP upon liquidation. 38 | \par Next, the attacker creates a CDP of this collateral type, deposits all of their gems, and borrows the maximum amount of dai from this CDP, hoping to get their position liquidated immediately. If their position does not become unsafe on the next collateral price feed update, they only have to draw more dai if possible, and wait until the next downward tick in the feed price. Once their CDP becomes unsafe, they initiate its liquidation, so the collateral auction will begin. 39 | \par The attacker's strategy is simple: their first goal is to win their own collateral's liquidation auction, at the lowest price possible, with the auction finishing in deficit. Thus, they will place bids while the auction is in the {\tt tend} phase, hoping to win the auction\footnote{The exact strategy (choice of timing, bid increment, etc.) by which the bidder will attempt to win the auction in the {\tt tend} phase is up to the attacker.}. If the attacker is successful, winning the auction in the {\tt tend} phase paying {\tt bid} dai, we will have reached outcome $W({\tt bid})$. If someone thwarts this plan by outbidding them until the auction is in the {\tt dent} phase, then the attacker will abandon that plan and simply place another, final bid at the implied price corresponding to the attacker's view of the correct collateral value. At this point, one of two things will happen: either no one will outbid the attacker and they will win the auction, which we will call outcome $N$, or someone will outbid the attacker and end up buying (some of) the collateral at a premium to the attacker's notion of the fair value, which we will denote $Y({\tt lot})$ where {\tt lot} is the amount of gems bought by the winning bidder. In any case, the attacker guarantees that the auction will finish in one of the states $W({\tt bid})$, $N$, or $Y({\tt lot})$. 40 | \par If the auction finishes in state $W({\tt bid})$, the attacker will have bought back their own collateral with less dai than they borrowed against it, ending with a profit. 41 | \par If the auction finishes in state $N$, the attacker will have spent the same amount of dai that they just borrowed, and received in return the same amount of collateral that they put in (part of it going to them as the CDP owner, and part as the winner of the auction), so has made neither a profit nor a loss. 42 | \par If the auction finishes in state $Y({\tt lot})$, the attacker will have effectively traded gems for dai with the winning bidder at a premium to their notion of the fair price, thus making a profit. 43 | \begin{figure} 44 | \centering 45 | % \includegraphics[width=1.5in]{diag/atlasex1} 46 | \includegraphics[width=3.5in]{gamegraph} 47 | \caption{Graph of attacker's strategy, where blue arrows depict moves that the attacker will make, and pink arrows depict moves that might be made by other auction participants. The horizontal position of a game state representing the total return for the attacker in the event that the auction terminates in that state.} 48 | \end{figure} 49 | 50 | We enumerate all relevant states of the auction, and display the game states as a graph in Figure 1: 51 | \begin{description} 52 | \item [$W({\tt bid})$] The auction is in the {\tt tend} phase and the attacker is the top bidder. 53 | \item [$B({\tt bid})$] The auction is in the {\tt tend} phase and someone else is the top bidder. 54 | \item [$N$] The auction is in the {\tt dent} phase and the attacker is the top bidder, having bid at most their fair price for the collateral. 55 | \item [$S({\tt lot})$] The auction is in the {\tt dent} phase and someone else is the top bidder, having bid less than the attacker's fair price for the collateral. 56 | \item [$Y({\tt lot})$] The auction is in the {\tt dent} phase and someone else is the top bidder, having bid more than the attacker's fair price for the collateral. 57 | \end{description} 58 | \par We also compute the attacker's return in case the auction finishes in each state, where return is calculated as the difference in the dai value between the attacker's starting and ending portfolio, assuming the attacker values one gem as {\tt wut} dai, and ignoring any gas costs, financing costs, and opportunity costs. 59 | \begin{table}[h] 60 | \centering 61 | \begin{tabular}{l{l}{l}r} 62 | termination state & auction winner & attacker return \\ 63 | \hline 64 | $W({\tt bid})$ & attacker & ${\tt tab} - {\tt bid}$ & $(>0)$ \\ 65 | $B({\tt bid})$ & other & ${\tt tab} - {\tt ink}\cdot{\tt wut}$ & $ (<0)$ \\ 66 | $N$ & attacker & $0$ \\ 67 | $S({\tt lot})$ & other & ${\tt tab} - {\tt lot}\cdot{\tt wut}$ & $(<0)$ \\ 68 | $Y({\tt lot})$ & other & ${\tt tab} - {\tt lot}\cdot{\tt wut}$ & $(>0)$ \\ 69 | \end{tabular} 70 | \caption{Payoff table for auction grinding strategy with no liquidation penalty.} 71 | \end{table} 72 | 73 | \par As we have seen, the attacker can force the auction to finish in one of the three scenarios where their return is non-negative. Thus, provided one of the two scenarios where the return is positive could happen with non-negligible probability, the attacker will expect a positive expected return from following this strategy. In particular, the attacker can increase the chance of profiting through outcome $W({\tt bid})$ if they have sufficient capital to initiate a large liquidation (or many simultaneous liquidations) that many other market participants will be priced out of. Moreover, if the attacker ``griefs'' the system by following this strategy repeatedly and winning every auction, others may be discouraged from participating in future auctions (in game terms, the attacker is always able to force a draw, so competing bidders might choose not to play). 74 | 75 | \newpage 76 | \section{The Liquidation Penalty} 77 | A natural approach to mitigating this attack is to add some friction to the liquidation process, in addition to the gas costs which are already present but may be too insignificant relative to the capital at play. I propose using the \emph{liquidation penalty} to this end. In short, a liquidation penalty is implemented by increasing the outstanding debt on a CDP by some factor {\tt axe} just as it goes to liquidation, so that a CDP that had {\tt tab} outstanding debt will be treated as if it had ${\tt axe} \cdot {\tt tab}$ of debt for the purposes of liquidation. {\tt axe} set to $1$ is equivalent to no liquidation penalty. The effect of this is that the CDP owner will get less of their collateral back at the end of a liquidation, since the auction would have been covering a greater debt: effectively, they have had an additional $({\tt axe} - 1) \cdot tab$ dai debited from their balance\footnote{There is an alternative way of implementing the liquidation penalty, by slashing a fraction of the collateral that is returned to the CDP owner at the end. It is straightforward to show that this method is completely equivalent to the one described above, for all parties involved.}. 78 | \par We now recompute the payoff matrix on the auction grinding strategy when a liquidation penalty of {\tt axe} is used: 79 | \begin{table}[h] 80 | \centering 81 | \begin{tabular}{l{l}r} 82 | termination state & attacker return \\ 83 | \hline 84 | $W({\tt bid})$ & ${\tt tab} - {\tt bid}$ & $(>0)$ \\ 85 | $B({\tt bid})$ & ${\tt tab} - {\tt ink}\cdot{\tt wut}$ & $ (<0)$ \\ 86 | $N$ & $-({\tt axe} - 1) \cdot {\tt tab}$ & $(<0)$ \\ 87 | $S({\tt lot})$ & ${\tt tab} - {\tt lot}\cdot{\tt wut}$ & $(<0)$ \\ 88 | $Y({\tt lot})$ & ${\tt tab} - {\tt lot}\cdot{\tt wut}$ & $(>0)$ \\ 89 | \end{tabular} 90 | \caption{Payoff table for auction grinding strategy with liquidation penalty set to {\tt axe}.} 91 | \end{table} 92 | \par The crucial difference is that now the attacker's strategy cannot guarantee that the attacker will not suffer losses from attempting the attack. Indeed, the previously neutral state $N$, where the attacker would have forced the game to go if they were unable to make a profit, now represents a loss for the attacker, and the attack is no longer risk-free. To discourage the attacker, it will be necessary to make their expected return negative by setting {\tt axe} sufficiently high, which requires some model of the probability of each auction outcome. We model this by taking a random variable $L \in [0, \infty ) $ for the implied price of the highest non-attacker bid, assuming this to be independent of the attacker's presence and behaviour: the distribution of this random variable should capture the attacker's expectation that they will be challenged by another bidder in the auction. Integrating over probabilities $\mathbb{P}(L = l)$ and splitting the integral over the three possible termination states of the auctions gives the attacker's expected return: 93 | \begin{equation*} 94 | \mathbb{E} = \int_{l = 0}^{\frac{\tt tab}{\tt ink}} ({\tt tab} - l \cdot {\tt ink})\cdot \mathbb{P}(L = l) \mathrm{d}l - \int_{l = \frac{\tt tab}{\tt ink}}^{\tt wut} ({\tt axe} - 1)\cdot {\tt tab} \cdot \mathbb{P}(L = l) \mathrm{d}l + \int_{l = {\tt wut}}^{\infty} (1 - \frac{{\tt axe} \cdot {\tt wut}}{l})\cdot {\tt tab} \cdot \mathbb{P}(L = l) \mathrm{d}l 95 | \end{equation*} 96 | whenever ${\tt axe} < \frac{{\tt wut} \cdot {\tt lot}}{\tt tab}$, and 97 | \begin{equation*} 98 | \mathbb{E} = \int_{l = 0}^{\tt wut} ({\tt tab} - l \cdot {\tt ink})\cdot \mathbb{P}(L = l) \mathrm{d}l + \int_{l = {\tt wut}}^{\frac{{\tt axe}\cdot{\tt tab}}{\tt lot}} ({\tt tab} - {\tt ink}\cdot {\tt wut}) \cdot \mathbb{P}(L = l) \mathrm{d}l + \int_{l = \frac{{\tt axe}\cdot{\tt tab}}{\tt lot}}^{\infty} (1 - \frac{{\tt axe}\cdot{\tt wut}}{l})\cdot {\tt tab} \cdot \mathbb{P}(L = l) \mathrm{d}l 99 | \end{equation*} 100 | when ${\tt axe} >= \frac{{\tt wut} \cdot {\tt lot}}{\tt tab}$. 101 | \par Thus, assuming a model of non-attacker auction participation in the form of a probability distribution for $L$, we can determine the minimum {\tt axe} for which the attacker's expected return is negative. Initially, we can use a heuristic to estimate this probability distribution, and after the auctions have been running for some time we can use empirical data to adjust our model, repeat the computation, and determine if {\tt axe} should be increased or decreased. 102 | \par As an example, we can take a generalised student's t-distribution for $L$, with mean ${\tt r \cdot wut}$ (where $r$ is some auction discount constant), scaling parameter $\frac{1}{20}{\tt wut}$, and one degree of freedom. An example of the probability density function, along with the attacker's profit, are shown in Figure 2. By numerically integrating the above expressions for the attacker's expected return with this distribution, taking ${\tt wut} = 500$, $r = 0.95$, ${\tt tab} = 10000$, ${\tt ink} = 30$, we find that in order to make the expected return negative we must set ${\tt axe} > 1.0835$ (i.e. we must apply a liquidation penalty of at least 8.35\% the value of the outstanding debt). 103 | 104 | \begin{figure}[h] 105 | \centering 106 | % \includegraphics[width=1.5in]{diag/atlasex1} 107 | \includegraphics[width=4.5in]{tgraph} 108 | \caption{The probability density function of the t-distribution model for $L$, with ${\tt wut} = 500$ and $r = 0.95$ (corresponding to a mean auction price discount of 5\%). Also plotted is the attacker's profit as a function of $L$, where we have set ${\tt tab} = 10000$, ${\tt ink} = 30$, and ${\tt axe} = 1.0835$} 109 | \end{figure} 110 | 111 | The above heuristic assumes that $L$ is independent of the attacker's behaviour, and in particular that the attacker is not able to predict $L$ ahead of time. This assumption is flawed, since a motivated attacker is likely to strike during a regime where liquidity is stretched, by observing market conditions before initiating the liquidation. Moreover, the attacker can influence liquidity conditions themselves, either by manipulating the market in some way, or simply by attempting this attack with a very large amount of debt. Therefore, the heuristic above should be used very conservatively, choosing a more pessimistic (worst-case) model for $L$ if possible. 112 | 113 | \section{Conclusion} 114 | \par In the discussion above, I have explained the necessity of the liquidation penalty, without which the collateral auctions are vulnerable to an unpleasant economic attack. With an appropriately chosen liquidation penalty, determined by estimating the expected collateral auction participation (or based on existing data), the attacker will no longer expect a profit from the attack, and will be deterred from attempting it in normal conditions. There remains a risk that this attack could be profitably executed by a well-capitalised attacker who times their liquidation to occur during a moment of extreme market stress, where for some reason auction participation is much lower than expected. This possibility should be kept in mind as a potential black swan risk, and will be the subject of future research. 115 | \end{document} 116 | -------------------------------------------------------------------------------- /example.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 5, 6 | "metadata": { 7 | "collapsed": true 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import matplotlib.pyplot as plt\n", 12 | "import numpy as np\n", 13 | "import scipy.stats" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 6, 19 | "metadata": { 20 | "collapsed": false 21 | }, 22 | "outputs": [ 23 | { 24 | "data": { 25 | "application/javascript": [ 26 | "/* Put everything inside the global mpl namespace */\n", 27 | "window.mpl = {};\n", 28 | "\n", 29 | "mpl.get_websocket_type = function() {\n", 30 | " if (typeof(WebSocket) !== 'undefined') {\n", 31 | " return WebSocket;\n", 32 | " } else if (typeof(MozWebSocket) !== 'undefined') {\n", 33 | " return MozWebSocket;\n", 34 | " } else {\n", 35 | " alert('Your browser does not have WebSocket support.' +\n", 36 | " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", 37 | " 'Firefox 4 and 5 are also supported but you ' +\n", 38 | " 'have to enable WebSockets in about:config.');\n", 39 | " };\n", 40 | "}\n", 41 | "\n", 42 | "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", 43 | " this.id = figure_id;\n", 44 | "\n", 45 | " this.ws = websocket;\n", 46 | "\n", 47 | " this.supports_binary = (this.ws.binaryType != undefined);\n", 48 | "\n", 49 | " if (!this.supports_binary) {\n", 50 | " var warnings = document.getElementById(\"mpl-warnings\");\n", 51 | " if (warnings) {\n", 52 | " warnings.style.display = 'block';\n", 53 | " warnings.textContent = (\n", 54 | " \"This browser does not support binary websocket messages. \" +\n", 55 | " \"Performance may be slow.\");\n", 56 | " }\n", 57 | " }\n", 58 | "\n", 59 | " this.imageObj = new Image();\n", 60 | "\n", 61 | " this.context = undefined;\n", 62 | " this.message = undefined;\n", 63 | " this.canvas = undefined;\n", 64 | " this.rubberband_canvas = undefined;\n", 65 | " this.rubberband_context = undefined;\n", 66 | " this.format_dropdown = undefined;\n", 67 | "\n", 68 | " this.image_mode = 'full';\n", 69 | "\n", 70 | " this.root = $('
');\n", 71 | " this._root_extra_style(this.root)\n", 72 | " this.root.attr('style', 'display: inline-block');\n", 73 | "\n", 74 | " $(parent_element).append(this.root);\n", 75 | "\n", 76 | " this._init_header(this);\n", 77 | " this._init_canvas(this);\n", 78 | " this._init_toolbar(this);\n", 79 | "\n", 80 | " var fig = this;\n", 81 | "\n", 82 | " this.waiting = false;\n", 83 | "\n", 84 | " this.ws.onopen = function () {\n", 85 | " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", 86 | " fig.send_message(\"send_image_mode\", {});\n", 87 | " fig.send_message(\"refresh\", {});\n", 88 | " }\n", 89 | "\n", 90 | " this.imageObj.onload = function() {\n", 91 | " if (fig.image_mode == 'full') {\n", 92 | " // Full images could contain transparency (where diff images\n", 93 | " // almost always do), so we need to clear the canvas so that\n", 94 | " // there is no ghosting.\n", 95 | " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", 96 | " }\n", 97 | " fig.context.drawImage(fig.imageObj, 0, 0);\n", 98 | " };\n", 99 | "\n", 100 | " this.imageObj.onunload = function() {\n", 101 | " this.ws.close();\n", 102 | " }\n", 103 | "\n", 104 | " this.ws.onmessage = this._make_on_message_function(this);\n", 105 | "\n", 106 | " this.ondownload = ondownload;\n", 107 | "}\n", 108 | "\n", 109 | "mpl.figure.prototype._init_header = function() {\n", 110 | " var titlebar = $(\n", 111 | " '
');\n", 113 | " var titletext = $(\n", 114 | " '
');\n", 116 | " titlebar.append(titletext)\n", 117 | " this.root.append(titlebar);\n", 118 | " this.header = titletext[0];\n", 119 | "}\n", 120 | "\n", 121 | "\n", 122 | "\n", 123 | "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", 124 | "\n", 125 | "}\n", 126 | "\n", 127 | "\n", 128 | "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", 129 | "\n", 130 | "}\n", 131 | "\n", 132 | "mpl.figure.prototype._init_canvas = function() {\n", 133 | " var fig = this;\n", 134 | "\n", 135 | " var canvas_div = $('
');\n", 136 | "\n", 137 | " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", 138 | "\n", 139 | " function canvas_keyboard_event(event) {\n", 140 | " return fig.key_event(event, event['data']);\n", 141 | " }\n", 142 | "\n", 143 | " canvas_div.keydown('key_press', canvas_keyboard_event);\n", 144 | " canvas_div.keyup('key_release', canvas_keyboard_event);\n", 145 | " this.canvas_div = canvas_div\n", 146 | " this._canvas_extra_style(canvas_div)\n", 147 | " this.root.append(canvas_div);\n", 148 | "\n", 149 | " var canvas = $('');\n", 150 | " canvas.addClass('mpl-canvas');\n", 151 | " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", 152 | "\n", 153 | " this.canvas = canvas[0];\n", 154 | " this.context = canvas[0].getContext(\"2d\");\n", 155 | "\n", 156 | " var rubberband = $('');\n", 157 | " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", 158 | "\n", 159 | " var pass_mouse_events = true;\n", 160 | "\n", 161 | " canvas_div.resizable({\n", 162 | " start: function(event, ui) {\n", 163 | " pass_mouse_events = false;\n", 164 | " },\n", 165 | " resize: function(event, ui) {\n", 166 | " fig.request_resize(ui.size.width, ui.size.height);\n", 167 | " },\n", 168 | " stop: function(event, ui) {\n", 169 | " pass_mouse_events = true;\n", 170 | " fig.request_resize(ui.size.width, ui.size.height);\n", 171 | " },\n", 172 | " });\n", 173 | "\n", 174 | " function mouse_event_fn(event) {\n", 175 | " if (pass_mouse_events)\n", 176 | " return fig.mouse_event(event, event['data']);\n", 177 | " }\n", 178 | "\n", 179 | " rubberband.mousedown('button_press', mouse_event_fn);\n", 180 | " rubberband.mouseup('button_release', mouse_event_fn);\n", 181 | " // Throttle sequential mouse events to 1 every 20ms.\n", 182 | " rubberband.mousemove('motion_notify', mouse_event_fn);\n", 183 | "\n", 184 | " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", 185 | " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", 186 | "\n", 187 | " canvas_div.on(\"wheel\", function (event) {\n", 188 | " event = event.originalEvent;\n", 189 | " event['data'] = 'scroll'\n", 190 | " if (event.deltaY < 0) {\n", 191 | " event.step = 1;\n", 192 | " } else {\n", 193 | " event.step = -1;\n", 194 | " }\n", 195 | " mouse_event_fn(event);\n", 196 | " });\n", 197 | "\n", 198 | " canvas_div.append(canvas);\n", 199 | " canvas_div.append(rubberband);\n", 200 | "\n", 201 | " this.rubberband = rubberband;\n", 202 | " this.rubberband_canvas = rubberband[0];\n", 203 | " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", 204 | " this.rubberband_context.strokeStyle = \"#000000\";\n", 205 | "\n", 206 | " this._resize_canvas = function(width, height) {\n", 207 | " // Keep the size of the canvas, canvas container, and rubber band\n", 208 | " // canvas in synch.\n", 209 | " canvas_div.css('width', width)\n", 210 | " canvas_div.css('height', height)\n", 211 | "\n", 212 | " canvas.attr('width', width);\n", 213 | " canvas.attr('height', height);\n", 214 | "\n", 215 | " rubberband.attr('width', width);\n", 216 | " rubberband.attr('height', height);\n", 217 | " }\n", 218 | "\n", 219 | " // Set the figure to an initial 600x600px, this will subsequently be updated\n", 220 | " // upon first draw.\n", 221 | " this._resize_canvas(600, 600);\n", 222 | "\n", 223 | " // Disable right mouse context menu.\n", 224 | " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", 225 | " return false;\n", 226 | " });\n", 227 | "\n", 228 | " function set_focus () {\n", 229 | " canvas.focus();\n", 230 | " canvas_div.focus();\n", 231 | " }\n", 232 | "\n", 233 | " window.setTimeout(set_focus, 100);\n", 234 | "}\n", 235 | "\n", 236 | "mpl.figure.prototype._init_toolbar = function() {\n", 237 | " var fig = this;\n", 238 | "\n", 239 | " var nav_element = $('
')\n", 240 | " nav_element.attr('style', 'width: 100%');\n", 241 | " this.root.append(nav_element);\n", 242 | "\n", 243 | " // Define a callback function for later on.\n", 244 | " function toolbar_event(event) {\n", 245 | " return fig.toolbar_button_onclick(event['data']);\n", 246 | " }\n", 247 | " function toolbar_mouse_event(event) {\n", 248 | " return fig.toolbar_button_onmouseover(event['data']);\n", 249 | " }\n", 250 | "\n", 251 | " for(var toolbar_ind in mpl.toolbar_items) {\n", 252 | " var name = mpl.toolbar_items[toolbar_ind][0];\n", 253 | " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", 254 | " var image = mpl.toolbar_items[toolbar_ind][2];\n", 255 | " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", 256 | "\n", 257 | " if (!name) {\n", 258 | " // put a spacer in here.\n", 259 | " continue;\n", 260 | " }\n", 261 | " var button = $('