From 6a2b956d6b9df4639358368c93e518ea458f5d68 Mon Sep 17 00:00:00 2001 From: Razvan Pop Date: Wed, 3 Mar 2021 17:59:31 +0200 Subject: [PATCH] SY.buyBond() returns gain BondModel is pure, and receives dependancies as params YieldOracle no longer depends on currentCumulatives() computations are now based on cToken.exchangeRateCurrent() --- contracts/IController.sol | 4 +- contracts/IProvider.sol | 2 +- contracts/ISmartYield.sol | 16 +++-- contracts/SmartYield.sol | 59 ++++++++++--------- .../compound-finance/ICToken.sol | 1 + ...odelMock.sol => BondModelMock.sol.REMOVED} | 0 ...sol => SmartYieldForModelMock.sol.REMOVED} | 2 +- contracts/model/BondModelV1.sol | 28 ++++----- contracts/model/IBondModel.sol | 4 +- contracts/oracle/IYieldOracle.sol | 2 +- contracts/oracle/IYieldOraclelizable.sol | 9 +-- contracts/oracle/YieldOracle.sol | 13 ++-- contracts/providers/CompoundController.sol | 41 +++++++++++-- contracts/providers/CompoundProvider.sol | 5 +- 14 files changed, 103 insertions(+), 83 deletions(-) rename contracts/mocks/barnbridge/{BondModelMock.sol => BondModelMock.sol.REMOVED} (100%) rename contracts/mocks/barnbridge/{SmartYieldForModelMock.sol => SmartYieldForModelMock.sol.REMOVED} (92%) diff --git a/contracts/IController.sol b/contracts/IController.sol index 8ebecef..0402228 100644 --- a/contracts/IController.sol +++ b/contracts/IController.sol @@ -4,7 +4,7 @@ pragma abicoder v2; import "./Governed.sol"; -contract IController is Governed { +abstract contract IController is Governed { address public oracle; // IYieldOracle @@ -99,4 +99,6 @@ contract IController is Governed { { feesOwner = newVal_; } + + function providerRatePerDay() external virtual returns (uint256); } diff --git a/contracts/IProvider.sol b/contracts/IProvider.sol index fc51d7d..86372c8 100644 --- a/contracts/IProvider.sol +++ b/contracts/IProvider.sol @@ -23,6 +23,6 @@ interface IProvider { function transferFees() external; // current total underlying balance as measured by the provider pool - function underlyingBalance() external view returns (uint256); + function underlyingBalance() external returns (uint256); } diff --git a/contracts/ISmartYield.sol b/contracts/ISmartYield.sol index be5ad5e..126f97d 100644 --- a/contracts/ISmartYield.sol +++ b/contracts/ISmartYield.sol @@ -36,7 +36,7 @@ interface ISmartYield { function currentTime() external view returns(uint256); - function buyBond(uint256 principalAmount_, uint256 minGain_, uint256 deadline_, uint16 forDays_) external; + function buyBond(uint256 principalAmount_, uint256 minGain_, uint256 deadline_, uint16 forDays_) external returns (uint256); function redeemBond(uint256 bondId_) external; @@ -56,7 +56,7 @@ interface ISmartYield { /** * token purchase price */ - function price() external view returns (uint256); + function price() external returns (uint256); function abondPaid() external view returns (uint256); @@ -67,18 +67,16 @@ interface ISmartYield { /** * @notice current total underlying balance, without accruing interest */ - function underlyingTotal() external view returns (uint256); + function underlyingTotal() external returns (uint256); /** * @notice current underlying loanable, without accruing interest */ - function underlyingLoanable() external view returns (uint256); + function underlyingLoanable() external returns (uint256); - function underlyingJuniors() external view returns (uint256); + function underlyingJuniors() external returns (uint256); - function providerRatePerDay() external view returns (uint256); + function bondGain(uint256 principalAmount_, uint16 forDays_) external returns (uint256); - function bondGain(uint256 principalAmount_, uint16 forDays_) external view returns (uint256); - - function maxBondDailyRate() external view returns (uint256); + function maxBondDailyRate() external returns (uint256); } diff --git a/contracts/SmartYield.sol b/contracts/SmartYield.sol index f291e30..6d5b22d 100644 --- a/contracts/SmartYield.sol +++ b/contracts/SmartYield.sol @@ -193,6 +193,7 @@ contract SmartYield is } // Purchase a senior bond with principalAmount_ underlying for forDays_, buyer gets a bond with gain >= minGain_ or revert. deadline_ is timestamp before which tx is not rejected. + // returns gain function buyBond( uint256 principalAmount_, uint256 minGain_, @@ -200,6 +201,7 @@ contract SmartYield is uint16 forDays_ ) external override + returns (uint256) { _beforeProviderOp(); @@ -218,6 +220,15 @@ contract SmartYield is "SY: buyBond forDays" ); + uint256 issuedAt = this.currentTime(); + + // --- + + address buyer = msg.sender; + + IProvider(pool)._takeUnderlying(buyer, principalAmount_); + IProvider(pool)._depositProvider(principalAmount_, 0); + uint256 gain = this.bondGain(principalAmount_, forDays_); require( @@ -235,15 +246,6 @@ contract SmartYield is "SY: buyBond underlyingLoanable" ); - uint256 issuedAt = this.currentTime(); - - // --- - - address buyer = msg.sender; - - IProvider(pool)._takeUnderlying(buyer, principalAmount_); - IProvider(pool)._depositProvider(principalAmount_, 0); - SeniorBond memory b = SeniorBond( principalAmount_, @@ -256,6 +258,8 @@ contract SmartYield is _mintBond(buyer, b); emit BuySeniorBond(buyer, seniorBondId, principalAmount_, gain, forDays_); + + return gain; } // buy an nft with tokenAmount_ jTokens, that matures at abond maturesAt @@ -371,32 +375,31 @@ contract SmartYield is emit RedeemJuniorBond(payTo, jBondId_, payAmnt); } - - function providerRatePerDay() - external view virtual override - returns (uint256) - { - return MathUtils.min( - IController(controller).BOND_MAX_RATE_PER_DAY(), - IYieldOracle(IController(controller).oracle()).consult(1 days) - ); - } - // given a principal amount and a number of days, compute the guaranteed bond gain, excluding principal function bondGain(uint256 principalAmount_, uint16 forDays_) - external view override + external override returns (uint256) { - return IBondModel(IController(controller).bondModel()).gain(address(this), principalAmount_, forDays_); + return IBondModel(IController(controller).bondModel()).gain( + this.underlyingTotal(), + this.underlyingLoanable(), + IController(controller).providerRatePerDay(), + principalAmount_, + forDays_ + ); } // returns the maximum theoretically possible daily rate for senior bonds, // in reality the actual rate given to a bond will always be lower due to slippage function maxBondDailyRate() - external view override + external override returns (uint256) { - return IBondModel(IController(controller).bondModel()).maxDailyRate(address(this)); + return IBondModel(IController(controller).bondModel()).maxDailyRate( + this.underlyingTotal(), + this.underlyingLoanable(), + IController(controller).providerRatePerDay() + ); } // /externals @@ -413,7 +416,7 @@ contract SmartYield is // jToken price * 1e18 function price() - public view override + public override returns (uint256) { uint256 ts = totalSupply(); @@ -421,21 +424,21 @@ contract SmartYield is } function underlyingTotal() - public view virtual override + public virtual override returns(uint256) { return IProvider(pool).underlyingBalance() - IProvider(pool).underlyingFees() - underlyingLiquidatedJuniors; } function underlyingJuniors() - public view virtual override + public virtual override returns (uint256) { return this.underlyingTotal() - abond.principal - this.abondPaid(); } function underlyingLoanable() - public view virtual override + public virtual override returns (uint256) { // underlyingTotal - abond.principal - abond.gain - queued withdrawls diff --git a/contracts/external-interfaces/compound-finance/ICToken.sol b/contracts/external-interfaces/compound-finance/ICToken.sol index 1c1534f..1c71582 100644 --- a/contracts/external-interfaces/compound-finance/ICToken.sol +++ b/contracts/external-interfaces/compound-finance/ICToken.sol @@ -7,6 +7,7 @@ interface ICToken { function redeemUnderlying(uint redeemAmount) external returns (uint256); function accrueInterest() external returns (uint256); function exchangeRateStored() external view returns (uint256); + function exchangeRateCurrent() external returns (uint256); function supplyRatePerBlock() external view returns (uint256); function totalBorrows() external view returns (uint256); function getCash() external view returns (uint256); diff --git a/contracts/mocks/barnbridge/BondModelMock.sol b/contracts/mocks/barnbridge/BondModelMock.sol.REMOVED similarity index 100% rename from contracts/mocks/barnbridge/BondModelMock.sol rename to contracts/mocks/barnbridge/BondModelMock.sol.REMOVED diff --git a/contracts/mocks/barnbridge/SmartYieldForModelMock.sol b/contracts/mocks/barnbridge/SmartYieldForModelMock.sol.REMOVED similarity index 92% rename from contracts/mocks/barnbridge/SmartYieldForModelMock.sol rename to contracts/mocks/barnbridge/SmartYieldForModelMock.sol.REMOVED index 241df89..d198fe1 100644 --- a/contracts/mocks/barnbridge/SmartYieldForModelMock.sol +++ b/contracts/mocks/barnbridge/SmartYieldForModelMock.sol.REMOVED @@ -36,7 +36,7 @@ contract SmartYieldForModelMock is HasClock, SmartYield { } function checkGas(uint256 principal, uint16 forDays) public { - _lastCheckGas = IBondModel(IController(controller).bondModel()).gain(address(this), principal, forDays); + //_lastCheckGas = IBondModel(IController(controller).bondModel()).gain(address(this), principal, forDays); } function setMockValues(uint256 underlyingLoanable_, uint256 underlyingTotal_, uint256 providerRatePerDay_) public { diff --git a/contracts/model/BondModelV1.sol b/contracts/model/BondModelV1.sol index a997321..7979fc8 100644 --- a/contracts/model/BondModelV1.sol +++ b/contracts/model/BondModelV1.sol @@ -12,43 +12,39 @@ contract BondModelV1 is IBondModel { using SafeMath for uint256; function gain( - address pool_, + uint256 total_, + uint256 loanable_, + uint256 dailyRate_, uint256 principal_, uint16 forDays_ ) - external view override + external pure override returns (uint256) { - uint256 loanable = ISmartYield(pool_).underlyingLoanable(); - uint256 total = ISmartYield(pool_).underlyingTotal(); - uint256 dailyRate = ISmartYield(pool_).providerRatePerDay(); - uint256 aproxGain = MathUtils.compound2( principal_, //dailyRate * (loanable * 1e18 / (total + principal)) / 1e18, - uint256(1e18).mul(dailyRate).mul(loanable) / (total + principal_) / 1e18, + uint256(1e18).mul(dailyRate_).mul(loanable_) / (total_ + principal_) / 1e18, forDays_ ).sub(principal_); - uint256 rate = uint256(1e18).mul(dailyRate).mul(loanable.sub(aproxGain, "BondModelV1: liquidity")) / (total + principal_) / 1e18; + uint256 rate = uint256(1e18).mul(dailyRate_).mul(loanable_.sub(aproxGain, "BondModelV1: liquidity")) / (total_ + principal_) / 1e18; return MathUtils.compound2(principal_, rate, forDays_).sub(principal_); } function maxDailyRate( - address pool_ + uint256 total_, + uint256 loanable_, + uint256 dailyRate_ ) - external view override + external pure override returns (uint256) { - uint256 total = ISmartYield(pool_).underlyingTotal(); - if (0 == total) { + if (0 == total_) { return 0; } - - uint256 loanable = ISmartYield(pool_).underlyingLoanable(); - uint256 dailyRate = ISmartYield(pool_).providerRatePerDay(); - return uint256(1e18).mul(dailyRate).mul(loanable) / (total) / 1e18; + return uint256(1e18).mul(dailyRate_).mul(loanable_) / (total_) / 1e18; } } diff --git a/contracts/model/IBondModel.sol b/contracts/model/IBondModel.sol index 6c3ba9a..d2023ca 100644 --- a/contracts/model/IBondModel.sol +++ b/contracts/model/IBondModel.sol @@ -4,8 +4,8 @@ pragma abicoder v2; interface IBondModel { - function gain(address pool_, uint256 principal_, uint16 forDays_) external view returns (uint256); + function gain(uint256 total_, uint256 loanable_, uint256 dailyRate_, uint256 principal_, uint16 forDays_) external pure returns (uint256); - function maxDailyRate(address pool_) external view returns (uint256); + function maxDailyRate(uint256 total_, uint256 loanable_, uint256 dailyRate_) external pure returns (uint256); } diff --git a/contracts/oracle/IYieldOracle.sol b/contracts/oracle/IYieldOracle.sol index 93ad3ab..3ee0e1b 100644 --- a/contracts/oracle/IYieldOracle.sol +++ b/contracts/oracle/IYieldOracle.sol @@ -5,5 +5,5 @@ pragma abicoder v2; interface IYieldOracle { function update() external; - function consult(uint256 forInterval) external view returns (uint256 amountOut); + function consult(uint256 forInterval) external returns (uint256 amountOut); } diff --git a/contracts/oracle/IYieldOraclelizable.sol b/contracts/oracle/IYieldOraclelizable.sol index c71d26e..96bc6a9 100644 --- a/contracts/oracle/IYieldOraclelizable.sol +++ b/contracts/oracle/IYieldOraclelizable.sol @@ -7,13 +7,6 @@ interface IYieldOraclelizable { // oracle should call this when updating function cumulatives() external - returns(uint256 cumulativeSecondlyYield); + returns(uint256 cumulativeYield); - // returns cumulative yield up to currentTime() - function currentCumulatives() - external view - returns (uint256 cumulativeSecondlyYield); - - // needs to return block.timestamp, otherwise used for mocks - function currentTime() external view returns (uint256); } diff --git a/contracts/oracle/YieldOracle.sol b/contracts/oracle/YieldOracle.sol index 7bcad8e..fc8ccc7 100644 --- a/contracts/oracle/YieldOracle.sol +++ b/contracts/oracle/YieldOracle.sol @@ -74,7 +74,7 @@ contract YieldOracle is IYieldOracle { view returns (Observation storage firstObservation) { - uint8 observationIndex = observationIndexOf(pool.currentTime()); + uint8 observationIndex = observationIndexOf(block.timestamp); // no overflow issue. if observationIndex + 1 overflows, result is still zero. uint8 firstObservationIndex = (observationIndex + 1) % granularity; firstObservation = yieldObservations[firstObservationIndex]; @@ -84,14 +84,14 @@ contract YieldOracle is IYieldOracle { // once per epoch period. function update() external virtual override { // get the observation for the current period - uint8 observationIndex = observationIndexOf(pool.currentTime()); + uint8 observationIndex = observationIndexOf(block.timestamp); Observation storage observation = yieldObservations[observationIndex]; // we only want to commit updates once per period (i.e. windowSize / granularity) - uint256 timeElapsed = pool.currentTime() - observation.timestamp; + uint256 timeElapsed = block.timestamp - observation.timestamp; if (timeElapsed > periodSize) { (uint256 yieldCumulative) = pool.cumulatives(); - observation.timestamp = pool.currentTime(); + observation.timestamp = block.timestamp; observation.yieldCumulative = yieldCumulative; } } @@ -113,14 +113,13 @@ contract YieldOracle is IYieldOracle { // update must have been called for the bucket corresponding to timestamp `now - windowSize` function consult(uint256 forInterval) external - view override virtual returns (uint256 yieldForInterval) { Observation storage firstObservation = getFirstObservationInWindow(); - uint256 timeElapsed = pool.currentTime() - firstObservation.timestamp; + uint256 timeElapsed = block.timestamp - firstObservation.timestamp; if (!(timeElapsed <= windowSize)) { // originally: @@ -143,7 +142,7 @@ contract YieldOracle is IYieldOracle { return 0; } - (uint256 yieldCumulative) = pool.currentCumulatives(); + (uint256 yieldCumulative) = pool.cumulatives(); return computeAmountOut( diff --git a/contracts/providers/CompoundController.sol b/contracts/providers/CompoundController.sol index ec9e412..06b8c95 100644 --- a/contracts/providers/CompoundController.sol +++ b/contracts/providers/CompoundController.sol @@ -20,8 +20,9 @@ import "./CompoundProvider.sol"; import "./../IController.sol"; import "./ICompoundCumulator.sol"; +import "./../oracle/IYieldOraclelizable.sol"; -contract CompoundController is IController, ICompoundCumulator { +contract CompoundController is IController, ICompoundCumulator, IYieldOraclelizable { using SafeMath for uint256; using SafeERC20 for IERC20; @@ -206,6 +207,34 @@ contract CompoundController is IController, ICompoundCumulator { { // at this point compound.finance state is updated since the pool did a deposit or withdrawl just before, so no need to ping updateCumulativesInternal(prevCTokenBalance_, false); + IYieldOracle(oracle).update(); + } + + function providerRatePerDay() + external override + returns (uint256) + { + return MathUtils.min( + MathUtils.min(BOND_MAX_RATE_PER_DAY, spotDailyRate()), + IYieldOracle(oracle).consult(1 days) + ); + } + + function cumulatives() + external override + returns (uint256) + { + uint256 timeElapsed = prevCumulationTime - block.timestamp; + + // only cumulate once per block + if (0 == timeElapsed) { + return (cumulativeSupplyRate + cumulativeDistributionRate); + } + + uint256 cTokenBalance = CompoundProvider(pool).cTokenBalance(); + updateCumulativesInternal(cTokenBalance, true); + + return (cumulativeSupplyRate + cumulativeDistributionRate); } function updateCumulativesInternal(uint256 prevCTokenBalance_, bool pingCompound_) private { @@ -318,8 +347,8 @@ contract CompoundController is IController, ICompoundCumulator { uint256 compIn_ ) public view returns (uint256) { ICToken cToken = ICToken(CompoundProvider(pool).cToken()); - IUniswapAnchoredOracle oracle = IUniswapAnchoredOracle(IComptroller(cToken.comptroller()).oracle()); - return compIn_.mul(oracle.price("COMP")).div(oracle.getUnderlyingPrice(address(cToken))); + IUniswapAnchoredOracle compOracle = IUniswapAnchoredOracle(IComptroller(cToken.comptroller()).oracle()); + return compIn_.mul(compOracle.price("COMP")).div(compOracle.getUnderlyingPrice(address(cToken))); } function uniswapAmountOut( @@ -344,12 +373,12 @@ contract CompoundController is IController, ICompoundCumulator { { ICToken cToken = ICToken(CompoundProvider(pool).cToken()); IComptroller comptroller = IComptroller(cToken.comptroller()); - IUniswapAnchoredOracle oracle = IUniswapAnchoredOracle(comptroller.oracle()); + IUniswapAnchoredOracle compOracle = IUniswapAnchoredOracle(comptroller.oracle()); // compSpeeds(cToken) * price("COMP") * BLOCKS_PER_DAY - uint256 compDollarsPerDay = comptroller.compSpeeds(address(cToken)).mul(oracle.price("COMP")).mul(BLOCKS_PER_DAY); + uint256 compDollarsPerDay = comptroller.compSpeeds(address(cToken)).mul(compOracle.price("COMP")).mul(BLOCKS_PER_DAY); // (totalBorrows() + getCash()) * getUnderlyingPrice(cToken) - uint256 totalSuppliedDollars = cToken.totalBorrows().add(cToken.getCash()).mul(oracle.getUnderlyingPrice(address(cToken))); + uint256 totalSuppliedDollars = cToken.totalBorrows().add(cToken.getCash()).mul(compOracle.getUnderlyingPrice(address(cToken))); // (compDollarsPerDay / totalSuppliedDollars) return compDollarsPerDay.div(totalSuppliedDollars); } diff --git a/contracts/providers/CompoundProvider.sol b/contracts/providers/CompoundProvider.sol index 322b9f0..636f3ce 100644 --- a/contracts/providers/CompoundProvider.sol +++ b/contracts/providers/CompoundProvider.sol @@ -176,12 +176,11 @@ contract CompoundProvider is IProvider { // current total underlying balance, as measured by pool function underlyingBalance() - external view virtual override + external virtual override returns (uint256) { // https://compound.finance/docs#protocol-math - return - cTokenBalance * ICToken(cToken).exchangeRateStored() / 1e18; + return cTokenBalance * ICToken(cToken).exchangeRateCurrent() / 1e18; } // /externals