diff --git a/.env_example b/.env_example new file mode 100644 index 0000000..35a2bc1 --- /dev/null +++ b/.env_example @@ -0,0 +1 @@ +PRIVATE_KEY= diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/script/deployRootFinder.s.sol b/script/deployRootFinder.s.sol new file mode 100644 index 0000000..d05bc5b --- /dev/null +++ b/script/deployRootFinder.s.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "../src/RootFinder.sol"; + +contract DeployRootFinder is Script { + function run() external { + // Begin broadcasting transactions + vm.startBroadcast(); + + // Deploy the RootFinder contract + RootFinder rootFinder = new RootFinder(); + + // Log the address of the deployed contract + console.log("RootFinder deployed at:", address(rootFinder)); + + // Stop broadcasting transactions + vm.stopBroadcast(); + } +} diff --git a/src/rootFinder.sol b/src/rootFinder.sol index e6a0890..533fd00 100644 --- a/src/rootFinder.sol +++ b/src/rootFinder.sol @@ -8,10 +8,17 @@ import { ERC20 } from "lib/solmate/src/tokens/ERC20.sol"; using FixedPointMathLib for uint256; using FixedPointMathLib for int256; +/// @title RootFinder +/// @notice A contract for finding roots using numerical methods +/// @dev Uses Newton's method for root finding contract RootFinder { uint256 private constant MAX_ITERATIONS = 20; uint256 private constant TOLERANCE = 10; + /// @notice Finds the root of a function using Newton's method + /// @param args Encoded arguments for the function + /// @param initialGuess Initial guess for the root + /// @return reserveX_ The found root function findRootNewX(bytes memory args, uint256 initialGuess) public pure returns (uint256 reserveX_) { reserveX_ = initialGuess; int256 reserveX_next; @@ -36,6 +43,10 @@ contract RootFinder { } } + /// @notice Computes the derivative of the trading function with respect to reserveX + /// @param args Encoded arguments for the function + /// @param rX Current value of reserveX + /// @return The computed derivative function computeTfDReserveX(bytes memory args, uint256 rX) internal pure returns (int256) { (, uint256 L,,,) = abi.decode(args, (uint256, uint256, uint256, uint256, uint256)); int256 a = Gaussian.ppf(toInt(rX * 1e18 / L)); @@ -43,6 +54,10 @@ contract RootFinder { return 1e36 / (int256(L) * pdf_a / 1e18); } + /// @notice Finds the value of X in the trading function + /// @param data Encoded arguments for the function + /// @param x Current value of X + /// @return The computed value of the trading function function findX(bytes memory data, uint256 x) internal pure returns (int256) { (uint256 reserveY_, uint256 liquidity, uint256 strike_, uint256 sigma_, uint256 tau_) = abi.decode(data, (uint256, uint256, uint256, uint256, uint256)); @@ -50,6 +65,14 @@ contract RootFinder { return computeTradingFunction(x, reserveY_, liquidity, strike_, sigma_, tau_); } + /// @notice Computes the trading function + /// @param reserveX_ Reserve X + /// @param reserveY_ Reserve Y + /// @param liquidity Liquidity + /// @param strike_ Strike price + /// @param sigma_ Volatility + /// @param tau_ Time to expiration + /// @return The computed value of the trading function function computeTradingFunction( uint256 reserveX_, uint256 reserveY_, @@ -67,18 +90,27 @@ contract RootFinder { return a + b + c; } + /// @notice Computes sigma * sqrt(tau) + /// @param sigma_ Volatility + /// @param tau_ Time to expiration + /// @return The computed value of sigma * sqrt(tau) function computeSigmaSqrtTau(uint256 sigma_, uint256 tau_) internal pure returns (uint256) { uint256 sqrtTau = FixedPointMathLib.sqrt(tau_) * 1e9; return sigma_.mulWadUp(sqrtTau); } + /// @notice Converts a uint256 to an int256 + /// @param x The uint256 value to convert + /// @return The converted int256 value function toInt(uint256 x) internal pure returns (int256) { require(x <= uint256(type(int256).max), "ToIntOverflow"); return int256(x); } + /// @notice Computes the absolute value of an int256 + /// @param x The int256 value + /// @return The absolute value of x function abs(int256 x) internal pure returns (int256) { return x < 0 ? -x : x; } } - diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/rootFinder.t.sol b/test/rootFinder.t.sol new file mode 100644 index 0000000..7a8db20 --- /dev/null +++ b/test/rootFinder.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/RootFinder.sol"; + +contract RootFinderTest is Test { + RootFinder rootFinder; + + // Initialize the RootFinder contract before each test + function setUp() public { + rootFinder = new RootFinder(); + } + + // Test findRootNewX to ensure it converges on the expected result + // for a given set of input parameters + function testFindRootNewX() public { + // Define inputs for findRootNewX + uint256 reserveY_ = 1000 ether; + uint256 liquidity = 5000 ether; + uint256 strike_ = 1500 ether; + uint256 sigma_ = 1e18; // 100% volatility + uint256 tau_ = 365 days; // 1-year duration + + bytes memory args = abi.encode(reserveY_, liquidity, strike_, sigma_, tau_); + + uint256 initialGuess = 1500 ether; + uint256 result = rootFinder.findRootNewX(args, initialGuess); + + // Assert result within 1 ether tolerance + uint256 expectedValue = 1500 ether; // TODO: Replace with actual expected value + assertApproxEqAbs(result, expectedValue, 1 ether, "Root did not converge to expected value within tolerance"); + } + + // Test computeTfDReserveX to verify the accuracy of the derivative computation + function testComputeTfDReserveX() public { + // Define inputs for derivative computation + uint256 reserveY_ = 1000 ether; + uint256 liquidity = 5000 ether; + uint256 strike_ = 1500 ether; + uint256 sigma_ = 1e18; // 100% volatility + uint256 tau_ = 365 days; // 1-year duration + + bytes memory args = abi.encode(reserveY_, liquidity, strike_, sigma_, tau_); + uint256 rX = 1200 ether; + + int256 derivative = rootFinder.computeTfDReserveX(args, rX); + int256 expectedDerivative = -1e18; // TODO: Replace with actual expected derivative + + assertApproxEqAbs(derivative, expectedDerivative, 1e16, "Computed derivative does not match expected value within tolerance"); + } + + // Test findX function to ensure it returns the expected value for given inputs + function testFindX() public { + // Test data for findX + uint256 reserveY_ = 1000 ether; + uint256 liquidity = 5000 ether; + uint256 strike_ = 1500 ether; + uint256 sigma_ = 1e18; // 100% volatility + uint256 tau_ = 365 days; // 1-year duration + + bytes memory data = abi.encode(reserveY_, liquidity, strike_, sigma_, tau_); + uint256 x = 1200 ether; + + int256 result = rootFinder.findX(data, x); + + int256 expectedValue = 0; // TODO: Replace with actual expected outcome + assertApproxEqAbs(result, expectedValue, 1e16, "findX returned unexpected value"); + } + + // Test computeTradingFunction with known parameters to verify correct calculation + function testComputeTradingFunction() public { + uint256 reserveX_ = 2000 ether; + uint256 reserveY_ = 3000 ether; + uint256 liquidity = 10000 ether; + uint256 strike_ = 1500 ether; + uint256 sigma_ = 1e18; // 100% volatility + uint256 tau_ = 365 days; // 1-year duration + + int256 result = rootFinder.computeTradingFunction(reserveX_, reserveY_, liquidity, strike_, sigma_, tau_); + + int256 expectedValue = 1000; // TODO: Replace with actual expected result + assertApproxEqAbs(result, expectedValue, 1e16, "computeTradingFunction result does not match expected value"); + } + + // Test computeSigmaSqrtTau to ensure correct calculation of sigma * sqrt(tau) + function testComputeSigmaSqrtTau() public { + uint256 sigma_ = 1e18; // 100% volatility + uint256 tau_ = 365 days; // 1-year duration + + uint256 result = rootFinder.computeSigmaSqrtTau(sigma_, tau_); + + uint256 expectedValue = 1e18; // TODO: Replace with actual expected sigma * sqrt(tau) result + assertApproxEqAbs(result, expectedValue, 1e16, "computeSigmaSqrtTau result does not match expected value"); + } + + // Test toInt function for correct uint256 to int256 conversion and overflow handling + function testToInt() public { + uint256 largeNumber = type(uint256).max; + + // Expect revert for input larger than max int256 + vm.expectRevert(bytes("ToIntOverflow")); + rootFinder.toInt(largeNumber); + + uint256 validNumber = uint256(type(int256).max); + int256 result = rootFinder.toInt(validNumber); + + assertEq(result, int256(validNumber), "toInt did not convert uint256 to int256 correctly"); + } + + // Test abs function to ensure correct absolute value calculation for positive and negative inputs + function testAbs() public { + int256 positiveValue = 1e18; + int256 negativeValue = -1e18; + + assertEq(rootFinder.abs(positiveValue), 1e18, "abs failed to return correct value for positive input"); + assertEq(rootFinder.abs(negativeValue), 1e18, "abs failed to return correct value for negative input"); + } +}