Contract Name:
GroGvtBridgeSwapper
Contract Source Code:
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
import "./ZkSyncBridgeSwapper.sol";
import "./interfaces/IGroController.sol";
import "./interfaces/IGroToken.sol";
import "./interfaces/IGroDepositHandler.sol";
import "./interfaces/IGroWithdrawHandler.sol";
import "./interfaces/IGroBuoy.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @notice Exchanges a stablecoin for Gro Vault LP tokens.
* Example indexes:
* 0: DAI
* 1: GVT
*/
contract GroGvtBridgeSwapper is ZkSyncBridgeSwapper {
address public immutable depositHandler;
address public immutable withdrawHandler;
address public immutable stablecoin;
uint256 public immutable stablecoinIndex;
address public immutable gvt;
address public immutable buoy;
address public immutable groReferral;
constructor(
address _zkSync,
address _l2Account,
address _groController,
uint256 _stablecoinIndex,
address _groReferral
)
ZkSyncBridgeSwapper(_zkSync, _l2Account)
{
require(_groController != address(0), "null _groController");
IGroController controller = IGroController(_groController);
require(_stablecoinIndex < 3, "invalid _stablecoinIndex");
stablecoin = controller.stablecoins()[_stablecoinIndex];
stablecoinIndex = _stablecoinIndex;
depositHandler = controller.depositHandler();
withdrawHandler = controller.withdrawHandler();
gvt = controller.gvt();
buoy = controller.buoy();
groReferral = _groReferral;
}
function exchange(uint256 _indexIn, uint256 _indexOut, uint256 _amountIn) external override returns (uint256 amountOut) {
require(_indexIn + _indexOut == 1, "invalid indexes");
if (_indexIn == 0) {
transferFromZkSync(stablecoin);
amountOut = swapStablecoinForGvt(_amountIn);
transferToZkSync(gvt, amountOut);
emit Swapped(stablecoin, _amountIn, gvt, amountOut);
} else {
transferFromZkSync(gvt);
amountOut = swapGvtForStablecoin(_amountIn);
transferToZkSync(stablecoin, amountOut);
emit Swapped(gvt, _amountIn, stablecoin, amountOut);
}
}
function swapStablecoinForGvt(uint256 _amountIn) public returns (uint256) {
uint256[3] memory inAmounts;
inAmounts[stablecoinIndex] = _amountIn;
uint256 balanceBefore = IGroToken(gvt).balanceOf(address(this));
IERC20(stablecoin).approve(depositHandler, _amountIn);
uint256 minLpAmount = getMinAmountOut(IGroBuoy(buoy).stableToLp(inAmounts, true));
IGroDepositHandler(depositHandler).depositGvt(inAmounts, minLpAmount, groReferral);
uint256 balanceAfter = IGroToken(gvt).balanceOf(address(this));
return balanceAfter - balanceBefore;
}
function swapGvtForStablecoin(uint256 _amountIn) public returns (uint256) {
uint256 balanceBefore = IERC20(stablecoin).balanceOf(address(this));
uint256 usdAmount = IGroToken(gvt).getShareAssets(_amountIn);
uint256 lpAmount = IGroBuoy(buoy).usdToLp(usdAmount);
uint256 stableAmount = IGroBuoy(buoy).singleStableFromUsd(usdAmount, int128(uint128(stablecoinIndex)));
uint256 minAmount = getMinAmountOut(stableAmount);
IGroWithdrawHandler(withdrawHandler).withdrawByStablecoin(false, stablecoinIndex, lpAmount, minAmount);
uint256 balanceAfter = IERC20(stablecoin).balanceOf(address(this));
return balanceAfter - balanceBefore;
}
function tokens(uint256 _index) external view returns (address) {
if (_index == 0) {
return stablecoin;
} else if (_index == 1) {
return gvt;
}
revert("invalid _index");
}
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
import "./interfaces/IZkSync.sol";
import "./interfaces/IBridgeSwapper.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
abstract contract ZkSyncBridgeSwapper is IBridgeSwapper {
// The owner of the contract
address public owner;
// The max slippage accepted for swapping. Defaults to 1% with 6 decimals.
uint256 public slippagePercent = 1e6;
// The ZkSync bridge contract
address public immutable zkSync;
// The L2 market maker account
address public immutable l2Account;
address constant internal ETH_TOKEN = address(0);
event OwnerChanged(address _owner, address _newOwner);
event SlippageChanged(uint256 _slippagePercent);
modifier onlyOwner {
require(msg.sender == owner, "unauthorised");
_;
}
constructor(address _zkSync, address _l2Account) {
zkSync = _zkSync;
l2Account = _l2Account;
owner = msg.sender;
}
function changeOwner(address _newOwner) external onlyOwner {
require(_newOwner != address(0), "invalid input");
owner = _newOwner;
emit OwnerChanged(owner, _newOwner);
}
function changeSlippage(uint256 _slippagePercent) external onlyOwner {
require(_slippagePercent != slippagePercent && _slippagePercent <= 100e6, "invalid slippage");
slippagePercent = _slippagePercent;
emit SlippageChanged(slippagePercent);
}
/**
* @dev Check if there is a pending balance to withdraw in zkSync and withdraw it if applicable.
* @param _token The token to withdraw.
*/
function transferFromZkSync(address _token) internal {
uint128 pendingBalance = IZkSync(zkSync).getPendingBalance(address(this), _token);
if (pendingBalance > 0) {
IZkSync(zkSync).withdrawPendingBalance(payable(address(this)), _token, pendingBalance);
}
}
/**
* @dev Deposit the ETH or ERC20 token to zkSync.
* @param _outputToken The token that was given.
* @param _amountOut The amount of given token.
*/
function transferToZkSync(address _outputToken, uint256 _amountOut) internal {
if (_outputToken == ETH_TOKEN) {
// deposit Eth to L2 bridge
IZkSync(zkSync).depositETH{value: _amountOut}(l2Account);
} else {
// approve the zkSync bridge to take the output token
IERC20(_outputToken).approve(zkSync, _amountOut);
// deposit the output token to the L2 bridge
IZkSync(zkSync).depositERC20(IERC20(_outputToken), toUint104(_amountOut), l2Account);
}
}
/**
* @dev Safety method to recover ETH or ERC20 tokens that are sent to the contract by error.
* @param _token The token to recover.
*/
function recoverToken(address _recipient, address _token) external onlyOwner returns (uint256 balance) {
bool success;
if (_token == ETH_TOKEN) {
balance = address(this).balance;
(success, ) = _recipient.call{value: balance}("");
} else {
balance = IERC20(_token).balanceOf(address(this));
success = IERC20(_token).transfer(_recipient, balance);
}
require(success, "failed to recover");
}
/**
* @dev fallback method to make sure we can receive ETH
*/
receive() external payable {
}
/**
* @dev Returns the minimum accepted out amount.
*/
function getMinAmountOut(uint256 _amountIn) internal view returns (uint256) {
return _amountIn * (100e6 - slippagePercent) / 100e6;
}
/**
* @dev Returns the downcasted uint104 from uint256, reverting on
* overflow (when the input is greater than largest uint104).
*/
function toUint104(uint256 value) internal pure returns (uint104) {
require(value <= type(uint104).max, "SafeCast: value doesn't fit in 104 bits");
return uint104(value);
}
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
interface IGroController {
function DAI() external view returns (address);
function USDC() external view returns (address);
function USDT() external view returns (address);
function stablecoins() external view returns (address[3] memory);
function buoy() external view returns (address);
function depositHandler() external view returns (address);
function withdrawHandler() external view returns (address);
function gvt() external view returns (address);
function pwrd() external view returns (address);
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IGroToken is IERC20 {
function pricePerShare() external view returns (uint256);
function getShareAssets(uint256) external view returns (uint256);
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
interface IGroDepositHandler {
function depositGvt(
uint256[3] calldata inAmounts,
uint256 minAmount,
address referral
) external;
function depositPwrd(
uint256[3] calldata inAmounts,
uint256 minAmount,
address referral
) external;
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
interface IGroWithdrawHandler {
function withdrawalFee(bool pwrd) external view returns (uint256);
function withdrawByLPToken(
bool pwrd,
uint256 lpAmount,
uint256[3] calldata minAmounts
) external;
function withdrawByStablecoin(
bool pwrd,
uint256 index,
uint256 lpAmount,
uint256 minAmount
) external;
function withdrawAllSingle(
bool pwrd,
uint256 index,
uint256 minAmount
) external;
function withdrawAllBalanced(bool pwrd, uint256[3] calldata minAmounts) external;
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
interface IGroBuoy {
function safetyCheck() external view returns (bool);
function updateRatios() external returns (bool);
function updateRatiosWithTolerance(uint256 tolerance) external returns (bool);
function lpToUsd(uint256 inAmount) external view returns (uint256);
function usdToLp(uint256 inAmount) external view returns (uint256);
function stableToUsd(uint256[3] calldata inAmount, bool deposit) external view returns (uint256);
function stableToLp(uint256[3] calldata inAmount, bool deposit) external view returns (uint256);
function singleStableFromLp(uint256 inAmount, int128 i) external view returns (uint256);
function getVirtualPrice() external view returns (uint256);
function singleStableFromUsd(uint256 inAmount, int128 i) external view returns (uint256);
function singleStableToUsd(uint256 inAmount, uint256 i) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IZkSync {
function getPendingBalance(address _address, address _token) external view returns (uint128);
function withdrawPendingBalance(address payable _owner, address _token, uint128 _amount) external;
function depositETH(address _zkSyncAddress) external payable;
function depositERC20(IERC20 _token, uint104 _amount, address _zkSyncAddress) external;
}
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.3;
interface IBridgeSwapper {
event Swapped(address _inputToken, uint256 _amountIn, address _outputToken, uint256 _amountOut);
/**
* @notice Perform an exchange between two tokens
* @dev Index values can usually be found via the constructor arguments (if not hardcoded)
* @param _indexIn Index value for the token to send
* @param _indexOut Index valie of the token to receive
* @param _amountIn Amount of `_indexIn` being exchanged
* @return Actual amount of `_indexOut` received
*/
function exchange(uint256 _indexIn, uint256 _indexOut, uint256 _amountIn) external returns (uint256);
}