diff --git a/.gitmodules b/.gitmodules index 888d42d..690924b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/openzeppelin-contracts"] + path = lib/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/env.example b/env.example new file mode 100644 index 0000000..c29f3a6 --- /dev/null +++ b/env.example @@ -0,0 +1,3 @@ +SEPOLIA_RPC_URL= +ETHERSCAN_API_KEY= +ACCOUNT= \ No newline at end of file diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts new file mode 160000 index 0000000..dbb6104 --- /dev/null +++ b/lib/openzeppelin-contracts @@ -0,0 +1 @@ +Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 diff --git a/makefile b/makefile new file mode 100644 index 0000000..b942336 --- /dev/null +++ b/makefile @@ -0,0 +1,12 @@ +-include .env + +ANVIL_PRIVATE_KEY := 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + +NETWORK_ARGS := --rpc-url http://localhost:8545 --private-key $(ANVIL_PRIVATE_KEY) --broadcast + +ifeq ($(findstring --network sepolia,$(ARGS)),--network sepolia) + NETWORK_ARGS := --rpc-url $(SEPOLIA_RPC_URL) --account $(ACCOUNT) --broadcast --verify --etherscan-api-key $(ETHERSCAN_API_KEY) -vvvv +endif + +deploy: + @forge script script/DeployMyToken.s.sol:DeployMyToken $(NETWORK_ARGS) diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/script/DeployMyToken.s.sol b/script/DeployMyToken.s.sol new file mode 100644 index 0000000..56ae562 --- /dev/null +++ b/script/DeployMyToken.s.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Script} from "forge-std/Script.sol"; +import {MyToken} from "src/MyToken.sol"; + +contract DeployMyToken is Script { + uint256 public constant INITIAL_SUPPLY = 10000 ether; + + function run() external returns (MyToken) { + vm.startBroadcast(); + MyToken myToken = new MyToken(INITIAL_SUPPLY); + vm.stopBroadcast(); + return myToken; + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/MyToken.sol b/src/MyToken.sol new file mode 100644 index 0000000..f7fcba6 --- /dev/null +++ b/src/MyToken.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20 { + constructor(uint256 initialSupply) ERC20("My Token", "MTK") { + _mint(msg.sender, initialSupply); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/test/MyTokenTest.t.sol b/test/MyTokenTest.t.sol new file mode 100644 index 0000000..8256b23 --- /dev/null +++ b/test/MyTokenTest.t.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Test} from "forge-std/Test.sol"; +import {MyToken} from "src/MyToken.sol"; +import {DeployMyToken} from "script/DeployMyToken.s.sol"; +import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; + +contract MyTokenTest is Test { + MyToken myToken; + DeployMyToken deployer; + uint256 public constant INITIAL_SUPPLY = 10000 ether; + address public constant USER = address(1); + address public constant SPENDER = address(2); + + function setUp() external { + deployer = new DeployMyToken(); + myToken = deployer.run(); + } + + function test_setUp() public view { + assertEq(myToken.balanceOf(msg.sender), INITIAL_SUPPLY); + } + + // Direct transfer + function test_transfer() public { + uint256 balSenderBefore = myToken.balanceOf(msg.sender); + uint256 balUserBefore = myToken.balanceOf(USER); + uint256 transferAmount = 10 ether; + + vm.prank(msg.sender); + myToken.transfer(USER, transferAmount); + + uint256 balSenderAfter = myToken.balanceOf(msg.sender); + uint256 balUserAfter = myToken.balanceOf(USER); + + assertEq(balSenderAfter, balSenderBefore - transferAmount); + assertEq(balUserAfter, balUserBefore + transferAmount); + } + + // Indirect transfer + function test_transferFrom() public { + uint256 balSenderBefore = myToken.balanceOf(msg.sender); + uint256 balUserBefore = myToken.balanceOf(USER); + uint256 transferAmount = 10 ether; + + // Revert if not approve + vm.expectRevert( + abi.encodeWithSelector(IERC20Errors.ERC20InsufficientAllowance.selector, SPENDER, 0, transferAmount) + ); + vm.prank(SPENDER); + myToken.transferFrom(msg.sender, USER, transferAmount); + + // Actual Transactions + vm.prank(msg.sender); + myToken.approve(SPENDER, transferAmount); + + vm.prank(SPENDER); + myToken.transferFrom(msg.sender, USER, transferAmount); + + uint256 balSenderAfter = myToken.balanceOf(msg.sender); + uint256 balUserAfter = myToken.balanceOf(USER); + + assertEq(balSenderAfter, balSenderBefore - transferAmount); + assertEq(balUserAfter, balUserBefore + transferAmount); + } +}