Skip to content

Instantly share code, notes, and snippets.

@ernestognw
Created March 21, 2024 21:35
Show Gist options
  • Save ernestognw/334ce36076d1d936e44b7fa1ad2a6320 to your computer and use it in GitHub Desktop.
Save ernestognw/334ce36076d1d936e44b7fa1ad2a6320 to your computer and use it in GitHub Desktop.
// src/MyERC20Capped.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {ERC20Capped, ERC20} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
contract MyERC20Capped is ERC20Capped {
uint256 addressZeroBalance;
function balanceOf(address account) public view override returns (uint256) {
if (account == address(0)) return addressZeroBalance;
return super.balanceOf(account);
}
function _update(
address from,
address to,
uint256 value
) internal virtual override {
super._update(from, to, value);
if (to == address(0)) {
addressZeroBalance++;
}
}
constructor(
string memory _name,
string memory _symbol,
uint256 _cap
) ERC20(_name, _symbol) ERC20Capped(_cap) {
_mint(msg.sender, _cap);
}
}
// test/MyERC20Capped.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {MyERC20Capped} from "../src/MyERC20Capped.sol";
contract MyERC20CappedTest is Test {
MyERC20Capped public token;
function setUp() public {
token = new MyERC20Capped("MyERC20Capped", "MTK", 1000);
}
function testTransfer() public {
token.transfer(address(this), 100);
}
}
// src/MyERC20CappedModified.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {Context, IERC20, IERC20Metadata, IERC20Errors} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
// 1. Make all variables public
mapping(address account => uint256) public _balances;
mapping(address account => mapping(address spender => uint256))
public _allowances;
uint256 public _totalSupply;
string public _name;
string public _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual returns (string memory) {
return _name;
}
function symbol() public view virtual returns (string memory) {
return _symbol;
}
function decimals() public view virtual returns (uint8) {
return 18;
}
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
function allowance(
address owner,
address spender
) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
function approve(
address spender,
uint256 value
) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
function transferFrom(
address from,
address to,
uint256 value
) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
function _approve(
address owner,
address spender,
uint256 value,
bool emitEvent
) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
function _spendAllowance(
address owner,
address spender,
uint256 value
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(
spender,
currentAllowance,
value
);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
abstract contract ERC20CappedModified is ERC20 {
uint256 private immutable _cap;
error ERC20ExceededCap(uint256 increasedSupply, uint256 cap);
error ERC20InvalidCap(uint256 cap);
constructor(uint256 cap_) {
if (cap_ == 0) {
revert ERC20InvalidCap(0);
}
_cap = cap_;
}
function cap() public view virtual returns (uint256) {
return _cap;
}
function _update(
address from,
address to,
uint256 value
) internal virtual override {
// 3. Move the cap check before the _update
if (from == address(0)) {
uint256 maxSupply = cap();
uint256 supply = totalSupply();
if (supply > maxSupply) {
revert ERC20ExceededCap(supply, maxSupply);
}
}
super._update(from, to, value);
}
}
contract MyERC20CappedModified is ERC20CappedModified {
constructor(
string memory _name,
string memory _symbol,
uint256 _cap
) ERC20(_name, _symbol) ERC20CappedModified(_cap) {
_mint(msg.sender, _cap);
}
}
// test/MyERC20CappedModified.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {MyERC20Capped} from "../src/MyERC20Capped.sol";
contract MyERC20CappedModifiedTest is Test {
MyERC20Capped public token;
function setUp() public {
token = new MyERC20Capped("MyERC20Capped", "MTK", 1000);
}
function testTransfer() public {
token.transfer(address(this), 100);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment