Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

Added GasManager facet and fixed an issue with GasFwd #250

Merged
merged 8 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 7 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
Binary file removed contracts/core/accounts/AccountsDiamond.png
SovereignAndrey marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
113 changes: 113 additions & 0 deletions contracts/core/accounts/facets/AccountsGasManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import {LibAccounts} from "../lib/LibAccounts.sol";
import {AccountStorage} from "../storage.sol";
import {Validator} from "../../validator.sol";
import {ReentrancyGuardFacet} from "./ReentrancyGuardFacet.sol";
import {IAccountsGasManager} from "../interfaces/IAccountsGasManager.sol";
import {IGasFwd} from "../../gasFwd/IGasFwd.sol";
import {IVault} from "../../vault/interfaces/IVault.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract AccountsGasManager is ReentrancyGuardFacet, IAccountsGasManager {
using SafeERC20 for IERC20;

/// @notice Ensure the caller is this same contract
/// @dev Necessary for cross-facet calls that need access control
modifier onlyAccountsContract() {
if (msg.sender != address(this)) {
revert OnlyAccountsContract();
}
_;
}

/// @notice Ensure the caller is admin
modifier onlyAdmin() {
AccountStorage.State storage state = LibAccounts.diamondStorage();
if (msg.sender != state.config.owner) {
revert OnlyAdmin();
}
_;
}

/// @notice Wrapper method for sweeping funds from gasFwd contract on endow closure
/// @dev Only callable by Accounts Diamond during endow closure flow
/// The endowment balance is not updated because we dont know which account to assign it to
/// @return balance returns the balance of the gasFwd contract which was swept to accounts
function sweepForClosure(
uint32 id,
address token
) external onlyAccountsContract returns (uint256) {
AccountStorage.State storage state = LibAccounts.diamondStorage();
return IGasFwd(state.ENDOWMENTS[id].gasFwd).sweep(token);
}

/// @notice Wrapper method for sweeping funds from gasFwd contract upon request
/// @dev If too many funds are allocated to gasFwd, need special permission from AP team
/// to unlock these funds, sweep them and reallocate them to the specified account.
/// This permissioned step precludes gaming the gasFwd'er as a way to avoid locked account funds from
/// incurring early withdraw penalties (i.e. overpay for gas from locked -> refunded to gas fwd -> sweep to liquid)
/// @return balance returns the balance of the gasFwd contract which was swept to accounts
function sweepForEndowment(
uint32 id,
IVault.VaultType vault,
address token
) external onlyAdmin returns (uint256) {
AccountStorage.State storage state = LibAccounts.diamondStorage();
uint256 funds = IGasFwd(state.ENDOWMENTS[id].gasFwd).sweep(token);
if (vault == IVault.VaultType.LOCKED) {
state.STATES[id].balances.locked[token] += funds;
} else {
state.STATES[id].balances.liquid[token] += funds;
}
return funds;
}

/// @notice Take funds from locked or liquid account and transfer them to the gasFwd
/// @dev Only callable by the owner, liquidInvestmentManager, or lockedInvestmentManager
/// Sends the balance specified from the specified token and vault type to the endow's gasFwd contract
function addGas(uint32 id, IVault.VaultType vault, address token, uint256 amount) external {
if (!_validateCaller(id)) {
revert Unauthorized();
}

AccountStorage.State storage state = LibAccounts.diamondStorage();
uint256 balance;
if (vault == IVault.VaultType.LOCKED) {
balance = state.STATES[id].balances.locked[token];
if (balance < amount) {
revert InsufficientFunds();
}
state.STATES[id].balances.locked[token] -= amount;
} else {
balance = state.STATES[id].balances.liquid[token];
if (balance < amount) {
revert InsufficientFunds();
}
state.STATES[id].balances.liquid[token] -= amount;
}

IERC20(token).safeTransfer(state.ENDOWMENTS[id].gasFwd, amount);
}

function _validateCaller(uint32 id) internal view returns (bool) {
AccountStorage.State storage state = LibAccounts.diamondStorage();
if (
Validator.canCall(
state.ENDOWMENTS[id].settingsController.lockedInvestmentManagement,
msg.sender,
block.timestamp
) ||
Validator.canCall(
state.ENDOWMENTS[id].settingsController.liquidInvestmentManagement,
msg.sender,
block.timestamp
) ||
msg.sender == state.ENDOWMENTS[id].owner
) {
return true;
}
return false;
}
}
SovereignAndrey marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {IRegistrar} from "../../registrar/interfaces/IRegistrar.sol";
import {IIndexFund} from "../../index-fund/IIndexFund.sol";
import {ReentrancyGuardFacet} from "./ReentrancyGuardFacet.sol";
import {IAccountsEvents} from "../interfaces/IAccountsEvents.sol";
import {IAccountsGasManager} from "../interfaces/IAccountsGasManager.sol";
import {IAccountsUpdateStatusEndowments} from "../interfaces/IAccountsUpdateStatusEndowments.sol";

