Initial commit
This commit is contained in:
commit
c0fb01c7d7
39
.github/workflows/main.yml
vendored
Normal file
39
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
name: Foundry Testing
|
||||
run-name: Homework 1 Running by ${{ github.actor }}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
FOUNDRY_PROFILE: ci
|
||||
WORKFLOW_NAME: Foundry Test on ${{ github.ref_name }}
|
||||
|
||||
jobs:
|
||||
check:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
|
||||
name: Foundry Testing
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install Foundry
|
||||
uses: foundry-rs/foundry-toolchain@v1
|
||||
with:
|
||||
version: nightly
|
||||
|
||||
- name: Run Forge tests
|
||||
run: |
|
||||
cd hw1
|
||||
forge test --mt test_check -vvv
|
||||
id: test
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "hw1/lib/forge-std"]
|
||||
path = hw1/lib/forge-std
|
||||
url = https://github.com/foundry-rs/forge-std
|
||||
14
hw1/.gitignore
vendored
Normal file
14
hw1/.gitignore
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
# Compiler files
|
||||
cache/
|
||||
out/
|
||||
|
||||
# Ignores development broadcast logs
|
||||
!/broadcast
|
||||
/broadcast/*/31337/
|
||||
/broadcast/**/dry-run/
|
||||
|
||||
# Docs
|
||||
docs/
|
||||
|
||||
# Dotenv file
|
||||
.env
|
||||
66
hw1/README.md
Normal file
66
hw1/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
## Foundry
|
||||
|
||||
**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
|
||||
|
||||
Foundry consists of:
|
||||
|
||||
- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
|
||||
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
|
||||
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
|
||||
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://book.getfoundry.sh/
|
||||
|
||||
## Usage
|
||||
|
||||
### Build
|
||||
|
||||
```shell
|
||||
$ forge build
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
```shell
|
||||
$ forge test
|
||||
```
|
||||
|
||||
### Format
|
||||
|
||||
```shell
|
||||
$ forge fmt
|
||||
```
|
||||
|
||||
### Gas Snapshots
|
||||
|
||||
```shell
|
||||
$ forge snapshot
|
||||
```
|
||||
|
||||
### Anvil
|
||||
|
||||
```shell
|
||||
$ anvil
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```shell
|
||||
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
|
||||
```
|
||||
|
||||
### Cast
|
||||
|
||||
```shell
|
||||
$ cast <subcommand>
|
||||
```
|
||||
|
||||
### Help
|
||||
|
||||
```shell
|
||||
$ forge --help
|
||||
$ anvil --help
|
||||
$ cast --help
|
||||
```
|
||||
6
hw1/foundry.toml
Normal file
6
hw1/foundry.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[profile.default]
|
||||
src = "src"
|
||||
out = "out"
|
||||
libs = ["lib"]
|
||||
|
||||
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
|
||||
1
hw1/lib/forge-std
Submodule
1
hw1/lib/forge-std
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce
|
||||
12
hw1/script/Counter.s.sol
Normal file
12
hw1/script/Counter.s.sol
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {Script, console} from "forge-std/Script.sol";
|
||||
|
||||
contract CounterScript is Script {
|
||||
function setUp() public {}
|
||||
|
||||
function run() public {
|
||||
vm.broadcast();
|
||||
}
|
||||
}
|
||||
29
hw1/src/Classroom/Classroom.sol
Normal file
29
hw1/src/Classroom/Classroom.sol
Normal file
@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/* Problem 1 Interface & Contract */
|
||||
contract StudentV1 {
|
||||
// Note: You can declare some state variable
|
||||
|
||||
function register() external returns (uint256) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
}
|
||||
|
||||
/* Problem 2 Interface & Contract */
|
||||
interface IClassroomV2 {
|
||||
function isEnrolled() external view returns (bool);
|
||||
}
|
||||
|
||||
contract StudentV2 {
|
||||
function register() external view returns (uint256) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
}
|
||||
|
||||
/* Problem 3 Interface & Contract */
|
||||
contract StudentV3 {
|
||||
function register() external view returns (uint256) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
}
|
||||
25
hw1/src/Delegation/Delegation.sol
Normal file
25
hw1/src/Delegation/Delegation.sol
Normal file
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface ID31eg4t3 {
|
||||
function proxyCall(bytes calldata data) external returns (address);
|
||||
function changeResult() external;
|
||||
}
|
||||
|
||||
contract Attack {
|
||||
address internal immutable victim;
|
||||
// TODO: Declare some variable here
|
||||
// Note: Checkout the storage layout in victim contract
|
||||
|
||||
constructor(address addr) payable {
|
||||
victim = addr;
|
||||
}
|
||||
|
||||
// NOTE: You might need some malicious function here
|
||||
|
||||
function exploit() external {
|
||||
// TODO: Add your implementation here
|
||||
// Note: Make sure you know how delegatecall works
|
||||
// bytes memory data = ...
|
||||
}
|
||||
}
|
||||
76
hw1/src/LiaoToken/LiaoToken.sol
Normal file
76
hw1/src/LiaoToken/LiaoToken.sol
Normal file
@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IERC20 {
|
||||
function totalSupply() external view returns (uint256);
|
||||
function balanceOf(address account) external view returns (uint256);
|
||||
function transfer(address to, uint256 amount) external returns (bool);
|
||||
function transferFrom(address from, address to, uint256 value) external returns (bool);
|
||||
function approve(address spender, uint256 amount) external returns (bool);
|
||||
function allowance(address owner, address spender) external view returns (uint256);
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
}
|
||||
|
||||
contract LiaoToken is IERC20 {
|
||||
// TODO: you might need to declare several state variable here
|
||||
mapping(address account => uint256) private _balances;
|
||||
mapping(address account => bool) isClaim;
|
||||
|
||||
uint256 private _totalSupply;
|
||||
|
||||
string private _name;
|
||||
string private _symbol;
|
||||
|
||||
event Claim(address indexed user, uint256 indexed amount);
|
||||
|
||||
constructor(string memory name_, string memory symbol_) payable {
|
||||
_name = name_;
|
||||
_symbol = symbol_;
|
||||
}
|
||||
|
||||
function decimals() public pure returns (uint8) {
|
||||
return 18;
|
||||
}
|
||||
|
||||
function name() public view returns (string memory) {
|
||||
return _name;
|
||||
}
|
||||
|
||||
function symbol() public view returns (string memory) {
|
||||
return _symbol;
|
||||
}
|
||||
|
||||
function totalSupply() external view returns (uint256) {
|
||||
return _totalSupply;
|
||||
}
|
||||
|
||||
function balanceOf(address account) external view returns (uint256) {
|
||||
return _balances[account];
|
||||
}
|
||||
|
||||
function claim() external returns (bool) {
|
||||
if (isClaim[msg.sender]) revert();
|
||||
_balances[msg.sender] += 1 ether;
|
||||
_totalSupply += 1 ether;
|
||||
emit Claim(msg.sender, 1 ether);
|
||||
return true;
|
||||
}
|
||||
|
||||
function transfer(address to, uint256 amount) external returns (bool) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint256 value) external returns (bool) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function approve(address spender, uint256 amount) external returns (bool) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function allowance(address owner, address spender) public view returns (uint256) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
}
|
||||
104
hw1/src/NFinTech/NFinTech.sol
Normal file
104
hw1/src/NFinTech/NFinTech.sol
Normal file
@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IERC721 {
|
||||
function balanceOf(address owner) external view returns (uint256 balance);
|
||||
function ownerOf(uint256 tokenId) external view returns (address owner);
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId) external;
|
||||
function transferFrom(address from, address to, uint256 tokenId) external;
|
||||
function approve(address to, uint256 tokenId) external;
|
||||
function setApprovalForAll(address operator, bool approved) external;
|
||||
function getApproved(uint256 tokenId) external view returns (address operator);
|
||||
function isApprovedForAll(address owner, address operator) external view returns (bool);
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
|
||||
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
|
||||
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
|
||||
}
|
||||
|
||||
interface IERC721TokenReceiver {
|
||||
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)
|
||||
external
|
||||
returns (bytes4);
|
||||
}
|
||||
|
||||
contract NFinTech is IERC721 {
|
||||
// Note: I have declared all variables you need to complete this challenge
|
||||
string private _name;
|
||||
string private _symbol;
|
||||
|
||||
uint256 private _tokenId;
|
||||
|
||||
mapping(uint256 => address) private _owner;
|
||||
mapping(address => uint256) private _balances;
|
||||
mapping(uint256 => address) private _tokenApproval;
|
||||
mapping(address => bool) private isClaim;
|
||||
mapping(address => mapping(address => bool)) _operatorApproval;
|
||||
|
||||
error ZeroAddress();
|
||||
|
||||
constructor(string memory name_, string memory symbol_) payable {
|
||||
_name = name_;
|
||||
_symbol = symbol_;
|
||||
}
|
||||
|
||||
function claim() public {
|
||||
if (isClaim[msg.sender] == false) {
|
||||
uint256 id = _tokenId;
|
||||
_owner[id] = msg.sender;
|
||||
|
||||
_balances[msg.sender] += 1;
|
||||
isClaim[msg.sender] = true;
|
||||
|
||||
_tokenId += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function name() public view returns (string memory) {
|
||||
return _name;
|
||||
}
|
||||
|
||||
function symbol() public view returns (string memory) {
|
||||
return _symbol;
|
||||
}
|
||||
|
||||
function balanceOf(address owner) public view returns (uint256) {
|
||||
if (owner == address(0)) revert ZeroAddress();
|
||||
return _balances[owner];
|
||||
}
|
||||
|
||||
function ownerOf(uint256 tokenId) public view returns (address) {
|
||||
address owner = _owner[tokenId];
|
||||
if (owner == address(0)) revert ZeroAddress();
|
||||
return owner;
|
||||
}
|
||||
|
||||
function setApprovalForAll(address operator, bool approved) external {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function isApprovedForAll(address owner, address operator) public view returns (bool) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function approve(address to, uint256 tokenId) external {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function getApproved(uint256 tokenId) public view returns (address operator) {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function transferFrom(address from, address to, uint256 tokenId) public {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) public {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
|
||||
function safeTransferFrom(address from, address to, uint256 tokenId) public {
|
||||
// TODO: please add your implementaiton here
|
||||
}
|
||||
}
|
||||
110
hw1/test/Classroom/Classroom.t.sol
Normal file
110
hw1/test/Classroom/Classroom.t.sol
Normal file
@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {Test, console} from "forge-std/Test.sol";
|
||||
import {StudentV1, StudentV2, StudentV3} from "../../src/Classroom/Classroom.sol";
|
||||
|
||||
/* Problem 1 Interface & Contract */
|
||||
|
||||
interface IStudentV1 {
|
||||
function register() external returns (uint256);
|
||||
}
|
||||
|
||||
contract ClassroomV1 {
|
||||
uint256 public code = 1000;
|
||||
bool public isEnrolled;
|
||||
|
||||
function enroll(address student) public {
|
||||
if (IStudentV1(student).register() >= code && !isEnrolled) {
|
||||
isEnrolled = true;
|
||||
code = IStudentV1(student).register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Problem 2 Interface & Contract */
|
||||
|
||||
interface IStudentV2 {
|
||||
function register() external view returns (uint256);
|
||||
}
|
||||
|
||||
contract ClassroomV2 {
|
||||
uint256 public code = 1000;
|
||||
bool public isEnrolled;
|
||||
|
||||
function enroll(address student) public {
|
||||
if (IStudentV2(student).register() >= code && !isEnrolled) {
|
||||
isEnrolled = true;
|
||||
code = IStudentV2(student).register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Problem 3 Interface & Contract */
|
||||
|
||||
interface IStudentV3 {
|
||||
function register() external view returns (uint256);
|
||||
}
|
||||
|
||||
contract ClassroomV3 {
|
||||
uint256 public code = 1000;
|
||||
bool public isEnrolled;
|
||||
|
||||
function enroll(address student) public {
|
||||
if (IStudentV3(student).register() >= code) {
|
||||
code = IStudentV3(student).register();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The testing contract starts here */
|
||||
|
||||
contract ClassroomTest is Test {
|
||||
ClassroomV1 internal class1;
|
||||
ClassroomV2 internal class2;
|
||||
ClassroomV3 internal class3;
|
||||
|
||||
address internal user;
|
||||
|
||||
function setUp() public {
|
||||
class1 = new ClassroomV1();
|
||||
class2 = new ClassroomV2();
|
||||
class3 = new ClassroomV3();
|
||||
|
||||
user = makeAddr("user");
|
||||
vm.deal(user, 1 ether);
|
||||
}
|
||||
|
||||
/* Problem 1 Test */
|
||||
function test_check_student_v1() public {
|
||||
vm.startPrank(user);
|
||||
StudentV1 student = new StudentV1();
|
||||
class1.enroll(address(student));
|
||||
vm.stopPrank();
|
||||
|
||||
assertEq(class1.code(), 123);
|
||||
console.log("Get 10 points");
|
||||
}
|
||||
|
||||
/* Problem 2 Test */
|
||||
function test_check_student_v2() public {
|
||||
vm.startPrank(user);
|
||||
StudentV2 student = new StudentV2();
|
||||
class2.enroll(address(student));
|
||||
vm.stopPrank();
|
||||
|
||||
assertEq(class2.code(), 123);
|
||||
console.log("Get 10 points");
|
||||
}
|
||||
|
||||
/* Problem 3 Test */
|
||||
function test_check_student_v3() public {
|
||||
vm.startPrank(user);
|
||||
StudentV3 student = new StudentV3();
|
||||
class3.enroll{gas: 10000 wei}(address(student));
|
||||
vm.stopPrank();
|
||||
|
||||
assertEq(class3.code(), 123);
|
||||
console.log("Get 10 points");
|
||||
}
|
||||
}
|
||||
57
hw1/test/Delegation/Delegation.t.sol
Normal file
57
hw1/test/Delegation/Delegation.t.sol
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {Test, console2} from "forge-std/Test.sol";
|
||||
|
||||
import {Attack} from "../../src/Delegation/Delegation.sol";
|
||||
|
||||
contract D31eg4t3 {
|
||||
uint256 var0 = 12345;
|
||||
uint8 var1 = 32;
|
||||
string private var2;
|
||||
address private var3;
|
||||
uint8 private var4;
|
||||
address public owner;
|
||||
mapping(address => bool) public result;
|
||||
|
||||
modifier onlyOwner() {
|
||||
require(msg.sender == owner, "Not a Owner");
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
owner = msg.sender;
|
||||
}
|
||||
|
||||
function proxyCall(bytes calldata data) public returns (address) {
|
||||
(bool success,) = address(msg.sender).delegatecall(data);
|
||||
require(success, "Delegate Failed");
|
||||
|
||||
return owner;
|
||||
}
|
||||
}
|
||||
|
||||
contract D31eg4t3Test is Test {
|
||||
D31eg4t3 internal delegate;
|
||||
Attack internal attack;
|
||||
address internal hacker;
|
||||
|
||||
function setUp() public {
|
||||
delegate = new D31eg4t3();
|
||||
attack = new Attack(address(delegate));
|
||||
hacker = makeAddr("hacker");
|
||||
}
|
||||
|
||||
function test_check_exploit() public {
|
||||
vm.prank(hacker, hacker);
|
||||
attack.exploit();
|
||||
|
||||
bool result = delegate.result(hacker);
|
||||
assertTrue(result);
|
||||
|
||||
address owner = delegate.owner();
|
||||
assertEq(owner, hacker);
|
||||
|
||||
console2.log("Get 10 points");
|
||||
}
|
||||
}
|
||||
182
hw1/test/LiaoToken/LiaoToken.t.sol
Normal file
182
hw1/test/LiaoToken/LiaoToken.t.sol
Normal file
@ -0,0 +1,182 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {LiaoToken} from "../../src/LiaoToken/LiaoToken.sol";
|
||||
import {Test, console2} from "forge-std/Test.sol";
|
||||
|
||||
/// @title Liao Token Test
|
||||
/// @author Louis Tsai
|
||||
/// @notice Do NOT modify this contract or you might get 0 points for the assingment.
|
||||
|
||||
contract LiaoTokenTest is Test {
|
||||
LiaoToken internal token;
|
||||
address internal Bob;
|
||||
address internal Alice;
|
||||
address internal user;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||
event Claim(address indexed user, uint256 indexed amount);
|
||||
|
||||
function setUp() public {
|
||||
token = new LiaoToken("LiaoToken", "Liao");
|
||||
Bob = makeAddr("Bob");
|
||||
Alice = makeAddr("Alice");
|
||||
user = makeAddr("user");
|
||||
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Claim(Bob, 1 ether);
|
||||
token.claim();
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Claim(Alice, 1 ether);
|
||||
token.claim();
|
||||
}
|
||||
|
||||
/* Default Tests */
|
||||
function test_decimal() public {
|
||||
uint8 decimals = token.decimals();
|
||||
assertEq(decimals, 18);
|
||||
}
|
||||
|
||||
function test_name() public {
|
||||
string memory name = token.name();
|
||||
assertEq(name, "LiaoToken");
|
||||
}
|
||||
|
||||
function test_symbol() public {
|
||||
string memory symbol = token.symbol();
|
||||
assertEq(symbol, "Liao");
|
||||
}
|
||||
|
||||
function test_totalSupply() public {
|
||||
uint256 totalSupply = token.totalSupply();
|
||||
assertEq(totalSupply, 2 ether);
|
||||
}
|
||||
|
||||
function testBalanceOf() public {
|
||||
uint256 balance;
|
||||
|
||||
balance = token.balanceOf(Bob);
|
||||
assertEq(balance, 1 ether);
|
||||
|
||||
balance = token.balanceOf(Alice);
|
||||
assertEq(balance, 1 ether);
|
||||
}
|
||||
|
||||
/* PART 1: Complete transfer function -> 10 points */
|
||||
function test_transfer() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Transfer(Bob, Alice, 0.5 ether);
|
||||
bool success = token.transfer(Alice, 0.5 ether);
|
||||
assertTrue(success);
|
||||
|
||||
uint256 balance;
|
||||
balance = token.balanceOf(Bob);
|
||||
assertEq(balance, 0.5 ether);
|
||||
|
||||
balance = token.balanceOf(Alice);
|
||||
assertEq(balance, 1.5 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_transfer_balance_not_enough() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectRevert();
|
||||
token.transfer(Alice, 1.5 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PART 2: Complete approve and allowance function -> 10 points */
|
||||
function test_approve_function() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Approval(Bob, Alice, 1 ether);
|
||||
bool success = token.approve(Alice, 1 ether);
|
||||
assertTrue(success);
|
||||
|
||||
uint256 allowance = token.allowance(Bob, Alice);
|
||||
assertEq(allowance, 1 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PART 3: Complete transferFrom function -> 10 points */
|
||||
function test_transferFrom() public returns (bool) {
|
||||
bool success;
|
||||
uint256 balance;
|
||||
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Approval(Bob, Alice, 1 ether);
|
||||
success = token.approve(Alice, 1 ether);
|
||||
assertTrue(success);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Transfer(Bob, user, 1 ether);
|
||||
success = token.transferFrom(Bob, user, 1 ether);
|
||||
assertTrue(success);
|
||||
|
||||
balance = token.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
|
||||
balance = token.balanceOf(user);
|
||||
assertEq(balance, 1 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_transferFrom_allowance_not_enough() public returns (bool) {
|
||||
bool success;
|
||||
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Approval(Bob, Alice, 1 ether);
|
||||
success = token.approve(Alice, 0.5 ether);
|
||||
assertTrue(success);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectRevert();
|
||||
token.transferFrom(Alice, user, 1 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_transferFrom_balance_not_enough() public returns (bool) {
|
||||
bool success;
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit Approval(Bob, Alice, 1 ether);
|
||||
success = token.approve(Alice, 0.5 ether);
|
||||
assertTrue(success);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectRevert();
|
||||
token.transferFrom(Alice, user, 2 ether);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* We use the following parts to calculate your score */
|
||||
function test_check_transfer_points() public {
|
||||
bool success = test_transfer() && test_transfer_balance_not_enough();
|
||||
if (success) console2.log("Get 10 points");
|
||||
}
|
||||
|
||||
function test_check_approve_points() public {
|
||||
bool success = test_approve_function();
|
||||
if (success) console2.log("Get 10 points");
|
||||
}
|
||||
|
||||
function test_check_transferFrom_points() public {
|
||||
bool success =
|
||||
test_transferFrom() && test_transferFrom_allowance_not_enough() && test_transferFrom_balance_not_enough();
|
||||
if (success) console2.log("Get 10 points");
|
||||
}
|
||||
}
|
||||
368
hw1/test/NFinTech/NFinTech.t.sol
Normal file
368
hw1/test/NFinTech/NFinTech.t.sol
Normal file
@ -0,0 +1,368 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {Test, console2} from "forge-std/Test.sol";
|
||||
import "../../src/NFinTech/NFinTech.sol";
|
||||
|
||||
/// @title NFinTech Test
|
||||
/// @author Louis Tsai
|
||||
/// @notice Do NOT modify this contract or you might get 0 points for the assingment.
|
||||
|
||||
contract NFinTechTest is Test {
|
||||
NFinTech internal nft;
|
||||
address internal Bob;
|
||||
address internal Alice;
|
||||
address internal user;
|
||||
|
||||
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
|
||||
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
|
||||
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
|
||||
|
||||
function setUp() public {
|
||||
nft = new NFinTech("NFinTech", "NFT");
|
||||
|
||||
user = makeAddr("user");
|
||||
|
||||
Bob = makeAddr("Bob");
|
||||
vm.prank(Bob);
|
||||
nft.claim();
|
||||
|
||||
Alice = makeAddr("Alice");
|
||||
vm.prank(Alice);
|
||||
nft.claim();
|
||||
}
|
||||
|
||||
/* Default Tests */
|
||||
function test_name() public {
|
||||
string memory name = nft.name();
|
||||
assertEq(name, "NFinTech");
|
||||
}
|
||||
|
||||
function test_symbol() public {
|
||||
string memory symbol = nft.symbol();
|
||||
assertEq(symbol, "NFT");
|
||||
}
|
||||
|
||||
function test_balanceOf() public {
|
||||
uint256 balance;
|
||||
|
||||
vm.prank(Bob);
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 1);
|
||||
|
||||
vm.prank(Alice);
|
||||
balance = nft.balanceOf(Alice);
|
||||
assertEq(balance, 1);
|
||||
|
||||
vm.expectRevert();
|
||||
nft.balanceOf(address(0));
|
||||
}
|
||||
|
||||
function test_ownerOf() public {
|
||||
address owner;
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, Bob);
|
||||
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
|
||||
vm.expectRevert();
|
||||
nft.ownerOf(3);
|
||||
}
|
||||
|
||||
/* PART 1: Complete approve related function -> 10 points */
|
||||
|
||||
function test_approve() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Approval(Bob, Alice, 0);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
address operator = nft.getApproved(0);
|
||||
assertEq(operator, Alice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_setApprovalForAll() public returns (bool) {
|
||||
bool approved;
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit ApprovalForAll(Bob, Alice, true);
|
||||
nft.setApprovalForAll(Alice, true);
|
||||
|
||||
approved = nft.isApprovedForAll(Bob, Alice);
|
||||
assertEq(approved, true);
|
||||
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit ApprovalForAll(Bob, Alice, false);
|
||||
nft.setApprovalForAll(Alice, false);
|
||||
|
||||
approved = nft.isApprovedForAll(Bob, Alice);
|
||||
assertEq(approved, false);
|
||||
|
||||
vm.prank(Bob);
|
||||
vm.expectRevert();
|
||||
nft.setApprovalForAll(address(0), true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_approve_not_token_owner() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectRevert();
|
||||
nft.approve(Alice, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_approve_then_setApprovalForAll() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit ApprovalForAll(Bob, Alice, true);
|
||||
nft.setApprovalForAll(Alice, true);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Approval(Bob, user, 0);
|
||||
nft.approve(user, 0);
|
||||
|
||||
address operator = nft.getApproved(0);
|
||||
assertEq(operator, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PART 2: Complete transferFrom function -> 10 points */
|
||||
function test_transferFrom() public returns (bool) {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, Alice, 0);
|
||||
nft.transferFrom(Bob, Alice, 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(Alice);
|
||||
assertEq(balance, 2);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, Alice);
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_transferFrom_zero_address() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectRevert();
|
||||
nft.transferFrom(Bob, address(0), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_transferFrom_not_owner() public returns (bool) {
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectRevert();
|
||||
nft.transferFrom(Bob, address(0), 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PART 3: Complete safeTransferFrom function -> 10 points */
|
||||
function test_safeTransferFrom_eoa() public returns (bool) {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, Alice, 0);
|
||||
nft.transferFrom(Bob, Alice, 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(Alice);
|
||||
assertEq(balance, 2);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, Alice);
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_safeTransferFrom_ca_success() public returns (bool) {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
MockSuccessReceiver receiver = new MockSuccessReceiver();
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, address(receiver), 0);
|
||||
nft.safeTransferFrom(Bob, address(receiver), 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(address(receiver));
|
||||
assertEq(balance, 1);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, address(receiver));
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function test_safeTransferFrom_ca_failure() public returns (bool) {
|
||||
MockBadReceiver receiver = new MockBadReceiver();
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectRevert();
|
||||
nft.safeTransferFrom(Bob, address(receiver), 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* PART 4: Mixed operation test */
|
||||
function test_approve_then_transferFrom() public {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, Alice, 0);
|
||||
nft.transferFrom(Bob, Alice, 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(Alice);
|
||||
assertEq(balance, 2);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, Alice);
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
}
|
||||
|
||||
function test_approve_user_then_transferFrom() public {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.approve(Alice, 0);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, user, 0);
|
||||
nft.transferFrom(Bob, user, 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(user);
|
||||
assertEq(balance, 1);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, user);
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
}
|
||||
|
||||
function test_setApprovalForAll_then_transferFrom() public {
|
||||
uint256 balance;
|
||||
address owner;
|
||||
|
||||
vm.prank(Bob);
|
||||
nft.setApprovalForAll(Alice, true);
|
||||
|
||||
vm.prank(Alice);
|
||||
vm.expectEmit(true, true, true, false);
|
||||
emit Transfer(Bob, user, 0);
|
||||
nft.transferFrom(Bob, user, 0);
|
||||
|
||||
balance = nft.balanceOf(Bob);
|
||||
assertEq(balance, 0);
|
||||
balance = nft.balanceOf(user);
|
||||
assertEq(balance, 1);
|
||||
|
||||
owner = nft.ownerOf(0);
|
||||
assertEq(owner, user);
|
||||
owner = nft.ownerOf(1);
|
||||
assertEq(owner, Alice);
|
||||
}
|
||||
/* We use the following parts to calculate your score */
|
||||
|
||||
function test_check_approve_related_points() public {
|
||||
test_approve();
|
||||
test_setApprovalForAll();
|
||||
test_approve_not_token_owner();
|
||||
test_approve_then_setApprovalForAll();
|
||||
console2.log("Get 10 points");
|
||||
}
|
||||
|
||||
function test_check_transferFrom_points() public {
|
||||
test_transferFrom();
|
||||
setUp();
|
||||
test_transferFrom_zero_address();
|
||||
setUp();
|
||||
test_transferFrom_not_owner();
|
||||
console2.log("Get 10 points");
|
||||
}
|
||||
|
||||
function test_check_safeTransferFrom_points() public {
|
||||
test_safeTransferFrom_eoa();
|
||||
setUp();
|
||||
test_safeTransferFrom_ca_failure();
|
||||
setUp();
|
||||
test_safeTransferFrom_ca_success();
|
||||
console2.log("Get 10 points");
|
||||
}
|
||||
|
||||
function test_check_mix_operation() public {
|
||||
test_approve_then_transferFrom();
|
||||
setUp();
|
||||
test_approve_user_then_transferFrom();
|
||||
setUp();
|
||||
test_setApprovalForAll_then_transferFrom();
|
||||
console2.log("Get 10 points");
|
||||
}
|
||||
}
|
||||
|
||||
contract MockSuccessReceiver is IERC721TokenReceiver {
|
||||
function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) {
|
||||
return IERC721TokenReceiver.onERC721Received.selector;
|
||||
}
|
||||
}
|
||||
|
||||
contract MockBadReceiver is IERC721TokenReceiver {
|
||||
function onERC721Received(address, address, uint256, bytes calldata) external returns (bytes4) {
|
||||
return bytes4(keccak256("approve(address,uint256)"));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user