Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jcarbonnell committed Nov 12, 2024
1 parent b015a18 commit 034871f
Show file tree
Hide file tree
Showing 10 changed files with 524 additions and 137 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22.11.0
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"name": "se-2",
"version": "0.0.1",
"name": "yousplit-contract",
"version": "0.1.0",
"description": "An ethereum smart contract to split YouTube royalties between a bunch of beneficiaries.",
"repository": "https://github.com/partagexyz/YouSplit",
"author": "jcarbonnell",
"license": "MIT",
"private": true,
"workspaces": {
"packages": [
Expand Down
121 changes: 121 additions & 0 deletions packages/hardhat/contracts/YouSplit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

// Useful for debugging. Remove when deploying to a live network.
import "hardhat/console.sol";

// Use openzeppelin to inherit battle-tested implementations (ERC20, ERC721, etc)
// import "@openzeppelin/contracts/access/Ownable.sol";

/// @title YouSplit
/// @author jcarbonnell (partage.xyz)
/// @notice An ethereum smart contract to split YouTube royalties between a bunch of beneficiaries.

contract YouSplit {
// State Variables
address public immutable owner;
uint256 public totalShares;
uint256 public totalBalance;

struct Beneficiary {
uint256 shares;
uint256 withdrawn;
bool isEligible;
}

mapping(address => Beneficiary) public beneficiaries;

// Events: a way to emit log statements from smart contract that can be listened to by external parties
event ContractFunded(address indexed sender, uint256 amount);
event BeneficiaryAdded(address indexed beneficiary, uint256 shares);
event BeneficiaryUpdated(address indexed beneficiary, uint256 newShares, bool newEligibility);
event BeneficiaryDeleted(address indexed beneficiary);
event Withdrawal(address indexed beneficiary, uint256 amount);

// Constructor: Called once on contract deployment
// Check packages/hardhat/deploy/00_deploy_your_contract.ts
constructor(address[] memory _beneficiaries, uint256[] memory _shares) {
require(_beneficiaries.length == _shares.length, "Must provide equal number of beneficiaries and shares.");
owner = msg.sender;
totalShares = 100;

// Ensure the onwer gets 5% of the total shares
uint256 ownerShares = 5;
beneficiaries[owner] = Beneficiary({
shares: ownerShares,
withdrawn: 0,
isEligible: true
});
emit BeneficiaryAdded(owner, ownerShares);

// Add the rest of the beneficiaries
uint256 remainingShares = 95;
for (uint i = 0; i < _beneficiaries.length; i++) {
uint256 sharePercentage = (_shares[i] * remainingShares) / 100;
beneficiaries[_beneficiaries[i]] = Beneficiary({
shares: sharePercentage,
withdrawn: 0,
isEligible: true
});
emit BeneficiaryAdded(_beneficiaries[i], sharePercentage);
}
}

// Function to receive ETH
receive() external payable {
totalBalance += msg.value;
emit ContractFunded(msg.sender, msg.value);
}

// Function to check balance and eligibility
function getBeneficiaryInfo(address _beneficiary) public view returns (uint256 shares, uint256 withdrawn, uint256 eligibleAmount, bool isEligible) {
Beneficiary memory beneficiary = beneficiaries[_beneficiary];
uint256 sharePercentage = beneficiary.shares * 100 / totalShares;
return (beneficiary.shares, beneficiary.withdrawn, (totalBalance * sharePercentage) / 100, beneficiary.isEligible);
}

// Function to withdraw funds
function withdraw() public {
Beneficiary storage beneficiary = beneficiaries[msg.sender];
require(beneficiary.isEligible, "You are not eligible to withdraw.");
uint256 sharePercentage = beneficiary.shares * 100 / totalShares;
uint256 amount = (totalBalance * sharePercentage) / 100 - beneficiary.withdrawn;

require(amount > 0, "You have no funds to withdraw.");
beneficiary.withdrawn += amount;
totalBalance -= amount;
payable(msg.sender).transfer(amount);
emit Withdrawal(msg.sender, amount);
}

// Function to add or update beneficiary
function setBeneficiary(address _beneficiary, uint256 _shares, bool _isEligible) public {
require(msg.sender == owner, "Only the owner can add or update beneficiaries.");
if (_beneficiary != owner) {
if(beneficiaries[_beneficiary].shares > 0) {
totalShares -= beneficiaries[_beneficiary].shares;
}
beneficiaries[_beneficiary] = Beneficiary({
shares: _shares,
withdrawn: 0,
isEligible: _isEligible
});
totalShares += _shares;
emit BeneficiaryAdded(_beneficiary, _shares);
}
}

// Function to remove beneficiary
function removeBeneficiary(address _beneficiary) public {
require(msg.sender == owner, "Only the owner can remove beneficiaries.");
require(_beneficiary != owner, "You cannot remove the owner.");
totalShares -= beneficiaries[_beneficiary].shares;
delete beneficiaries[_beneficiary];
emit BeneficiaryDeleted(_beneficiary);
}

// Function to get total funds in the contract
function getTotalFunds() public view returns (uint256) {
return address(this).balance;
}
}
87 changes: 0 additions & 87 deletions packages/hardhat/contracts/YourContract.sol

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";

/**
* Deploys a contract named "YourContract" using the deployer account and
* Deploys a contract named "YouSplit" using the deployer account and
* constructor arguments set to the deployer address
*
* @param hre HardhatRuntimeEnvironment object.
*/
const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const deployYouSplit: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
/*
On localhost, the deployer account is the one that comes with Hardhat, which is already funded.
Expand All @@ -22,23 +22,33 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("YourContract", {
// Example of beneficiaries and their share percentages
// Note: The shares should add up to 9500 (95%) when considering all beneficiaries combined.

Check warning on line 26 in packages/hardhat/deploy/00_deploy_YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Delete `·`
const signers = await hre.ethers.getSigners();
const beneficiary1 = signers[1];
const beneficiary2 = signers[2];

const beneficiaries = [beneficiary1.address, beneficiary2.address];
// we assume each beneficiary gets an equal share of the remaining 95% after the owner's 5%
const shares =[475, 475];

Check warning on line 33 in packages/hardhat/deploy/00_deploy_YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Insert `·`

await deploy("YouSplit", {
from: deployer,
// Contract constructor arguments
args: [deployer],
args: [beneficiaries, shares],
log: true,
// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by
// automatically mining the contract deployment transaction. There is no effect on live networks.
autoMine: true,
});

// Get the deployed contract to interact with it after deploying.
const yourContract = await hre.ethers.getContract<Contract>("YourContract", deployer);
console.log("👋 Initial greeting:", await yourContract.greeting());
const youSplit = await hre.ethers.getContract<Contract>("YouSplit", deployer);

Check failure on line 46 in packages/hardhat/deploy/00_deploy_YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

'youSplit' is assigned a value but never used
console.log("YouSplit Contract deployed successfully");
};

export default deployYourContract;
export default deployYouSplit;

// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags YourContract
deployYourContract.tags = ["YourContract"];
// e.g. yarn deploy --tags YouSplit
deployYouSplit.tags = ["YouSplit"];
84 changes: 84 additions & 0 deletions packages/hardhat/test/YouSplit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { YouSplit } from "../typechain-types/YouSplit";
import { YouSplit__factory } from "../typechain-types/factories/YouSplit__factory";

Check failure on line 4 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

'YouSplit__factory' is defined but never used

describe("YouSplit", function () {
let youSplit: YouSplit;
let owner: any;
let beneficiary1: any;
let beneficiary2: any;

Check warning on line 11 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Delete `··`
before(async () => {
// signers for different roles in the contract
const signers = await ethers.getSigners();
owner = signers[0];
beneficiary1 = signers[1];
beneficiary2 = signers[2];

// deploy the contract
const youSplitFactory = await ethers.getContractFactory("YouSplit");
youSplit = await youSplitFactory.deploy([beneficiary1.address, beneficiary2.address],[475, 475]);

Check warning on line 21 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Insert `·`
await youSplit.waitForDeployment();
});

describe("Deployment", function () {
it("Should set the correct owner", async function () {
expect(await youSplit.owner()).to.equal(owner.address);
});

it("Should initialize with correct share distribution", async function () {
// Check owner shares
const { shares: ownerShares } = await youSplit.beneficiaries(owner.address);
expect(ownerShares).to.equal(5); // Owner should have 5% of shares

// Check beneficiary shares
const { shares: beneficiary1Shares } = await youSplit.beneficiaries(beneficiary1.address);
expect(beneficiary1Shares).to.equal(475); // 95% / 2 = 47.5% (represented as 475 out of 10000)

const { shares: beneficiary2Shares } = await youSplit.beneficiaries(beneficiary2.address);
expect(beneficiary2Shares).to.equal(475);
});

it("Should start with zero withdrawn for all beneficiaries", async function () {
expect((await youSplit.beneficiaries(owner.address)).withdrawn).to.equal(0);
expect((await youSplit.beneficiaries(beneficiary1.address)).withdrawn).to.equal(0);
expect((await youSplit.beneficiaries(beneficiary2.address)).withdrawn).to.equal(0);
});

it("Should set all beneficiaries as eligible", async function () {
expect((await youSplit.beneficiaries(owner.address)).isEligible).to.equal(true);
expect((await youSplit.beneficiaries(beneficiary1.address)).isEligible).to.equal(true);
expect((await youSplit.beneficiaries(beneficiary2.address)).isEligible).to.equal(true);
});

it("Should start with zero balance", async function () {
expect(await youSplit.totalBalance()).to.equal(0);
});

it("Should allow funds to be sent to the contract", async function () {
const amount = ethers.parseEther("1");
await owner.sendTransaction({ to: await youSplit.getAddress(), value: amount });

expect(await youSplit.totalBalance()).to.equal(amount);
});

it("Should allow beneficiaries to withdraw their funds", async function () {
const initialBalance = await ethers.provider.getBalance(beneficiary1.address);
await youSplit.connect(beneficiary1).withdraw();
const afterWithdrawBalance = await ethers.provider.getBalance(beneficiary1.address);

Check warning on line 70 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Delete `······`
// Check if balance increased after withdrawal
expect(afterWithdrawBalance > initialBalance).to.be.true;

Check failure on line 72 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Expected an assignment or function call and instead saw an expression

// Check the withdrawn amount for beneficiary1
const { withdrawn: withdrawnAmount } = await youSplit.beneficiaries(beneficiary1.address);
expect(withdrawnAmount).to.be.gt(0);
});

it("Should not allow non-eligible beneficiaries to withdraw", async function () {
await youSplit.setBeneficiary(beneficiary2.address, 0, false); // Make beneficiary2 ineligible

Check warning on line 80 in packages/hardhat/test/YouSplit.ts

View workflow job for this annotation

GitHub Actions / ci (ubuntu-latest, lts/*)

Delete `·`
await expect(youSplit.connect(beneficiary2).withdraw()).to.be.revertedWith("You are not eligible to withdraw.");
});
});
});
28 changes: 0 additions & 28 deletions packages/hardhat/test/YourContract.ts

This file was deleted.

Loading

0 comments on commit 034871f

Please sign in to comment.