Created
April 5, 2024 17:36
-
-
Save ernestognw/9bc89668722090c5976f42531032a81a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; | |
import {StorageSlot} from "@openzeppelin/contracts/utils/StorageSlot.sol"; | |
import {SlotDerivation} from "@openzeppelin/contracts/utils/SlotDerivation.sol"; | |
abstract contract ERC20TemporaryAllowance is ERC20 { | |
using StorageSlot for bytes32; | |
using SlotDerivation for bytes32; | |
struct TemporaryStorage { | |
mapping(address owner => mapping(address spender => mapping(uint256 blockNumber => uint256 amount))) temporaryAllowances; | |
} | |
// We use a special pointer to a storage slot to store the temporary allowance. | |
// keccak256(abi.encode(uint256(keccak256("openzeppelin.temporary.ERC20")) - 1)) & ~bytes32(uint256(0xff)) | |
bytes32 private constant ERC20TemporaryStorageLocation = | |
0xe0b9cbfb8f57e861de5c79285c40a1da35a924d5d9ed042e8294c0f481e84700; | |
function allowance( | |
address owner, | |
address spender | |
) public view virtual override returns (uint256) { | |
(bool success, uint256 amount) = Math.tryAdd( | |
super.allowance(owner, spender), | |
_loadTemporaryAllowance(owner, spender) | |
); | |
return success ? amount : type(uint256).max; | |
} | |
function temporaryApproval( | |
address spender, | |
uint256 value | |
) public virtual returns (bool) { | |
address owner = _msgSender(); | |
_storeTemporaryAllowance(owner, spender, value); | |
return true; | |
} | |
function _spendAllowance( | |
address owner, | |
address spender, | |
uint256 value | |
) internal virtual override { | |
unchecked { | |
// load transient allowance | |
uint256 currentTemporaryAllowance = _loadTemporaryAllowance( | |
owner, | |
spender | |
); | |
// if there is temporary allowance | |
if (currentTemporaryAllowance > 0) { | |
// if infinite, do nothing | |
if (currentTemporaryAllowance == type(uint256).max) return; | |
// check how much of the value is covered by the transient allowance | |
uint256 spendTemporaryAllowance = Math.min( | |
currentTemporaryAllowance, | |
value | |
); | |
// decrease transient allowance accordingly | |
_storeTemporaryAllowance( | |
owner, | |
spender, | |
currentTemporaryAllowance - spendTemporaryAllowance | |
); | |
// update value necessary | |
value -= spendTemporaryAllowance; | |
} | |
// if allowance is still needed | |
if (value > 0) { | |
super._spendAllowance(owner, spender, value); | |
} | |
} | |
} | |
function _loadTemporaryAllowance( | |
address owner, | |
address spender | |
) private view returns (uint256) { | |
return | |
_temporaryStorage().temporaryAllowances[owner][spender][ | |
block.number | |
]; | |
} | |
function _storeTemporaryAllowance( | |
address owner, | |
address spender, | |
uint256 value | |
) private { | |
_temporaryStorage().temporaryAllowances[owner][spender][ | |
block.number | |
] = value; | |
} | |
function _temporaryStorage() | |
internal | |
pure | |
returns (TemporaryStorage storage $) | |
{ | |
assembly { | |
$.slot := ERC20TemporaryStorageLocation | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment