diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..45a452f7e --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,39 @@ +on: [push] + +name: hyperdrive + +jobs: + check: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: install node + uses: actions/setup-node@v3 + with: + node-version: 14.x + + - name: install packages + uses: borales/actions-yarn@v4 + with: + cmd: install # will run `yarn install` command + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # if needed + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Run coverage + run: | + forge coverage --report lcov + sudo apt-get install lcov + lcov --remove lcov.info -o lcov.info 'test/*' 'script/*' + + - name: Coveralls + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: "./lcov.info" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 60a67ac8e..805c7f4d9 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,10 +1,10 @@ on: [push] -name: lint +name: hyperdrive jobs: build: - name: solidity + name: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 258a72fb0..3dfccffcf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,10 +1,10 @@ on: [push] -name: test +name: hyperdrive jobs: check: - name: solidity + name: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 4bb3c375a..7ce3c7dc4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ node_modules/ yarn-error.log .direnv + +lcov.info diff --git a/README.md b/README.md index e2d0bd5d9..ac0218b8e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![Coverage Status](https://coveralls.io/repos/github/element-fi/hyperdrive/badge.svg?branch=via-ir-2&t=US78Aq)](https://coveralls.io/github/element-fi/hyperdrive?branch=via-ir-2) + # Hyperdrive Hyperdrive is an automated market maker that enables fixed-rate markets to be @@ -34,3 +36,5 @@ If you want to automatically format the code, run `yarn prettier`. The language used in this codebase is for coding convenience only, and is not intended to, and does not, have any particular legal or regulatory significance. + +22cc2cx2qxw diff --git a/contracts/libraries/HyperdriveMath.sol b/contracts/libraries/HyperdriveMath.sol index ba509c0cb..298b93bea 100644 --- a/contracts/libraries/HyperdriveMath.sol +++ b/contracts/libraries/HyperdriveMath.sol @@ -133,15 +133,18 @@ library HyperdriveMath { // timeRemaining*amountIn shares to purchase newly minted bonds on a // YieldSpace curve configured to timeRemaining = 1. uint256 curveIn = _amountIn.mulDown(_timeRemaining); + + // Credit the share reserves by the flat trade. + _shareReserves = _shareReserves.add(flat); + // Debit the bond reserves by the flat trade. + _bondReserves = _bondReserves.sub(flat.mulDown(_sharePrice)); + uint256 curveOut = YieldSpaceMath.calculateOutGivenIn( - // Credit the share reserves by the flat trade. - _shareReserves.add(flat), - // Debit the bond reserves by the flat trade. - _bondReserves.sub(flat.mulDown(_sharePrice)), + _shareReserves, + _bondReserves, _bondReserveAdjustment, curveIn, - FixedPointMath.ONE_18, - _timeStretch, + FixedPointMath.ONE_18.sub(_timeStretch), _sharePrice, _initialSharePrice, _isBondOut @@ -162,15 +165,18 @@ library HyperdriveMath { uint256 curveIn = _amountIn.mulDown(_timeRemaining).divDown( _sharePrice ); + + // Debit the share reserves by the flat trade. + _shareReserves = _shareReserves.sub(flat); + // Credit the bond reserves by the flat trade. + _bondReserves = _bondReserves.add(flat.mulDown(_sharePrice)); + uint256 curveOut = YieldSpaceMath.calculateOutGivenIn( - // Debit the share reserves by the flat trade. - _shareReserves.sub(flat), - // Credit the bond reserves by the flat trade. - _bondReserves.add(flat.mulDown(_sharePrice)), + _shareReserves, + _bondReserves, _bondReserveAdjustment, curveIn, - FixedPointMath.ONE_18, - _timeStretch, + FixedPointMath.ONE_18.sub(_timeStretch), _sharePrice, _initialSharePrice, _isBondOut @@ -230,15 +236,17 @@ library HyperdriveMath { uint256 curveOut = _amountOut.mulDown(_timeRemaining).divDown( _sharePrice ); + + // Credit the share reserves by the flat trade. + _shareReserves = _shareReserves.add(flat); + // Debit the bond reserves by the flat trade. + _bondReserves = _bondReserves.sub(flat.mulDown(_sharePrice)); uint256 curveIn = YieldSpaceMath.calculateInGivenOut( - // Credit the share reserves by the flat trade. - _shareReserves.add(flat), - // Debit the bond reserves by the flat trade. - _bondReserves.sub(flat.mulDown(_sharePrice)), + _shareReserves, + _bondReserves, _bondReserveAdjustment, curveOut, - FixedPointMath.ONE_18, - _timeStretch, + FixedPointMath.ONE_18.sub(_timeStretch), _sharePrice, _initialSharePrice, false diff --git a/contracts/libraries/YieldSpaceMath.sol b/contracts/libraries/YieldSpaceMath.sol index 27053f428..3678cdac1 100644 --- a/contracts/libraries/YieldSpaceMath.sol +++ b/contracts/libraries/YieldSpaceMath.sol @@ -19,8 +19,7 @@ library YieldSpaceMath { /// @param bondReserves bond reserves amount, unit is the face value in underlying /// @param bondReserveAdjustment An optional adjustment to the reserve which MUST have units of underlying. /// @param amountIn amount to be traded, if bonds in the unit is underlying, if shares in the unit is shares - /// @param t time till maturity in seconds - /// @param s time stretch coefficient. e.g. 25 years in seconds + /// @param oneMinusT 1 - st /// @param c price of shares in terms of their base /// @param mu Normalization factor -- starts as c at initialization /// @param isBondOut determines if the output is bond or shares @@ -30,56 +29,31 @@ library YieldSpaceMath { uint256 bondReserves, uint256 bondReserveAdjustment, uint256 amountIn, - uint256 t, - uint256 s, + uint256 oneMinusT, uint256 c, uint256 mu, bool isBondOut - ) internal pure returns (uint256 result) { - uint256 outReserves; - uint256 rhs; - // Notes: 1 >= 1-st >= 0 - uint256 oneMinusT = FixedPointMath.ONE_18.sub(s.mulDown(t)); - // c/mu + ) internal pure returns (uint256) { uint256 cDivMu = c.divDown(mu); - // Adjust the bond reserve, optionally shifts the curve around the inflection point - uint256 modifiedBondReserves = bondReserves.add(bondReserveAdjustment); - // c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - uint256 k = cDivMu - .mulDown(mu.mulDown(shareReserves).pow(oneMinusT)) - .add(modifiedBondReserves.pow(oneMinusT)); - + bondReserves = bondReserves.add(bondReserveAdjustment); + uint256 k = _k(cDivMu, mu, shareReserves, oneMinusT, bondReserves); if (isBondOut) { - // bondOut = bondReserves - ( c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - c/mu * (mu*(shareReserves + shareIn))^(1-t) )^(1 / (1 - t)) - outReserves = modifiedBondReserves; - // (mu*(shareReserves + amountIn))^(1-t) - uint256 newScaledShareReserves = mu - .mulDown(shareReserves.add(amountIn)) - .pow(oneMinusT); - // c/mu * (mu*(shareReserves + amountIn))^(1-t) - newScaledShareReserves = cDivMu.mulDown(newScaledShareReserves); - // Notes: k - newScaledShareReserves >= 0 to avoid a complex number - // ( c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - c/mu * (mu*(shareReserves + amountIn))^(1-t) )^(1 / (1 - t)) - rhs = k.sub(newScaledShareReserves).pow( + shareReserves = mu.mulDown(shareReserves.add(amountIn)).pow( + oneMinusT + ); + shareReserves = cDivMu.mulDown(shareReserves); + uint256 rhs = k.sub(shareReserves).pow( FixedPointMath.ONE_18.divDown(oneMinusT) ); + return bondReserves.sub(rhs); } else { - // shareOut = shareReserves - [ ( c/mu * (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves + bondIn)^(1-t) ) / c/u ]^(1 / (1 - t)) / mu - outReserves = shareReserves; - // (bondReserves + bondIn)^(1-t) - uint256 newScaledBondReserves = modifiedBondReserves - .add(amountIn) - .pow(oneMinusT); - // Notes: k - newScaledBondReserves >= 0 to avoid a complex number - // [( (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves + bondIn)^(1-t) ) / c/u ]^(1 / (1 - t)) - rhs = k.sub(newScaledBondReserves).divDown(cDivMu).pow( + bondReserves = bondReserves.add(amountIn).pow(oneMinusT); + uint256 rhs = k.sub(bondReserves).divDown(cDivMu).pow( FixedPointMath.ONE_18.divDown(oneMinusT) ); - // [( (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves + bondIn)^(1-t) ) / c/u ]^(1 / (1 - t)) / mu rhs = rhs.divDown(mu); + return shareReserves.sub(rhs); } - // Notes: outReserves - rhs >= 0, but i think avoiding a complex number in the step above ensures this never happens - result = outReserves.sub(rhs); } /// @dev Calculates the amount of an asset that will be received given a @@ -88,8 +62,7 @@ library YieldSpaceMath { /// @param bondReserves bond reserves amount, unit is the face value in underlying /// @param bondReserveAdjustment An optional adjustment to the reserve which MUST have units of underlying. /// @param amountOut amount to be received, if bonds in the unit is underlying, if shares in the unit is shares - /// @param t time till maturity in seconds - /// @param s time stretch coefficient. e.g. 25 years in seconds + /// @param oneMinusT 1 - st /// @param c price of shares in terms of their base /// @param mu Normalization factor -- starts as c at initialization /// @param isBondIn determines if the input is bond or shares @@ -99,57 +72,43 @@ library YieldSpaceMath { uint256 bondReserves, uint256 bondReserveAdjustment, uint256 amountOut, - uint256 t, - uint256 s, + uint256 oneMinusT, uint256 c, uint256 mu, bool isBondIn - ) internal pure returns (uint256 result) { - uint256 inReserves; - uint256 rhs; - // Notes: 1 >= 1-st >= 0 - uint256 oneMinusT = FixedPointMath.ONE_18.sub(s.mulDown(t)); - // c/mu + ) internal pure returns (uint256) { uint256 cDivMu = c.divDown(mu); - // Adjust the bond reserve, optionally shifts the curve around the inflection point - uint256 modifiedBondReserves = bondReserves.add(bondReserveAdjustment); - // c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - uint256 k = cDivMu - .mulDown(mu.mulDown(shareReserves).pow(oneMinusT)) - .add(modifiedBondReserves.pow(oneMinusT)); - + bondReserves = bondReserves.add(bondReserveAdjustment); + uint256 k = _k(cDivMu, mu, shareReserves, oneMinusT, bondReserves); if (isBondIn) { - // bondIn = ( c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - c/mu * (mu*(shareReserves - shareOut))^(1-t) )^(1 / (1 - t)) - bond_reserves - inReserves = modifiedBondReserves; - // (mu*(shareReserves - amountOut))^(1-t) - uint256 newScaledShareReserves = mu - .mulDown(shareReserves.sub(amountOut)) - .pow(oneMinusT); - // c/mu * (mu*(shareReserves - amountOut))^(1-t) - newScaledShareReserves = cDivMu.mulDown(newScaledShareReserves); - // Notes: k - newScaledShareReserves >= 0 to avoid a complex number - // ( c/mu * (mu*shareReserves)^(1-t) + bondReserves^(1-t) - c/mu * (mu*(shareReserves - amountOut))^(1-t) )^(1 / (1 - t)) - rhs = k.sub(newScaledShareReserves).pow( + shareReserves = mu.mulDown(shareReserves.sub(amountOut)).pow( + oneMinusT + ); + shareReserves = cDivMu.mulDown(shareReserves); + uint256 rhs = k.sub(shareReserves).pow( FixedPointMath.ONE_18.divDown(oneMinusT) ); + return rhs.sub(bondReserves); } else { - // shareOut = [ ( c/mu * (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves - bondOut)^(1-t) ) / c/u ]^(1 / (1 - t)) / mu - share_reserves - inReserves = shareReserves; - // (bondReserves - amountOut)^(1-t) - uint256 newScaledBondReserves = modifiedBondReserves - .sub(amountOut) - .pow(oneMinusT); - // Notes: k - newScaledBondReserves >= 0 to avoid a complex number - // [( (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves - amountOut)^(1-t) ) / c/u ]^(1 / (1 - t)) - rhs = k.sub(newScaledBondReserves).divDown(cDivMu).pow( + bondReserves = bondReserves.sub(amountOut).pow(oneMinusT); + uint256 rhs = k.sub(bondReserves).divDown(cDivMu).pow( FixedPointMath.ONE_18.divDown(oneMinusT) ); - // [( (mu * shareReserves)^(1-t) + bondReserves^(1-t) - (bondReserves - amountOut)^(1-t) ) / c/u ]^(1 / (1 - t)) / mu rhs = rhs.divDown(mu); + return rhs.sub(shareReserves); } - // TODO: Double check this. - // - // Notes: rhs - inReserves >= 0, but i think avoiding a complex number in the step above ensures this never happens - result = rhs.sub(inReserves); + } + + function _k( + uint256 cDivMu, + uint256 mu, + uint256 shareReserves, + uint256 oneMinusT, + uint256 modifiedBondReserves + ) private pure returns (uint256) { + return + cDivMu.mulDown(mu.mulDown(shareReserves).pow(oneMinusT)).add( + modifiedBondReserves.pow(oneMinusT) + ); } } diff --git a/foundry.toml b/foundry.toml index df0b7e6b4..16fcc9adc 100644 --- a/foundry.toml +++ b/foundry.toml @@ -16,7 +16,7 @@ optimizer = true # The number of optimizer runs optimizer_runs = 7500 # Whether or not to use the Yul intermediate representation compilation pipeline -via_ir = true +via_ir = false remappings = ['forge-std=lib/forge-std/src', '@openzeppelin/=node_modules/@openzeppelin'] # gas limit - max u64 diff --git a/test/YieldSpaceMath.t.sol b/test/YieldSpaceMath.t.sol index d271a9a9c..e4be630d1 100644 --- a/test/YieldSpaceMath.t.sol +++ b/test/YieldSpaceMath.t.sol @@ -12,8 +12,7 @@ contract YieldSpaceMathTest is Test { 62.38101813e18, // bondReserves 119.1741606776616e18, // bondReserveAdjustment 5.03176076e18, // amountOut - 0.08065076081220067e18, // t - 1e18, // s + 1e18 - 0.08065076081220067e18, // t 1e18, // c 1e18, // mu true // isBondIn @@ -29,8 +28,7 @@ contract YieldSpaceMathTest is Test { 56.92761678068477e18, // bondReserves 119.1741606776616e18, // bondReserveAdjustment 5.500250311701939e18, // amountOut - 0.08065076081220067e18, // t - 1e18, // s + 1e18 - 0.08065076081220067e18, // t 1e18, // c 1e18, // mu false // isBondIn