Skip to content

Commit

Permalink
Solution added
Browse files Browse the repository at this point in the history
  • Loading branch information
dacarva committed Dec 16, 2024
1 parent 253e014 commit bf1146d
Show file tree
Hide file tree
Showing 8 changed files with 403 additions and 127 deletions.
11 changes: 0 additions & 11 deletions packages/hardhat/.env.example

This file was deleted.

81 changes: 81 additions & 0 deletions packages/hardhat/contracts/PiggyBank.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract PiggyBank {
address public owner; // Owner of the piggy bank (could be the contract deployer)
uint public lockUpPeriod; // Lock-up period in seconds

// Struct to represent each user's deposit information
struct Deposit {
uint amount; // Amount of Ether the user has deposited
uint depositTime; // Timestamp of when the deposit was made
}

mapping(address => Deposit) public deposits; // Mapping from user address to their deposit details

event Deposited(address indexed user, uint amount);
event Withdrawn(address indexed user, uint amount);

// Modifier to ensure only the owner can withdraw or set the lock-up period
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_;
}

// Constructor to set the initial owner and the lock-up period
constructor(uint _lockUpPeriod) {
owner = msg.sender;
lockUpPeriod = _lockUpPeriod; // Set the lock-up period (in seconds)
}

// Deposit Ether into the PiggyBank
function deposit() external payable {
require(msg.value > 0, "Must send Ether to deposit");

Deposit storage userDeposit = deposits[msg.sender];

// If the user already has a deposit, we just add to it
userDeposit.amount += msg.value;

// If this is the user's first deposit, we set the deposit time
if (userDeposit.depositTime == 0) userDeposit.depositTime = block.timestamp;

emit Deposited(msg.sender, msg.value);
}

// Withdraw funds after the lock-up period has passed
function withdraw() external {
Deposit storage userDeposit = deposits[msg.sender];
require(userDeposit.amount > 0, "No funds to withdraw");
require(block.timestamp >= userDeposit.depositTime + lockUpPeriod, "Funds are still locked");

uint amountToWithdraw = userDeposit.amount;
userDeposit.amount = 0; // Reset the user's deposit

// Transfer the funds back to the user
payable(msg.sender).transfer(amountToWithdraw);

emit Withdrawn(msg.sender, amountToWithdraw);
}

// View the balance of the user's deposit
function getDepositBalance(address _user) external view returns (uint) {
return deposits[_user].amount;
}

// View the time at which the user can withdraw their funds
function getUnlockTime() external view returns (uint) {
return deposits[msg.sender].depositTime + lockUpPeriod;
}

// Owner can update the lock-up period (if desired)
function setLockUpPeriod(uint _newLockUpPeriod) external onlyOwner {
lockUpPeriod = _newLockUpPeriod;
}

