From b3ccc78df97fe5ed2d51ddf856bca6bdcd3306a2 Mon Sep 17 00:00:00 2001 From: Dann Wee Date: Sat, 19 Oct 2024 17:30:04 +0800 Subject: [PATCH] feat: created erc4626 vault and updated exchange contract --- script/deploy/Exchange.s.sol | 5 +- src/Exchange.sol | 96 +++++++++++++++++++++++++++++++++++- src/Vault.sol | 16 +++--- 3 files changed, 107 insertions(+), 10 deletions(-) diff --git a/script/deploy/Exchange.s.sol b/script/deploy/Exchange.s.sol index 055dabe..6fdc745 100644 --- a/script/deploy/Exchange.s.sol +++ b/script/deploy/Exchange.s.sol @@ -8,10 +8,11 @@ contract DeployExchange is Script { function run() external { vm.startBroadcast(); - address registryAddress = 0x9d69b9b2907Bba74E65Aa6c87B2284fF0F0931e0; + address registryAddress = 0x2b4836d81370e37030727E4DCbd9cC5a772cf43A; address usdcAddress = 0x036CbD53842c5426634e7929541eC2318f3dCF7e; + address vaultAddress = 0xd580248163CDD5AE3225A700E9f4e7CD525b27b0; - Exchange exchange = new Exchange(registryAddress, usdcAddress); + Exchange exchange = new Exchange(registryAddress, usdcAddress, vaultAddress); console.log("Exchange deployed at:", address(exchange)); diff --git a/src/Exchange.sol b/src/Exchange.sol index 0e4eb3f..aea0b99 100644 --- a/src/Exchange.sol +++ b/src/Exchange.sol @@ -7,12 +7,19 @@ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {Registry} from "./Registry.sol"; +import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; +/** + * @title Cube Exchange + * @author Dann Wee + * @notice Cube Exchange is a contract that allows users to transfer USDC to merchants. + */ contract Exchange is ReentrancyGuard, Ownable { using SafeERC20 for IERC20; Registry public immutable registry; IERC20 public immutable usdcToken; + IERC4626 public immutable vault; uint256 public fee = 100; // 100 basis points fee (1%) address public feeCollector; @@ -21,15 +28,28 @@ contract Exchange is ReentrancyGuard, Ownable { event FeeUpdated(uint256 newFee); event FeeCollectorUpdated(address newFeeCollector); event FeesWithdrawn(address indexed to, uint256 amount); + event VaultDeposit(address indexed merchant, uint256 assets, uint256 shares); + event VaultWithdraw(address indexed merchant, uint256 assets, uint256 shares); - constructor(address _registryAddress, address _usdcAddress) Ownable(msg.sender) { + constructor(address _registryAddress, address _usdcAddress, address _vaultAddress) Ownable(msg.sender) { require(_registryAddress != address(0), "Invalid registry address"); require(_usdcAddress != address(0), "Invalid USDC address"); + require(_vaultAddress != address(0), "Invalid vault address"); registry = Registry(_registryAddress); usdcToken = IERC20(_usdcAddress); feeCollector = address(this); + vault = IERC4626(_vaultAddress); } + ///////////////////////// + /////// FUNCTIONS /////// + ///////////////////////// + + /** + * @notice Transfer USDC to merchant. + * @param _uen Merchant's UEN. + * @param _amount Amount of USDC to transfer to merchant. + */ function transferToMerchant(string memory _uen, uint256 _amount) external nonReentrant { require(_amount > 0, "Amount must be greater than zero"); @@ -47,18 +67,92 @@ contract Exchange is ReentrancyGuard, Ownable { emit Transfer(msg.sender, merchantWalletAddress, merchantAmount, _uen); } + /** + * @notice Transfer USDC to vault. + * @param _uen Merchant's UEN. + * @param _amount Amount of USDC to transfer to vault. + */ + function transferToVault(string memory _uen, uint256 _amount) external nonReentrant { + require(_amount > 0, "Amount must be greater than zero"); + + address merchantWalletAddress = registry.getMerchantByUEN(_uen).wallet_address; + require(merchantWalletAddress != address(0), "Invalid merchant wallet address"); + + uint256 feeAmount = (_amount * fee) / 10000; + uint256 vaultAmount = _amount - feeAmount; + + // Transfer USDC from sender to this contract + usdcToken.safeTransferFrom(msg.sender, address(this), _amount); + + // Transfer fee to fee collector + if (feeAmount > 0) { + usdcToken.safeTransfer(feeCollector, feeAmount); + } + + // Approve vault to spend USDC + usdcToken.approve(address(vault), vaultAmount); + + // Deposit USDC into vault and mint shares to merchant + uint256 shares = vault.deposit(vaultAmount, merchantWalletAddress); + + emit Transfer(msg.sender, merchantWalletAddress, vaultAmount, _uen); + emit VaultDeposit(merchantWalletAddress, vaultAmount, shares); + } + + /** + * @notice Withdraw USDC from vault to merchant's wallet. + * @param _shares Amount of shares to withdraw. + */ + function withdrawToWallet(uint256 _shares) external nonReentrant { + address merchantWalletAddress = msg.sender; + + // Check if the caller has enough shares + uint256 userShares = vault.balanceOf(merchantWalletAddress); + require(userShares >= _shares, "Insufficient shares"); + + // Check if the vault has enough assets to cover the withdrawal + uint256 assets = vault.convertToAssets(_shares); + require(vault.totalAssets() >= assets, "Insufficient vault assets"); + + // Approve the vault to spend shares on behalf of the user + vault.approve(address(this), _shares); + + // Withdraw USDC from vault and burn shares + try vault.redeem(_shares, merchantWalletAddress, merchantWalletAddress) returns (uint256 redeemedAssets) { + require(redeemedAssets == assets, "Unexpected amount of assets redeemed"); + emit VaultWithdraw(merchantWalletAddress, redeemedAssets, _shares); + } catch Error(string memory reason) { + revert(string(abi.encodePacked("Vault redeem failed: ", reason))); + } catch (bytes memory) /* lowLevelData */ { + revert("Vault redeem failed with unknown error"); + } + } + + /** + * @notice Set the fee. + * @param _newFee New fee. + */ function setFee(uint256 _newFee) external onlyOwner { require(_newFee <= 1000, "Fee cannot exceed 10%"); fee = _newFee; emit FeeUpdated(_newFee); } + /** + * @notice Set the fee collector. + * @param _newFeeCollector New fee collector. + */ function setFeeCollector(address _newFeeCollector) external onlyOwner { require(_newFeeCollector != address(0), "Invalid fee collector address"); feeCollector = _newFeeCollector; emit FeeCollectorUpdated(_newFeeCollector); } + /** + * @notice Withdraw fees. + * @param _to Withdrawal address. + * @param _amount Amount of USDC to withdraw. + */ function withdrawFees(address _to, uint256 _amount) external onlyOwner { require(_to != address(0), "Invalid withdrawal address"); require(_amount > 0, "Withdrawal amount must be greater than zero"); diff --git a/src/Vault.sol b/src/Vault.sol index eec149d..289cde4 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: MIT -pragma solidity 0.8.21; + +pragma solidity ^0.8.21; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -23,7 +25,7 @@ contract Vault is IERC4626Vault, ERC4626Upgradeable, OwnableUpgradeable { /* ========== STATE VARIABLES ========== */ address public usdc; - address public aEthUSDC; + address public aBaseUsdc; address public aaveV3Pool; /* ========== CONSTRUCTOR ========== */ @@ -37,16 +39,16 @@ contract Vault is IERC4626Vault, ERC4626Upgradeable, OwnableUpgradeable { * @dev Initialize the ERC4626AaveV3UsdcVault. * @param _usdc USDC contract address. * @param _aaveV3Pool Aave V3 Pool contract address. - * @param _aEthUSDC Aave USDC address. + * @param _aBaseUsdc Aave USDC address. */ - function initialize(IERC20 _usdc, address _aaveV3Pool, address _aEthUSDC) external initializer { + function initialize(IERC20 _usdc, address _aaveV3Pool, address _aBaseUsdc) external initializer { __Ownable_init(_msgSender()); __ERC4626_init(_usdc); - __ERC20_init("Wrapped Aave USDC V3", "wAEthUSDC"); + __ERC20_init("Wrapped Aave USDC V3", "wABaseUSDC"); usdc = address(_usdc); aaveV3Pool = _aaveV3Pool; - aEthUSDC = _aEthUSDC; + aBaseUsdc = _aBaseUsdc; } /* ========== VIEWS ========== */ @@ -55,7 +57,7 @@ contract Vault is IERC4626Vault, ERC4626Upgradeable, OwnableUpgradeable { * @dev See {IERC4626-totalAssets}. */ function totalAssets() public view override returns (uint256) { - return IERC20(aEthUSDC).balanceOf(address(this)); + return IERC20(aBaseUsdc).balanceOf(address(this)); } /* ========== MUTATIVE FUNCTIONS ========== */