Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
12bceae
feat: pay-it-forward
daffafaizan Feb 9, 2025
e6666fb
chore: relocated src and test directories
daffafaizan Feb 9, 2025
dfeb5ba
fix: updated README file
RepoBug-UX Feb 9, 2025
3766af8
feat: added our proj to top-level README
RepoBug-UX Feb 9, 2025
f125e0e
feat: added draft tests and changed visibility of chain in compmanage…
daffafaizan Feb 9, 2025
646c277
feat: temporarily removed tests
daffafaizan Feb 9, 2025
d658751
updated readme.md
daffafaizan Feb 9, 2025
2430944
style: removed line
daffafaizan Feb 9, 2025
7d3bbb6
style: minor readme styling change
daffafaizan Feb 9, 2025
a579721
style: minor readme styling change
daffafaizan Feb 9, 2025
77be000
style: minor readme styling change
daffafaizan Feb 9, 2025
c22dfee
style: minor readme styling change
daffafaizan Feb 9, 2025
53a7c32
fix: cleaned up README
RepoBug-UX Feb 9, 2025
e519c73
added submodule
daffafaizan Feb 9, 2025
74c8d07
Merge branch 'pay-it-forward' of https://github.com/daffafaizan/proto…
daffafaizan Feb 9, 2025
2764b6c
added submodule
daffafaizan Feb 9, 2025
218ffab
feat: put project into its own directory
daffafaizan Feb 9, 2025
e72be73
added submodule
daffafaizan Feb 9, 2025
4a1362a
removed .DS_Store
daffafaizan Feb 9, 2025
9c4281b
fix: edited top level README
RepoBug-UX Feb 9, 2025
f51546a
added readme
daffafaizan Feb 9, 2025
2001403
relocated license and added new .gitignore for root level
daffafaizan Feb 9, 2025
093a282
fix: updated comments to be in compliance with NatSpec Formatting
RepoBug-UX Feb 10, 2025
9c74b36
ran sforge fmt
RepoBug-UX Feb 10, 2025
57cabf8
feat: added draft tests
daffafaizan Feb 10, 2025
1d427ee
changed Chain back to ChainContract
RepoBug-UX Feb 10, 2025
d076488
feat: finalized tests for chain contract
daffafaizan Feb 10, 2025
ea76f80
feat: completed building and runnning tests.
RepoBug-UX Feb 10, 2025
c4998a6
feat: completed building and runnning tests.
RepoBug-UX Feb 10, 2025
75cd87b
Merge branch 'tests' into pay-it-forward
RepoBug-UX Feb 10, 2025
fd2e4e2
fix: updated codebase + tests based on PR comments
RepoBug-UX Feb 10, 2025
116c664
feat: updated README files
RepoBug-UX Feb 11, 2025
eb2ddeb
feat: renamed parent directory and updated readme
RepoBug-UX Feb 11, 2025
b41954b
fix: corrected name on inner readme
RepoBug-UX Feb 12, 2025
7c12a62
Merge branch 'main' into pay-it-forward
lyronctk Feb 12, 2025
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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ docs/
.env
.DS_Store

node_modules/
node_modules/
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
[submodule "folio/lib/forge-std"]
path = folio/lib/forge-std
url = https://github.com/foundry-rs/forge-std

[submodule "nibble/lib/forge-std"]
path = OneByTwo/lib/forge-std
url = https://github.com/foundry-rs/forge-std
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Below is a quick summary of each prototype currently available in this repositor
Pay your rent with a yield-bearing stablecoin.
1. **`RIFF`**
Listen to a bonding curve.
1. **`Folio`**
Participate in a global pay-it-forward chain.
1. **`Nibble`**
Earn revenue share in your favorite restaurant.

Expand Down
14 changes: 14 additions & 0 deletions folio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
12 changes: 12 additions & 0 deletions folio/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Folio
### Pay it forward, get paid back

### Problem
Traditional pay-it-forward chains can raise charitable funds, but they are often vulnerable to exploitation. Additionally, they tend to lack excitement, and most efforts remain local rather than global-limiting their overall impact.

### Insight
By gamifying the pay-it-forward process, we can make chains more engaging and encourage increased donations. People are motivated when they believe they will be a part of something big. By using a blockchain, we can expand the scope to be worldwide, amplifying reach and participation.


### Solution
Create a global pay-it-forward competition on consumer payment rails. The potential to win a portion of the prize pot adds a competitive spark, increasing excitement and contributions. By keeping the chain encrypted, we avoid the case where people exploit the system by only contributing to the winning chain. This way every participant (new or returning) will feel that they have a fair chance to be a winner.
6 changes: 6 additions & 0 deletions folio/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions folio/lib/forge-std
Submodule forge-std added at 3b20d6
168 changes: 168 additions & 0 deletions folio/src/ChainTracker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

