Todo :
0. npx hardhat 명령어
1. Native Bank
2. Withdraw 기능
hardhat 설치 시 자동으로 test/node_modules/.bin 에 해당 기능이 설치가 된다고 한다.
❯ npx hardhat test
❯ hh test
이렇게 쓸 수 있다는데....
나는 안됨....
이전의 MyToken이나 TinyBank까지 테스트 할 필요 없으니까
❯ npx hardhat test test/NativeBank.ts
위의 코드처럼 NativeBank만 테스트한다고 함.
이전 MyToken을 처음 만들 때 (4-3) native token 이 무엇인지 나왔었다.
특정 블록체인 네트워크의 기본이 되는 토큰으로, 거래 시 해당 토큰으로 수수료가 지불된다. (ETH같은 거)
그래서 일단 파일을 새로 만들었고
import hre from "hardhat";
describe("NativeBank", () => {
it("Should send native token to contract", async () => {
const signers = await hre.ethers.getSigners();
const staker = signers[0];
const nativeBankC = await hre.ethers.deployContract("NativeBank");
const tx = {
from: staker.address,
to: await nativeBankC.getAddress(),
value: hre.ethers.parseEther("1"),
};
const txResp = await staker.sendTransaction(tx);
const txReceipt = await txResp.wait();
console.log(
// total balance
await hre.ethers.provider.getBalance(await nativeBankC.getAddress()),
);
console.log(await nativeBankC.balanceOf(staker.address));
});
});
이렇게 하고 테스트를 그냥 돌리면 (NativeBank.sol파일의 contract 내부 없을 때)
1) NativeBank
Should send native token to contract:
Error: Transaction reverted: function selector was not recognized and there's no fallback nor receive function
이런 에러가 뜬다.
그러니까
전송된 트랜잭션이 호출하려는 함수가 컨트랙트 안에 존재하지 않고
(= 비어있음)
컨트랙트에 fallback() 함수나 receive() 함수도 없어서 이더를 못받고
reverted 되었다는 소리다
receive()는 이더를 받는 기능인데, fallback()이 뭔지 몰라서 찾아봤다.
fallback()은 잘못된 호출·없는 함수 호출·예기치 못한 데이터 처리를 담당하는 함수로, receive()가 없는데 호출해서 fallback()으로 넘어갔는데 그것마저 없었다~ 는 소리이다
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
contract NativeBank {
mapping(address => uint256) public balanceOf;
receive() external payable {
balanceOf[msg.sender] += msg.value; //tx value
}
}
그래서 receive()를 만들어주면 되는데, 이 때
이더를 보내거나 받을 수 있는 함수/주소에는 반드시 payable을 붙여야 한다
참고로 저런 건 컴파일러 수준의 타입 속성(type attribute) 키워드라고 한다.
function withdraw() external{
uint256 balance = balanceOf[msg.sender];
require(balance > 0, "insufficient balance");
(bool success,) = msg.sender.call{value:balance}("");
require(success, "failed to send native token);
balanceOf[msg.sender] = 0;
}
native token은 스마트컨트랙트 기반 토큰과 다르게 transfer 함수가 없고, transaction을 만들어야 한다고 한다.
(bool success,(반환값)) = msg.sender.call{value:balance}("");
이 때 address.call(\~~)형태의 call function을 사용한다.
call function 참고 : [Solidity] call function
contract에서 만드는 transaction은
지갑이 만드는 것과 다르게 transaction이 찍히지는 않는다.
대신 에러메세지와 log로 분석이 가능하다고 한다.
import hre from "hardhat";
import { NativeBank } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
import { expect } from "chai";
describe("NativeBank", () => {
let signers: HardhatEthersSigner[];
let nativeBankC: NativeBank;
beforeEach("Deploy NativeBank contract", async () => {
signers = await hre.ethers.getSigners();
nativeBankC = await hre.ethers.deployContract("NativeBank");
});
it("Should send native token to contract", async () => {
const staker = signers[0];
const tx = {
from: staker.address,
to: await nativeBankC.getAddress(),
value: hre.ethers.parseEther("1"),
};
const txResp = await staker.sendTransaction(tx);
const txReceipt = await txResp.wait();
console.log(
// total balance
await hre.ethers.provider.getBalance(await nativeBankC.getAddress()),
);
console.log(await nativeBankC.balanceOf(staker.address));
});
it("Should withdraw all the tokens", async () => {
const staker = signers[0];
const stakingAmount = hre.ethers.parseEther("10");
const tx = {
from: staker.address,
to: await nativeBankC.getAddress(),
value: stakingAmount,
};
const sentTx = await staker.sendTransaction(tx);
sentTx.wait();
expect(await nativeBankC.balanceOf(staker.address)).to.be.equal(
stakingAmount,
);
await nativeBankC.withdraw();
expect(await nativeBankC.balanceOf(staker.address)).to.be.equal(0n);
});
});
이전 테스트 코드들이랑 비슷하게
beforeEach로 공통으로 쓰이는 부분 넣어주기
"Should withdraw all the tokens" 추가
10ETH 넣어주고(tx전송), 기다렸다가 잔고확인
withdraw() 실행하고 잔고 확인 (0ETH인지)
공격&방지 --> (solidity 끝.)
Vyper라는 python과 유사한 EVM 스마트컨트랙트를 짜는 언어로
TinyBank, MyToken 바꿔보면서 언어를 익혀볼 예정