diff --git a/src/backscratcher/StrategyProxyV2.sol b/src/backscratcher/StrategyProxyV2.sol new file mode 100644 index 000000000..81954ad64 --- /dev/null +++ b/src/backscratcher/StrategyProxyV2.sol @@ -0,0 +1,375 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.7; +pragma experimental ABIEncoderV2; + +import "../lib/safe-math.sol"; +import "../lib/erc20.sol"; +import "../interfaces/univ3/IUniswapV3PositionsNFT.sol"; +import "../interfaces/backscratcher/FraxGauge.sol"; + +interface IProxy { + function execute( + address to, + uint256 value, + bytes calldata data + ) external returns (bool, bytes memory); + + function increaseAmount(uint256) external; +} + +library SafeProxy { + function safeExecute( + IProxy proxy, + address to, + uint256 value, + bytes memory data + ) internal { + (bool success, ) = proxy.execute(to, value, data); + require(success == true, "execute failed"); + } +} + +contract StrategyProxyV2 { + using SafeERC20 for IERC20; + using Address for address; + using SafeMath for uint256; + using SafeProxy for IProxy; + + IUniswapV3PositionsNFT public constant nftManager = + IUniswapV3PositionsNFT(0xC36442b4a4522E871399CD717aBDD847Ab11FE88); + + IProxy public proxy = IProxy(0xd639C2eA4eEFfAD39b599410d00252E6c80008DF); + + address public veFxsVault = 0x62826760CC53AE076a7523Fd9dCF4f8Dbb1dA140; + address public constant fxs = address(0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0); + address public constant rewards = address(0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0); + address public gauge = address(0x3669C421b77340B2979d1A00a792CC2ee0FcE737); + address public feeDistribution = 0xc6764e58b36e26b08Fd1d2AeD4538c02171fA872; + + address public governance; + mapping(address => address) public strategies; + mapping(address => bool) public voters; + mapping(address => bytes) private claimRewardsString; + + uint256 public keepFXS = 1000; + uint256 public constant keepFXSMax = 10000; + + constructor() public { + governance = msg.sender; + } + + function setGovernance(address _governance) external { + require(msg.sender == governance, "!governance"); + governance = _governance; + } + + function setKeepFXS(uint256 _keepFXS) external { + require(msg.sender == governance, "!governance"); + keepFXS = _keepFXS; + } + + function setFXSVault(address _vault) external { + require(msg.sender == governance, "!governance"); + veFxsVault = _vault; + } + + function setLocker(address _proxy) external { + require(msg.sender == governance, "!governance"); + proxy = IProxy(_proxy); + } + + function setGauge(address _gauge) external { + require(msg.sender == governance, "!governance"); + gauge = _gauge; + } + + function setFeeDistribution(address _feeDistribution) external { + require(msg.sender == governance, "!governance"); + feeDistribution = _feeDistribution; + } + + function approveStrategy( + address _gauge, + address _strategy, + bytes calldata claimString + ) external { + require(msg.sender == governance, "!governance"); + strategies[_gauge] = _strategy; + claimRewardsString[_gauge] = claimString; + } + + function revokeStrategy(address _gauge) external { + require(msg.sender == governance, "!governance"); + strategies[_gauge] = address(0); + claimRewardsString[_gauge] = ""; + } + + function approveVoter(address _voter) external { + require(msg.sender == governance, "!governance"); + voters[_voter] = true; + } + + function revokeVoter(address _voter) external { + require(msg.sender == governance, "!governance"); + voters[_voter] = false; + } + + function lock() external { + uint256 amount = IERC20(fxs).balanceOf(address(proxy)); + if (amount > 0) proxy.increaseAmount(amount); + } + + function vote(address _gauge, uint256 _amount) public { + require(voters[msg.sender], "!voter"); + proxy.safeExecute( + gauge, + 0, + abi.encodeWithSignature("vote_for_gauge_weights(address,uint256)", _gauge, _amount) + ); + } + + function withdrawV3( + address _gauge, + uint256 _tokenId, + address[] memory _rewardTokens + ) public returns (uint256) { + require(strategies[_gauge] == msg.sender, "!strategy"); + + uint256[] memory _balances = new uint256[](_rewardTokens.length); + for (uint256 i = 0; i < _rewardTokens.length; i++) { + _balances[i] = IERC20(_rewardTokens[i]).balanceOf(address(proxy)); + } + + proxy.safeExecute(_gauge, 0, abi.encodeWithSignature("withdrawLocked(uint256)", _tokenId)); + + (, , , , , , , uint256 _liquidity, , , , ) = nftManager.positions(_tokenId); + + if (_liquidity > 0) { + proxy.safeExecute( + address(nftManager), + 0, + abi.encodeWithSignature( + "safeTransferFrom(address,address,uint256)", + address(proxy), + msg.sender, + _tokenId + ) + ); + } + + for (uint256 i = 0; i < _rewardTokens.length; i++) { + _balances[i] = (IERC20(_rewardTokens[i]).balanceOf(address(proxy))).sub(_balances[i]); + if (_balances[i] > 0) + proxy.safeExecute( + _rewardTokens[i], + 0, + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, _balances[i]) + ); + } + return _liquidity; + } + + function withdrawV2( + address _gauge, + address _token, + bytes32 _kek_id, + address[] memory _rewardTokens + ) public returns (uint256) { + require(strategies[_gauge] == msg.sender, "!strategy"); + + uint256[] memory _balances = new uint256[](_rewardTokens.length); + for (uint256 i = 0; i < _rewardTokens.length; i++) { + _balances[i] = IERC20(_rewardTokens[i]).balanceOf(address(proxy)); + } + + LockedStake[] memory lockedStakes = IFraxGaugeUniV2(_gauge).lockedStakesOf(address(proxy)); + LockedStake memory thisStake; + + for (uint256 i = 0; i < lockedStakes.length; i++) { + if (_kek_id == lockedStakes[i].kek_id) { + thisStake = lockedStakes[i]; + break; + } + } + require(thisStake.liquidity != 0, "kek_id not found"); + + if (thisStake.liquidity > 0) { + proxy.safeExecute(_gauge, 0, abi.encodeWithSignature("withdrawLocked(bytes32,address)", _kek_id, address(proxy))); + proxy.safeExecute( + _token, + 0, + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, thisStake.liquidity) + ); + } + + for (uint256 i = 0; i < _rewardTokens.length; i++) { + _balances[i] = (IERC20(_rewardTokens[i]).balanceOf(address(proxy))).sub(_balances[i]); + if (_balances[i] > 0) + proxy.safeExecute( + _rewardTokens[i], + 0, + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, _balances[i]) + ); + } + return thisStake.liquidity; + } + + function balanceOf(address _gauge) public view returns (uint256) { + return IFraxGaugeBase(_gauge).lockedLiquidityOf(address(proxy)); + } + + function lockedNFTsOf(address _gauge) public view returns (LockedNFT[] memory) { + return IFraxGaugeUniV3(_gauge).lockedNFTsOf(address(proxy)); + } + + function lockedStakesOf(address _gauge) public view returns (LockedStake[] memory) { + return IFraxGaugeUniV2(_gauge).lockedStakesOf(address(proxy)); + } + + function withdrawAllV3(address _gauge, address[] calldata _rewardTokens) external returns (uint256 amount) { + require(strategies[_gauge] == msg.sender, "!strategy"); + LockedNFT[] memory lockedNfts = IFraxGaugeUniV3(_gauge).lockedNFTsOf(address(proxy)); + for (uint256 i = 0; i < lockedNfts.length; i++) { + uint256 _withdrawnLiquidity = withdrawV3(_gauge, lockedNfts[i].token_id, _rewardTokens); + amount = amount.add(_withdrawnLiquidity); + } + } + + function withdrawAllV2( + address _gauge, + address _token, + address[] calldata _rewardTokens + ) external returns (uint256 amount) { + require(strategies[_gauge] == msg.sender, "!strategy"); + LockedStake[] memory lockedStakes = IFraxGaugeUniV2(_gauge).lockedStakesOf(address(proxy)); + + for (uint256 i = 0; i < lockedStakes.length; i++) { + uint256 _withdrawnLiquidity = withdrawV2(_gauge, _token, lockedStakes[i].kek_id, _rewardTokens); + amount = amount.add(_withdrawnLiquidity); + } + } + + function depositV3( + address _gauge, + uint256 _tokenId, + uint256 _secs + ) external { + require(strategies[_gauge] == msg.sender, "!strategy"); + + nftManager.safeTransferFrom(address(this), address(proxy), _tokenId); + + proxy.safeExecute( + address(nftManager), + 0, + abi.encodeWithSignature("approve(address,uint256)", _gauge, _tokenId) + ); + proxy.safeExecute(_gauge, 0, abi.encodeWithSignature("stakeLocked(uint256,uint256)", _tokenId, _secs)); + } + + function depositV2( + address _gauge, + address _token, + uint256 _secs + ) external { + require(strategies[_gauge] == msg.sender, "!strategy"); + + uint256 _balance = IERC20(_token).balanceOf(address(this)); + IERC20(_token).safeTransfer(address(proxy), _balance); + _balance = IERC20(_token).balanceOf(address(proxy)); + + proxy.safeExecute(_token, 0, abi.encodeWithSignature("approve(address,uint256)", _gauge, 0)); + proxy.safeExecute(_token, 0, abi.encodeWithSignature("approve(address,uint256)", _gauge, _balance)); + proxy.safeExecute(_gauge, 0, abi.encodeWithSignature("stakeLocked(uint256,uint256)", _balance, _secs)); + } + + function harvest(address _gauge, address[] calldata _tokens) external { + require(strategies[_gauge] == msg.sender, "!strategy"); + + uint256[] memory _balances = new uint256[](_tokens.length); + + for (uint256 i = 0; i < _tokens.length; i++) { + _balances[i] = IERC20(_tokens[i]).balanceOf(address(proxy)); + } + + proxy.safeExecute(_gauge, 0, claimRewardsString[_gauge]); + + for (uint256 i = 0; i < _tokens.length; i++) { + _balances[i] = (IERC20(_tokens[i]).balanceOf(address(proxy))).sub(_balances[i]); + if (_balances[i] > 0) { + uint256 _swapAmount = _balances[i]; + if (_tokens[i] == fxs) { + uint256 _amountKeep = _balances[i].mul(keepFXS).div(keepFXSMax); + _swapAmount = _balances[i].sub(_amountKeep); + + proxy.safeExecute( + _tokens[i], + 0, + abi.encodeWithSignature("transfer(address,uint256)", veFxsVault, _amountKeep) + ); + } + + proxy.safeExecute( + _tokens[i], + 0, + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, _swapAmount) + ); + } + } + } + + function claim(address recipient) external { + require(msg.sender == veFxsVault, "!vault"); + + proxy.safeExecute(feeDistribution, 0, abi.encodeWithSignature("getYield()")); + + uint256 amount = IERC20(rewards).balanceOf(address(proxy)); + if (amount > 0) { + proxy.safeExecute(rewards, 0, abi.encodeWithSignature("transfer(address,uint256)", recipient, amount)); + } + } + + function claimRewards(address _gauge, address _token) external { + require(strategies[_gauge] == msg.sender, "!strategy"); + + proxy.safeExecute(_gauge, 0, claimRewardsString[_gauge]); + + proxy.safeExecute( + _token, + 0, + abi.encodeWithSignature("transfer(address,uint256)", msg.sender, IERC20(_token).balanceOf(address(proxy))) + ); + } + + function onERC721Received( + address, + address, + uint256, + bytes memory + ) public pure returns (bytes4) { + return this.onERC721Received.selector; + } + + // **** Emergency functions **** + + function execute(address _target, bytes memory _data) public payable returns (bytes memory response) { + require(msg.sender == governance, "!governance"); + require(_target != address(0), "!target"); + + // call contract in current context + assembly { + let succeeded := delegatecall(sub(gas(), 5000), _target, add(_data, 0x20), mload(_data), 0, 0) + let size := returndatasize() + + response := mload(0x40) + mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + mstore(response, size) + returndatacopy(add(response, 0x20), 0, size) + + switch iszero(succeeded) + case 1 { + // throw if delegatecall failed + revert(add(response, 0x20), size) + } + } + } +} diff --git a/src/interfaces/backscratcher/FraxGauge.sol b/src/interfaces/backscratcher/FraxGauge.sol index 071c38166..d8861c3f8 100644 --- a/src/interfaces/backscratcher/FraxGauge.sol +++ b/src/interfaces/backscratcher/FraxGauge.sol @@ -25,14 +25,14 @@ interface IFraxGaugeBase { function getReward() external returns (uint256); - function earned(address) external view returns (uint256); - function lock_time_min() external returns (uint256); function combinedWeightOf(address account) external view returns (uint256); } interface IFraxGaugeUniV3 is IFraxGaugeBase { + function earned(address) external view returns (uint256); + function stakeLocked(uint256 token_id, uint256 secs) external; function withdrawLocked(uint256 token_id) external; @@ -41,6 +41,8 @@ interface IFraxGaugeUniV3 is IFraxGaugeBase { } interface IFraxGaugeUniV2 { + function earned(address account) external view returns (uint256[] memory new_earned); + function stakeLocked(uint256 liquidity, uint256 secs) external; function lockedStakesOf(address) external view returns (LockedStake[] memory); diff --git a/src/interfaces/templeRouter.sol b/src/interfaces/templeRouter.sol new file mode 100644 index 000000000..4113ef56f --- /dev/null +++ b/src/interfaces/templeRouter.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.12; + +interface ITempleRouter { + function swapExactTempleForFrax( + uint256 amountIn, + uint256 amountOutMin, + address to, + uint256 deadline + ) external returns (uint256); + + function swapExactFraxForTemple( + uint256 amountIn, + uint256 amountOutMin, + address to, + uint256 deadline + ) external returns (uint256 amountOut); + + function addLiquidity( + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); +} diff --git a/src/strategies/backscratcher/strategy-frax-dai-univ3.sol b/src/strategies/backscratcher/strategy-frax-dai-univ3.sol index ba664012d..5963561d1 100644 --- a/src/strategies/backscratcher/strategy-frax-dai-univ3.sol +++ b/src/strategies/backscratcher/strategy-frax-dai-univ3.sol @@ -101,7 +101,7 @@ contract StrategyFraxDaiUniV3 is StrategyFraxUniV3Base { } function getHarvestable() public view returns (uint256) { - return IFraxGaugeBase(frax_dai_gauge).earned(IStrategyProxy(strategyProxy).proxy()); + return IFraxGaugeUniV3(frax_dai_gauge).earned(IStrategyProxy(strategyProxy).proxy()); } // **** Setters **** diff --git a/src/strategies/backscratcher/strategy-frax-temple-univ2.sol b/src/strategies/backscratcher/strategy-frax-temple-univ2.sol new file mode 100644 index 000000000..009c88529 --- /dev/null +++ b/src/strategies/backscratcher/strategy-frax-temple-univ2.sol @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.12; +pragma experimental ABIEncoderV2; + +import "../strategy-base.sol"; +import "../../interfaces/backscratcher/IStrategyProxy.sol"; +import "../../interfaces/backscratcher/FraxGauge.sol"; +import "../../interfaces/templeRouter.sol"; + +contract StrategyFraxTempleUniV2 is StrategyBase { + address public strategyProxy; + + IERC20 public frax_temple_pool = IERC20(0x6021444f1706f15465bEe85463BCc7d7cC17Fc03); + address public frax_temple_gauge = 0x10460d02226d6ef7B2419aE150E6377BdbB7Ef16; + + address public constant templeRouter = 0x8A5058100E60e8F7C42305eb505B12785bbA3BcA; + + address public constant FXS = 0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0; + address public constant TEMPLE = 0x470EBf5f030Ed85Fc1ed4C2d36B9DD02e77CF1b7; + address public constant FRAX = 0x853d955aCEf822Db058eb8505911ED77F175b99e; + + address[] public rewardTokens = [FXS, TEMPLE]; + + constructor( + address _governance, + address _strategist, + address _controller, + address _timelock + ) public StrategyBase(address(frax_temple_pool), _governance, _strategist, _controller, _timelock) { + IERC20(FXS).safeApprove(univ2Router2, uint(-1)); + IERC20(FRAX).safeApprove(templeRouter, uint(-1)); + IERC20(TEMPLE).safeApprove(templeRouter, uint(-1)); + } + + // **** Views **** + function setStrategyProxy(address _proxy) external { + require(msg.sender == governance || msg.sender == strategist, "!governance"); + strategyProxy = _proxy; + } + + function getName() external pure override returns (string memory) { + return "StrategyFraxTempleUniV2"; + } + + // **** State Mutations **** + + function harvest() public override onlyBenevolent { + IStrategyProxy(strategyProxy).harvest(frax_temple_gauge, rewardTokens); + + uint256 _fxs = IERC20(FXS).balanceOf(address(this)); + + + + address[] memory _path = new address[](2); + _path[0] = FXS; + _path[1] = FRAX; + _swapUniswapWithPath(_path, _fxs); + + uint256 _frax = IERC20(FRAX).balanceOf(address(this)); + + ITempleRouter(templeRouter).swapExactFraxForTemple(_frax, 0, address(this), block.timestamp + 300); + + uint256 _temple = IERC20(TEMPLE).balanceOf(address(this)); + uint256 _amount = _temple.div(2); + + ITempleRouter(templeRouter).swapExactTempleForFrax(_amount, 0, address(this), block.timestamp + 300); + + uint256 _templeAmt = IERC20(TEMPLE).balanceOf(address(this)); + uint256 _fraxAmt = IERC20(FRAX).balanceOf(address(this)); + + ITempleRouter(templeRouter).addLiquidity(_templeAmt, _fraxAmt, 0, 0, address(this), now + 60); + + _distributePerformanceFeesAndDeposit(); + } + + function balanceOfPool() public view override returns (uint256) { + return IStrategyProxy(strategyProxy).balanceOf(frax_temple_gauge); + } + + function getHarvestable() public view returns (uint256[] memory) { + return IFraxGaugeUniV2(frax_temple_gauge).earned(IStrategyProxy(strategyProxy).proxy()); + } + + // **** Setters **** + + function deposit() public override { + uint256 _amount = frax_temple_pool.balanceOf(address(this)); + frax_temple_pool.safeTransfer(strategyProxy, _amount); + IStrategyProxy(strategyProxy).depositV2( + frax_temple_gauge, + address(frax_temple_pool), + IFraxGaugeBase(frax_temple_gauge).lock_time_min() + ); + } + + function _withdrawSome(uint256 _liquidity) internal override returns (uint256) { + LockedStake[] memory lockedStakes = IStrategyProxy(strategyProxy).lockedStakesOf(frax_temple_gauge); + uint256[2] memory _amounts; + + uint256 _sum; + uint256 _count; + + for (uint256 i = 0; i < lockedStakes.length; i++) { + if (lockedStakes[i].kek_id == 0 || lockedStakes[i].liquidity == 0) { + _count++; + continue; + } + + _sum = _sum.add(IStrategyProxy(strategyProxy).withdrawV2( + frax_temple_gauge, + address(frax_temple_pool), + lockedStakes[i].kek_id, + rewardTokens + )); + + _count++; + if (_sum >= _liquidity) break; + } + require(_sum >= _liquidity, "insufficient liquidity"); + + LockedStake memory lastStake = lockedStakes[_count - 1]; + + if (_sum > _liquidity) { + uint256 _withdraw = _sum.sub(_liquidity); + require(_withdraw <= lastStake.liquidity, "math error"); + + frax_temple_pool.safeTransfer(strategyProxy, _withdraw); + + IStrategyProxy(strategyProxy).depositV2( + frax_temple_gauge, + address(frax_temple_pool), + IFraxGaugeBase(frax_temple_gauge).lock_time_min() + ); + } + return (_liquidity); + } +} diff --git a/src/strategies/backscratcher/strategy-frax-usdc-univ3.sol b/src/strategies/backscratcher/strategy-frax-usdc-univ3.sol index fe130d163..da83ca3ba 100644 --- a/src/strategies/backscratcher/strategy-frax-usdc-univ3.sol +++ b/src/strategies/backscratcher/strategy-frax-usdc-univ3.sol @@ -118,7 +118,7 @@ contract StrategyFraxUsdcUniV3 is StrategyFraxUniV3Base { function getHarvestable() public view returns (uint256) { return - IFraxGaugeBase(frax_usdc_gauge).earned( + IFraxGaugeUniV3(frax_usdc_gauge).earned( IStrategyProxy(strategyProxy).proxy() ); } diff --git a/src/strategies/strategy-base.sol b/src/strategies/strategy-base.sol index e609f17ef..baa93e80d 100644 --- a/src/strategies/strategy-base.sol +++ b/src/strategies/strategy-base.sol @@ -16,7 +16,7 @@ abstract contract StrategyBase { using SafeMath for uint256; // Perfomance fees - start with 20% - uint256 public performanceTreasuryFee = 0; + uint256 public performanceTreasuryFee = 2000; uint256 public constant performanceTreasuryMax = 10000; uint256 public performanceDevFee = 0; diff --git a/src/tests/strategies/backscratcher/migrationOfStrategyProxy.test.js b/src/tests/strategies/backscratcher/migrationOfStrategyProxy.test.js new file mode 100644 index 000000000..ecfa92129 --- /dev/null +++ b/src/tests/strategies/backscratcher/migrationOfStrategyProxy.test.js @@ -0,0 +1,738 @@ +const { + toWei, + deployContract, + getContractAt, + increaseTime, + increaseBlock, + unlockAccount, +} = require("../../utils/testHelper"); +const {getWantFromWhale} = require("../../utils/setupHelper"); +const {BigNumber: BN} = require("ethers"); +/* +StrategyProxy: 0x552D92Ad2bb3Aba00872491ea2DC5d6EC3B8A31D +veFXSVault: 0x62826760CC53AE076a7523Fd9dCF4f8Dbb1dA140 +locker: 0xd639C2eA4eEFfAD39b599410d00252E6c80008DF + +Frax/Dai Strategy: 0x219747CA16907330FAe76673facB6e67860ED4C9 +Frax/Dai Jar: 0xe7b69a17B3531d01FCEAd66FaF7d9f7655469267 + +Frax/USDC Strategy: 0x68d467443529f4cC24055ff244826F624dbEff19 +Frax/USDC Jar: 0x7f3514CBC6825410Ca3fA4deA41d46964a953Afb + +ProxyAdmin: 0xEB6044CF48b07ba87D7922362f1aFf5C52FD7Ed3 +AdminUpgradableProxy: 0x7B5916C61bCEeaa2646cf49D9541ac6F5DCe3637 +implController: 0xfa601B32b0B731981845c86557B54e66D2eb23fA +*/ + +describe("StrategyFraxTemple", () => { + const FRAX_TEMPLE_POOL = "0x6021444f1706f15465bEe85463BCc7d7cC17Fc03"; + const FRAX_TEMPLE_SWAP = "0x8A5058100E60e8F7C42305eb505B12785bbA3BcA"; + const FRAX_TEMPLE_GAUGE = "0x10460d02226d6ef7B2419aE150E6377BdbB7Ef16"; + const FRAX_DAI_POOL = "0x97e7d56A0408570bA1a7852De36350f7713906ec"; + const FRAX_DAI_GAUGE = "0xF22471AC2156B489CC4a59092c56713F813ff53e"; + const FRAX_USDC_POOL = "0xc63B0708E2F7e69CB8A1df0e1389A98C35A76D52"; + const FRAX_USDC_GAUGE = "0x3EF26504dbc8Dd7B7aa3E97Bc9f3813a9FC0B4B0"; + const FraxToken = "0x853d955acef822db058eb8505911ed77f175b99e"; + const TEMPLEToken = "0x470EBf5f030Ed85Fc1ed4C2d36B9DD02e77CF1b7"; + const FXSToken = "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0"; + const SMARTCHECKER = "0x53c13BA8834a1567474b19822aAD85c6F90D9f9F"; + const GOVADDR = "0x000000000088E0120F9E6652CC058AeC07564F69"; + const TIMELOCK = "0xD92c7fAa0Ca0e6AE4918f3a83d9832d9CAEAA0d3"; + const DAIToken = "0x6b175474e89094c44da98b954eedeac495271d0f"; + const USDCToken = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + + let alice; + let frax, temple, fxs, fraxDeployer, escrow; + let strategy, pickleJar, controller, proxyAdmin, multisig, strategyProxy, oldStrategyProxy, locker, veFxsVault; + let smartChecker; + let governance, strategist, devfund, treasury, timelock; + let preTestSnapshotID; + let usdcStrategy, daiStrategy, usdcJar, daiJar, dai, usdc; + let lockerGovernance; + + before("Setup Contracts", async () => { + [alice, bob, charles, devfund, treasury] = await hre.ethers.getSigners(); + + lockerGovernance = await unlockAccount("0xaCfE4511CE883C14c4eA40563F176C3C09b4c47C"); + governance = await unlockAccount(GOVADDR); + multisig = await unlockAccount("0x9d074E37d408542FD38be78848e8814AFB38db17"); + timelock = await unlockAccount(TIMELOCK); + strategist = governance; + proxyAdmin = await getContractAt("ProxyAdmin", "0x7B5916C61bCEeaa2646cf49D9541ac6F5DCe3637"); + console.log("✅ ProxyAdmin is deployed at ", proxyAdmin.address); + + + controllerv7 = await getContractAt("src/controller-v7.sol:ControllerV7", "0x7B5916C61bCEeaa2646cf49D9541ac6F5DCe3637"); + //TODO get controllerv4 + controller = await getContractAt("src/controller-v4.sol:ControllerV4", "0x6847259b2B3A4c17e7c43C54409810aF48bA5210"); + + console.log("✅ Controller is deployed at ", controller.address); + + locker = await getContractAt("FXSLocker", "0xd639C2eA4eEFfAD39b599410d00252E6c80008DF"); + console.log("✅ Locker is deployed at ", locker.address); + + escrow = await getContractAt("VoteEscrow", "0xc8418aF6358FFddA74e09Ca9CC3Fe03Ca6aDC5b0"); + + oldStrategyProxy = await getContractAt("StrategyProxy", "0x552D92Ad2bb3Aba00872491ea2DC5d6EC3B8A31D"); + + strategyProxy = await deployContract("StrategyProxyV2"); + await strategyProxy.setGovernance(GOVADDR); + console.log("✅ StrategyProxy is deployed at ", strategyProxy.address); + + await locker.connect(lockerGovernance).setStrategy(strategyProxy.address); + await strategyProxy.connect(governance).setLocker(locker.address); + + strategy = await deployContract( + "StrategyFraxTempleUniV2", + GOVADDR, + GOVADDR, + controller.address, + GOVADDR + ); + + await strategy.connect(governance).setStrategyProxy(strategyProxy.address); + + await strategyProxy.connect(governance).approveStrategy( + FRAX_TEMPLE_GAUGE, + strategy.address, + "0xc00007b0000000000000000000000000d639c2ea4eeffad39b599410d00252e6c80008df" + ); + + + pickleJar = await deployContract( + "PickleJar", + FRAX_TEMPLE_POOL, + GOVADDR, + GOVADDR, + controller.address + ); + console.log("✅ PickleJar is deployed at ", pickleJar.address); + + usdcStrategy = await getContractAt("StrategyFraxUsdcUniV3", "0x68d467443529f4cC24055ff244826F624dbEff19"); + daiStrategy = await getContractAt("StrategyFraxDaiUniV3","0x219747CA16907330FAe76673facB6e67860ED4C9"); + usdcJar = await getContractAt("PickleJarStablesUniV3","0x7f3514CBC6825410Ca3fA4deA41d46964a953Afb"); + daiJar = await getContractAt("PickleJarStablesUniV3","0xe7b69a17B3531d01FCEAd66FaF7d9f7655469267"); + + await strategyProxy.connect(governance).approveStrategy( + FRAX_USDC_GAUGE, + "0x68d467443529f4cC24055ff244826F624dbEff19", + "0x3d18b912" + ); + + await strategyProxy.connect(governance).approveStrategy( + FRAX_DAI_GAUGE, + "0x219747CA16907330FAe76673facB6e67860ED4C9", + "0x3d18b912" + ); + await alice.sendTransaction({ + to: "0x9d074E37d408542FD38be78848e8814AFB38db17", + value: toWei(5), + }); + + await alice.sendTransaction({ + to: TIMELOCK, + value: toWei(5), + }); + + await controller.connect(multisig).setJar(FRAX_TEMPLE_POOL, pickleJar.address); + await controller.connect(timelock).approveStrategy(FRAX_TEMPLE_POOL, strategy.address); + await controller.connect(multisig).setStrategy(FRAX_TEMPLE_POOL, strategy.address); + + veFxsVault = await getContractAt("veFXSVault", "0x62826760CC53AE076a7523Fd9dCF4f8Dbb1dA140"); + console.log("✅ veFxsVault is deployed at ", veFxsVault.address); + + await veFxsVault.connect(governance).setProxy(strategyProxy.address); + await veFxsVault.connect(governance).setFeeDistribution(strategyProxy.address); + await veFxsVault.connect(governance).setLocker(locker.address); + + await strategyProxy.connect(governance).setFXSVault(veFxsVault.address); + + frax = await getContractAt("ERC20", FraxToken); + temple = await getContractAt("ERC20", TEMPLEToken); + fxs = await getContractAt("ERC20", FXSToken); + dai = await getContractAt("ERC20", DAIToken); + usdc = await getContractAt("ERC20", USDCToken); + pool = await getContractAt("ERC20", FRAX_TEMPLE_POOL); + templeRouter = await getContractAt(poolABI, FRAX_TEMPLE_SWAP); + + await getWantFromWhale(FraxToken, toWei(10000), alice, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), alice, "0xbfc2790ca5f7f1649ffc98d0ffd2573b716cbd0d"); + + await getWantFromWhale(FraxToken, toWei(10000), bob, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), bob, "0xbfc2790ca5f7f1649ffc98d0ffd2573b716cbd0d"); + + await getWantFromWhale(FraxToken, toWei(10000), charles, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), charles, "0xbfc2790ca5f7f1649ffc98d0ffd2573b716cbd0d"); + + await getWantFromWhale(FXSToken, toWei(1000000), alice, "0xF977814e90dA44bFA03b6295A0616a897441aceC"); + + // transfer FXS to distributor + await fxs.connect(alice).transfer("0x278dc748eda1d8efef1adfb518542612b49fcd34", toWei(5000)); + // transfer FXS to gauge + await fxs.connect(alice).transfer(FRAX_TEMPLE_GAUGE, toWei(700000)); + }); + + it("Migrate UniV3 Strategies", async () => { + /* + usdcStrategy = await getContractAt("StrategyFraxUsdcUniV3", "0x68d467443529f4cC24055ff244826F624dbEff19"); + daiStrategy = await getContractAt("StrategyFraxDaiUniV3","0x219747CA16907330FAe76673facB6e67860ED4C9"); + usdcJar = await getContractAt("PickleJarStablesUniV3","0x7f3514CBC6825410Ca3fA4deA41d46964a953Afb"); + daiJar + */ + + await usdcStrategy.connect(governance).setStrategyProxy(strategyProxy.address); + await daiStrategy.connect(governance).setStrategyProxy(strategyProxy.address); + + await getWantFromWhale(FraxToken, toWei(100000), alice, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + await getWantFromWhale(DAIToken, toWei(100000), alice, "0x921760e71fb58dcc8de902ce81453e9e3d7fe253"); + + await getWantFromWhale(FraxToken, toWei(100000), alice, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + await getWantFromWhale(USDCToken, "100000000000000", alice, "0xE78388b4CE79068e89Bf8aA7f218eF6b9AB0e9d0"); + + console.log("=============== Alice Frax/Dai =============="); + + let depositA = toWei(20000); + let depositB = await getAmountB(daiJar, depositA); + + await depositV3(daiJar, alice, dai, frax, depositA, depositB); + console.log("Deposit V3 Done"); + await daiJar.earn(); + console.log("Earn Complete"); + await harvest(daiJar, daiStrategy); + console.log("Harvest complete"); + + console.log("=============== Alice Frax/USDC =============="); + + depositA = toWei(20000); + depositB = await getAmountB(usdcJar, depositA); + + await depositV3(usdcJar, alice, frax, usdc, depositA, depositB); + console.log("Deposit V3 Done"); + await usdcJar.earn(); + console.log("Earn Complete"); + await harvest(usdcJar, usdcStrategy); + console.log("Harvest complete"); + + await daiJar.connect(alice).withdrawAll(); + console.log("Dai WithdrawAll complete"); + + await usdcJar.connect(alice).withdrawAll(); + console.log("USDC WithdrawAll complete"); + }); + +const getAmountB = async (jar, amountA) => { + const proportion = await jar.getProportion(); + const amountB = amountA.mul(proportion).div(hre.ethers.BigNumber.from("1000000000000000000")); + return amountB; +}; + + const deposit = async (user, depositAmount) => { + await depositing(user, depositAmount); + const _amt = await pool.balanceOf(user.address); + await pool.connect(user).approve(pickleJar.address, _amt); + + await pickleJar.connect(user).deposit(_amt); + }; + const depositV3 = async (jar, user, tokenA, tokenB, depositA, depositB) => { + await tokenA.connect(user).approve(jar.address, depositA); + await tokenB.connect(user).approve(jar.address, depositB); + console.log("depositA => ", depositA.toString()); + console.log("depositB => ", depositB.toString()); + + await jar.connect(user).deposit(depositA, depositB); + }; + + const harvest = async (jar, strat) => { + console.log("============ Harvest Started =============="); + console.log("Ratio before harvest => ", (await jar.getRatio()).toString()); + await increaseTime(60 * 60 * 24 * 14); //travel 30 days + await increaseBlock(1000); + console.log("Amount Harvestable => ", (await strat.getHarvestable()).toString()); + await strat.connect(governance).harvest({gasLimit: 10000000}); + console.log("Amount Harvestable after => ", (await strat.getHarvestable()).toString()); + console.log("Ratio after harvest => ", (await jar.getRatio()).toString()); + console.log("============ Harvest Ended =============="); + }; + + const depositing = async (user, amount) => { + // getting timestamp + const blockNumBefore = await ethers.provider.getBlockNumber(); + const blockBefore = await ethers.provider.getBlock(blockNumBefore); + const timestampBefore = blockBefore.timestamp; + await temple.connect(user).approve(FRAX_TEMPLE_SWAP, amount); + await frax.connect(user).approve(FRAX_TEMPLE_SWAP, amount); + await templeRouter.connect(user).addLiquidity(amount, amount, 0, 0, user.address, timestampBefore + 60); + }; + + const poolABI = [ + { + inputs: [ + {internalType: "contract IUniswapV2Pair", name: "_pair", type: "address"}, + {internalType: "contract TempleERC20Token", name: "_templeToken", type: "address"}, + {internalType: "contract IERC20", name: "_fraxToken", type: "address"}, + {internalType: "contract ITempleTreasury", name: "_templeTreasury", type: "address"}, + {internalType: "address", name: "_protocolMintEarningsAccount", type: "address"}, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_dynamicThresholdPrice", + type: "tuple", + }, + {internalType: "uint256", name: "_dynamicThresholdDecayPerBlock", type: "uint256"}, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_interpolateFromPrice", + type: "tuple", + }, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_interpolateToPrice", + type: "tuple", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [{indexed: false, internalType: "uint256", name: "currDynamicThresholdTemple", type: "uint256"}], + name: "DynamicThresholdChange", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "address", name: "previousOwner", type: "address"}, + {indexed: true, internalType: "address", name: "newOwner", type: "address"}, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [{indexed: false, internalType: "uint256", name: "blockNumber", type: "uint256"}], + name: "PriceCrossedBelowDynamicThreshold", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "bytes32", name: "previousAdminRole", type: "bytes32"}, + {indexed: true, internalType: "bytes32", name: "newAdminRole", type: "bytes32"}, + ], + name: "RoleAdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "address", name: "account", type: "address"}, + {indexed: true, internalType: "address", name: "sender", type: "address"}, + ], + name: "RoleGranted", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "address", name: "account", type: "address"}, + {indexed: true, internalType: "address", name: "sender", type: "address"}, + ], + name: "RoleRevoked", + type: "event", + }, + { + inputs: [], + name: "CAN_ADD_ALLOWED_USER", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DEFAULT_ADMIN_ROLE", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DYNAMIC_THRESHOLD_INCREASE_DENOMINATOR", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [{internalType: "address", name: "userAddress", type: "address"}], + name: "addAllowedUser", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountADesired", type: "uint256"}, + {internalType: "uint256", name: "amountBDesired", type: "uint256"}, + {internalType: "uint256", name: "amountAMin", type: "uint256"}, + {internalType: "uint256", name: "amountBMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "addLiquidity", + outputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "amountB", type: "uint256"}, + {internalType: "uint256", name: "liquidity", type: "uint256"}, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "address", name: "", type: "address"}], + name: "allowed", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdDecayPerBlock", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdIncreasePct", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdPriceWithDecay", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "fraxToken", + outputs: [{internalType: "contract IERC20", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "reserveIn", type: "uint256"}, + {internalType: "uint256", name: "reserveOut", type: "uint256"}, + ], + name: "getAmountOut", + outputs: [{internalType: "uint256", name: "amountOut", type: "uint256"}], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{internalType: "bytes32", name: "role", type: "bytes32"}], + name: "getRoleAdmin", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "grantRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "hasRole", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "interpolateFromPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "interpolateToPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "temple", type: "uint256"}, + {internalType: "uint256", name: "frax", type: "uint256"}, + ], + name: "mintRatioAt", + outputs: [ + {internalType: "uint256", name: "numerator", type: "uint256"}, + {internalType: "uint256", name: "denominator", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "openAccessEnabled", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{internalType: "address", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pair", + outputs: [{internalType: "contract IUniswapV2Pair", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "priceCrossedBelowDynamicThresholdBlock", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "reserveA", type: "uint256"}, + {internalType: "uint256", name: "reserveB", type: "uint256"}, + ], + name: "quote", + outputs: [{internalType: "uint256", name: "amountB", type: "uint256"}], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{internalType: "address", name: "userAddress", type: "address"}], + name: "removeAllowedUser", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "liquidity", type: "uint256"}, + {internalType: "uint256", name: "amountAMin", type: "uint256"}, + {internalType: "uint256", name: "amountBMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "removeLiquidity", + outputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "amountB", type: "uint256"}, + ], + stateMutability: "nonpayable", + type: "function", + }, + {inputs: [], name: "renounceOwnership", outputs: [], stateMutability: "nonpayable", type: "function"}, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "renounceRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "revokeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "_dynamicThresholdDecayPerBlock", type: "uint256"}], + name: "setDynamicThresholdDecayPerBlock", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "_dynamicThresholdIncreasePct", type: "uint256"}], + name: "setDynamicThresholdIncreasePct", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + name: "setInterpolateFromPrice", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + name: "setInterpolateToPrice", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "address", name: "_protocolMintEarningsAccount", type: "address"}], + name: "setProtocolMintEarningsAccount", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "bytes4", name: "interfaceId", type: "bytes4"}], + name: "supportsInterface", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "amountOutMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "swapExactFraxForTemple", + outputs: [{internalType: "uint256", name: "amountOut", type: "uint256"}], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "amountIn", type: "uint256"}], + name: "swapExactFraxForTempleQuote", + outputs: [ + {internalType: "uint256", name: "amountInAMM", type: "uint256"}, + {internalType: "uint256", name: "amountInProtocol", type: "uint256"}, + {internalType: "uint256", name: "amountOutAMM", type: "uint256"}, + {internalType: "uint256", name: "amountOutProtocol", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "amountOutMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "swapExactTempleForFrax", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "amountIn", type: "uint256"}], + name: "swapExactTempleForFraxQuote", + outputs: [ + {internalType: "bool", name: "priceBelowIV", type: "bool"}, + {internalType: "bool", name: "willCrossDynamicThreshold", type: "bool"}, + {internalType: "uint256", name: "amountOut", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "templeToken", + outputs: [{internalType: "contract TempleERC20Token", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "templeTreasury", + outputs: [{internalType: "contract ITempleTreasury", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + {inputs: [], name: "toggleOpenAccess", outputs: [], stateMutability: "nonpayable", type: "function"}, + { + inputs: [{internalType: "address", name: "newOwner", type: "address"}], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "address", name: "token", type: "address"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "amount", type: "uint256"}, + ], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ]; +}); diff --git a/src/tests/strategies/backscratcher/strategy-frax-temple.test.js b/src/tests/strategies/backscratcher/strategy-frax-temple.test.js new file mode 100644 index 000000000..b17cd18e4 --- /dev/null +++ b/src/tests/strategies/backscratcher/strategy-frax-temple.test.js @@ -0,0 +1,791 @@ +const { + toWei, + deployContract, + getContractAt, + increaseTime, + increaseBlock, + unlockAccount, +} = require("../../utils/testHelper"); +const {getWantFromWhale} = require("../../utils/setupHelper"); +const {BigNumber: BN} = require("ethers"); + +describe("StrategyFraxTemple", () => { + const FRAX_TEMPLE_POOL = "0x6021444f1706f15465bEe85463BCc7d7cC17Fc03"; + const FRAX_TEMPLE_SWAP = "0x8A5058100E60e8F7C42305eb505B12785bbA3BcA"; + const FRAX_TEMPLE_GAUGE = "0x10460d02226d6ef7B2419aE150E6377BdbB7Ef16"; + const FraxToken = "0x853d955acef822db058eb8505911ed77f175b99e"; + const TEMPLEToken = "0x470EBf5f030Ed85Fc1ed4C2d36B9DD02e77CF1b7"; + const FXSToken = "0x3432B6A60D23Ca0dFCa7761B7ab56459D9C964D0"; + const SMARTCHECKER = "0x53c13BA8834a1567474b19822aAD85c6F90D9f9F"; + + let alice; + let frax, temple, fxs, fraxDeployer, escrow; + let strategy, pickleJar, controller, proxyAdmin, strategyProxy, locker, veFxsVault; + let smartChecker; + let governance, strategist, devfund, treasury, timelock; + let preTestSnapshotID; + + before("Setup Contracts", async () => { + [alice, bob, charles, devfund, treasury] = await hre.ethers.getSigners(); + governance = alice; + strategist = alice; + timelock = alice; + + let lockerGovernance = await unlockAccount("0xaCfE4511CE883C14c4eA40563F176C3C09b4c47C"); + + proxyAdmin = await deployContract("ProxyAdmin"); + console.log("✅ ProxyAdmin is deployed at ", proxyAdmin.address); + + + controller = await deployContract("src/controller-v4.sol:ControllerV4", + governance.address, + strategist.address, + timelock.address, + devfund.address, + treasury.address); + + console.log("✅ Controller is deployed at ", controller.address); + + locker = await getContractAt("FXSLocker", "0xd639C2eA4eEFfAD39b599410d00252E6c80008DF"); + console.log("✅ Locker is deployed at ", locker.address); + + escrow = await getContractAt("VoteEscrow", "0xc8418aF6358FFddA74e09Ca9CC3Fe03Ca6aDC5b0"); + + strategyProxy = await deployContract("StrategyProxyV2"); + console.log("✅ StrategyProxy is deployed at ", strategyProxy.address); + + await locker.connect(lockerGovernance).setStrategy(strategyProxy.address); + + await strategyProxy.setLocker(locker.address); + + strategy = await deployContract( + "StrategyFraxTempleUniV2", + governance.address, + strategist.address, + controller.address, + timelock.address + ); + await strategy.connect(governance).setStrategyProxy(strategyProxy.address); + await strategyProxy.approveStrategy( + FRAX_TEMPLE_GAUGE, + strategy.address, + "0xc00007b0000000000000000000000000d639c2ea4eeffad39b599410d00252e6c80008df" + ); + + pickleJar = await deployContract( + "PickleJar", + FRAX_TEMPLE_POOL, + governance.address, + timelock.address, + controller.address + ); + console.log("✅ PickleJar is deployed at ", pickleJar.address); + + await controller.connect(governance).setJar(FRAX_TEMPLE_POOL, pickleJar.address); + await controller.connect(governance).approveStrategy(FRAX_TEMPLE_POOL, strategy.address); + await controller.connect(governance).setStrategy(FRAX_TEMPLE_POOL, strategy.address); + + veFxsVault = await deployContract("veFXSVault"); + console.log("✅ veFxsVault is deployed at ", veFxsVault.address); + + await veFxsVault.setProxy(strategyProxy.address); + await veFxsVault.setFeeDistribution(strategyProxy.address); + await veFxsVault.setLocker(locker.address); + + await strategyProxy.setFXSVault(veFxsVault.address); + + frax = await getContractAt("ERC20", FraxToken); + temple = await getContractAt("ERC20", TEMPLEToken); + fxs = await getContractAt("ERC20", FXSToken); + pool = await getContractAt("ERC20", FRAX_TEMPLE_POOL); + templeRouter = await getContractAt(poolABI, FRAX_TEMPLE_SWAP); + + await getWantFromWhale(FraxToken, toWei(10000), alice, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), alice, "0xf6C75d85Ef66d57339f859247C38f8F47133BD39"); + + await getWantFromWhale(FraxToken, toWei(10000), bob, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), bob, "0xf6C75d85Ef66d57339f859247C38f8F47133BD39"); + + await getWantFromWhale(FraxToken, toWei(10000), charles, "0x820A9eb227BF770A9dd28829380d53B76eAf1209"); + + await getWantFromWhale(TEMPLEToken, toWei(10000), charles, "0xf6C75d85Ef66d57339f859247C38f8F47133BD39"); + + await getWantFromWhale(FXSToken, toWei(1000000), alice, "0xF977814e90dA44bFA03b6295A0616a897441aceC"); + + // transfer FXS to distributor + await fxs.connect(alice).transfer("0x278dc748eda1d8efef1adfb518542612b49fcd34", toWei(5000)); + // transfer FXS to gauge + await fxs.connect(alice).transfer(FRAX_TEMPLE_GAUGE, toWei(700000)); + }); + + it("should harvest correctly", async () => { + let depositAmount = toWei(200); + + let aliceShare, bobShare, charlesShare; + + console.log("=============== Alice deposit =============="); + await deposit(alice, depositAmount); + const _amt = await pool.balanceOf(pickleJar.address); + console.log("Calling Earn:", _amt.toString()); + await pickleJar.earn(); + console.log("Calling Harvest:"); + await harvest(); + + console.log("=============== Bob deposit =============="); + depositAmount = toWei(400); + + await deposit(bob, depositAmount); + await pickleJar.earn(); + await harvest(); + + await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + aliceShare = await pickleJar.balanceOf(alice.address); + console.log("Alice share amount => ", aliceShare.toString()); + + console.log("===============Alice partial withdraw=============="); + console.log("Alice temple balance before withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance before withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + await pickleJar.connect(alice).withdraw(aliceShare.div(BN.from(2))); + + console.log("Alice temple balance after withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance after withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + + await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + console.log("=============== Charles deposit =============="); + + depositAmount = toWei(700); + + await deposit(charles, depositAmount); + + console.log("===============Bob withdraw=============="); + console.log("Bob temple balance before withdrawal => ", (await temple.balanceOf(bob.address)).toString()); + console.log("Bob frax balance before withdrawal => ", (await frax.balanceOf(bob.address)).toString()); + await pickleJar.connect(bob).withdrawAll(); + + console.log("Bob temple balance after withdrawal => ", (await temple.balanceOf(bob.address)).toString()); + console.log("Bob frax balance after withdrawal => ", (await frax.balanceOf(bob.address)).toString()); + + await harvest(); + await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + await pickleJar.earn(); + + await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + console.log("=============== Controller withdraw ==============="); + console.log( + "PickleJar temple balance before withdrawal => ", + (await temple.balanceOf(pickleJar.address)).toString() + ); + console.log("PickleJar frax balance before withdrawal => ", (await frax.balanceOf(pickleJar.address)).toString()); + + await controller.withdrawAll(FRAX_TEMPLE_POOL,{gasLimit:2000000}); + + console.log( + "PickleJar temple balance after withdrawal => ", + (await temple.balanceOf(pickleJar.address)).toString() + ); + console.log("PickleJar frax balance after withdrawal => ", (await frax.balanceOf(pickleJar.address)).toString()); + + console.log("===============Alice Full withdraw=============="); + + console.log("Alice temple balance before withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance before withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + await pickleJar.connect(alice).withdrawAll(); + + console.log("Alice temple balance after withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance after withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + + // await harvest(); + // await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + console.log("=============== charles withdraw =============="); + console.log("Charles temple balance before withdrawal => ", (await temple.balanceOf(charles.address)).toString()); + console.log("Charles frax balance before withdrawal => ", (await frax.balanceOf(charles.address)).toString()); + await pickleJar.connect(charles).withdrawAll(); + + console.log("Charles temple balance after withdrawal => ", (await temple.balanceOf(charles.address)).toString()); + console.log("Charles frax balance after withdrawal => ", (await frax.balanceOf(charles.address)).toString()); + + // console.log("=============== Alice redeposit =============="); + // depositA = toWei(50000); + // depositB = await getAmountB(depositA); + + // await deposit(alice, depositA, depositB); + // await pickleJar.earn(); + + // await harvest(); + // await increaseTime(60 * 60 * 24 * 1); //travel 14 days + + // console.log("===============Alice final withdraw=============="); + + // console.log("Alice temple balance before withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + // console.log("Alice frax balance before withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + // await pickleJar.connect(alice).withdrawAll(); + + // console.log("Alice temple balance after withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + // console.log("Alice frax balance after withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + + console.log("------------------ Finished -----------------------"); + + console.log("Treasury temple balance => ", (await temple.balanceOf(treasury.address)).toString()); + console.log("Treasury frax balance => ", (await frax.balanceOf(treasury.address)).toString()); + console.log("Treasury Frax/Temple LP balance => ", (await pool.balanceOf(treasury.address)).toString()); + + console.log("Strategy temple balance => ", (await temple.balanceOf(strategy.address)).toString()); + console.log("Strategy frax balance => ", (await frax.balanceOf(strategy.address)).toString()); + console.log("Strategy fxs balance => ", (await fxs.balanceOf(strategy.address)).toString()); + + console.log("PickleJar temple balance => ", (await temple.balanceOf(pickleJar.address)).toString()); + console.log("PickleJar frax balance => ", (await frax.balanceOf(pickleJar.address)).toString()); + console.log("PickleJar fxs balance => ", (await fxs.balanceOf(pickleJar.address)).toString()); + + console.log("Locker temple balance => ", (await temple.balanceOf(locker.address)).toString()); + console.log("Locker frax balance => ", (await frax.balanceOf(locker.address)).toString()); + console.log("Locker fxs balance => ", (await fxs.balanceOf(locker.address)).toString()); + + console.log("StrategyProxy temple balance => ", (await temple.balanceOf(strategyProxy.address)).toString()); + console.log("StrategyProxy frax balance => ", (await frax.balanceOf(strategyProxy.address)).toString()); + console.log("StrategyProxy fxs balance => ", (await fxs.balanceOf(strategyProxy.address)).toString()); + }); + /* + it("should withdraw correctly", async () => { + let depositA = toWei(50000); + let depositB = await getAmountB(depositA); + + console.log("=============== Alice deposit =============="); + await deposit(alice, depositA, depositB); + await pickleJar.earn(); + await harvest(); + + await increaseTime(60 * 60 * 24 * 1); //travel 14 days + console.log("PickleJar temple balance before withdrawal => ", (await temple.balanceOf(pickleJar.address)).toString()); + console.log("PickleJar frax balance before withdrawal => ", (await frax.balanceOf(pickleJar.address)).toString()); + + await controller.withdrawAll(FRAX_TEMPLE_POOL); + + console.log("PickleJar temple balance after withdrawal => ", (await temple.balanceOf(pickleJar.address)).toString()); + console.log("PickleJar frax balance after withdrawal => ", (await frax.balanceOf(pickleJar.address)).toString()); + + console.log("Alice temple balance before withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance before withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + + await pickleJar.connect(alice).withdrawAll(); + + console.log("Alice temple balance after withdrawal => ", (await temple.balanceOf(alice.address)).toString()); + console.log("Alice frax balance after withdrawal => ", (await frax.balanceOf(alice.address)).toString()); + }); +*/ + const deposit = async (user, depositAmount) => { + await depositing(user, depositAmount); + const _amt = await pool.balanceOf(user.address); + await pool.connect(user).approve(pickleJar.address, _amt); + + await pickleJar.connect(user).deposit(_amt); + }; + + const harvest = async () => { + console.log("============ Harvest Started =============="); + console.log("Harvest"); + console.log("Ratio before harvest => ", (await pickleJar.getRatio()).toString()); + console.log("Amount in Treasury before: ", (await pool.balanceOf(treasury.address)).toString()); + await increaseTime(60 * 60 * 24 * 14); //travel 30 days + await increaseBlock(1000); + console.log("Amount Harvestable => ", (await strategy.getHarvestable()).toString()); + await strategy.harvest({gasLimit: 10000000}); + console.log("Amount Harvestable after => ", (await strategy.getHarvestable()).toString()); + console.log("Ratio after harvest => ", (await pickleJar.getRatio()).toString()); + console.log("Amount in Treasury after: ", (await pool.balanceOf(treasury.address)).toString()); + console.log("============ Harvest Ended =============="); + }; + + const depositing = async (user, amount) => { + // getting timestamp + const blockNumBefore = await ethers.provider.getBlockNumber(); + const blockBefore = await ethers.provider.getBlock(blockNumBefore); + const timestampBefore = blockBefore.timestamp; + await temple.connect(user).approve(FRAX_TEMPLE_SWAP, amount); + await frax.connect(user).approve(FRAX_TEMPLE_SWAP, amount); + await templeRouter.connect(user).addLiquidity(amount, amount, 0, 0, user.address, timestampBefore + 60); + }; + + // beforeEach(async () => { + // preTestSnapshotID = await hre.network.provider.send("evm_snapshot"); + // }); + + // afterEach(async () => { + // await hre.network.provider.send("evm_revert", [preTestSnapshotID]); + // }); + const poolABI = [ + { + inputs: [ + {internalType: "contract IUniswapV2Pair", name: "_pair", type: "address"}, + {internalType: "contract TempleERC20Token", name: "_templeToken", type: "address"}, + {internalType: "contract IERC20", name: "_fraxToken", type: "address"}, + {internalType: "contract ITempleTreasury", name: "_templeTreasury", type: "address"}, + {internalType: "address", name: "_protocolMintEarningsAccount", type: "address"}, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_dynamicThresholdPrice", + type: "tuple", + }, + {internalType: "uint256", name: "_dynamicThresholdDecayPerBlock", type: "uint256"}, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_interpolateFromPrice", + type: "tuple", + }, + { + components: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + internalType: "struct TempleFraxAMMRouter.Price", + name: "_interpolateToPrice", + type: "tuple", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [{indexed: false, internalType: "uint256", name: "currDynamicThresholdTemple", type: "uint256"}], + name: "DynamicThresholdChange", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "address", name: "previousOwner", type: "address"}, + {indexed: true, internalType: "address", name: "newOwner", type: "address"}, + ], + name: "OwnershipTransferred", + type: "event", + }, + { + anonymous: false, + inputs: [{indexed: false, internalType: "uint256", name: "blockNumber", type: "uint256"}], + name: "PriceCrossedBelowDynamicThreshold", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "bytes32", name: "previousAdminRole", type: "bytes32"}, + {indexed: true, internalType: "bytes32", name: "newAdminRole", type: "bytes32"}, + ], + name: "RoleAdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "address", name: "account", type: "address"}, + {indexed: true, internalType: "address", name: "sender", type: "address"}, + ], + name: "RoleGranted", + type: "event", + }, + { + anonymous: false, + inputs: [ + {indexed: true, internalType: "bytes32", name: "role", type: "bytes32"}, + {indexed: true, internalType: "address", name: "account", type: "address"}, + {indexed: true, internalType: "address", name: "sender", type: "address"}, + ], + name: "RoleRevoked", + type: "event", + }, + { + inputs: [], + name: "CAN_ADD_ALLOWED_USER", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DEFAULT_ADMIN_ROLE", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "DYNAMIC_THRESHOLD_INCREASE_DENOMINATOR", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [{internalType: "address", name: "userAddress", type: "address"}], + name: "addAllowedUser", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountADesired", type: "uint256"}, + {internalType: "uint256", name: "amountBDesired", type: "uint256"}, + {internalType: "uint256", name: "amountAMin", type: "uint256"}, + {internalType: "uint256", name: "amountBMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "addLiquidity", + outputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "amountB", type: "uint256"}, + {internalType: "uint256", name: "liquidity", type: "uint256"}, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "address", name: "", type: "address"}], + name: "allowed", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdDecayPerBlock", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdIncreasePct", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "dynamicThresholdPriceWithDecay", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "fraxToken", + outputs: [{internalType: "contract IERC20", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "reserveIn", type: "uint256"}, + {internalType: "uint256", name: "reserveOut", type: "uint256"}, + ], + name: "getAmountOut", + outputs: [{internalType: "uint256", name: "amountOut", type: "uint256"}], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{internalType: "bytes32", name: "role", type: "bytes32"}], + name: "getRoleAdmin", + outputs: [{internalType: "bytes32", name: "", type: "bytes32"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "grantRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "hasRole", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "interpolateFromPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "interpolateToPrice", + outputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "temple", type: "uint256"}, + {internalType: "uint256", name: "frax", type: "uint256"}, + ], + name: "mintRatioAt", + outputs: [ + {internalType: "uint256", name: "numerator", type: "uint256"}, + {internalType: "uint256", name: "denominator", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "openAccessEnabled", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [{internalType: "address", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "pair", + outputs: [{internalType: "contract IUniswapV2Pair", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "priceCrossedBelowDynamicThresholdBlock", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "reserveA", type: "uint256"}, + {internalType: "uint256", name: "reserveB", type: "uint256"}, + ], + name: "quote", + outputs: [{internalType: "uint256", name: "amountB", type: "uint256"}], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{internalType: "address", name: "userAddress", type: "address"}], + name: "removeAllowedUser", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "liquidity", type: "uint256"}, + {internalType: "uint256", name: "amountAMin", type: "uint256"}, + {internalType: "uint256", name: "amountBMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "removeLiquidity", + outputs: [ + {internalType: "uint256", name: "amountA", type: "uint256"}, + {internalType: "uint256", name: "amountB", type: "uint256"}, + ], + stateMutability: "nonpayable", + type: "function", + }, + {inputs: [], name: "renounceOwnership", outputs: [], stateMutability: "nonpayable", type: "function"}, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "renounceRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "bytes32", name: "role", type: "bytes32"}, + {internalType: "address", name: "account", type: "address"}, + ], + name: "revokeRole", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "_dynamicThresholdDecayPerBlock", type: "uint256"}], + name: "setDynamicThresholdDecayPerBlock", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "_dynamicThresholdIncreasePct", type: "uint256"}], + name: "setDynamicThresholdIncreasePct", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + name: "setInterpolateFromPrice", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "frax", type: "uint256"}, + {internalType: "uint256", name: "temple", type: "uint256"}, + ], + name: "setInterpolateToPrice", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "address", name: "_protocolMintEarningsAccount", type: "address"}], + name: "setProtocolMintEarningsAccount", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "bytes4", name: "interfaceId", type: "bytes4"}], + name: "supportsInterface", + outputs: [{internalType: "bool", name: "", type: "bool"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "amountOutMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "swapExactFraxForTemple", + outputs: [{internalType: "uint256", name: "amountOut", type: "uint256"}], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "amountIn", type: "uint256"}], + name: "swapExactFraxForTempleQuote", + outputs: [ + {internalType: "uint256", name: "amountInAMM", type: "uint256"}, + {internalType: "uint256", name: "amountInProtocol", type: "uint256"}, + {internalType: "uint256", name: "amountOutAMM", type: "uint256"}, + {internalType: "uint256", name: "amountOutProtocol", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + {internalType: "uint256", name: "amountIn", type: "uint256"}, + {internalType: "uint256", name: "amountOutMin", type: "uint256"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "deadline", type: "uint256"}, + ], + name: "swapExactTempleForFrax", + outputs: [{internalType: "uint256", name: "", type: "uint256"}], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{internalType: "uint256", name: "amountIn", type: "uint256"}], + name: "swapExactTempleForFraxQuote", + outputs: [ + {internalType: "bool", name: "priceBelowIV", type: "bool"}, + {internalType: "bool", name: "willCrossDynamicThreshold", type: "bool"}, + {internalType: "uint256", name: "amountOut", type: "uint256"}, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "templeToken", + outputs: [{internalType: "contract TempleERC20Token", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "templeTreasury", + outputs: [{internalType: "contract ITempleTreasury", name: "", type: "address"}], + stateMutability: "view", + type: "function", + }, + {inputs: [], name: "toggleOpenAccess", outputs: [], stateMutability: "nonpayable", type: "function"}, + { + inputs: [{internalType: "address", name: "newOwner", type: "address"}], + name: "transferOwnership", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + {internalType: "address", name: "token", type: "address"}, + {internalType: "address", name: "to", type: "address"}, + {internalType: "uint256", name: "amount", type: "uint256"}, + ], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ]; +});