draft-ERC20TemporaryApproval.sol 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v5.1.0-rc.0) (token/ERC20/extensions/draft-ERC20TemporaryApproval.sol)
  3. pragma solidity ^0.8.20;
  4. import {IERC20, ERC20} from "../ERC20.sol";
  5. import {IERC7674} from "../../../interfaces/draft-IERC7674.sol";
  6. import {Math} from "../../../utils/math/Math.sol";
  7. import {SlotDerivation} from "../../../utils/SlotDerivation.sol";
  8. import {StorageSlot} from "../../../utils/StorageSlot.sol";
  9. /**
  10. * @dev Extension of {ERC20} that adds support for temporary allowances following ERC-7674.
  11. *
  12. * WARNING: This is a draft contract. The corresponding ERC is still subject to changes.
  13. */
  14. abstract contract ERC20TemporaryApproval is ERC20, IERC7674 {
  15. using SlotDerivation for bytes32;
  16. using StorageSlot for bytes32;
  17. using StorageSlot for StorageSlot.Uint256SlotType;
  18. // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20_TEMPORARY_APPROVAL_STORAGE")) - 1)) & ~bytes32(uint256(0xff))
  19. bytes32 private constant ERC20_TEMPORARY_APPROVAL_STORAGE =
  20. 0xea2d0e77a01400d0111492b1321103eed560d8fe44b9a7c2410407714583c400;
  21. /**
  22. * @dev {allowance} override that includes the temporary allowance when looking up the current allowance. If
  23. * adding up the persistent and the temporary allowances result in an overflow, type(uint256).max is returned.
  24. */
  25. function allowance(address owner, address spender) public view virtual override(IERC20, ERC20) returns (uint256) {
  26. (bool success, uint256 amount) = Math.tryAdd(
  27. super.allowance(owner, spender),
  28. _temporaryAllowance(owner, spender)
  29. );
  30. return success ? amount : type(uint256).max;
  31. }
  32. /**
  33. * @dev Internal getter for the current temporary allowance that `spender` has over `owner` tokens.
  34. */
  35. function _temporaryAllowance(address owner, address spender) internal view virtual returns (uint256) {
  36. return _temporaryAllowanceSlot(owner, spender).tload();
  37. }
  38. /**
  39. * @dev Alternative to {approve} that sets a `value` amount of tokens as the temporary allowance of `spender` over
  40. * the caller's tokens.
  41. *
  42. * Returns a boolean value indicating whether the operation succeeded.
  43. *
  44. * Requirements:
  45. * - `spender` cannot be the zero address.
  46. *
  47. * Does NOT emit an {Approval} event.
  48. */
  49. function temporaryApprove(address spender, uint256 value) public virtual returns (bool) {
  50. _temporaryApprove(_msgSender(), spender, value);
  51. return true;
  52. }
  53. /**
  54. * @dev Sets `value` as the temporary allowance of `spender` over the `owner` s tokens.
  55. *
  56. * This internal function is equivalent to `temporaryApprove`, and can be used to e.g. set automatic allowances
  57. * for certain subsystems, etc.
  58. *
  59. * Requirements:
  60. * - `owner` cannot be the zero address.
  61. * - `spender` cannot be the zero address.
  62. *
  63. * Does NOT emit an {Approval} event.
  64. */
  65. function _temporaryApprove(address owner, address spender, uint256 value) internal virtual {
  66. if (owner == address(0)) {
  67. revert ERC20InvalidApprover(address(0));
  68. }
  69. if (spender == address(0)) {
  70. revert ERC20InvalidSpender(address(0));
  71. }
  72. _temporaryAllowanceSlot(owner, spender).tstore(value);
  73. }
  74. /**
  75. * @dev {_spendAllowance} override that consumes the temporary allowance (if any) before eventually falling back
  76. * to consuming the persistent allowance.
  77. * NOTE: This function skips calling `super._spendAllowance` if the temporary allowance
  78. * is enough to cover the spending.
  79. */
  80. function _spendAllowance(address owner, address spender, uint256 value) internal virtual override {
  81. // load transient allowance
  82. uint256 currentTemporaryAllowance = _temporaryAllowance(owner, spender);
  83. // Check and update (if needed) the temporary allowance + set remaining value
  84. if (currentTemporaryAllowance > 0) {
  85. // All value is covered by the infinite allowance. nothing left to spend, we can return early
  86. if (currentTemporaryAllowance == type(uint256).max) {
  87. return;
  88. }
  89. // check how much of the value is covered by the transient allowance
  90. uint256 spendTemporaryAllowance = Math.min(currentTemporaryAllowance, value);
  91. unchecked {
  92. // decrease transient allowance accordingly
  93. _temporaryApprove(owner, spender, currentTemporaryAllowance - spendTemporaryAllowance);
  94. // update value necessary
  95. value -= spendTemporaryAllowance;
  96. }
  97. }
  98. // reduce any remaining value from the persistent allowance
  99. if (value > 0) {
  100. super._spendAllowance(owner, spender, value);
  101. }
  102. }
  103. function _temporaryAllowanceSlot(
  104. address owner,
  105. address spender
  106. ) private pure returns (StorageSlot.Uint256SlotType) {
  107. return ERC20_TEMPORARY_APPROVAL_STORAGE.deriveMapping(owner).deriveMapping(spender).asUint256();
  108. }
  109. }