From 45a8ca8f6a744bec1d2d0ceebe28e1f653710152 Mon Sep 17 00:00:00 2001 From: q1q0 Date: Fri, 17 Feb 2023 11:55:24 -0500 Subject: [PATCH] add erc4626 connector --- .../basic-ERC4626/IERC20Metadata.sol | 28 +++ .../connectors/basic-ERC4626/IERC4626.sol | 232 ++++++++++++++++++ .../connectors/basic-ERC4626/events.sol | 35 +++ .../mainnet/connectors/basic-ERC4626/main.sol | 136 ++++++++++ package-lock.json | 2 +- 5 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 contracts/mainnet/connectors/basic-ERC4626/IERC20Metadata.sol create mode 100644 contracts/mainnet/connectors/basic-ERC4626/IERC4626.sol create mode 100644 contracts/mainnet/connectors/basic-ERC4626/events.sol create mode 100644 contracts/mainnet/connectors/basic-ERC4626/main.sol diff --git a/contracts/mainnet/connectors/basic-ERC4626/IERC20Metadata.sol b/contracts/mainnet/connectors/basic-ERC4626/IERC20Metadata.sol new file mode 100644 index 00000000..279b2217 --- /dev/null +++ b/contracts/mainnet/connectors/basic-ERC4626/IERC20Metadata.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol) + +pragma solidity ^0.7.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +/** + * @dev Interface for the optional metadata functions from the ERC20 standard. + * + * _Available since v4.1._ + */ +interface IERC20Metadata is IERC20 { + /** + * @dev Returns the name of the token. + */ + function name() external view returns (string memory); + + /** + * @dev Returns the symbol of the token. + */ + function symbol() external view returns (string memory); + + /** + * @dev Returns the decimals places of the token. + */ + function decimals() external view returns (uint8); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/basic-ERC4626/IERC4626.sol b/contracts/mainnet/connectors/basic-ERC4626/IERC4626.sol new file mode 100644 index 00000000..3eef65c7 --- /dev/null +++ b/contracts/mainnet/connectors/basic-ERC4626/IERC4626.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.8.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.7.0; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "./IERC20Metadata.sol"; + +/** + * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in + * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. + * + * _Available since v4.7._ + */ +interface IERC4626 is IERC20, IERC20Metadata { + event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); + + event Withdraw( + address indexed sender, + address indexed receiver, + address indexed owner, + uint256 assets, + uint256 shares + ); + + /** + * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + * + * - MUST be an ERC-20 token contract. + * - MUST NOT revert. + */ + function asset() external view returns (address assetTokenAddress); + + /** + * @dev Returns the total amount of the underlying asset that is “managed” by Vault. + * + * - SHOULD include any compounding that occurs from yield. + * - MUST be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT revert. + */ + function totalAssets() external view returns (uint256 totalManagedAssets); + + /** + * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToShares(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal + * scenario where all the conditions are met. + * + * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. + * - MUST NOT show any variations depending on the caller. + * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. + * - MUST NOT revert. + * + * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the + * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and + * from. + */ + function convertToAssets(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, + * through a deposit call. + * + * - MUST return a limited value if receiver is subject to some deposit limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. + * - MUST NOT revert. + */ + function maxDeposit(address receiver) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + * in the same transaction. + * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + * deposit would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewDeposit(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * deposit execution, and are accounted for during deposit. + * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function deposit(uint256 assets, address receiver) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + * - MUST return a limited value if receiver is subject to some mint limit. + * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. + * - MUST NOT revert. + */ + function maxMint(address receiver) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given + * current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + * same transaction. + * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + * would be accepted, regardless if the user has enough tokens approved, etc. + * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by minting. + */ + function previewMint(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + * + * - MUST emit the Deposit event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + * execution, and are accounted for during mint. + * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + * approving enough underlying tokens to the Vault contract, etc). + * + * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + */ + function mint(uint256 shares, address receiver) external returns (uint256 assets); + + /** + * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the + * Vault, through a withdraw call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxWithdraw(address owner) external view returns (uint256 maxAssets); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + * called + * in the same transaction. + * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + * the withdrawal would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by depositing. + */ + function previewWithdraw(uint256 assets) external view returns (uint256 shares); + + /** + * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * withdraw execution, and are accounted for during withdraw. + * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares); + + /** + * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, + * through a redeem call. + * + * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. + * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. + * - MUST NOT revert. + */ + function maxRedeem(address owner) external view returns (uint256 maxShares); + + /** + * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, + * given current on-chain conditions. + * + * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + * same transaction. + * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + * redemption would be accepted, regardless if the user has enough shares, etc. + * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. + * - MUST NOT revert. + * + * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in + * share price or some other type of condition, meaning the depositor will lose assets by redeeming. + */ + function previewRedeem(uint256 shares) external view returns (uint256 assets); + + /** + * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. + * + * - MUST emit the Withdraw event. + * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + * redeem execution, and are accounted for during redeem. + * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + * not having enough shares, etc). + * + * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. + * Those methods should be performed separately. + */ + function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets); +} \ No newline at end of file diff --git a/contracts/mainnet/connectors/basic-ERC4626/events.sol b/contracts/mainnet/connectors/basic-ERC4626/events.sol new file mode 100644 index 00000000..e35e994f --- /dev/null +++ b/contracts/mainnet/connectors/basic-ERC4626/events.sol @@ -0,0 +1,35 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogWithdraw( + address indexed erc20, + uint256 tokenAmt, + address indexed to, + uint256 getId, + uint256 setId + ); + event LogRedeem( + address indexed erc20, + uint256 tokenAmt, + address indexed to, + uint256 getId, + uint256 setId + ); + event LogDeposit( + address indexed erc20, + uint256 tokenAmt, + address indexed receiver, + address indexed owner, + uint256 getId, + uint256 setId + ); + event LogMint( + address indexed erc20, + uint256 tokenAmt, + address indexed receiver, + address indexed owner, + uint256 getId, + uint256 setId + ); +} diff --git a/contracts/mainnet/connectors/basic-ERC4626/main.sol b/contracts/mainnet/connectors/basic-ERC4626/main.sol new file mode 100644 index 00000000..24f417b2 --- /dev/null +++ b/contracts/mainnet/connectors/basic-ERC4626/main.sol @@ -0,0 +1,136 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title Basic. + * @dev Deposit & Withdraw from DSA. + */ + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { IERC4626 } from "./IERC4626.sol"; + +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; +import { Events } from "./events.sol"; + +abstract contract BasicResolver is Events, DSMath, Basic { + using SafeERC20 for IERC4626; + + /** + * @dev Deposit Assets To Smart Account From any user. + * @notice Deposit a token to DSA from any user. + * @param token The address of the token to deposit. + * @param amt The amount of tokens to deposit. (For max: `uint256(-1)`) + * @param from The address depositing the token. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function deposit( + address token, + uint256 amt, + address from, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint _amt = getUint(getId, amt); + IERC4626 tokenContract = IERC4626(token); + _amt = _amt == uint(-1) ? tokenContract.balanceOf(from) : _amt; + uint _shares = tokenContract.deposit(_amt, from); + + setUint(setId, _shares); + + _eventName = "LogDeposit(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode(token, _amt, from, getId, setId); + } + + function mint( + address token, + uint256 amt, + address from, + uint256 getId, + uint256 setId + ) + public + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint _amt = getUint(getId, amt); + IERC4626 tokenContract = IERC4626(token); + _amt = _amt == uint(-1) ? tokenContract.balanceOf(from) : _amt; + uint _shares = tokenContract.mint(_amt, from); + + setUint(setId, _shares); + + _eventName = "LogMint(address,uint256,address,uint256,uint256)"; + _eventParam = abi.encode(token, _amt, from, getId, setId); + } + + /** + * @dev Withdraw Assets from Smart Account + * @notice Withdraw a token from DSA + * @param token The address of the token to withdraw. + * @param amt The amount of tokens to withdraw. (For max: `uint256(-1)`) + * @param receiver The address to receive the token upon withdrawal + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdraw( + address token, + uint amt, + address receiver, + address owner, + uint getId, + uint setId + ) + public + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint _amt = getUint(getId, amt); + + IERC4626 tokenContract = IERC4626(token); + _amt = _amt == uint(-1) + ? tokenContract.balanceOf(address(this)) + : _amt; + uint _shares = tokenContract.withdraw(_amt, receiver, owner); + + setUint(setId, _shares); + + _eventName = "LogWithdraw(address,uint256,address,address,uint256,uint256)"; + _eventParam = abi.encode(token, _amt, receiver, owner, getId, setId); + } + + function redeem( + address token, + uint amt, + address receiver, + address owner, + uint getId, + uint setId + ) + public + payable + returns (string memory _eventName, bytes memory _eventParam) + { + uint _amt = getUint(getId, amt); + + IERC4626 tokenContract = IERC4626(token); + _amt = _amt == uint(-1) + ? tokenContract.balanceOf(address(this)) + : _amt; + uint _shares = tokenContract.redeem(_amt, receiver, owner); + + setUint(setId, _shares); + + _eventName = "LogRedeem(address,uint256,address,address,uint256,uint256)"; + _eventParam = abi.encode(token, _amt, receiver, owner, getId, setId); + } +} + +contract ConnectV2Basic is BasicResolver { + string public constant name = "BASIC-ERC4626-A"; +} diff --git a/package-lock.json b/package-lock.json index bc7ad39e..0be1689d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@openzeppelin/contracts": "^3.4.0-solc-0.7", + "@openzeppelin/contracts": "^3.4.2", "@typechain/ethers-v5": "^8.0.5", "@typechain/hardhat": "^3.0.0", "@uniswap/sdk-core": "^3.0.1",