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
58 changes: 58 additions & 0 deletions test/Base.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30 <0.9.0;

import {StdCheats} from "forge-std/StdCheats.sol";
import {StdAssertions} from "forge-std/StdAssertions.sol";

import {Constants} from "./utils/Constants.sol";
import {Defaults} from "./utils/Defaults.sol";
import {Modifiers} from "./utils/Modifiers.sol";
import {Users} from "./utils/Types.sol";

abstract contract Base_Test is Constants, Modifiers, StdAssertions, StdCheats {
/*//////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////*/

Users internal users;

/*//////////////////////////////////////////////////////////////
TEST CONTRACTS
//////////////////////////////////////////////////////////////*/

Defaults internal defaults;

/*//////////////////////////////////////////////////////////////
SET-UP FUNCTION
//////////////////////////////////////////////////////////////*/

function setUp() public virtual {
defaults = new Defaults();

createTestUsers();
defaults.setUsers(users);

setVariables(defaults, users); // set in modifier contract

setMsgSender(users.alice); // alice default caller
}

/*//////////////////////////////////////////////////////////////
HELPERS
//////////////////////////////////////////////////////////////*/

function createUser(string memory name) internal returns (address payable user) {
user = payable(makeAddr(name)); // label implicitly created via Foundry
vm.deal({account: user, newBalance: 100 ether});
}

function createTestUsers() internal {
users.alice = createUser("Alice");
users.bob = createUser("Bob");
users.charlee = createUser("Charlee");

users.admin = createUser("Admin");
users.receiver = createUser("Receiver");
users.sender = createUser("Sender");
}
}
52 changes: 52 additions & 0 deletions test/harnesses/token/ERC20/ERC20/ERC20BurnFacetHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {ERC20BurnFacet} from "src/token/ERC20/ERC20/ERC20BurnFacet.sol";

/**
* @title ERC20BurnFacetHarness
* @notice Test harness for ERC20BurnFacet that adds initialization and minting for testing
*/
contract ERC20BurnFacetHarness is ERC20BurnFacet {
event Approval(address indexed _owner, address indexed _spender, uint256 _value);

/**
* @notice ERC20 view helpers so tests can call the standard API
*/
function balanceOf(address _account) external view returns (uint256) {
return getStorage().balanceOf[_account];
}

function totalSupply() external view returns (uint256) {
return getStorage().totalSupply;
}

function allowance(address _owner, address _spender) external view returns (uint256) {
return getStorage().allowance[_owner][_spender];
}

/**
* @notice Minimal approve implementation for tests (writes into the same storage used by burnFrom)
*/
function approve(address _spender, uint256 _value) external returns (bool) {
require(_spender != address(0), "ERC20: approve to zero address");
ERC20Storage storage s = getStorage();
s.allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}

/**
* @notice Mint tokens to an address
* @dev Only used for testing - exposes internal mint functionality
*/
function mint(address _to, uint256 _value) external {
ERC20Storage storage s = getStorage();
require(_to != address(0), "ERC20: mint to zero address");
unchecked {
s.totalSupply += _value;
s.balanceOf[_to] += _value;
}
emit Transfer(address(0), _to, _value);
}
}
37 changes: 37 additions & 0 deletions test/harnesses/token/ERC20/ERC20/ERC20FacetHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {ERC20Facet} from "src/token/ERC20/ERC20/ERC20Facet.sol";

