Skip to content

Commit 6477886

Browse files
authored
feat(PRT): Add Prt and PrtFeeSplitExtension (#176)
Add `Prt` and `PrtFeeSplitExtension`
1 parent 72243db commit 6477886

File tree

13 files changed

+1201
-2
lines changed

13 files changed

+1201
-2
lines changed

contracts/adapters/FeeSplitExtension.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ contract FeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
9191
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
9292
* sufficient for accounting for all collected fees.
9393
*/
94-
function accrueFeesAndDistribute() public {
94+
function accrueFeesAndDistribute() public virtual {
9595
// Emits a FeeActualized event
9696
streamingFeeModule.accrueFee(setToken);
9797

@@ -260,6 +260,7 @@ contract FeeSplitExtension is BaseExtension, TimeLockUpgrade, MutualUpgrade {
260260
*/
261261
function updateFeeSplit(uint256 _newFeeSplit)
262262
external
263+
virtual
263264
mutualUpgrade(manager.operator(), manager.methodologist())
264265
{
265266
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
Copyright 2024 Index Cooperative
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
pragma experimental ABIEncoderV2;
21+
22+
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
23+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
24+
25+
import { FeeSplitExtension } from "./FeeSplitExtension.sol";
26+
import { IBaseManager } from "../interfaces/IBaseManager.sol";
27+
import { IIssuanceModule } from "../interfaces/IIssuanceModule.sol";
28+
import { IPrt } from "../interfaces/IPrt.sol";
29+
import { IPrtStakingPool } from "../interfaces/IPrtStakingPool.sol";
30+
import { IStreamingFeeModule } from "../interfaces/IStreamingFeeModule.sol";
31+
import { PreciseUnitMath } from "../lib/PreciseUnitMath.sol";
32+
33+
/**
34+
* @title PrtFeeSplitExtension
35+
* @dev Extension that allows for splitting and setting streaming and mint/redeem fees with a
36+
* PRT Staking Pool. The operator can accrue fees from the streaming fee module and distribute
37+
* them to the operator and the PRT Staking Pool, snapshotting the PRT Staking Pool. The operator
38+
* can update the PRT staking pool address and the fee split between the operator and the
39+
* PRT staking pool. Includes an optional allow list and timelock on accrue function.
40+
*/
41+
contract PrtFeeSplitExtension is FeeSplitExtension {
42+
using Address for address;
43+
using PreciseUnitMath for uint256;
44+
using SafeMath for uint256;
45+
46+
/* ============ Events ============ */
47+
48+
event AnyoneAccrueUpdated(bool isAnyoneAllowedToAccrue);
49+
event AccruerStatusUpdated(address indexed accruer, bool isAccruerAllowed);
50+
event OperatorFeeSplitUpdated(uint256 newFeeSplit);
51+
event PrtFeesDistributed(
52+
address indexed operatorFeeRecipient,
53+
address indexed prtStakingPool,
54+
uint256 operatorTake,
55+
uint256 prtTake
56+
);
57+
event PrtStakingPoolUpdated(address newPrtStakingPool);
58+
59+
/* ============ Immutables ============ */
60+
61+
IPrt public immutable prt;
62+
63+
/* ============ State Variables ============ */
64+
65+
bool public isAnyoneAllowedToAccrue;
66+
address[] accrueAllowList;
67+
mapping(address => bool) public accrueAllowMap;
68+
IPrtStakingPool public prtStakingPool;
69+
70+
/* ============ Modifiers ============ */
71+
72+
modifier onlyAllowedAccruer() {
73+
require(_isAllowedAccruer(msg.sender), "Not allowed to accrue");
74+
_;
75+
}
76+
77+
/* ============ Constructor ============ */
78+
79+
constructor(
80+
IBaseManager _manager,
81+
IStreamingFeeModule _streamingFeeModule,
82+
IIssuanceModule _issuanceModule,
83+
uint256 _operatorFeeSplit,
84+
address _operatorFeeRecipient,
85+
IPrt _prt
86+
)
87+
public
88+
FeeSplitExtension(
89+
_manager,
90+
_streamingFeeModule,
91+
_issuanceModule,
92+
_operatorFeeSplit,
93+
_operatorFeeRecipient
94+
)
95+
{
96+
require(_prt.setToken() == address(manager.setToken()), "SetToken mismatch with Prt");
97+
prt = _prt;
98+
}
99+
100+
/* ============ External Functions ============ */
101+
102+
/**
103+
* @notice MUTUAL UPGRADE: Updates PRT staking pool. PRT staking pool must have this extension set as the feeSplitExtension.
104+
* @param _prtStakingPool Address of the new PRT staking pool
105+
*/
106+
function updatePrtStakingPool(IPrtStakingPool _prtStakingPool)
107+
external
108+
mutualUpgrade(manager.operator(), manager.methodologist())
109+
{
110+
require(address(_prtStakingPool) != address(0), "Zero address not valid");
111+
require(_prtStakingPool.distributor() == address(this), "PRT Staking Pool distributor must be this extension");
112+
require(_prtStakingPool.stakeToken() == address(prt), "PRT Staking Pool stake token must be PRT");
113+
require(_prtStakingPool.rewardToken() == address(manager.setToken()), "PRT Staking Pool reward token must be SetToken");
114+
prtStakingPool = _prtStakingPool;
115+
emit PrtStakingPoolUpdated(address(_prtStakingPool));
116+
}
117+
118+
/**
119+
* @notice ONLY ALLOWED ACCRUER: Accrues fees from streaming fee module. Gets resulting balance after fee accrual, calculates fees for
120+
* operator and PRT staking pool, and sends to operator fee recipient and PRT Staking Pool respectively. NOTE: mint/redeem fees
121+
* will automatically be sent to this address so reading the balance of the SetToken in the contract after accrual is
122+
* sufficient for accounting for all collected fees. If the PRT take is greater than 0, the PRT Staking Pool will accrue the fees
123+
* and update the snapshot.
124+
*/
125+
function accrueFeesAndDistribute() public override onlyAllowedAccruer {
126+
require(address(prtStakingPool) != address(0), "PRT Staking Pool not set");
127+
128+
// Emits a FeeActualized event
129+
streamingFeeModule.accrueFee(setToken);
130+
131+
uint256 totalFees = setToken.balanceOf(address(this));
132+
133+
uint256 operatorTake = totalFees.preciseMul(operatorFeeSplit);
134+
uint256 prtTake = totalFees.sub(operatorTake);
135+
136+
if (operatorTake > 0) {
137+
setToken.transfer(operatorFeeRecipient, operatorTake);
138+
}
139+
140+
// Accrue PRT Staking Pool rewards and update snapshot
141+
if (prtTake > 0) {
142+
setToken.approve(address(prtStakingPool), prtTake);
143+
prtStakingPool.accrue(prtTake);
144+
}
145+
146+
emit PrtFeesDistributed(operatorFeeRecipient, address(prtStakingPool), operatorTake, prtTake);
147+
}
148+
149+
/**
150+
* @notice MUTUAL UPGRADE: Updates fee split between operator and PRT Staking Pool. Split defined in precise units (1% = 10^16).
151+
* Does not accrue fees and snapshot PRT Staking Pool.
152+
* @param _newFeeSplit Percent of fees in precise units (10^16 = 1%) sent to operator, (rest go to the PRT Staking Pool).
153+
*/
154+
function updateFeeSplit(uint256 _newFeeSplit)
155+
external
156+
override
157+
mutualUpgrade(manager.operator(), manager.methodologist())
158+
{
159+
require(_newFeeSplit <= PreciseUnitMath.preciseUnit(), "Fee must be less than 100%");
160+
operatorFeeSplit = _newFeeSplit;
161+
emit OperatorFeeSplitUpdated(_newFeeSplit);
162+
}
163+
164+
/**
165+
* @notice ONLY OPERATOR: Toggles the permission status of specified addresses to call the `accrueFeesAndDistribute()` function.
166+
* @param _accruers An array of addresses whose accrue permission status is to be toggled.
167+
* @param _statuses An array of booleans indicating the new accrue permission status for each corresponding address in `_accruers`.
168+
*/
169+
function setAccruersStatus(
170+
address[] memory _accruers,
171+
bool[] memory _statuses
172+
)
173+
external
174+
onlyOperator
175+
{
176+
_accruers.validatePairsWithArray(_statuses);
177+
for (uint256 i = 0; i < _accruers.length; i++) {
178+
_updateAccrueAllowList(_accruers[i], _statuses[i]);
179+
accrueAllowMap[_accruers[i]] = _statuses[i];
180+
emit AccruerStatusUpdated(_accruers[i], _statuses[i]);
181+
}
182+
}
183+
184+
/**
185+
* @notice ONLY OPERATOR: Toggles whether or not anyone is allowed to call the `accrueFeesAndDistribute()` function.
186+
* If set to true, it bypasses the accrueAllowList, allowing any address to call the `accrueFeesAndDistribute()` function.
187+
* @param _status A boolean indicating if anyone can accrue.
188+
*/
189+
function updateAnyoneAccrue(bool _status)
190+
external
191+
onlyOperator
192+
{
193+
isAnyoneAllowedToAccrue = _status;
194+
emit AnyoneAccrueUpdated(_status);
195+
}
196+
197+
/**
198+
* @notice Determines whether the given address is permitted to `accrueFeesAndDistribute()`.
199+
* @param _accruer Address of the accruer.
200+
* @return bool True if the given `_accruer` is permitted to accrue, false otherwise.
201+
*/
202+
function isAllowedAccruer(address _accruer) external view returns (bool) {
203+
return _isAllowedAccruer(_accruer);
204+
}
205+
206+
/**
207+
* @dev Retrieves the list of addresses that are permitted to `accrueFeesAndDistribute()`.
208+
* @return address[] Array of addresses representing the allowed accruers.
209+
*/
210+
function getAllowedAccruers() external view returns (address[] memory) {
211+
return accrueAllowList;
212+
}
213+
214+
215+
/* ============ Internal Functions ============ */
216+
217+
function _isAllowedAccruer(address _accruer) internal view returns (bool) {
218+
return isAnyoneAllowedToAccrue || accrueAllowMap[_accruer];
219+
}
220+
221+
function _updateAccrueAllowList(address _accruer, bool _status) internal {
222+
if (_status && !accrueAllowList.contains(_accruer)) {
223+
accrueAllowList.push(_accruer);
224+
} else if(!_status && accrueAllowList.contains(_accruer)) {
225+
accrueAllowList.removeStorage(_accruer);
226+
}
227+
}
228+
}

contracts/interfaces/IPrt.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: Apache License, Version 2.0
2+
pragma solidity 0.6.10;
3+
4+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
interface IPrt is IERC20 {
7+
function setToken() external view returns (address);
8+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: Apache License, Version 2.0
2+
pragma solidity ^0.6.10;
3+
4+
interface IPrtStakingPool {
5+
function accrue(uint256 _amount) external;
6+
function distributor() external view returns (address);
7+
function stakeToken() external view returns (address);
8+
function rewardToken() external view returns (address);
9+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
Copyright 2024 Index Cooperative
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity ^0.6.10;
20+
pragma experimental ABIEncoderV2;
21+
22+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
23+
24+
contract PrtStakingPoolMock {
25+
IERC20 public immutable rewardToken;
26+
IERC20 public immutable stakeToken;
27+
address public distributor;
28+
29+
constructor(IERC20 _rewardToken, IERC20 _stakeToken, address _distributor) public {
30+
rewardToken = _rewardToken;
31+
stakeToken = _stakeToken;
32+
distributor = _distributor;
33+
}
34+
35+
function accrue(uint256 _amount) external {
36+
rewardToken.transferFrom(msg.sender, address(this), _amount);
37+
}
38+
}

contracts/token/Prt.sol

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
Copyright 2024 Index Cooperative
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
21+
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
22+
23+
/**
24+
* @title Prt
25+
* @author Index Cooperative
26+
* @notice Standard ERC20 token with a fixed supply allocated to a distributor. Associated with a SetToken.
27+
*/
28+
contract Prt is ERC20 {
29+
/// @notice Address of the SetToken associated with this Prt token.
30+
address public immutable setToken;
31+
32+
/**
33+
* @notice Constructor for the Prt token.
34+
* @dev Mints the total supply of tokens and assigns them to the distributor.
35+
* @param _name The name of the Prt token.
36+
* @param _symbol The symbol of the Prt token.
37+
* @param _setToken The address of the SetToken associated with this Prt token.
38+
* @param _distributor The address that will receive and distribute the total supply of Prt tokens.
39+
* @param _totalSupply The total supply of Prt tokens to be minted and distributed.
40+
*/
41+
constructor(
42+
string memory _name,
43+
string memory _symbol,
44+
address _setToken,
45+
address _distributor,
46+
uint256 _totalSupply
47+
) public
48+
ERC20(_name, _symbol)
49+
{
50+
setToken = _setToken;
51+
_mint(_distributor, _totalSupply);
52+
}
53+
}

0 commit comments

Comments
 (0)