이론 대 실제: 탄소 DeFi의 고정 소수점 산술의 정밀도와 정확도

이론 대 실제: 탄소 DeFi의 고정 소수점 산술의 정밀도와 정확도

이론 대 실제: 탄소 DeFi의 고정 소수점 산술의 정밀도와 정확도

Mark Richardson

Mark Richardson

Mark Richardson

2024. 2. 19.

2024. 2. 19.

2024. 2. 19.

My approach to de novo protocol design begins with establishing a theoretical framework. I assume this is common, but I am reluctant to speak for other designers. Speaking only to my experience, this phase of the development process invariably relies on pure algebraic abstraction. The goal is to achieve an exhaustive mathematical description of the protocol’s core methods such that its functionality can be demonstrated symbolically. It is wonderful to arrive at a proven (or, at the very least provable) source of truth with respect to what the expected outcomes are for any conceivable interaction with a newly imagined system. However, as a veteran experimentalist I understand better than most the difference between pen-and-paper theory and the limits of physical reality. For protocol design this is the difference between the immaculate mathematical abstraction of the system and its implementation under the limitations of its host blockchain. With only finite memory and compute cycles at hand, the challenge is to create the best possible approximation of the model while respecting the boundaries of what the network can realistically support. This is no small task. I am very fortunate to be working with talented embedded systems engineers, including and especially Barak Manos, with whom I can collaborate to thread this specific needle.


Carbon DeFi in Brief


Carbon DeFi is a token exchange protocol, made to resemble a conventional orderbook in some respects, without completely turning its back on its AMM heritage. Fittingly, the system variables are those required to encapsulate a singular, or series of user-nominated exchange rates and token balances, which are then broadcast to other market participants who may elect to take the tokens and prices on offer. As is customary in DeFi, the exchange is formalized with an invariant function, from which its price quoting and swap functions can be derived:


Invariant Function


Price Function


Trade by Source Function (i.e. output amount, given the input amount)


Trade by Target Function (i.e. input amount, given the output amount)



The ab, and z terms are constants that describe the relative curvature and size of the bonding curve; z denotes the y-intercept, and a and b are derived from the two price bounds of the range.


Exchange Rate Parameter Definitions


In the equations above, (xy) represent cartesian coordinates for a point that lies on the bonding curve, (Δx, Δy) represent token quantities corresponding to a translocation upon the same curve, and Δand Δhave opposite signs. By convention, the swapped token amounts are expressed as unsigned integers, Δ≥ 0 and Δ≥ 0. In other words, we adopt a moving frame of reference, such that Δrepresents a number of tokens sent from the market participant to the contract, and Δrepresents a number of tokens sent from the contract to the market participant:

  • Following a token exchange, the contract balance of x is x + Δx.

  • Following a token exchange, the contract balance of y is y − Δy.


Consequently, the exchange rates for any curve inherit this convention; the exchange rate, ∂y/∂xis the drop in the y-balance with respect to some input amount as it approaches zero (Δ→ 0). The benefit here is that rates are quoted as positive (rather than negative) numbers. While technically incorrect with respect to the invariant, these conventions support standard financial intuition. However, it is important to recognize that this formulation is necessarily numeraire-agnostic, as the y-axis may represent either the “cash” (or “quote”), or “risk” (or “base”) asset. In cases where the y-axis refers to a common numeraire, such as a $USD stablecoin, the price quote is intuitive: ∂y/∂xhas units of $USD per risk token. However, in cases where the y-axis refers to the risk asset the price quote is inverted: ∂y/∂has units of risk token per $USD.


For example, a curve which represents bidding liquidity denominated in USDC for the ETH markets beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH has intuitive units, and the depletion of the USDC balance on the curve is commensurate with receding bidding prices. Suppose that the curve was initialized with 10,000 USDC of which 5,000 USDC remains. Through application of the price function, the marginal highest bid on the curve, P_m, is found to be ~1,995 USDC per ETH. Whereas P_m is the marginal rate corresponding to the current y balance of the curve, P_a and P_b refer to the marginal rates when y = z, and y = 0, respectively.


Figure 1: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC.


Consider now how a curve representing asking liquidity might look compared to the bidding liquidity example, above. As per the conventions the curve which offers ETH is necessarily denominated in ETH, and its contract balance is represented on the y-axis. Therefore, the output of the price function (and the variables P_a, P_b, and P_m) are reported in units of ETH per USDC. This can be a little jarring. For example, the curve which offers ETH at a rate beginning at 2,500 USDC per ETH and ending at 3,000 USDC per ETH must use the reciprocals of these values. Suppose that the curve was initialized with 10 ETH, of which 5 ETH remains. The price function returns the marginal lowest ask on the curve, P_m, as 3.66 × 10⁻⁴ ETH per USDC; as before, P_a and P_b refer to the marginal rates when y = z, and y = 0, which are 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) and 3.33 × 10⁻⁴ (i.e. 3,000⁻¹) ETH per USDC, respectively. Note that this arrangement still exhibits the desired behavior; as the ETH balance is depleted, the reciprocal price of ETH is diminished, which is commensurate with higher asking prices.


Figure 2: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH.


If the reader is appropriately motivated, the significance of this parametrization is elaborated in the Carbon DeFi whitepaperlitepaper, and invention disclosure. For the present discussion, assume that the user provides all three marginal rates P_a, P_b, and P_m as inputs, along with the token quantity, y, from which the z value must be calculated. In almost all cases, the user elects to have P_m = P_a at the time the curve is initialized, in which case z = y; however, this is not a requirement.

Curve Size Parameter Definition


Token Decimals


For the benefit of the uninitiated, a single token is not what it first appears. Tokens have a property which determines its divisibility, called its “token decimals”. For example, Bitcoin’s decimals are 8, meaning that a single BTC unit is defined as comprising a set of 10⁸ individual parts called “satoshis” or “sats”. Ether’s decimals are 18, meaning that a single ETH unit comprises 10¹⁸ parts called “wei”. Circle’s USD stablecoin uses only 6 decimals, meaning that a single USDC unit comprises 10⁶ parts (also called wei), and so on.


From the perspective of the exchange system, these smallest indivisible parts are all that matter, and exchange rates are unavoidably expressed in these terms. For example, suppose that 20 ETH is equal in value to 1 BTC (e.g. ETH/USD = $2,500; BTC/USD = $50,000). At the contract level, this translates to 20 × 10¹⁰ ETH wei is equal in value to 1 BTC sat. Since 1 BTC sat is indivisible, BTC-to-ETH exchange is necessarily [for lack of a better word] “chunky”. This forms the basis for further erosion of the perceived accuracy of the system; however, this would be unavoidable even with unlimited storage and computer power.


To demonstrate how token decimals impact the data available on the smart contracts, the two bonding curves presented above have been recreated here, but this time accounting for the difference in the token decimals. The curve representing bidding liquidity on ETH denominated in USDC, beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH, initialized with 10,000 USDC, of which 5,000 USDC remains, is now revealed to have a tiny fraction of the naïve exchange rate. At the wei level, the marginal price is no longer ~1,995 USDC per ETH, but 1.995 × 10⁻⁹ USDC wei per ETH wei.


Figure 3: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC, represented at the wei scale.


The curve representing asking liquidity for ETH denominated in USDC, beginning at which are 4.00 × 10⁻⁴ (2,500⁻¹) USDC per ETH and ending at 3.33 × 10⁻⁴ (3,000⁻¹) USDC per ETH, initialized with 10 ETH, of which 5 ETH remains, is revealed to exhibit a substantially more aggressive exchange profile compared to the naïve rate. At the wei level, the marginal price is no longer 3.66 × 10⁻⁴ ETH per USDC, but rather 3.66 × 10⁸ ETH wei per USDC wei.


Figure 4: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH, represented at the wei scale.


Differences in token decimals can alter the effective exchange rates by several orders of magnitude. This presents an interesting challenge, as even common token pairs (e.g. ETH/USDC) can result in exchange rates one might have otherwise concluded were sufficiently unusual to justify ignoring. Unfortunately, no — the scope of the expected input space really is so vast as to include numbers both vanishingly small, and impenetrably large. The challenge is to define the boundaries of what is reasonable and ensure that calculations performed at any point within these bounds result in outputs that closely adhere to the theory, while remaining mindful of the limited computer resources of the network.


The Scaling Constant


Fixed point systems only know about integers. For an exchange protocol based on theory that is so thoroughly entrenched in the real numbers, this is a problem. To illustrate, consider that any exchange rate less than one rounds to zero. Given the reciprocal nature of the bidding and asking prices, about half of all exchange rates will default to zero. For example, the value 2/3 cannot be stored in a fixed-point system; however, it can be recreated in-situ by storing the numerator and denominator discretely, (2, 3), and its parts can be summoned when performing further mathematical operations when needed. The approach implemented by Carbon DeFi is fundamentally the same, save for the fact that the in-situ recreation of the original fraction is abstracted algebraically.


Carbon DeFi multiplies the rate variables, a and b, by a large constant, C, and records the integer part as the contract variables A and B (n.b. the capitalization is intended to symbolically capture their meaning). Implicitly, these variables represent the numerators of the fractions a = A/C and b = B/C. To recreate a and b, the general formulae are appropriately modified to compensate for the implied denominator, C, which compensates for the scaling-up in A and B and allows these contract variables to be used directly during exchange calculations.


Exchange Rate Parameter Definitions (Scaling Corrected)


Invariant Function (Scaling Corrected)


Price Function (Scaling Corrected)


Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Curve Size Parameter Definition (Scaling Corrected)


Data Storage


The strategy data is organized into three slots:

  • Slot #1: Contains the y_bid and y_ask values. Each value has 112 bits of allocated memory.

  • Slots #2 and #3: Contains the z_bid, A_bid, and B_bid, and the z_ask, A_ask, and B_ask values, and each slot is exclusive to either the bidding or asking curve. The z_bid and z_ask values have the same 112 bits of allocated memory as the y_bid and y_ask values in Slot #1. The A_bid, B_bid, A_ask, and B_ask values have 54 bits of allocated memory apiece, of which the first 48 bits is reserved for the mantissa (or “significand”) and the remaining 6 bits are reserved for the exponent.

  • Unused Bits: The y_bid, y_ask, z_bid and z_ask values contain a 16-bit appendix, and the A_bid, B_bid, A_ask, and B_ask values contain a 10-bit appendix of unutilized memory. These spaces are envisioned to support other features of the system in the future. Being that this has no bearing on the present discussion, these appendices can safely be ignored.


This arrangement allows the write operations to be restricted to only one slot in most cases. A trade executed against a strategy necessarily results in changes to both the y_bid and y_ask liquidity values in Slot #1. On occasion, if one of the y_bid or y_ask values exceed its corresponding z_bid or z_ask value, then a second write operation is required to reset the appropriate value in Slot #2 or #3. Therefore, trades executed against a strategy will require data to be overwritten in at most two slots in memory.


Figure 5: Carbon DeFi strategy data storage and organization


Smart Contract Implementation, Fixed-Point Precision, and Relative Error


The core object of the Carbon DeFi protocol is called a strategy, which comprises two different orders represented by two different bonding curves, each representing one of the bid/ask pair. Someone who is quoting the bid and ask prices is called a maker, and the market participant who agrees to an exchange at the quoted price is the taker. There are two types of user-protocol interaction where numeric precision should be scrutinized:

  1. During strategy creation or editing an existing strategy, wherein the strategy’s owner (i.e. the maker) may provide price inputs of arbitrary precision (highestRate, lowestRate, marginalRate) which must then be transformed into fixed-point format and recorded to the smart contracts.

  2. During a trade, wherein an outside market participant (i.e. the taker) calls one of the two available trade functions and sends an amount of the source token to the contract in exchange for the target token.



Figure 6: The two points of user-protocol interaction where precision loss occurs.


Strategy Creation and Editing


The maker’s purpose [one hopes] is conceived off-chain, where they may imagine an infinitely precise pair of bonding curves that represent their market outlook. Each curve’s parametrization can be described and communicated entirely by the three marginal rates P_a, P_b, and P_m, and the number of tokens, y, to be distributed across each curve. Once received, this data must be processed into the variables AByz. While these variables may also be infinitely precise with respect to the maker’s imagined strategy, their on-chain representation is limited by the storage allocated to each value. Therefore, this process of collecting and processing the maker’s inputs, and their truncation to fit the storage requirements of the blockchain is an important source of precision loss.


To understand how this looks in practice, consider an arbitrary set of inputs received from the maker during strategy creation: thehighestRate, lowestRate, marginalRate, and the liquidityvalues. The context is also important. If the maker’s ambition is to buy the risk/base asset, then the liquidity value refers to the cash/quote asset and the exchange rates can be used verbatim; whereas if the maker intends to sell the risk/base asset, then the liquidity value refers to the risk/base asset itself and the exchange rates must be inverted. In both cases, the token decimals and compression of the curve parameters A and B is worth close inspection.


One of the most challenging pairs in all of DeFi from a precision perspective is Shiba Inu (SHIB, a relatively worthless 18 decimals token) versus wrapped Bitcoin (wBTC, an extremely valuable, 8 decimals token). At the time of writing, the SHIB/USD rate is $0.0₅9756 ($9.756 × 10⁻⁶) and the wBTC/USD rate is $ 51,854.04 ($5.185404 × 10⁴). Therefore, the current wBTC/SHIB market rate is 5,315,092,250.92251 (5.31509225092251 × 10⁹).


Suppose that a user wants to create a direct market for this pair at the current market rate, offering to buy wBTC with SHIB as the price of SHIB improves in value versus wBTC, with a market depth of 10 billion SHIB tokens or approximately $97,560 USD equivalent value. The following hypothetical inputs represent such a view, and the maker’s arbitrarily precise bonding curve at the wei scale is depicted in Figure 7. Note that we adopt the perspective here that the maker’s off-chain inputs are in the familiar non-wei format, and that the decimal corrections must be applied as part of the input processing.


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “buy wBTC with SHIB”

  lowestRate = 3,543,394,833.948345819174724191772607317991320232065325065623989176702584205824065767190258
 highestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
marginalRate = 5,315,092,250.922518728762086287658910976986980348097987598435983765053876308736098650785387
   liquidity = 10,000,000,000

Figure 7: Invariant and Price Curves for lowestRate = 3,543,394,833.9483… SHIB per wBTC, highestRate = 7,972,638,376.3837… SHIB per wBTC, y = 10,000,000,000 SHIB and z = 22,247,448,713.9158… SHIB at the wei scale.


Here we can examine the translation of the user’s inputs into the curve parameters, and their compression prior to storage. Since this curve has the cash/quote token (SHIB) as its own balance, the rates are used verbatim (i.e. that is, not their reciprocal values), and only adjusted for the decimals of the pair. The sheer size of the result is especially pronounced due to the enormous difference in the token values, which is further amplified by their decimals and the scaling constant.


The off-chain compression algorithm then converts the rate parameter to an integer and normalizes it by the scaling constant to determine its bit length for truncation. Truncation occurs by bitwise shifting, removing insignificant bits (duh!). The exponent, indicating the value’s scale, is extracted from the truncated value’s bit length. The mantissa is isolated by right-shifting this value by the exponent. Finally, the mantissa and scaled exponent are combined with a bitwise OR, producing a compressed binary representation optimized for minimal storage.


In other words, (referring only to the integer part of the binary form of the result) the significand is defined as the first 48 digits, which is extracted to be used as the mantissa, and the remaining digits are replaced with zeroes. Then, the trailing zeroes after the 48th digit are counted; the count, expressed as a 6-bit binary number, is extracted as the exponent. The compressed value is then stored as the concatenation of the exponent and the mantissa, in that order.


