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 @@
# Auction Contract Assignment

## Overview
This is the Auction contract assignment (as-w4-d3). The Auction contract provides functionality for decentralized auctions.

## 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/auction/`
- **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-d561e92d8769fccdba4d2cfc37ea3a101e655058",
"solcVersion": "0.8.28",
"solcLongVersion": "0.8.28+commit.7893614a",
"userSourceNameMap": {
"contracts/auction.sol": "project/contracts/auction.sol"
},
"input": {
"language": "Solidity",
"settings": {
"evmVersion": "cancun",
"outputSelection": {
"*": {
"": [
"ast"
],
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
]
}
},
"remappings": []
},
"sources": {
"project/contracts/auction.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.28;\n\ncontract SimpleAuction {\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 startingPrice;\n uint256 public endTime;\n bool public ended;\n\n address public highestBidder;\n uint256 public highestBid;\n\n mapping(address => uint256) public pendingReturns;\n\n event BidPlaced(address indexed bidder, uint256 amount);\n event AuctionEnded(address winner, uint256 amount);\n event RefundWithdrawn(address indexed bidder, uint256 amount);\n\n constructor(uint256 _startingPrice, uint256 _auctionDuration) {\n require(_startingPrice > 0, \"Starting price must be > 0\");\n require(_auctionDuration > 0, \"Duration must be > 0\");\n owner = msg.sender;\n startingPrice = _startingPrice;\n endTime = block.timestamp + _auctionDuration;\n highestBid = _startingPrice;\n }\n\n function bid() public payable {\n require(block.timestamp < endTime, \"Auction ended\");\n require(!ended, \"Auction already ended\");\n require(msg.sender != owner, \"Owner cannot bid\");\n require(msg.value > highestBid, \"Bid too low\");\n\n if (highestBidder != address(0)) {\n pendingReturns[highestBidder] += highestBid;\n }\n\n highestBidder = msg.sender;\n highestBid = msg.value;\n emit BidPlaced(msg.sender, msg.value);\n }\n\n function withdrawRefund() external nonReentrant {\n uint256 amount = pendingReturns[msg.sender];\n require(amount > 0, \"No refund\");\n pendingReturns[msg.sender] = 0;\n (bool ok, ) = payable(msg.sender).call{value: amount}(\"\");\n require(ok, \"Refund failed\");\n emit RefundWithdrawn(msg.sender, amount);\n }\n\n function endAuction() external nonReentrant {\n require(msg.sender == owner, \"Only owner\");\n require(block.timestamp >= endTime, \"Auction not ended\");\n require(!ended, \"Already ended\");\n ended = true;\n if (highestBidder != address(0)) {\n (bool ok, ) = payable(owner).call{value: highestBid}(\"\");\n require(ok, \"Payout failed\");\n }\n emit AuctionEnded(highestBidder, highestBidder == address(0) ? 0 : highestBid);\n }\n\n receive() external payable {\n bid();\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/auction/contracts/auction.sol":{"jobHash":"solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058","isolated":false,"artifactPaths":["/home/luhrhenz/Desktop/New Folder/contracts/auction/artifacts/contracts/auction.sol/SimpleAuction.json"],"buildInfoPath":"/home/luhrhenz/Desktop/New Folder/contracts/auction/artifacts/build-info/solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058.json","buildInfoOutputPath":"/home/luhrhenz/Desktop/New Folder/contracts/auction/artifacts/build-info/solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058.output.json","typeFilePath":"/home/luhrhenz/Desktop/New Folder/contracts/auction/artifacts/contracts/auction.sol/artifacts.d.ts","wasm":false},"/home/luhrhenz/Desktop/blockchain/assignments/7-assignment-solidity/contracts/auction/contracts/auction.sol":{"jobHash":"solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058","isolated":false,"artifactPaths":["/home/luhrhenz/Desktop/blockchain/assignments/7-assignment-solidity/contracts/auction/artifacts/contracts/auction.sol/SimpleAuction.json"],"buildInfoPath":"/home/luhrhenz/Desktop/blockchain/assignments/7-assignment-solidity/contracts/auction/artifacts/build-info/solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058.json","buildInfoOutputPath":"/home/luhrhenz/Desktop/blockchain/assignments/7-assignment-solidity/contracts/auction/artifacts/build-info/solc-0_8_28-d561e92d8769fccdba4d2cfc37ea3a101e655058.output.json","typeFilePath":"/home/luhrhenz/Desktop/blockchain/assignments/7-assignment-solidity/contracts/auction/artifacts/contracts/auction.sol/artifacts.d.ts","wasm":false}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

contract SimpleAuction {
bool private locked;

modifier nonReentrant() {
require(!locked, "Reentrancy");
locked = true;
_;
locked = false;
}
address public owner;
uint256 public startingPrice;
uint256 public endTime;
bool public ended;

address public highestBidder;
uint256 public highestBid;

mapping(address => uint256) public pendingReturns;

event BidPlaced(address indexed bidder, uint256 amount);
event AuctionEnded(address winner, uint256 amount);
event RefundWithdrawn(address indexed bidder, uint256 amount);

constructor(uint256 _startingPrice, uint256 _auctionDuration) {
require(_startingPrice > 0, "Starting price must be > 0");
require(_auctionDuration > 0, "Duration must be > 0");
owner = msg.sender;
startingPrice = _startingPrice;
endTime = block.timestamp + _auctionDuration;
highestBid = _startingPrice;
}

function bid() public payable {
require(block.timestamp < endTime, "Auction ended");
require(!ended, "Auction already ended");
require(msg.sender != owner, "Owner cannot bid");
require(msg.value > highestBid, "Bid too low");

if (highestBidder != address(0)) {
pendingReturns[highestBidder] += highestBid;
}

highestBidder = msg.sender;
highestBid = msg.value;
emit BidPlaced(msg.sender, msg.value);
}

function withdrawRefund() external nonReentrant {
uint256 amount = pendingReturns[msg.sender];
require(amount > 0, "No refund");
pendingReturns[msg.sender] = 0;
(bool ok, ) = payable(msg.sender).call{value: amount}("");
require(ok, "Refund failed");
emit RefundWithdrawn(msg.sender, amount);
}

function endAuction() external nonReentrant {
require(msg.sender == owner, "Only owner");
require(block.timestamp >= endTime, "Auction not ended");
require(!ended, "Already ended");
ended = true;
if (highestBidder != address(0)) {
(bool ok, ) = payable(owner).call{value: highestBid}("");
require(ok, "Payout failed");
}
emit AuctionEnded(highestBidder, highestBidder == address(0) ? 0 : highestBid);
}

receive() external payable {
bid();
}
}
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("AuctionModule", (m) => {
const startingPrice = m.getParameter("startingPrice", 1n);
const auctionDuration = m.getParameter("auctionDuration", 3600n);

const auction = m.contract("SimpleAuction", [startingPrice, auctionDuration]);

return { auction };
});
17 changes: 17 additions & 0 deletions Assignment/solidity-assignment7/contracts/auction/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"
}
}
115 changes: 115 additions & 0 deletions Assignment/solidity-assignment7/contracts/auction/test/Auction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { network } from "hardhat";

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

