DCIPs/assets/eip-5827/ERC5827.sol

150 lines
4.6 KiB
Solidity

// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.8.17;
import "openzeppelin-contracts/token/ERC20/ERC20.sol";
import "./IERC5827.sol";
contract ERC5827 is ERC20, IERC5827 {
struct RenewableAllowance {
uint256 amount;
uint192 recoveryRate;
uint64 lastUpdated;
}
// owner => spender => renewableAllowance
mapping(address => mapping(address => RenewableAllowance))
private rAllowance;
constructor(
string memory name_,
string memory symbol_
) ERC20(name_, symbol_) {}
function approve(
address _spender,
uint256 _value
) public override(ERC20, IERC5827) returns (bool success) {
address owner = _msgSender();
_approve(owner, _spender, _value, 0);
return true;
}
function approveRenewable(
address _spender,
uint256 _value,
uint256 _recoveryRate
) public override returns (bool success) {
address owner = _msgSender();
_approve(owner, _spender, _value, _recoveryRate);
return true;
}
function _approve(
address _owner,
address _spender,
uint256 _value,
uint256 _recoveryRate
) internal virtual {
require(
_recoveryRate <= _value,
"recoveryRate must be less than or equal to value"
);
rAllowance[_owner][_spender] = RenewableAllowance({
amount: _value,
recoveryRate: uint192(_recoveryRate),
lastUpdated: uint64(block.timestamp)
});
_approve(_owner, _spender, _value);
emit RenewableApproval(_owner, _spender, _value, _recoveryRate);
}
/// @notice fetch amounts spendable by _spender
/// @return remaining allowance at the current point in time
function allowance(
address _owner,
address _spender
) public view override(ERC20, IERC5827) returns (uint256 remaining) {
return _remainingAllowance(_owner, _spender);
}
/// @dev returns the sum of two uint256 values, saturating at 2**256 - 1
function saturatingAdd(
uint256 a,
uint256 b
) internal pure returns (uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return type(uint256).max;
return c;
}
}
function _remainingAllowance(
address _owner,
address _spender
) private view returns (uint256) {
RenewableAllowance memory a = rAllowance[_owner][_spender];
uint256 remaining = super.allowance(_owner, _spender);
uint256 recovered = uint256(a.recoveryRate) *
uint64(block.timestamp - a.lastUpdated);
uint256 remainingAllowance = saturatingAdd(remaining, recovered);
return remainingAllowance > a.amount ? a.amount : remainingAllowance;
}
/// @notice fetch approved max amount and recovery rate
/// @return amount initial and maximum allowance given to spender
/// @return recoveryRate recovery amount per second
function renewableAllowance(
address _owner,
address _spender
) public view returns (uint256 amount, uint256 recoveryRate) {
RenewableAllowance memory a = rAllowance[_owner][_spender];
return (a.amount, uint256(a.recoveryRate));
}
/// @notice transfers base token with renewable allowance logic applied
/// @param from owner of base token
/// @param to recipient of base token
/// @param amount amount to transfer
function transferFrom(
address from,
address to,
uint256 amount
) public override(ERC20, IERC5827) returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual override {
(uint256 maxAllowance, ) = renewableAllowance(owner, spender);
if (maxAllowance != type(uint256).max) {
uint256 currentAllowance = _remainingAllowance(owner, spender);
if (currentAllowance < amount) {
revert InsufficientRenewableAllowance({
available: currentAllowance
});
}
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
rAllowance[owner][spender].lastUpdated = uint64(block.timestamp);
}
}
function supportsInterface(
bytes4 interfaceId
) public view virtual returns (bool) {
return interfaceId == type(IERC5827).interfaceId;
}
}