A contribution-quality extension of OpenZeppelin's ERC20 contract that adds configurable approval caps and per-approval expiration timestamps.
Team: Kenny Bartel & Brianna Patten Course: CS5833 Upstream Repo: OpenZeppelin/openzeppelin-contracts Related Issue: openzeppelin-contracts#6500
The standard ERC-20 approval mechanism allows unlimited, non-expiring approvals.
This has been a known vector for token drains in DeFi, where users who previously
signed unlimited approvals to contracts that later turned malicious had their tokens
drained. While OpenZeppelin's ERC20Permit (ERC-2612) addresses expiry via
off-chain signatures, it requires EIP-712 infrastructure not available in simpler
applications and does not address approval caps.
ERC20SafeApproval is an abstract Solidity extension that enforces both on-chain
through simple inheritance, requiring no off-chain signing infrastructure.
- Approval Cap — a configurable maximum approval ceiling; no spender can ever be approved above this limit regardless of what the user signs
- Approval Expiry — per-approval expiration timestamps; expired approvals
automatically revert in
transferFrom()
New files added to the OpenZeppelin repository structure:
contracts/token/ERC20/extensions/ERC20SafeApproval.sol— main extension contractcontracts/mocks/token/ERC20SafeApprovalMock.sol— mock contract used for testingtest/token/ERC20/extensions/ERC20SafeApproval.test.js— Hardhat test suitescripts/deployERC20SafeApproval.js— Sepolia deployment script
- Node.js
- Git
git clone https://github.com/kennyb66/CS5833-FinalProject
cd CS5833-FinalProject
npm installnpm run compilenpm run test test/token/ERC20/extensions/ERC20SafeApproval.test.jsCreate a .env file in the root of the repo:
SEPOLIA_RPC_URL=your_rpc_url_here
PRIVATE_KEY=your_private_key_here
Then run:
./node_modules/.bin/hardhat run scripts/deployERC20SafeApproval.js --network sepolia| Contract | Network | Address |
|---|---|---|
| ERC20SafeApprovalMock | Sepolia | 0xdD1bf110349890E6183537eec6200775d425Dd0f |
Verified on Sepolia Etherscan
Transactions:
| Transaction | Hash |
|---|---|
| Mint | 0x6ff8385d... |
| Approve | 0x0a48fc2c... |
| ApproveWithExpiration | 0xfc6893b8... |
| Function | Standard ERC-20 | ERC20SafeApproval | Overhead |
|---|---|---|---|
approve() |
46,000 | 48,296 | +2,296 (+5%) |
approveWithExpiration() |
N/A | 73,112 | N/A |
transferFrom() |
34,000 | 59,910 | +25,910 (+76%) |
Measured using hardhat-gas-reporter. The transferFrom() overhead reflects the additional expiry check performed before each transfer.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./extensions/ERC20SafeApproval.sol";
contract MyToken is ERC20SafeApproval {
constructor() ERC20("MyToken", "MTK") ERC20SafeApproval(1000) {
// approval cap set to 1000 tokens at deploy time
}
}| Member | Contributions |
|---|---|
| Kenny Bartel | Project proposal, repository setup, test suite implementation, deployment script, GitHub contribution artifacts |
| Brianna Patten | Project report, ERC20SafeApproval extension contract, ERC20SafeApprovalToken contract, ERC20SafeApprovalMock contract |