const startingPrice = 1n;
const duration = 3600n;

async function deployAuction() {
return viem.deployContract(
"SimpleAuction",
[startingPrice, duration],
{ client: { wallet: owner, public: publicClient } },
);
}

it("accepts higher bids and tracks refunds for outbid users", async function () {
const auction = await deployAuction();

const auctionAsBidder1 = await viem.getContractAt(
"SimpleAuction",
auction.address,
{ client: { wallet: bidder1, public: publicClient } },
);
const auctionAsBidder2 = await viem.getContractAt(
"SimpleAuction",
auction.address,
{ client: { wallet: bidder2, public: publicClient } },
);

await auctionAsBidder1.write.bid({ value: 2n });
await auctionAsBidder2.write.bid({ value: 3n });

const refund = await auction.read.pendingReturns([bidder1.account.address]);
assert.equal(refund, 2n);

await auctionAsBidder1.write.withdrawRefund();
const refundAfter = await auction.read.pendingReturns([bidder1.account.address]);
assert.equal(refundAfter, 0n);
});

it("rejects low or equal bids and owner bidding", async function () {
const auction = await deployAuction();

const auctionAsBidder1 = await viem.getContractAt(
"SimpleAuction",
auction.address,
{ client: { wallet: bidder1, public: publicClient } },
);

await auctionAsBidder1.write.bid({ value: 2n });

await assert.rejects(async () => {
await auctionAsBidder1.write.bid({ value: 2n });
});

await assert.rejects(async () => {
await auction.write.bid({ value: 2n });
});
});

it("allows owner to end after duration and prevents double end", async function () {
const auction = await deployAuction();

const auctionAsBidder1 = await viem.getContractAt(
"SimpleAuction",
auction.address,
{ client: { wallet: bidder1, public: publicClient } },
);

await auctionAsBidder1.write.bid({ value: 2n });

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

await auction.write.endAuction();
assert.equal(await auction.read.ended(), true);

await assert.rejects(async () => {
await auction.write.endAuction();
});
});

it("rejects bids after auction ends", async function () {
const auction = await deployAuction();

const auctionAsBidder1 = await viem.getContractAt(
"SimpleAuction",
auction.address,
{ client: { wallet: bidder1, public: publicClient } },
);

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

await assert.rejects(async () => {
await auctionAsBidder1.write.bid({ value: 2n });
});
});

it("rejects refund when no pending returns", async function () {
const auction = await deployAuction();

await assert.rejects(async () => {
await auction.write.withdrawRefund();
});
});
});
13 changes: 13 additions & 0 deletions Assignment/solidity-assignment7/contracts/auction/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*
Loading