Skip to content

Commit

Permalink
Merge pull request #62 from Gearbox-protocol/bot-list-fixes
Browse files Browse the repository at this point in the history
feat: Bot list fixes + BalancesLogic tests/fixes
  • Loading branch information
0xmikko authored May 31, 2023
2 parents 021f15d + 43dbea6 commit 6ff3b16
Show file tree
Hide file tree
Showing 8 changed files with 366 additions and 87 deletions.
18 changes: 14 additions & 4 deletions contracts/interfaces/IBotListV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ interface IBotListV3Events {
event BotForbiddenStatusChanged(address indexed bot, bool status);

/// @dev Emits when the user changes the amount of funds in his bot wallet
event ChangeFunding(address indexed payer, uint256 newRemainingFunds);
event Deposit(address indexed payer, uint256 amount);

/// @dev Emits when the user changes the amount of funds in his bot wallet
event Withdraw(address indexed payer, uint256 amount);

/// @dev Emits when the allowed weekly amount of bot's spending is changed by the user
event ChangeBotWeeklyAllowance(address indexed payer, address indexed bot, uint72 newWeeklyAllowance);
Expand All @@ -44,7 +47,7 @@ interface IBotListV3Events {
event SetBotDAOFee(uint16 newFee);

/// @dev Emits when all bot permissions for a Credit Account are erased
event EraseBots(address creditAccount);
event EraseBot(address indexed creditAccount, address indexed bot);

/// @dev Emits when a new Credit Manager is approved in BotList
event CreditManagerAdded(address indexed creditManager);
Expand All @@ -69,10 +72,10 @@ interface IBotListV3 is IBotListV3Events, IVersion {
function eraseAllBotPermissions(address creditAccount) external;

/// @dev Adds funds to the borrower's bot payment wallet
function addFunding() external payable;
function deposit() external payable;

/// @dev Removes funds from the borrower's bot payment wallet
function removeFunding(uint256 amount) external;
function withdraw(uint256 amount) external;

/// @dev Takes payment for performed services from the user's balance and sends to the bot
/// @param payer Address to charge
Expand All @@ -81,6 +84,10 @@ interface IBotListV3 is IBotListV3Events, IVersion {
/// @param paymentAmount Amount to pay
function payBot(address payer, address creditAccount, address bot, uint72 paymentAmount) external;

//
// GETTERS
//

/// @dev Returns all active bots currently on the account
function getActiveBots(address creditAccount) external view returns (address[] memory);

Expand All @@ -95,4 +102,7 @@ interface IBotListV3 is IBotListV3Events, IVersion {
external
view
returns (uint192 permissions, bool forbidden);

/// @dev Returns user funcding balance in ETH
function balanceOf(address payer) external view returns (uint256);
}
2 changes: 1 addition & 1 deletion contracts/libraries/BalancesLogic.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ library BalancesLogic {
forbiddenBalances = new BalanceWithMask[](forbiddenTokensOnAccount.calcEnabledTokens());
unchecked {
uint256 i;
for (uint256 tokenMask = 1; tokenMask < forbiddenTokensOnAccount; tokenMask <<= 1) {
for (uint256 tokenMask = 1; tokenMask <= forbiddenTokensOnAccount; tokenMask <<= 1) {
if (forbiddenTokensOnAccount & tokenMask != 0) {
address token = getTokenByMaskFn(tokenMask);
forbiddenBalances[i].token = token;
Expand Down
140 changes: 85 additions & 55 deletions contracts/support/BotListV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ pragma solidity ^0.8.17;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {IWETH} from "@gearbox-protocol/core-v2/contracts/interfaces/external/IWETH.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";

import "../interfaces/IAddressProviderV3.sol";
import {ACLNonReentrantTrait} from "../traits/ACLNonReentrantTrait.sol";
import {IBotListV3, BotFunding} from "../interfaces/IBotListV3.sol";
import {IAddressProvider} from "@gearbox-protocol/core-v2/contracts/interfaces/IAddressProvider.sol";
import {ICreditManagerV3} from "../interfaces/ICreditManagerV3.sol";
import {ICreditFacadeV3} from "../interfaces/ICreditFacadeV3.sol";
import {ICreditAccountBase} from "../interfaces/ICreditAccountV3.sol";

import "../interfaces/IExceptions.sol";
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";

/// @title BotList
/// @notice Used to store a mapping of borrowers => bots. A separate contract is used for transferability when
Expand All @@ -25,6 +29,22 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
using Address for address;
using Address for address payable;
using EnumerableSet for EnumerableSet.AddressSet;
using SafeERC20 for IERC20;

/// @notice Contract version
uint256 public constant override version = 3_00;

/// @notice Address of the DAO treasury
address public immutable treasury;

/// @notice Address of the DAO treasury
address public immutable weth;

/// @notice ERC20 compatibility to be able to add to wallet to manager user's bot funding
string public constant symbol = "gETH";

/// @notice ERC20 compatibility to be able to add to wallet to manager user's bot funding
string public constant name = "Gearbox bot funding";

/// @notice Mapping from Credit Manager address to their status as an approved Credit Manager
/// Only Credit Facades connected to approved Credit Managers can alter bot permissions
Expand All @@ -40,22 +60,17 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
mapping(address => bool) public forbiddenBot;

/// @notice Mapping from borrower to their bot funding balance
mapping(address => uint256) public fundingBalances;
mapping(address => uint256) public override balanceOf;

/// @notice Mapping of (creditAccount, bot) to bot funding parameters
mapping(address => mapping(address => BotFunding)) public botFunding;

/// @notice A fee (in PERCENTAGE_FACTOR format) charged by the DAO on bot payments
uint16 public daoFee = 0;

/// @notice Address of the DAO treasury
address public immutable treasury;

/// @notice Contract version
uint256 public constant override version = 3_00;

constructor(address _addressProvider) ACLNonReentrantTrait(_addressProvider) {
treasury = IAddressProvider(_addressProvider).getTreasuryContract();
constructor(address addressProvider) ACLNonReentrantTrait(addressProvider) {
treasury = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_TREASURY, NO_VERSION_CONTROL);
weth = IAddressProviderV3(addressProvider).getAddressOrRevert(AP_WETH_TOKEN, NO_VERSION_CONTROL);
}

/// @notice Limits access to a function only to Credit Facades connected to approved CMs
Expand Down Expand Up @@ -95,23 +110,28 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {

if (permissions != 0) {
activeBots[creditAccount].add(bot); // F: [BL-03]
} else if (permissions == 0) {
if (fundingAmount != 0 || weeklyFundingAllowance != 0) {
revert PositiveFundingForInactiveBotException(); // F: [BL-03]
}

activeBots[creditAccount].remove(bot); // F: [BL-03]
}
botPermissions[creditAccount][bot] = permissions; // F: [BL-03]

botPermissions[creditAccount][bot] = permissions; // F: [BL-03]
botFunding[creditAccount][bot].remainingFunds = fundingAmount; // F: [BL-03]
botFunding[creditAccount][bot].maxWeeklyAllowance = weeklyFundingAllowance; // F: [BL-03]
botFunding[creditAccount][bot].remainingWeeklyAllowance = weeklyFundingAllowance; // F: [BL-03]
botFunding[creditAccount][bot].allowanceLU = uint40(block.timestamp); // F: [BL-03]
BotFunding storage bf = botFunding[creditAccount][bot];

activeBotsRemaining = activeBots[creditAccount].length(); // F: [BL-03]
bf.remainingFunds = fundingAmount; // F: [BL-03]
bf.maxWeeklyAllowance = weeklyFundingAllowance; // F: [BL-03]
bf.remainingWeeklyAllowance = weeklyFundingAllowance; // F: [BL-03]
bf.allowanceLU = uint40(block.timestamp); // F: [BL-03]

emit SetBotPermissions({
creditAccount: creditAccount,
bot: bot,
permissions: permissions,
fundingAmount: fundingAmount,
weeklyFundingAllowance: weeklyFundingAllowance
}); // F: [BL-03]
} else {
_eraseBot(creditAccount, bot); // F: [BL-03]
}

emit SetBotPermissions(creditAccount, bot, permissions, fundingAmount, weeklyFundingAllowance); // F: [BL-03]
activeBotsRemaining = activeBots[creditAccount].length(); // F: [BL-03]
}

/// @notice Removes permissions and funding for all bots with non-zero permissions for a credit account
Expand All @@ -122,22 +142,20 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
{
uint256 len = activeBots[creditAccount].length();

for (uint256 i = 0; i < len;) {
address bot = activeBots[creditAccount].at(0); // F: [BL-06]
botPermissions[creditAccount][bot] = 0; // F: [BL-06]
botFunding[creditAccount][bot].remainingFunds = 0; // F: [BL-06]
botFunding[creditAccount][bot].maxWeeklyAllowance = 0; // F: [BL-06]
botFunding[creditAccount][bot].remainingWeeklyAllowance = 0; // F: [BL-06]
botFunding[creditAccount][bot].allowanceLU = uint40(block.timestamp); // F: [BL-06]
activeBots[creditAccount].remove(bot); // F: [BL-06]
unchecked {
++i;
unchecked {
for (uint256 i = 0; i < len; ++i) {
address bot = activeBots[creditAccount].at(len - i - 1); // F: [BL-06]
_eraseBot(creditAccount, bot);
}
}
}

if (len > 0) {
emit EraseBots(creditAccount); // F: [BL-06]
}
function _eraseBot(address creditAccount, address bot) internal {
delete botPermissions[creditAccount][bot]; // F: [BL-06]
delete botFunding[creditAccount][bot]; // F: [BL-06]

activeBots[creditAccount].remove(bot); // F: [BL-06]
emit EraseBot(creditAccount, bot); // F: [BL-06]
}

/// @notice Takes payment for performed services from the user's balance and sends to the bot
Expand All @@ -149,9 +167,7 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
external
onlyValidCreditFacade
{
if (paymentAmount == 0) {
revert AmountCantBeZeroException(); // F: [BL-05]
}
if (paymentAmount == 0) return;

BotFunding storage bf = botFunding[creditAccount][bot]; // F: [BL-05]

Expand All @@ -160,40 +176,45 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
bf.remainingWeeklyAllowance = bf.maxWeeklyAllowance; // F: [BL-05]
}

uint72 feeAmount = daoFee * paymentAmount / PERCENTAGE_FACTOR; // F: [BL-05]
/// feeAmount is always < paymentAmount, however uint256 conversation adds more space for computations
uint72 feeAmount = uint72(uint256(daoFee) * paymentAmount / PERCENTAGE_FACTOR); // F: [BL-05]

uint72 totalAmount = paymentAmount + feeAmount;

bf.remainingWeeklyAllowance -= paymentAmount + feeAmount; // F: [BL-05]
bf.remainingFunds -= paymentAmount + feeAmount; // F: [BL-05]
bf.remainingWeeklyAllowance -= totalAmount; // F: [BL-05]
bf.remainingFunds -= totalAmount; // F: [BL-05]

fundingBalances[payer] -= uint256(paymentAmount + feeAmount); // F: [BL-05]
balanceOf[payer] -= totalAmount; // F: [BL-05]

payable(bot).sendValue(paymentAmount); // F: [BL-05]
if (feeAmount > 0) payable(treasury).sendValue(feeAmount); // F: [BL-05]
IERC20(weth).safeTransfer(bot, paymentAmount); // F: [BL-05]

if (feeAmount != 0) {
IERC20(weth).safeTransfer(treasury, feeAmount); // F: [BL-05]
}

emit PayBot(payer, creditAccount, bot, paymentAmount, feeAmount); // F: [BL-05]
}

/// @notice Adds funds to the borrower's bot payment wallet
function addFunding() external payable nonReentrant {
function deposit() public payable nonReentrant {
if (msg.value == 0) {
revert AmountCantBeZeroException(); // F: [BL-04]
}

uint256 newFunds = fundingBalances[msg.sender] + msg.value; // F: [BL-04]

fundingBalances[msg.sender] = newFunds; // F: [BL-04]
IWETH(weth).deposit{value: msg.value}();
balanceOf[msg.sender] += msg.value;

emit ChangeFunding(msg.sender, newFunds); // F: [BL-04]
emit Deposit(msg.sender, msg.value); // F: [BL-04]
}

/// @notice Removes funds from the borrower's bot payment wallet
function removeFunding(uint256 amount) external nonReentrant {
uint256 newFunds = fundingBalances[msg.sender] - amount; // F: [BL-04]
function withdraw(uint256 amount) external nonReentrant {
balanceOf[msg.sender] -= amount; // F: [BL-04]

fundingBalances[msg.sender] = newFunds; // F: [BL-04]
IWETH(weth).withdraw(amount);
payable(msg.sender).sendValue(amount); // F: [BL-04]

emit ChangeFunding(msg.sender, newFunds); // F: [BL-04]
emit Withdraw(msg.sender, amount); // F: [BL-04]
}

/// @notice Returns all active bots currently on the account
Expand Down Expand Up @@ -223,6 +244,10 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
/// @notice Sets the DAO fee on bot payments
/// @param newFee The new fee value
function setDAOFee(uint16 newFee) external configuratorOnly {
if (daoFee > PERCENTAGE_FACTOR) {
revert IncorrectParameterException();
}

daoFee = newFee; // F: [BL-02]

emit SetBotDAOFee(newFee); // F: [BL-02]
Expand All @@ -240,4 +265,9 @@ contract BotListV3 is ACLNonReentrantTrait, IBotListV3 {
emit CreditManagerRemoved(creditManager);
}
}

/// @notice Allows this contract to unwrap WETH and deposit if address is not WETH
receive() external payable {
if (msg.sender != weth) deposit();
}
}
3 changes: 3 additions & 0 deletions contracts/test/mocks/core/AddressProviderV3ACLMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {AccountFactoryMock} from "../core/AccountFactoryMock.sol";
import {PriceOracleMock} from "../oracles/PriceOracleMock.sol";
import {WithdrawalManagerMock} from "../support/WithdrawalManagerMock.sol";
import {BotListMock} from "../support/BotListMock.sol";
import {WETHMock} from "../token/WETHMock.sol";

import {Test} from "forge-std/Test.sol";

Expand Down Expand Up @@ -43,6 +44,8 @@ contract AddressProviderV3ACLMock is Test, AddressProviderV3 {

_setAddress(AP_TREASURY, makeAddr("TREASURY"), 0);

_setAddress(AP_WETH_TOKEN, address(new WETHMock()), 0);

isConfigurator[msg.sender] = true;
owner = msg.sender;
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/suites/PoolDeployer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ contract PoolDeployer is Test {
withdrawalManager =
WithdrawalManagerV3(payable(addressProvider.getAddressOrRevert(AP_WITHDRAWAL_MANAGER, 3_00)));

botList = BotListV3(addressProvider.getAddressOrRevert(AP_BOT_LIST, 3_00));
botList = BotListV3(payable(addressProvider.getAddressOrRevert(AP_BOT_LIST, 3_00)));

underlying = _underlying;

Expand Down
Loading

0 comments on commit 6ff3b16

Please sign in to comment.