From b0387245f052a62c8ac6e2b8898eff4bdfae3b27 Mon Sep 17 00:00:00 2001 From: Lawrence Forman Date: Wed, 5 Feb 2020 01:49:47 -0500 Subject: [PATCH] `@0x/contracts-utils`: Roll back additions to `LibFractions`. `@0x/contracts-dev-utils`: Add `D18` library for working with base-10, 18-digit decimals. `@0x/contracts-dev-utils`: Use `D18` library instead of `LibFractions` in `LibDydxBalance`. --- contracts/dev-utils/CHANGELOG.json | 4 + contracts/dev-utils/contracts/src/D18.sol | 239 ++++++++++++++++++ contracts/dev-utils/package.json | 2 +- contracts/dev-utils/test/artifacts.ts | 2 + contracts/dev-utils/test/wrappers.ts | 1 + contracts/dev-utils/tsconfig.json | 1 + contracts/utils/CHANGELOG.json | 4 - .../utils/contracts/src/LibFractions.sol | 96 +------ 8 files changed, 249 insertions(+), 100 deletions(-) create mode 100644 contracts/dev-utils/contracts/src/D18.sol diff --git a/contracts/dev-utils/CHANGELOG.json b/contracts/dev-utils/CHANGELOG.json index 7e57ffa7d0..d0495169a2 100644 --- a/contracts/dev-utils/CHANGELOG.json +++ b/contracts/dev-utils/CHANGELOG.json @@ -50,6 +50,10 @@ { "note": "Add `DydxBridge` order validation", "pr": 2466 + }, + { + "note": "Add `D18` library for working with base-10, 18-precision decimals", + "pr": 2466 } ], "timestamp": 1581204851 diff --git a/contracts/dev-utils/contracts/src/D18.sol b/contracts/dev-utils/contracts/src/D18.sol new file mode 100644 index 0000000000..f491976186 --- /dev/null +++ b/contracts/dev-utils/contracts/src/D18.sol @@ -0,0 +1,239 @@ +/* + + Copyright 2019 ZeroEx Intl. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pragma solidity ^0.5.16; + + +/// @dev A library for working with 18 digit, base 10 decimals. +library D18 { + + /// @dev Decimal places for dydx value quantities. + uint256 private constant PRECISION = 18; + /// @dev 1.0 in base-18 decimal. + int256 private constant DECIMAL_ONE = int256(10 ** PRECISION); + /// @dev Minimum signed integer value. + int256 private constant MIN_INT256_VALUE = int256(0x8000000000000000000000000000000000000000000000000000000000000000); + + /// @dev Return `1.0` + function one() + internal + pure + returns (int256 r) + { + r = DECIMAL_ONE; + } + + /// @dev Add two decimals. + function add(int256 a, int256 b) + internal + pure + returns (int256 r) + { + r = _add(a, b); + } + + /// @dev Add two decimals. + function add(uint256 a, int256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _add(int256(a), b); + } + + /// @dev Add two decimals. + function add(uint256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _add(int256(a), int256(b)); + } + + /// @dev Subract two decimals. + function sub(int256 a, int256 b) + internal + pure + returns (int256 r) + { + r = _add(a, -b); + } + + /// @dev Subract two decimals. + function sub(uint256 a, int256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _add(int256(a), -b); + } + + /// @dev Subract two decimals. + function sub(uint256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _add(int256(a), -int256(b)); + } + + /// @dev Multiply two decimals. + function mul(int256 a, int256 b) + internal + pure + returns (int256 r) + { + r = _div(_mul(a, b), DECIMAL_ONE); + } + + /// @dev Multiply two decimals. + function mul(uint256 a, int256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(int256(a), b), DECIMAL_ONE); + } + + /// @dev Multiply two decimals. + function mul(int256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(a, int256(b)), DECIMAL_ONE); + } + + /// @dev Multiply two decimals. + function mul(uint256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(int256(a), int256(b)), DECIMAL_ONE); + } + + /// @dev Divide two decimals. + function div(int256 a, int256 b) + internal + pure + returns (int256 r) + { + require(a != MIN_INT256_VALUE || b != -1, "D18/DECIMAL_MUL_OVERFLOW"); + r = _div(_mul(a, DECIMAL_ONE), b); + } + + /// @dev Divide two decimals. + function div(uint256 a, int256 b) + internal + pure + returns (int256 r) + { + require(int256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(int256(a), DECIMAL_ONE), b); + } + + /// @dev Divide two decimals. + function div(int256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(int256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(a, DECIMAL_ONE), int256(b)); + } + + /// @dev Divide two decimals. + function div(uint256 a, uint256 b) + internal + pure + returns (int256 r) + { + require(uint256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + require(uint256(b) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = _div(_mul(int256(a), DECIMAL_ONE), int256(b)); + } + + /// @dev Safely convert an unsigned integer into a signed integer. + function toSigned(uint256 a) + internal + pure + returns (int256 r) + { + require(uint256(a) >= 0, "D18/DECIMAL_VALUE_TOO_BIG"); + r = int256(a); + } + + /// @dev Clip a signed value to be positive. + function clip(int256 a) + internal + pure + returns (int256 r) + { + r = a < 0 ? 0 : a; + } + + /// @dev Safely multiply two signed integers. + function _mul(int256 a, int256 b) + private + pure + returns (int256 r) + { + if (a == 0 || b == 0) { + return 0; + } + r = a * b; + require(r / a == b && r / b == a, "D18/DECIMAL_MUL_OVERFLOW"); + return r; + } + + /// @dev Safely divide two signed integers. + function _div(int256 a, int256 b) + private + pure + returns (int256 r) + { + require(b != 0, "D18/DECIMAL_DIVISION_BY_ZERO"); + require(a != MIN_INT256_VALUE || b != -1, "D18/DECIMAL_MUL_OVERFLOW"); + r = a / b; + } + + /// @dev Safely add two signed integers. + function _add(int256 a, int256 b) + private + pure + returns (int256 r) + { + r = a + b; + require( + !((a < 0 && b < 0 && r > a) || (a > 0 && b > 0 && r < a)), + "D18/DECIMAL_MUL_OVERFLOW" + ); + } + +} diff --git a/contracts/dev-utils/package.json b/contracts/dev-utils/package.json index c54868e86d..8788816691 100644 --- a/contracts/dev-utils/package.json +++ b/contracts/dev-utils/package.json @@ -28,7 +28,7 @@ }, "config": { "publicInterfaceContracts": "DevUtils,LibAssetData,LibDydxBalance,LibOrderTransferSimulation,LibTransactionDecoder", - "abis": "./test/generated-artifacts/@(Addresses|AssetBalance|DevUtils|EthBalanceChecker|ExternalFunctions|LibAssetData|LibDydxBalance|LibOrderTransferSimulation|LibTransactionDecoder|OrderTransferSimulationUtils|OrderValidationUtils|TestDydx|TestLibDydxBalance).json", + "abis": "./test/generated-artifacts/@(Addresses|AssetBalance|D18|DevUtils|EthBalanceChecker|ExternalFunctions|LibAssetData|LibDydxBalance|LibOrderTransferSimulation|LibTransactionDecoder|OrderTransferSimulationUtils|OrderValidationUtils|TestDydx|TestLibDydxBalance).json", "abis:comment": "This list is auto-generated by contracts-gen. Don't edit manually." }, "repository": { diff --git a/contracts/dev-utils/test/artifacts.ts b/contracts/dev-utils/test/artifacts.ts index e4c1802bc0..a4ff20758e 100644 --- a/contracts/dev-utils/test/artifacts.ts +++ b/contracts/dev-utils/test/artifacts.ts @@ -7,6 +7,7 @@ import { ContractArtifact } from 'ethereum-types'; import * as Addresses from '../test/generated-artifacts/Addresses.json'; import * as AssetBalance from '../test/generated-artifacts/AssetBalance.json'; +import * as D18 from '../test/generated-artifacts/D18.json'; import * as DevUtils from '../test/generated-artifacts/DevUtils.json'; import * as EthBalanceChecker from '../test/generated-artifacts/EthBalanceChecker.json'; import * as ExternalFunctions from '../test/generated-artifacts/ExternalFunctions.json'; @@ -21,6 +22,7 @@ import * as TestLibDydxBalance from '../test/generated-artifacts/TestLibDydxBala export const artifacts = { Addresses: Addresses as ContractArtifact, AssetBalance: AssetBalance as ContractArtifact, + D18: D18 as ContractArtifact, DevUtils: DevUtils as ContractArtifact, EthBalanceChecker: EthBalanceChecker as ContractArtifact, ExternalFunctions: ExternalFunctions as ContractArtifact, diff --git a/contracts/dev-utils/test/wrappers.ts b/contracts/dev-utils/test/wrappers.ts index 01f291fa09..587e4c6281 100644 --- a/contracts/dev-utils/test/wrappers.ts +++ b/contracts/dev-utils/test/wrappers.ts @@ -5,6 +5,7 @@ */ export * from '../test/generated-wrappers/addresses'; export * from '../test/generated-wrappers/asset_balance'; +export * from '../test/generated-wrappers/d18'; export * from '../test/generated-wrappers/dev_utils'; export * from '../test/generated-wrappers/eth_balance_checker'; export * from '../test/generated-wrappers/external_functions'; diff --git a/contracts/dev-utils/tsconfig.json b/contracts/dev-utils/tsconfig.json index eccc9bf633..b2b229e0c7 100644 --- a/contracts/dev-utils/tsconfig.json +++ b/contracts/dev-utils/tsconfig.json @@ -10,6 +10,7 @@ "generated-artifacts/LibTransactionDecoder.json", "test/generated-artifacts/Addresses.json", "test/generated-artifacts/AssetBalance.json", + "test/generated-artifacts/D18.json", "test/generated-artifacts/DevUtils.json", "test/generated-artifacts/EthBalanceChecker.json", "test/generated-artifacts/ExternalFunctions.json", diff --git a/contracts/utils/CHANGELOG.json b/contracts/utils/CHANGELOG.json index 5c22139cf5..cef6aca18c 100644 --- a/contracts/utils/CHANGELOG.json +++ b/contracts/utils/CHANGELOG.json @@ -56,10 +56,6 @@ { "note": "Export `EvmBytecodeOutputLinkReferences` type.", "pr": 2462 - }, - { - "note": "Add more functions to `LibFractions`.", - "pr": 2466 } ], "timestamp": 1580811564 diff --git a/contracts/utils/contracts/src/LibFractions.sol b/contracts/utils/contracts/src/LibFractions.sol index 1582733988..f5a05706c6 100644 --- a/contracts/utils/contracts/src/LibFractions.sol +++ b/contracts/utils/contracts/src/LibFractions.sol @@ -37,101 +37,7 @@ library LibFractions { .safeMul(d2) .safeAdd(n2.safeMul(d1)); denominator = d1.safeMul(d2); - return normalize(numerator, denominator); - } - - /// @dev Safely subracts two fractions `n1/d1 - n2/d2` - /// @param n1 numerator of `1` - /// @param d1 denominator of `1` - /// @param n2 numerator of `2` - /// @param d2 denominator of `2` - /// @return numerator Numerator of sum - /// @return denominator Denominator of sum - function sub( - uint256 n1, - uint256 d1, - uint256 n2, - uint256 d2 - ) - internal - pure - returns ( - uint256 numerator, - uint256 denominator - ) - { - if (n2 == 0) { - return (numerator = n1, denominator = d1); - } - numerator = n1 - .safeMul(d2) - .safeSub(n2.safeMul(d1)); - denominator = d1.safeMul(d2); - return normalize(numerator, denominator); - } - - /// @dev Multiply two fractions. - /// @param n1 numerator of `1` - /// @param d1 denominator of `1` - /// @param n2 numerator of `2` - /// @param d2 denominator of `2` - /// @return numerator numerator of product. - /// @return denominator numerator of product. - function mul( - uint256 n1, - uint256 d1, - uint256 n2, - uint256 d2 - ) - internal - pure - returns ( - uint256 numerator, - uint256 denominator - ) - { - return normalize(n1.safeMul(n2), d1.safeMul(d2)); - } - - /// @dev Compares two fractions. - /// @param n1 numerator of `1` - /// @param d1 denominator of `1` - /// @param n2 numerator of `2` - /// @param d2 denominator of `2` - /// @return compareResult - /// `-1` if `n1/d1 < n2/d2`. - /// `0` if `n1/d1 == n2/d2`. - /// `1` if `n1/d1 > n2/d2`. - function cmp( - uint256 n1, - uint256 d1, - uint256 n2, - uint256 d2 - ) - internal - pure - returns (int8 compareResult) - { - // Handle infinities. - if (d1 == 0) { - if (d2 == 0) { - return 0; - } - return 1; - } else { - if (d2 == 0) { - return -1; - } - } - uint256 nd1 = n1.safeMul(d2); - uint256 nd2 = n2.safeMul(d1); - if (nd1 > nd2) { - return 1; - } - if (nd1 < nd2) { - return -1; - } - return 0; + return (numerator, denominator); } /// @dev Rescales a fraction to prevent overflows during addition if either