I am sincerely hopeful that one of the above descriptions is helpful. Failing that, I am providing the following code snippet from the class I use to perform these analyses, as well as the plain text tables it can produce, which represent each step of the compression sequence for the wBTC/SHIB curve presently under examination for both its B and A parameters.


def compress_rate_parameter(
    self,
    parameter_name: str, 
    print_process_summary: bool = True
    ) -> int:
    """
    ### Compresses a rate parameter for storage in blockchain environments.

    ## Parameters:
    | Parameter Name          | Type      | Description                                                                           |
    |-------------------------|-----------|---------------------------------------------------------------------------------------|
    | `parameter_name`        | `str`     | The name of the parameter to be compressed.                                           |
    | `print_process_summary` | `bool`    | Whether to print a detailed summary of the compression process (default is `True`).   |

    ## Returns:
    | Return Name                | Type  | Description                                                                      |
    |----------------------------|-------|----------------------------------------------------------------------------------|
    | `compressed_rate_parameter`| `int` | The compressed form of the rate parameter, optimized for blockchain storage.     |

    ## Notes:
    - This method reduces the size of rate parameters, making them suitable for efficient storage and processing on blockchain platforms.
    - The compression process involves:
        1. Converting the parameter to an integer, if not already.
        2. Truncating any trailing zeroes to minimize the parameter's size.
        3. Extracting an exponent representing the scale of truncation.
        4. Identifying the mantissa as the significant part of the parameter.
        5. Combining the exponent and mantissa into a single integer.
    - Optionally, a detailed visual summary of the compression process can be printed, illustrating the transformation of the parameter into its compressed form.
    - This method is crucial for understanding the use of smart contract storage, reducing gas costs, and maintaining acceptable accuracy.
    """
    rate_parameter_int = int(self.maker_precise_curve_parameters[parameter_name])
    number_of_bits = (rate_parameter_int // self.scaling_constant_int).bit_length()
    truncated_rate_parameter = (rate_parameter_int >> number_of_bits) << number_of_bits
    exponent = (truncated_rate_parameter // self.scaling_constant_int).bit_length()
    mantissa = truncated_rate_parameter >> exponent
    compressed_rate_parameter = mantissa | (exponent * self.scaling_constant_int)
    if print_process_summary:
        self.print_compression_process_summary(parameter_name,
                                               rate_parameter_int,
                                               truncated_rate_parameter,
                                               exponent,
                                               mantissa,
                                               compressed_rate_parameter)
    return compressed_rate_parameter


+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |               |
|            B parameter (decimal integer) | >>> 1675519805183645805377564                                                                             |               |
|             B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001001111111110010101111100000011100               | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001000000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||--------trailing-zeroes--------|               |               |
|              B exponent (binary integer) | >>> 100001                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                                | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 100001101100010110011100001110001010010111110100010001                                                | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                                |               |
| compressed B parameter (decimal integer) | >>> 9483730408799505


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554 |               |
|            A parameter (decimal integer) | >>> 837759902591822902688782                                                                             |               |
|             A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100111111111001010111110000001110               | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||-------trailing-zeroes--------|               |               |
|              A exponent (binary integer) | >>> 100000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                               | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 100000101100010110011100001110001010010111110100010001                                               | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                               |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                               |               |
| compressed A parameter (decimal integer) | >>> 9202255432088849


In addition to the compression of these curve parameters, only the integer parts of the y and z values are stored. While this is obvious for y, which represents the SHIB token balance of the curve (in wei units), it is still worth raising as the maker be unaware of the decimals of the token they are using and provide an off-chain input that implies divisibility beyond that supported by the token contract (e.g. wBTC beyond the 8th decimal place). While z has an explicit correspondence with y and must also be an integer, it is also computed from the marginal rate user input. Therefore, its rounding will also have an impact on the overall precision of the system — albeit a negligible one. The error caused by rounding the y and values and resulting from a compression/decompression cycle of the A and B values can be examined directly versus the maker’s arbitrarily precise inputs by comparing the digits in-place.


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 10000000000000000000000000000                                                                         |
|                          | |||||||||||||||||||||||||||||                                                                         |
| y fixed point (contract) | 10000000000000000000000000000                                                                         |
|                          |                                                                                                       |
|        z precise (maker) | 22247448713915890490986420373.52945695982973740328335064216346283625480188728657513269929716552320115 |
|                          | |||||||||||||||||||||||||||||                                                                         |
| z fixed point (contract) | 22247448713915890490986420373                                                                         |
|                          |                                                                                                       |
|        A precise (maker) | 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554  |
|                          | ||||||||||||||::::||:::|                                                                              |
| A fixed point (contract) | 837759902591821830684672                                                                              |
|  A compressed (contract) | 9202255432088849                                                                                      |
|                          |                                                                                                       |
|        B precise (maker) | 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |
|                          | |||||||||||||||::::|::::|                                                                             |
| B fixed point (contract) | 1675519805183643661369344                                                                             |
|  B compressed (contract) | 9483730408799505


Be reminded that a Carbon DeFi strategy is composed of two bonding curves, each taking a different side of the market. It is worthwhile to consider the opposite order type — one which offers wBTC liquidity in exchange for SHIB as the price of wBTC appreciates relative to SHIB — because it represents the opposite extreme of the curve parametrization.


Suppose that the maker includes the following order as part of their strategy, representing the opposite side of the market with a depth of 4 wBTC tokens or approximately $207,416 USD equivalent value:


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “sell wBTC for SHIB”

  lowestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
 highestRate = 15,945,276,752.76755618628625886297673293096094104429396279530795129516162892620829595235616
marginalRate = 10,630,184,501.84503745752417257531782195397396069619597519687196753010775261747219730157077
   liquidity = 4


Figure 7: Invariant and Price Curves for lowestRate = 7,972,638,376.3837… SHIB per wBTC, highestRate = 15,945,276,752.7675… SHIB per wBTC, y = 4 wBTC and z = 7.3721… wBTC at the wei scale. Note that the curve rates P_a, P_m, and P_b are inverted with respect to the user inputs.


Note that since the y balance of the curve refers to the risk/base asset, the price quotes and token decimals must be converted to their reciprocals, which can be abstracted in the math as follows:


With these numbers in-hand, the same compression algorithm and rounding of the y and z values apply, exactly as before.

+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |               |
|            B parameter (decimal integer) | >>> 22290                                                                                                 |               |
|             B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |-significand-|                                                                                 |               |
|              B exponent (binary integer) | >>> 000000                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101011100010010                                                                                 | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 000000101011100010010                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa---|                                                                                 |               |
| compressed B parameter (decimal integer) | >>> 22290


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009 |               |
|            A parameter (decimal integer) | >>> 9233                                                                                                 |               |
|             A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |significand-|                                                                                 |               |
|              A exponent (binary integer) | >>> 000000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       10010000010001                                                                                 | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 00000010010000010001                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa--|                                                                                 |               |
| compressed A parameter (decimal integer) | >>> 9233


What should be noted here is that the A and B curve parameters are sufficiently small to fit entirely within the 48-bit memory allocation of the mantissa, and the compressed value is identical to the integer part of the input. In other words, the scaled-up value is stored as-is, without compression. This is true for all rate parameters on any token pair, smaller than 48-bits. While the precision loss associated with compression is forgone, this does not mean that the process is necessarily more precise. On the contrary, the discarded fractional component may represent a more significant source of error, as its relative size with respect to the integer being stored is orders of magnitude larger (which is partly why low decimals tokens such as wBTC and USDC are such a nuisance in exchange protocols).


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 400000000                                                                                             |
|                          | |||||||||                                                                                             |
| y fixed point (contract) | 400000000                                                                                             |
|                          |                                                                                                       |
|        z precise (maker) | 737215598.8403066345843944226900997996236027110871949096129123470537482453137996501292832036260560230 |
|                          | |||||||||                                                                                             |
| z fixed point (contract) | 737215598                                                                                             |
|                          |                                                                                                       |
|        A precise (maker) | 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009  |
|                          | ||||                                                                                                  |
| A fixed point (contract) | 9233                                                                                                  |
|  A compressed (contract) | 9233                                                                                                  |
|                          |                                                                                                       |
|        B precise (maker) | 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |
|                          | |||||                                                                                                 |
| B fixed point (contract) | 22290                                                                                                 |
|  B compressed (contract) | 22290


Note that after the data is processed and stored, the original inputs are unknowable to anyone except the maker who produced them. This is an important observation because it adds ambiguity to the precision measurement as it relates to trade outputs, and to what we should refer to as the “precise standard”. For completeness’ sake, this investigation will include two different measurements: one from the perspective of the maker’s arbitrarily precise inputs, and one with the assumption that the data stored on the contract is itself, arbitrarily precise. In other words, the result of a trade executed against the strategy as it exists on Ethereum will be compared to:

  • The same trade executed on a hypothetical system with unlimited memory and where tokens have infinite decimals, where the maker’s curve was created with perfect precision with respect to their inputs. That is, we will measure the error with respect to what the maker knows (referred to as maker-precise).

  • The same trade executed on the same hypothetical system as above, but where the maker’s inputs are substituted for the rounded and compressed/decompressed data of the fixed-point system. That is, we will measure the error with respect to what the smart contract knows (referred to as contract-precise).


Performing Exchange


From the perspective of the smart contract implementation, it is important to note that all rounding is performed in such a way as to always benefit the maker. That is, when the output of a trade function is rounded from its theoretical contract-precise value to an integer, it is always in the direction of a slightly less desirable exchange rate from the taker’s perspective. This approach annuls any attempt by an adversary to engineer an input whereby they might receive more than the theoretically allowed token amount.


There are two trade functions available on Carbon DeFi’s smart contracts:

  • tradeBySource (i.e. amount-out-given-amount-in) always returns a number less than or equal to the contract-precise theoretical amount.

  • tradeByTarget (i.e. amount-in-given-amount-out) always returns a number greater than or equal to the contract-precise theoretical amount.


The purpose of this analysis is to examine how and to what extent information is eroded between conceiving a trading strategy, communicating it to the Carbon DeFi contracts, and its execution. With that in mind, the perspective of the taker is not really within scope — nor should it be. Unlike the maker, the taker can read the information from the contracts and simulate the inputs and outputs with 100% precision. Having said this, I do consider the effective price differences on execution resulting from the choice of either tradeBySourceor tradeByTarget to be worth a considered analysis.


Trade by Source

Be reminded that the theory requires:

Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Which can be functionally composed such that each intermediate operation does not exceed the 256-bit EVM mandate:


The final trade value is then computed from:

Functional composition of the Trade by Source Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Source Function (3 of 3)


The 256-bit limitation for the uint256 data type necessitates careful arithmetic management to prevent overflow while maintaining calculation precision. The smart contract’s approach is designed to navigate these constraints effectively.


The architecture utilizes mulDivC functions in certain computations (f₃, f₄, f₆, and f₇) to permit intermediate values to momentarily surpass the 256-bit boundary, ensuring that while the numerator in these operations may exceed the limit, the final results conform to the uint256 range. The final step also utilizes a mulDivC function to compute the scaled-down C²z² term, and uses a utilizes a mulDivF function to compute the scaled-down Δ(Ay + Bz) term. This method balances precision with the technical limitations of Ethereum, adopting a strategic approach to expression refactoring when necessary.


The computation of the final trade value, Δy, reflects Carbon DeFi’s defensive programming: it underestimates the numerator and overestimates the denominator, and the final division utilizes a mulDivF function to round the result towards zero. This tactic intentionally biases rounding errors in favor of the maker, a measure taken to prevent giving undue advantage to the taker owing to calculation imprecision. This choice underscores a principle of transaction security and fairness innate to Carbon DeFi’s implementation.


Overall, the contract demonstrates a balance between retaining calculation precision and preventing overflow, while adhering to Ethereum’s specifications and ensuring transaction integrity. By carefully managing arithmetic operations, the contract maintains a high degree of accuracy and reliability of its core functions without venturing out-of-bounds.


Returning to the example above, where the maker has offered SHIB tokens in exchange for wBTC tokens, we can simulate an exchange to reveal the implemented fixed-point precision result, and measure the relative error compared to the hypothetical maker-precise, and contract-precise outputs. For this example, assume that the taker wishes to send 0.32100123 wBTC tokens against the maker’s order, in return for the appropriate number of SHIB tokens as dictated by the general formula. The fixed-point smart contract implementation returns 1,654,355,822.074328684134994879 SHIB tokens, which is within 2.481523 parts per Quadrillion of the maker’s arbitrarily precise expectation, and within 26.631197 parts per Octillion of the arbitrarily precise result referencing only the data available stored on the contract itself. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, meaning the taker trades at a slight disadvantage.


Function Called: trade_by_source

Trade Input (Token Wei): 32100123 wBTC (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822074332789456944072.627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||||||:::|::::|:|:::.::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822074328684134994923.057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | |||||||||||||||||||||||||:::                                                                          |
| Fixed-point Precision (Contract) | 1654355822074328684134994879                                                                          |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.32100123 wBTC

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822.074332789456944072627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||.||||:::|::::|:|:::::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822.074328684134994923057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | ||||||||||.|||||||||||||||:::                                                                         |
| Fixed-point Precision (Contract) | 1654355822.074328684134994879                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 2.481523 parts per Quadrillion
Fixed-Point vs Arbitrary Precision (Contract): 26.631197 parts per Octillion


Now, referring to the opposite order within the strategy, where the maker has offered wBTC tokens in exchange for SHIB tokens, assume that the taker wishes to exchange 10,000,000 SHIB the appropriate number of wBTC tokens as dictated by the general formula. The fixed-point smart contract implementation returns 0.00094062 wBTC tokens, which is within 60.433641 parts per Million of the maker’s arbitrarily precise expectation, and within 4.523412 parts per Million with respect to the contract data. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, which is the desired behavior.


Function Called: trade_by_source

Trade Input (Token Wei): 10000000000000000000000000 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 94067.68485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | ||||:.:::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::|: |
|   Arbitrary Precision (Contract) | 94062.42548314780701261249837916821935266674645663587209179344662617977680268270640530830378232720800 |
|                                  | |||||                                                                                                 |
| Fixed-point Precision (Contract) | 94062                                                                                                 |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 10000000 SHIB

+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                         |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.0009406768485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | |.|||||||::::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::   |
|   Arbitrary Precision (Contract) | 0.00094062425483147807012612498379168219352666746456635872091793446626179776802682706405308303782327208   |
|                                  | |.||||||||                                                                                                |
| Fixed-point Precision (Contract) | 0.00094062                                                                                                |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 60.433641 parts per Million
Fixed-Point vs Arbitrary Precision (Contract): 4.523412 parts per Million


Trade by Target


Be reminded that the theory requires:

Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Functional composition of the Trade by Target Function (1 of 3)


The final trade value is then computed from:

Functional composition of the Trade by Target Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Target Function (3 of 3)


In the computation of Δx, the implementation approach diverges from that of Δin a critical aspect: the direction of rounding errors. This difference is a consequence of the reciprocal nature of these two functions. For Δx, which calculates the quantity of tokens a trader must send to the contract, the approach intentionally overestimates the numerator and underestimates the denominator, with the overall result being rounded up. This method is a deliberate inversion of the error biases applied in the Δcalculation.


This inversion is necessary to align the contract’s defensive programming with the transaction’s direction — ensuring that any rounding errors do not unjustly penalize the maker by requiring them to overpay. By overestimating the numerator and underestimating the denominator in the final computation, and rounding the result up, the contract aims to slightly favor the maker in the presence of calculation imprecision. Whereas the taker, who can observe the trade result prior to committing to the transaction, still get exactly the exchange rate they expected. This careful adjustment ensures that the contract remains equitable and avoids inadvertently disadvantaging users, maintaining fairness, transparency and integrity in all transactions that occur between users of the protocol.


Revisiting again the scenario where SHIB tokens are offered by the maker in exchange for wBTC tokens, let us undertake another exchange simulation to measure the precision loss of the fixed-point arithmetic, but this time using tradebyTarget. Consider a situation where the taker’s goal is to secure 1,654,355,822.074328684134994879 SHIB tokens from the maker’s market, meaning she queries how many wBTC tokens must be relinquished from her wallet to the maker’s order to complete the trade. The smart contract’s fixed-point precision calculation determines that 0.32100123 wBTC tokens are needed. This outcome reveals a difference of only 2.559216 parts per Quadrillion with respect to the maker’s exact calculations, and a difference of only 27.464979 parts per Octillion while taking the curve parametrization on the smart contract at face-value. Notably, the amount of wBTC tokens calculated for the exchange slightly overshoots the arbitrarily precise amount, subtly disadvantaging the taker, akin to the earlier observations.


Function Called: trade_by_target

Trade Input (Token Wei): 1654355822074328684134994879 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 32100122.99999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | ||||||||.|||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 32100122.99999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |||||||:                                                                                              |
| Fixed-point Precision (Contract) | 32100123                                                                                              |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 1654355822.074328684134994879 SHIB

+----------------------------------+--------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                      |
+----------------------------------+--------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.3210012299999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | |.|||||||||||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 0.3210012299999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |.|||||||:                                                                                             |
| Fixed-point Precision (Contract) | 0.32100123                                                                                             |
+----------------------------------+--------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 2.559216 parts per Quadrillion
Fixed-Point vs Arbitrary Precision (Contract): 27.464979 parts per Octillion


Lastly, and again turning our attention to the opposite order, where wBTC tokens are offered from the maker in exchange for SHIB tokens, consider a scenario where the taker aims to acquire 0.00094062 wBTC tokens using SHIB tokens. The taker requests the quantity of SHIB tokens that should be sent to the contract; in response, the smart contract’s fixed-point execution returns that 9,999,954.763923315127815869 SHIB tokens are needed. This result demonstrates a discrepancy of 55.916021 parts per Million from what was anticipated by the maker’s high-precision calculation and deviates by 40.578300 parts per Octillion when aligned against the contract data assuming arbitrary runtime resources. Consistent with prior examples, the computed SHIB token quantity for the trade is marginally greater than both precise evaluations, subtly positioning the taker at a slight disadvantage.


Function Called: trade_by_target

Trade Input (Token Wei): 94062 wBTC (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 9999395637511301625506302.622771540656487995730800965380663671805155126785782213292248238466542297775 |
|                                  | ||||:::::::::|:::|:::::::.:::::::::::::::::::::::|::::::|:::::|:::::::::::::::|::|:::::::::|::|:::::: |
|   Arbitrary Precision (Contract) | 9999954763923315127815868.594218836892670162259140677147615505874263278292550311194597366596202611817 |
|                                  | ||||||||||||||||||||||||:                                                                             |
| Fixed-point Precision (Contract) | 9999954763923315127815869                                                                             |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.00094062 wBTC

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 9999395.637511301625506302622771540656487995730800965380663671805155126785782213292248238466542297775 |
|                                  | ||||:::.::::::|:::|::::::::::::::::::::::::::::::|::::::|:::::|:::::::::::::::|::|:::::::::|::|:::::: |
|   Arbitrary Precision (Contract) | 9999954.763923315127815868594218836892670162259140677147615505874263278292550311194597366596202611817 |
|                                  | |||||||.|||||||||||||||||:                                                                            |
| Fixed-point Precision (Contract) | 9999954.763923315127815869                                                                            |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 55.916021 parts per Million
Fixed-Point vs Arbitrary Precision (Contract): 40.578300 parts per Octillion



Testing to Failure


What I have tried to demonstrate above is that Carbon DeFi’s implementation can handle even the most extreme of token exchange rates, such as that exhibited by high-decimals, relatively worthless tokens such as Shiba Inu, versus low-decimals, exceedingly valuable tokens such as wBTC. This begs the question as to where the limits of reasonable behavior are. Any system based on finite computer resources will have a limited scope of precision and accuracy, and it is a worthwhile endeavor to discover those boundaries.


This is not as easy as it sounds.


The parametrization of Carbon DeFi’s bonding curves is four-dimensional (AByz), and while trade inputs are only one-dimension, there are two different kinds:tradeBySource (Δx) and tradeByTarget (Δy), which makes for a six-dimensional hypervolume of possible input space. Accounting for the data compression and storage, the upper bound on the rate parameter is 281,474,976,710,655 × 2⁴⁸, which in terms of price quoting and/or range width is 79,228,162,514,263,774,643,590,529,025 wei/wei units. Taken together, the input space is absurdly large — but it can be meaningfully searched.


When Carbon DeFi was still in its gestational stage, I developed a suite of analysis tools to systematically map the input space (terabytes of data) and report problematic regions requiring further attention. Among its goals are to characterize how precision and accuracy change as a function of the inputs, and to discover situations where the effects of rounding have not had the desired result (i.e. the taker has an advantage over the maker). Fortunately, the only examples that exist for reversal of the rounding error benefit are nonsensical. Regardless, these families of input types are worth acknowledging and characterizing. The following example is representative of the set.


          risk/base token = ABCD
 risk/base token decimals = 18
         cash/quote token = WXYZ
cash/quote token decimals = 18
               order type = "buy ABCD with WXYZ"

  lowestRate = 4.0000000000000000000000000000000000000000000000000000000000000000000000000000000000
 highestRate = 4.0000000000000000000000000000000001925929944387235792646606569178402423858642578125
marginalRate = 4.0000000000000000000000000000000001925929944387235792646606569178402423858642578125
   liquidity = 1


The first thing to notice is that the difference between the input rates is very small — beneath the resolution that can be achieved after compression/decompression of the exchange range width parameter, A. Therefore, it rounds to zero. There is no such issue for the B parameter.


+------------------------------------------+-----------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                       | Note          |
+------------------------------------------+-----------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 1.35525271560688049999999999999999998368673883000368970659882225079E-20 |               |
|            A parameter (decimal integer) | >>> 0                                                                       |               |
|             A parameter (binary integer) | >>>       0                                                                 | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       0                                                                 | (max 96 bits) |
|                                          | >>>                                                                         |               |
|                                          | >>>       |significand|                                                     |               |
|              A exponent (binary integer) | >>> 000000                                                                  | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       0                                                                 | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 000000                                                                  | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑                                                                  |               |
|                                          | >>> |expt||mantissa|                                                        |               |
| compressed A parameter (decimal integer) | >>> 0


+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 562949953421312.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |               |
|            B parameter (decimal integer) | >>> 562949953421312                                                                                       |               |
|             B parameter (binary integer) | >>>       10000000000000000000000000000000000000000000000000                                              | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       10000000000000000000000000000000000000000000000000                                              | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑                                              |               |
|                                          | >>>       |-----------------significand------------------||trailing-zeroes|                               |               |
|              B exponent (binary integer) | >>> 000010                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       100000000000000000000000000000000000000000000000                                                | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 000010100000000000000000000000000000000000000000000000                                                | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                                |               |
| compressed B parameter (decimal integer) | >>> 703687441776640


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 1000000000000000000                                                                                   |
|                          | |||||||||||||||||||                                                                                   |
| y fixed point (contract) | 1000000000000000000                                                                                   |
|                          |                                                                                                       |
|        z precise (maker) | 1000000000000000000                                                                                   |
|                          | |||||||||||||||||||                                                                                   |
| z fixed point (contract) | 1000000000000000000                                                                                   |
|                          |                                                                                                       |
|        A precise (maker) | 1.35525271560688049999999999999999998368673883000368970659882225079E-20                               |
|                          | :                                                                                                     |
| A fixed point (contract) | 0                                                                                                     |
|  A compressed (contract) | 0                                                                                                     |
|                          |                                                                                                       |
|        B precise (maker) | 562949953421312.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
|                          | |||||||||||||||                                                                                       |
| B fixed point (contract) | 562949953421312                                                                                       |
|  B compressed (contract) | 703687441776640


WhentradeBySourceis called with its theoretical maximum input (with respect to the contract variables), 0.25 ABCD, there is nothing remarkable about the output:


Function Called: trade_by_source

Trade Input (Token Wei): 250000000000000000 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1000000000000000000.000000000000000024074124304840447408082582114730029428887840659198167255476383850 |
|                                  | |||||||||||||||||||                                                                                   |
|   Arbitrary Precision (Contract) | 1000000000000000000                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 1000000000000000000                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.25 ABCD

+----------------------------------+------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                    |
+----------------------------------+------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.00000000000000000000000000000000002407412430484044740808258211473002942888784065919816725547638385 |
|                                  | |                                                                                                    |
|   Arbitrary Precision (Contract) | 1                                                                                                    |
|                                  | |                                                                                                    |
| Fixed-point Precision (Contract) | 1                                                                                                    |
+----------------------------------+------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 24.074124 parts per Undecillion
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


Exceeding the maximum input is impracticable, as the maker’s strategy will simply not have any more tokens to give, and the transaction will revert. Save for the fact that it is completely meaningless to do so, continuing to increase thetradeBySourceinput amount up to exactly 2× its theoretical limit reveals something unexpected. First, examine the outcome at some negligible amount smaller than 2×.


Function Called: trade_by_source

Trade Input (Token Wei): 499999999999999999 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1999999999999999996.000000000000000000000000000000000192592994438723577720347745950573968894003767416 |
|                                  | |||||||||||||||||||                                                                                   |
|   Arbitrary Precision (Contract) | 1999999999999999996                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 1999999999999999996                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.4999999999999999999999999999999999999999999999999999999999999999999999999999999999999 ABCD

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.999999999999999996000000000000000000000000000000000192592994438723577720347745950573968894003767416 |
|                                  | |.||||||||||||||||||                                                                                  |
|   Arbitrary Precision (Contract) | 1.999999999999999996                                                                                  |
|                                  | |.||||||||||||||||||                                                                                  |
| Fixed-point Precision (Contract) | 1.999999999999999996                                                                                  |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 96.296497 parts per Septendecillion
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


The error is significantly reduced compared to the maker-precise result, and the rounding is still in the right direction. Now, increasing by an amount so small I would consider it meaningless if I didn’t already know the outcome:


Function Called: trade_by_source

Trade Input (Token Wei): 500000000000000000 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1999999999999999999.999999999999999999999999999999999999999999999999998840873077910180889673968511799 |
|                                  | :::::::::::::::::::                                                                                   |
|   Arbitrary Precision (Contract) | 2000000000000000000                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 2000000000000000000                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.5 ABCD

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.999999999999999999999999999999999999999999999999999999999999999999998840873077910180889673968511799 |
|                                  | :                                                                                                     |
|   Arbitrary Precision (Contract) | 2                                                                                                     |
|                                  | |                                                                                                     |
| Fixed-point Precision (Contract) | 2                                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): Negligible error
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


The error is now so small as to have become nearly immeasurable. More importantly, the rounding is now in favor of the taker compared to the maker’s precise calculations; compared to the contract variables, there is no difference. As it happens, this is a general phenomenon. There exists an input space where, if the range width is sufficiently narrow so as to round to nothing on the contracts and we ignore the upper bound on the tradeBySourceinput, it is possible to achieve a rounding error that the maker would argue is in the wrong direction. Fortuitously this phenomenon is contained to nonsense inputs, and so has no bearing on the real-world functioning of Carbon DeFi’s Smart contracts. I raise it here for the benefit of academic curiosity, and in the hope that it may shed light on other unexpected behaviors that may affect other systems designs.


Conclusion


The exploration into Carbon DeFi’s fixed-point arithmetic and its impact on trade precision reveals both the challenges and achievements in supporting financial models within the constraints of blockchain technology. Through careful design and strategic implementation, Carbon DeFi navigates the inherent limitations of fixed-point computation, ensuring that the protocol remains equitable for both makers and takers. This study underscores the importance of precision in decentralized finance, illustrating that even minute discrepancies can have tangible effects on trading outcomes. As blockchain technology evolves, so too will the methodologies to optimize precision and efficiency in DeFi protocols, further bridging the gap between theory and execution.

My approach to de novo protocol design begins with establishing a theoretical framework. I assume this is common, but I am reluctant to speak for other designers. Speaking only to my experience, this phase of the development process invariably relies on pure algebraic abstraction. The goal is to achieve an exhaustive mathematical description of the protocol’s core methods such that its functionality can be demonstrated symbolically. It is wonderful to arrive at a proven (or, at the very least provable) source of truth with respect to what the expected outcomes are for any conceivable interaction with a newly imagined system. However, as a veteran experimentalist I understand better than most the difference between pen-and-paper theory and the limits of physical reality. For protocol design this is the difference between the immaculate mathematical abstraction of the system and its implementation under the limitations of its host blockchain. With only finite memory and compute cycles at hand, the challenge is to create the best possible approximation of the model while respecting the boundaries of what the network can realistically support. This is no small task. I am very fortunate to be working with talented embedded systems engineers, including and especially Barak Manos, with whom I can collaborate to thread this specific needle.


Carbon DeFi in Brief


Carbon DeFi is a token exchange protocol, made to resemble a conventional orderbook in some respects, without completely turning its back on its AMM heritage. Fittingly, the system variables are those required to encapsulate a singular, or series of user-nominated exchange rates and token balances, which are then broadcast to other market participants who may elect to take the tokens and prices on offer. As is customary in DeFi, the exchange is formalized with an invariant function, from which its price quoting and swap functions can be derived:


Invariant Function


Price Function


Trade by Source Function (i.e. output amount, given the input amount)


Trade by Target Function (i.e. input amount, given the output amount)



The ab, and z terms are constants that describe the relative curvature and size of the bonding curve; z denotes the y-intercept, and a and b are derived from the two price bounds of the range.


Exchange Rate Parameter Definitions


In the equations above, (xy) represent cartesian coordinates for a point that lies on the bonding curve, (Δx, Δy) represent token quantities corresponding to a translocation upon the same curve, and Δand Δhave opposite signs. By convention, the swapped token amounts are expressed as unsigned integers, Δ≥ 0 and Δ≥ 0. In other words, we adopt a moving frame of reference, such that Δrepresents a number of tokens sent from the market participant to the contract, and Δrepresents a number of tokens sent from the contract to the market participant:

  • Following a token exchange, the contract balance of x is x + Δx.

  • Following a token exchange, the contract balance of y is y − Δy.


Consequently, the exchange rates for any curve inherit this convention; the exchange rate, ∂y/∂xis the drop in the y-balance with respect to some input amount as it approaches zero (Δ→ 0). The benefit here is that rates are quoted as positive (rather than negative) numbers. While technically incorrect with respect to the invariant, these conventions support standard financial intuition. However, it is important to recognize that this formulation is necessarily numeraire-agnostic, as the y-axis may represent either the “cash” (or “quote”), or “risk” (or “base”) asset. In cases where the y-axis refers to a common numeraire, such as a $USD stablecoin, the price quote is intuitive: ∂y/∂xhas units of $USD per risk token. However, in cases where the y-axis refers to the risk asset the price quote is inverted: ∂y/∂has units of risk token per $USD.


For example, a curve which represents bidding liquidity denominated in USDC for the ETH markets beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH has intuitive units, and the depletion of the USDC balance on the curve is commensurate with receding bidding prices. Suppose that the curve was initialized with 10,000 USDC of which 5,000 USDC remains. Through application of the price function, the marginal highest bid on the curve, P_m, is found to be ~1,995 USDC per ETH. Whereas P_m is the marginal rate corresponding to the current y balance of the curve, P_a and P_b refer to the marginal rates when y = z, and y = 0, respectively.


Figure 1: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC.


Consider now how a curve representing asking liquidity might look compared to the bidding liquidity example, above. As per the conventions the curve which offers ETH is necessarily denominated in ETH, and its contract balance is represented on the y-axis. Therefore, the output of the price function (and the variables P_a, P_b, and P_m) are reported in units of ETH per USDC. This can be a little jarring. For example, the curve which offers ETH at a rate beginning at 2,500 USDC per ETH and ending at 3,000 USDC per ETH must use the reciprocals of these values. Suppose that the curve was initialized with 10 ETH, of which 5 ETH remains. The price function returns the marginal lowest ask on the curve, P_m, as 3.66 × 10⁻⁴ ETH per USDC; as before, P_a and P_b refer to the marginal rates when y = z, and y = 0, which are 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) and 3.33 × 10⁻⁴ (i.e. 3,000⁻¹) ETH per USDC, respectively. Note that this arrangement still exhibits the desired behavior; as the ETH balance is depleted, the reciprocal price of ETH is diminished, which is commensurate with higher asking prices.


Figure 2: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH.


If the reader is appropriately motivated, the significance of this parametrization is elaborated in the Carbon DeFi whitepaperlitepaper, and invention disclosure. For the present discussion, assume that the user provides all three marginal rates P_a, P_b, and P_m as inputs, along with the token quantity, y, from which the z value must be calculated. In almost all cases, the user elects to have P_m = P_a at the time the curve is initialized, in which case z = y; however, this is not a requirement.

Curve Size Parameter Definition


Token Decimals


For the benefit of the uninitiated, a single token is not what it first appears. Tokens have a property which determines its divisibility, called its “token decimals”. For example, Bitcoin’s decimals are 8, meaning that a single BTC unit is defined as comprising a set of 10⁸ individual parts called “satoshis” or “sats”. Ether’s decimals are 18, meaning that a single ETH unit comprises 10¹⁸ parts called “wei”. Circle’s USD stablecoin uses only 6 decimals, meaning that a single USDC unit comprises 10⁶ parts (also called wei), and so on.


From the perspective of the exchange system, these smallest indivisible parts are all that matter, and exchange rates are unavoidably expressed in these terms. For example, suppose that 20 ETH is equal in value to 1 BTC (e.g. ETH/USD = $2,500; BTC/USD = $50,000). At the contract level, this translates to 20 × 10¹⁰ ETH wei is equal in value to 1 BTC sat. Since 1 BTC sat is indivisible, BTC-to-ETH exchange is necessarily [for lack of a better word] “chunky”. This forms the basis for further erosion of the perceived accuracy of the system; however, this would be unavoidable even with unlimited storage and computer power.


To demonstrate how token decimals impact the data available on the smart contracts, the two bonding curves presented above have been recreated here, but this time accounting for the difference in the token decimals. The curve representing bidding liquidity on ETH denominated in USDC, beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH, initialized with 10,000 USDC, of which 5,000 USDC remains, is now revealed to have a tiny fraction of the naïve exchange rate. At the wei level, the marginal price is no longer ~1,995 USDC per ETH, but 1.995 × 10⁻⁹ USDC wei per ETH wei.


Figure 3: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC, represented at the wei scale.


The curve representing asking liquidity for ETH denominated in USDC, beginning at which are 4.00 × 10⁻⁴ (2,500⁻¹) USDC per ETH and ending at 3.33 × 10⁻⁴ (3,000⁻¹) USDC per ETH, initialized with 10 ETH, of which 5 ETH remains, is revealed to exhibit a substantially more aggressive exchange profile compared to the naïve rate. At the wei level, the marginal price is no longer 3.66 × 10⁻⁴ ETH per USDC, but rather 3.66 × 10⁸ ETH wei per USDC wei.


Figure 4: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH, represented at the wei scale.


Differences in token decimals can alter the effective exchange rates by several orders of magnitude. This presents an interesting challenge, as even common token pairs (e.g. ETH/USDC) can result in exchange rates one might have otherwise concluded were sufficiently unusual to justify ignoring. Unfortunately, no — the scope of the expected input space really is so vast as to include numbers both vanishingly small, and impenetrably large. The challenge is to define the boundaries of what is reasonable and ensure that calculations performed at any point within these bounds result in outputs that closely adhere to the theory, while remaining mindful of the limited computer resources of the network.


The Scaling Constant


Fixed point systems only know about integers. For an exchange protocol based on theory that is so thoroughly entrenched in the real numbers, this is a problem. To illustrate, consider that any exchange rate less than one rounds to zero. Given the reciprocal nature of the bidding and asking prices, about half of all exchange rates will default to zero. For example, the value 2/3 cannot be stored in a fixed-point system; however, it can be recreated in-situ by storing the numerator and denominator discretely, (2, 3), and its parts can be summoned when performing further mathematical operations when needed. The approach implemented by Carbon DeFi is fundamentally the same, save for the fact that the in-situ recreation of the original fraction is abstracted algebraically.


Carbon DeFi multiplies the rate variables, a and b, by a large constant, C, and records the integer part as the contract variables A and B (n.b. the capitalization is intended to symbolically capture their meaning). Implicitly, these variables represent the numerators of the fractions a = A/C and b = B/C. To recreate a and b, the general formulae are appropriately modified to compensate for the implied denominator, C, which compensates for the scaling-up in A and B and allows these contract variables to be used directly during exchange calculations.


Exchange Rate Parameter Definitions (Scaling Corrected)


Invariant Function (Scaling Corrected)


Price Function (Scaling Corrected)


Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Curve Size Parameter Definition (Scaling Corrected)


Data Storage


The strategy data is organized into three slots:

  • Slot #1: Contains the y_bid and y_ask values. Each value has 112 bits of allocated memory.

  • Slots #2 and #3: Contains the z_bid, A_bid, and B_bid, and the z_ask, A_ask, and B_ask values, and each slot is exclusive to either the bidding or asking curve. The z_bid and z_ask values have the same 112 bits of allocated memory as the y_bid and y_ask values in Slot #1. The A_bid, B_bid, A_ask, and B_ask values have 54 bits of allocated memory apiece, of which the first 48 bits is reserved for the mantissa (or “significand”) and the remaining 6 bits are reserved for the exponent.

  • Unused Bits: The y_bid, y_ask, z_bid and z_ask values contain a 16-bit appendix, and the A_bid, B_bid, A_ask, and B_ask values contain a 10-bit appendix of unutilized memory. These spaces are envisioned to support other features of the system in the future. Being that this has no bearing on the present discussion, these appendices can safely be ignored.


This arrangement allows the write operations to be restricted to only one slot in most cases. A trade executed against a strategy necessarily results in changes to both the y_bid and y_ask liquidity values in Slot #1. On occasion, if one of the y_bid or y_ask values exceed its corresponding z_bid or z_ask value, then a second write operation is required to reset the appropriate value in Slot #2 or #3. Therefore, trades executed against a strategy will require data to be overwritten in at most two slots in memory.


Figure 5: Carbon DeFi strategy data storage and organization


Smart Contract Implementation, Fixed-Point Precision, and Relative Error


The core object of the Carbon DeFi protocol is called a strategy, which comprises two different orders represented by two different bonding curves, each representing one of the bid/ask pair. Someone who is quoting the bid and ask prices is called a maker, and the market participant who agrees to an exchange at the quoted price is the taker. There are two types of user-protocol interaction where numeric precision should be scrutinized:

  1. During strategy creation or editing an existing strategy, wherein the strategy’s owner (i.e. the maker) may provide price inputs of arbitrary precision (highestRate, lowestRate, marginalRate) which must then be transformed into fixed-point format and recorded to the smart contracts.

  2. During a trade, wherein an outside market participant (i.e. the taker) calls one of the two available trade functions and sends an amount of the source token to the contract in exchange for the target token.



Figure 6: The two points of user-protocol interaction where precision loss occurs.


Strategy Creation and Editing


The maker’s purpose [one hopes] is conceived off-chain, where they may imagine an infinitely precise pair of bonding curves that represent their market outlook. Each curve’s parametrization can be described and communicated entirely by the three marginal rates P_a, P_b, and P_m, and the number of tokens, y, to be distributed across each curve. Once received, this data must be processed into the variables AByz. While these variables may also be infinitely precise with respect to the maker’s imagined strategy, their on-chain representation is limited by the storage allocated to each value. Therefore, this process of collecting and processing the maker’s inputs, and their truncation to fit the storage requirements of the blockchain is an important source of precision loss.


To understand how this looks in practice, consider an arbitrary set of inputs received from the maker during strategy creation: thehighestRate, lowestRate, marginalRate, and the liquidityvalues. The context is also important. If the maker’s ambition is to buy the risk/base asset, then the liquidity value refers to the cash/quote asset and the exchange rates can be used verbatim; whereas if the maker intends to sell the risk/base asset, then the liquidity value refers to the risk/base asset itself and the exchange rates must be inverted. In both cases, the token decimals and compression of the curve parameters A and B is worth close inspection.


One of the most challenging pairs in all of DeFi from a precision perspective is Shiba Inu (SHIB, a relatively worthless 18 decimals token) versus wrapped Bitcoin (wBTC, an extremely valuable, 8 decimals token). At the time of writing, the SHIB/USD rate is $0.0₅9756 ($9.756 × 10⁻⁶) and the wBTC/USD rate is $ 51,854.04 ($5.185404 × 10⁴). Therefore, the current wBTC/SHIB market rate is 5,315,092,250.92251 (5.31509225092251 × 10⁹).


Suppose that a user wants to create a direct market for this pair at the current market rate, offering to buy wBTC with SHIB as the price of SHIB improves in value versus wBTC, with a market depth of 10 billion SHIB tokens or approximately $97,560 USD equivalent value. The following hypothetical inputs represent such a view, and the maker’s arbitrarily precise bonding curve at the wei scale is depicted in Figure 7. Note that we adopt the perspective here that the maker’s off-chain inputs are in the familiar non-wei format, and that the decimal corrections must be applied as part of the input processing.


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “buy wBTC with SHIB”

  lowestRate = 3,543,394,833.948345819174724191772607317991320232065325065623989176702584205824065767190258
 highestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
marginalRate = 5,315,092,250.922518728762086287658910976986980348097987598435983765053876308736098650785387
   liquidity = 10,000,000,000

Figure 7: Invariant and Price Curves for lowestRate = 3,543,394,833.9483… SHIB per wBTC, highestRate = 7,972,638,376.3837… SHIB per wBTC, y = 10,000,000,000 SHIB and z = 22,247,448,713.9158… SHIB at the wei scale.


Here we can examine the translation of the user’s inputs into the curve parameters, and their compression prior to storage. Since this curve has the cash/quote token (SHIB) as its own balance, the rates are used verbatim (i.e. that is, not their reciprocal values), and only adjusted for the decimals of the pair. The sheer size of the result is especially pronounced due to the enormous difference in the token values, which is further amplified by their decimals and the scaling constant.


The off-chain compression algorithm then converts the rate parameter to an integer and normalizes it by the scaling constant to determine its bit length for truncation. Truncation occurs by bitwise shifting, removing insignificant bits (duh!). The exponent, indicating the value’s scale, is extracted from the truncated value’s bit length. The mantissa is isolated by right-shifting this value by the exponent. Finally, the mantissa and scaled exponent are combined with a bitwise OR, producing a compressed binary representation optimized for minimal storage.


In other words, (referring only to the integer part of the binary form of the result) the significand is defined as the first 48 digits, which is extracted to be used as the mantissa, and the remaining digits are replaced with zeroes. Then, the trailing zeroes after the 48th digit are counted; the count, expressed as a 6-bit binary number, is extracted as the exponent. The compressed value is then stored as the concatenation of the exponent and the mantissa, in that order.


I am sincerely hopeful that one of the above descriptions is helpful. Failing that, I am providing the following code snippet from the class I use to perform these analyses, as well as the plain text tables it can produce, which represent each step of the compression sequence for the wBTC/SHIB curve presently under examination for both its B and A parameters.


def compress_rate_parameter(
    self,
    parameter_name: str, 
    print_process_summary: bool = True
    ) -> int:
    """
    ### Compresses a rate parameter for storage in blockchain environments.

    ## Parameters:
    | Parameter Name          | Type      | Description                                                                           |
    |-------------------------|-----------|---------------------------------------------------------------------------------------|
    | `parameter_name`        | `str`     | The name of the parameter to be compressed.                                           |
    | `print_process_summary` | `bool`    | Whether to print a detailed summary of the compression process (default is `True`).   |

    ## Returns:
    | Return Name                | Type  | Description                                                                      |
    |----------------------------|-------|----------------------------------------------------------------------------------|
    | `compressed_rate_parameter`| `int` | The compressed form of the rate parameter, optimized for blockchain storage.     |

    ## Notes:
    - This method reduces the size of rate parameters, making them suitable for efficient storage and processing on blockchain platforms.
    - The compression process involves:
        1. Converting the parameter to an integer, if not already.
        2. Truncating any trailing zeroes to minimize the parameter's size.
        3. Extracting an exponent representing the scale of truncation.
        4. Identifying the mantissa as the significant part of the parameter.
        5. Combining the exponent and mantissa into a single integer.
    - Optionally, a detailed visual summary of the compression process can be printed, illustrating the transformation of the parameter into its compressed form.
    - This method is crucial for understanding the use of smart contract storage, reducing gas costs, and maintaining acceptable accuracy.
    """
    rate_parameter_int = int(self.maker_precise_curve_parameters[parameter_name])
    number_of_bits = (rate_parameter_int // self.scaling_constant_int).bit_length()
    truncated_rate_parameter = (rate_parameter_int >> number_of_bits) << number_of_bits
    exponent = (truncated_rate_parameter // self.scaling_constant_int).bit_length()
    mantissa = truncated_rate_parameter >> exponent
    compressed_rate_parameter = mantissa | (exponent * self.scaling_constant_int)
    if print_process_summary:
        self.print_compression_process_summary(parameter_name,
                                               rate_parameter_int,
                                               truncated_rate_parameter,
                                               exponent,
                                               mantissa,
                                               compressed_rate_parameter)
    return compressed_rate_parameter


+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |               |
|            B parameter (decimal integer) | >>> 1675519805183645805377564                                                                             |               |
|             B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001001111111110010101111100000011100               | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001000000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||--------trailing-zeroes--------|               |               |
|              B exponent (binary integer) | >>> 100001                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                                | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 100001101100010110011100001110001010010111110100010001                                                | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                                |               |
| compressed B parameter (decimal integer) | >>> 9483730408799505


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554 |               |
|            A parameter (decimal integer) | >>> 837759902591822902688782                                                                             |               |
|             A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100111111111001010111110000001110               | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||-------trailing-zeroes--------|               |               |
|              A exponent (binary integer) | >>> 100000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                               | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 100000101100010110011100001110001010010111110100010001                                               | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                               |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                               |               |
| compressed A parameter (decimal integer) | >>> 9202255432088849


In addition to the compression of these curve parameters, only the integer parts of the y and z values are stored. While this is obvious for y, which represents the SHIB token balance of the curve (in wei units), it is still worth raising as the maker be unaware of the decimals of the token they are using and provide an off-chain input that implies divisibility beyond that supported by the token contract (e.g. wBTC beyond the 8th decimal place). While z has an explicit correspondence with y and must also be an integer, it is also computed from the marginal rate user input. Therefore, its rounding will also have an impact on the overall precision of the system — albeit a negligible one. The error caused by rounding the y and values and resulting from a compression/decompression cycle of the A and B values can be examined directly versus the maker’s arbitrarily precise inputs by comparing the digits in-place.


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 10000000000000000000000000000                                                                         |
|                          | |||||||||||||||||||||||||||||                                                                         |
| y fixed point (contract) | 10000000000000000000000000000                                                                         |
|                          |                                                                                                       |
|        z precise (maker) | 22247448713915890490986420373.52945695982973740328335064216346283625480188728657513269929716552320115 |
|                          | |||||||||||||||||||||||||||||                                                                         |
| z fixed point (contract) | 22247448713915890490986420373                                                                         |
|                          |                                                                                                       |
|        A precise (maker) | 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554  |
|                          | ||||||||||||||::::||:::|                                                                              |
| A fixed point (contract) | 837759902591821830684672                                                                              |
|  A compressed (contract) | 9202255432088849                                                                                      |
|                          |                                                                                                       |
|        B precise (maker) | 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |
|                          | |||||||||||||||::::|::::|                                                                             |
| B fixed point (contract) | 1675519805183643661369344                                                                             |
|  B compressed (contract) | 9483730408799505


Be reminded that a Carbon DeFi strategy is composed of two bonding curves, each taking a different side of the market. It is worthwhile to consider the opposite order type — one which offers wBTC liquidity in exchange for SHIB as the price of wBTC appreciates relative to SHIB — because it represents the opposite extreme of the curve parametrization.


Suppose that the maker includes the following order as part of their strategy, representing the opposite side of the market with a depth of 4 wBTC tokens or approximately $207,416 USD equivalent value:


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “sell wBTC for SHIB”

  lowestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
 highestRate = 15,945,276,752.76755618628625886297673293096094104429396279530795129516162892620829595235616
marginalRate = 10,630,184,501.84503745752417257531782195397396069619597519687196753010775261747219730157077
   liquidity = 4


Figure 7: Invariant and Price Curves for lowestRate = 7,972,638,376.3837… SHIB per wBTC, highestRate = 15,945,276,752.7675… SHIB per wBTC, y = 4 wBTC and z = 7.3721… wBTC at the wei scale. Note that the curve rates P_a, P_m, and P_b are inverted with respect to the user inputs.


Note that since the y balance of the curve refers to the risk/base asset, the price quotes and token decimals must be converted to their reciprocals, which can be abstracted in the math as follows:


With these numbers in-hand, the same compression algorithm and rounding of the y and z values apply, exactly as before.

+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |               |
|            B parameter (decimal integer) | >>> 22290                                                                                                 |               |
|             B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |-significand-|                                                                                 |               |
|              B exponent (binary integer) | >>> 000000                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101011100010010                                                                                 | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 000000101011100010010                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa---|                                                                                 |               |
| compressed B parameter (decimal integer) | >>> 22290


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009 |               |
|            A parameter (decimal integer) | >>> 9233                                                                                                 |               |
|             A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |significand-|                                                                                 |               |
|              A exponent (binary integer) | >>> 000000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       10010000010001                                                                                 | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 00000010010000010001                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa--|                                                                                 |               |
| compressed A parameter (decimal integer) | >>> 9233


What should be noted here is that the A and B curve parameters are sufficiently small to fit entirely within the 48-bit memory allocation of the mantissa, and the compressed value is identical to the integer part of the input. In other words, the scaled-up value is stored as-is, without compression. This is true for all rate parameters on any token pair, smaller than 48-bits. While the precision loss associated with compression is forgone, this does not mean that the process is necessarily more precise. On the contrary, the discarded fractional component may represent a more significant source of error, as its relative size with respect to the integer being stored is orders of magnitude larger (which is partly why low decimals tokens such as wBTC and USDC are such a nuisance in exchange protocols).


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 400000000                                                                                             |
|                          | |||||||||                                                                                             |
| y fixed point (contract) | 400000000                                                                                             |
|                          |                                                                                                       |
|        z precise (maker) | 737215598.8403066345843944226900997996236027110871949096129123470537482453137996501292832036260560230 |
|                          | |||||||||                                                                                             |
| z fixed point (contract) | 737215598                                                                                             |
|                          |                                                                                                       |
|        A precise (maker) | 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009  |
|                          | ||||                                                                                                  |
| A fixed point (contract) | 9233                                                                                                  |
|  A compressed (contract) | 9233                                                                                                  |
|                          |                                                                                                       |
|        B precise (maker) | 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |
|                          | |||||                                                                                                 |
| B fixed point (contract) | 22290                                                                                                 |
|  B compressed (contract) | 22290


Note that after the data is processed and stored, the original inputs are unknowable to anyone except the maker who produced them. This is an important observation because it adds ambiguity to the precision measurement as it relates to trade outputs, and to what we should refer to as the “precise standard”. For completeness’ sake, this investigation will include two different measurements: one from the perspective of the maker’s arbitrarily precise inputs, and one with the assumption that the data stored on the contract is itself, arbitrarily precise. In other words, the result of a trade executed against the strategy as it exists on Ethereum will be compared to:

  • The same trade executed on a hypothetical system with unlimited memory and where tokens have infinite decimals, where the maker’s curve was created with perfect precision with respect to their inputs. That is, we will measure the error with respect to what the maker knows (referred to as maker-precise).

  • The same trade executed on the same hypothetical system as above, but where the maker’s inputs are substituted for the rounded and compressed/decompressed data of the fixed-point system. That is, we will measure the error with respect to what the smart contract knows (referred to as contract-precise).


Performing Exchange


From the perspective of the smart contract implementation, it is important to note that all rounding is performed in such a way as to always benefit the maker. That is, when the output of a trade function is rounded from its theoretical contract-precise value to an integer, it is always in the direction of a slightly less desirable exchange rate from the taker’s perspective. This approach annuls any attempt by an adversary to engineer an input whereby they might receive more than the theoretically allowed token amount.


There are two trade functions available on Carbon DeFi’s smart contracts:

  • tradeBySource (i.e. amount-out-given-amount-in) always returns a number less than or equal to the contract-precise theoretical amount.

  • tradeByTarget (i.e. amount-in-given-amount-out) always returns a number greater than or equal to the contract-precise theoretical amount.


The purpose of this analysis is to examine how and to what extent information is eroded between conceiving a trading strategy, communicating it to the Carbon DeFi contracts, and its execution. With that in mind, the perspective of the taker is not really within scope — nor should it be. Unlike the maker, the taker can read the information from the contracts and simulate the inputs and outputs with 100% precision. Having said this, I do consider the effective price differences on execution resulting from the choice of either tradeBySourceor tradeByTarget to be worth a considered analysis.


Trade by Source

Be reminded that the theory requires:

Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Which can be functionally composed such that each intermediate operation does not exceed the 256-bit EVM mandate:


The final trade value is then computed from:

Functional composition of the Trade by Source Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Source Function (3 of 3)


The 256-bit limitation for the uint256 data type necessitates careful arithmetic management to prevent overflow while maintaining calculation precision. The smart contract’s approach is designed to navigate these constraints effectively.


The architecture utilizes mulDivC functions in certain computations (f₃, f₄, f₆, and f₇) to permit intermediate values to momentarily surpass the 256-bit boundary, ensuring that while the numerator in these operations may exceed the limit, the final results conform to the uint256 range. The final step also utilizes a mulDivC function to compute the scaled-down C²z² term, and uses a utilizes a mulDivF function to compute the scaled-down Δ(Ay + Bz) term. This method balances precision with the technical limitations of Ethereum, adopting a strategic approach to expression refactoring when necessary.


The computation of the final trade value, Δy, reflects Carbon DeFi’s defensive programming: it underestimates the numerator and overestimates the denominator, and the final division utilizes a mulDivF function to round the result towards zero. This tactic intentionally biases rounding errors in favor of the maker, a measure taken to prevent giving undue advantage to the taker owing to calculation imprecision. This choice underscores a principle of transaction security and fairness innate to Carbon DeFi’s implementation.


Overall, the contract demonstrates a balance between retaining calculation precision and preventing overflow, while adhering to Ethereum’s specifications and ensuring transaction integrity. By carefully managing arithmetic operations, the contract maintains a high degree of accuracy and reliability of its core functions without venturing out-of-bounds.


Returning to the example above, where the maker has offered SHIB tokens in exchange for wBTC tokens, we can simulate an exchange to reveal the implemented fixed-point precision result, and measure the relative error compared to the hypothetical maker-precise, and contract-precise outputs. For this example, assume that the taker wishes to send 0.32100123 wBTC tokens against the maker’s order, in return for the appropriate number of SHIB tokens as dictated by the general formula. The fixed-point smart contract implementation returns 1,654,355,822.074328684134994879 SHIB tokens, which is within 2.481523 parts per Quadrillion of the maker’s arbitrarily precise expectation, and within 26.631197 parts per Octillion of the arbitrarily precise result referencing only the data available stored on the contract itself. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, meaning the taker trades at a slight disadvantage.


Function Called: trade_by_source

Trade Input (Token Wei): 32100123 wBTC (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822074332789456944072.627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||||||:::|::::|:|:::.::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822074328684134994923.057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | |||||||||||||||||||||||||:::                                                                          |
| Fixed-point Precision (Contract) | 1654355822074328684134994879                                                                          |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.32100123 wBTC

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822.074332789456944072627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||.||||:::|::::|:|:::::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822.074328684134994923057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | ||||||||||.|||||||||||||||:::                                                                         |
| Fixed-point Precision (Contract) | 1654355822.074328684134994879                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 2.481523 parts per Quadrillion
Fixed-Point vs Arbitrary Precision (Contract): 26.631197 parts per Octillion


Now, referring to the opposite order within the strategy, where the maker has offered wBTC tokens in exchange for SHIB tokens, assume that the taker wishes to exchange 10,000,000 SHIB the appropriate number of wBTC tokens as dictated by the general formula. The fixed-point smart contract implementation returns 0.00094062 wBTC tokens, which is within 60.433641 parts per Million of the maker’s arbitrarily precise expectation, and within 4.523412 parts per Million with respect to the contract data. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, which is the desired behavior.


Function Called: trade_by_source

Trade Input (Token Wei): 10000000000000000000000000 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 94067.68485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | ||||:.:::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::|: |
|   Arbitrary Precision (Contract) | 94062.42548314780701261249837916821935266674645663587209179344662617977680268270640530830378232720800 |
|                                  | |||||                                                                                                 |
| Fixed-point Precision (Contract) | 94062                                                                                                 |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 10000000 SHIB

+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                         |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.0009406768485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | |.|||||||::::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::   |
|   Arbitrary Precision (Contract) | 0.00094062425483147807012612498379168219352666746456635872091793446626179776802682706405308303782327208   |
|                                  | |.||||||||                                                                                                |
| Fixed-point Precision (Contract) | 0.00094062                                                                                                |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 60.433641 parts per Million
Fixed-Point vs Arbitrary Precision (Contract): 4.523412 parts per Million


Trade by Target


Be reminded that the theory requires:

Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Functional composition of the Trade by Target Function (1 of 3)


The final trade value is then computed from:

Functional composition of the Trade by Target Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Target Function (3 of 3)


In the computation of Δx, the implementation approach diverges from that of Δin a critical aspect: the direction of rounding errors. This difference is a consequence of the reciprocal nature of these two functions. For Δx, which calculates the quantity of tokens a trader must send to the contract, the approach intentionally overestimates the numerator and underestimates the denominator, with the overall result being rounded up. This method is a deliberate inversion of the error biases applied in the Δcalculation.


This inversion is necessary to align the contract’s defensive programming with the transaction’s direction — ensuring that any rounding errors do not unjustly penalize the maker by requiring them to overpay. By overestimating the numerator and underestimating the denominator in the final computation, and rounding the result up, the contract aims to slightly favor the maker in the presence of calculation imprecision. Whereas the taker, who can observe the trade result prior to committing to the transaction, still get exactly the exchange rate they expected. This careful adjustment ensures that the contract remains equitable and avoids inadvertently disadvantaging users, maintaining fairness, transparency and integrity in all transactions that occur between users of the protocol.


Revisiting again the scenario where SHIB tokens are offered by the maker in exchange for wBTC tokens, let us undertake another exchange simulation to measure the precision loss of the fixed-point arithmetic, but this time using tradebyTarget. Consider a situation where the taker’s goal is to secure 1,654,355,822.074328684134994879 SHIB tokens from the maker’s market, meaning she queries how many wBTC tokens must be relinquished from her wallet to the maker’s order to complete the trade. The smart contract’s fixed-point precision calculation determines that 0.32100123 wBTC tokens are needed. This outcome reveals a difference of only 2.559216 parts per Quadrillion with respect to the maker’s exact calculations, and a difference of only 27.464979 parts per Octillion while taking the curve parametrization on the smart contract at face-value. Notably, the amount of wBTC tokens calculated for the exchange slightly overshoots the arbitrarily precise amount, subtly disadvantaging the taker, akin to the earlier observations.


Function Called: trade_by_target

Trade Input (Token Wei): 1654355822074328684134994879 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 32100122.99999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | ||||||||.|||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 32100122.99999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |||||||:                                                                                              |
| Fixed-point Precision (Contract) | 32100123                                                                                              |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 1654355822.074328684134994879 SHIB

+----------------------------------+--------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                      |
+----------------------------------+--------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.3210012299999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | |.|||||||||||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 0.3210012299999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |.|||||||:                                                                                             |
| Fixed-point Precision (Contract) | 0.32100123                                                                                             |
+----------------------------------+--------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 2.559216 parts per Quadrillion
Fixed-Point vs Arbitrary Precision (Contract): 27.464979 parts per Octillion


Lastly, and again turning our attention to the opposite order, where wBTC tokens are offered from the maker in exchange for SHIB tokens, consider a scenario where the taker aims to acquire 0.00094062 wBTC tokens using SHIB tokens. The taker requests the quantity of SHIB tokens that should be sent to the contract; in response, the smart contract’s fixed-point execution returns that 9,999,954.763923315127815869 SHIB tokens are needed. This result demonstrates a discrepancy of 55.916021 parts per Million from what was anticipated by the maker’s high-precision calculation and deviates by 40.578300 parts per Octillion when aligned against the contract data assuming arbitrary runtime resources. Consistent with prior examples, the computed SHIB token quantity for the trade is marginally greater than both precise evaluations, subtly positioning the taker at a slight disadvantage.


Function Called: trade_by_target

Trade Input (Token Wei): 94062 wBTC (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 9999395637511301625506302.622771540656487995730800965380663671805155126785782213292248238466542297775 |
|                                  | ||||:::::::::|:::|:::::::.:::::::::::::::::::::::|::::::|:::::|:::::::::::::::|::|:::::::::|::|:::::: |
|   Arbitrary Precision (Contract) | 9999954763923315127815868.594218836892670162259140677147615505874263278292550311194597366596202611817 |
|                                  | ||||||||||||||||||||||||:                                                                             |
| Fixed-point Precision (Contract) | 9999954763923315127815869                                                                             |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.00094062 wBTC

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 9999395.637511301625506302622771540656487995730800965380663671805155126785782213292248238466542297775 |
|                                  | ||||:::.::::::|:::|::::::::::::::::::::::::::::::|::::::|:::::|:::::::::::::::|::|:::::::::|::|:::::: |
|   Arbitrary Precision (Contract) | 9999954.763923315127815868594218836892670162259140677147615505874263278292550311194597366596202611817 |
|                                  | |||||||.|||||||||||||||||:                                                                            |
| Fixed-point Precision (Contract) | 9999954.763923315127815869                                                                            |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 55.916021 parts per Million
Fixed-Point vs Arbitrary Precision (Contract): 40.578300 parts per Octillion



Testing to Failure


What I have tried to demonstrate above is that Carbon DeFi’s implementation can handle even the most extreme of token exchange rates, such as that exhibited by high-decimals, relatively worthless tokens such as Shiba Inu, versus low-decimals, exceedingly valuable tokens such as wBTC. This begs the question as to where the limits of reasonable behavior are. Any system based on finite computer resources will have a limited scope of precision and accuracy, and it is a worthwhile endeavor to discover those boundaries.


This is not as easy as it sounds.


The parametrization of Carbon DeFi’s bonding curves is four-dimensional (AByz), and while trade inputs are only one-dimension, there are two different kinds:tradeBySource (Δx) and tradeByTarget (Δy), which makes for a six-dimensional hypervolume of possible input space. Accounting for the data compression and storage, the upper bound on the rate parameter is 281,474,976,710,655 × 2⁴⁸, which in terms of price quoting and/or range width is 79,228,162,514,263,774,643,590,529,025 wei/wei units. Taken together, the input space is absurdly large — but it can be meaningfully searched.


When Carbon DeFi was still in its gestational stage, I developed a suite of analysis tools to systematically map the input space (terabytes of data) and report problematic regions requiring further attention. Among its goals are to characterize how precision and accuracy change as a function of the inputs, and to discover situations where the effects of rounding have not had the desired result (i.e. the taker has an advantage over the maker). Fortunately, the only examples that exist for reversal of the rounding error benefit are nonsensical. Regardless, these families of input types are worth acknowledging and characterizing. The following example is representative of the set.


          risk/base token = ABCD
 risk/base token decimals = 18
         cash/quote token = WXYZ
cash/quote token decimals = 18
               order type = "buy ABCD with WXYZ"

  lowestRate = 4.0000000000000000000000000000000000000000000000000000000000000000000000000000000000
 highestRate = 4.0000000000000000000000000000000001925929944387235792646606569178402423858642578125
marginalRate = 4.0000000000000000000000000000000001925929944387235792646606569178402423858642578125
   liquidity = 1


The first thing to notice is that the difference between the input rates is very small — beneath the resolution that can be achieved after compression/decompression of the exchange range width parameter, A. Therefore, it rounds to zero. There is no such issue for the B parameter.


+------------------------------------------+-----------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                       | Note          |
+------------------------------------------+-----------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 1.35525271560688049999999999999999998368673883000368970659882225079E-20 |               |
|            A parameter (decimal integer) | >>> 0                                                                       |               |
|             A parameter (binary integer) | >>>       0                                                                 | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       0                                                                 | (max 96 bits) |
|                                          | >>>                                                                         |               |
|                                          | >>>       |significand|                                                     |               |
|              A exponent (binary integer) | >>> 000000                                                                  | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       0                                                                 | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 000000                                                                  | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑                                                                  |               |
|                                          | >>> |expt||mantissa|                                                        |               |
| compressed A parameter (decimal integer) | >>> 0


+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 562949953421312.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |               |
|            B parameter (decimal integer) | >>> 562949953421312                                                                                       |               |
|             B parameter (binary integer) | >>>       10000000000000000000000000000000000000000000000000                                              | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       10000000000000000000000000000000000000000000000000                                              | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑                                              |               |
|                                          | >>>       |-----------------significand------------------||trailing-zeroes|                               |               |
|              B exponent (binary integer) | >>> 000010                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       100000000000000000000000000000000000000000000000                                                | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 000010100000000000000000000000000000000000000000000000                                                | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                                |               |
| compressed B parameter (decimal integer) | >>> 703687441776640


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 1000000000000000000                                                                                   |
|                          | |||||||||||||||||||                                                                                   |
| y fixed point (contract) | 1000000000000000000                                                                                   |
|                          |                                                                                                       |
|        z precise (maker) | 1000000000000000000                                                                                   |
|                          | |||||||||||||||||||                                                                                   |
| z fixed point (contract) | 1000000000000000000                                                                                   |
|                          |                                                                                                       |
|        A precise (maker) | 1.35525271560688049999999999999999998368673883000368970659882225079E-20                               |
|                          | :                                                                                                     |
| A fixed point (contract) | 0                                                                                                     |
|  A compressed (contract) | 0                                                                                                     |
|                          |                                                                                                       |
|        B precise (maker) | 562949953421312.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000 |
|                          | |||||||||||||||                                                                                       |
| B fixed point (contract) | 562949953421312                                                                                       |
|  B compressed (contract) | 703687441776640


WhentradeBySourceis called with its theoretical maximum input (with respect to the contract variables), 0.25 ABCD, there is nothing remarkable about the output:


Function Called: trade_by_source

Trade Input (Token Wei): 250000000000000000 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1000000000000000000.000000000000000024074124304840447408082582114730029428887840659198167255476383850 |
|                                  | |||||||||||||||||||                                                                                   |
|   Arbitrary Precision (Contract) | 1000000000000000000                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 1000000000000000000                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.25 ABCD

+----------------------------------+------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                    |
+----------------------------------+------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.00000000000000000000000000000000002407412430484044740808258211473002942888784065919816725547638385 |
|                                  | |                                                                                                    |
|   Arbitrary Precision (Contract) | 1                                                                                                    |
|                                  | |                                                                                                    |
| Fixed-point Precision (Contract) | 1                                                                                                    |
+----------------------------------+------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 24.074124 parts per Undecillion
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


Exceeding the maximum input is impracticable, as the maker’s strategy will simply not have any more tokens to give, and the transaction will revert. Save for the fact that it is completely meaningless to do so, continuing to increase thetradeBySourceinput amount up to exactly 2× its theoretical limit reveals something unexpected. First, examine the outcome at some negligible amount smaller than 2×.


Function Called: trade_by_source

Trade Input (Token Wei): 499999999999999999 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1999999999999999996.000000000000000000000000000000000192592994438723577720347745950573968894003767416 |
|                                  | |||||||||||||||||||                                                                                   |
|   Arbitrary Precision (Contract) | 1999999999999999996                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 1999999999999999996                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.4999999999999999999999999999999999999999999999999999999999999999999999999999999999999 ABCD

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.999999999999999996000000000000000000000000000000000192592994438723577720347745950573968894003767416 |
|                                  | |.||||||||||||||||||                                                                                  |
|   Arbitrary Precision (Contract) | 1.999999999999999996                                                                                  |
|                                  | |.||||||||||||||||||                                                                                  |
| Fixed-point Precision (Contract) | 1.999999999999999996                                                                                  |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 96.296497 parts per Septendecillion
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


The error is significantly reduced compared to the maker-precise result, and the rounding is still in the right direction. Now, increasing by an amount so small I would consider it meaningless if I didn’t already know the outcome:


Function Called: trade_by_source

Trade Input (Token Wei): 500000000000000000 ABCD (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1999999999999999999.999999999999999999999999999999999999999999999999998840873077910180889673968511799 |
|                                  | :::::::::::::::::::                                                                                   |
|   Arbitrary Precision (Contract) | 2000000000000000000                                                                                   |
|                                  | |||||||||||||||||||                                                                                   |
| Fixed-point Precision (Contract) | 2000000000000000000                                                                                   |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.5 ABCD

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output WXYZ                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1.999999999999999999999999999999999999999999999999999999999999999999998840873077910180889673968511799 |
|                                  | :                                                                                                     |
|   Arbitrary Precision (Contract) | 2                                                                                                     |
|                                  | |                                                                                                     |
| Fixed-point Precision (Contract) | 2                                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): Negligible error
Fixed-Point vs Arbitrary Precision (Contract): Negligible error


The error is now so small as to have become nearly immeasurable. More importantly, the rounding is now in favor of the taker compared to the maker’s precise calculations; compared to the contract variables, there is no difference. As it happens, this is a general phenomenon. There exists an input space where, if the range width is sufficiently narrow so as to round to nothing on the contracts and we ignore the upper bound on the tradeBySourceinput, it is possible to achieve a rounding error that the maker would argue is in the wrong direction. Fortuitously this phenomenon is contained to nonsense inputs, and so has no bearing on the real-world functioning of Carbon DeFi’s Smart contracts. I raise it here for the benefit of academic curiosity, and in the hope that it may shed light on other unexpected behaviors that may affect other systems designs.


Conclusion


The exploration into Carbon DeFi’s fixed-point arithmetic and its impact on trade precision reveals both the challenges and achievements in supporting financial models within the constraints of blockchain technology. Through careful design and strategic implementation, Carbon DeFi navigates the inherent limitations of fixed-point computation, ensuring that the protocol remains equitable for both makers and takers. This study underscores the importance of precision in decentralized finance, illustrating that even minute discrepancies can have tangible effects on trading outcomes. As blockchain technology evolves, so too will the methodologies to optimize precision and efficiency in DeFi protocols, further bridging the gap between theory and execution.

My approach to de novo protocol design begins with establishing a theoretical framework. I assume this is common, but I am reluctant to speak for other designers. Speaking only to my experience, this phase of the development process invariably relies on pure algebraic abstraction. The goal is to achieve an exhaustive mathematical description of the protocol’s core methods such that its functionality can be demonstrated symbolically. It is wonderful to arrive at a proven (or, at the very least provable) source of truth with respect to what the expected outcomes are for any conceivable interaction with a newly imagined system. However, as a veteran experimentalist I understand better than most the difference between pen-and-paper theory and the limits of physical reality. For protocol design this is the difference between the immaculate mathematical abstraction of the system and its implementation under the limitations of its host blockchain. With only finite memory and compute cycles at hand, the challenge is to create the best possible approximation of the model while respecting the boundaries of what the network can realistically support. This is no small task. I am very fortunate to be working with talented embedded systems engineers, including and especially Barak Manos, with whom I can collaborate to thread this specific needle.


Carbon DeFi in Brief


Carbon DeFi is a token exchange protocol, made to resemble a conventional orderbook in some respects, without completely turning its back on its AMM heritage. Fittingly, the system variables are those required to encapsulate a singular, or series of user-nominated exchange rates and token balances, which are then broadcast to other market participants who may elect to take the tokens and prices on offer. As is customary in DeFi, the exchange is formalized with an invariant function, from which its price quoting and swap functions can be derived:


Invariant Function


Price Function


Trade by Source Function (i.e. output amount, given the input amount)


Trade by Target Function (i.e. input amount, given the output amount)



The ab, and z terms are constants that describe the relative curvature and size of the bonding curve; z denotes the y-intercept, and a and b are derived from the two price bounds of the range.


Exchange Rate Parameter Definitions


In the equations above, (xy) represent cartesian coordinates for a point that lies on the bonding curve, (Δx, Δy) represent token quantities corresponding to a translocation upon the same curve, and Δand Δhave opposite signs. By convention, the swapped token amounts are expressed as unsigned integers, Δ≥ 0 and Δ≥ 0. In other words, we adopt a moving frame of reference, such that Δrepresents a number of tokens sent from the market participant to the contract, and Δrepresents a number of tokens sent from the contract to the market participant:

  • Following a token exchange, the contract balance of x is x + Δx.

  • Following a token exchange, the contract balance of y is y − Δy.


Consequently, the exchange rates for any curve inherit this convention; the exchange rate, ∂y/∂xis the drop in the y-balance with respect to some input amount as it approaches zero (Δ→ 0). The benefit here is that rates are quoted as positive (rather than negative) numbers. While technically incorrect with respect to the invariant, these conventions support standard financial intuition. However, it is important to recognize that this formulation is necessarily numeraire-agnostic, as the y-axis may represent either the “cash” (or “quote”), or “risk” (or “base”) asset. In cases where the y-axis refers to a common numeraire, such as a $USD stablecoin, the price quote is intuitive: ∂y/∂xhas units of $USD per risk token. However, in cases where the y-axis refers to the risk asset the price quote is inverted: ∂y/∂has units of risk token per $USD.


For example, a curve which represents bidding liquidity denominated in USDC for the ETH markets beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH has intuitive units, and the depletion of the USDC balance on the curve is commensurate with receding bidding prices. Suppose that the curve was initialized with 10,000 USDC of which 5,000 USDC remains. Through application of the price function, the marginal highest bid on the curve, P_m, is found to be ~1,995 USDC per ETH. Whereas P_m is the marginal rate corresponding to the current y balance of the curve, P_a and P_b refer to the marginal rates when y = z, and y = 0, respectively.


Figure 1: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC.


Consider now how a curve representing asking liquidity might look compared to the bidding liquidity example, above. As per the conventions the curve which offers ETH is necessarily denominated in ETH, and its contract balance is represented on the y-axis. Therefore, the output of the price function (and the variables P_a, P_b, and P_m) are reported in units of ETH per USDC. This can be a little jarring. For example, the curve which offers ETH at a rate beginning at 2,500 USDC per ETH and ending at 3,000 USDC per ETH must use the reciprocals of these values. Suppose that the curve was initialized with 10 ETH, of which 5 ETH remains. The price function returns the marginal lowest ask on the curve, P_m, as 3.66 × 10⁻⁴ ETH per USDC; as before, P_a and P_b refer to the marginal rates when y = z, and y = 0, which are 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) and 3.33 × 10⁻⁴ (i.e. 3,000⁻¹) ETH per USDC, respectively. Note that this arrangement still exhibits the desired behavior; as the ETH balance is depleted, the reciprocal price of ETH is diminished, which is commensurate with higher asking prices.


Figure 2: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH.


If the reader is appropriately motivated, the significance of this parametrization is elaborated in the Carbon DeFi whitepaperlitepaper, and invention disclosure. For the present discussion, assume that the user provides all three marginal rates P_a, P_b, and P_m as inputs, along with the token quantity, y, from which the z value must be calculated. In almost all cases, the user elects to have P_m = P_a at the time the curve is initialized, in which case z = y; however, this is not a requirement.

Curve Size Parameter Definition


Token Decimals


For the benefit of the uninitiated, a single token is not what it first appears. Tokens have a property which determines its divisibility, called its “token decimals”. For example, Bitcoin’s decimals are 8, meaning that a single BTC unit is defined as comprising a set of 10⁸ individual parts called “satoshis” or “sats”. Ether’s decimals are 18, meaning that a single ETH unit comprises 10¹⁸ parts called “wei”. Circle’s USD stablecoin uses only 6 decimals, meaning that a single USDC unit comprises 10⁶ parts (also called wei), and so on.


From the perspective of the exchange system, these smallest indivisible parts are all that matter, and exchange rates are unavoidably expressed in these terms. For example, suppose that 20 ETH is equal in value to 1 BTC (e.g. ETH/USD = $2,500; BTC/USD = $50,000). At the contract level, this translates to 20 × 10¹⁰ ETH wei is equal in value to 1 BTC sat. Since 1 BTC sat is indivisible, BTC-to-ETH exchange is necessarily [for lack of a better word] “chunky”. This forms the basis for further erosion of the perceived accuracy of the system; however, this would be unavoidable even with unlimited storage and computer power.


To demonstrate how token decimals impact the data available on the smart contracts, the two bonding curves presented above have been recreated here, but this time accounting for the difference in the token decimals. The curve representing bidding liquidity on ETH denominated in USDC, beginning at P_a = 2,200 USDC per ETH and ending at P_b = 1,800 USDC per ETH, initialized with 10,000 USDC, of which 5,000 USDC remains, is now revealed to have a tiny fraction of the naïve exchange rate. At the wei level, the marginal price is no longer ~1,995 USDC per ETH, but 1.995 × 10⁻⁹ USDC wei per ETH wei.


Figure 3: Invariant and Price Curves for P_a = 2,200 USDC per ETH, P_b = 1,800 USDC per ETH, y = 5,000 USDC and z = 10,000 USDC, represented at the wei scale.


The curve representing asking liquidity for ETH denominated in USDC, beginning at which are 4.00 × 10⁻⁴ (2,500⁻¹) USDC per ETH and ending at 3.33 × 10⁻⁴ (3,000⁻¹) USDC per ETH, initialized with 10 ETH, of which 5 ETH remains, is revealed to exhibit a substantially more aggressive exchange profile compared to the naïve rate. At the wei level, the marginal price is no longer 3.66 × 10⁻⁴ ETH per USDC, but rather 3.66 × 10⁸ ETH wei per USDC wei.


Figure 4: Invariant and Price Curves for P_a = 4.00 × 10⁻⁴ (i.e. 2,500⁻¹) ETH per USDC, P_b = 3.33 × 10⁻⁴ (3,000⁻¹) ETH per USDC, y = 5 ETH and z = 10 ETH, represented at the wei scale.


Differences in token decimals can alter the effective exchange rates by several orders of magnitude. This presents an interesting challenge, as even common token pairs (e.g. ETH/USDC) can result in exchange rates one might have otherwise concluded were sufficiently unusual to justify ignoring. Unfortunately, no — the scope of the expected input space really is so vast as to include numbers both vanishingly small, and impenetrably large. The challenge is to define the boundaries of what is reasonable and ensure that calculations performed at any point within these bounds result in outputs that closely adhere to the theory, while remaining mindful of the limited computer resources of the network.


The Scaling Constant


Fixed point systems only know about integers. For an exchange protocol based on theory that is so thoroughly entrenched in the real numbers, this is a problem. To illustrate, consider that any exchange rate less than one rounds to zero. Given the reciprocal nature of the bidding and asking prices, about half of all exchange rates will default to zero. For example, the value 2/3 cannot be stored in a fixed-point system; however, it can be recreated in-situ by storing the numerator and denominator discretely, (2, 3), and its parts can be summoned when performing further mathematical operations when needed. The approach implemented by Carbon DeFi is fundamentally the same, save for the fact that the in-situ recreation of the original fraction is abstracted algebraically.


Carbon DeFi multiplies the rate variables, a and b, by a large constant, C, and records the integer part as the contract variables A and B (n.b. the capitalization is intended to symbolically capture their meaning). Implicitly, these variables represent the numerators of the fractions a = A/C and b = B/C. To recreate a and b, the general formulae are appropriately modified to compensate for the implied denominator, C, which compensates for the scaling-up in A and B and allows these contract variables to be used directly during exchange calculations.


Exchange Rate Parameter Definitions (Scaling Corrected)


Invariant Function (Scaling Corrected)


Price Function (Scaling Corrected)


Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Curve Size Parameter Definition (Scaling Corrected)


Data Storage


The strategy data is organized into three slots:

  • Slot #1: Contains the y_bid and y_ask values. Each value has 112 bits of allocated memory.

  • Slots #2 and #3: Contains the z_bid, A_bid, and B_bid, and the z_ask, A_ask, and B_ask values, and each slot is exclusive to either the bidding or asking curve. The z_bid and z_ask values have the same 112 bits of allocated memory as the y_bid and y_ask values in Slot #1. The A_bid, B_bid, A_ask, and B_ask values have 54 bits of allocated memory apiece, of which the first 48 bits is reserved for the mantissa (or “significand”) and the remaining 6 bits are reserved for the exponent.

  • Unused Bits: The y_bid, y_ask, z_bid and z_ask values contain a 16-bit appendix, and the A_bid, B_bid, A_ask, and B_ask values contain a 10-bit appendix of unutilized memory. These spaces are envisioned to support other features of the system in the future. Being that this has no bearing on the present discussion, these appendices can safely be ignored.


This arrangement allows the write operations to be restricted to only one slot in most cases. A trade executed against a strategy necessarily results in changes to both the y_bid and y_ask liquidity values in Slot #1. On occasion, if one of the y_bid or y_ask values exceed its corresponding z_bid or z_ask value, then a second write operation is required to reset the appropriate value in Slot #2 or #3. Therefore, trades executed against a strategy will require data to be overwritten in at most two slots in memory.


Figure 5: Carbon DeFi strategy data storage and organization


Smart Contract Implementation, Fixed-Point Precision, and Relative Error


The core object of the Carbon DeFi protocol is called a strategy, which comprises two different orders represented by two different bonding curves, each representing one of the bid/ask pair. Someone who is quoting the bid and ask prices is called a maker, and the market participant who agrees to an exchange at the quoted price is the taker. There are two types of user-protocol interaction where numeric precision should be scrutinized:

  1. During strategy creation or editing an existing strategy, wherein the strategy’s owner (i.e. the maker) may provide price inputs of arbitrary precision (highestRate, lowestRate, marginalRate) which must then be transformed into fixed-point format and recorded to the smart contracts.

  2. During a trade, wherein an outside market participant (i.e. the taker) calls one of the two available trade functions and sends an amount of the source token to the contract in exchange for the target token.



Figure 6: The two points of user-protocol interaction where precision loss occurs.


Strategy Creation and Editing


The maker’s purpose [one hopes] is conceived off-chain, where they may imagine an infinitely precise pair of bonding curves that represent their market outlook. Each curve’s parametrization can be described and communicated entirely by the three marginal rates P_a, P_b, and P_m, and the number of tokens, y, to be distributed across each curve. Once received, this data must be processed into the variables AByz. While these variables may also be infinitely precise with respect to the maker’s imagined strategy, their on-chain representation is limited by the storage allocated to each value. Therefore, this process of collecting and processing the maker’s inputs, and their truncation to fit the storage requirements of the blockchain is an important source of precision loss.


To understand how this looks in practice, consider an arbitrary set of inputs received from the maker during strategy creation: thehighestRate, lowestRate, marginalRate, and the liquidityvalues. The context is also important. If the maker’s ambition is to buy the risk/base asset, then the liquidity value refers to the cash/quote asset and the exchange rates can be used verbatim; whereas if the maker intends to sell the risk/base asset, then the liquidity value refers to the risk/base asset itself and the exchange rates must be inverted. In both cases, the token decimals and compression of the curve parameters A and B is worth close inspection.


One of the most challenging pairs in all of DeFi from a precision perspective is Shiba Inu (SHIB, a relatively worthless 18 decimals token) versus wrapped Bitcoin (wBTC, an extremely valuable, 8 decimals token). At the time of writing, the SHIB/USD rate is $0.0₅9756 ($9.756 × 10⁻⁶) and the wBTC/USD rate is $ 51,854.04 ($5.185404 × 10⁴). Therefore, the current wBTC/SHIB market rate is 5,315,092,250.92251 (5.31509225092251 × 10⁹).


Suppose that a user wants to create a direct market for this pair at the current market rate, offering to buy wBTC with SHIB as the price of SHIB improves in value versus wBTC, with a market depth of 10 billion SHIB tokens or approximately $97,560 USD equivalent value. The following hypothetical inputs represent such a view, and the maker’s arbitrarily precise bonding curve at the wei scale is depicted in Figure 7. Note that we adopt the perspective here that the maker’s off-chain inputs are in the familiar non-wei format, and that the decimal corrections must be applied as part of the input processing.


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “buy wBTC with SHIB”

  lowestRate = 3,543,394,833.948345819174724191772607317991320232065325065623989176702584205824065767190258
 highestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
marginalRate = 5,315,092,250.922518728762086287658910976986980348097987598435983765053876308736098650785387
   liquidity = 10,000,000,000

Figure 7: Invariant and Price Curves for lowestRate = 3,543,394,833.9483… SHIB per wBTC, highestRate = 7,972,638,376.3837… SHIB per wBTC, y = 10,000,000,000 SHIB and z = 22,247,448,713.9158… SHIB at the wei scale.


Here we can examine the translation of the user’s inputs into the curve parameters, and their compression prior to storage. Since this curve has the cash/quote token (SHIB) as its own balance, the rates are used verbatim (i.e. that is, not their reciprocal values), and only adjusted for the decimals of the pair. The sheer size of the result is especially pronounced due to the enormous difference in the token values, which is further amplified by their decimals and the scaling constant.


The off-chain compression algorithm then converts the rate parameter to an integer and normalizes it by the scaling constant to determine its bit length for truncation. Truncation occurs by bitwise shifting, removing insignificant bits (duh!). The exponent, indicating the value’s scale, is extracted from the truncated value’s bit length. The mantissa is isolated by right-shifting this value by the exponent. Finally, the mantissa and scaled exponent are combined with a bitwise OR, producing a compressed binary representation optimized for minimal storage.


In other words, (referring only to the integer part of the binary form of the result) the significand is defined as the first 48 digits, which is extracted to be used as the mantissa, and the remaining digits are replaced with zeroes. Then, the trailing zeroes after the 48th digit are counted; the count, expressed as a 6-bit binary number, is extracted as the exponent. The compressed value is then stored as the concatenation of the exponent and the mantissa, in that order.


I am sincerely hopeful that one of the above descriptions is helpful. Failing that, I am providing the following code snippet from the class I use to perform these analyses, as well as the plain text tables it can produce, which represent each step of the compression sequence for the wBTC/SHIB curve presently under examination for both its B and A parameters.


def compress_rate_parameter(
    self,
    parameter_name: str, 
    print_process_summary: bool = True
    ) -> int:
    """
    ### Compresses a rate parameter for storage in blockchain environments.

    ## Parameters:
    | Parameter Name          | Type      | Description                                                                           |
    |-------------------------|-----------|---------------------------------------------------------------------------------------|
    | `parameter_name`        | `str`     | The name of the parameter to be compressed.                                           |
    | `print_process_summary` | `bool`    | Whether to print a detailed summary of the compression process (default is `True`).   |

    ## Returns:
    | Return Name                | Type  | Description                                                                      |
    |----------------------------|-------|----------------------------------------------------------------------------------|
    | `compressed_rate_parameter`| `int` | The compressed form of the rate parameter, optimized for blockchain storage.     |

    ## Notes:
    - This method reduces the size of rate parameters, making them suitable for efficient storage and processing on blockchain platforms.
    - The compression process involves:
        1. Converting the parameter to an integer, if not already.
        2. Truncating any trailing zeroes to minimize the parameter's size.
        3. Extracting an exponent representing the scale of truncation.
        4. Identifying the mantissa as the significant part of the parameter.
        5. Combining the exponent and mantissa into a single integer.
    - Optionally, a detailed visual summary of the compression process can be printed, illustrating the transformation of the parameter into its compressed form.
    - This method is crucial for understanding the use of smart contract storage, reducing gas costs, and maintaining acceptable accuracy.
    """
    rate_parameter_int = int(self.maker_precise_curve_parameters[parameter_name])
    number_of_bits = (rate_parameter_int // self.scaling_constant_int).bit_length()
    truncated_rate_parameter = (rate_parameter_int >> number_of_bits) << number_of_bits
    exponent = (truncated_rate_parameter // self.scaling_constant_int).bit_length()
    mantissa = truncated_rate_parameter >> exponent
    compressed_rate_parameter = mantissa | (exponent * self.scaling_constant_int)
    if print_process_summary:
        self.print_compression_process_summary(parameter_name,
                                               rate_parameter_int,
                                               truncated_rate_parameter,
                                               exponent,
                                               mantissa,
                                               compressed_rate_parameter)
    return compressed_rate_parameter


+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |               |
|            B parameter (decimal integer) | >>> 1675519805183645805377564                                                                             |               |
|             B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001001111111110010101111100000011100               | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101100010110011100001110001010010111110100010001000000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||--------trailing-zeroes--------|               |               |
|              B exponent (binary integer) | >>> 100001                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                                | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 100001101100010110011100001110001010010111110100010001                                                | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                                |               |
| compressed B parameter (decimal integer) | >>> 9483730408799505


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554 |               |
|            A parameter (decimal integer) | >>> 837759902591822902688782                                                                             |               |
|             A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100111111111001010111110000001110               | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10110001011001110000111000101001011111010001000100000000000000000000000000000000               | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑               |               |
|                                          | >>>       |-----------------significand------------------||-------trailing-zeroes--------|               |               |
|              A exponent (binary integer) | >>> 100000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       101100010110011100001110001010010111110100010001                                               | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 100000101100010110011100001110001010010111110100010001                                               | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                               |               |
|                                          | >>> |expt||-------------------mantissa-------------------|                                               |               |
| compressed A parameter (decimal integer) | >>> 9202255432088849


In addition to the compression of these curve parameters, only the integer parts of the y and z values are stored. While this is obvious for y, which represents the SHIB token balance of the curve (in wei units), it is still worth raising as the maker be unaware of the decimals of the token they are using and provide an off-chain input that implies divisibility beyond that supported by the token contract (e.g. wBTC beyond the 8th decimal place). While z has an explicit correspondence with y and must also be an integer, it is also computed from the marginal rate user input. Therefore, its rounding will also have an impact on the overall precision of the system — albeit a negligible one. The error caused by rounding the y and values and resulting from a compression/decompression cycle of the A and B values can be examined directly versus the maker’s arbitrarily precise inputs by comparing the digits in-place.


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 10000000000000000000000000000                                                                         |
|                          | |||||||||||||||||||||||||||||                                                                         |
| y fixed point (contract) | 10000000000000000000000000000                                                                         |
|                          |                                                                                                       |
|        z precise (maker) | 22247448713915890490986420373.52945695982973740328335064216346283625480188728657513269929716552320115 |
|                          | |||||||||||||||||||||||||||||                                                                         |
| z fixed point (contract) | 22247448713915890490986420373                                                                         |
|                          |                                                                                                       |
|        A precise (maker) | 837759902591822902688782.151808538512721337750052157486256732430801088713171228048933617446014812554  |
|                          | ||||||||||||||::::||:::|                                                                              |
| A fixed point (contract) | 837759902591821830684672                                                                              |
|  A compressed (contract) | 9202255432088849                                                                                      |
|                          |                                                                                                       |
|        B precise (maker) | 1675519805183645805377564.303617077025442675500104314972513464861602177426342456097867234892029625106 |
|                          | |||||||||||||||::::|::::|                                                                             |
| B fixed point (contract) | 1675519805183643661369344                                                                             |
|  B compressed (contract) | 9483730408799505


Be reminded that a Carbon DeFi strategy is composed of two bonding curves, each taking a different side of the market. It is worthwhile to consider the opposite order type — one which offers wBTC liquidity in exchange for SHIB as the price of wBTC appreciates relative to SHIB — because it represents the opposite extreme of the curve parametrization.


Suppose that the maker includes the following order as part of their strategy, representing the opposite side of the market with a depth of 4 wBTC tokens or approximately $207,416 USD equivalent value:


          risk/base token = wBTC
 risk/base token decimals = 8
         cash/quote token = SHIB
cash/quote token decimals = 18
               order type = “sell wBTC for SHIB”

  lowestRate = 7,972,638,376.383778093143129431488366465480470522146981397653975647580814463104147976178081
 highestRate = 15,945,276,752.76755618628625886297673293096094104429396279530795129516162892620829595235616
marginalRate = 10,630,184,501.84503745752417257531782195397396069619597519687196753010775261747219730157077
   liquidity = 4


Figure 7: Invariant and Price Curves for lowestRate = 7,972,638,376.3837… SHIB per wBTC, highestRate = 15,945,276,752.7675… SHIB per wBTC, y = 4 wBTC and z = 7.3721… wBTC at the wei scale. Note that the curve rates P_a, P_m, and P_b are inverted with respect to the user inputs.


Note that since the y balance of the curve refers to the risk/base asset, the price quotes and token decimals must be converted to their reciprocals, which can be abstracted in the math as follows:


With these numbers in-hand, the same compression algorithm and rounding of the y and z values apply, exactly as before.

+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                     | Note          |
+------------------------------------------+-----------------------------------------------------------------------------------------------------------+---------------+
|      precise B parameter (decimal float) | >>> 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |               |
|            B parameter (decimal integer) | >>> 22290                                                                                                 |               |
|             B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|   truncated B parameter (binary integer) | >>>       101011100010010                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |-significand-|                                                                                 |               |
|              B exponent (binary integer) | >>> 000000                                                                                                | (max 6 bits)  |
|              B mantissa (binary integer) | >>>       101011100010010                                                                                 | (max 48 bits) |
|  compressed B parameter (binary integer) | >>> 000000101011100010010                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa---|                                                                                 |               |
| compressed B parameter (decimal integer) | >>> 22290


+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|                                 Variable | Value                                                                                                    | Note          |
+------------------------------------------+----------------------------------------------------------------------------------------------------------+---------------+
|      precise A parameter (decimal float) | >>> 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009 |               |
|            A parameter (decimal integer) | >>> 9233                                                                                                 |               |
|             A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|   truncated A parameter (binary integer) | >>>       10010000010001                                                                                 | (max 96 bits) |
|                                          | >>>       ↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>>       |significand-|                                                                                 |               |
|              A exponent (binary integer) | >>> 000000                                                                                               | (max 6 bits)  |
|              A mantissa (binary integer) | >>>       10010000010001                                                                                 | (max 48 bits) |
|  compressed A parameter (binary integer) | >>> 00000010010000010001                                                                                 | (max 54 bits) |
|                                          | >>> ↑↑↑↑↑↑↓↓↓↓↓↓↓↓↓↓↓↓↓↓                                                                                 |               |
|                                          | >>> |expt||--mantissa--|                                                                                 |               |
| compressed A parameter (decimal integer) | >>> 9233


What should be noted here is that the A and B curve parameters are sufficiently small to fit entirely within the 48-bit memory allocation of the mantissa, and the compressed value is identical to the integer part of the input. In other words, the scaled-up value is stored as-is, without compression. This is true for all rate parameters on any token pair, smaller than 48-bits. While the precision loss associated with compression is forgone, this does not mean that the process is necessarily more precise. On the contrary, the discarded fractional component may represent a more significant source of error, as its relative size with respect to the integer being stored is orders of magnitude larger (which is partly why low decimals tokens such as wBTC and USDC are such a nuisance in exchange protocols).


+--------------------------+-------------------------------------------------------------------------------------------------------+
|                Parameter | Value                                                                                                 |
+--------------------------+-------------------------------------------------------------------------------------------------------+
|        y precise (maker) | 400000000                                                                                             |
|                          | |||||||||                                                                                             |
| y fixed point (contract) | 400000000                                                                                             |
|                          |                                                                                                       |
|        z precise (maker) | 737215598.8403066345843944226900997996236027110871949096129123470537482453137996501292832036260560230 |
|                          | |||||||||                                                                                             |
| z fixed point (contract) | 737215598                                                                                             |
|                          |                                                                                                       |
|        A precise (maker) | 9233.11140725261452182535808827061525937857598134623803709959495937678947350425791519043211464010009  |
|                          | ||||                                                                                                  |
| A fixed point (contract) | 9233                                                                                                  |
|  A compressed (contract) | 9233                                                                                                  |
|                          |                                                                                                       |
|        B precise (maker) | 22290.70278229099528962926755992160011182305315686523376722634019307511285975496714971660409421763525 |
|                          | |||||                                                                                                 |
| B fixed point (contract) | 22290                                                                                                 |
|  B compressed (contract) | 22290


Note that after the data is processed and stored, the original inputs are unknowable to anyone except the maker who produced them. This is an important observation because it adds ambiguity to the precision measurement as it relates to trade outputs, and to what we should refer to as the “precise standard”. For completeness’ sake, this investigation will include two different measurements: one from the perspective of the maker’s arbitrarily precise inputs, and one with the assumption that the data stored on the contract is itself, arbitrarily precise. In other words, the result of a trade executed against the strategy as it exists on Ethereum will be compared to:

  • The same trade executed on a hypothetical system with unlimited memory and where tokens have infinite decimals, where the maker’s curve was created with perfect precision with respect to their inputs. That is, we will measure the error with respect to what the maker knows (referred to as maker-precise).

  • The same trade executed on the same hypothetical system as above, but where the maker’s inputs are substituted for the rounded and compressed/decompressed data of the fixed-point system. That is, we will measure the error with respect to what the smart contract knows (referred to as contract-precise).


Performing Exchange


From the perspective of the smart contract implementation, it is important to note that all rounding is performed in such a way as to always benefit the maker. That is, when the output of a trade function is rounded from its theoretical contract-precise value to an integer, it is always in the direction of a slightly less desirable exchange rate from the taker’s perspective. This approach annuls any attempt by an adversary to engineer an input whereby they might receive more than the theoretically allowed token amount.


There are two trade functions available on Carbon DeFi’s smart contracts:

  • tradeBySource (i.e. amount-out-given-amount-in) always returns a number less than or equal to the contract-precise theoretical amount.

  • tradeByTarget (i.e. amount-in-given-amount-out) always returns a number greater than or equal to the contract-precise theoretical amount.


The purpose of this analysis is to examine how and to what extent information is eroded between conceiving a trading strategy, communicating it to the Carbon DeFi contracts, and its execution. With that in mind, the perspective of the taker is not really within scope — nor should it be. Unlike the maker, the taker can read the information from the contracts and simulate the inputs and outputs with 100% precision. Having said this, I do consider the effective price differences on execution resulting from the choice of either tradeBySourceor tradeByTarget to be worth a considered analysis.


Trade by Source

Be reminded that the theory requires:

Trade by Source Function (i.e. output amount, given the input amount) (Scaling Corrected)


Which can be functionally composed such that each intermediate operation does not exceed the 256-bit EVM mandate:


The final trade value is then computed from:

Functional composition of the Trade by Source Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Source Function (3 of 3)


The 256-bit limitation for the uint256 data type necessitates careful arithmetic management to prevent overflow while maintaining calculation precision. The smart contract’s approach is designed to navigate these constraints effectively.


The architecture utilizes mulDivC functions in certain computations (f₃, f₄, f₆, and f₇) to permit intermediate values to momentarily surpass the 256-bit boundary, ensuring that while the numerator in these operations may exceed the limit, the final results conform to the uint256 range. The final step also utilizes a mulDivC function to compute the scaled-down C²z² term, and uses a utilizes a mulDivF function to compute the scaled-down Δ(Ay + Bz) term. This method balances precision with the technical limitations of Ethereum, adopting a strategic approach to expression refactoring when necessary.


The computation of the final trade value, Δy, reflects Carbon DeFi’s defensive programming: it underestimates the numerator and overestimates the denominator, and the final division utilizes a mulDivF function to round the result towards zero. This tactic intentionally biases rounding errors in favor of the maker, a measure taken to prevent giving undue advantage to the taker owing to calculation imprecision. This choice underscores a principle of transaction security and fairness innate to Carbon DeFi’s implementation.


Overall, the contract demonstrates a balance between retaining calculation precision and preventing overflow, while adhering to Ethereum’s specifications and ensuring transaction integrity. By carefully managing arithmetic operations, the contract maintains a high degree of accuracy and reliability of its core functions without venturing out-of-bounds.


Returning to the example above, where the maker has offered SHIB tokens in exchange for wBTC tokens, we can simulate an exchange to reveal the implemented fixed-point precision result, and measure the relative error compared to the hypothetical maker-precise, and contract-precise outputs. For this example, assume that the taker wishes to send 0.32100123 wBTC tokens against the maker’s order, in return for the appropriate number of SHIB tokens as dictated by the general formula. The fixed-point smart contract implementation returns 1,654,355,822.074328684134994879 SHIB tokens, which is within 2.481523 parts per Quadrillion of the maker’s arbitrarily precise expectation, and within 26.631197 parts per Octillion of the arbitrarily precise result referencing only the data available stored on the contract itself. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, meaning the taker trades at a slight disadvantage.


Function Called: trade_by_source

Trade Input (Token Wei): 32100123 wBTC (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822074332789456944072.627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||||||:::|::::|:|:::.::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822074328684134994923.057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | |||||||||||||||||||||||||:::                                                                          |
| Fixed-point Precision (Contract) | 1654355822074328684134994879                                                                          |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 0.32100123 wBTC

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output SHIB                                                                                     |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 1654355822.074332789456944072627885974582462779091418036616284712304625160064989689902030257444409839 |
|                                  | ||||||||||.||||:::|::::|:|:::::|::::|::::::::|:::|::::::::::::::::::::::::::::::::::::::::::::::::::: |
|   Arbitrary Precision (Contract) | 1654355822.074328684134994923057476573629605278371744220559652825496956981343545367598264564735765621 |
|                                  | ||||||||||.|||||||||||||||:::                                                                         |
| Fixed-point Precision (Contract) | 1654355822.074328684134994879                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 2.481523 parts per Quadrillion
Fixed-Point vs Arbitrary Precision (Contract): 26.631197 parts per Octillion


Now, referring to the opposite order within the strategy, where the maker has offered wBTC tokens in exchange for SHIB tokens, assume that the taker wishes to exchange 10,000,000 SHIB the appropriate number of wBTC tokens as dictated by the general formula. The fixed-point smart contract implementation returns 0.00094062 wBTC tokens, which is within 60.433641 parts per Million of the maker’s arbitrarily precise expectation, and within 4.523412 parts per Million with respect to the contract data. Again, the fixed-point calculation returns a number that is very slightly less than either of the precise calculations, which is the desired behavior.


Function Called: trade_by_source

Trade Input (Token Wei): 10000000000000000000000000 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 94067.68485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | ||||:.:::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::|: |
|   Arbitrary Precision (Contract) | 94062.42548314780701261249837916821935266674645663587209179344662617977680268270640530830378232720800 |
|                                  | |||||                                                                                                 |
| Fixed-point Precision (Contract) | 94062                                                                                                 |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 10000000 SHIB

+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                         |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.0009406768485269045831388380332087239085166052240773657843008681176800858259514885400558683400775461902 |
|                                  | |.|||||||::::::::::::::|:::::::|:::::::::|:||::::|:::|::|:::::::::::::::::::::::::::::||:::::::::::::::   |
|   Arbitrary Precision (Contract) | 0.00094062425483147807012612498379168219352666746456635872091793446626179776802682706405308303782327208   |
|                                  | |.||||||||                                                                                                |
| Fixed-point Precision (Contract) | 0.00094062                                                                                                |
+----------------------------------+-----------------------------------------------------------------------------------------------------------+


Fixed-Point vs Arbitrary Precision (Maker): 60.433641 parts per Million
Fixed-Point vs Arbitrary Precision (Contract): 4.523412 parts per Million


Trade by Target


Be reminded that the theory requires:

Trade by Target Function (i.e. input amount, given the output amount) (Scaling Corrected)


Functional composition of the Trade by Target Function (1 of 3)


The final trade value is then computed from:

Functional composition of the Trade by Target Function (2 of 3)


Which is equivalent to:

Functional composition of the Trade by Target Function (3 of 3)


In the computation of Δx, the implementation approach diverges from that of Δin a critical aspect: the direction of rounding errors. This difference is a consequence of the reciprocal nature of these two functions. For Δx, which calculates the quantity of tokens a trader must send to the contract, the approach intentionally overestimates the numerator and underestimates the denominator, with the overall result being rounded up. This method is a deliberate inversion of the error biases applied in the Δcalculation.


This inversion is necessary to align the contract’s defensive programming with the transaction’s direction — ensuring that any rounding errors do not unjustly penalize the maker by requiring them to overpay. By overestimating the numerator and underestimating the denominator in the final computation, and rounding the result up, the contract aims to slightly favor the maker in the presence of calculation imprecision. Whereas the taker, who can observe the trade result prior to committing to the transaction, still get exactly the exchange rate they expected. This careful adjustment ensures that the contract remains equitable and avoids inadvertently disadvantaging users, maintaining fairness, transparency and integrity in all transactions that occur between users of the protocol.


Revisiting again the scenario where SHIB tokens are offered by the maker in exchange for wBTC tokens, let us undertake another exchange simulation to measure the precision loss of the fixed-point arithmetic, but this time using tradebyTarget. Consider a situation where the taker’s goal is to secure 1,654,355,822.074328684134994879 SHIB tokens from the maker’s market, meaning she queries how many wBTC tokens must be relinquished from her wallet to the maker’s order to complete the trade. The smart contract’s fixed-point precision calculation determines that 0.32100123 wBTC tokens are needed. This outcome reveals a difference of only 2.559216 parts per Quadrillion with respect to the maker’s exact calculations, and a difference of only 27.464979 parts per Octillion while taking the curve parametrization on the smart contract at face-value. Notably, the amount of wBTC tokens calculated for the exchange slightly overshoots the arbitrarily precise amount, subtly disadvantaging the taker, akin to the earlier observations.


Function Called: trade_by_target

Trade Input (Token Wei): 1654355822074328684134994879 SHIB (wei scale)

+----------------------------------+-------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC (wei scale)                                                                         |
+----------------------------------+-------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 32100122.99999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | ||||||||.|||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 32100122.99999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |||||||:                                                                                              |
| Fixed-point Precision (Contract) | 32100123                                                                                              |
+----------------------------------+-------------------------------------------------------------------------------------------------------+

Trade Input (Ignoring Decimals): 1654355822.074328684134994879 SHIB

+----------------------------------+--------------------------------------------------------------------------------------------------------+
|                        Precision | Trade Output wBTC                                                                                      |
+----------------------------------+--------------------------------------------------------------------------------------------------------+
|      Arbitrary Precision (Maker) | 0.3210012299999991784886412852376781350937039114955097711412169114634721350541566773448401388225188758 |
|                                  | |.|||||||||||||||::::::::::::::::::::::|:|:|:::::::|:|::::|:::|::::|::::::|:|::::::::::::|:::::::|:::: |
|   Arbitrary Precision (Contract) | 0.3210012299999999999999999911837079065977833525874017487511909540535177810240205627620351953898580437 |
|                                  | |.|||||||:                                                                                             |
| Fixed-point Precision (Contract) | 0.32100123                                                                                             |
+----------------------------------+--------------------------------------------------------------------------