From 2f0e0000e1de0bc7e412bdf4e17d4bf2e7dafa50 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Mon, 3 Jun 2024 19:42:29 +0900 Subject: [PATCH 1/7] Feat: Donation Contract Finished --- src/contracts/Donation.sol | 151 +++++++++++--- .../hardhat/todo/001_deploy_contracts.ts | 16 +- src/test/donation.test.ts | 186 +++++++++++++----- src/test/setup.ts | 6 +- 4 files changed, 269 insertions(+), 90 deletions(-) diff --git a/src/contracts/Donation.sol b/src/contracts/Donation.sol index bf26135..5caf6f3 100644 --- a/src/contracts/Donation.sol +++ b/src/contracts/Donation.sol @@ -5,29 +5,35 @@ import "./interface/DaoTokenInterface.sol"; import "./interface/DaoInterface.sol"; import "./interface/DonationInterface.sol"; -contract Donation { +contract Donation is DonationInterface { ///////////// @notice 아래에 변수 추가 //////////// /// @notice Admin 주소 - + address public admin; /// @notice 캠페인 아이디 카운트 - + uint256 public count; /// @notice DAO 토큰 컨트랙트 주소 - + DaoTokenInterface public daoToken; ///////////// @notice 아래에 매핑 추가 //////////// /// @notice 캠페인 아이디 -> 캠페인 구조체 - + mapping(uint256 => Campaign) public campaigns; /// @notice 캠페인 아이디 -> 사용자 주소 -> 기부 금액 - + mapping(uint256 => mapping(address => uint256)) public pledgedUserToAmount; ///////////// @notice 아래에 생성자 및 컨트랙트 주소 설정 //////////// /// @notice 관리자 및 DAO Token 컨트랙트 주소 설정 - + constructor(address _daoToken) { + admin = msg.sender; + daoToken = DaoTokenInterface(_daoToken); + } ///////////// @notice 아래에 modifier 추가 //////////// /// @notice 관리자만 접근 가능하도록 설정 - + modifier onlyAdmin() { + require(msg.sender == admin, "Only admin can execute"); + _; + } /// @notice DAO 회원만 접근 가능하도록 설정 function launch( @@ -37,39 +43,122 @@ contract Donation { uint256 _goal, uint32 _startAt, uint32 _endAt - ) external {} + ) external { + require(_startAt > block.timestamp, "start at < now"); + require(_endAt > _startAt, "end at < start at"); + require(_endAt < block.timestamp + 90 days, "The maximum allowed campaign duration is 90 days."); + + count += 1; + + campaigns[count] = Campaign({ + creator: msg.sender, + target: _target, + title: _title, + description: _description, + goal: _goal, + pledged: 0, + startAt: _startAt, + endAt: _endAt, + claimed: false + }); + + emit Launch(count, campaigns[count]); + } + + function cancel(uint256 _campaignId) external { + Campaign memory campaignInfo; + campaignInfo = campaigns[_campaignId]; + require(msg.sender == campaignInfo.creator, "Only creater can cancel"); + require(campaignInfo.startAt > block.timestamp, "Already Started"); + + delete campaigns[_campaignId]; + + emit Cancel(_campaignId); + } + + function pledge(uint256 _campaignId, uint256 _amount) external { + Campaign memory campaignInfo; + campaignInfo = campaigns[_campaignId]; + require(campaignInfo.startAt < block.timestamp, "not started"); + require(!getIsEnded(_campaignId), "Campaign ended"); + require(_amount > 0, "Amount must be greater than zero"); + + require(daoToken.transferFrom(msg.sender, address(this), _amount), "Token transfer failed"); + + campaigns[_campaignId].pledged += _amount; + pledgedUserToAmount[_campaignId][msg.sender] += _amount; + + emit Pledge(_campaignId, msg.sender, _amount, campaigns[_campaignId].pledged); + } + + function unpledge(uint256 _campaignId, uint256 _amount) external { + Campaign memory campaignInfo; + campaignInfo = campaigns[_campaignId]; + require(_amount > 0, "Amount must be greater than zero"); + require(!getIsEnded(_campaignId), "Campaign ended"); + require( + _amount <= pledgedUserToAmount[_campaignId][msg.sender], + "Unpledge amount must be smaller than the amount you pledged" + ); + + require(daoToken.transfer(msg.sender, _amount)); + + campaigns[_campaignId].pledged -= _amount; + pledgedUserToAmount[_campaignId][msg.sender] -= _amount; + + emit Unpledge(_campaignId, msg.sender, _amount, campaigns[_campaignId].pledged); + } - function cancel(uint256 _campaignId) external {} + //2. onlyDao modifier 추가 + function claim(uint256 _campaignId) external { + require(getIsEnded(_campaignId), "Campaign not ended"); - function pledge(uint256 _campaignId, uint256 _amount) external {} + Campaign memory campaignInfo; + campaignInfo = campaigns[_campaignId]; - function unpledge(uint256 _campaignId, uint256 _amount) external {} + require(!campaignInfo.claimed, "claimed"); + require(daoToken.transfer(campaignInfo.target, campaignInfo.pledged), "Token transfer failed"); - //2. onlyDao modifier 추가 - function claim(uint256 _campaignId) external {} + campaigns[_campaignId].claimed = true; + + // event Claim(uint256 indexed campaignId, bool claimed, uint256 amount); + + emit Claim(_campaignId, campaigns[_campaignId].claimed, campaigns[_campaignId].pledged); + } + + function refund(uint256 _campaignId) external { + require(getIsEnded(_campaignId), "Campaign not ended"); + uint256 bal = pledgedUserToAmount[_campaignId][msg.sender]; + + require(daoToken.transfer(msg.sender, bal), "Token transfer failed"); + + pledgedUserToAmount[_campaignId][msg.sender] = 0; + + // event Refund(uint256 indexed campaignId, address indexed caller, uint256 amount); - function refund(uint256 _campaignId) external {} + emit Refund(_campaignId, msg.sender, bal); + } ///////////// @notice 아래에 get함수는 필요한 경우 주석을 해제해 사용해주세요 //////////// - // function getIsEnded(uint256 _campaignId) public view returns (bool) { - // Campaign memory campaign = campaigns[_campaignId]; - // return block.timestamp >= campaign.endAt || campaign.pledged >= campaign.goal; - // } + function getIsEnded(uint256 _campaignId) public view returns (bool) { + Campaign memory campaign = campaigns[_campaignId]; + return block.timestamp >= campaign.endAt || campaign.pledged >= campaign.goal; + } - // function getCampaign(uint256 _campaignId) external view returns (Campaign memory) { - // return campaigns[_campaignId]; - // } + function getCampaign(uint256 _campaignId) external view returns (Campaign memory) { + return campaigns[_campaignId]; + } - // function getCampaignCreator(uint256 _campaignId) external view returns (address) { - // return campaigns[_campaignId].creator; - // } + function getCampaignCreator(uint256 _campaignId) external view returns (address) { + return campaigns[_campaignId].creator; + } - // function getCampaignGoal(uint256 _campaignId) external view returns (uint256) { - // return campaigns[_campaignId].goal; - // } + function getCampaignGoal(uint256 _campaignId) external view returns (uint256) { + return campaigns[_campaignId].goal; + } - // function getCampaignTotalAmount(uint256 _campaignId) external view returns (uint256) { - // return campaigns[_campaignId].pledged; - // } + function getCampaignTotalAmount(uint256 _campaignId) external view returns (uint256) { + return campaigns[_campaignId].pledged; + } } diff --git a/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts b/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts index 34807e1..8a8fbf8 100644 --- a/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts +++ b/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts @@ -1,3 +1,5 @@ +//001_deploy_contracts.ts + import { hardhatInfo } from "@constants"; import { ethers } from "hardhat"; import { DeployFunction } from "hardhat-deploy/types"; @@ -16,13 +18,13 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { autoMine: true, }); - // const DonationContract = await deploy("Donation", { - // from: developer.address, - // contract: "Donation", - // args: [DaoTokenContract.address], - // log: true, - // autoMine: true, - // }); + const DonationContract = await deploy("Donation", { + from: developer.address, + contract: "Donation", + args: [DaoTokenContract.address], + log: true, + autoMine: true, + }); // const DaoContract = await deploy("Dao", { // from: developer.address, diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 1b929e5..12cf651 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -1,49 +1,137 @@ -// import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; -// import { setup } from "./setup"; -// import { DaoToken, Dao, Donation } from "@typechains"; -// import { expect } from "chai"; -// import { ethers, network } from "hardhat"; -// import { hardhatInfo } from "@constants"; -// import { faker } from "@faker-js/faker"; -// import { BigNumber } from "ethers"; -// import { HardhatUtil } from "./lib/hardhat_utils"; -// import { GAS_PER_TRANSACTION } from "./mock/mock"; - -// describe("Dao Token 테스트", () => { -// /* Signer */ -// let admin: SignerWithAddress; -// let users: SignerWithAddress[]; - -// /* 컨트랙트 객체 */ -// let daoToken: DaoToken; -// let dao: Dao; -// let donation: Donation; - -// /* 테스트 스냅샷 */ -// let initialSnapshotId: number; -// let snapshotId: number; - -// before(async () => { -// /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ -// ({ admin, users, daoToken, dao, donation } = await setup()); -// initialSnapshotId = await network.provider.send("evm_snapshot"); -// }); - -// beforeEach(async () => { -// snapshotId = await network.provider.send("evm_snapshot"); -// }); - -// afterEach(async () => { -// await network.provider.send("evm_revert", [snapshotId]); -// }); - -// after(async () => { -// await network.provider.send("evm_revert", [initialSnapshotId]); -// }); - -// it("Hardhat 환경 배포 테스트", () => { -// expect(daoToken.address).to.not.be.undefined; -// expect(dao.address).to.not.be.undefined; -// expect(donation.address).to.not.be.undefined; -// }); -// }); +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; +import { setup } from "./setup"; +import { DaoToken, Dao, Donation } from "@typechains"; +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import { hardhatInfo } from "@constants"; +import { faker } from "@faker-js/faker"; +import { BigNumber } from "ethers"; +import { HardhatUtil } from "./lib/hardhat_utils"; +import { GAS_PER_TRANSACTION } from "./mock/mock"; + +describe("Dao Token 테스트", () => { + /* Signer */ + let admin: SignerWithAddress; + let users: SignerWithAddress[]; + + /* 컨트랙트 객체 */ + let daoToken: DaoToken; + let dao: Dao; + let donation: Donation; + + /* 테스트 스냅샷 */ + let initialSnapshotId: number; + let snapshotId: number; + + before(async () => { + /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ + ({ admin, users, daoToken, donation } = await setup()); + initialSnapshotId = await network.provider.send("evm_snapshot"); + }); + + beforeEach(async () => { + snapshotId = await network.provider.send("evm_snapshot"); + }); + + afterEach(async () => { + await network.provider.send("evm_revert", [snapshotId]); + }); + + after(async () => { + await network.provider.send("evm_revert", [initialSnapshotId]); + }); + + it("Hardhat 환경 배포 테스트", () => { + expect(daoToken.address).to.not.be.undefined; + // expect(dao.address).to.not.be.undefined; + expect(donation.address).to.not.be.undefined; + }); + + describe("launch함수 테스트", () => { + // 정상케이스 테스트 + // 1. Count 변수가 정상적으로 반영되었는가 + it("Count 변수가 정상적으로 반영되었는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + expect(await donation.count()).to.equal(1); + }); + // 2. campaign 객체가 정상적으로 반영되었는가 + // it("campaign 객체가 정상적으로 반영되었는가", async () => { + // const currentTime = Math.floor(Date.now() / 1000); + // const startAt = currentTime + 10; + // const endAt = startAt + 3600; + // const goal = ethers.utils.parseUnits("1000", 18); + + // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + // const campaign = await donation.getCampaign(0); + + // expect(campaign.creator).to.equal(users[0].address); + // expect(campaign.target).to.equal(users[1].address); + // expect(campaign.title).to.equal("test"); + // expect(campaign.description).to.equal("test description"); + // expect(campaign.goal.toString()).to.equal(goal.toString()); + // expect(campaign.startAt).to.equal(startAt); + // expect(campaign.endAt).to.equal(endAt); + // expect(campaign.claimed).to.be.false; + // }); + // 3. launch 이벤트가 발생하였는가 + // 오류케이스 테스트 + // 1. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 + // 2. 종료시간이 시작시간보다 빠르면 에러가 발생하는가 + // 3. 캠페인 기간이 90일을 넘으면 에러가 발생하는가 + }); + describe("cancel함수 테스트", () => { + // 정상케이스 테스트 + // 1. 캠페인이 정상적으로 삭제되는가? + // 2 cancel 이벤트가 발생하였는가 + // 오류케이스 테스트 + // 1. creater 외 호출시 에러가 발생하는가 + // 2. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 + }); + describe("pledge함수 테스트", () => { + // 정상케이스 테스트 + // 1. 기부금액 ( pledged )가 정상적으로 업데이트 되는가? + // 2. 캠페인 아이디와 기부자의 기부금액이 기록되는가? + // 3. 기부자로부터 컨트랙트로 DAO 토큰이 전송되는갸? + // 4. Pledge 이벤트가 발생하는가? + // 오류케이스 테스트 + // 1. 캠페인 시작 전 실행시 에러가 발생하는가 + // 2. 종료된 캠페인에 기부시 에러가 발생하는가 + // 3. 기부금액이 0 원이면 에러가 발생하는가 + }); + describe("unpledge함수 테스트", () => { + // 정상케이스 테스트 + // 1. 기부금액 ( pledged )가 정상적으로 업데이트 되는가? + // 2. 캠페인 아이디와 기부자의 기부금액이 기록되는가? + // 3. 컨트랙트로부터 기부자의 주소로 DAO 토큰이 전송되는갸? + // 4. UnPledge 이벤트가 발생하는가? + // 오류케이스 테스트 + // 1. 취소금액이 0 보다 작으면 에러가 발생하는가 + // 2. 종료된 캠페인에 기부취소시 에러가 발생하는가 + // 3. 기존에 냈던 금액보다 크게 취소할 시 에러가 발생하는가 + }); + describe("claim함수 테스트", () => { + // 정상케이스 테스트 + // 1. 캠페인 타켓 주소로 DAO 토큰이 전송되는가? + // 2. 캠페인의 클레임 상태가 업데이트 되는가? + // 3. Claim 이벤트가 발생하는가? + // 4. UnPledge 이벤트가 발생하는가? + // 오류케이스 테스트 + // 1. 캠페인 종료 전 실행시 오류가 발생하는가 + // 2. 이미 claimed 된 캠페인에 호출시 에러가 발생하는가 + }); + describe("refund함수 테스트", () => { + // 정상케이스 테스트 + // 1. 기부자의 기부 금액이 0으로 초기화 되는가? + // 2. 컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가? + // 3. Refund 이벤트가 발생하는가? + // 오류케이스 테스트 + // 1. 캠페인 종료 전 실행시 오류가 발생하는가 + }); +}); diff --git a/src/test/setup.ts b/src/test/setup.ts index 5bdeddb..6bf7a3a 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,3 +1,4 @@ +//setup.ts import { DaoToken, Donation, Dao } from "@typechains"; import { deployments, ethers } from "hardhat"; @@ -6,11 +7,10 @@ export const setup = async () => { const [admin, ...users] = await ethers.getSigners(); /* 컨트랙트 데이터 설정: deployments.fixture를 통하여 hardhat 환경에 배포된 컨트랙트 정보를 가져온다. */ - await deployments.fixture(["DaoToken", "Donation", "Dao"]); + await deployments.fixture(["DaoToken", "Donation"]); const contracts = { daoToken: await ethers.getContract("DaoToken"), - // donation: await ethers.getContract("Donation"), - // dao: await ethers.getContract("Dao"), + donation: await ethers.getContract("Donation"), }; return { From 3dfd83665b995f3e75ea1f771dae76bdaac951b9 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Mon, 3 Jun 2024 20:45:23 +0900 Subject: [PATCH 2/7] =?UTF-8?q?Feat:=20Launch=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/donation.test.ts | 80 +++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 19 deletions(-) diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 12cf651..279a8ed 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -61,30 +61,72 @@ describe("Dao Token 테스트", () => { expect(await donation.count()).to.equal(1); }); // 2. campaign 객체가 정상적으로 반영되었는가 - // it("campaign 객체가 정상적으로 반영되었는가", async () => { - // const currentTime = Math.floor(Date.now() / 1000); - // const startAt = currentTime + 10; - // const endAt = startAt + 3600; - // const goal = ethers.utils.parseUnits("1000", 18); - - // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - // const campaign = await donation.getCampaign(0); - - // expect(campaign.creator).to.equal(users[0].address); - // expect(campaign.target).to.equal(users[1].address); - // expect(campaign.title).to.equal("test"); - // expect(campaign.description).to.equal("test description"); - // expect(campaign.goal.toString()).to.equal(goal.toString()); - // expect(campaign.startAt).to.equal(startAt); - // expect(campaign.endAt).to.equal(endAt); - // expect(campaign.claimed).to.be.false; - // }); + it("campaign 객체가 정상적으로 반영되었는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const campaign = await donation.getCampaign(1); + + expect(campaign.creator).to.equal(users[0].address); + expect(campaign.target).to.equal(users[1].address); + expect(campaign.title).to.equal("test"); + expect(campaign.description).to.equal("test description"); + expect(campaign.goal.toString()).to.equal(goal.toString()); + expect(campaign.startAt).to.equal(startAt); + expect(campaign.endAt).to.equal(endAt); + expect(campaign.claimed).to.be.false; + }); // 3. launch 이벤트가 발생하였는가 + it("launch 이벤트가 발생하였는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await expect( + donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), + ) + .to.emit(donation, "Launch") + .withArgs(1, await donation.getCampaign(1)); + }); // 오류케이스 테스트 // 1. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 + it("시작시간이 현재시간보다 빠르면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime - 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + expect( + donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), + ).to.be.revertedWith("start at < now"); + }); // 2. 종료시간이 시작시간보다 빠르면 에러가 발생하는가 + it("종료시간이 시작시간보다 빠르면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 8; + const goal = ethers.utils.parseUnits("1000", 18); + + expect( + donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), + ).to.be.revertedWith("end at < start at"); + }); // 3. 캠페인 기간이 90일을 넘으면 에러가 발생하는가 + it("캠페인 기간이 90일을 넘으면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 2678400; // 31 days + const goal = ethers.utils.parseUnits("1000", 18); + + expect( + donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), + ).to.be.revertedWith("The maximum allowed campaign duration is 90 days."); + }); }); describe("cancel함수 테스트", () => { // 정상케이스 테스트 From 19a03371c2f57d0d321fa5a5e145fd673c6920d8 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Mon, 3 Jun 2024 22:32:34 +0900 Subject: [PATCH 3/7] =?UTF-8?q?Feat:=20Pledge=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/donation.test.ts | 189 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 279a8ed..19ec87c 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -131,21 +131,210 @@ describe("Dao Token 테스트", () => { describe("cancel함수 테스트", () => { // 정상케이스 테스트 // 1. 캠페인이 정상적으로 삭제되는가? + it("캠페인이 정상적으로 삭제되는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + await donation.connect(users[0]).cancel(1); + + const campaign = await donation.getCampaign(1); + + expect(campaign.creator).to.equal("0x0000000000000000000000000000000000000000"); + expect(campaign.target).to.equal("0x0000000000000000000000000000000000000000"); + expect(campaign.title).to.equal(""); + expect(campaign.description).to.equal(""); + expect(campaign.goal.toString()).to.equal("0"); + expect(campaign.pledged.toString()).to.equal("0"); + expect(campaign.startAt).to.equal(0); + expect(campaign.endAt).to.equal(0); + expect(campaign.claimed).to.be.false; + }); // 2 cancel 이벤트가 발생하였는가 + it("cancel 이벤트가 발생하였는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const campaign = await donation.getCampaign(1); + + await expect(donation.connect(users[0]).cancel(1)).to.emit(donation, "Cancel").withArgs(1); + }); // 오류케이스 테스트 // 1. creater 외 호출시 에러가 발생하는가 + it("creater 외 호출시 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + await expect(donation.connect(users[1]).cancel(1)).to.revertedWith("Only creater can cancel"); + }); // 2. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 + it("시작시간이 현재시간보다 빠르면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + await HardhatUtil.passNSeconds(100); + + await expect(donation.connect(users[0]).cancel(1)).to.revertedWith("Already Started"); + }); }); describe("pledge함수 테스트", () => { // 정상케이스 테스트 // 1. 기부금액 ( pledged )가 정상적으로 업데이트 되는가? + it("기부금액 ( pledged )가 정상적으로 업데이트 되는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + const campaign = await donation.getCampaign(1); + + expect(campaign.pledged.toString()).to.equal(_amount.toString()); + }); // 2. 캠페인 아이디와 기부자의 기부금액이 기록되는가? + it("캠페인 아이디와 기부자의 기부금액이 기록되는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + const campaign = await donation.getCampaign(1); + + const pledgedAmount = await donation.pledgedUserToAmount(1, users[1].address); + expect(pledgedAmount.toString()).to.equal(_amount.toString()); + }); // 3. 기부자로부터 컨트랙트로 DAO 토큰이 전송되는갸? + it("기부자로부터 컨트랙트로 DAO 토큰이 전송되는갸?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + await expect((await daoToken.balanceOf(donation.address)).toString()).to.equal(_amount.toString()); + }); // 4. Pledge 이벤트가 발생하는가? + it("Pledge 이벤트가 발생하는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + + expect(await donation.connect(users[1]).pledge(1, _amount)) + .to.emit(donation, "Pledge") + .withArgs(1, _amount); + }); // 오류케이스 테스트 // 1. 캠페인 시작 전 실행시 에러가 발생하는가 + it("캠페인 시작 전 실행시 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 1000; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await expect(donation.connect(users[1]).pledge(1, _amount)).to.revertedWith("not started"); + }); // 2. 종료된 캠페인에 기부시 에러가 발생하는가 + it("종료된 캠페인에 기부시 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 10; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(30); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await expect(donation.connect(users[1]).pledge(1, _amount)).to.revertedWith("Campaign ended"); + }); // 3. 기부금액이 0 원이면 에러가 발생하는가 + it("기부금액이 0 원이면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 1000; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(30); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await expect(donation.connect(users[1]).pledge(1, 0)).to.revertedWith("Amount must be greater than zero"); + }); }); describe("unpledge함수 테스트", () => { // 정상케이스 테스트 From 3c248bae2b5315ae52380c43d0aa167902404438 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Mon, 3 Jun 2024 23:56:01 +0900 Subject: [PATCH 4/7] =?UTF-8?q?Feat:=20Claim=20and=20Refund=20Function=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=EC=99=80=20=ED=95=A8=EA=BB=98=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/donation.test.ts | 239 +++++++++++++++++++++++++++++++++++--- 1 file changed, 221 insertions(+), 18 deletions(-) diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 19ec87c..8d946b7 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -338,31 +338,234 @@ describe("Dao Token 테스트", () => { }); describe("unpledge함수 테스트", () => { // 정상케이스 테스트 - // 1. 기부금액 ( pledged )가 정상적으로 업데이트 되는가? - // 2. 캠페인 아이디와 기부자의 기부금액이 기록되는가? + // 1. 기부금액 ( unpledged )가 정상적으로 업데이트 되는가? + it("기부금액 ( unpledged )가 정상적으로 업데이트 되는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + await donation.connect(users[1]).unpledge(1, _amount); + + const campaign = await donation.getCampaign(1); + + expect(campaign.pledged.toString()).to.equal("0"); + }); // 3. 컨트랙트로부터 기부자의 주소로 DAO 토큰이 전송되는갸? + it("컨트랙트로부터 기부자의 주소로 DAO 토큰이 전송되는갸?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + await donation.connect(users[1]).unpledge(1, _amount); + + const campaign = await donation.getCampaign(1); + + expect(await daoToken.balanceOf(donation.address)).to.equal(0); + }); // 4. UnPledge 이벤트가 발생하는가? + it("UnPledge 이벤트가 발생하는가?", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + const campaign = await donation.getCampaign(1); + + expect(await donation.connect(users[1]).unpledge(1, _amount)) + .to.emit(donation, "Unpledge") + .withArgs(1, users[1].address, _amount, campaign.pledged); + }); // 오류케이스 테스트 // 1. 취소금액이 0 보다 작으면 에러가 발생하는가 + + it("취소금액이 0 보다 작으면 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + expect(donation.connect(users[1]).unpledge(1, 0)).to.revertedWith("Amount must be greater than zero"); + }); // 2. 종료된 캠페인에 기부취소시 에러가 발생하는가 + it("종료된 캠페인에 기부취소시 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 10; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await HardhatUtil.passNSeconds(10); + await donation.connect(users[1]).pledge(1, _amount); + + await HardhatUtil.passNSeconds(30); + expect(donation.connect(users[1]).unpledge(1, _amount)).to.revertedWith("Campaign ended"); + }); // 3. 기존에 냈던 금액보다 크게 취소할 시 에러가 발생하는가 + it("기존에 냈던 금액보다 크게 취소할 시 에러가 발생하는가", async () => { + const currentTime = Math.floor(Date.now() / 1000); + const startAt = currentTime + 10; + const endAt = startAt + 3600; + const goal = ethers.utils.parseUnits("1000", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + + const _amount = ethers.utils.parseUnits("10", 18); + const _bigAmount = ethers.utils.parseUnits("20", 18); + + await HardhatUtil.passNSeconds(12); + + await daoToken.connect(admin).transfer(users[1].address, _amount); + + await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[1]).pledge(1, _amount); + + expect(donation.connect(users[1]).unpledge(1, _bigAmount)).to.revertedWith( + "Unpledge amount must be smaller than the amount you pledged", + ); + }); }); - describe("claim함수 테스트", () => { - // 정상케이스 테스트 - // 1. 캠페인 타켓 주소로 DAO 토큰이 전송되는가? - // 2. 캠페인의 클레임 상태가 업데이트 되는가? - // 3. Claim 이벤트가 발생하는가? - // 4. UnPledge 이벤트가 발생하는가? - // 오류케이스 테스트 - // 1. 캠페인 종료 전 실행시 오류가 발생하는가 - // 2. 이미 claimed 된 캠페인에 호출시 에러가 발생하는가 + // describe("claim함수 테스트", () => { + // // 정상케이스 테스트 + // // 1. 캠페인 타켓 주소로 DAO 토큰이 전송되는가? + // // 2. 캠페인의 클레임 상태가 업데이트 되는가? + // // 3. Claim 이벤트가 발생하는가? + // // 4. UnPledge 이벤트가 발생하는가? + // // 오류케이스 테스트 + // // 1. 캠페인 종료 전 실행시 오류가 발생하는가 + // // 2. 이미 claimed 된 캠페인에 호출시 에러가 발생하는가 + // }); + // describe("refund함수 테스트", () => { + // // 정상케이스 테스트 + // // 1. 기부자의 기부 금액이 0으로 초기화 되는가? + // // 2. 컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가? + // // 3. Refund 이벤트가 발생하는가? + // // 오류케이스 테스트 + // // 1. 캠페인 종료 전 실행시 오류가 발생하는가 + // }); + + describe("claim 함수 테스트", () => { + let admin: SignerWithAddress; + let users: SignerWithAddress[]; + let donation: Donation; + let daoToken: DaoToken; + let startAt: number; + let endAt: number; + let goal: BigNumber; + + beforeEach(async () => { + const currentTime = Math.floor(Date.now() / 1000); + startAt = currentTime + 100; + endAt = startAt + 3600; + goal = ethers.utils.parseUnits("100", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + await donation.connect(users[1]).pledge(1, goal); + await HardhatUtil.passNSeconds(3700); + }); + + it("캠페인 타겟 주소로 DAO 토큰이 전송되는가?", async () => { + const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); + await donation.connect(users[0]).claim(1); + const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); + expect(finalBalance.sub(initialBalance)).to.equal(goal); + }); + + it("Claim 이벤트가 발생하는가?", async () => { + await expect(donation.connect(users[0]).claim(1)).to.emit(donation, "Claim").withArgs(1, true, goal); + }); + + it("Claim 호출 후 캠페인의 claimed 상태가 업데이트 되는가?", async () => { + await donation.connect(users[0]).claim(1); + const campaign = await donation.getCampaign(1); + expect(campaign.claimed).to.be.true; + }); }); - describe("refund함수 테스트", () => { - // 정상케이스 테스트 - // 1. 기부자의 기부 금액이 0으로 초기화 되는가? - // 2. 컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가? - // 3. Refund 이벤트가 발생하는가? - // 오류케이스 테스트 - // 1. 캠페인 종료 전 실행시 오류가 발생하는가 + + describe("refund 함수 테스트", () => { + let startAt: number; + let endAt: number; + let goal: BigNumber; + + beforeEach(async () => { + const currentTime = Math.floor(Date.now() / 1000); + startAt = currentTime + 100; + endAt = startAt + 3600; + goal = ethers.utils.parseUnits("100", 18); + + await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + await donation.connect(users[1]).pledge(1, goal); + await HardhatUtil.passNSeconds(3700); + }); + + it("기부자의 기부 금액이 0으로 초기화 되는가?", async () => { + await donation.connect(users[1]).refund(1); + const pledgedAmount: BigNumber = await donation.pledgedUserToAmount(1, users[1].address); + expect(pledgedAmount).to.equal(0); + }); + + it("컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가?", async () => { + const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); + await donation.connect(users[1]).refund(1); + const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); + expect(finalBalance.sub(initialBalance)).to.equal(goal); + }); + + it("Refund 이벤트가 발생하는가?", async () => { + await expect(donation.connect(users[1]).refund(1)) + .to.emit(donation, "Refund") + .withArgs(1, users[1].address, goal); + }); }); }); From c65889b899d36d5556534cefb0b5b3275dc1f772 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Tue, 4 Jun 2024 00:04:29 +0900 Subject: [PATCH 5/7] =?UTF-8?q?Fix:=20Claim=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=EC=99=80=20Refund=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=ED=95=A8=EC=88=98=20=EC=A3=BC=EC=84=9D=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/donation.test.ts | 144 +++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 8d946b7..98b2ec5 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -495,77 +495,77 @@ describe("Dao Token 테스트", () => { // // 1. 캠페인 종료 전 실행시 오류가 발생하는가 // }); - describe("claim 함수 테스트", () => { - let admin: SignerWithAddress; - let users: SignerWithAddress[]; - let donation: Donation; - let daoToken: DaoToken; - let startAt: number; - let endAt: number; - let goal: BigNumber; - - beforeEach(async () => { - const currentTime = Math.floor(Date.now() / 1000); - startAt = currentTime + 100; - endAt = startAt + 3600; - goal = ethers.utils.parseUnits("100", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - await donation.connect(users[1]).pledge(1, goal); - await HardhatUtil.passNSeconds(3700); - }); - - it("캠페인 타겟 주소로 DAO 토큰이 전송되는가?", async () => { - const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); - await donation.connect(users[0]).claim(1); - const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); - expect(finalBalance.sub(initialBalance)).to.equal(goal); - }); - - it("Claim 이벤트가 발생하는가?", async () => { - await expect(donation.connect(users[0]).claim(1)).to.emit(donation, "Claim").withArgs(1, true, goal); - }); - - it("Claim 호출 후 캠페인의 claimed 상태가 업데이트 되는가?", async () => { - await donation.connect(users[0]).claim(1); - const campaign = await donation.getCampaign(1); - expect(campaign.claimed).to.be.true; - }); - }); - - describe("refund 함수 테스트", () => { - let startAt: number; - let endAt: number; - let goal: BigNumber; - - beforeEach(async () => { - const currentTime = Math.floor(Date.now() / 1000); - startAt = currentTime + 100; - endAt = startAt + 3600; - goal = ethers.utils.parseUnits("100", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - await donation.connect(users[1]).pledge(1, goal); - await HardhatUtil.passNSeconds(3700); - }); - - it("기부자의 기부 금액이 0으로 초기화 되는가?", async () => { - await donation.connect(users[1]).refund(1); - const pledgedAmount: BigNumber = await donation.pledgedUserToAmount(1, users[1].address); - expect(pledgedAmount).to.equal(0); - }); - - it("컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가?", async () => { - const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); - await donation.connect(users[1]).refund(1); - const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); - expect(finalBalance.sub(initialBalance)).to.equal(goal); - }); + // describe("claim 함수 테스트", () => { + // let admin: SignerWithAddress; + // let users: SignerWithAddress[]; + // let donation: Donation; + // let daoToken: DaoToken; + // let startAt: number; + // let endAt: number; + // let goal: BigNumber; + + // beforeEach(async () => { + // const currentTime = Math.floor(Date.now() / 1000); + // startAt = currentTime + 100; + // endAt = startAt + 3600; + // goal = ethers.utils.parseUnits("100", 18); + + // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + // await donation.connect(users[1]).pledge(1, goal); + // await HardhatUtil.passNSeconds(3700); + // }); + + // it("캠페인 타겟 주소로 DAO 토큰이 전송되는가?", async () => { + // const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); + // await donation.connect(users[0]).claim(1); + // const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); + // expect(finalBalance.sub(initialBalance)).to.equal(goal); + // }); + + // it("Claim 이벤트가 발생하는가?", async () => { + // await expect(donation.connect(users[0]).claim(1)).to.emit(donation, "Claim").withArgs(1, true, goal); + // }); + + // it("Claim 호출 후 캠페인의 claimed 상태가 업데이트 되는가?", async () => { + // await donation.connect(users[0]).claim(1); + // const campaign = await donation.getCampaign(1); + // expect(campaign.claimed).to.be.true; + // }); + // }); - it("Refund 이벤트가 발생하는가?", async () => { - await expect(donation.connect(users[1]).refund(1)) - .to.emit(donation, "Refund") - .withArgs(1, users[1].address, goal); - }); - }); + // describe("refund 함수 테스트", () => { + // let startAt: number; + // let endAt: number; + // let goal: BigNumber; + + // beforeEach(async () => { + // const currentTime = Math.floor(Date.now() / 1000); + // startAt = currentTime + 100; + // endAt = startAt + 3600; + // goal = ethers.utils.parseUnits("100", 18); + + // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + // await donation.connect(users[1]).pledge(1, goal); + // await HardhatUtil.passNSeconds(3700); + // }); + + // it("기부자의 기부 금액이 0으로 초기화 되는가?", async () => { + // await donation.connect(users[1]).refund(1); + // const pledgedAmount: BigNumber = await donation.pledgedUserToAmount(1, users[1].address); + // expect(pledgedAmount).to.equal(0); + // }); + + // it("컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가?", async () => { + // const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); + // await donation.connect(users[1]).refund(1); + // const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); + // expect(finalBalance.sub(initialBalance)).to.equal(goal); + // }); + + // it("Refund 이벤트가 발생하는가?", async () => { + // await expect(donation.connect(users[1]).refund(1)) + // .to.emit(donation, "Refund") + // .withArgs(1, users[1].address, goal); + // }); + // }); }); From 87ff273c92f8c7fb8df5c27a83acb67abc9400b9 Mon Sep 17 00:00:00 2001 From: Minwoo Date: Sun, 30 Jun 2024 23:25:36 +0900 Subject: [PATCH 6/7] Feat: Donation Contract Finished --- src/contracts/Dao.sol | 147 +++- src/contracts/Donation.sol | 78 +-- src/contracts/interface/DaoInterface.sol | 2 + .../hardhat/todo/001_deploy_contracts.ts | 35 +- src/test/dao.test.ts | 98 +-- src/test/donation.test.ts | 644 ++++++------------ src/test/setup.ts | 4 +- 7 files changed, 425 insertions(+), 583 deletions(-) diff --git a/src/contracts/Dao.sol b/src/contracts/Dao.sol index d7055d7..2770c72 100644 --- a/src/contracts/Dao.sol +++ b/src/contracts/Dao.sol @@ -6,52 +6,153 @@ import {DaoTokenInterface} from "./interface/DaoTokenInterface.sol"; import {DonationInterface} from "./interface/DonationInterface.sol"; import {Initializable} from "./common/upgradeable/Initializable.sol"; -contract Dao { +contract Dao is DaoInterface, Initializable { ///////////// @notice 아래에 변수 추가 //////////// /// @notice Admin 주소 - + address public admin; /// @notice DAO 토큰 컨트랙트 주소 - + DaoTokenInterface public daoToken; /// @notice 기부 컨트랙트 주소 - + DonationInterface public donation; /// @notice DAO 가입시 필요한 DAO 토큰 수량 - + uint256 public daoMembershipAmount; /// @notice DAO 멤버 리스트 - + address[] public daoMemberList; /// @notice 멤버십 신청자 목록 - + address[] public membershipRequests; ///////////// @notice 아래에 매핑 추가 //////////// /// @notice 주소 -> DAO 멤버 여부 - + mapping(address => bool) public isDaoMember; /// @notice 신청자 주소 -> DAO 멤버십 신청 승인 여부 - + mapping(address => MembershipRequestStatusCode) public membershipRequestStatus; /// @notice 투표 아이디 -> 찬성 투표 수 - + mapping(uint256 => uint256) public voteCountYes; /// @notice 투표 아이디 -> 반대 투표 수 - + mapping(uint256 => uint256) public voteCountNo; /// @notice 투표 아이디 -> 투표 진행 여부 - + mapping(uint256 => bool) public voteInProgress; /// @notice 투표 아이디 -> 투표자 주소 -> 투표 여부 + mapping(uint256 => mapping(address => bool)) public hasVoted; + uint256[50] private __gap; ///////////// @notice 아래에 modifier 추가 //////////// /// @notice DAO 멤버만 접근 가능하도록 설정 + modifier onlyDaoMember() { + require(isDaoMember[msg.sender], "Only Dao members can perform this action"); + _; + } /// @notice 관리자만 접근 가능하도록 설정 + modifier onlyAdmin() { + require(msg.sender == admin, "Only admin can mint tokens"); + _; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + function initialize(DonationInterface _donation, DaoTokenInterface _daoToken) public initializer { + admin = msg.sender; + donation = _donation; + daoToken = _daoToken; + } + + function startVote(uint256 _campaignId) external { + uint256 goalAmount = donation.getCampaignGoal(_campaignId); + uint256 totalAmount = donation.getCampaignTotalAmount(_campaignId); + + voteCountYes[_campaignId] = 0; + voteCountNo[_campaignId] = 0; + voteInProgress[_campaignId] = true; + + emit VoteStarted(_campaignId, goalAmount, totalAmount); + } + + function vote(uint256 _campaignId, bool _agree) public onlyDaoMember { + require(voteInProgress[_campaignId], "No vote in progress for this campaign"); + require(!hasVoted[_campaignId][msg.sender], "You have already voted"); + + hasVoted[_campaignId][msg.sender] = true; + _agree ? voteCountYes[_campaignId] += 1 : voteCountNo[_campaignId] += 1; + + if (voteCountYes[_campaignId] + voteCountNo[_campaignId] == daoMemberList.length) { + voteEnd(_campaignId); + } + + emit Voted(_campaignId, msg.sender, _agree); + } + + function voteEnd(uint256 _campaignId) internal { + uint256 DECIMAL_PRECISION = 1e18; + uint256 agreePercentage = (100 * DECIMAL_PRECISION * voteCountYes[_campaignId]) / daoMemberList.length; + string memory approveMessage = "The campaign has been approved for claim."; + string memory rejectMessage = "he campaign was declined for claim."; + + voteInProgress[_campaignId] = false; + + uint256 threshold = 70 * DECIMAL_PRECISION; + + if (agreePercentage >= threshold) { + donation.claim(_campaignId); + emit VoteEnded(_campaignId, true, agreePercentage, approveMessage); + } else { + emit VoteEnded(_campaignId, false, agreePercentage, rejectMessage); + } + } + + function requestDaoMembership() external { + require(!isDaoMember[msg.sender], "User is already a DAO member"); + require(daoToken.balanceOf(msg.sender) >= daoMembershipAmount, "Insufficient DAO tokens"); + + membershipRequests.push(msg.sender); + membershipRequestStatus[msg.sender] = MembershipRequestStatusCode.PENDING; + + emit DaoMembershipRequested(msg.sender, "User has requested DAO membership"); + } + + function handleDaoMembership(address _user, bool _approve) external onlyAdmin { + if (_approve) { + membershipRequestStatus[_user] = MembershipRequestStatusCode.APPROVED; + daoMemberList.push(_user); + isDaoMember[_user] = true; + + emit DaoMembershipApproved(_user, "User has been approved as a DAO member"); + } else { + membershipRequestStatus[_user] = MembershipRequestStatusCode.REJECTED; + + emit DaoMembershipRejected(_user, "User has been rejected as a DAO member"); + } + } + + function removeDaoMembership(address _user) external { + require(isDaoMember[_user], "User is not a DAO member"); + isDaoMember[_user] = false; + + for (uint256 i = 0; i < daoMemberList.length; i++) { + if (daoMemberList[i] == _user) { + if (i < daoMemberList.length - 1) { + daoMemberList[i] = daoMemberList[daoMemberList.length - 1]; + } + daoMemberList.pop(); + break; + } + } + + emit DaoMembershipRemoved(_user, "User has been removed from DAO membership"); + } - function startVote(uint256 _campaignId) external {} - - function vote(uint256 _campaignId, bool agree) public {} - - function voteEnd(uint256 _campaignId) internal {} - - function requestDaoMembership() external {} - - function handleDaoMembership(address _user, bool _approve) external {} + ///////////// @notice 아래에 set함수 & get함수 추가 //////////// - function removeDaoMembership(address _user) external {} + function getMembershipRequests() external view onlyAdmin returns (address[] memory) { + return membershipRequests; + } - ///////////// @notice 아래에 set함수 & get함수 추가 //////////// + function getDaoList() external view onlyAdmin returns (address[] memory) { + return daoMemberList; + } } diff --git a/src/contracts/Donation.sol b/src/contracts/Donation.sol index 5caf6f3..1942f07 100644 --- a/src/contracts/Donation.sol +++ b/src/contracts/Donation.sol @@ -10,30 +10,33 @@ contract Donation is DonationInterface { /// @notice Admin 주소 address public admin; + /// @notice 캠페인 아이디 카운트 uint256 public count; + /// @notice DAO 토큰 컨트랙트 주소 DaoTokenInterface public daoToken; + ///////////// @notice 아래에 매핑 추가 //////////// /// @notice 캠페인 아이디 -> 캠페인 구조체 mapping(uint256 => Campaign) public campaigns; + /// @notice 캠페인 아이디 -> 사용자 주소 -> 기부 금액 mapping(uint256 => mapping(address => uint256)) public pledgedUserToAmount; + ///////////// @notice 아래에 생성자 및 컨트랙트 주소 설정 //////////// /// @notice 관리자 및 DAO Token 컨트랙트 주소 설정 - constructor(address _daoToken) { + constructor(address daoTokenAddr) { admin = msg.sender; - daoToken = DaoTokenInterface(_daoToken); + daoToken = DaoTokenInterface(daoTokenAddr); } + ///////////// @notice 아래에 modifier 추가 //////////// /// @notice 관리자만 접근 가능하도록 설정 - modifier onlyAdmin() { - require(msg.sender == admin, "Only admin can execute"); - _; - } + /// @notice DAO 회원만 접근 가능하도록 설정 function launch( @@ -44,12 +47,11 @@ contract Donation is DonationInterface { uint32 _startAt, uint32 _endAt ) external { - require(_startAt > block.timestamp, "start at < now"); - require(_endAt > _startAt, "end at < start at"); - require(_endAt < block.timestamp + 90 days, "The maximum allowed campaign duration is 90 days."); + require(_startAt >= block.timestamp, "start at < now"); + require(_endAt >= _startAt, "end at < start at"); + require(_endAt <= block.timestamp + 90 days, "end at > max duration"); count += 1; - campaigns[count] = Campaign({ creator: msg.sender, target: _target, @@ -66,75 +68,57 @@ contract Donation is DonationInterface { } function cancel(uint256 _campaignId) external { - Campaign memory campaignInfo; - campaignInfo = campaigns[_campaignId]; - require(msg.sender == campaignInfo.creator, "Only creater can cancel"); - require(campaignInfo.startAt > block.timestamp, "Already Started"); + Campaign memory campaign = campaigns[_campaignId]; + require(msg.sender == campaign.creator, "not creator"); + require(block.timestamp < campaign.startAt, "started"); delete campaigns[_campaignId]; - emit Cancel(_campaignId); } function pledge(uint256 _campaignId, uint256 _amount) external { - Campaign memory campaignInfo; - campaignInfo = campaigns[_campaignId]; - require(campaignInfo.startAt < block.timestamp, "not started"); + Campaign storage campaign = campaigns[_campaignId]; + require(block.timestamp >= campaign.startAt, "not started"); require(!getIsEnded(_campaignId), "Campaign ended"); require(_amount > 0, "Amount must be greater than zero"); - require(daoToken.transferFrom(msg.sender, address(this), _amount), "Token transfer failed"); - - campaigns[_campaignId].pledged += _amount; + campaign.pledged += _amount; pledgedUserToAmount[_campaignId][msg.sender] += _amount; + daoToken.transferFrom(msg.sender, address(this), _amount); - emit Pledge(_campaignId, msg.sender, _amount, campaigns[_campaignId].pledged); + emit Pledge(_campaignId, msg.sender, _amount, campaign.pledged); } function unpledge(uint256 _campaignId, uint256 _amount) external { - Campaign memory campaignInfo; - campaignInfo = campaigns[_campaignId]; + Campaign storage campaign = campaigns[_campaignId]; require(_amount > 0, "Amount must be greater than zero"); require(!getIsEnded(_campaignId), "Campaign ended"); - require( - _amount <= pledgedUserToAmount[_campaignId][msg.sender], - "Unpledge amount must be smaller than the amount you pledged" - ); - - require(daoToken.transfer(msg.sender, _amount)); - campaigns[_campaignId].pledged -= _amount; + campaign.pledged -= _amount; pledgedUserToAmount[_campaignId][msg.sender] -= _amount; + daoToken.transfer(msg.sender, _amount); - emit Unpledge(_campaignId, msg.sender, _amount, campaigns[_campaignId].pledged); + emit Unpledge(_campaignId, msg.sender, _amount, campaign.pledged); } - //2. onlyDao modifier 추가 function claim(uint256 _campaignId) external { require(getIsEnded(_campaignId), "Campaign not ended"); - Campaign memory campaignInfo; - campaignInfo = campaigns[_campaignId]; - - require(!campaignInfo.claimed, "claimed"); - require(daoToken.transfer(campaignInfo.target, campaignInfo.pledged), "Token transfer failed"); - - campaigns[_campaignId].claimed = true; + Campaign storage campaign = campaigns[_campaignId]; + require(!campaign.claimed, "claimed"); - // event Claim(uint256 indexed campaignId, bool claimed, uint256 amount); + daoToken.transfer(campaign.target, campaign.pledged); + campaign.claimed = true; - emit Claim(_campaignId, campaigns[_campaignId].claimed, campaigns[_campaignId].pledged); + emit Claim(_campaignId, campaign.claimed, campaign.pledged); } function refund(uint256 _campaignId) external { require(getIsEnded(_campaignId), "Campaign not ended"); - uint256 bal = pledgedUserToAmount[_campaignId][msg.sender]; - - require(daoToken.transfer(msg.sender, bal), "Token transfer failed"); + uint256 bal = pledgedUserToAmount[_campaignId][msg.sender]; pledgedUserToAmount[_campaignId][msg.sender] = 0; - - // event Refund(uint256 indexed campaignId, address indexed caller, uint256 amount); + daoToken.transfer(msg.sender, bal); emit Refund(_campaignId, msg.sender, bal); } diff --git a/src/contracts/interface/DaoInterface.sol b/src/contracts/interface/DaoInterface.sol index 94e44a8..d94f0ac 100644 --- a/src/contracts/interface/DaoInterface.sol +++ b/src/contracts/interface/DaoInterface.sol @@ -23,6 +23,8 @@ interface DaoInterface { function getDaoList() external view returns (address[] memory); + function isDaoMember(address _user) external view returns (bool); + event VoteStarted(uint256 indexed campaignId, uint256 goalAmount, uint256 totalAmount); event Voted(uint256 indexed campaignId, address voter, bool agree); diff --git a/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts b/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts index 8a8fbf8..b44fa9e 100644 --- a/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts +++ b/src/scripts/deploy/hardhat/todo/001_deploy_contracts.ts @@ -1,5 +1,3 @@ -//001_deploy_contracts.ts - import { hardhatInfo } from "@constants"; import { ethers } from "hardhat"; import { DeployFunction } from "hardhat-deploy/types"; @@ -26,23 +24,32 @@ const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { autoMine: true, }); - // const DaoContract = await deploy("Dao", { + //이곳에 코드를 추가할 예정입니다. + const initializeParams = [DaoTokenContract.address, DonationContract.address]; + + await deploy("Dao", { + from: developer.address, + contract: "Dao", + proxy: { + execute: { + init: { + methodName: "initialize", // initializer modifier가 붙은 함수의 이름 + args: initializeParams, // initialize 실행 시 필요한 파라미터, 배열로 전달 + }, + }, + }, + log: true, + autoMine: true, + }); + + // 이후 업그레이드 시 사용 + // await deploy("Dao", { // from: developer.address, // contract: "Dao", - // proxy: { - // execute: { - // init: { - // methodName: "initialize", - // args: [DaoTokenContract.address, DonationContract.address], - // }, - // }, - // }, + // proxy: true, // log: true, // autoMine: true, // }); - - // const donation = await ethers.getContractAt("Donation", DonationContract.address); - // await donation.connect(developer).setDaoAddress(DaoContract.address); }; export default func; diff --git a/src/test/dao.test.ts b/src/test/dao.test.ts index 1b929e5..00455af 100644 --- a/src/test/dao.test.ts +++ b/src/test/dao.test.ts @@ -1,49 +1,49 @@ -// import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; -// import { setup } from "./setup"; -// import { DaoToken, Dao, Donation } from "@typechains"; -// import { expect } from "chai"; -// import { ethers, network } from "hardhat"; -// import { hardhatInfo } from "@constants"; -// import { faker } from "@faker-js/faker"; -// import { BigNumber } from "ethers"; -// import { HardhatUtil } from "./lib/hardhat_utils"; -// import { GAS_PER_TRANSACTION } from "./mock/mock"; - -// describe("Dao Token 테스트", () => { -// /* Signer */ -// let admin: SignerWithAddress; -// let users: SignerWithAddress[]; - -// /* 컨트랙트 객체 */ -// let daoToken: DaoToken; -// let dao: Dao; -// let donation: Donation; - -// /* 테스트 스냅샷 */ -// let initialSnapshotId: number; -// let snapshotId: number; - -// before(async () => { -// /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ -// ({ admin, users, daoToken, dao, donation } = await setup()); -// initialSnapshotId = await network.provider.send("evm_snapshot"); -// }); - -// beforeEach(async () => { -// snapshotId = await network.provider.send("evm_snapshot"); -// }); - -// afterEach(async () => { -// await network.provider.send("evm_revert", [snapshotId]); -// }); - -// after(async () => { -// await network.provider.send("evm_revert", [initialSnapshotId]); -// }); - -// it("Hardhat 환경 배포 테스트", () => { -// expect(daoToken.address).to.not.be.undefined; -// expect(dao.address).to.not.be.undefined; -// expect(donation.address).to.not.be.undefined; -// }); -// }); +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; +import { setup } from "./setup"; +import { DaoToken, Dao, Donation } from "@typechains"; +import { expect } from "chai"; +import { ethers, network } from "hardhat"; +import { hardhatInfo } from "@constants"; +import { faker } from "@faker-js/faker"; +import { BigNumber } from "ethers"; +import { HardhatUtil } from "./lib/hardhat_utils"; +import { GAS_PER_TRANSACTION } from "./mock/mock"; + +describe("Dao Token 테스트", () => { + /* Signer */ + let admin: SignerWithAddress; + let users: SignerWithAddress[]; + + /* 컨트랙트 객체 */ + let daoToken: DaoToken; + let dao: Dao; + let donation: Donation; + + /* 테스트 스냅샷 */ + let initialSnapshotId: number; + let snapshotId: number; + + before(async () => { + /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ + ({ admin, users, daoToken, donation, dao } = await setup()); + initialSnapshotId = await network.provider.send("evm_snapshot"); + }); + + beforeEach(async () => { + snapshotId = await network.provider.send("evm_snapshot"); + }); + + afterEach(async () => { + await network.provider.send("evm_revert", [snapshotId]); + }); + + after(async () => { + await network.provider.send("evm_revert", [initialSnapshotId]); + }); + + it("Hardhat 환경 배포 테스트", () => { + expect(daoToken.address).to.not.be.undefined; + expect(donation.address).to.not.be.undefined; + expect(dao.address).to.not.be.undefined; + }); +}); diff --git a/src/test/donation.test.ts b/src/test/donation.test.ts index 98b2ec5..3fc1de8 100644 --- a/src/test/donation.test.ts +++ b/src/test/donation.test.ts @@ -3,20 +3,16 @@ import { setup } from "./setup"; import { DaoToken, Dao, Donation } from "@typechains"; import { expect } from "chai"; import { ethers, network } from "hardhat"; -import { hardhatInfo } from "@constants"; -import { faker } from "@faker-js/faker"; -import { BigNumber } from "ethers"; import { HardhatUtil } from "./lib/hardhat_utils"; -import { GAS_PER_TRANSACTION } from "./mock/mock"; +import { mockCampaign } from "./mock/mock"; -describe("Dao Token 테스트", () => { +describe("Donation 테스트", () => { /* Signer */ let admin: SignerWithAddress; let users: SignerWithAddress[]; /* 컨트랙트 객체 */ let daoToken: DaoToken; - let dao: Dao; let donation: Donation; /* 테스트 스냅샷 */ @@ -43,529 +39,281 @@ describe("Dao Token 테스트", () => { it("Hardhat 환경 배포 테스트", () => { expect(daoToken.address).to.not.be.undefined; - // expect(dao.address).to.not.be.undefined; expect(donation.address).to.not.be.undefined; }); - describe("launch함수 테스트", () => { - // 정상케이스 테스트 - // 1. Count 변수가 정상적으로 반영되었는가 - it("Count 변수가 정상적으로 반영되었는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); + describe("캠페인 생성(Launch) 테스트", () => { + it("launch 함수가 시작 시간이 현재 시간보다 이전인 경우 실패하는지 확인", async () => { + const campaignData = mockCampaign({ startAt: Math.floor(Date.now() / 1000) - 1000 }); + const { target, title, description, goal, startAt, endAt } = campaignData; - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - expect(await donation.count()).to.equal(1); + await expect( + donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt), + ).to.be.revertedWith("start at < now"); }); - // 2. campaign 객체가 정상적으로 반영되었는가 - it("campaign 객체가 정상적으로 반영되었는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - const campaign = await donation.getCampaign(1); - - expect(campaign.creator).to.equal(users[0].address); - expect(campaign.target).to.equal(users[1].address); - expect(campaign.title).to.equal("test"); - expect(campaign.description).to.equal("test description"); - expect(campaign.goal.toString()).to.equal(goal.toString()); - expect(campaign.startAt).to.equal(startAt); - expect(campaign.endAt).to.equal(endAt); - expect(campaign.claimed).to.be.false; - }); - // 3. launch 이벤트가 발생하였는가 - it("launch 이벤트가 발생하였는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); + it("launch 함수가 종료 시간이 시작 시간보다 이전인 경우 실패하는지 확인", async () => { + const campaignData = mockCampaign({ + startAt: Math.floor(Date.now() / 1000) + 1000, + endAt: Math.floor(Date.now() / 1000) - 1000, + }); + const { target, title, description, goal, startAt, endAt } = campaignData; await expect( - donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), - ) - .to.emit(donation, "Launch") - .withArgs(1, await donation.getCampaign(1)); - }); - // 오류케이스 테스트 - // 1. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 - it("시작시간이 현재시간보다 빠르면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime - 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - expect( - donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), - ).to.be.revertedWith("start at < now"); - }); - // 2. 종료시간이 시작시간보다 빠르면 에러가 발생하는가 - it("종료시간이 시작시간보다 빠르면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 8; - const goal = ethers.utils.parseUnits("1000", 18); - - expect( - donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), + donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt), ).to.be.revertedWith("end at < start at"); }); - // 3. 캠페인 기간이 90일을 넘으면 에러가 발생하는가 - it("캠페인 기간이 90일을 넘으면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 2678400; // 31 days - const goal = ethers.utils.parseUnits("1000", 18); - - expect( - donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt), - ).to.be.revertedWith("The maximum allowed campaign duration is 90 days."); - }); - }); - describe("cancel함수 테스트", () => { - // 정상케이스 테스트 - // 1. 캠페인이 정상적으로 삭제되는가? - it("캠페인이 정상적으로 삭제되는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - await donation.connect(users[0]).cancel(1); - - const campaign = await donation.getCampaign(1); - - expect(campaign.creator).to.equal("0x0000000000000000000000000000000000000000"); - expect(campaign.target).to.equal("0x0000000000000000000000000000000000000000"); - expect(campaign.title).to.equal(""); - expect(campaign.description).to.equal(""); - expect(campaign.goal.toString()).to.equal("0"); - expect(campaign.pledged.toString()).to.equal("0"); - expect(campaign.startAt).to.equal(0); - expect(campaign.endAt).to.equal(0); - expect(campaign.claimed).to.be.false; - }); - // 2 cancel 이벤트가 발생하였는가 - it("cancel 이벤트가 발생하였는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - const campaign = await donation.getCampaign(1); + it("launch 함수가 종료 시간이 90일을 초과하는 경우 실패하는지 확인", async () => { + const campaignData = mockCampaign({ endAt: Math.floor(Date.now() / 1000) + 100 * 24 * 60 * 60 }); + const { target, title, description, goal, startAt, endAt } = campaignData; - await expect(donation.connect(users[0]).cancel(1)).to.emit(donation, "Cancel").withArgs(1); + await expect( + donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt), + ).to.be.revertedWith("end at > max duration"); }); - // 오류케이스 테스트 - // 1. creater 외 호출시 에러가 발생하는가 - it("creater 외 호출시 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - await expect(donation.connect(users[1]).cancel(1)).to.revertedWith("Only creater can cancel"); - }); - // 2. 시작시간이 현재시간보다 빠르면 에러가 발생하는가 - it("시작시간이 현재시간보다 빠르면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); + it("launch 함수 실행 후 캠페인 정보가 정상적으로 등록되는지 확인", async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); - await HardhatUtil.passNSeconds(100); + const campaign = await donation.campaigns(1); + expect(campaign.creator).to.equal(users[0].address); + expect(campaign.target).to.equal(target); + expect(campaign.title).to.equal(title); + expect(campaign.description).to.equal(description); + expect(campaign.goal).to.equal(goal); + expect(campaign.startAt).to.equal(startAt); + expect(campaign.endAt).to.equal(endAt); + expect(campaign.pledged).to.equal(0); + expect(campaign.claimed).to.equal(false); - await expect(donation.connect(users[0]).cancel(1)).to.revertedWith("Already Started"); + expect(await donation.count()).to.equal(1); }); - }); - describe("pledge함수 테스트", () => { - // 정상케이스 테스트 - // 1. 기부금액 ( pledged )가 정상적으로 업데이트 되는가? - it("기부금액 ( pledged )가 정상적으로 업데이트 되는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - const _amount = ethers.utils.parseUnits("10", 18); - - await HardhatUtil.passNSeconds(12); - - await daoToken.connect(admin).transfer(users[1].address, _amount); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); + it("launch 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; - const campaign = await donation.getCampaign(1); - - expect(campaign.pledged.toString()).to.equal(_amount.toString()); + await expect(donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt)) + .to.emit(donation, "Launch") + .withArgs(1, [users[0].address, target, title, description, goal, 0, startAt, endAt, false]); }); - // 2. 캠페인 아이디와 기부자의 기부금액이 기록되는가? - it("캠페인 아이디와 기부자의 기부금액이 기록되는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - const _amount = ethers.utils.parseUnits("10", 18); - - await HardhatUtil.passNSeconds(12); - - await daoToken.connect(admin).transfer(users[1].address, _amount); - - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); - - const campaign = await donation.getCampaign(1); + }); - const pledgedAmount = await donation.pledgedUserToAmount(1, users[1].address); - expect(pledgedAmount.toString()).to.equal(_amount.toString()); + describe("캠페인 취소(Cancel) 테스트", () => { + beforeEach(async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); }); - // 3. 기부자로부터 컨트랙트로 DAO 토큰이 전송되는갸? - it("기부자로부터 컨트랙트로 DAO 토큰이 전송되는갸?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - const _amount = ethers.utils.parseUnits("10", 18); - - await HardhatUtil.passNSeconds(12); - - await daoToken.connect(admin).transfer(users[1].address, _amount); - - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); - - await expect((await daoToken.balanceOf(donation.address)).toString()).to.equal(_amount.toString()); + it("cancel 함수가 캠페인 생성자에 의해 호출되지 않는 경우 실패하는지 확인", async () => { + await expect(donation.connect(users[1]).cancel(1)).to.be.revertedWith("not creator"); }); - // 4. Pledge 이벤트가 발생하는가? - it("Pledge 이벤트가 발생하는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("cancel 함수가 캠페인이 시작된 후 호출되는 경우 실패하는지 확인", async () => { + const campaign = await donation.campaigns(1); + const currentTime = await HardhatUtil.blockTimeStamp(); + const startInFuture = campaign.startAt - currentTime + 10; - const _amount = ethers.utils.parseUnits("10", 18); + await HardhatUtil.passNSeconds(startInFuture); - await HardhatUtil.passNSeconds(12); + await expect(donation.connect(users[0]).cancel(1)).to.be.revertedWith("started"); + }); - await daoToken.connect(admin).transfer(users[1].address, _amount); + it("cancel 함수가 정상적으로 실행되는지 확인", async () => { + const newCampaignData = mockCampaign({ startAt: Math.floor(Date.now() / 1000) + 5000 }); + const { target, title, description, goal, startAt, endAt } = newCampaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); - await daoToken.connect(users[1]).approve(donation.address, _amount); + await donation.connect(users[0]).cancel(2); + const campaign = await donation.campaigns(2); - expect(await donation.connect(users[1]).pledge(1, _amount)) - .to.emit(donation, "Pledge") - .withArgs(1, _amount); + expect(campaign.creator).to.equal(ethers.constants.AddressZero); }); - // 오류케이스 테스트 - // 1. 캠페인 시작 전 실행시 에러가 발생하는가 - it("캠페인 시작 전 실행시 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 1000; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - const _amount = ethers.utils.parseUnits("10", 18); - await HardhatUtil.passNSeconds(12); + it("cancel 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + const newCampaignData = mockCampaign({ startAt: Math.floor(Date.now() / 1000) + 5000 }); + const { target, title, description, goal, startAt, endAt } = newCampaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); - await daoToken.connect(admin).transfer(users[1].address, _amount); + await expect(donation.connect(users[0]).cancel(2)).to.emit(donation, "Cancel").withArgs(2); + }); + }); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await expect(donation.connect(users[1]).pledge(1, _amount)).to.revertedWith("not started"); + describe("기부(Pledge) 테스트", () => { + beforeEach(async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); + await HardhatUtil.setNextBlockTimestamp(startAt); + await HardhatUtil.mineNBlocks(1); }); - // 2. 종료된 캠페인에 기부시 에러가 발생하는가 - it("종료된 캠페인에 기부시 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 10; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("pledge 함수가 캠페인이 종료된 경우 실패하는지 확인", async () => { + const amount = HardhatUtil.ToETH(1); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); - const _amount = ethers.utils.parseUnits("10", 18); + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); - await HardhatUtil.passNSeconds(30); + await expect(donation.connect(users[1]).pledge(1, amount)).to.be.revertedWith("Campaign ended"); + }); - await daoToken.connect(admin).transfer(users[1].address, _amount); + it("pledge 함수가 0 이상의 금액을 기부하는지 확인", async () => { + const amount = HardhatUtil.ToETH(0); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await expect(donation.connect(users[1]).pledge(1, _amount)).to.revertedWith("Campaign ended"); + await expect(donation.connect(users[1]).pledge(1, amount)).to.be.revertedWith("Amount must be greater than zero"); }); - // 3. 기부금액이 0 원이면 에러가 발생하는가 - it("기부금액이 0 원이면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 1000; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("pledge 함수 실행 후 캠페인에 기부금이 정상적으로 반영되는지 확인", async () => { + const amount = HardhatUtil.ToETH(1); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); - const _amount = ethers.utils.parseUnits("10", 18); + await donation.connect(users[1]).pledge(1, amount); + const campaign = await donation.campaigns(1); - await HardhatUtil.passNSeconds(30); + expect(campaign.pledged).to.equal(amount); + }); - await daoToken.connect(admin).transfer(users[1].address, _amount); + it("pledge 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + const amount = HardhatUtil.ToETH(1); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await expect(donation.connect(users[1]).pledge(1, 0)).to.revertedWith("Amount must be greater than zero"); + await expect(donation.connect(users[1]).pledge(1, amount)) + .to.emit(donation, "Pledge") + .withArgs(1, users[1].address, amount, amount); }); }); - describe("unpledge함수 테스트", () => { - // 정상케이스 테스트 - // 1. 기부금액 ( unpledged )가 정상적으로 업데이트 되는가? - it("기부금액 ( unpledged )가 정상적으로 업데이트 되는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - - const _amount = ethers.utils.parseUnits("10", 18); - - await HardhatUtil.passNSeconds(12); - - await daoToken.connect(admin).transfer(users[1].address, _amount); - - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); - - await donation.connect(users[1]).unpledge(1, _amount); - - const campaign = await donation.getCampaign(1); - expect(campaign.pledged.toString()).to.equal("0"); + describe("기부 취소(Unpledge) 테스트", () => { + beforeEach(async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); + await HardhatUtil.setNextBlockTimestamp(startAt); + await HardhatUtil.mineNBlocks(1); + + const amount = HardhatUtil.ToETH(1); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); + await donation.connect(users[1]).pledge(1, amount); }); - // 3. 컨트랙트로부터 기부자의 주소로 DAO 토큰이 전송되는갸? - it("컨트랙트로부터 기부자의 주소로 DAO 토큰이 전송되는갸?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("unpledge 함수가 0 이상의 금액을 기부 취소하는지 확인", async () => { + const amount = HardhatUtil.ToETH(0); - const _amount = ethers.utils.parseUnits("10", 18); - - await HardhatUtil.passNSeconds(12); - - await daoToken.connect(admin).transfer(users[1].address, _amount); - - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); - - await donation.connect(users[1]).unpledge(1, _amount); - - const campaign = await donation.getCampaign(1); - - expect(await daoToken.balanceOf(donation.address)).to.equal(0); + await expect(donation.connect(users[1]).unpledge(1, amount)).to.be.revertedWith( + "Amount must be greater than zero", + ); }); - // 4. UnPledge 이벤트가 발생하는가? - it("UnPledge 이벤트가 발생하는가?", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("unpledge 함수가 캠페인이 종료된 경우 실패하는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); - const _amount = ethers.utils.parseUnits("10", 18); + await expect(donation.connect(users[1]).unpledge(1, HardhatUtil.ToETH(1))).to.be.revertedWith("Campaign ended"); + }); - await HardhatUtil.passNSeconds(12); + it("unpledge 함수 실행 후 캠페인에 기부 취소 금액이 정상적으로 반영되는지 확인", async () => { + const amount = HardhatUtil.ToETH(1); - await daoToken.connect(admin).transfer(users[1].address, _amount); + await donation.connect(users[1]).unpledge(1, amount); + const campaign = await donation.campaigns(1); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); + expect(campaign.pledged).to.equal(HardhatUtil.ToETH(0)); + }); - const campaign = await donation.getCampaign(1); + it("unpledge 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + const amount = HardhatUtil.ToETH(1); - expect(await donation.connect(users[1]).unpledge(1, _amount)) + await expect(donation.connect(users[1]).unpledge(1, amount)) .to.emit(donation, "Unpledge") - .withArgs(1, users[1].address, _amount, campaign.pledged); + .withArgs(1, users[1].address, amount, HardhatUtil.ToETH(0)); }); - // 오류케이스 테스트 - // 1. 취소금액이 0 보다 작으면 에러가 발생하는가 - - it("취소금액이 0 보다 작으면 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + }); - const _amount = ethers.utils.parseUnits("10", 18); + describe("기부금 수령(Claim) 테스트", () => { + beforeEach(async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); + await HardhatUtil.setNextBlockTimestamp(startAt); + await HardhatUtil.mineNBlocks(1); + + const amount = HardhatUtil.ToETH(10); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); + await donation.connect(users[1]).pledge(1, amount); + }); - await HardhatUtil.passNSeconds(12); + it("claim 함수가 캠페인이 종료되지 않은 경우 실패하는지 확인", async () => { + await expect(donation.connect(users[0]).claim(1)).to.be.revertedWith("Campaign not ended"); + }); - await daoToken.connect(admin).transfer(users[1].address, _amount); + it("claim 함수가 이미 수령된 캠페인에서 호출된 경우 실패하는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); + await donation.connect(users[0]).claim(1); - expect(donation.connect(users[1]).unpledge(1, 0)).to.revertedWith("Amount must be greater than zero"); + await expect(donation.connect(users[0]).claim(1)).to.be.revertedWith("claimed"); }); - // 2. 종료된 캠페인에 기부취소시 에러가 발생하는가 - it("종료된 캠페인에 기부취소시 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 10; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("claim 함수 실행 후 기부금이 정상적으로 수령되는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); + await donation.connect(users[0]).claim(1); - const _amount = ethers.utils.parseUnits("10", 18); + const campaign = await donation.campaigns(1); + expect(campaign.claimed).to.be.true; + }); - await daoToken.connect(admin).transfer(users[1].address, _amount); + it("claim 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); + await expect(donation.connect(users[0]).claim(1)) + .to.emit(donation, "Claim") + .withArgs(1, true, HardhatUtil.ToETH(10)); + }); + }); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await HardhatUtil.passNSeconds(10); - await donation.connect(users[1]).pledge(1, _amount); + describe("환불(Refund) 테스트", () => { + beforeEach(async () => { + const campaignData = mockCampaign(); + const { target, title, description, goal, startAt, endAt } = campaignData; + await donation.connect(users[0]).launch(target, title, description, goal, startAt, endAt); + await HardhatUtil.setNextBlockTimestamp(startAt); + await HardhatUtil.mineNBlocks(1); + + const amount = HardhatUtil.ToETH(1); + await daoToken.transfer(users[1].address, amount); + await daoToken.connect(users[1]).approve(donation.address, amount); + await donation.connect(users[1]).pledge(1, amount); + }); - await HardhatUtil.passNSeconds(30); - expect(donation.connect(users[1]).unpledge(1, _amount)).to.revertedWith("Campaign ended"); + it("refund 함수가 캠페인이 종료되지 않은 경우 실패하는지 확인", async () => { + await expect(donation.connect(users[1]).refund(1)).to.be.revertedWith("Campaign not ended"); }); - // 3. 기존에 냈던 금액보다 크게 취소할 시 에러가 발생하는가 - it("기존에 냈던 금액보다 크게 취소할 시 에러가 발생하는가", async () => { - const currentTime = Math.floor(Date.now() / 1000); - const startAt = currentTime + 10; - const endAt = startAt + 3600; - const goal = ethers.utils.parseUnits("1000", 18); - await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); + it("refund 함수 실행 후 기부자가 환불받는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); - const _amount = ethers.utils.parseUnits("10", 18); - const _bigAmount = ethers.utils.parseUnits("20", 18); + const initialBalance = await daoToken.balanceOf(users[1].address); - await HardhatUtil.passNSeconds(12); + await donation.connect(users[1]).refund(1); + const finalBalance = await daoToken.balanceOf(users[1].address); - await daoToken.connect(admin).transfer(users[1].address, _amount); + expect(finalBalance).to.equal(initialBalance.add(HardhatUtil.ToETH(1))); + }); - await daoToken.connect(users[1]).approve(donation.address, _amount); - await donation.connect(users[1]).pledge(1, _amount); + it("refund 함수 실행 후 이벤트가 정상적으로 발생하는지 확인", async () => { + await HardhatUtil.setNextBlockTimestamp((await donation.campaigns(1)).endAt); - expect(donation.connect(users[1]).unpledge(1, _bigAmount)).to.revertedWith( - "Unpledge amount must be smaller than the amount you pledged", - ); + await expect(donation.connect(users[1]).refund(1)) + .to.emit(donation, "Refund") + .withArgs(1, users[1].address, HardhatUtil.ToETH(1)); }); }); - // describe("claim함수 테스트", () => { - // // 정상케이스 테스트 - // // 1. 캠페인 타켓 주소로 DAO 토큰이 전송되는가? - // // 2. 캠페인의 클레임 상태가 업데이트 되는가? - // // 3. Claim 이벤트가 발생하는가? - // // 4. UnPledge 이벤트가 발생하는가? - // // 오류케이스 테스트 - // // 1. 캠페인 종료 전 실행시 오류가 발생하는가 - // // 2. 이미 claimed 된 캠페인에 호출시 에러가 발생하는가 - // }); - // describe("refund함수 테스트", () => { - // // 정상케이스 테스트 - // // 1. 기부자의 기부 금액이 0으로 초기화 되는가? - // // 2. 컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가? - // // 3. Refund 이벤트가 발생하는가? - // // 오류케이스 테스트 - // // 1. 캠페인 종료 전 실행시 오류가 발생하는가 - // }); - - // describe("claim 함수 테스트", () => { - // let admin: SignerWithAddress; - // let users: SignerWithAddress[]; - // let donation: Donation; - // let daoToken: DaoToken; - // let startAt: number; - // let endAt: number; - // let goal: BigNumber; - - // beforeEach(async () => { - // const currentTime = Math.floor(Date.now() / 1000); - // startAt = currentTime + 100; - // endAt = startAt + 3600; - // goal = ethers.utils.parseUnits("100", 18); - - // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - // await donation.connect(users[1]).pledge(1, goal); - // await HardhatUtil.passNSeconds(3700); - // }); - - // it("캠페인 타겟 주소로 DAO 토큰이 전송되는가?", async () => { - // const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); - // await donation.connect(users[0]).claim(1); - // const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); - // expect(finalBalance.sub(initialBalance)).to.equal(goal); - // }); - - // it("Claim 이벤트가 발생하는가?", async () => { - // await expect(donation.connect(users[0]).claim(1)).to.emit(donation, "Claim").withArgs(1, true, goal); - // }); - - // it("Claim 호출 후 캠페인의 claimed 상태가 업데이트 되는가?", async () => { - // await donation.connect(users[0]).claim(1); - // const campaign = await donation.getCampaign(1); - // expect(campaign.claimed).to.be.true; - // }); - // }); - - // describe("refund 함수 테스트", () => { - // let startAt: number; - // let endAt: number; - // let goal: BigNumber; - - // beforeEach(async () => { - // const currentTime = Math.floor(Date.now() / 1000); - // startAt = currentTime + 100; - // endAt = startAt + 3600; - // goal = ethers.utils.parseUnits("100", 18); - - // await donation.connect(users[0]).launch(users[1].address, "test", "test description", goal, startAt, endAt); - // await donation.connect(users[1]).pledge(1, goal); - // await HardhatUtil.passNSeconds(3700); - // }); - - // it("기부자의 기부 금액이 0으로 초기화 되는가?", async () => { - // await donation.connect(users[1]).refund(1); - // const pledgedAmount: BigNumber = await donation.pledgedUserToAmount(1, users[1].address); - // expect(pledgedAmount).to.equal(0); - // }); - - // it("컨트랙트로부터 기부자에게 DAO 토큰이 전송되는가?", async () => { - // const initialBalance: BigNumber = await daoToken.balanceOf(users[1].address); - // await donation.connect(users[1]).refund(1); - // const finalBalance: BigNumber = await daoToken.balanceOf(users[1].address); - // expect(finalBalance.sub(initialBalance)).to.equal(goal); - // }); - - // it("Refund 이벤트가 발생하는가?", async () => { - // await expect(donation.connect(users[1]).refund(1)) - // .to.emit(donation, "Refund") - // .withArgs(1, users[1].address, goal); - // }); - // }); }); diff --git a/src/test/setup.ts b/src/test/setup.ts index 6bf7a3a..cfe508c 100644 --- a/src/test/setup.ts +++ b/src/test/setup.ts @@ -1,4 +1,3 @@ -//setup.ts import { DaoToken, Donation, Dao } from "@typechains"; import { deployments, ethers } from "hardhat"; @@ -7,10 +6,11 @@ export const setup = async () => { const [admin, ...users] = await ethers.getSigners(); /* 컨트랙트 데이터 설정: deployments.fixture를 통하여 hardhat 환경에 배포된 컨트랙트 정보를 가져온다. */ - await deployments.fixture(["DaoToken", "Donation"]); + await deployments.fixture(["DaoToken", "Donation", "Dao"]); const contracts = { daoToken: await ethers.getContract("DaoToken"), donation: await ethers.getContract("Donation"), + dao: await ethers.getContract("Dao"), }; return { From 23d3499db591400c76ba6cd43ba3d31c3f9df29f Mon Sep 17 00:00:00 2001 From: Minwoo Date: Sun, 30 Jun 2024 23:55:49 +0900 Subject: [PATCH 7/7] Feat: DAO Contract Test finished with bugs --- src/test/dao.test.ts | 310 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 261 insertions(+), 49 deletions(-) diff --git a/src/test/dao.test.ts b/src/test/dao.test.ts index 00455af..c05e196 100644 --- a/src/test/dao.test.ts +++ b/src/test/dao.test.ts @@ -1,49 +1,261 @@ -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; -import { setup } from "./setup"; -import { DaoToken, Dao, Donation } from "@typechains"; -import { expect } from "chai"; -import { ethers, network } from "hardhat"; -import { hardhatInfo } from "@constants"; -import { faker } from "@faker-js/faker"; -import { BigNumber } from "ethers"; -import { HardhatUtil } from "./lib/hardhat_utils"; -import { GAS_PER_TRANSACTION } from "./mock/mock"; - -describe("Dao Token 테스트", () => { - /* Signer */ - let admin: SignerWithAddress; - let users: SignerWithAddress[]; - - /* 컨트랙트 객체 */ - let daoToken: DaoToken; - let dao: Dao; - let donation: Donation; - - /* 테스트 스냅샷 */ - let initialSnapshotId: number; - let snapshotId: number; - - before(async () => { - /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ - ({ admin, users, daoToken, donation, dao } = await setup()); - initialSnapshotId = await network.provider.send("evm_snapshot"); - }); - - beforeEach(async () => { - snapshotId = await network.provider.send("evm_snapshot"); - }); - - afterEach(async () => { - await network.provider.send("evm_revert", [snapshotId]); - }); - - after(async () => { - await network.provider.send("evm_revert", [initialSnapshotId]); - }); - - it("Hardhat 환경 배포 테스트", () => { - expect(daoToken.address).to.not.be.undefined; - expect(donation.address).to.not.be.undefined; - expect(dao.address).to.not.be.undefined; - }); -}); +// import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signers"; +// import { setup } from "./setup"; +// import { DaoToken, Dao, Donation } from "@typechains"; +// import { expect } from "chai"; +// import { ethers, network } from "hardhat"; +// import { hardhatInfo } from "@constants"; +// import { faker } from "@faker-js/faker"; +// import { BigNumber } from "ethers"; +// import { HardhatUtil } from "./lib/hardhat_utils"; +// import { GAS_PER_TRANSACTION } from "./mock/mock"; + +// describe("Dao 테스트", () => { +// /* Signer */ +// let admin: SignerWithAddress; +// let users: SignerWithAddress[]; + +// /* 컨트랙트 객체 */ +// let daoToken: DaoToken; +// let dao: Dao; +// let donation: Donation; + +// /* 테스트 스냅샷 */ +// let initialSnapshotId: number; +// let snapshotId: number; + +// before(async () => { +// /* 테스트에 필요한 컨트랙트 및 Signer 정보를 불러오는 함수 */ +// ({ admin, users, daoToken, dao, donation } = await setup()); +// initialSnapshotId = await network.provider.send("evm_snapshot"); +// }); + +// beforeEach(async () => { +// snapshotId = await network.provider.send("evm_snapshot"); +// }); + +// afterEach(async () => { +// await network.provider.send("evm_revert", [snapshotId]); +// }); + +// after(async () => { +// await network.provider.send("evm_revert", [initialSnapshotId]); +// }); + +// it("Hardhat 환경 배포 테스트", () => { +// expect(daoToken.address).to.not.be.undefined; +// expect(donation.address).to.not.be.undefined; +// expect(dao.address).to.not.be.undefined; +// }); + +// describe("DaoToken 초기화 테스트", () => { +// it("DaoToken의 관리자가 정상적으로 설정되어 있는지 확인", async () => { +// expect(await daoToken.admin()).to.equal(admin.address); +// }); + +// it("DaoToken의 초기 값이 정상적으로 설정되어 있는지 확인", async () => { +// const { daoTokenName, daoTokenSymbol, exchangeRate } = hardhatInfo; +// await Promise.all([ +// expect(await daoToken.name()).to.equal(daoTokenName), +// expect(await daoToken.symbol()).to.equal(daoTokenSymbol), +// expect(await daoToken.exchangeRate()).to.equal(exchangeRate), +// ]); +// }); + +// it("DaoToken의 초기 공급량이 정상적으로 설정되어 있는지 확인", async () => { +// const initialSupply = await daoToken.totalSupply(); +// expect(initialSupply).to.equal(hardhatInfo.initialSupply); + +// const adminBalance = await daoToken.balanceOf(admin.address); +// expect(adminBalance).to.equal(initialSupply); +// }); +// }); + +// describe("Mint 함수 테스트", () => { +// const amount = ethers.utils.parseEther(faker.datatype.number({ min: 1, max: 100 }).toString()); + +// it("DaoToken의 mint 함수가 정상적으로 동작하는지 확인", async () => { +// await daoToken.connect(admin).mint(users[0].address, amount); +// const balance = await daoToken.balanceOf(users[0].address); +// expect(balance).to.equal(amount); +// }); + +// it("DaoToken의 mint 함수가 관리자만 사용 가능한지 확인", async () => { +// await expect(daoToken.connect(users[0]).mint(users[0].address, amount)).to.be.revertedWith( +// "Only admin can mint tokens", +// ); +// }); +// }); + +// describe("BuyToken 함수 테스트", () => { +// const amount = ethers.utils.parseEther(faker.datatype.number({ min: 1, max: 100 }).toString()); + +// it("DaoToken의 buyToken 함수에서 잔고가 부족한 경우 실패하는지 확인", async () => { +// await expect(daoToken.connect(users[0]).buyTokens()).to.be.revertedWith("You must send ETH to buy tokens"); +// }); + +// it("DaoToken의 buyToken 함수 실행 시 가치 교환이 정상적으로 이루어지는지 확인", async () => { +// await daoToken.connect(users[0]).buyTokens({ value: amount }); + +// /* 사용자의 토큰 잔고 확인 */ +// const balance = await daoToken.balanceOf(users[0].address); +// const expectedTokenBalance = HardhatUtil.divExp(amount.mul(hardhatInfo.exchangeRate)); +// expect(balance).to.equal(expectedTokenBalance); + +// /* 컨트랙트의 ETH 잔고 확인 */ +// const contractBalance = await daoToken.getContractBalance(); +// expect(contractBalance).to.equal(amount); +// }); + +// it("DaoToken의 buyToken 함수 실행 시 이벤트가 정상적으로 발생하는지 확인", async () => { +// const expectedTokenBalance = HardhatUtil.divExp(amount.mul(hardhatInfo.exchangeRate)); + +// await expect(daoToken.connect(users[0]).buyTokens({ value: amount })) +// .to.emit(daoToken, "TokensPurchased") +// .withArgs(users[0].address, amount, expectedTokenBalance); // 1 ETH => 100,000 DAO +// }); +// }); + +// describe("SellToken 함수 테스트", () => { +// let tokenBalance: BigNumber; +// beforeEach(async () => { +// /* 토큰 구매 */ +// const amount = ethers.utils.parseEther(faker.datatype.number({ min: 1, max: 100 }).toString()); +// await daoToken.connect(users[0]).buyTokens({ value: amount }); +// tokenBalance = await daoToken.balanceOf(users[0].address); +// }); + +// it("DaoToken의 sellToken 함수 실행 시 이벤트가 정상적으로 발생하는지 확인", async () => { +// const expectedETHAmount = HardhatUtil.mulExp(tokenBalance).div(hardhatInfo.exchangeRate); + +// await expect(daoToken.connect(users[0]).sellTokens(tokenBalance)) +// .to.emit(daoToken, "TokensWithdrawn") +// .withArgs(users[0].address, expectedETHAmount); +// }); + +// it("sellToken 함수 실행 시 정상적으로 가치 교환이 이루어지는지 확인", async () => { +// const expectedETHAmount = HardhatUtil.mulExp(tokenBalance).div(hardhatInfo.exchangeRate); +// const balanceBefore = await ethers.provider.getBalance(users[0].address); + +// await daoToken.connect(users[0]).sellTokens(tokenBalance); + +// /* 사용자의 토큰 잔고 확인 */ +// const balanceAfter = await ethers.provider.getBalance(users[0].address); +// expect(balanceAfter).to.closeTo(balanceBefore.add(expectedETHAmount), GAS_PER_TRANSACTION); + +// /* 컨트랙트의 ETH 잔고 확인 */ +// const contractBalance = await daoToken.getContractBalance(); +// expect(contractBalance).to.equal(0); +// }); + +// it("DaoToken의 sellToken 함수에서 잔고가 부족한 경우 실패하는지 확인", async () => { +// await daoToken.connect(users[0]).sellTokens(tokenBalance); +// await expect(daoToken.connect(users[0]).sellTokens(tokenBalance)).to.be.revertedWith("Insufficient balance"); +// }); +// }); + +// it("ExchangeRate 변경이 정상적으로 이루어지는지 확인", async () => { +// const newExchangeRate = ethers.utils.parseEther(faker.datatype.float({ min: 0.01, max: 1 }).toString()); +// await daoToken.connect(admin).setExchangeRate(newExchangeRate); + +// expect(await daoToken.exchangeRate()).to.equal(newExchangeRate); +// }); + +// describe("DAO 멤버십 테스트", () => { +// const membershipAmount = ethers.utils.parseEther("100"); + +// it("DAO 멤버십 요청이 정상적으로 동작하는지 확인", async () => { +// const user = users[0]; +// await daoToken.connect(admin).transfer(user.address, membershipAmount); +// await dao.connect(user).requestDaoMembership(); + +// const requestStatus = await dao.membershipRequestStatus(user.address); +// expect(requestStatus).to.equal(0); // PENDING 상태 +// }); + +// it("DAO 멤버십 승인 테스트", async () => { +// const user = users[0]; +// await daoToken.connect(admin).transfer(user.address, membershipAmount); +// await dao.connect(user).requestDaoMembership(); +// await dao.connect(admin).handleDaoMembership(user.address, true); + +// const isMember = await dao.isDaoMember(user.address); +// expect(isMember).to.be.true; + +// const daoMemberList = await dao.getDaoList(); +// expect(daoMemberList).to.include(user.address); +// }); + +// it("DAO 멤버십 거절 테스트", async () => { +// const user = users[1]; +// await daoToken.connect(admin).transfer(user.address, membershipAmount); +// await dao.connect(user).requestDaoMembership(); +// await dao.connect(admin).handleDaoMembership(user.address, false); + +// const isMember = await dao.isDaoMember(user.address); +// expect(isMember).to.be.false; + +// const requestStatus = await dao.membershipRequestStatus(user.address); +// expect(requestStatus).to.equal(2); // REJECTED 상태 +// }); + +// describe("투표 기능 테스트", () => { +// const goalAmount = ethers.utils.parseEther("1000"); +// const totalAmount = ethers.utils.parseEther("500"); +// let campaignId: number; + +// beforeEach(async () => { +// // 새로운 캠페인을 생성하고 기부를 수행합니다. +// await donation.connect(admin).launch( +// users[0].address, // 타겟 +// "테스트 캠페인", // 제목 +// "테스트용 캠페인 설명", // 설명 +// goalAmount, // 목표 금액 +// Math.floor(new Date().getTime() / 1000) + 100, // 시작 시간 +// Math.floor(new Date().getTime() / 1000) + 200, // 종료 시간 +// ); +// campaignId = 1; // 캠페인 ID는 1로 설정했다고 가정합니다. + +// await donation.connect(admin).pledge(campaignId, totalAmount); +// }); + +// it("투표 시작 테스트", async () => { +// await dao.connect(admin).startVote(campaignId); + +// const inProgress = await dao.voteInProgress(campaignId); +// expect(inProgress).to.be.true; +// }); + +// it("투표 진행 중에 다시 시작하려고 하면 실패하는지 확인", async () => { +// await dao.connect(admin).startVote(campaignId); +// await expect(dao.connect(admin).startVote(campaignId)).to.be.revertedWith("Vote is already in progress"); +// }); + +// it("투표 종료 테스트", async () => { +// const user = users[0]; +// await daoToken.connect(admin).transfer(user.address, membershipAmount); +// await dao.connect(user).requestDaoMembership(); +// await dao.connect(admin).handleDaoMembership(user.address, true); + +// await dao.connect(admin).startVote(campaignId); +// await dao.connect(user).vote(campaignId, true); + +// const inProgress = await dao.voteInProgress(campaignId); +// expect(inProgress).to.be.false; + +// const voteCountYes = await dao.voteCountYes(campaignId); +// expect(voteCountYes).to.equal(1); +// }); + +// it("중복 투표가 불가능한지 확인", async () => { +// const user = users[0]; +// await daoToken.connect(admin).transfer(user.address, membershipAmount); +// await dao.connect(user).requestDaoMembership(); +// await dao.connect(admin).handleDaoMembership(user.address, true); + +// await dao.connect(admin).startVote(campaignId); +// await dao.connect(user).vote(campaignId, true); +// await expect(dao.connect(user).vote(campaignId, true)).to.be.revertedWith("User has already voted"); +// }); +// }); +// }); +// });