Skip to content

Commit

Permalink
Merge branch 'v2.1' of github.com:allo-protocol/allo-v2 into feat/upg…
Browse files Browse the repository at this point in the history
…rade-solidity-0.8.22
  • Loading branch information
ilpepepig committed Aug 27, 2024
2 parents 3db8b06 + a0142e8 commit 4b6ee52
Show file tree
Hide file tree
Showing 40 changed files with 1,532 additions and 265 deletions.
66 changes: 24 additions & 42 deletions contracts/core/Allo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import "openzeppelin-contracts-upgradeable/contracts/access/AccessControlUpgrade
import "openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol";
// Interfaces
import "./interfaces/IAllo.sol";

// Internal Libraries
import {Clone} from "./libraries/Clone.sol";
import {Errors} from "./libraries/Errors.sol";
import "./libraries/Native.sol";
import {Native} from "./libraries/Native.sol";
import {Transfer} from "./libraries/Transfer.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Expand All @@ -35,16 +34,9 @@ import {Transfer} from "./libraries/Transfer.sol";
/// @author @thelostone-mc <aditya@gitcoin.co>, @0xKurt <kurt@gitcoin.co>, @codenamejason <jason@gitcoin.co>, @0xZakk <zakk@gitcoin.co>, @nfrgosselin <nate@gitcoin.co>
/// @notice This contract is used to create & manage pools as well as manage the protocol.
/// @dev The contract must be initialized with the 'initialize()' function.
contract Allo is
IAllo,
Native,
Transfer,
Initializable,
Ownable,
AccessControlUpgradeable,
ReentrancyGuardUpgradeable,
Errors
{
contract Allo is IAllo, Native, Initializable, Ownable, AccessControlUpgradeable, ReentrancyGuardUpgradeable, Errors {
using Transfer for address;

// ==========================
// === Storage Variables ====
// ==========================
Expand Down Expand Up @@ -319,7 +311,7 @@ contract Allo is
uint256 amount = _token == NATIVE ? address(this).balance : IERC20Upgradeable(_token).balanceOf(address(this));

// Transfer the amount to the recipient (pool owner)
_transferAmount(_token, _recipient, amount);
_token.transferAmount(_recipient, amount);
}

// ====================================
Expand Down Expand Up @@ -544,10 +536,11 @@ contract Allo is
// To prevent paying the baseFee from the Allo contract's balance
// If _token is NATIVE, then baseFee + _amount should be equal to _msgValue.
// If _token is not NATIVE, then baseFee should be equal to _msgValue.
if ((_token == NATIVE && (baseFee + _amount != _msgValue)) || (_token != NATIVE && baseFee != _msgValue)) {
revert NOT_ENOUGH_FUNDS();
}
_transferAmount(NATIVE, treasury, baseFee);
if (_token == NATIVE && (baseFee + _amount != _msgValue)) revert NOT_ENOUGH_FUNDS();
if (_token != NATIVE && baseFee != _msgValue) revert NOT_ENOUGH_FUNDS();

address(treasury).transferAmountNative(baseFee);

emit BaseFeePaid(poolId, baseFee);
}