/**
Expand All @@ -32,9 +33,8 @@ contract AccountsUpdateStatusEndowments is
LibAccounts.Beneficiary memory beneficiary
) public nonReentrant {
AccountStorage.State storage state = LibAccounts.diamondStorage();
AccountStorage.Endowment storage tempEndowment = state.ENDOWMENTS[id];

require(msg.sender == tempEndowment.owner, "Unauthorized");
require(msg.sender == state.ENDOWMENTS[id].owner, "Unauthorized");
require(!state.STATES[id].closingEndowment, "Endowment is closed");
require(checkFullyExited(id), "Not fully exited");

Expand Down
21 changes: 21 additions & 0 deletions contracts/core/accounts/interfaces/IAccountsGasManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;
import {IVault} from "../../vault/interfaces/IVault.sol";

interface IAccountsGasManager {
error OnlyAccountsContract();
error OnlyAdmin();
error Unauthorized();
error InsufficientFunds();
error TransferFailed();

function sweepForClosure(uint32 id, address token) external returns (uint256);

function sweepForEndowment(
uint32 id,
IVault.VaultType vault,
address token
) external returns (uint256);

function addGas(uint32 id, IVault.VaultType vault, address token, uint256 amount) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
AccountsUpdateStatusEndowments__factory,
AccountsUpdate__factory,
AccountsStrategy__factory,
AccountsGasManager__factory,
DiamondLoupeFacet__factory,
OwnershipFacet__factory,
} from "typechain-types";
Expand Down Expand Up @@ -87,5 +88,9 @@ export default async function getFacetFactoryEntries(diamondOwner: SignerWithAdd
addressField: "accountsStrategy",
factory: new AccountsStrategy__factory(diamondOwner),
},
{
addressField: "accountsGasManager",
factory: new AccountsGasManager__factory(diamondOwner),
},
];
}
10 changes: 8 additions & 2 deletions contracts/core/gasFwd/GasFwd.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini

contract GasFwd is IGasFwd, Initializable {
error OnlyAccounts();
event GasPay(address token, uint256 amount);
event Sweep(address token, uint256 amount);

using SafeERC20 for IERC20;

Expand All @@ -26,9 +28,13 @@ contract GasFwd is IGasFwd, Initializable {

function payForGas(address token, uint256 amount) external onlyAccounts {
IERC20(token).safeTransfer(msg.sender, amount);
emit GasPay(token, amount);
}

function sweep(address token) external onlyAccounts {
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
function sweep(address token) external onlyAccounts returns (uint256) {
uint256 balance = IERC20(token).balanceOf(address(this));
IERC20(token).safeTransfer(msg.sender, balance);
emit Sweep(token, balance);
return balance;
}
}
2 changes: 1 addition & 1 deletion contracts/core/gasFwd/IGasFwd.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ pragma solidity ^0.8.16;
interface IGasFwd {
function payForGas(address token, uint256 amount) external;

function sweep(address token) external;
function sweep(address token) external returns (uint256);
}
21 changes: 21 additions & 0 deletions contracts/core/validator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,27 @@ library Validator {
(delegateIsValid(permissions.delegate, sender, envTime) || sender == owner));
}

function canCall(
LibAccounts.SettingsPermission memory permissions,
address sender,
uint256 envTime
) internal pure returns (bool) {
// return true if:
// Caller is the specified delegate address AND
// the delegate hasn't expired OR doesn't expire
bool approved;
if (sender == permissions.delegate.addr) {
if (permissions.delegate.expires > 0) {
if (permissions.delegate.expires > envTime) {
approved = true;
}
} else {
approved = true;
}
}
return approved;
}

function validateFee(LibAccounts.FeeSetting memory fee) internal pure {
if (fee.bps > 0 && fee.payoutAddress == address(0)) {
revert("Invalid fee payout zero address given");
Expand Down
5 changes: 5 additions & 0 deletions contracts/test/accounts/TestFacetProxyContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.16;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import {LibAccounts} from "../../core/accounts/lib/LibAccounts.sol";
import {AccountStorage} from "../../core/accounts/storage.sol";
import {Utils} from "../../lib/utils.sol";

/**
* @dev This contract implements a proxy that is upgradeable by an admin.
Expand Down Expand Up @@ -185,4 +186,8 @@ contract TestFacetProxyContract is TransparentUpgradeableProxy {
AccountStorage.State storage state = LibAccounts.diamondStorage();
return state.config;
}

function callSelf(uint256 value, bytes memory data) external {
Utils._execute(address(this), value, data);
}
}
Loading
Loading