Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permit2 support for ERC20 Terminals #57

Open
wants to merge 9 commits into
base: feature/remove-fee-discount
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
132 changes: 129 additions & 3 deletions contracts/JBERC20PaymentTerminal3_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,51 @@ pragma solidity ^0.8.16;
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {IERC20Metadata} from '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {JBPayoutRedemptionPaymentTerminal3_2} from './abstract/JBPayoutRedemptionPaymentTerminal3_2.sol';
import {IPermit2, IAllowanceTransfer} from 'permit2/src/interfaces/IPermit2.sol';
import {JBPayoutRedemptionPaymentTerminal3_2, IERC165} from './abstract/JBPayoutRedemptionPaymentTerminal3_2.sol';
import {IJBDirectory} from './interfaces/IJBDirectory.sol';
import {IJBOperatorStore} from './interfaces/IJBOperatorStore.sol';
import {IJBProjects} from './interfaces/IJBProjects.sol';
import {IJBSplitsStore} from './interfaces/IJBSplitsStore.sol';
import {IJBPrices} from './interfaces/IJBPrices.sol';
import {IJBPermit2PaymentTerminal} from './interfaces/IJBPermit2PaymentTerminal.sol';
import {JBSingleAllowanceData} from './structs/JBSingleAllowanceData.sol';

/// @notice Manages the inflows and outflows of an ERC-20 token.
contract JBERC20PaymentTerminal3_1_2 is JBPayoutRedemptionPaymentTerminal3_2 {
contract JBERC20PaymentTerminal3_1_2 is
JBPayoutRedemptionPaymentTerminal3_2,
IJBPermit2PaymentTerminal
{
using SafeERC20 for IERC20;

//*********************************************************************//
// --------------------------- custom errors ------------------------- //
//*********************************************************************//

error PERMIT_ALLOWANCE_NOT_ENOUGH(uint256 _transactionAmount, uint256 _permitAllowance);

//*********************************************************************//
// ---------------- public immutable stored properties --------------- //
//*********************************************************************//

IPermit2 public immutable PERMIT2;

//*********************************************************************//
// -------------------------- public views --------------------------- //
//*********************************************************************//

/// @notice Indicates if this contract adheres to the specified interface.
/// @dev See {IERC165-supportsInterface}.
/// @param _interfaceId The ID of the interface to check for adherance to.
/// @return A flag indicating if the provided interface ID is supported.
function supportsInterface(
bytes4 _interfaceId
) public view virtual override(JBPayoutRedemptionPaymentTerminal3_2, IERC165) returns (bool) {
return
_interfaceId == type(IJBPermit2PaymentTerminal).interfaceId ||
super.supportsInterface(_interfaceId);
}

//*********************************************************************//
// -------------------------- internal views ------------------------- //
//*********************************************************************//
Expand Down Expand Up @@ -47,7 +81,8 @@ contract JBERC20PaymentTerminal3_1_2 is JBPayoutRedemptionPaymentTerminal3_2 {
IJBSplitsStore _splitsStore,
IJBPrices _prices,
address _store,
address _owner
address _owner,
IPermit2 _permit2
)
JBPayoutRedemptionPaymentTerminal3_2(
address(_token),
Expand All @@ -64,13 +99,104 @@ contract JBERC20PaymentTerminal3_1_2 is JBPayoutRedemptionPaymentTerminal3_2 {
)
// solhint-disable-next-line no-empty-blocks
{
PERMIT2 = _permit2;
}

//*********************************************************************//
// ----------------------- public transactions ----------------------- //
//*********************************************************************//

/// @notice Contribute tokens to a project and sets an allowance for this terminal (using Permit2).
/// @param _projectId The ID of the project being paid.
/// @param _amount The amount of terminal tokens being received, as a fixed point number with the same amount of decimals as this terminal. If this terminal's token is ETH, this is ignored and msg.value is used in its place.
/// @param _token The token being paid. This terminal ignores this property since it only manages one token.
/// @param _beneficiary The address to mint tokens for and pass along to the funding cycle's data source and delegate.
/// @param _minReturnedTokens The minimum number of project tokens expected in return, as a fixed point number with the same amount of decimals as this terminal.
/// @param _preferClaimedTokens A flag indicating whether the request prefers to mint project tokens into the beneficiaries wallet rather than leaving them unclaimed. This is only possible if the project has an attached token contract. Leaving them unclaimed saves gas.
/// @param _memo A memo to pass along to the emitted event, and passed along the the funding cycle's data source and delegate. A data source can alter the memo before emitting in the event and forwarding to the delegate.
/// @param _metadata Bytes to send along to the data source, delegate, and emitted event, if provided.
/// @param _allowance The allowance to set for this terminal (using Permit2).
/// @return The number of tokens minted for the beneficiary, as a fixed point number with 18 decimals.
function payAndSetAllowance(
uint256 _projectId,
uint256 _amount,
address _token,
address _beneficiary,
uint256 _minReturnedTokens,
bool _preferClaimedTokens,
string calldata _memo,
bytes calldata _metadata,
JBSingleAllowanceData calldata _allowance
) external virtual returns (uint256) {
// If the `_allowance.amount` is less than `_amount` then
// setting the permit will still not result in a succeful payment
if (_amount < _allowance.amount) revert PERMIT_ALLOWANCE_NOT_ENOUGH(_amount, _allowance.amount);
// Get allowance to `spend` tokens for the sender
_permitAllowance(_allowance);
// Continue with the regular pay flow
return
pay(
_projectId,
_amount,
_token,
_beneficiary,
_minReturnedTokens,
_preferClaimedTokens,
_memo,
_metadata
);
}

/// @notice Receives funds belonging to the specified project.
/// @param _projectId The ID of the project to which the funds received belong.
/// @param _amount The amount of tokens to add, as a fixed point number with the same number of decimals as this terminal. If this is an ETH terminal, this is ignored and msg.value is used instead.
/// @param _token The token being paid. This terminal ignores this property since it only manages one currency.
/// @param _shouldRefundHeldFees A flag indicating if held fees should be refunded based on the amount being added.
/// @param _memo A memo to pass along to the emitted event.
/// @param _metadata Extra data to pass along to the emitted event.
/// @param _allowance The allowance to set for this terminal (using Permit2).
function addToBalanceOfAndSetAllowance(
uint256 _projectId,
uint256 _amount,
address _token,
bool _shouldRefundHeldFees,
string calldata _memo,
bytes calldata _metadata,
JBSingleAllowanceData calldata _allowance
) external virtual {
// If the `_allowance.amount` is less than `_amount` then
// setting the permit will still not result in a succeful payment
if (_amount < _allowance.amount) revert PERMIT_ALLOWANCE_NOT_ENOUGH(_amount, _allowance.amount);
// Get allowance to `spend` tokens for the user
_permitAllowance(_allowance);
// Continue with the regular addToBalanceOf flow
return addToBalanceOf(_projectId, _amount, _token, _shouldRefundHeldFees, _memo, _metadata);
}

//*********************************************************************//
// ---------------------- internal transactions ---------------------- //
//*********************************************************************//

/// @notice Gets allowance
/// @param _allowance the allowance to get using permit2
function _permitAllowance(JBSingleAllowanceData calldata _allowance) internal {
// Use Permit2 to set the allowance
PERMIT2.permit(
msg.sender,
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: address(token),
amount: _allowance.amount,
expiration: _allowance.expiration,
nonce: _allowance.nonce
}),
spender: address(this),
sigDeadline: _allowance.sigDeadline
}),
_allowance.signature
);
}