// Owner can withdraw all funds (for example, in case of emergencies)
function ownerWithdraw(uint amount) external onlyOwner {
require(amount <= address(this).balance, "Insufficient balance in the contract");
payable(owner).transfer(amount);
}
}
78 changes: 0 additions & 78 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 "PiggyBank" using the deployer account and
* constructor arguments set to the deployer address
*
* @param hre HardhatRuntimeEnvironment object.
*/
const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const deployPiggyBank: 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,23 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("YourContract", {
await deploy("PiggyBank", {
from: deployer,
// Contract constructor arguments
args: [deployer],
args: [20],
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 piggyBank = await hre.ethers.getContract<Contract>("PiggyBank", deployer);
console.log("👋 Initial lockup period:", await piggyBank.lockUpPeriod());
};

export default deployYourContract;
export default deployPiggyBank;

// 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"];
deployPiggyBank.tags = ["PiggyBank"];
132 changes: 132 additions & 0 deletions packages/hardhat/test/PiggyBank.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { PiggyBank } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";

describe("PiggyBank Contract", function () {
let piggyBank: PiggyBank;
let owner: HardhatEthersSigner;
let user: HardhatEthersSigner;
let lockUpPeriod: number;

beforeEach(async () => {
// Get the signers (owner and a user)
[owner, user] = await ethers.getSigners();

// Deploy the PiggyBank contract with a lock-up period of 1 week
lockUpPeriod = 7 * 24 * 60 * 60; // 7 days in seconds
const PiggyBankFactory = await ethers.getContractFactory("PiggyBank");
piggyBank = await PiggyBankFactory.deploy(lockUpPeriod);
await piggyBank.waitForDeployment();
});

describe("Deployment", function () {
it("Should deploy with the correct owner and lock-up period", async function () {
expect(await piggyBank.owner()).to.equal(owner.address);
expect(await piggyBank.lockUpPeriod()).to.equal(lockUpPeriod);
});
});

describe("Deposit", function () {
it("Should allow a user to deposit Ether", async function () {
const depositAmount = ethers.parseEther("1.0");

// Use the deposit function instead of direct transfer
await piggyBank.connect(user).deposit({ value: depositAmount });

const depositBalance = await piggyBank.getDepositBalance(user.address);
expect(depositBalance).to.equal(depositAmount);
});

it("Should revert when trying to deposit 0 Ether", async function () {
await expect(piggyBank.connect(user).deposit({ value: 0 }))
.to.be.revertedWith("Must send Ether to deposit");
});

it("Should emit a Deposited event when Ether is deposited", async function () {
const depositAmount = ethers.parseEther("0.5");

await expect(piggyBank.connect(user).deposit({ value: depositAmount }))
.to.emit(piggyBank, "Deposited")
.withArgs(user.address, depositAmount);
});

it("Should update deposit time only on first deposit", async function () {
const firstDeposit = ethers.parseEther("0.5");
const secondDeposit = ethers.parseEther("0.5");

// First deposit
await piggyBank.connect(user).deposit({ value: firstDeposit });
const firstDepositTime = (await ethers.provider.getBlock('latest'))!.timestamp;

// Second deposit
await piggyBank.connect(user).deposit({ value: secondDeposit });

const deposit = await piggyBank.deposits(user.address);
expect(deposit.depositTime).to.equal(firstDepositTime);
expect(deposit.amount).to.equal(firstDeposit + secondDeposit);
});
});

describe("Withdraw", function () {
beforeEach(async function () {
// Setup: User deposits some Ether
await piggyBank.connect(user).deposit({ value: ethers.parseEther("1.0") });
});

it("Should allow withdrawal after lock-up period", async function () {
// Fast-forward time
await ethers.provider.send("evm_increaseTime", [lockUpPeriod]);
await ethers.provider.send("evm_mine", []);

await expect(piggyBank.connect(user).withdraw())
.to.changeEtherBalance(user, ethers.parseEther("1.0"));
});

it("Should revert withdrawal before lock-up period", async function () {
await expect(piggyBank.connect(user).withdraw())
.to.be.revertedWith("Funds are still locked");
});

it("Should revert withdrawal with no deposit", async function () {
const newUser = (await ethers.getSigners())[2];
await expect(piggyBank.connect(newUser).withdraw())
.to.be.revertedWith("No funds to withdraw");
});

it("Should correctly report unlock time", async function () {
const depositTime = (await ethers.provider.getBlock('latest'))!.timestamp;
const expectedUnlockTime = depositTime + lockUpPeriod;
expect(await piggyBank.connect(user).getUnlockTime())
.to.equal(expectedUnlockTime);
});
});

describe("Owner Functions", function () {
it("Should allow only owner to change lock-up period", async function () {
const newPeriod = 30 * 24 * 60 * 60; // 30 days
await expect(piggyBank.connect(user).setLockUpPeriod(newPeriod))
.to.be.revertedWith("Not the contract owner");

await piggyBank.connect(owner).setLockUpPeriod(newPeriod);
expect(await piggyBank.lockUpPeriod()).to.equal(newPeriod);
});

it("Should allow only owner to withdraw funds", async function () {
const amount = ethers.parseEther("1.0");
await piggyBank.connect(user).deposit({ value: amount });

await expect(piggyBank.connect(user).ownerWithdraw(amount))
.to.be.revertedWith("Not the contract owner");

await expect(piggyBank.connect(owner).ownerWithdraw(amount))
.to.changeEtherBalance(owner, amount);
});

it("Should revert owner withdrawal if insufficient balance", async function () {
const amount = ethers.parseEther("1.0");
await expect(piggyBank.connect(owner).ownerWithdraw(amount))
.to.be.revertedWith("Insufficient balance in the contract");
});
});
});
28 changes: 0 additions & 28 deletions packages/hardhat/test/YourContract.ts

This file was deleted.

Loading

0 comments on commit bf1146d

Please sign in to comment.