// test/TokenProxy.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Test, console2} from "forge-std/Test.sol";
import {TokenProxy} from "../src/TokenProxy.sol";
import {IERC20} from "../src/IERC20.sol";
import {IERC20Metadata} from "../src/IERC20Metadata.sol";
// Mock ERC20 for testing
contract MockERC20 is IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() external view returns (string memory) {
return _name;
}
function symbol() external view returns (string memory) {
return _symbol;
}
function decimals() external pure returns (uint8) {
return 18;
}
function totalSupply() external view returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) external view returns (uint256) {
return _balances[account];
}
function transfer(address to, uint256 amount) external returns (bool) {
_balances[msg.sender] -= amount;
_balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function allowance(
address owner,
address spender
) external view returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) external returns (bool) {
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool) {
_allowances[from][msg.sender] -= amount;
_balances[from] -= amount;
_balances[to] += amount;
emit Transfer(from, to, amount);
return true;
}
// Helper function to mint tokens for testing
function mint(address to, uint256 amount) external {
_balances[to] += amount;
_totalSupply += amount;
emit Transfer(address(0), to, amount);
}
}
contract TokenProxyTest is Test {
TokenProxy public proxy;
address public mockToken;
address public user1;
address public user2;
function setUp() public {
mockToken = address(new MockERC20("Mock Token", "MTK"));
proxy = new TokenProxy(mockToken);
user1 = makeAddr("user1");
user2 = makeAddr("user2");
// Mint some tokens to user1
MockERC20(mockToken).mint(user1, 1000 ether);
// Slot 2: _totalSupply
vm.store(
address(proxy),
bytes32(uint256(2)),
vm.load(mockToken, bytes32(uint256(2)))
);
// Slot 3: _name
vm.store(
address(proxy),
bytes32(uint256(3)),
vm.load(mockToken, bytes32(uint256(3)))
);
// Slot 4: _symbol
vm.store(
address(proxy),
bytes32(uint256(4)),
vm.load(mockToken, bytes32(uint256(4)))
);
}
function testInitialState() public {
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("name()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), "Mock Token");
(success, data) = address(proxy).call(
abi.encodeWithSignature("symbol()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), "MTK");
(success, data) = address(proxy).call(
abi.encodeWithSignature("decimals()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint8)), 18);
(success, data) = address(proxy).call(
abi.encodeWithSignature("totalSupply()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint256)), 1000 ether);
}
function testSetName() public {
string memory newName = "New Token Name";
proxy.setName(newName);
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("name()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), newName);
}
function testSetSymbol() public {
string memory newSymbol = "NTK";
proxy.setSymbol(newSymbol);
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("symbol()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), newSymbol);
}
function testSetDecimals() public {
uint8 newDecimals = 6;
proxy.setDecimals(newDecimals);
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("decimals()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint8)), newDecimals);
}
function testSetTotalSupply() public {
uint256 newTotalSupply = 2000 ether;
proxy.setTotalSupply(newTotalSupply);
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("totalSupply()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint256)), newTotalSupply);
}
function testMetadataOverride() public {
// Set custom values
string memory newName = "Custom Token";
string memory newSymbol = "CTK";
uint8 newDecimals = 8;
uint256 newTotalSupply = 5000 ether;
proxy.setName(newName);
proxy.setSymbol(newSymbol);
proxy.setDecimals(newDecimals);
proxy.setTotalSupply(newTotalSupply);
// Verify all custom values are set
(bool success, bytes memory data) = address(proxy).call(
abi.encodeWithSignature("name()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), newName);
(success, data) = address(proxy).call(
abi.encodeWithSignature("symbol()")
);
assertTrue(success);
assertEq(abi.decode(data, (string)), newSymbol);
(success, data) = address(proxy).call(
abi.encodeWithSignature("decimals()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint8)), newDecimals);
(success, data) = address(proxy).call(
abi.encodeWithSignature("totalSupply()")
);
assertTrue(success);
assertEq(abi.decode(data, (uint256)), newTotalSupply);
}
function testBalanceOf() public {
// Set custom balance for user1
proxy.setBalance(user1, 100 ether);
// Check balance
assertEq(proxy.balanceOf(user1), 100 ether);
}
function testTransfer() public {
// Set initial balances
proxy.setBalance(user1, 100 ether);
assertEq(proxy.balanceOf(user1), 100 ether);
// Mock transfer event
vm.expectEmit(true, true, false, true);
emit Transfer(user1, user2, 50 ether);
// Transfer tokens
vm.prank(user1);
(bool success, ) = address(proxy).call(
abi.encodeWithSignature(
"transfer(address,uint256)",
user2,
50 ether
)
);
assertTrue(success);
// Check balances
assertEq(proxy.balanceOf(user1), 50 ether);
assertEq(proxy.balanceOf(user2), 50 ether);
}
function testAllowance() public {
// Set initial balance
proxy.setBalance(user1, 100 ether);
// Mock approval event
vm.expectEmit(true, true, false, true);
emit Approval(user1, user2, 50 ether);
// Approve spending
vm.prank(user1);
(bool success, ) = address(proxy).call(
abi.encodeWithSignature("approve(address,uint256)", user2, 50 ether)
);
assertTrue(success);
// Check allowance
assertEq(proxy.allowance(user1, user2), 50 ether);
}
function testTransferFrom() public {
// Set initial balances
proxy.setBalance(user1, 100 ether);
// Approve spending
vm.prank(user1);
(bool success, ) = address(proxy).call(
abi.encodeWithSignature("approve(address,uint256)", user2, 50 ether)
);
assertTrue(success);
// Mock transfer event
vm.expectEmit(true, true, false, true);
emit Transfer(user1, user2, 30 ether);
// Transfer from
vm.prank(user2);
(success, ) = address(proxy).call(
abi.encodeWithSignature(
"transferFrom(address,address,uint256)",
user1,
user2,
30 ether
)
);
assertTrue(success);
// Check balances and allowance
assertEq(proxy.balanceOf(user1), 70 ether);
assertEq(proxy.balanceOf(user2), 30 ether);
assertEq(proxy.allowance(user1, user2), 20 ether);
}
// // Fuzz test for balanceOf
function testFuzzBalanceOf(address user, uint256 amount) public {
proxy.setBalance(user, amount);
assertEq(proxy.balanceOf(user), amount);
}
// Events for testing
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}