Skip to content
This repository has been archived by the owner on Nov 30, 2024. It is now read-only.

Commit

Permalink
WIP: Eduena endowment fund contract
Browse files Browse the repository at this point in the history
  • Loading branch information
fiando committed Nov 14, 2024
1 parent b606553 commit 2992897
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 111 deletions.
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@openzeppelin/contracts=lib/openzeppelin-contracts/contracts
14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

73 changes: 0 additions & 73 deletions src/EduEna.sol

This file was deleted.

114 changes: 114 additions & 0 deletions src/EduenaEndowmentFund.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IUSDe.sol";
import "forge-std/console.sol"; //NOTE: testing only

error InsufficientShares();
error InsufficientFunds();
error InsufficientBalance();
error OnlyOwner();
error DepositAmountZero();
error InsufficientYield();

contract EduenaEndowmentFund is ReentrancyGuard {
using SafeERC20 for IERC20;

address public owner;
ISUSDe public sUSDe;
IERC20 public USDe;
uint256 public totalShares;
uint256 public lastAssetValueInUSDe;
uint256 public totalUnclaimedYield;
mapping(address => uint256) public donorShares;

event Deposit(address indexed donor, uint256 amount);
event Stake(uint256 amount);
event Withdraw(address indexed recipient, uint256 amount);
event YieldUpdated(uint256 newAssetValueInUSDe, uint256 yield);

constructor(address _USDeAddress, address _sUSDeAddress) {
owner = msg.sender;
USDe = IERC20(_USDeAddress);
sUSDe = ISUSDe(_sUSDeAddress);
}

function poolBalance() public view returns (uint256) {
return USDe.balanceOf(address(this));
}

function deposit(uint256 amount) external nonReentrant {
if (amount == 0) revert DepositAmountZero();

USDe.safeTransferFrom(msg.sender, address(this), amount);

uint256 shares;
if (totalShares == 0) {
shares = amount;
} else {
shares = (amount * totalShares) / lastAssetValueInUSDe;
}

donorShares[msg.sender] += shares;
totalShares += shares;
emit Deposit(msg.sender, amount);

_stake(amount);
updateYield();
}

function _stake(uint256 amount) internal {
USDe.approve(address(sUSDe), amount);
sUSDe.deposit(amount, address(this));
emit Stake(amount);
}

function withdraw(uint256 amount) external nonReentrant {
uint256 shares = (amount * totalShares) / lastAssetValueInUSDe;
if (donorShares[msg.sender] < shares) revert InsufficientShares();

uint256 previewAmount = sUSDe.previewRedeem(shares);
if (previewAmount < amount) revert InsufficientFunds();

donorShares[msg.sender] -= shares;
totalShares -= shares;

//FIXME: Fix the withdraw to the donor as a sUSDe
sUSDe.redeem(shares, msg.sender, address(this));
emit Withdraw(msg.sender, previewAmount);
updateYield();
}

//TODO: Implement the distribute function to distribute the returns to eligible students verified by scholarships creator
//TODO: Use scholarship manager to handle the scholarship creation and verification
function distribute(address payable student, uint256 amount) external {
if (msg.sender != owner) revert OnlyOwner();
if (amount > totalUnclaimedYield) revert InsufficientYield();

uint256 sUSDeBalance = sUSDe.balanceOf(address(this));
if (amount > sUSDeBalance) revert InsufficientBalance();

uint256 previewAmount = sUSDe.previewRedeem(amount);
// sUSDe.redeem(amount);
USDe.safeTransfer(student, previewAmount);
totalUnclaimedYield -= amount;

emit Withdraw(student, previewAmount);
updateYield();
}

function updateYield() public {
//todo: Implement the yield calculation
uint256 sUSDeBalance = sUSDe.balanceOf(address(this));

uint256 assetValueInUSDe = sUSDe.previewDeposit(sUSDeBalance);
lastAssetValueInUSDe = assetValueInUSDe;

uint256 yield = assetValueInUSDe - lastAssetValueInUSDe;
totalUnclaimedYield += yield;
emit YieldUpdated(assetValueInUSDe, yield);
}
}
85 changes: 85 additions & 0 deletions src/ScholarshipManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract ScholarshipManager {
address public owner;
uint256 public scholarshipCount;

struct Scholarship {
uint256 id;
uint256 amount;
string criteria;
address[] applicants;
mapping(address => bool) verifiedApplicants;
mapping(address => bool) claimed;
}

mapping(uint256 => Scholarship) public scholarships;

event ScholarshipCreated(uint256 indexed scholarshipId, uint256 amount, string criteria);
event ScholarshipUpdated(uint256 indexed scholarshipId, uint256 amount, string criteria);
event ScholarshipApplied(uint256 indexed scholarshipId, address indexed applicant);
event ApplicantVerified(uint256 indexed scholarshipId, address indexed applicant);
event ScholarshipClaimed(uint256 indexed scholarshipId, address indexed applicant);

modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}