/// @notice Transfers tokens.
/// @param _from The address from which the transfer should originate.
/// @param _to The address to which the transfer should go.
Expand Down
3 changes: 1 addition & 2 deletions contracts/JBETHPaymentTerminal3_2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ contract JBETHPaymentTerminal3_1_2 is JBPayoutRedemptionPaymentTerminal3_2 {
address _store,
address _owner
)
JBPayoutRedemptionPaymentTerminal3_1_2(
JBPayoutRedemptionPaymentTerminal3_2(
JBTokens.ETH,
18, // 18 decimals.
JBCurrencies.ETH,
_baseWeightCurrency,
JBSplitsGroups.ETH_PAYOUT,
_operatorStore,
_projects,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1504,10 +1504,8 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_1_2 is

// Process each fee.
for (uint256 _i; _i < _heldFeesLength; ) {

if (leftoverAmount == 0) {
if (leftoverAmount == 0) {
_heldFeesOf[_projectId].push(_heldFees[_i]);

} else {
// Notice here we take feeIn the stored .amount
uint256 _feeAmount = (
Expand Down Expand Up @@ -1542,7 +1540,6 @@ abstract contract JBPayoutRedemptionPaymentTerminal3_1_2 is
}
leftoverAmount = 0;
}

}

unchecked {
Expand Down
Loading