Skip to content

Instantly share code, notes, and snippets.

@ernestognw
Created April 5, 2024 17:36
Show Gist options
  • Save ernestognw/9bc89668722090c5976f42531032a81a to your computer and use it in GitHub Desktop.
Save ernestognw/9bc89668722090c5976f42531032a81a to your computer and use it in GitHub Desktop.
// 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