/// @title Pay-it-Forward Chain Management Contract
/// @notice This contract manages the process of tracking chains and chain details during a pay-it-forward competition.
contract ChainTracker {
/// @notice Sets up the manager
address manager;

constructor() {
manager = msg.sender;
}

/// @notice Modifier to ensure only the competition contract can call certain functions
modifier competitionOnly() {
require(msg.sender == manager, "Only the competition smart contract can call this.");
_;
}

/**
* @dev Represents a "pay-it-forward" chain.
* Tracks participants, their contributions, and overall chain statistics.
*/
struct ChainStats {
suint chainId; // Unique identifier for the chain
mapping(saddress => suint) links; // Tracks number of contributions for each participant/business
suint uniqueBusinesses; // Total distinct businesses involved in the chain
suint uniqueParticipants; // Total distinct participants in the chain
suint chainLength; // Total number of transactions in the chain
}

/**
* @dev Stores competition-wide statistics of terminated (nuked) chains.
* Tracks scores, lengths, and the highest-scoring chain.
*/
struct CompStats {
mapping(suint => suint) chainFinalScores; // Chain ID => Final score of that chain
mapping(suint => suint) chainFinalLengths; // Chain ID => Total length of that chain
suint overallWinningChainId; // The ID of the highest-scoring chain
}

/**
* @dev Stores per-participant and per-business statistics for chain participation.
* Tracks their latest chains, and best chains and their contributions.
*/
struct UserStats {
mapping(saddress => suint) latestChain; // Address => Last chain participated in
mapping(saddress => suint) bestChain; // Address => Best (longest) chain participated in
mapping(saddress => suint) bestChainLinks; // Address => Number of contributions in the best chain
}

ChainStats activeChain; // The currently active chain
CompStats comp; // Tracks global competition statistics
UserStats user; // Tracks user-specific statistics

/**
* @notice Adds transaction details to the active chain.
* Updates participation records for both the participant and the business.
* If the participant or business is contributing for the first time, their records are updated accordingly.
* @param pAddr The participant's address.
* @param bAddr The business's address.
*/
function forgeLink(saddress pAddr, saddress bAddr) public competitionOnly {
// If this is the participant's first time contributing to this chain, update records.
if (user.latestChain[pAddr] != activeChain.chainId) {
updateToCurrentChain(pAddr);
activeChain.uniqueParticipants++;
}
activeChain.links[pAddr]++; // Increment participant’s contribution count

// If this is the business's first time being added to this chain, update records.
if (user.latestChain[bAddr] != activeChain.chainId) {
updateToCurrentChain(bAddr);
activeChain.uniqueBusinesses++;
}
activeChain.links[bAddr]++; // Increment business’s contribution count
activeChain.chainLength++; // Increment chain length
}

/**
* @dev Resets a participant/business’s contribution count and updates their latest chain.
* @param addr The address of the participant/business.
*/
function updateToCurrentChain(saddress addr) internal {
checkIsChainLongest(addr); // Check if their latest chain was their longest
activeChain.links[addr] = suint(0); // Reset contribution count
user.latestChain[addr] = activeChain.chainId; // Assign the latest chain
}

/**
* @dev Checks if the participant/business’s latest chain had the highest score.
* If so, update their best chain records.
* This is public due to the need to check the final chain after the competition ends
* @param addr The address of the participant/business.
*/
function checkIsChainLongest(saddress addr) public competitionOnly {
suint latestScore = comp.chainFinalScores[user.latestChain[addr]];
suint bestScore = comp.chainFinalScores[user.bestChain[addr]];
if (latestScore > bestScore) {
user.bestChain[addr] = user.latestChain[addr]; // Update best chain
user.bestChainLinks[addr] = activeChain.links[addr]; // Update best chain's contribution count
}
}

/**
* @notice Ends the active chain, records its statistics, and resets it to be used as the next chain.
* Updates the overall highest-scoring chain if applicable.
*/
function nuke() public competitionOnly {
// Record final score and length of the nuked chain
comp.chainFinalScores[activeChain.chainId] =
calcChainScore(activeChain.uniqueParticipants, activeChain.uniqueBusinesses);
comp.chainFinalLengths[activeChain.chainId] = activeChain.chainLength;

// Update the overall winning chain if this one has a higher score
if (comp.chainFinalScores[activeChain.chainId] > comp.chainFinalScores[comp.overallWinningChainId]) {
comp.overallWinningChainId = activeChain.chainId;
}

// Reset the active chain for a fresh start
activeChain.chainLength = suint(0);
activeChain.uniqueParticipants = suint(0);
activeChain.uniqueBusinesses = suint(0);
activeChain.chainId++;
}

/**
* @dev Calculates a chain's final score based on distinct participants and businesses.
* @param pScore The number of distinct participants.
* @param bScore The number of distinct businesses.
* @return The calculated final score.
*/
function calcChainScore(suint pScore, suint bScore) internal pure returns (suint) {
// Formula: finalScore = unique businesses * (1 + (unique participants / 10))
return bScore * (suint(1) + (pScore / suint(10)));
}

/// @notice Returns the ID of the highest-scoring chain.
function getWinningChainId() public view competitionOnly returns (uint256) {
return uint256(comp.overallWinningChainId);
}

/// @notice Returns the total length of the highest-scoring chain.
function getWinningChainLength() public view competitionOnly returns (uint256) {
return uint256(comp.chainFinalLengths[comp.overallWinningChainId]);
}

/// @notice Returns the best (highest-scoring) chain a participant/business has contributed to.
/// @param addr The address of the participant/business.
function getBestChainId(saddress addr) public view competitionOnly returns (uint256) {
return uint256(user.bestChain[addr]);
}

/// @notice Returns the number of times a participant/business contributed to their best chain.
/// @param addr The address of the participant/business.
function getBestChainCount(saddress addr) public view competitionOnly returns (uint256) {
return uint256(user.bestChainLinks[addr]);
}

/**
* @dev Resets a participant/business contribution count on the winning chain
* Prevents them from claiming rewards multiple times.
* @param addr The address of the participant/business.
*/
function walletHasBeenPaid(saddress addr) public competitionOnly {
user.bestChainLinks[addr] = suint(0);
}
}
Loading
Loading