/**
* @title ERC20FacetHarness
* @notice Test harness for ERC20Facet that adds initialization and minting for testing
*/
contract ERC20FacetHarness is ERC20Facet {
/**
* @notice Initialize the ERC20 token storage
* @dev Only used for testing - production diamonds should initialize in constructor
*/
function initialize(string memory _name, string memory _symbol, uint8 _decimals) external {
ERC20Storage storage s = getStorage();
s.name = _name;
s.symbol = _symbol;
s.decimals = _decimals;
}

/**
* @notice Mint tokens to an address
* @dev Only used for testing - exposes internal mint functionality
*/
function mint(address _to, uint256 _value) external {
ERC20Storage storage s = getStorage();
if (_to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
unchecked {
s.totalSupply += _value;
s.balanceOf[_to] += _value;
}
emit Transfer(address(0), _to, _value);
}
}
84 changes: 84 additions & 0 deletions test/harnesses/token/ERC20/ERC20/ERC20Harness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

/**
* @title ERC20Harness
* @notice Test harness that exposes LibERC20's internal functions as external
* @dev Required for testing since LibERC20 only has internal functions
*/
contract ERC20Harness {
/**
* @notice Initialize the ERC20 token storage
* @dev Only used for testing
*/
function initialize(string memory _name, string memory _symbol, uint8 _decimals) external {
ERC20Mod.ERC20Storage storage s = ERC20Mod.getStorage();
s.name = _name;
s.symbol = _symbol;
s.decimals = _decimals;
}

/**
* @notice Exposes ERC20Mod.mint as an external function
*/
function mint(address _account, uint256 _value) external {
ERC20Mod.mint(_account, _value);
}

/**
* @notice Exposes ERC20Mod.burn as an external function
*/
function burn(address _account, uint256 _value) external {
ERC20Mod.burn(_account, _value);
}

/**
* @notice Exposes ERC20Mod.transferFrom as an external function
*/
function transferFrom(address _from, address _to, uint256 _value) external {
ERC20Mod.transferFrom(_from, _to, _value);
}

/**
* @notice Exposes ERC20Mod.transfer as an external function
*/
function transfer(address _to, uint256 _value) external {
ERC20Mod.transfer(_to, _value);
}

/**
* @notice Exposes ERC20Mod.approve as an external function
*/
function approve(address _spender, uint256 _value) external {
ERC20Mod.approve(_spender, _value);
}

/**
* @notice Get storage values for testing
*/
function name() external view returns (string memory) {
return ERC20Mod.getStorage().name;
}

function symbol() external view returns (string memory) {
return ERC20Mod.getStorage().symbol;
}

function decimals() external view returns (uint8) {
return ERC20Mod.getStorage().decimals;
}

function totalSupply() external view returns (uint256) {
return ERC20Mod.getStorage().totalSupply;
}

function balanceOf(address _account) external view returns (uint256) {
return ERC20Mod.getStorage().balanceOf[_account];
}

function allowance(address _owner, address _spender) external view returns (uint256) {
return ERC20Mod.getStorage().allowance[_owner][_spender];
}
}
81 changes: 81 additions & 0 deletions test/harnesses/token/ERC20/ERC20/ERC20PermitFacetHarness.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {ERC20PermitFacet} from "src/token/ERC20/ERC20Permit/ERC20PermitFacet.sol";

/**
* @title ERC20PermitFacetHarness
* @notice Test harness for ERC20PermitFacet that adds initialization and minting for testing
*/
contract ERC20PermitFacetHarness is ERC20PermitFacet {
event Transfer(address indexed _from, address indexed _to, uint256 _value);

/**
* @notice Initialize the ERC20 token storage
* @dev Only used for testing - production diamonds should initialize in constructor
*/
function initialize(string memory _name) external {
ERC20Storage storage s = getERC20Storage();
s.name = _name;
}

/**
* @notice Mint tokens to an address
* @dev Only used for testing - exposes internal mint functionality
*/
function mint(address _to, uint256 _value) external {
ERC20Storage storage s = getERC20Storage();
require(_to != address(0), "ERC20: mint to zero address");
unchecked {
s.totalSupply += _value;
s.balanceOf[_to] += _value;
}
emit Transfer(address(0), _to, _value);
}

/**
* @notice ERC20 view helpers so tests can call the standard API
*/
function balanceOf(address _account) external view returns (uint256) {
return getERC20Storage().balanceOf[_account];
}

function totalSupply() external view returns (uint256) {
return getERC20Storage().totalSupply;
}

function allowance(address _owner, address _spender) external view returns (uint256) {
return getERC20Storage().allowance[_owner][_spender];
}

/**
* @notice Minimal approve implementation for tests
*/
function approve(address _spender, uint256 _value) external returns (bool) {
require(_spender != address(0), "ERC20: approve to zero address");
ERC20Storage storage s = getERC20Storage();
s.allowance[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}

/**
* @notice TransferFrom implementation for tests (needed by test_Permit_ThenTransferFrom)
*/
function transferFrom(address _from, address _to, uint256 _value) external returns (bool) {
ERC20Storage storage s = getERC20Storage();
require(_to != address(0), "ERC20: transfer to zero address");
require(s.balanceOf[_from] >= _value, "ERC20: insufficient balance");

uint256 currentAllowance = s.allowance[_from][msg.sender];
require(currentAllowance >= _value, "ERC20: insufficient allowance");

unchecked {
s.allowance[_from][msg.sender] = currentAllowance - _value;
s.balanceOf[_from] -= _value;
}
s.balanceOf[_to] += _value;
emit Transfer(_from, _to, _value);
return true;
}
}
35 changes: 35 additions & 0 deletions test/unit/concrete/token/ERC20/ERC20/metadata.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "test/Base.t.sol";
import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol";

import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Metadata_ERC20Mod_Concrete_Unit_Test is Base_Test {
ERC20Harness internal harness;

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
harness.initialize(TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS);
}

function test_Name() external view {
assertEq(harness.name(), "Test Token");
}

function test_Symbol() external view {
assertEq(harness.symbol(), "TEST");
}

function test_Decimals() external view {
assertEq(harness.decimals(), 18);
}

function test_InitialTotalSupply() external view {
assertEq(harness.totalSupply(), 0);
}
}
36 changes: 36 additions & 0 deletions test/unit/fuzz/token/ERC20/ERC20/mod/approve.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.30;

import {stdError} from "forge-std/StdError.sol";
import {Base_Test} from "test/Base.t.sol";
import {ERC20Harness} from "test/harnesses/token/ERC20/ERC20/ERC20Harness.sol";

import "src/token/ERC20/ERC20/ERC20Mod.sol" as ERC20Mod;

contract Approve_ERC20Mod_Fuzz_Unit_Test is Base_Test {
ERC20Harness internal harness;

event Approval(address indexed _owner, address indexed _spender, uint256 _value);

function setUp() public override {
Base_Test.setUp();

harness = new ERC20Harness();
}

function testFuzz_ShouldRevert_SpenderIsZeroAddress(uint256 value) external {
vm.expectRevert(abi.encodeWithSelector(ERC20Mod.ERC20InvalidSpender.selector, ADDRESS_ZERO));
harness.approve(ADDRESS_ZERO, value);
}

function testFuzz_Approve(address spender, uint256 value) external whenSpenderNotZeroAddress {
vm.assume(spender != ADDRESS_ZERO);

vm.expectEmit(address(harness));
emit Approval(users.alice, spender, value);
harness.approve(spender, value);

assertEq(harness.allowance(users.alice, spender), value);
}
}

6 changes: 6 additions & 0 deletions test/unit/fuzz/token/ERC20/ERC20/mod/approve.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Approve_ERC20Mod_Fuzz_Unit_Test
├── when the spender is the zero address
│ └── it should revert
└── when the spender is not the zero address
├── it should set the spender's allowance from the caller
└── it should emit an {Approval} event
Loading