Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ test = 'tests'
script = 'scripts'
optimizer = true
optimizer_runs = 200
solc = '0.8.20'
solc = '0.8.27'
evm_version = 'shanghai'
bytecode_hash = 'none'
ignored_warnings_from = ["src/periphery/contracts/treasury/RevenueSplitter.sol"]
Expand Down
90 changes: 47 additions & 43 deletions src/contracts/treasury/Collector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.0;

import {ICollector} from './ICollector.sol';
import {IAccessControl} from '../dependencies/openzeppelin/contracts/IAccessControl.sol';
import {ReentrancyGuard} from '../dependencies/openzeppelin/ReentrancyGuard.sol';
import {VersionedInitializable} from '../misc/aave-upgradeability/VersionedInitializable.sol';
import {IERC20} from '../dependencies/openzeppelin/contracts/IERC20.sol';
Expand All @@ -28,14 +29,22 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
/*** Storage Properties ***/

/**
* @notice Address of the current funds admin.
* @notice Current revision of the contract.
*/
address internal _fundsAdmin;
uint256 public constant REVISION = 6;

/// @inheritdoc ICollector
address public constant ETH_MOCK_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/**
* @notice Current revision of the contract.
* @notice FUNDS_ADMIN role granted by ACL Manager
*/
uint256 public constant REVISION = 5;
bytes32 public constant FUNDS_ADMIN_ROLE = 'FUNDS_ADMIN';

/**
* @notice Address of the current ACL Manager.
*/
address public immutable ACL_MANAGER;

/**
* @notice Counter for new stream ids.
Expand All @@ -47,16 +56,18 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
*/
mapping(uint256 => Stream) private _streams;

/// @inheritdoc ICollector
address public constant ETH_MOCK_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/**
* @notice [DEPRECATED] Use `isFundsAdmin()` to check address.
*/
address internal _fundsAdmin_deprecated;

/*** Modifiers ***/

