-
Notifications
You must be signed in to change notification settings - Fork 1
/
BasePortfolio.sol
130 lines (110 loc) · 5.29 KB
/
BasePortfolio.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {Upgradeable} from "./access/Upgradeable.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IProtocolConfig} from "./interfaces/IProtocolConfig.sol";
import {IBasePortfolio} from "./interfaces/IBasePortfolio.sol";
import {IERC20WithDecimals} from "./interfaces/IERC20WithDecimals.sol";
import {ITransferStrategy} from "./interfaces/ITransferStrategy.sol";
abstract contract BasePortfolio is IBasePortfolio, ERC20Upgradeable, Upgradeable {
using SafeERC20 for IERC20;
event Deposited(uint256 shares, uint256 amount, address indexed sender);
event Withdrawn(uint256 shares, uint256 amount, address indexed sender);
event TransferStrategyChanged(address indexed oldStrategy, address indexed newStrategy);
event FeePaid(address indexed sender, address indexed recipient, uint256 amount);
bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
bytes32 public constant DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE");
bytes32 public constant WITHDRAW_ROLE = keccak256("WITHDRAW_ROLE");
uint256 public constant BASIS_PRECISION = 10000;
uint256 public endDate;
IERC20 public underlyingToken;
uint8 public underlyingTokenDecimals;
address public transferStrategy;
IProtocolConfig public protocolConfig;
uint256 public managerFee;
uint256 public virtualTokenBalance;
function __BasePortfolio_init(
IProtocolConfig _protocolConfig,
uint256 _duration,
IERC20 _underlyingToken,
address _manager,
uint256 _managerFee
) internal initializer {
require(_duration > 0, "BasePortfolio: Cannot have zero duration");
__Upgradeable_init(_protocolConfig.protocolAddress(), _protocolConfig.pauserAddress());
_grantRole(MANAGER_ROLE, _manager);
_setRoleAdmin(DEPOSIT_ROLE, MANAGER_ROLE);
_setRoleAdmin(WITHDRAW_ROLE, MANAGER_ROLE);
protocolConfig = _protocolConfig;
endDate = block.timestamp + _duration;
underlyingToken = _underlyingToken;
underlyingTokenDecimals = IERC20WithDecimals(address(_underlyingToken)).decimals();
if (_managerFee > 0) {
managerFee = _managerFee;
}
}
function setTransferStrategy(address _transferStrategy) public onlyRole(MANAGER_ROLE) {
_setTransferStrategy(_transferStrategy);
}
/* @notice This contract is upgradeable and interacts with settable deposit strategies,
* that may change over the contract's lifespan. As a safety measure, we recommend approving
* this contract with the desired deposit amount instead of performing infinite allowance.
*/
function deposit(uint256 amount, address sender) public virtual onlyRole(DEPOSIT_ROLE) whenNotPaused {
uint256 sharesToMint = calculateSharesToMint(amount);
_mint(sender, sharesToMint);
virtualTokenBalance += amount;
underlyingToken.safeTransferFrom(sender, address(this), amount);
emit Deposited(sharesToMint, amount, sender);
}
function withdraw(uint256 shares, address sender) public virtual onlyRole(WITHDRAW_ROLE) whenNotPaused {
uint256 amountToWithdraw = calculateAmountToWithdraw(shares);
require(amountToWithdraw <= virtualTokenBalance, "BasePortfolio: Amount exceeds pool balance");
virtualTokenBalance -= amountToWithdraw;
_burn(sender, shares);
underlyingToken.safeTransfer(sender, amountToWithdraw);
emit Withdrawn(shares, amountToWithdraw, sender);
}
function calculateSharesToMint(uint256 depositedAmount) public view virtual returns (uint256) {
uint256 _totalSupply = totalSupply();
if (_totalSupply == 0) {
return (depositedAmount * 10**decimals()) / (10**underlyingTokenDecimals);
} else {
return (depositedAmount * _totalSupply) / value();
}
}
function calculateAmountToWithdraw(uint256 shares) public view virtual returns (uint256) {
return (shares * value()) / totalSupply();
}
function value() public view virtual returns (uint256) {
return virtualTokenBalance;
}
function _setTransferStrategy(address _transferStrategy) internal {
address oldTransferStrategy = transferStrategy;
require(_transferStrategy != oldTransferStrategy, "BasePortfolio: New transfer strategy needs to be different");
transferStrategy = _transferStrategy;
emit TransferStrategyChanged(oldTransferStrategy, _transferStrategy);
}
function _transfer(
address sender,
address recipient,
uint256 amount
) internal override whenNotPaused {
if (transferStrategy != address(0)) {
require(
ITransferStrategy(transferStrategy).canTransfer(sender, recipient, amount),
"BasePortfolio: This transfer not permitted"
);
}
super._transfer(sender, recipient, amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal override whenNotPaused {
super._approve(owner, spender, amount);
}
}