generated from PaulRBerg/foundry-template
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Token.sol
116 lines (104 loc) · 4.12 KB
/
Token.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
import { SafeERC20, IERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
/**
* @title Token Library
* @author Sam Bugs
* @notice A small library that contains helpers for tokens (both ERC20 and native)
*/
library Token {
using SafeERC20 for IERC20;
using Address for address payable;
using Address for address;
/// @notice A specific target to distribute tokens to
struct DistributionTarget {
address recipient;
uint256 shareBps;
}
address public constant NATIVE_TOKEN = address(0);
/**
* @notice Calculates the amount of token balance held by the contract
* @param _token The token to check
* @return _balance The current balance held by the contract
*/
function balanceOnContract(address _token) internal view returns (uint256 _balance) {
return _token == NATIVE_TOKEN ? address(this).balance : IERC20(_token).balanceOf(address(this));
}
/**
* @notice Performs a max approval to the allowance target, for the given token
* @param _token The token to approve
* @param _allowanceTarget The spender that will be approved
*/
function maxApprove(IERC20 _token, address _allowanceTarget) internal {
// This helper should handle cases like USDT. Thanks OZ!
_token.forceApprove(_allowanceTarget, type(uint256).max);
}
/**
* @notice Performs a max approval to the allowance target for the given token, as long as the token is not
* the native token, and the allowance target is not the zero address
* @param _token The token to approve
* @param _allowanceTarget The spender that will be approved
*/
function maxApproveIfNecessary(address _token, address _allowanceTarget) internal {
if (_token != NATIVE_TOKEN && _allowanceTarget != address(0)) {
maxApprove(IERC20(_token), _allowanceTarget);
}
}
/**
* @notice Distributes the available amount of the given token according to the set distribution. All tokens
* will be distributed according to the configured shares. The last target will get sent all unassigned
* tokens
* @param _token The token to distribute
* @param _distribution How to distribute the available amount of the token. Must have at least one target
*/
function distributeTo(
address _token,
DistributionTarget[] calldata _distribution
)
internal
returns (uint256 _available)
{
_available = balanceOnContract(_token);
uint256 _amountLeft = _available;
// Distribute amounts
for (uint256 i; i < _distribution.length - 1;) {
uint256 _toSend = _available * _distribution[i].shareBps / 10_000;
sendAmountTo(_token, _toSend, _distribution[i].recipient);
unchecked {
// We know that _toSend <= _amountLeft because if it wasn't, sendAmountTo would have reverted
_amountLeft -= _toSend;
++i;
}
}
// Send amount left to the last recipient
sendAmountTo(_token, _amountLeft, _distribution[_distribution.length - 1].recipient);
}
/**
* @notice Checks if the contract has any balance of the given token, and if it does,
* it sends it to the given recipient
* @param _token The token to check
* @param _recipient The recipient of the token balance
* @return _balance The current balance held by the contract
*/
function sendBalanceOnContractTo(address _token, address _recipient) internal returns (uint256 _balance) {
_balance = balanceOnContract(_token);
sendAmountTo(_token, _balance, _recipient);
}
/**
* @notice Transfers the given amount of tokens from the contract to the recipient
* @param _token The token to check
* @param _amount The amount to send
* @param _recipient The recipient
*/
function sendAmountTo(address _token, uint256 _amount, address _recipient) internal {
if (_amount > 0) {
if (_recipient == address(0)) _recipient = msg.sender;
if (_token == NATIVE_TOKEN) {
payable(_recipient).sendValue(_amount);
} else {
IERC20(_token).safeTransfer(_recipient, _amount);
}
}
}
}