Skip to content
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ jobs:
run: |
forge --version

# - name: Run Forge fmt
# run: |
# forge fmt --check
# id: fmt
- name: Run Forge fmt
run: |
forge fmt --check
id: fmt

- name: Run Forge build
run: |
Expand Down
7 changes: 1 addition & 6 deletions script/1_DeployQuest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ import {Script, console} from "forge-std/Script.sol";
import {Helper} from "./Helper.sol";
import {QuestFactory} from "../src/QuestFactory.sol";



contract DeployQuestScript is Script, Helper {

function run() external returns (address questFactoryAddress, address questDonationAddress) {
uint256 deployerPrivateKey = getDeployerPrivateKey();
vm.startBroadcast(deployerPrivateKey);

// Deploy QuestFactory with constructor parameters
// Sets msg.sender as admin for factory and all future quests created
QuestFactory questFactory = new QuestFactory();
questFactoryAddress = address(questFactory);
questFactoryAddress = address(questFactory);

// Create a new Quest with a target amount (for example, 1 ether)
// Msg.sender is the creator of the quest, but only admin can withdraw funds
Expand All @@ -29,6 +26,4 @@ contract DeployQuestScript is Script, Helper {

vm.stopBroadcast();
}

}

6 changes: 1 addition & 5 deletions script/2_SetupQuest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import {Script, console} from "forge-std/Script.sol";
import {Helper} from "./Helper.sol";
import {QuestDonation} from "../src/QuestDonation.sol";


contract SetupQuestScript is Script, Helper {

function run(address questDonationAddress) external {
uint256 deployerPrivateKey = getDeployerPrivateKey();
vm.startBroadcast(deployerPrivateKey);
Expand All @@ -22,11 +20,9 @@ contract SetupQuestScript is Script, Helper {

// Set price oracles
questDonation.setPriceOracle(address(0), ETH_USD_FEED); // For Native ETH
questDonation.setPriceOracle(USDC, USDC_USD_FEED); // USDC Oracle
questDonation.setPriceOracle(USDC, USDC_USD_FEED); // USDC Oracle
console.log("Price oracles set for ETH and USDC");

vm.stopBroadcast();
}

}

29 changes: 11 additions & 18 deletions script/3_InteractionQuest.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import {Helper} from "./Helper.sol";
import {QuestDonation} from "../src/QuestDonation.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";


contract InteractionQuestScript is Script, Helper {

function run(address questDonationAddress) external {
uint256 deployerPrivateKey = getDeployerPrivateKey();
vm.startBroadcast(deployerPrivateKey);
Expand All @@ -19,41 +17,38 @@ contract InteractionQuestScript is Script, Helper {
// CHECK ETH BALANCE
address deployerAddress = vm.addr(deployerPrivateKey);
console.log("Address:", deployerAddress);
console.log("ETH Balance: ", deployerAddress.balance/ 10**18);
console.log("ETH Balance: ", deployerAddress.balance / 10 ** 18);
// CHECK USDC BALANCE
console.log("USDC balance:", IERC20(USDC).balanceOf(address(deployerAddress))/ 10**6);

console.log("USDC balance:", IERC20(USDC).balanceOf(address(deployerAddress)) / 10 ** 6);

// Make donations
// ETH donation
questDonation.donateETH{value: 0.1 ether}(); // Donate ETH
questDonation.donateETH{value: 0.1 ether}(); // Donate ETH
console.log("Donated 0.1 ETH");

// USDC donation
uint256 usdcAmount = 100 * 10**6;
uint256 usdcAmount = 100 * 10 ** 6;
IERC20(USDC).approve(address(questDonation), usdcAmount); // DONATE USDC
questDonation.donateERC20(USDC, usdcAmount);
console.log("Donated 100 USDC");


// Log balances after donations
console.log("===QUEST CONTRACT: Balances after donations ===");
console.log("ETH balance:", address(questDonation).balance);
console.log("USDC balance:", IERC20(USDC).balanceOf(address(questDonation)));


// Withdraw funds
uint256 ethBalance = address(questDonation).balance;
uint256 usdcBalance = IERC20(USDC).balanceOf(address(questDonation));
uint256 ethBalance = address(questDonation).balance;
uint256 usdcBalance = IERC20(USDC).balanceOf(address(questDonation));

if (ethBalance > 0) {
questDonation.withdraw(address(0), ethBalance/2); // WITHDRAW ETH
console.log("Withdrawn ETH balance:", ethBalance/2);
questDonation.withdraw(address(0), ethBalance / 2); // WITHDRAW ETH
console.log("Withdrawn ETH balance:", ethBalance / 2);
}

if (usdcBalance > 0) {
questDonation.withdraw(USDC, usdcBalance/2); // EITHDRAW USDC
console.log("Withdrawn USDC balance:", usdcBalance/2);
questDonation.withdraw(USDC, usdcBalance / 2); // EITHDRAW USDC
console.log("Withdrawn USDC balance:", usdcBalance / 2);
}

// Log balances after withdrawals
Expand All @@ -63,6 +58,4 @@ contract InteractionQuestScript is Script, Helper {

vm.stopBroadcast();
}

}

6 changes: 2 additions & 4 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import {InteractionQuestScript} from "./3_InteractionQuest.s.sol";

contract DeployScript is Helper {
function run() external {

//DEPLOYS FACTORY AND FIRST EXAMPLE QUEST
DeployQuestScript deployQuest = new DeployQuestScript();
( , address firstQuest) = deployQuest.run();
(, address firstQuest) = deployQuest.run();

// ALLOW TOKENS AND ADD ORACLE
SetupQuestScript setupQuest = new SetupQuestScript();
Expand All @@ -20,6 +19,5 @@ contract DeployScript is Helper {
// INTERACTING WITH FIRST QUEST
InteractionQuestScript interactingQuest = new InteractionQuestScript();
interactingQuest.run(firstQuest);

}
}
}
12 changes: 5 additions & 7 deletions script/Helper.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
import {Script} from "forge-std/Script.sol";

import {Script} from "forge-std/Script.sol";

contract Helper is Script{
contract Helper is Script {
address constant USDC = 0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8;

// Chainlink Price Feed addresses
address constant ETH_USD_FEED = 0x694AA1769357215DE4FAC081bf1f309aDC325306;
address constant USDC_USD_FEED = 0xA2F78ab2355fe2f984D808B5CeE7FD0A93D5270E;
error InvalidPrivateKey(string);

error InvalidPrivateKey(string);

function getDeployerPrivateKey() internal view returns (uint256 deployerPrivateKey) {
deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY");
Expand All @@ -20,6 +20,4 @@ contract Helper is Script{
);
}
}


}
}
32 changes: 14 additions & 18 deletions src/QuestDonation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,29 +26,24 @@ contract QuestDonation is Ownable {
event FundsWithdrawn(address indexed token, uint256 amount);
event PriceOracleSet(address indexed token, address indexed oracle);

constructor(
address _admin,
uint256 _targetAmount,
address _creator
) Ownable(_admin) {
constructor(address _admin, uint256 _targetAmount, address _creator) Ownable(_admin) {
require(_admin != address(0), "Invalid admin address");
require(_creator != address(0), "Invalid creator address");
require(_targetAmount > 0, "Invalid target amount");

admin = _admin;
targetAmount = _targetAmount;
creator = _creator;
}


modifier withinDonationLimit(address token, uint256 amount) {
require(address(priceOracles[token]) != address(0), "No price oracle set for token");
AggregatorV3Interface priceFeed = priceOracles[token];
(, int256 price, , , ) = priceFeed.latestRoundData();
(, int256 price,,,) = priceFeed.latestRoundData();
uint8 priceDecimals = priceFeed.decimals();

require(price > 0, "Invalid price");

uint256 usdValue;
if (token == address(0)) {
// Native token (ETH) has 18 decimals
Expand All @@ -59,23 +54,25 @@ contract QuestDonation is Ownable {
// Adjust the calculation based on token decimals
usdValue = (amount * uint256(price)) / (10 ** tokenDecimals) / (10 ** priceDecimals);
}

uint256 lastYear = lastDonationTimestamp[msg.sender];
if (block.timestamp - lastYear > YEAR) {
yearlyDonations[msg.sender] = 0;
lastDonationTimestamp[msg.sender] = block.timestamp;
}
require(yearlyDonations[msg.sender] + usdValue <= MAX_DONATION, "Donation exceeds $5000/year. Contact info@etherguild.xyz");

require(
yearlyDonations[msg.sender] + usdValue <= MAX_DONATION,
"Donation exceeds $5000/year. Contact info@etherguild.xyz"
);

yearlyDonations[msg.sender] += usdValue;
_;
}

function donateETH() external payable withinDonationLimit(address(0), msg.value) {

require(msg.value > 0, "Donation amount must be greater than 0");
AggregatorV3Interface priceFeed = priceOracles[address(0)];
(, int256 price, , , ) = priceFeed.latestRoundData();
(, int256 price,,,) = priceFeed.latestRoundData();
uint8 priceDecimals = priceFeed.decimals();
emit DonationReceived(msg.sender, address(0), msg.value, msg.value * uint256(price) / (10 ** priceDecimals));
}
Expand All @@ -84,7 +81,7 @@ contract QuestDonation is Ownable {
require(allowedTokens[token], "Token not allowed");
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
AggregatorV3Interface priceFeed = priceOracles[token];
(, int256 price, , , ) = priceFeed.latestRoundData();
(, int256 price,,,) = priceFeed.latestRoundData();
uint8 priceDecimals = priceFeed.decimals();
emit DonationReceived(msg.sender, token, amount, amount * uint256(price) / (10 ** priceDecimals));
}
Expand All @@ -100,12 +97,11 @@ contract QuestDonation is Ownable {
function setPriceOracle(address token, address oracle) external onlyOwner {
require(allowedTokens[token], "Token not allowed");
require(oracle != address(0), "Invalid oracle address");

priceOracles[token] = AggregatorV3Interface(oracle);
emit PriceOracleSet(token, oracle);
}


function withdraw(address token, uint256 amount) external {
require(msg.sender == admin, "Only admin can withdraw");
if (token == address(0)) {
Expand Down
44 changes: 12 additions & 32 deletions src/QuestFactory.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "./QuestDonation.sol";
import "./QuestDonation.sol";

contract QuestFactory {
address public admin;

constructor() {
admin = msg.sender; // Set contract deployer as admin
admin = msg.sender; // Set contract deployer as admin
}

// Structure to store quest details
Expand All @@ -22,22 +22,12 @@ contract QuestFactory {
Quest[] public quests;

// Event emitted when new quest is created
event QuestCreated(
address indexed questContract,
address indexed creator,
uint256 timestamp
);
event QuestCreated(address indexed questContract, address indexed creator, uint256 timestamp);

// Function to create new quest
function createQuest(
uint256 _targetAmount
) external returns (address) {
function createQuest(uint256 _targetAmount) external returns (address) {
// Deploy new QuestDonation contract
QuestDonation newQuest = new QuestDonation(
admin,
_targetAmount,
msg.sender
);
QuestDonation newQuest = new QuestDonation(admin, _targetAmount, msg.sender);

// Store quest details
quests.push(
Expand All @@ -50,11 +40,7 @@ contract QuestFactory {
);

// Emit event
emit QuestCreated(
address(newQuest),
msg.sender,
block.timestamp
);
emit QuestCreated(address(newQuest), msg.sender, block.timestamp);

return address(newQuest);
}
Expand All @@ -65,19 +51,13 @@ contract QuestFactory {
}

// Get quest by index
function getQuestByIndex(uint256 _index) external view returns (
address questContract,
uint256 targetAmount,
address creator,
uint256 timestamp
) {
function getQuestByIndex(uint256 _index)
external
view
returns (address questContract, uint256 targetAmount, address creator, uint256 timestamp)
{
require(_index < quests.length, "Quest index out of bounds");
Quest memory quest = quests[_index];
return (
quest.questContract,
quest.targetAmount,
quest.creator,
quest.timestamp
);
return (quest.questContract, quest.targetAmount, quest.creator, quest.timestamp);
}
}
Loading