Creating an ERC-20 contract
Now that we have ERC-20 in our codebase, lets extend it to create our own ERC-20 contract
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.13;
import "forge-std/Script.sol";import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 { uint256 public number;
constructor() ERC20("MyToken", "MTK") {} function mint(address to, uint256 amount) public { _mint(to, amount); }}
Positive tests
Add tests for the standard functions in an ERC-20 contract
- test minting
- Test transfers
- Test approvals
-
Solution
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.13;import {Test, console} from "forge-std/Test.sol";import { MyToken } from "../src/Token.sol";contract CounterTest is Test {MyToken public token;function setUp() public {token = new MyToken();}function test_Increment() public {token.mint(address(this), 100);assertEq(token.balanceOf(address(this)), 100);token.mint(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);assertEq(token.balanceOf(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD), 100);}function test_Transfer() public {token.mint(address(this), 100);token.transfer(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);assertEq(token.balanceOf(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD), 100);assertEq(token.balanceOf(address(this)), 0);}function test_Approve() public {token.mint(address(this), 100);token.approve(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);uint amount = token.allowance(address(this), 0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD);assertEq(amount, 100);vm.prank(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD);token.transferFrom(address(this), 0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);assertEq(token.balanceOf(address(this)), 0);assertEq(token.balanceOf(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD), 100);}}
Negative tests
Add tests that expect failures
- User tries to spend tokens they dont have
- User tries to spend on someone elses behalf what they dont have
-
Solution
function testFail_Mint() public {assertEq(token.balanceOf(address(this)), 200);token.mint(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);assertEq(token.balanceOf(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD), 200);}function testFail_Transfer() public {token.transfer(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);}function testFail_Allowance() public {token.mint(address(this), 100);vm.prank(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD);token.transferFrom(address(this), 0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD, 100);assertEq(token.balanceOf(address(this)), 0);assertEq(token.balanceOf(0x075c299cf3b9FCF7C9fD5272cd2ed21A4688bEeD), 100);}
Class tests
// SPDX-License-Identifier: Unlicensepragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "src/KiratCoin.sol";
contract TestKiratCoin is Test {
KiratCoin c;
function setUp() public { c = new KiratCoin(); }
function testMint() public { c.mint(address(this), 100); assertEq(c.balanceOf(address(this)), 100 ,"ok"); assertEq(c.balanceOf(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), uint256(0) ,"ok");
c.mint(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 100); assertEq(c.balanceOf(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 100, "ok"); }
function testTransfer() public { c.mint(address(this), 100); c.transfer(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 50);
assertEq(c.balanceOf(address(this)), 50); assertEq(c.balanceOf(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 50);
vm.prank(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f); c.transfer(address(this), 50);
assertEq(c.balanceOf(address(this)), 100); assertEq(c.balanceOf(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 0); }
function testApprovals() public { c.mint(address(this), 100);
c.approve(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 10);
assertEq(c.allowance(address(this), 0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 10); assertEq(c.allowance(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, address(this)), 0);
vm.prank(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f); c.transferFrom(address(this), 0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 5);
assertEq(c.balanceOf(address(this)), 95, "ok"); assertEq(c.balanceOf(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 5, "ok"); assertEq(c.allowance(address(this), 0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f), 5); }
function testFailApprovals() public { c.mint(address(this), 100); c.approve(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 10);
vm.prank(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f); c.transferFrom(address(this), 0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 100); }
function testFailTransfer() public { c.mint(address(this), 20); c.transfer(0x587EFaEe4f308aB2795ca35A27Dff8c1dfAF9e3f, 100); }
}