/**
* @dev Throws if the caller is not the funds admin.
* @dev Throws if the caller does not have the FUNDS_ADMIN role
*/
modifier onlyFundsAdmin() {
require(msg.sender == _fundsAdmin, 'ONLY_BY_FUNDS_ADMIN');
modifier onlyFundsAdmins() {
require(_onlyFundsAdmins(), OnlyFundsAdmin());
_;
}

Expand All @@ -66,8 +77,8 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
*/
modifier onlyAdminOrRecipient(uint256 streamId) {
require(
msg.sender == _fundsAdmin || msg.sender == _streams[streamId].recipient,
'caller is not the funds admin or the recipient of the stream'
_onlyFundsAdmins() || msg.sender == _streams[streamId].recipient,
OnlyFundsAdminOrRceipient()
);
_;
}
Expand All @@ -76,19 +87,22 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
* @dev Throws if the provided id does not point to a valid stream.
*/
modifier streamExists(uint256 streamId) {
require(_streams[streamId].isEntity, 'stream does not exist');
if (!_streams[streamId].isEntity) revert StreamDoesNotExist();
_;
}

constructor(address aclManager) {
if (aclManager == address(0)) revert InvalidZeroAddress();
ACL_MANAGER = aclManager;
}

/*** Contract Logic Starts Here */

/// @inheritdoc ICollector
function initialize(address fundsAdmin, uint256 nextStreamId) external initializer {
function initialize(uint256 nextStreamId) external virtual initializer {
if (nextStreamId != 0) {
_nextStreamId = nextStreamId;
}

_setFundsAdmin(fundsAdmin);
}

/*** View Functions ***/
Expand All @@ -99,8 +113,8 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
}

/// @inheritdoc ICollector
function getFundsAdmin() external view returns (address) {
return _fundsAdmin;
function isFundsAdmin(address admin) external view returns (bool) {
return IAccessControl(ACL_MANAGER).hasRole(FUNDS_ADMIN_ROLE, admin);
}

/// @inheritdoc ICollector
Expand Down Expand Up @@ -189,13 +203,13 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
/*** Public Effects & Interactions Functions ***/

/// @inheritdoc ICollector
function approve(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmin {
function approve(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmins {
token.safeApprove(recipient, amount);
}

/// @inheritdoc ICollector
function transfer(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmin {
require(recipient != address(0), 'INVALID_0X_RECIPIENT');
function transfer(IERC20 token, address recipient, uint256 amount) external onlyFundsAdmins {
if (recipient == address(0)) revert InvalidZeroAddress();

if (address(token) == ETH_MOCK_ADDRESS) {
payable(recipient).sendValue(amount);
Expand All @@ -204,18 +218,8 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
}
}

/// @inheritdoc ICollector
function setFundsAdmin(address admin) external onlyFundsAdmin {
_setFundsAdmin(admin);
}

/**
* @dev Transfer the ownership of the funds administrator role.
* @param admin The address of the new funds administrator
*/
function _setFundsAdmin(address admin) internal {
_fundsAdmin = admin;
emit NewFundsAdmin(admin);
function _onlyFundsAdmins() internal view returns (bool) {
return IAccessControl(ACL_MANAGER).hasRole(FUNDS_ADMIN_ROLE, msg.sender);
}

struct CreateStreamLocalVars {
Expand Down Expand Up @@ -243,22 +247,22 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
address tokenAddress,
uint256 startTime,
uint256 stopTime
) external onlyFundsAdmin returns (uint256) {
require(recipient != address(0), 'stream to the zero address');
require(recipient != address(this), 'stream to the contract itself');
require(recipient != msg.sender, 'stream to the caller');
require(deposit > 0, 'deposit is zero');
require(startTime >= block.timestamp, 'start time before block.timestamp');
require(stopTime > startTime, 'stop time before the start time');
) external onlyFundsAdmins returns (uint256) {
require(recipient != address(0), InvalidZeroAddress());
require(recipient != address(this), InvalidRecipient());
require(recipient != msg.sender, InvalidRecipient());
require(deposit > 0, InvalidZeroAmount());
require(startTime >= block.timestamp, InvalidStartTime());
require(stopTime > startTime, InvalidStopTime());

CreateStreamLocalVars memory vars;
vars.duration = stopTime - startTime;

/* Without this, the rate per second would be zero. */
require(deposit >= vars.duration, 'deposit smaller than time delta');
require(deposit >= vars.duration, DepositSmallerTimeDelta());

/* This condition avoids dealing with remainders */
require(deposit % vars.duration == 0, 'deposit not multiple of time delta');
require(deposit % vars.duration == 0, DepositNotMultipleTimeDelta());

vars.ratePerSecond = deposit / vars.duration;

Expand Down Expand Up @@ -302,11 +306,11 @@ contract Collector is VersionedInitializable, ICollector, ReentrancyGuard {
uint256 streamId,
uint256 amount
) external nonReentrant streamExists(streamId) onlyAdminOrRecipient(streamId) returns (bool) {
require(amount > 0, 'amount is zero');
require(amount > 0, InvalidZeroAmount());
Stream memory stream = _streams[streamId];

uint256 balance = balanceOf(streamId, stream.recipient);
require(balance >= amount, 'amount exceeds the available balance');
require(balance >= amount, BalanceExceeded());

_streams[streamId].remainingBalance = stream.remainingBalance - amount;

Expand Down
74 changes: 58 additions & 16 deletions src/contracts/treasury/ICollector.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,60 @@ interface ICollector {
bool isEntity;
}

/** @notice Emitted when the funds admin changes
* @param fundsAdmin The new funds admin.
**/
event NewFundsAdmin(address indexed fundsAdmin);
/**
* @dev Withdraw amount exceeds available balance
*/
error BalanceExceeded();

/**
* @dev Deposit smaller than time delta
*/
error DepositSmallerTimeDelta();

/**
* @dev Deposit not multiple of time delta
*/
error DepositNotMultipleTimeDelta();

/**
* @dev Recipient cannot be the contract itself or msg.sender
*/
error InvalidRecipient();

/**
* @dev Start time cannot be before block.timestamp
*/
error InvalidStartTime();

/**
* @dev Stop time must be greater than startTime
*/
error InvalidStopTime();

/**
* @dev Provided address cannot be the zero-address
*/
error InvalidZeroAddress();

/**
* @dev Amount cannot be zero
*/
error InvalidZeroAmount();

/**
* @dev Only caller with FUNDS_ADMIN role can call
*/
error OnlyFundsAdmin();

/**
* @dev Only caller with FUNDS_ADMIN role or stream recipient can call
*/
error OnlyFundsAdminOrRceipient();

/**
* @dev The provided ID does not belong to an existing stream
*/
error StreamDoesNotExist();

/** @notice Emitted when the new stream is created
* @param streamId The identifier of the stream.
Expand Down Expand Up @@ -70,16 +120,15 @@ interface ICollector {
function ETH_MOCK_ADDRESS() external pure returns (address);

/** @notice Initializes the contracts
* @param fundsAdmin Funds admin address
* @param nextStreamId StreamId to set, applied if greater than 0
**/
function initialize(address fundsAdmin, uint256 nextStreamId) external;
function initialize(uint256 nextStreamId) external;

/**
* @notice Return the funds admin, only entity to be able to interact with this contract (controller of reserve)
* @return address The address of the funds admin
* @notice Checks if address is funds admin
* @return bool If the address has the funds admin role
**/
function getFundsAdmin() external view returns (address);
function isFundsAdmin(address admin) external view returns (bool);

/**
* @notice Returns the available funds for the given stream id and address.
Expand All @@ -105,13 +154,6 @@ interface ICollector {
**/
function transfer(IERC20 token, address recipient, uint256 amount) external;

/**
* @dev Transfer the ownership of the funds administrator role.
This function should only be callable by the current funds administrator.
* @param admin The address of the new funds administrator
*/
function setFundsAdmin(address admin) external;

/**
* @notice Creates a new stream funded by this contracts itself and paid towards `recipient`.
* @param recipient The address towards which the money is streamed.
Expand Down
21 changes: 7 additions & 14 deletions src/deployments/contracts/procedures/AaveV3TreasuryProcedure.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,37 +20,30 @@ contract AaveV3TreasuryProcedure {
TreasuryReport memory treasuryReport;
bytes32 salt = collectorSalt;
address treasuryOwner = poolAdmin;
address aclManager;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems incomplete, you need to pass in the aclManager otherwise i guess deployment would revert.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Luigy-Lemon you can pass the aclManager from AaveV3PeripheryBatch contract as a function param to _deployAaveV3Treasury()


if (salt != '') {
Collector treasuryImplementation = new Collector{salt: salt}();
treasuryImplementation.initialize(address(0), 0);
Collector treasuryImplementation = new Collector{salt: salt}(aclManager);
treasuryImplementation.initialize(0);
treasuryReport.treasuryImplementation = address(treasuryImplementation);

treasuryReport.treasury = address(
new TransparentUpgradeableProxy{salt: salt}(
treasuryReport.treasuryImplementation,
ProxyAdmin(deployedProxyAdmin),
abi.encodeWithSelector(
treasuryImplementation.initialize.selector,
address(treasuryOwner),
0
)
abi.encodeWithSelector(treasuryImplementation.initialize.selector, 0)
)
);
} else {
Collector treasuryImplementation = new Collector();
treasuryImplementation.initialize(address(0), 0);
Collector treasuryImplementation = new Collector(aclManager);
treasuryImplementation.initialize(0);
treasuryReport.treasuryImplementation = address(treasuryImplementation);

treasuryReport.treasury = address(
new TransparentUpgradeableProxy(
treasuryReport.treasuryImplementation,
ProxyAdmin(deployedProxyAdmin),
abi.encodeWithSelector(
treasuryImplementation.initialize.selector,
address(treasuryOwner),
100_000
)
abi.encodeWithSelector(treasuryImplementation.initialize.selector, 100_000)
)
);
}
Expand Down
Loading