Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

박민우- 2차시 과제 제출 #10

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 124 additions & 23 deletions src/contracts/Dao.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
131 changes: 102 additions & 29 deletions src/contracts/Donation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,33 @@ 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 daoTokenAddr) {
admin = msg.sender;
daoToken = DaoTokenInterface(daoTokenAddr);
}

///////////// @notice 아래에 modifier 추가 ////////////

Expand All @@ -37,39 +46,103 @@ contract Donation {
uint256 _goal,
uint32 _startAt,
uint32 _endAt
) external {}

function cancel(uint256 _campaignId) external {}

function pledge(uint256 _campaignId, uint256 _amount) external {}

function unpledge(uint256 _campaignId, uint256 _amount) external {}

//2. onlyDao modifier 추가
function claim(uint256 _campaignId) external {}

function refund(uint256 _campaignId) external {}
) external {
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,
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 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 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");

campaign.pledged += _amount;
pledgedUserToAmount[_campaignId][msg.sender] += _amount;
daoToken.transferFrom(msg.sender, address(this), _amount);

emit Pledge(_campaignId, msg.sender, _amount, campaign.pledged);
}

function unpledge(uint256 _campaignId, uint256 _amount) external {
Campaign storage campaign = campaigns[_campaignId];
require(_amount > 0, "Amount must be greater than zero");
require(!getIsEnded(_campaignId), "Campaign ended");

campaign.pledged -= _amount;
pledgedUserToAmount[_campaignId][msg.sender] -= _amount;
daoToken.transfer(msg.sender, _amount);

emit Unpledge(_campaignId, msg.sender, _amount, campaign.pledged);
}

function claim(uint256 _campaignId) external {
require(getIsEnded(_campaignId), "Campaign not ended");

Campaign storage campaign = campaigns[_campaignId];
require(!campaign.claimed, "claimed");

daoToken.transfer(campaign.target, campaign.pledged);
campaign.claimed = true;

emit Claim(_campaignId, campaign.claimed, campaign.pledged);
}

function refund(uint256 _campaignId) external {
require(getIsEnded(_campaignId), "Campaign not ended");

uint256 bal = pledgedUserToAmount[_campaignId][msg.sender];
pledgedUserToAmount[_campaignId][msg.sender] = 0;
daoToken.transfer(msg.sender, bal);

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;
}
}
2 changes: 2 additions & 0 deletions src/contracts/interface/DaoInterface.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading