diff --git a/src/workshop_2/WorkshopVault.sol b/src/workshop_2/WorkshopVault.sol index d0a3f4e..dd7cda7 100644 --- a/src/workshop_2/WorkshopVault.sol +++ b/src/workshop_2/WorkshopVault.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0-or-later +//SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.19; @@ -9,6 +9,20 @@ import "./IWorkshopVault.sol"; contract WorkshopVault is ERC4626, IVault, IWorkshopVault { IEVC internal immutable evc; + uint internal totalBorrowedasset; + IERC20 assetss; + + //assumption + uint internal borrowCap = type(uint104).max; + + error NO_asset(); + + mapping(address => uint) internal balanceOfs; + mapping(address => uint) internal debtor; + + event Borrow(address operator, address onBehalfOf, uint amount); + event Repay(address operator, address onBehalfOf, uint amount); + event deposits(uint amount, address from, address onBehalfOf); constructor( IEVC _evc, @@ -17,14 +31,19 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { string memory _symbol ) ERC4626(_asset) ERC20(_name, _symbol) { evc = _evc; + assetss = _asset; } - // [ASSIGNMENT]: what is the purpose of this modifier? modifier callThroughEVC() { if (msg.sender == address(evc)) { _; } else { - bytes memory result = evc.call(address(this), msg.sender, 0, msg.data); + bytes memory result = evc.call( + address(this), + msg.sender, + 0, + msg.data + ); assembly { return(add(32, result), mload(result)) @@ -32,9 +51,10 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { } } - // [ASSIGNMENT]: why the account status check might not be necessary in certain situations? - // [ASSIGNMENT]: is the vault status check always necessary? why? modifier withChecks(address account) { + //do take the snapshot of the vault and compare with the borrow cap + //totalBorrowedasset > borrowCap ? revert + _; if (account == address(0)) { @@ -44,43 +64,45 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { } } - // [ASSIGNMENT]: can this function be used to authenticate the account for the sake of the borrow-related - // operations? why? - // [ASSIGNMENT]: if the answer to the above is "no", how this function could be modified to allow safe borrowing? function _msgSender() internal view virtual override returns (address) { if (msg.sender == address(evc)) { - (address onBehalfOfAccount,) = evc.getCurrentOnBehalfOfAccount(address(0)); + (address onBehalfOfAccount, ) = evc.getCurrentOnBehalfOfAccount( + address(0) + ); return onBehalfOfAccount; } else { return msg.sender; } } - // IVault - // [ASSIGNMENT]: why this function is necessary? is it safe to unconditionally disable the controller? - function disableController() external { - evc.disableController(_msgSender()); - } - - // [ASSIGNMENT]: provide a couple use cases for this function - function checkAccountStatus( - address account, - address[] calldata collaterals - ) public virtual returns (bytes4 magicValue) { + function checkAccountStatus(address account, address[] calldata collaterals) + public + virtual + returns (bytes4 magicValue) + { require(msg.sender == address(evc), "only evc can call this"); - require(evc.areChecksInProgress(), "can only be called when checks in progress"); + require( + evc.areChecksInProgress(), + "can only be called when checks in progress" + ); // some custom logic evaluating the account health return IVault.checkAccountStatus.selector; } - // [ASSIGNMENT]: provide a couple use cases for this function function checkVaultStatus() public virtual returns (bytes4 magicValue) { require(msg.sender == address(evc), "only evc can call this"); - require(evc.areChecksInProgress(), "can only be called when checks in progress"); + require( + evc.areChecksInProgress(), + "can only be called when checks in progress" + ); // some custom logic evaluating the vault health + if (totalBorrowedasset > borrowCap) { + revert("borrow cap exceeded"); + } + //EIP-7265 should implemented here to control large outflow of funds // [ASSIGNMENT]: what can be done if the vault status check needs access to the initial state of the vault in // order to evaluate the vault health? @@ -88,17 +110,51 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { return IVault.checkVaultStatus.selector; } - function deposit( - uint256 assets, - address receiver - ) public virtual override callThroughEVC withChecks(address(0)) returns (uint256 shares) { - return super.deposit(assets, receiver); + function _convertToShares(uint256 assets, Math.Rounding rounding) + internal + view + virtual + override + returns (uint256) + { + return assets; } - function mint( - uint256 shares, - address receiver - ) public virtual override callThroughEVC withChecks(address(0)) returns (uint256 assets) { + function deposit(uint256 assets, address receiver) + public + virtual + override + callThroughEVC + withChecks(address(0)) + returns (uint256 shares) + { + require(assets > 0, "ZERO ASSET"); + assetss.transferFrom(_msgSender(), address(this), assets); + + totalBorrowedasset += assets; + shares = _convertToShares(assets, Math.Rounding.Floor); + balanceOfs[receiver] += shares; + _mint(receiver, shares); + } + + function convertToAssets(uint256 shares) + public + view + virtual + override + returns (uint256) + { + return shares; + } + + function mint(uint256 shares, address receiver) + public + virtual + override + callThroughEVC + withChecks(address(0)) + returns (uint256 assets) + { return super.mint(shares, receiver); } @@ -106,7 +162,14 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { uint256 assets, address receiver, address owner - ) public virtual override callThroughEVC withChecks(owner) returns (uint256 shares) { + ) + public + virtual + override + callThroughEVC + withChecks(owner) + returns (uint256 shares) + { return super.withdraw(assets, receiver, owner); } @@ -114,14 +177,25 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { uint256 shares, address receiver, address owner - ) public virtual override callThroughEVC withChecks(owner) returns (uint256 assets) { + ) + public + virtual + override + callThroughEVC + withChecks(owner) + returns (uint256 assets) + { return super.redeem(shares, receiver, owner); } - function transfer( - address to, - uint256 value - ) public virtual override (ERC20, IERC20) callThroughEVC withChecks(_msgSender()) returns (bool) { + function transfer(address to, uint256 value) + public + virtual + override(ERC20, IERC20) + callThroughEVC + withChecks(_msgSender()) + returns (bool) + { return super.transfer(to, value); } @@ -129,13 +203,136 @@ contract WorkshopVault is ERC4626, IVault, IWorkshopVault { address from, address to, uint256 value - ) public virtual override (ERC20, IERC20) callThroughEVC withChecks(from) returns (bool) { + ) + public + virtual + override(ERC20, IERC20) + callThroughEVC + withChecks(from) + returns (bool) + { return super.transferFrom(from, to, value); } - // IWorkshopVault - function borrow(uint256 assets, address receiver) external {} - function repay(uint256 assets, address receiver) external {} - function pullDebt(address from, uint256 assets) external returns (bool) {} - function liquidate(address violator, address collateral) external {} + function _msgSenderBorrower() internal view virtual returns (address) { + bool enabled; + if (msg.sender == address(evc)) { + (address onBehalfOfAccount, ) = evc.getCurrentOnBehalfOfAccount( + address(0) + ); + return onBehalfOfAccount; + } else { + enabled = evc.isControllerEnabled(msg.sender, address(this)); + require(enabled, "controller disabled"); + } + return msg.sender; + } + + function disableController() external { + if (debtor[_msgSender()] > 0) { + revert(); + } else { + evc.disableController(_msgSender()); + } + } + + function borrow(uint256 assets, address receiver) + external + callThroughEVC + withChecks(_msgSenderBorrower()) + { + require( + evc.isControllerEnabled(_msgSender(), address(this)), + "oops you cannot borrow" + ); + + if (assets <= 0) { + revert NO_asset(); + } + + debtor[_msgSenderBorrower()] += assets; + totalBorrowedasset += assets; + + assetss.transfer(receiver, assets); + } + + function repay(uint256 assets, address receiver) + external + callThroughEVC + withChecks(address(0)) + { + require(assets != 0, "ZERO_assetS"); + require( + evc.isControllerEnabled(receiver, address(this)), + "controller not enabled for this operator" + ); + debtor[receiver] -= assets; + totalBorrowedasset -= assets; + emit Repay(_msgSender(), receiver, assets); + assetss.transferFrom(_msgSender(), address(this), assets); + } + + function pullDebt(address from, uint256 assets) + external + callThroughEVC + withChecks(_msgSender()) + returns (bool) + { + require( + evc.isControllerEnabled(_msgSender(), address(this)), + "controller not enabled for this operator" + ); + + require(assets != 0, "no amount"); + + debtor[_msgSender()] += assets; + debtor[from] -= assets; + + return true; + } + + function liquidate(address violator, address collateral) + external + callThroughEVC + withChecks(_msgSender()) + { + uint debt = debtor[violator]; + //if debt value > collateral value + //UNDER_COLLATERIZED LOAN? + } + + function maxWithdraw(address owner) + public + view + virtual + override + returns (uint256) + { + uint256 borrowedassets = totalAssets(); + uint256 ownerassets = convertToAssets( + balanceOfs[owner] - debtor[owner] + ); + + return ownerassets > borrowedassets ? borrowedassets : ownerassets; + } + + function totalAssets() public view virtual override returns (uint256) { + return totalBorrowedasset; + } + + function maxRedeem(address owner) + public + view + virtual + override + returns (uint256) + { + uint256 borrowedassets = totalAssets(); + uint256 ownerShares = balanceOfs[owner] - debtor[owner]; + + return + _convertToAssets(ownerShares, Math.Rounding.Floor) > borrowedassets + ? _convertToShares(borrowedassets, Math.Rounding.Floor) + : ownerShares; + } } diff --git a/src/workshop_3/PositionManager.sol b/src/workshop_3/PositionManager.sol index 2836d51..51eda17 100644 --- a/src/workshop_3/PositionManager.sol +++ b/src/workshop_3/PositionManager.sol @@ -3,5 +3,103 @@ pragma solidity ^0.8.19; import "evc-playground/vaults/VaultRegularBorrowable.sol"; +import "evc/interfaces/IEthereumVaultConnector.sol"; + +contract PositionManager { + //a simple position manager that allows keepers to rebalance + //assets between multiple vaults of user's choice using EVC authentication mechanism + //and account owner's authorization + IEVC internal evc; + + uint internal rebalanceTimeStamp; + uint internal constant interval = 2 days; + + mapping(address => mapping(address => address[])) internal accountVaults; + + constructor() { + rebalanceTimeStamp = block.timestamp; + } + + //AN ACCOUNT OWNER MUST ADD AN OPERATOR OR OPERATORS TO OVERSEE ITS ASSETS ACCROSS VAULTS + + + ////////////////an external function, anybody can add but sanity check is required/////////////////// + + //@note for full implementation, an operator can manage multiple accounts + function mustAddOperator(address operator, address[] calldata vaults) + external + { + //@audit RE-ENTRANCY GUARD + //set account operator + require(operator != msg.sender, "you cannot set yourself as an operator"); + require(operator != address(0), "invalid operator"); + + //@note for simplicity, acceptable vaults must be contract instances of VaultRegularBorrowable + address[] memory vaultMemoryTransient = vaults; + for (uint i = 0; i < vaultMemoryTransient.length; i++) { + //sanity check + require( + VaultRegularBorrowable(vaultMemoryTransient[i]) + .getInterestRate() > 0, + "must be an instance of VaultRegular..." + ); + address vault = vaultMemoryTransient[i]; + accountVaults[operator][msg.sender].push(vault); + } + //the account owner sets an operator + evc.setAccountOperator(msg.sender, operator, true); + } + + ///REBALANCE CAN ONLY BE CALLED 2 DAYS AFTER THE LAST CALL by the operator of the account + + function rebalanceOnMyWatch(address owner) external { + //SANITY CHECK + //Check if the operator is in charge of this account + //Check if the operator is registered in this PositionManager contract + address[] memory operatorExist = accountVaults[msg.sender][owner]; + //if empty, then operator does not exist + require(operatorExist.length != 0, "No registered as operator"); + //Check the schedule call + require(block.timestamp - rebalanceTimeStamp > interval, "wait!"); + uint lastHighestRate = 0; + uint indexWithHighest; + + for (uint i = 0; i < operatorExist.length; i++) { + //get interest rate + + address vault = operatorExist[i]; + uint interestRate = uint(VaultRegularBorrowable(vault).getInterestRate()); + if (interestRate > lastHighestRate) { + lastHighestRate = interestRate; + indexWithHighest = i; + } + } + _rebalancingAction( + operatorExist, + owner, + operatorExist[indexWithHighest] + ); + + rebalanceTimeStamp = block.timestamp; + } + + function _rebalancingAction( + address[] memory Vault, + address owner, + address theChosenVault + ) internal { + for (uint i = 0; i < Vault.length; i++) { + if (Vault[i] != theChosenVault) { + address vault = Vault[i]; + uint maxWithdrawValue = VaultRegularBorrowable(vault) + .maxWithdraw(owner); + VaultRegularBorrowable(vault).withdraw( + maxWithdrawValue, + theChosenVault, + owner + ); + } + } + } +} -contract PositionManager {}