From 8ccf9d249ec20aec8ebc2b7944c0aca408aa1c59 Mon Sep 17 00:00:00 2001 From: Shriya Tyagi Date: Tue, 30 Jan 2024 17:13:17 +0530 Subject: [PATCH] feat: Set up Basic-D on Polygon --- .../connectors/basic-ERC4626/events.sol | 42 +++ .../connectors/basic-ERC4626/interface.sol | 233 ++++++++++++++++ .../polygon/connectors/basic-ERC4626/main.sol | 255 ++++++++++++++++++ 3 files changed, 530 insertions(+) create mode 100644 contracts/polygon/connectors/basic-ERC4626/events.sol create mode 100644 contracts/polygon/connectors/basic-ERC4626/interface.sol create mode 100644 contracts/polygon/connectors/basic-ERC4626/main.sol diff --git a/contracts/polygon/connectors/basic-ERC4626/events.sol b/contracts/polygon/connectors/basic-ERC4626/events.sol new file mode 100644 index 00000000..e99b458d --- /dev/null +++ b/contracts/polygon/connectors/basic-ERC4626/events.sol @@ -0,0 +1,42 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +contract Events { + event LogDeposit( + address indexed token, + uint256 underlyingAmt, + uint256 minSharesPerToken, + uint256 sharesReceieved, + uint256 getId, + uint256 setId + ); + + event LogMint( + address indexed token, + uint256 shareAmt, + uint256 maxTokenPerShares, + uint256 tokensDeducted, + uint256 getId, + uint256 setId + ); + + event LogWithdraw( + address indexed token, + uint256 underlyingAmt, + uint256 maxSharesPerToken, + uint256 sharedBurned, + address indexed to, + uint256 getId, + uint256 setId + ); + + event LogRedeem( + address indexed token, + uint256 shareAmt, + uint256 minTokenPerShares, + uint256 underlyingAmtReceieved, + address to, + uint256 getId, + uint256 setId + ); +} \ No newline at end of file diff --git a/contracts/polygon/connectors/basic-ERC4626/interface.sol b/contracts/polygon/connectors/basic-ERC4626/interface.sol new file mode 100644 index 00000000..9d5fd33a --- /dev/null +++ b/contracts/polygon/connectors/basic-ERC4626/interface.sol @@ -0,0 +1,233 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC4626.sol) + +pragma solidity ^0.7.0; + +import "@openzeppelin/contracts/token/ERC20/IERC20.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 { + 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 + ); + + function decimals() external view returns (uint8); + + /** + * @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/polygon/connectors/basic-ERC4626/main.sol b/contracts/polygon/connectors/basic-ERC4626/main.sol new file mode 100644 index 00000000..2d286dd2 --- /dev/null +++ b/contracts/polygon/connectors/basic-ERC4626/main.sol @@ -0,0 +1,255 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; + +/** + * @title Basic D. + * @dev Deposit, Mint, Withdraw, & Redeem from ERC4626 DSA. + */ + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC4626 } from "./interface.sol"; +import { TokenInterface } from "../../common/interfaces.sol"; +import { DSMath } from "../../common/math.sol"; +import { Basic } from "../../common/basic.sol"; +import { Events } from "./events.sol"; + +abstract contract BasicConnector is Events, DSMath, Basic { + /** + * @dev Deposit underlying asset to ERC4626 Vault. + * @notice Mints vault shares by depositing exactly amount of underlying assets + * @param vaultToken ERC4626 Token address. + * @param underlyingAmt The amount of the underlying asset to deposit. (For max: `uint256(-1)`) + * @param minSharesPerToken The min share rate of deposit. Should always be in 18 decimals. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens deposited. + */ + function deposit( + address vaultToken, + uint256 underlyingAmt, + uint256 minSharesPerToken, + uint256 getId, + uint256 setId + ) public returns (string memory _eventName, bytes memory _eventParam) { + uint256 _underlyingAmt = getUint(getId, underlyingAmt); + + IERC4626 vaultTokenContract = IERC4626(vaultToken); + TokenInterface _underlyingTokenContract = TokenInterface( + vaultTokenContract.asset() + ); + + _underlyingAmt = _underlyingAmt == uint256(-1) + ? _underlyingTokenContract.balanceOf(address(this)) + : _underlyingAmt; + + // Returns final amount in token decimals. + uint256 _minShares = wmul(minSharesPerToken, _underlyingAmt); + + // Initial share balance + uint256 _initialVaultBal = vaultTokenContract.balanceOf(address(this)); + + approve(_underlyingTokenContract, vaultToken, _underlyingAmt); + + // Deposit tokens for shares + vaultTokenContract.deposit(_underlyingAmt, address(this)); + + uint256 _sharesReceieved = sub( + vaultTokenContract.balanceOf(address(this)), + _initialVaultBal + ); + + require(_minShares <= _sharesReceieved, "Less shares received"); + + setUint(setId, _sharesReceieved); + + _eventName = "LogDeposit(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + vaultToken, + _underlyingAmt, + minSharesPerToken, + _sharesReceieved, + getId, + setId + ); + } + + /** + * @dev Mint underlying asset to ERC4626 Vault. + * @notice Mints vault shares by minting exactly amount of underlying assets + * @param vaultToken ERC4626 Token address. + * @param shareAmt The amount of the share to mint. (For max: `uint256(-1)`) + * @param maxTokenPerShares The max underyling token rate of mint. Always in 18 decimals. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens minted. + */ + function mint( + address vaultToken, + uint256 shareAmt, + uint256 maxTokenPerShares, + uint256 getId, + uint256 setId + ) public returns (string memory _eventName, bytes memory _eventParam) { + uint256 _shareAmt = getUint(getId, shareAmt); + + IERC4626 vaultTokenContract = IERC4626(vaultToken); + TokenInterface underlyingTokenContract = TokenInterface( + vaultTokenContract.asset() + ); + + _shareAmt = _shareAmt == uint256(-1) + ? vaultTokenContract.balanceOf(address(this)) + : _shareAmt; + + // Returns final amount in token decimals. + uint256 _maxTokens = wmul(maxTokenPerShares, _shareAmt); + + uint256 _underlyingTokenAmount = vaultTokenContract.previewMint( + _shareAmt + ); + + uint256 _initalUnderlyingBal = underlyingTokenContract.balanceOf( + address(this) + ); + + approve(underlyingTokenContract, vaultToken, _underlyingTokenAmount); + + // Mint shares for tokens + vaultTokenContract.mint(_shareAmt, address(this)); + + uint256 _tokensDeducted = sub( + _initalUnderlyingBal, + underlyingTokenContract.balanceOf(address(this)) + ); + + require(_maxTokens >= _tokensDeducted, "maxTokenPerShares-exceeds"); + + setUint(setId, _shareAmt); + + _eventName = "LogMint(address,uint256,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode( + vaultToken, + _shareAmt, + maxTokenPerShares, + _tokensDeducted, + getId, + setId + ); + } + + /** + * @dev Withdraw underlying asset from ERC4626 Vault. + * @notice Withdraw vault shares with exactly amount of underlying assets + * @param vaultToken ERC4626 Token address. + * @param underlyingAmt The amount of the token to withdraw. (For max: `uint256(-1)`) + * @param maxSharesPerToken The max share rate of withdrawn amount. Always send in 18 decimals. + * @param to The address of receiver. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens withdrawn. + */ + function withdraw( + address vaultToken, + uint256 underlyingAmt, + uint256 maxSharesPerToken, + address payable to, + uint256 getId, + uint256 setId + ) public returns (string memory _eventName, bytes memory _eventParam) { + uint256 _underlyingAmt = getUint(getId, underlyingAmt); + + IERC4626 vaultTokenContract = IERC4626(vaultToken); + TokenInterface underlyingTokenContract = TokenInterface( + vaultTokenContract.asset() + ); + + _underlyingAmt = _underlyingAmt == uint256(-1) + ? underlyingTokenContract.balanceOf(address(this)) + : _underlyingAmt; + + // Returns final amount in token decimals. + uint256 _maxShares = wmul(maxSharesPerToken, _underlyingAmt); + + uint256 _initialVaultBal = vaultTokenContract.balanceOf(to); + + // Withdraw tokens for shares + vaultTokenContract.withdraw(_underlyingAmt, to, address(this)); + + uint256 _sharesBurned = sub(_initialVaultBal, vaultTokenContract.balanceOf(to)); + + require(_maxShares >= _sharesBurned, "maxShares-exceeds"); + + setUint(setId, _underlyingAmt); + + _eventName = "LogWithdraw(address,uint256,uint256,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + vaultToken, + _underlyingAmt, + maxSharesPerToken, + _sharesBurned, + to, + getId, + setId + ); + } + + /** + * @dev Redeem underlying asset from ERC4626 Vault. + * @notice Redeem vault shares with exactly amount of underlying assets + * @param vaultToken ERC4626 Token address. + * @param shareAmt The amount of the token to redeem. (For max: `uint256(-1)`) + * @param minTokenPerShares The min underlying token rate of withdraw. Always in 18 decimals. + * @param to The address of receiver. + * @param getId ID to retrieve amt. + * @param setId ID stores the amount of tokens redeem. + */ + + function redeem( + address vaultToken, + uint256 shareAmt, + uint256 minTokenPerShares, + address payable to, + uint256 getId, + uint256 setId + ) public returns (string memory _eventName, bytes memory _eventParam) { + uint256 _shareAmt = getUint(getId, shareAmt); + + IERC4626 vaultTokenContract = IERC4626(vaultToken); + TokenInterface underlyingTokenContract = TokenInterface( + vaultTokenContract.asset() + ); + + _shareAmt = _shareAmt == uint256(-1) + ? vaultTokenContract.balanceOf(address(this)) + : _shareAmt; + + // Returns final amount in token decimals. + uint256 _minUnderlyingAmt = wmul(minTokenPerShares, _shareAmt); + + uint256 _initalUnderlyingBal = underlyingTokenContract.balanceOf(to); + + // Redeem tokens for shares + vaultTokenContract.redeem(_shareAmt, to, address(this)); + + uint256 _underlyingAmtReceieved = sub( + underlyingTokenContract.balanceOf(to), + _initalUnderlyingBal + ); + + require(_minUnderlyingAmt <= _underlyingAmtReceieved, "_minUnderlyingAmt-exceeds"); + + setUint(setId, _shareAmt); + + _eventName = "LogRedeem(address,uint256,uint256,uint256,address,uint256,uint256)"; + _eventParam = abi.encode( + vaultToken, + _shareAmt, + minTokenPerShares, + _underlyingAmtReceieved, + to, + getId, + setId + ); + } +} + +contract ConnectV2BasicERC4626Polygon is BasicConnector { + string public constant name = "BASIC-ERC4626-v1.0"; +} \ No newline at end of file