Expand Down Expand Up @@ -586,39 +579,28 @@ contract Allo is
/// @param _poolId The 'poolId' for the pool you are funding
/// @param _strategy The address of the strategy
function _fundPool(uint256 _amount, address _funder, uint256 _poolId, IBaseStrategy _strategy) internal virtual {
uint256 feeAmount;
uint256 amountAfterFee = _amount;
uint256 feeAmount = (_amount * percentFee) / getFeeDenominator(); // Can be zero if percentFee is zero
uint256 amountAfterFee = _amount - feeAmount;

Pool storage pool = pools[_poolId];
address _token = pool.token;

if (percentFee > 0) {
feeAmount = (_amount * percentFee) / getFeeDenominator();
amountAfterFee -= feeAmount;

if (feeAmount + amountAfterFee != _amount) revert INVALID();

if (_token == NATIVE) {
_transferAmountFrom(_token, TransferData({from: _funder, to: treasury, amount: feeAmount}));
} else {
uint256 balanceBeforeFee = _getBalance(_token, treasury);
_transferAmountFrom(_token, TransferData({from: _funder, to: treasury, amount: feeAmount}));
uint256 balanceAfterFee = _getBalance(_token, treasury);
// Track actual fee paid to account for fee on ERC20 token transfers
feeAmount = balanceAfterFee - balanceBeforeFee;
}
}
if (_token == NATIVE && msg.value < _amount) revert ETH_MISMATCH();

if (_token == NATIVE) {
_transferAmountFrom(_token, TransferData({from: _funder, to: address(_strategy), amount: amountAfterFee}));
} else {
uint256 balanceBeforeFundingPool = _getBalance(_token, address(_strategy));
_transferAmountFrom(_token, TransferData({from: _funder, to: address(_strategy), amount: amountAfterFee}));
uint256 balanceAfterFundingPool = _getBalance(_token, address(_strategy));
if (feeAmount > 0) {
uint256 balanceBeforeFee = _token.getBalance(treasury);
_token.transferAmountFrom(_funder, treasury, feeAmount);
uint256 balanceAfterFee = _token.getBalance(treasury);
// Track actual fee paid to account for fee on ERC20 token transfers
amountAfterFee = balanceAfterFundingPool - balanceBeforeFundingPool;
feeAmount = balanceAfterFee - balanceBeforeFee;
}

uint256 balanceBeforeFundingPool = _token.getBalance(address(_strategy));
_token.transferAmountFrom(_funder, address(_strategy), amountAfterFee);
uint256 balanceAfterFundingPool = _token.getBalance(address(_strategy));
// Track actual fee paid to account for fee on ERC20 token transfers
amountAfterFee = balanceAfterFundingPool - balanceBeforeFundingPool;

_strategy.increasePoolAmount(amountAfterFee);

emit PoolFunded(_poolId, amountAfterFee, feeAmount);
Expand Down
11 changes: 6 additions & 5 deletions contracts/core/Registry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import "./interfaces/IRegistry.sol";
import {Anchor} from "./Anchor.sol";
import {Errors} from "./libraries/Errors.sol";
import {Metadata} from "./libraries/Metadata.sol";
import "./libraries/Native.sol";
import "./libraries/Transfer.sol";
import {Transfer} from "./libraries/Transfer.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Expand All @@ -36,7 +35,9 @@ import "./libraries/Transfer.sol";
/// It is also used to deploy the anchor contract for each profile which acts as a proxy
/// for the profile and is used to receive funds and execute transactions on behalf of the profile
/// The Registry is also used to add and remove members from a profile and update the profile 'Metadata'
contract Registry is IRegistry, Initializable, Native, AccessControlUpgradeable, Transfer, Errors {
contract Registry is IRegistry, Initializable, AccessControlUpgradeable, Errors {
using Transfer for address;

/// ==========================
/// === Storage Variables ====
/// ==========================
Expand Down Expand Up @@ -383,7 +384,7 @@ contract Registry is IRegistry, Initializable, Native, AccessControlUpgradeable,
function recoverFunds(address _token, address _recipient) external onlyRole(ALLO_OWNER) {
if (_recipient == address(0)) revert ZERO_ADDRESS();

uint256 amount = _token == NATIVE ? address(this).balance : ERC20(_token).balanceOf(address(this));
_transferAmount(_token, _recipient, amount);
uint256 amount = _token.getBalance(address(this));
_token.transferAmount(_recipient, amount);
}
}
15 changes: 15 additions & 0 deletions contracts/core/interfaces/IDAI.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

interface IDAI {
function permit(
address holder,
address spender,
uint256 nonce,
uint256 expiry,
bool allowed,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
159 changes: 106 additions & 53 deletions contracts/core/libraries/Transfer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ pragma solidity ^0.8.19;

// External Libraries
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
// Internal Libraries
import "./Native.sol";
// Interfaces
import {ISignatureTransfer} from "permit2/ISignatureTransfer.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IDAI} from "contracts/core/interfaces/IDAI.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Expand All @@ -21,86 +24,136 @@ import "./Native.sol";
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠙⠋⠛⠙⠋⠛⠙⠋⠛⠙⠋⠃⠀⠀⠀⠀⠀⠀⠀⠀⠠⠿⠻⠟⠿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠟⠿⠟⠿⠆⠀⠸⠿⠿⠟⠯⠀⠀⠀⠸⠿⠿⠿⠏⠀⠀⠀⠀⠀⠈⠉⠻⠻⡿⣿⢿⡿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀
// allo.gitcoin.co

/// @title Transfer contract
/// @author @thelostone-mc <aditya@gitcoin.co>, @0xKurt <kurt@gitcoin.co>, @codenamejason <jason@gitcoin.co>, @0xZakk <zakk@gitcoin.co>, @nfrgosselin <nate@gitcoin.co>
/// @notice A helper contract to transfer tokens within Allo protocol
/// @title Transfer library
/// @notice A helper library to transfer tokens within Allo protocol
/// @dev Handles the transfer of tokens to an address
contract Transfer is Native {
/// @notice Thrown when the amount of tokens sent does not match the amount of tokens expected
error AMOUNT_MISMATCH();
library Transfer {
using SafeTransferLib for address;

/// @notice This holds the details for a transfer
struct TransferData {
address from;
address to;
uint256 amount;
}

/// @notice Transfer an amount of a token to an array of addresses
/// @param _token The address of the token
/// @param _transferData TransferData[]
/// @return Whether the transfer was successful or not
function _transferAmountsFrom(address _token, TransferData[] memory _transferData)
internal
virtual
returns (bool)
{
uint256 msgValue = msg.value;
/// @notice Address of the native token
address public constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

for (uint256 i; i < _transferData.length; ++i) {
TransferData memory transferData = _transferData[i];

if (_token == NATIVE) {
msgValue -= transferData.amount;
SafeTransferLib.safeTransferETH(transferData.to, transferData.amount);
} else {
SafeTransferLib.safeTransferFrom(_token, transferData.from, transferData.to, transferData.amount);
}
}
/// @notice Thrown if a signature of a length different than 64 or 65 bytes is passed
error INVALID_SIGNATURE_LENGTH();

if (msgValue != 0) revert AMOUNT_MISMATCH();
/// @notice Supported permit formats
enum PermitFormat {
None,
Permit,
PermitDAI,
Permit2
}

return true;
/// @notice Stores the permit2 data for the allocation
struct PermitData {
ISignatureTransfer permit2;
uint256 nonce;
uint256 deadline;
bytes signature;
}

/// @notice Transfer an amount of a token to an address
/// @param _token The address of the token
/// @param _transferData Individual TransferData
/// @return Whether the transfer was successful or not
function _transferAmountFrom(address _token, TransferData memory _transferData) internal virtual returns (bool) {
uint256 amount = _transferData.amount;
/// @dev When this function is used, it must be checked that balances or msg.value is correct for native tokens
/// @param _token The token to transfer
/// @param _from The address to transfer to
/// @param _to The address to transfer to
/// @param _amount The amount to transfer
function transferAmountFrom(address _token, address _from, address _to, uint256 _amount) internal {
if (_token == NATIVE) {
// Native Token
if (msg.value < amount) revert AMOUNT_MISMATCH();

SafeTransferLib.safeTransferETH(_transferData.to, amount);
// '_from' is ignored. The contract's balance is used.
if (_to != address(this)) _to.safeTransferETH(_amount);
} else {
SafeTransferLib.safeTransferFrom(_token, _transferData.from, _transferData.to, amount);
_token.safeTransferFrom(_from, _to, _amount);
}
return true;
}

/// @notice Transfer an amount of a token to an address
/// @param _token The token to transfer
/// @param _to The address to transfer to
/// @param _amount The amount to transfer
function _transferAmount(address _token, address _to, uint256 _amount) internal virtual {
function transferAmount(address _token, address _to, uint256 _amount) internal {
if (_token == NATIVE) {
SafeTransferLib.safeTransferETH(_to, _amount);
_to.safeTransferETH(_amount);
} else {
SafeTransferLib.safeTransfer(_token, _to, _amount);
_token.safeTransfer(_to, _amount);
}
}

/// @notice Transfer an amount of native token to an address
/// @param _to The address to transfer to
/// @param _amount The amount to transfer
function transferAmountNative(address _to, uint256 _amount) internal {
_to.safeTransferETH(_amount);
}

/// @notice Get the balance of a token for an account
/// @param _token The token to get the balance of
/// @param _account The account to get the balance for
/// @return The balance of the token for the account
function _getBalance(address _token, address _account) internal view returns (uint256) {
function getBalance(address _token, address _account) internal view returns (uint256) {
if (_token == NATIVE) {
return payable(_account).balance;
} else {
return SafeTransferLib.balanceOf(_token, _account);
return _token.balanceOf(_account);
}
}

/// @notice Uses a permit if given. Three permit formats are accepted: permit2, ERC20Permit and DAI-like permits
/// @dev permit data is ignored if empty
/// @param _token The token address
/// @param _from The address signing the permit
/// @param _to The address to give allowance to
/// @param _amount The amount to allow
/// @param _permitData The PermitData containing the signature and relevant permit data
function usePermit(address _token, address _from, address _to, uint256 _amount, bytes memory _permitData)
internal
{
// Only try to use permit if needed
if (_permitData.length == 0) return;
if (IERC20(_token).allowance(_from, _to) >= _amount) return;

(PermitFormat permitFormat, PermitData memory permit) = abi.decode(_permitData, (PermitFormat, PermitData));
if (permitFormat == PermitFormat.Permit2) {
permit.permit2.permitTransferFrom(
ISignatureTransfer.PermitTransferFrom({
permitted: ISignatureTransfer.TokenPermissions({token: _token, amount: _amount}),
nonce: permit.nonce,
deadline: permit.deadline
}),
ISignatureTransfer.SignatureTransferDetails({to: _to, requestedAmount: _amount}),
_from,
permit.signature
);
} else if (permitFormat == PermitFormat.Permit) {
(bytes32 r, bytes32 s, uint8 v) = _splitSignature(permit.signature);
IERC20Permit(_token).permit(_from, _to, _amount, permit.deadline, v, r, s);
} else if (permitFormat == PermitFormat.PermitDAI) {
(bytes32 r, bytes32 s, uint8 v) = _splitSignature(permit.signature);
IDAI(_token).permit(_from, _to, permit.nonce, permit.deadline, true, v, r, s);
}
}

/// @notice Splits a signature into its r, s, v components
/// @dev compact EIP-2098 signatures are accepted as well
/// @param _signature The signature
function _splitSignature(bytes memory _signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
if (_signature.length == 65) {
assembly {
r := mload(add(_signature, 0x20))
s := mload(add(_signature, 0x40))
v := byte(0, mload(add(_signature, 0x60)))
}
} else if (_signature.length == 64) {
// EIP-2098
bytes32 vs;
assembly {
r := mload(add(_signature, 0x20))
vs := mload(add(_signature, 0x40))
}
s = vs & (0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
v = uint8(uint256(vs >> 255)) + 27;
} else {
revert INVALID_SIGNATURE_LENGTH();
}
}
}
7 changes: 3 additions & 4 deletions contracts/strategies/BaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
pragma solidity ^0.8.19;

// Interfaces
import "../core/interfaces/IStrategy.sol";
import "contracts/core/interfaces/IStrategy.sol";

// Libraries
import {Transfer} from "../core/libraries/Transfer.sol";
import {Errors} from "../core/libraries/Errors.sol";
import {Errors} from "contracts/core/libraries/Errors.sol";

// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣗⠀⠀⠀⢸⣿⣿⣿⡯⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Expand All @@ -27,7 +26,7 @@ import {Errors} from "../core/libraries/Errors.sol";
/// @author @thelostone-mc <aditya@gitcoin.co>, @0xKurt <kurt@gitcoin.co>, @codenamejason <jason@gitcoin.co>, @0xZakk <zakk@gitcoin.co>, @nfrgosselin <nate@gitcoin.co>
/// @notice This contract is the base contract for all strategies
/// @dev This contract is implemented by all strategies.
abstract contract BaseStrategy is IStrategy, Transfer, Errors {
abstract contract BaseStrategy is IStrategy, Errors {
/// ==========================
/// === Storage Variables ====
/// ==========================
Expand Down
Loading

0 comments on commit 4b6ee52

Please sign in to comment.