diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol index f205383..23cdf76 100644 --- a/contracts/ERC20.sol +++ b/contracts/ERC20.sol @@ -4,8 +4,8 @@ pragma solidity ^0.8.7; import { IERC20 } from "./interfaces/IERC20.sol"; /** - * @title Modern and gas efficient ERC-20 implementation. - * @dev Acknowledgements to Solmate, OpenZeppelin, and DSS for inspiring this code. + * @title Modern ERC-20 implementation. + * @dev Acknowledgements to Solmate, OpenZeppelin, and DSS for inspiring this code. */ contract ERC20 is IERC20 { @@ -29,15 +29,14 @@ contract ERC20 is IERC20 { /****************/ // PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 amount,uint256 nonce,uint256 deadline)"); - bytes32 public constant override PERMIT_TYPEHASH = 0xfc77c2b9d30fe91687fd39abb7d16fcdfe1472d065740051ab8b13e4bf4a617f; - uint256 public constant S_VALUE_INCLUSIVE_UPPER_BOUND = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; + bytes32 public constant override PERMIT_TYPEHASH = 0xfc77c2b9d30fe91687fd39abb7d16fcdfe1472d065740051ab8b13e4bf4a617f; - mapping (address => uint256) public override nonces; + mapping(address => uint256) public override nonces; /** - * @param name_ The name of the token. - * @param symbol_ The symbol of the token. - * @param decimals_ The decimal precision used by the token. + * @param name_ The name of the token. + * @param symbol_ The symbol of the token. + * @param decimals_ The decimal precision used by the token. */ constructor(string memory name_, string memory symbol_, uint8 decimals_) { name = name_; @@ -69,8 +68,12 @@ contract ERC20 is IERC20 { // Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. - require(uint256(s) <= S_VALUE_INCLUSIVE_UPPER_BOUND && (v == 27 || v == 28), "ERC20:P:MALLEABLE"); - + require( + uint256(s) <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0 && + (v == 27 || v == 28), + "ERC20:P:MALLEABLE" + ); + // Nonce realistically cannot overflow. unchecked { bytes32 digest = keccak256( @@ -83,7 +86,7 @@ contract ERC20 is IERC20 { address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress == owner && owner != address(0), "ERC20:P:INVALID_SIGNATURE"); } - + _approve(owner, spender, amount); } @@ -126,10 +129,8 @@ contract ERC20 is IERC20 { balanceOf[owner_] -= amount_; // Cannot underflow because a user's balance will never be larger than the total supply. - unchecked { - totalSupply -= amount_; - } - + unchecked { totalSupply -= amount_; } + emit Transfer(owner_, address(0), amount_); } @@ -137,9 +138,7 @@ contract ERC20 is IERC20 { totalSupply += amount_; // Cannot overflow because totalSupply would first overflow in the statement above. - unchecked { - balanceOf[recipient_] += amount_; - } + unchecked { balanceOf[recipient_] += amount_; } emit Transfer(address(0), recipient_, amount_); } @@ -148,9 +147,7 @@ contract ERC20 is IERC20 { balanceOf[owner_] -= amount_; // Cannot overflow because minting prevents overflow of totalSupply, and sum of user balances == totalSupply. - unchecked { - balanceOf[recipient_] += amount_; - } + unchecked { balanceOf[recipient_] += amount_; } emit Transfer(owner_, recipient_, amount_); } diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 2e257c3..e9d524d 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -92,54 +92,60 @@ interface IERC20 { /**********************/ /** - * @dev Function that returns the allowance that one account has given another over their tokens. - * @param owner_ Account that tokens are approved from. - * @param spender_ Account that tokens are approved for. + * @dev Returns the allowance that one account has given another over their tokens. + * @param owner_ Account that tokens are approved from. + * @param spender_ Account that tokens are approved for. + * @return allowance_ Allowance that one account has given another over their tokens. */ function allowance(address owner_, address spender_) external view returns (uint256 allowance_); /** - * @dev Returns the amount of tokens owned by a given account. - * @param account_ Account that owns the tokens. + * @dev Returns the amount of tokens owned by a given account. + * @param account_ Account that owns the tokens. + * @return balance_ Amount of tokens owned by a given account. */ function balanceOf(address account_) external view returns (uint256 balance_); /** - * @dev Returns the decimal precision used by the token. + * @dev Returns the decimal precision used by the token. + * @return decimals_ The decimal precision used by the token. */ function decimals() external view returns (uint8 decimals_); /** - * @dev Returns the signature domain separator. - * @return domainSeparator_ The domain for the contract. + * @dev Returns the signature domain separator. + * @return domainSeparator_ The signature domain separator. */ function DOMAIN_SEPARATOR() external view returns (bytes32 domainSeparator_); /** - * @dev Returns the name of the token. + * @dev Returns the name of the token. + * @return name_ The name of the token. */ function name() external view returns (string memory name_); /** - * @dev Returns the nonce for the given owner. - * @param owner The address of the owner account. - * @return nonce_ The current nonce. + * @dev Returns the nonce for the given owner. + * @param owner The address of the owner account. + * @return nonce_ The nonce for the given owner. */ function nonces(address owner) external view returns (uint256 nonce_); /** * @dev Returns the permit type hash. - * @return hash_ The typehash for the commit. + * @return hash_ The permit type hash. */ function PERMIT_TYPEHASH() external view returns (bytes32 hash_); /** - * @dev Returns the symbol of the token. + * @dev Returns the symbol of the token. + * @return symbol_ The symbol of the token. */ function symbol() external view returns (string memory symbol_); /** - * @dev Returns the total amount of tokens in existence. + * @dev Returns the total amount of tokens in existence. + * @return totalSupply_ The total amount of tokens in existence. */ function totalSupply() external view returns (uint256 totalSupply_); diff --git a/contracts/test/ERC20.t.sol b/contracts/test/ERC20.t.sol index d29606a..6f38e95 100644 --- a/contracts/test/ERC20.t.sol +++ b/contracts/test/ERC20.t.sol @@ -179,6 +179,8 @@ contract ERC20PermitTest is TestUtils { bytes constant ARITHMETIC_ERROR = abi.encodeWithSignature("Panic(uint256)", 0x11); + uint256 constant S_VALUE_INCLUSIVE_UPPER_BOUND = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; + ERC20 token; ERC20User user; @@ -222,7 +224,7 @@ contract ERC20PermitTest is TestUtils { assertEq(token.nonces(owner), 1); } - function test_permitZeroAddress() external { + function test_permit_zeroAddress() external { uint256 amount = 10 * WAD; ( uint8 v, bytes32 r, bytes32 s ) = _getValidPermitSignature(amount, owner, skOwner, deadline); @@ -230,7 +232,7 @@ contract ERC20PermitTest is TestUtils { user.erc20_permit(address(token), address(0), spender, amount, deadline, v, r, s); } - function test_permitNonOwnerAddress() external { + function test_permit_nonOwnerAddress() external { uint256 amount = 10 * WAD; ( uint8 v, bytes32 r, bytes32 s ) = _getValidPermitSignature(amount, owner, skOwner, deadline); @@ -244,7 +246,7 @@ contract ERC20PermitTest is TestUtils { user.erc20_permit(address(token), owner, spender, amount, deadline, v, r, s); } - function test_permitWithExpiry() external { + function test_permit_withExpiry() external { uint256 amount = 10 * WAD; uint256 expiry = 482112000 + 1 hours; @@ -271,7 +273,7 @@ contract ERC20PermitTest is TestUtils { assertEq(token.nonces(owner), 1); } - function test_permitReplay() external { + function test_permit_replay() external { uint256 amount = 10 * WAD; ( uint8 v, bytes32 r, bytes32 s ) = _getValidPermitSignature(amount, owner, skOwner, deadline); @@ -283,23 +285,23 @@ contract ERC20PermitTest is TestUtils { user.erc20_permit(address(token), owner, spender, amount, deadline, v, r, s); } - function test_permitBadS() external { + function test_permit_badS() external { uint256 amount = 10 * WAD; ( uint8 v, bytes32 r, bytes32 s ) = _getValidPermitSignature(amount, owner, skOwner, deadline); // Send in an s that is above the upper bound. - bytes32 badS = bytes32(token.S_VALUE_INCLUSIVE_UPPER_BOUND() + 1); + bytes32 badS = bytes32(S_VALUE_INCLUSIVE_UPPER_BOUND + 1); vm.expectRevert(bytes("ERC20:P:MALLEABLE")); user.erc20_permit(address(token), owner, spender, amount, deadline, v, r, badS); user.erc20_permit(address(token), owner, spender, amount, deadline, v, r, s); } - function test_permitBadV() external { + function test_permit_badV() external { uint256 amount = 10 * WAD; // Get valid signature. The `v` value is the expected v value that will cause `permit` to succeed, and must be 27 or 28. - // Any other value should fail. + // Any other value should fail. // If v is 27, then 28 should make it past the MALLEABLE require, but should result in an invalid signature, and vice versa when v is 28. ( uint8 v, bytes32 r, bytes32 s ) = _getValidPermitSignature(amount, owner, skOwner, deadline); diff --git a/contracts/test/mocks/MockERC20.sol b/contracts/test/mocks/MockERC20.sol index 861006f..faee207 100644 --- a/contracts/test/mocks/MockERC20.sol +++ b/contracts/test/mocks/MockERC20.sol @@ -5,7 +5,7 @@ import { ERC20 } from "../../ERC20.sol"; contract MockERC20 is ERC20 { - constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_, decimals_) {} + constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_, decimals_) { } function mint(address to_, uint256 value_) external { _mint(to_, value_);