constructor() {
owner = msg.sender;
}

function createScholarship(uint256 amount, string memory criteria) external onlyOwner {
scholarshipCount++;
Scholarship storage scholarship = scholarships[scholarshipCount];
scholarship.id = scholarshipCount;
scholarship.amount = amount;
scholarship.criteria = criteria;

emit ScholarshipCreated(scholarshipCount, amount, criteria);
}

function updateScholarship(uint256 scholarshipId, uint256 amount, string memory criteria) external onlyOwner {
Scholarship storage scholarship = scholarships[scholarshipId];
scholarship.amount = amount;
scholarship.criteria = criteria;

emit ScholarshipUpdated(scholarshipId, amount, criteria);
}

function getScholarshipDetails(uint256 scholarshipId) external view returns (uint256, uint256, string memory, address[] memory) {
Scholarship storage scholarship = scholarships[scholarshipId];
return (scholarship.id, scholarship.amount, scholarship.criteria, scholarship.applicants);
}

function applyForScholarship(uint256 scholarshipId) external {
Scholarship storage scholarship = scholarships[scholarshipId];
scholarship.applicants.push(msg.sender);

emit ScholarshipApplied(scholarshipId, msg.sender);
}

function verifyApplicant(address applicant, uint256 scholarshipId) external onlyOwner {
Scholarship storage scholarship = scholarships[scholarshipId];
scholarship.verifiedApplicants[applicant] = true;

emit ApplicantVerified(scholarshipId, applicant);
}

function getApplicantStatus(address applicant, uint256 scholarshipId) external view returns (bool) {
Scholarship storage scholarship = scholarships[scholarshipId];
return scholarship.verifiedApplicants[applicant];
}

function claimScholarship(uint256 scholarshipId) external {
Scholarship storage scholarship = scholarships[scholarshipId];
require(scholarship.verifiedApplicants[msg.sender], "Applicant not verified");
require(!scholarship.claimed[msg.sender], "Scholarship already claimed");

scholarship.claimed[msg.sender] = true;

emit ScholarshipClaimed(scholarshipId, msg.sender);
}
}
27 changes: 27 additions & 0 deletions src/interfaces/IUSDe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface ISUSDe {
function approve(address spender, uint256 amount) external;

function transfer(
address to,
uint256 amount
) external returns (uint256);

function deposit(
uint256 assets,
address receiver
) external returns (uint256);

function redeem(
uint256 shares,
address receiver,
address _owner
) external returns (uint256);

function previewRedeem(uint256 shares) external view returns (uint256);
function previewDeposit(uint256 assets) external view returns (uint256);

function balanceOf(address account) external view returns (uint256);
}
10 changes: 10 additions & 0 deletions src/mocks/MockSUSDe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";

contract MockSUSDe is ERC4626 {
constructor(
ERC20 _usdeToken
) ERC4626(_usdeToken) ERC20("Mock Staked USDe", "MSUSDe") {}
}
12 changes: 12 additions & 0 deletions src/mocks/MockUSDe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockUSDe is ERC20 {
constructor() ERC20("Mock USDe", "mUSDe") {}

function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
24 changes: 0 additions & 24 deletions test/Counter.t.sol

This file was deleted.

Loading

0 comments on commit 2992897

Please sign in to comment.