-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/zap #344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feat/zap #344
Changes from 6 commits
8bae565
31d963f
9598798
28122fb
a322d11
5325ee4
eda7291
9f16819
77fab5b
f85501a
2a91684
ad91d71
5214f27
9f90257
ceaacd0
2d40fef
32e7dfc
079c899
810eaa8
80299e3
8be0f88
d998a93
1a4733e
039c986
f415df7
26b14ea
3b4f2f0
f62deb6
540b782
ca20242
a055983
0306760
cc8bfbb
e582cd3
7b15274
3e1a449
a6ae777
5a3583e
cb9b392
76b4f47
a9738ba
c275565
79302dd
48d532d
aa3e221
7af1a29
7f4c2b7
09c6823
d3aac97
e54463b
199b7ca
4409b7e
b49929e
317e635
c2c700e
9e7167d
8914f34
3ffae50
c66e76a
894638e
3bfdafa
7ecbf76
a4dc1f6
e765e76
b90db76
ab034ac
80b5497
0c606c5
a2e4973
12dcdd0
26d0b26
6ff329a
e5ebd26
a573a48
ce6e222
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.15; | ||
|
|
||
| /** | ||
| * @title Library for custom structs for OptyFiZapper | ||
| * @author OptyFi | ||
| */ | ||
| library DataTypes { | ||
| /** | ||
| * @param vault address of the vault | ||
| * @param toAmount amount of toToken to receive | ||
| * @param callees array of addresses to call (DEX addresses) | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * @param exchangeData calldata to execute on callees | ||
| * @param startIndexes the index of the beginning of each call in exchangeData | ||
| * @param values array of encoded values for each call in exchangeData | ||
| * @param permit ERC2612 permit | ||
| * @param deadline timestamp until which swap may be fulfilled | ||
| * @param accountsProof merkle proof for caller | ||
| * @param codesProof merkle proof for code hash if caller is smart contract | ||
| */ | ||
| struct ZapData { | ||
| address vault; | ||
| uint256 toAmount; | ||
| uint256 deadline; | ||
| bytes exchangeData; | ||
| bytes permit; | ||
| address[] callees; | ||
| uint256[] startIndexes; | ||
| uint256[] values; | ||
| bytes32[] accountsProof; | ||
| bytes32[] codesProof; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity >=0.6.0 <0.8.0; | ||
|
|
||
NouDaimon marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /** | ||
| * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data. | ||
| * | ||
| * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, | ||
| * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding | ||
| * they need in their contracts using a combination of `abi.encode` and `keccak256`. | ||
| * | ||
| * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding | ||
| * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA | ||
| * ({_hashTypedDataV4}). | ||
| * | ||
| * The implementation of the domain separator was designed to be as efficient as possible while still properly updating | ||
| * the chain id to protect against replay attacks on an eventual fork of the chain. | ||
| * | ||
| * NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method | ||
| * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask]. | ||
| * | ||
| * _Available since v3.4._ | ||
| */ | ||
| abstract contract EIP712 { | ||
| /* solhint-disable var-name-mixedcase */ | ||
| bytes32 private immutable _HASHED_NAME; | ||
| bytes32 private immutable _HASHED_VERSION; | ||
| bytes32 private immutable _TYPE_HASH; | ||
|
|
||
| /* solhint-enable var-name-mixedcase */ | ||
|
|
||
| /** | ||
| * @dev Initializes the domain separator and parameter caches. | ||
| * | ||
| * The meaning of `name` and `version` is specified in | ||
| * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]: | ||
| * | ||
| * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. | ||
| * - `version`: the current major version of the signing domain. | ||
| * | ||
| * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart | ||
| * contract upgrade]. | ||
| */ | ||
| constructor(string memory name, string memory version) internal { | ||
| bytes32 hashedName = keccak256(bytes(name)); | ||
| bytes32 hashedVersion = keccak256(bytes(version)); | ||
| bytes32 typeHash = | ||
| keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); | ||
| _HASHED_NAME = hashedName; | ||
| _HASHED_VERSION = hashedVersion; | ||
| _TYPE_HASH = typeHash; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Returns the domain separator for the current chain. | ||
| */ | ||
| function _domainSeparatorV4() internal view virtual returns (bytes32) { | ||
| return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION); | ||
| } | ||
|
|
||
| function _buildDomainSeparator( | ||
| bytes32 typeHash, | ||
| bytes32 name, | ||
| bytes32 version | ||
| ) private view returns (bytes32) { | ||
| return keccak256(abi.encode(typeHash, name, version, _getChainId(), address(this))); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this | ||
| * function returns the hash of the fully encoded EIP712 message for this domain. | ||
| * | ||
| * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example: | ||
| * | ||
| * ```solidity | ||
| * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( | ||
| * keccak256("Mail(address to,string contents)"), | ||
| * mailTo, | ||
| * keccak256(bytes(mailContents)) | ||
| * ))); | ||
| * address signer = ECDSA.recover(digest, signature); | ||
| * ``` | ||
| */ | ||
| function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) { | ||
| return keccak256(abi.encodePacked("\x19\x01", _domainSeparatorV4(), structHash)); | ||
| } | ||
|
|
||
| function _getChainId() private view returns (uint256 chainId) { | ||
| this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691 | ||
| // solhint-disable-next-line no-inline-assembly | ||
| assembly { | ||
| chainId := chainid() | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| //SPDX-license-identifier: MIT | ||
| pragma solidity ^0.8.15; | ||
|
|
||
| import { DataTypes } from "./DataTypes.sol"; | ||
|
|
||
| /** | ||
| * @title OptyFiZapper interface | ||
| * @author OptyFi | ||
| */ | ||
| interface IOptyFiZapper { | ||
| /** | ||
| * @notice performs an arbitrary swap of a given token or ETH to deposit in a OptyFi Vault | ||
| * @param _token the address of the input token | ||
| * @param _amount input token amount to deposit | ||
| * @param _permitParams ERC2612 permit params | ||
| * @param _zapParams the zapParams for the zap to be performed | ||
| * @return the shares received from the vault deposit | ||
NouDaimon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| function zapIn( | ||
| address _token, | ||
| uint256 _amount, | ||
| bytes memory _permitParams, | ||
| DataTypes.ZapData memory _zapParams | ||
| ) external payable returns (uint256); | ||
|
|
||
| /** | ||
| * @notice redeems the vault shares and performs an arbitrary swap | ||
| * from the OptyFi Vault underlying token to any given token | ||
| * @param _token the address of the input token | ||
| * @param _amount input token amount to deposit | ||
| * @param _permitParams ERC2612 permit params | ||
| * @param _zapParams the zapParams for the zap to be performed | ||
| * @return the amount of output tokens received | ||
| */ | ||
| function zapOut( | ||
| address _token, | ||
| uint256 _amount, | ||
| bytes memory _permitParams, | ||
| DataTypes.ZapData memory _zapParams | ||
| ) external returns (uint256); | ||
|
|
||
| /** | ||
| * @notice set swapper address | ||
| * @param _swapper swapper address | ||
| */ | ||
| function setSwapper(address _swapper) external; | ||
|
|
||
| /** | ||
| * @notice get swapper address | ||
| */ | ||
| function getSwapper() external view returns (address); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| //SPDX-license-identifier: MIT | ||
| pragma solidity ^0.8.15; | ||
|
|
||
| import "@openzeppelin/contracts-0.8.x/token/ERC20/extensions/draft-IERC20Permit.sol"; | ||
| import "@openzeppelin/contracts-0.8.x/token/ERC20/utils/SafeERC20.sol"; | ||
| import "@openzeppelin/contracts-0.8.x/token/ERC20/IERC20.sol"; | ||
| import "@openzeppelin/contracts-0.8.x/access/Ownable.sol"; | ||
| import { IOptyFiZapper } from "./IOptyFiZapper.sol"; | ||
| import { ISwapper } from "../optyfi-swapper/contracts/swap/ISwapper.sol"; | ||
| import { DataTypes as SwapTypes } from "../optyfi-swapper/contracts/swap/DataTypes.sol"; | ||
| import { DataTypes } from "./DataTypes.sol"; | ||
|
|
||
| interface IWETH is IERC20 { | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| function deposit() external payable; | ||
|
|
||
| function withdraw(uint256 wad) external; | ||
| } | ||
|
|
||
| interface IVault is IERC20 { | ||
| function userDepositVault( | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| address _beneficiary, | ||
| uint256 _userDepositUT, | ||
| bytes calldata _permitParams, | ||
| bytes32[] calldata _accountsProof, | ||
| bytes32[] calldata _codesProof | ||
| ) external returns (uint256); | ||
|
|
||
| function userWithdrawVault( | ||
| address _receiver, | ||
| uint256 _userWithdrawVT, | ||
| bytes32[] calldata _accountsProof, | ||
| bytes32[] calldata _codesProof | ||
| ) external returns (uint256); | ||
|
|
||
| function underlyingToken() external returns (address); | ||
| } | ||
|
|
||
| /** | ||
| * @title OptyFiZapper | ||
| * @author OptyFi | ||
| */ | ||
| contract OptyFiZapper is IOptyFiZapper, Ownable { | ||
|
||
| using SafeERC20 for IERC20; | ||
|
|
||
| address public constant ETH = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); | ||
| ISwapper public swapper; | ||
|
|
||
| constructor(address _swapper) { | ||
| swapper = ISwapper(_swapper); | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc IOptyFiZapper | ||
| */ | ||
| function zapIn( | ||
| address _token, | ||
| uint256 _amount, | ||
| bytes memory _permitParams, | ||
| DataTypes.ZapData memory _zapParams | ||
| ) external payable override returns (uint256) { | ||
| address underlyingToken = _getUnderlyingToken(_zapParams.vault); | ||
|
||
| require(underlyingToken != _token, "Invalid Token"); | ||
|
|
||
| if (_token != ETH) { | ||
| _permit(_token, _permitParams); | ||
|
||
| IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); | ||
| _approveTokenIfNeeded(_token, swapper.tokenTransferProxy(), _amount); | ||
|
||
| } | ||
|
|
||
| SwapTypes.SwapData memory swapData = | ||
| SwapTypes.SwapData( | ||
| _token, | ||
| underlyingToken, | ||
| _amount, | ||
| _zapParams.toAmount, | ||
| _zapParams.callees, | ||
| _zapParams.exchangeData, | ||
| _zapParams.startIndexes, | ||
| _zapParams.values, | ||
| payable(address(this)), | ||
| _zapParams.permit, | ||
| _zapParams.deadline | ||
| ); | ||
|
|
||
| (uint256 receivedAmount, uint256 returnedBalance) = swapper.swap{ value: msg.value }(swapData); | ||
|
||
| if (returnedBalance > 0) { | ||
| IERC20(_token).safeTransfer(msg.sender, returnedBalance); | ||
| } | ||
|
|
||
| _approveTokenIfNeeded(underlyingToken, _zapParams.vault, receivedAmount); | ||
|
||
|
|
||
| uint256 sharesReceived = | ||
|
||
| IVault(_zapParams.vault).userDepositVault( | ||
| msg.sender, | ||
| receivedAmount, | ||
| _zapParams.permit, | ||
| _zapParams.accountsProof, | ||
| _zapParams.codesProof | ||
| ); | ||
|
|
||
| return sharesReceived; | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc IOptyFiZapper | ||
| */ | ||
| function zapOut( | ||
| address _token, | ||
| uint256 _amount, | ||
| bytes memory _permitParams, | ||
| DataTypes.ZapData memory _zapParams | ||
| ) external override returns (uint256) { | ||
| address underlyingToken = _getUnderlyingToken(_zapParams.vault); | ||
| require(underlyingToken != _token, "Invalid Token"); | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| _permit(_zapParams.vault, _permitParams); | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| IERC20(_zapParams.vault).safeTransferFrom(msg.sender, address(this), _amount); | ||
| uint256 amountToSwap = | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| IVault(_zapParams.vault).userWithdrawVault( | ||
| address(this), | ||
| _amount, | ||
| _zapParams.accountsProof, | ||
| _zapParams.codesProof | ||
| ); | ||
|
|
||
| _approveTokenIfNeeded(underlyingToken, swapper.tokenTransferProxy(), amountToSwap); | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| SwapTypes.SwapData memory swapData = | ||
| SwapTypes.SwapData( | ||
| underlyingToken, | ||
| _token, | ||
| amountToSwap, | ||
| _zapParams.toAmount, | ||
| _zapParams.callees, | ||
| _zapParams.exchangeData, | ||
| _zapParams.startIndexes, | ||
| _zapParams.values, | ||
| payable(msg.sender), | ||
| _zapParams.permit, | ||
| _zapParams.deadline | ||
| ); | ||
|
|
||
| (uint256 receivedAmount, uint256 returnedBalance) = swapper.swap(swapData); | ||
| if (returnedBalance > 0) { | ||
| IERC20(_token).safeTransfer(msg.sender, returnedBalance); | ||
| } | ||
|
|
||
| return receivedAmount; | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc IOptyFiZapper | ||
| */ | ||
| function setSwapper(address _swapper) external override onlyOwner { | ||
0xhafa marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| swapper = ISwapper(_swapper); | ||
| } | ||
|
|
||
| /** | ||
| * @inheritdoc IOptyFiZapper | ||
| */ | ||
| function getSwapper() external view returns (address) { | ||
| return address(swapper); | ||
| } | ||
|
|
||
| /** | ||
| * @dev function to get the underlying token of the OptyFi Vault | ||
| */ | ||
| function _getUnderlyingToken(address _vault) internal returns (address) { | ||
| return IVault(_vault).underlyingToken(); | ||
| } | ||
|
|
||
| /** | ||
| * @dev function to call token permit method of extended ERC20 | ||
| * @param _token address of the token to call | ||
| * @param _permitParams raw data of the call `permit` of the token | ||
| */ | ||
| function _permit(address _token, bytes memory _permitParams) internal { | ||
| if (_permitParams.length == 32 * 7) { | ||
| (bool success, ) = _token.call(abi.encodePacked(IERC20Permit.permit.selector, _permitParams)); | ||
| require(success, "Permit failed"); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @dev function to increase allowance | ||
| * @param _token address of the token to call | ||
| * @param _spender address of the spender | ||
| * @param _amount transfer amount | ||
| */ | ||
| function _approveTokenIfNeeded( | ||
| address _token, | ||
| address _spender, | ||
| uint256 _amount | ||
| ) private { | ||
| if (IERC20(_token).allowance(address(this), _spender) < _amount) { | ||
| IERC20(_token).safeApprove(_spender, type(uint256).max); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.