Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions script/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
pragma solidity ^0.8.13;

import {Script} from "forge-std/Script.sol";
import {ERC20} from "../src/ERC20/ERC20/ERC20.sol";
import {ERC20Facet} from "../src/ERC20/ERC20/ERC20Facet.sol";

contract CounterScript is Script {
ERC20 public erc20;
ERC20Facet public erc20;

function setUp() public {}

function run() public {
vm.startBroadcast();

erc20 = new ERC20();
erc20 = new ERC20Facet();

vm.stopBroadcast();
}
Expand Down
104 changes: 104 additions & 0 deletions src/ERC20/ERC20/ERC20Facet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ contract ERC20Facet {
/// @param _spender Invalid spender address.
error ERC20InvalidSpender(address _spender);

/// @notice Thrown when a permit signature is invalid or expired.
/// @param _owner The address that signed the permit.
/// @param _spender The address that was approved.
/// @param _value The amount that was approved.
/// @param _deadline The deadline for the permit.
/// @param _v The recovery byte of the signature.
/// @param _r The r value of the signature.
/// @param _s The s value of the signature.
error ERC2612InvalidSignature(address _owner, address _spender, uint256 _value, uint256 _deadline, uint8 _v, bytes32 _r, bytes32 _s);


/// @notice Emitted when an approval is made for a spender by an owner.
/// @param _owner The address granting the allowance.
Expand Down Expand Up @@ -61,6 +71,7 @@ contract ERC20Facet {
uint256 totalSupply;
mapping(address owner => uint256 balance) balanceOf;
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
mapping(address owner => uint256) nonces;
}

/**
Expand Down Expand Up @@ -126,6 +137,7 @@ contract ERC20Facet {
return getStorage().allowances[_owner][_spender];
}



/**
* @notice Approves a spender to transfer up to a certain amount of tokens on behalf of the caller.
Expand Down Expand Up @@ -234,4 +246,96 @@ contract ERC20Facet {
}
emit Transfer(msg.sender, address(0), _value);
}

// EIP-2612 Permit Extension

/**
* @notice Returns the current nonce for an owner.
* @dev This value changes each time a permit is used.
* @param _owner The address of the owner.
* @return The current nonce.
*/
function nonces(address _owner) external view returns (uint256) {
return getStorage().nonces[_owner];
}

/**
* @notice Returns the domain separator used in the encoding of the signature for {permit}.
* @dev This value is unique to a contract and chain ID combination to prevent replay attacks.
* @return The domain separator.
*/
function DOMAIN_SEPARATOR() external view returns (bytes32) {
return keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(getStorage().name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}

/**
* @notice Sets the allowance for a spender via a signature.
* @dev This function implements EIP-2612 permit functionality.
* @param _owner The address of the token owner.
* @param _spender The address of the spender.
* @param _value The amount of tokens to approve.
* @param _deadline The deadline for the permit (timestamp).
* @param _v The recovery byte of the signature.
* @param _r The r value of the signature.
* @param _s The s value of the signature.
*/
function permit(
address _owner,
address _spender,
uint256 _value,
uint256 _deadline,
uint8 _v,
bytes32 _r,
bytes32 _s
) external {
if (block.timestamp > _deadline) {
revert ERC2612InvalidSignature(_owner, _spender, _value, _deadline, _v, _r, _s);
}

ERC20Storage storage s = getStorage();
uint256 currentNonce = s.nonces[_owner];
bytes32 structHash = keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
_owner,
_spender,
_value,
currentNonce,
_deadline
)
);

bytes32 hash = keccak256(
abi.encodePacked(
"\x19\x01",
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(s.name)),
keccak256("1"),
block.chainid,
address(this)
)
),
structHash
)
);

address signer = ecrecover(hash, _v, _r, _s);
if (signer != _owner || signer == address(0)) {
revert ERC2612InvalidSignature(_owner, _spender, _value, _deadline, _v, _r, _s);
}

s.allowances[_owner][_spender] = _value;
s.nonces[_owner] = currentNonce + 1;
emit Approval(_owner, _spender, _value);
}
}
45 changes: 41 additions & 4 deletions src/ERC20/ERC20/libraries/LibERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ library LibERC20 {
/// @param _value The amount of tokens transferred.
event Transfer(address indexed _from, address indexed _to, uint256 _value);

/// @notice Emitted when an approval is made for a spender by an owner.
/// @param _owner The address granting the allowance.
/// @param _spender The address receiving the allowance.
/// @param _value The amount approved.
event Approval(address indexed _owner, address indexed _spender, uint256 _value);


/// @notice Storage slot identifier, defined using keccak256 hash of the library diamond storage identifier.
bytes32 constant STORAGE_POSITION = keccak256("compose.erc20");
Expand All @@ -47,6 +53,7 @@ library LibERC20 {
uint256 totalSupply;
mapping(address owner => uint256 balance) balanceOf;
mapping(address owner => mapping(address spender => uint256 allowance)) allowances;
mapping(address owner => uint256) nonces;
}


Expand All @@ -61,7 +68,7 @@ library LibERC20 {
}

/// @notice Mints new tokens to a specified address.
/// @dev Increases both total supply and the recipients balance.
/// @dev Increases both total supply and the recipient's balance.
/// @param _account The address receiving the newly minted tokens.
/// @param _value The number of tokens to mint.
function mint(address _account, uint256 _value) internal {
Expand All @@ -77,7 +84,7 @@ library LibERC20 {
}

/// @notice Burns tokens from a specified address.
/// @dev Decreases both total supply and the senders balance.
/// @dev Decreases both total supply and the sender's balance.
/// @param _account The address whose tokens will be burned.
/// @param _value The number of tokens to burn.
function burn(address _account, uint256 _value) internal {
Expand All @@ -97,7 +104,7 @@ library LibERC20 {
}

/// @notice Transfers tokens from one address to another using an allowance.
/// @dev Deducts the spenders allowance and updates balances.
/// @dev Deducts the spender's allowance and updates balances.
/// @param _from The address to send tokens from.
/// @param _to The address to send tokens to.
/// @param _value The number of tokens to transfer.
Expand All @@ -124,4 +131,34 @@ library LibERC20 {
}
emit Transfer(_from, _to, _value);
}
}

/// @notice Transfers tokens from the caller to another address.
/// @dev Updates balances directly without allowance mechanism.
/// @param _to The address to send tokens to.
/// @param _value The number of tokens to transfer.
function transfer(address _to, uint256 _value) internal {
ERC20Storage storage s = getStorage();
if (_to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
uint256 fromBalance = s.balanceOf[msg.sender];
if (fromBalance < _value) {
revert ERC20InsufficientBalance(msg.sender, fromBalance, _value);
}
unchecked {
s.balanceOf[msg.sender] = fromBalance - _value;
s.balanceOf[_to] += _value;
}
emit Transfer(msg.sender, _to, _value);
}

/// @notice Approves a spender to transfer tokens on behalf of the caller.
/// @dev Sets the allowance for the spender.
/// @param _spender The address to approve for spending.
/// @param _value The amount of tokens to approve.
function approve(address _spender, uint256 _value) internal {
ERC20Storage storage s = getStorage();
s.allowances[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
}
}
6 changes: 3 additions & 3 deletions test/ERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import {ERC20} from "../src/ERC20/ERC20/ERC20.sol";
import {ERC20Facet} from "../src/ERC20/ERC20/ERC20Facet.sol";

contract CounterTest is Test {
ERC20 public erc20;
ERC20Facet public erc20;

function setUp() public {
erc20 = new ERC20();
erc20 = new ERC20Facet();
//erc20.setNumber(0);
}

Expand Down
Loading