250 lines
8.1 KiB
Solidity
250 lines
8.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import {IERC20} from "@openzeppelin-contracts/token/ERC20/IERC20.sol";
|
|
import {SafeERC20} from "@openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
|
|
|
|
import {GhostAccessControlled} from "./types/GhostAccessControlled.sol";
|
|
|
|
import {ITreasury} from "./interfaces/ITreasury.sol";
|
|
import {IAllocator} from "./interfaces/IAllocator.sol";
|
|
import {ITreasuryExtender} from "./interfaces/ITreasuryExtender.sol";
|
|
import {IGhostAuthority} from "./interfaces/IGhostAuthority.sol";
|
|
|
|
contract TreasuryExtender is GhostAccessControlled, ITreasuryExtender {
|
|
using SafeERC20 for IERC20;
|
|
|
|
ITreasury public immutable treasury; // forge-lint: disable-line(screaming-snake-case-immutable)
|
|
IAllocator[] public allocators;
|
|
mapping(IAllocator => mapping(uint256 => AllocatorData)) public allocatorData;
|
|
|
|
constructor(address treasuryAddress, address authorityAddress)
|
|
GhostAccessControlled(IGhostAuthority(authorityAddress))
|
|
{
|
|
treasury = ITreasury(treasuryAddress);
|
|
allocators.push(IAllocator(address(0)));
|
|
}
|
|
|
|
function _allocatorActivated(IAllocator.AllocatorStatus status) internal pure {
|
|
if (IAllocator.AllocatorStatus.ACTIVATED != status) revert AllocatorNotActivated();
|
|
}
|
|
|
|
function _allocatorOffline(IAllocator.AllocatorStatus status) internal pure {
|
|
if (IAllocator.AllocatorStatus.OFFLINE != status) revert AllocatorNotOffline();
|
|
}
|
|
|
|
function _onlyAllocator(
|
|
IAllocator byStateId,
|
|
address sender,
|
|
uint256 id
|
|
) internal pure {
|
|
if (IAllocator(sender) != byStateId) revert OnlyAllocator(id, sender);
|
|
}
|
|
|
|
function registerDeposit(address newAllocator) external override onlyGuardian {
|
|
IAllocator allocator = IAllocator(newAllocator);
|
|
uint256 id = allocators.length;
|
|
allocators.push(allocator);
|
|
allocator.addId(id);
|
|
emit NewDepositRegistered(
|
|
newAllocator,
|
|
address(allocator.tokens()[allocator.tokenIds(id)]),
|
|
id
|
|
);
|
|
}
|
|
|
|
function setAllocatorLimits(
|
|
uint256 id,
|
|
AllocatorLimits calldata limits
|
|
) external override onlyGuardian {
|
|
IAllocator allocator = allocators[id];
|
|
_allocatorOffline(allocator.status());
|
|
allocatorData[allocator][id].limits = limits;
|
|
emit AllocatorLimitsChanged(id, limits.allocated, limits.loss);
|
|
}
|
|
|
|
function report(
|
|
uint256 id,
|
|
uint128 gain,
|
|
uint128 loss
|
|
) external override {
|
|
IAllocator allocator = allocators[id];
|
|
AllocatorData storage data = allocatorData[allocator][id];
|
|
AllocatorPerformance memory perf = data.performance;
|
|
IAllocator.AllocatorStatus status = allocator.status();
|
|
|
|
_onlyAllocator(allocator, msg.sender, id);
|
|
if (status == IAllocator.AllocatorStatus.OFFLINE) revert AllocatorOffline();
|
|
|
|
if (gain >= loss) {
|
|
if (loss == type(uint128).max) {
|
|
AllocatorData storage newAllocatorData =
|
|
allocatorData[allocators[allocators.length - 1]][id];
|
|
|
|
newAllocatorData.holdings.allocated = data.holdings.allocated;
|
|
newAllocatorData.performance.gain = data.performance.gain;
|
|
data.holdings.allocated = 0;
|
|
|
|
perf.gain = 0;
|
|
perf.loss = 0;
|
|
|
|
emit AllocatorReportedMigration(id);
|
|
} else {
|
|
perf.gain += gain;
|
|
emit AllocatorReportedGain(id, gain);
|
|
}
|
|
} else {
|
|
data.holdings.allocated -= loss;
|
|
perf.loss += loss;
|
|
emit AllocatorReportedLoss(id, loss);
|
|
}
|
|
data.performance = perf;
|
|
}
|
|
|
|
function requestFundsFromTreasury(
|
|
uint256 id,
|
|
uint256 amount
|
|
) external override onlyGuardian {
|
|
IAllocator allocator = allocators[id];
|
|
AllocatorData memory data = allocatorData[allocator][id];
|
|
address token = address(allocator.tokens()[allocator.tokenIds(id)]);
|
|
uint256 value = treasury.tokenValue(token, amount);
|
|
|
|
_allocatorActivated(allocator.status());
|
|
_allocatorBelowLimit(data, amount);
|
|
|
|
treasury.manage(token, amount);
|
|
allocatorData[allocator][id].holdings.allocated += amount;
|
|
|
|
IERC20(token).safeTransfer(address(allocator), amount);
|
|
emit AllocatorFunded(id, amount, value);
|
|
}
|
|
|
|
function returnFundsToTreasury(
|
|
uint256 id,
|
|
uint256 amount
|
|
) external override onlyGuardian {
|
|
IAllocator allocator = allocators[id];
|
|
uint256 allocated = allocatorData[allocator][id].holdings.allocated;
|
|
uint128 gain = allocatorData[allocator][id].performance.gain;
|
|
address token = address(allocator.tokens()[allocator.tokenIds(id)]);
|
|
|
|
if (amount > allocated) {
|
|
amount -= allocated;
|
|
if (amount > gain) {
|
|
amount = allocated + gain;
|
|
gain = 0;
|
|
} else {
|
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
gain -= uint128(amount);
|
|
amount += allocated;
|
|
}
|
|
allocated = 0;
|
|
} else {
|
|
allocated -= amount;
|
|
}
|
|
|
|
uint256 value = treasury.tokenValue(token, amount);
|
|
_allowTreasuryWithdrawal(IERC20(token));
|
|
IERC20(token).safeTransferFrom(address(allocator), address(this), amount);
|
|
|
|
allocatorData[allocator][id].holdings.allocated = allocated;
|
|
if (allocated == 0) allocatorData[allocator][id].performance.gain = gain;
|
|
|
|
assert(treasury.deposit(token, amount, value) == 0);
|
|
emit AllocatedWithdrawal(id, amount, value);
|
|
}
|
|
|
|
function returnRewardsToTreasury(
|
|
uint256 id,
|
|
address token,
|
|
uint256 amount
|
|
) external override {
|
|
_returnRewardsToTreasury(allocators[id], IERC20(token), amount);
|
|
}
|
|
|
|
function returnRewardsToTreasury(
|
|
address allocatorAddress,
|
|
address token,
|
|
uint256 amount
|
|
) external override {
|
|
_returnRewardsToTreasury(IAllocator(allocatorAddress), IERC20(token), amount);
|
|
}
|
|
|
|
function getAllocatorById(uint256 id)
|
|
external
|
|
view
|
|
override
|
|
returns (address)
|
|
{
|
|
return address(allocators[id]);
|
|
}
|
|
|
|
function getTotalAllocatorCount()
|
|
external
|
|
view
|
|
returns (uint256)
|
|
{
|
|
return allocators.length;
|
|
}
|
|
|
|
function getAllocatorLimits(uint256 id)
|
|
external
|
|
view
|
|
override
|
|
returns (AllocatorLimits memory)
|
|
{
|
|
return allocatorData[allocators[id]][id].limits;
|
|
}
|
|
|
|
function getAllocatorPerformance(uint256 id)
|
|
external
|
|
view
|
|
override
|
|
returns (AllocatorPerformance memory)
|
|
{
|
|
return allocatorData[allocators[id]][id].performance;
|
|
}
|
|
|
|
function getAllocatorAllocated(uint256 id)
|
|
external
|
|
view
|
|
override
|
|
returns (uint256)
|
|
{
|
|
return allocatorData[allocators[id]][id].holdings.allocated;
|
|
}
|
|
|
|
function _returnRewardsToTreasury(
|
|
IAllocator allocator,
|
|
IERC20 token,
|
|
uint256 amount
|
|
) internal onlyGuardian {
|
|
uint256 balance = token.balanceOf(address(allocator));
|
|
amount = balance < amount ? balance : amount;
|
|
uint256 value = treasury.tokenValue(address(token), amount);
|
|
|
|
_allowTreasuryWithdrawal(token);
|
|
|
|
token.safeTransferFrom(address(allocator), address(this), amount);
|
|
assert(treasury.deposit(address(token), amount, value) == 0);
|
|
emit AllocatorRewardsWithdrawal(address(allocator), amount, value);
|
|
}
|
|
|
|
function _allowTreasuryWithdrawal(IERC20 token) internal {
|
|
if (token.allowance(address(this), address(treasury)) == 0) {
|
|
token.approve(address(treasury), type(uint256).max);
|
|
}
|
|
}
|
|
|
|
function _allocatorBelowLimit(
|
|
AllocatorData memory data,
|
|
uint256 amount
|
|
) internal pure {
|
|
uint256 newAllocated = data.holdings.allocated + amount;
|
|
if (newAllocated > data.limits.allocated) {
|
|
revert AllocatorMaxAllocation(newAllocated, data.limits.allocated);
|
|
}
|
|
}
|
|
}
|