Skip to content
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
23 changes: 23 additions & 0 deletions ASSIGNMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Crowdfund Contract Assignment

## Overview
This is the Crowdfund contract assignment (as-w4-d2). The Crowdfund contract provides functionality for crowdfunding campaigns.

## Setup
1. Install dependencies: `npm install`
2. Configure hardhat: See `hardhat.config.ts`
3. Run tests: `npm test`

## Contract Details
- **Location**: `Assignment/solidity-assignment7/contracts/crowdfund/`
- **Language**: Solidity

## Testing
```bash
npm test
```

## Deployment
```bash
npx hardhat run scripts/deploy.ts
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"_format": "hh3-sol-build-info-1",
"id": "solc-0_8_28-7253f0f64ebfd7bdd2421717b0eea855e0e57ca1",
"solcVersion": "0.8.28",
"solcLongVersion": "0.8.28+commit.7893614a",
"userSourceNameMap": {
"contracts/crownfund.sol": "project/contracts/crownfund.sol"
},
"input": {
"language": "Solidity",
"settings": {
"evmVersion": "cancun",
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"remappings": []
},
"sources": {
"project/contracts/crownfund.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ncontract SimpleCrowdfunding {\n bool private locked;\n\n modifier nonReentrant() {\n require(!locked, \"Reentrancy\");\n locked = true;\n _;\n locked = false;\n }\n address public owner;\n uint256 public goal;\n uint256 public deadline;\n uint256 public totalRaised;\n bool public withdrawn;\n\n mapping(address => uint256) public contributions; // Tracks each contributor's amount.\n\n event Contribution(address indexed contributor, uint256 amount);\n event Withdraw(address indexed owner, uint256 amount);\n event Refund(address indexed contributor, uint256 amount);\n\n constructor(uint256 _goal, uint256 _deadline) {\n require(_goal > 0, \"Goal must be > 0\");\n require(_deadline > block.timestamp, \"Deadline must be future\");\n owner = msg.sender;\n goal = _goal;\n deadline = _deadline;\n }\n\n function contribute() public payable {\n require(block.timestamp < deadline, \"Funding ended\");\n require(msg.value > 0, \"Must send ETH\");\n require(!withdrawn, \"Already withdrawn\");\n contributions[msg.sender] += msg.value;\n totalRaised += msg.value;\n emit Contribution(msg.sender, msg.value);\n }\n\n function withdraw() external nonReentrant {\n require(msg.sender == owner, \"Only owner\");\n require(totalRaised >= goal, \"Goal not met\");\n require(!withdrawn, \"Already withdrawn\");\n withdrawn = true;\n uint256 amount = address(this).balance;\n (bool ok, ) = payable(owner).call{value: amount}(\"\");\n require(ok, \"Withdraw failed\");\n emit Withdraw(owner, amount);\n }\n\n function refund() external nonReentrant {\n require(block.timestamp >= deadline, \"Too early\");\n require(totalRaised < goal, \"Goal met\");\n uint256 amount = contributions[msg.sender];\n require(amount > 0, \"No contribution\");\n contributions[msg.sender] = 0;\n (bool ok, ) = payable(msg.sender).call{value: amount}(\"\");\n require(ok, \"Refund failed\");\n emit Refund(msg.sender, amount);\n }\n\n receive() external payable {\n contribute();\n }\n}\n"
}
}
}
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"/home/luhrhenz/Desktop/New Folder/contracts/crowdfund/contracts/crownfund.sol":{"jobHash":"solc-0_8_28-7253f0f64ebfd7bdd2421717b0eea855e0e57ca1","isolated":false,"artifactPaths":["/home/luhrhenz/Desktop/New Folder/contracts/crowdfund/artifacts/contracts/crownfund.sol/SimpleCrowdfunding.json"],"buildInfoPath":"/home/luhrhenz/Desktop/New Folder/contracts/crowdfund/artifacts/build-info/solc-0_8_28-7253f0f64ebfd7bdd2421717b0eea855e0e57ca1.json","buildInfoOutputPath":"/home/luhrhenz/Desktop/New Folder/contracts/crowdfund/artifacts/build-info/solc-0_8_28-7253f0f64ebfd7bdd2421717b0eea855e0e57ca1.output.json","typeFilePath":"/home/luhrhenz/Desktop/New Folder/contracts/crowdfund/artifacts/contracts/crownfund.sol/artifacts.d.ts","wasm":false}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

contract SimpleCrowdfunding {
bool private locked;

modifier nonReentrant() {
require(!locked, "Reentrancy");
locked = true;
_;
locked = false;
}
address public owner;
uint256 public goal;
uint256 public deadline;
uint256 public totalRaised;
bool public withdrawn;

mapping(address => uint256) public contributions; // Tracks each contributor's amount.

event Contribution(address indexed contributor, uint256 amount);
event Withdraw(address indexed owner, uint256 amount);
event Refund(address indexed contributor, uint256 amount);

constructor(uint256 _goal, uint256 _deadline) {
require(_goal > 0, "Goal must be > 0");
require(_deadline > block.timestamp, "Deadline must be future");
owner = msg.sender;
goal = _goal;
deadline = _deadline;
}

function contribute() public payable {
require(block.timestamp < deadline, "Funding ended");
require(msg.value > 0, "Must send ETH");
require(!withdrawn, "Already withdrawn");
contributions[msg.sender] += msg.value;
totalRaised += msg.value;
emit Contribution(msg.sender, msg.value);
}

function withdraw() external nonReentrant {
require(msg.sender == owner, "Only owner");
require(totalRaised >= goal, "Goal not met");
require(!withdrawn, "Already withdrawn");
withdrawn = true;
uint256 amount = address(this).balance;
(bool ok, ) = payable(owner).call{value: amount}("");
require(ok, "Withdraw failed");
emit Withdraw(owner, amount);
}

function refund() external nonReentrant {
require(block.timestamp >= deadline, "Too early");
require(totalRaised < goal, "Goal met");
uint256 amount = contributions[msg.sender];
require(amount > 0, "No contribution");
contributions[msg.sender] = 0;
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "Refund failed");
emit Refund(msg.sender, amount);
}

receive() external payable {
contribute();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import dotenv from "dotenv";
dotenv.config({ path: "../../.env" });
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
plugins: [hardhatToolboxViemPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
hardhatMainnet: {
type: "edr-simulated",
chainType: "l1",
},
hardhatOp: {
type: "edr-simulated",
chainType: "op",
},
sepolia: {
type: "http",
chainType: "l1",
url: configVariable("SEPOLIA_RPC_URL"),
accounts: [configVariable("SEPOLIA_PRIVATE_KEY")],
},
},
verify: {
etherscan: {
apiKey: configVariable("ETHERSCAN_API_KEY"),
},
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

export default buildModule("CrowdfundModule", (m) => {
const goal = m.getParameter("goal", 10n);
const deadline = m.getParameter("deadline", 2000000000n);

const crowdfunding = m.contract("SimpleCrowdfunding", [goal, deadline]);

return { crowdfunding };
});
17 changes: 17 additions & 0 deletions Assignment/solidity-assignment7/contracts/crowdfund/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "New Folder",
"version": "1.0.0",
"type": "module",
"devDependencies": {
"@nomicfoundation/hardhat-ignition": "^3.0.7",
"@nomicfoundation/hardhat-toolbox-viem": "^5.0.2",
"@types/node": "^22.19.8",
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"hardhat": "^3.1.6",
"typescript": "~5.8.0",
"viem": "^2.45.1"
},
"dependencies": {
"dotenv": "^17.2.4"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { network } from "hardhat";

describe("SimpleCrowdfunding", async function () {
const { viem } = await network.connect();
const publicClient = await viem.getPublicClient();
const testClient = await viem.getTestClient();
const [owner, contributor] = await viem.getWalletClients();

async function deployCrowdfunding(goal: bigint, deadline: bigint) {
return viem.deployContract("SimpleCrowdfunding", [goal, deadline], {
client: { wallet: owner, public: publicClient },
});
}

it("allows owner withdrawal when goal is met", async function () {
const block = await publicClient.getBlock();
const deadline = BigInt(block.timestamp) + 3600n;
const contract = await deployCrowdfunding(5n, deadline);

const asContributor = await viem.getContractAt(
"SimpleCrowdfunding",
contract.address,
{ client: { wallet: contributor, public: publicClient } },
);

await asContributor.write.contribute({ value: 5n });
await contract.write.withdraw();
assert.equal(await contract.read.withdrawn(), true);
});

it("allows refunds after deadline when goal not met", async function () {
const block = await publicClient.getBlock();
const deadline = BigInt(block.timestamp) + 3600n;
const contract = await deployCrowdfunding(10n, deadline);

const asContributor = await viem.getContractAt(
"SimpleCrowdfunding",
contract.address,
{ client: { wallet: contributor, public: publicClient } },
);

await asContributor.write.contribute({ value: 5n });

await testClient.increaseTime({ seconds: 3600 });
await testClient.mine({ blocks: 1 });

await asContributor.write.refund();
const refunded = await contract.read.contributions([contributor.account.address]);
assert.equal(refunded, 0n);

await assert.rejects(async () => {
await asContributor.write.refund();
});
});
});
13 changes: 13 additions & 0 deletions Assignment/solidity-assignment7/contracts/crowdfund/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */
{
"compilerOptions": {
"lib": ["es2023"],
"module": "node16",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "node16",
"outDir": "dist"
}
}
9 changes: 9 additions & 0 deletions template/.github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Summary
What does this PR implement?

## Testing
- [ ] Unit tests added
- [ ] Tests passing locally

## Notes
Any assumptions or trade-offs?
24 changes: 24 additions & 0 deletions template/.github/workflows/.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
formatting:
name: Code Formatting (Prettier)
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- name: Run Prettier check
run: npx prettier --check .
5 changes: 5 additions & 0 deletions template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
.vscode/

package-lock.json
node_modules
4 changes: 4 additions & 0 deletions template/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
dist
build
.env*
7 changes: 7 additions & 0 deletions template/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 80,
"tabWidth": 2
}
35 changes: 35 additions & 0 deletions template/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# How to Contribute

1. Getting Started
- Fork the repository
- Sync with upstream
- Create a feature branch

2. Branch Naming Convention
Example:
`submission/assignment-01/<github-username>`

3. Commit Message Guidelines

Example (Conventional Commits–lite):
```
feat: implement token transfer logic
fix: handle zero-address edge case
test: add coverage for revert conditions
```

4. Pull Request Checklist
- [x] Code compiles
- [x] Tests pass
- [x] README included
- [x] No changes outside /submissions

5. PR Review Process
- [x] Automated checks
- [x] Manual review by mentors
- [x] Required fixes before merge

6. Common Mistakes to Avoid
- Editing assignment specs
- Submitting compiled artifacts
- Force-pushing after review
Loading