Skip to content

refac: integrate Chainlink in CrowdfundingV2 #65

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

Open
wants to merge 14 commits into
base: chainlink
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ node_modules

# Hardhat Ignition default folder for deployments against a local node
ignition/deployments/chain-31337

/lib
/forge-std
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
4 changes: 3 additions & 1 deletion PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<!-- Thank you for your interest in contributing to BlockHeaderWeb3! -->

Assignment 1 link [here](https://github.com/BlockheaderWeb3-Community/cohort-6/blob/main/assignments/Assignment1.md)

Assignment 2 link [here](https://github.com/BlockheaderWeb3-Community/cohort-6/blob/main/assignments/Assignment2.md)

<!-- Describe the changes introduced in this pull request. -->
<!-- Include any context necessary for understanding the PR's purpose. -->
Expand All @@ -12,4 +15,3 @@

- [ ] Tests
- [ ] Documentation

68 changes: 2 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,2 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
# Introduction to Foundry
Master the concepts to develop, test and deploy EVM contracts with Foundry
162 changes: 162 additions & 0 deletions contracts/chainlink-integration/CrowdFundingV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;
import {console} from "forge-std/Test.sol";
import "@chainlink/contracts/src/v0.7/interfaces/AggregatorV3Interface.sol";
import "../../contracts/with-foundry/RewardToken.sol";
import "../../contracts/with-foundry/RewardNft.sol";

contract CrowdfundingV2 {
address public Owner;
uint public constant FUNDING_GOAL_IN_USD = 50000;
uint public constant NFT_THRESHOLD = 1000;
uint256 public totalFundsRaised;
bool public isFundingComplete;

RewardToken public rewardToken;
RewardNft public rewardNFT;
uint256 public tokenRewardRate;

// Contribution tracking
mapping(address => uint256) public contributions;
mapping(address => bool) public hasReceivedNFT;

// Chainlink PriceFeed
AggregatorV3Interface priceFeed;
address public constant ETH_USD_ADDR = 0x694AA1769357215DE4FAC081bf1f309aDC325306;
// Events
event ContributionReceived(address indexed contributor, uint256 amount);
event TokenRewardSent(address indexed contributor, uint256 amount);
event NFTRewardSent(address indexed contributor, uint256 tokenId);
event FundsWithdrawn(address indexed projectOwner, uint256 amount);

constructor(uint256 _tokenRewardRate, address _rewardToken, address _rewardNft) {
/**
* Network: Sepolia
* Data Feed: ETH/USD
* Address: 0x694AA1769357215DE4FAC081bf1f309aDC325306
*/
priceFeed = AggregatorV3Interface(ETH_USD_ADDR);
Owner = msg.sender;
rewardToken = RewardToken(_rewardToken);
rewardNFT = RewardNft(_rewardNft);
tokenRewardRate = _tokenRewardRate;
}

// function to retrieve ETH price in USD with Chainlink priceFeed
function getLatestPrice() public view returns (int256) {
(
,
// uint80 roundID
int256 answer, // uint256 startedAt
,
,

) = // uint256 updatedAt
priceFeed.latestRoundData();

return answer; // Price has 8 decimals, e.g., 3000.00000000
}

function contribute() external payable returns (bool) {
require(msg.value > 0, "Contribution must be greater than 0");
require(!isFundingComplete, "Funding goal already reached");

// Calculate contribution amount and process any refunds

uint256 refundableAmount = _determineIfAmountIsRefundable(msg.value);
uint256 actualContribution = msg.value - refundableAmount;

// check if refundable amount is > 0
if (refundableAmount > 0) {
transferRefundableAmount(refundableAmount, msg.sender);
}

// Update contribution record
// uint256 contributionsValue = msg.value - refundableAmount;
contributions[msg.sender] += actualContribution;
totalFundsRaised += actualContribution;

// Check if funding goal is reached
if (totalFundsRaised >= FUNDING_GOAL_IN_USD) {
isFundingComplete = true;
}

// Calculate token reward
uint256 tokenReward = calculateReward(actualContribution);

if (tokenReward > 0) {
bool isTransfered = sendRewardToken(tokenReward, msg.sender);
require(isTransfered, "Token transfer failed");

// Check for NFT eligibility
if (checkNftEligibility(msg.sender)) {
mintNft(msg.sender);
}

emit ContributionReceived(msg.sender, actualContribution);
return true;
} else {
return false;
}
}

function checkNftEligibility(address _address) private view returns (bool) {
return contributions[_address] >= NFT_THRESHOLD && !hasReceivedNFT[_address];
}

function mintNft(address _contributor) private returns (bool) {
// require(checkNftEligibilty(_contributor), "Not eligible for NFT reward");
uint256 tokenId = rewardNFT.mintNFT(_contributor);
hasReceivedNFT[_contributor] = true;
emit NFTRewardSent(_contributor, tokenId);
return true;
}

function calculateReward(uint256 _value) private view returns (uint256) {
uint256 tokenReward = (_value * tokenRewardRate) / 1 ether;
return tokenReward;
}

function sendRewardToken(uint256 _tokenReward, address _recipient) private returns (bool) {
bool success = rewardToken.transfer(_recipient, _tokenReward);
require(success, "Token transfer failed");
emit TokenRewardSent(msg.sender, _tokenReward);

return true;
}

function _determineIfAmountIsRefundable(uint256 _contributionAmount) private view returns (uint256) {
// Calculate the remaining amount needed to complete the funding goal
uint256 amountToReachThreshold = FUNDING_GOAL_IN_USD - totalFundsRaised;
if (_contributionAmount >= amountToReachThreshold) {
// return the excess amount
uint256 refundAmount = _contributionAmount - amountToReachThreshold;
return refundAmount;
}
return 0;
}

function transferRefundableAmount(uint256 _amount, address _contributor) private {
// uint256 refundable = _determineIfAmountIsRefundable(_amount);
uint256 refundable = _amount;
if (refundable > 0) {
(bool success, ) = _contributor.call{value: refundable}("");
require(success, "Transfer failed");
}
}

function withdrawFunds() external {
require(msg.sender == Owner, "Only project owner can withdraw");
require(isFundingComplete, "Funding goal not yet reached");
require(address(this).balance > 0, "No funds to withdraw");

uint256 amount = address(this).balance;
payable(Owner).transfer(amount);

emit FundsWithdrawn(Owner, amount);
}

function getContribution(address contributor) external view returns (uint256) {
return contributions[contributor];
}
}
78 changes: 78 additions & 0 deletions contracts/counter_alex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.0 <0.9.0;

/**
* @title Counter Contract
* @notice A simple counter contract that supports incrementing and decrementing operations.
* @dev Provides functions to increment, decrement and adjust the counter by a specified value.
*/

contract Counter {
/**
* @notice The current count value.
* @dev Public state variable initialized to zero.
*/
uint public countVal = 0;

/**
* @notice Retrieves the current count value.
* @return The current counter value.
*/
function getCount() public view returns (uint) {
return countVal;
}

/**
* @notice Increments the counter by one.
* @dev Ensures that the counter does not exceed the maximum value defined by runner().
*/
function increment() public {
uint maxValue = runner();
require(countVal < maxValue);
countVal++;
}

/**
* @notice Decrements the counter by one.
* @dev Prevents the counter from decrementing below zero.
*/
function decrement() public {
require(countVal > 0, "Counter cannot be negative");
countVal--;
}

/**
* @notice Increments the counter by a specified positive value.
* @param _val The value to add to the current count.
* @dev Validates that _val is greater than zero and that the resulting count does not exceed the maximum value.
*/
function incrementByVal(uint _val) public {
uint maxValue = runner();
require(_val > 0, "Must be greater than zero");
require(countVal + _val <= maxValue, "Counter cannot be negative");
countVal += _val;
}

/**
* @notice Decrements the counter by a specified positive value.
* @param _val The value to subtract from the current count.
* @dev Validates that _val is greater than zero and that the current count is sufficient to subtract _val without underflow.
*/
function decrementByVal(uint _val) public {
require(_val > 0, "Must be greater than zero");
require(countVal >= _val, "Underflow: Cannot decrement below zero"); // Prevent underflow

countVal -= _val;
}

/**
* @notice Computes the maximum value for a uint256.
* @return The maximum possible value of a uint256.
* @dev Uses unchecked arithmetic to derive the maximum uint256 value.
*/
function runner() public pure returns (uint) {
unchecked {
return uint256(0) - 1;
}
}
}
6 changes: 6 additions & 0 deletions contracts/crowdfunding_alex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

import "./reward_token.sol";
import "./reward_nft_alex.sol";
contract CrowdFund {}
6 changes: 6 additions & 0 deletions contracts/reward_nft_alex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract RewardNFT {
// ...
}
6 changes: 6 additions & 0 deletions contracts/reward_token_alex.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract RewardToken {
// ...
}
Loading
Loading