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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ node_modules
# Rust build artifacts
/target
**/target
/arms_cache
123 changes: 123 additions & 0 deletions kelechi/assingmet/TodoFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract TodoList {
struct Todo {
string text;
bool completed;
bool deleted;
uint256 createdAt;
uint256 updatedAt;
}

address public immutable owner;
uint256 private _todoCount;
mapping(uint256 => Todo) private _todos;

event TodoAdded(uint256 indexed todoId, string text, uint256 timestamp);
event TodoUpdated(uint256 indexed todoId, string newText, uint256 timestamp);
event TodoCompleted(uint256 indexed todoId, bool completed, uint256 timestamp);
event TodoDeleted(uint256 indexed todoId, uint256 timestamp);

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

constructor(address _owner) {
require(_owner != address(0), "Invalid owner");
owner = _owner;
}

function addTodo(string calldata text) external onlyOwner returns (uint256 todoId) {
require(bytes(text).length > 0, "Text cannot be empty");

todoId = _todoCount;
_todos[todoId] = Todo({
text: text,
completed: false,
deleted: false,
createdAt: block.timestamp,
updatedAt: block.timestamp
});

_todoCount++;
emit TodoAdded(todoId, text, block.timestamp);
}

function updateTodoText(uint256 todoId, string calldata newText) external onlyOwner {
require(todoId < _todoCount, "Todo does not exist");
require(bytes(newText).length > 0, "Text cannot be empty");

Todo storage todo = _todos[todoId];
require(!todo.deleted, "Todo is deleted");

todo.text = newText;
todo.updatedAt = block.timestamp;
emit TodoUpdated(todoId, newText, block.timestamp);
}

function setTodoCompleted(uint256 todoId, bool completed) external onlyOwner {
require(todoId < _todoCount, "Todo does not exist");

Todo storage todo = _todos[todoId];
require(!todo.deleted, "Todo is deleted");

todo.completed = completed;
todo.updatedAt = block.timestamp;
emit TodoCompleted(todoId, completed, block.timestamp);
}

function deleteTodo(uint256 todoId) external onlyOwner {
require(todoId < _todoCount, "Todo does not exist");

Todo storage todo = _todos[todoId];
require(!todo.deleted, "Todo already deleted");

todo.deleted = true;
todo.updatedAt = block.timestamp;
emit TodoDeleted(todoId, block.timestamp);
}

function getTodo(uint256 todoId) external view returns (Todo memory todo) {
require(todoId < _todoCount, "Todo does not exist");
todo = _todos[todoId];
}

function totalTodos() external view returns (uint256) {
return _todoCount;
}
}

contract TodoFactory {
mapping(address => address[]) private _userTodoLists;
address[] private _allTodoLists;

event TodoListCreated(address indexed creator, address indexed todoList, uint256 userListCount);

function createTodoList() external returns (address todoListAddress) {
TodoList todoList = new TodoList(msg.sender);
todoListAddress = address(todoList);

_userTodoLists[msg.sender].push(todoListAddress);
_allTodoLists.push(todoListAddress);

emit TodoListCreated(msg.sender, todoListAddress, _userTodoLists[msg.sender].length);
}

function getMyTodoLists() external view returns (address[] memory) {
return _userTodoLists[msg.sender];
}

function getTodoListsByUser(address user) external view returns (address[] memory) {
return _userTodoLists[user];
}

function getAllTodoLists() external view returns (address[] memory) {
return _allTodoLists;
}

function totalTodoLists() external view returns (uint256) {
return _allTodoLists.length;
}
}
38 changes: 38 additions & 0 deletions kelechi/assingmet/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable, defineConfig } from "hardhat/config";

export default defineConfig({
plugins: [hardhatToolboxMochaEthersPlugin],
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")],
},
},
});
20 changes: 20 additions & 0 deletions kelechi/assingmet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "todo-factory-hardhat",
"version": "1.0.0",
"type": "module",
"devDependencies": {
"@nomicfoundation/hardhat-ethers": "^4.0.4",
"@nomicfoundation/hardhat-ignition": "^3.0.7",
"@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2",
"@types/chai": "^4.3.20",
"@types/chai-as-promised": "^8.0.2",
"@types/mocha": "^10.0.10",
"@types/node": "^22.19.11",
"chai": "^5.3.3",
"ethers": "^6.16.0",
"forge-std": "github:foundry-rs/forge-std#v1.9.4",
"hardhat": "^3.1.8",
"mocha": "^11.7.5",
"typescript": "~5.8.0"
}
}
123 changes: 123 additions & 0 deletions kelechi/assingmet/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Todo Factory (Solidity)

Public factory pattern for todo lists:

- Anyone can create a personal `TodoList` from `TodoFactory`.
- Each `TodoList` is owned by its creator.
- Only the owner of a list can add, update, complete, or delete todos in that list.

## Project Structure

- `TodoFactory.sol`: contains both `TodoFactory` and `TodoList`
- `test/TodoFactory.test.ts`: TypeScript unit tests (Hardhat + Chai)
- `hardhat.config.ts`: Hardhat config
- `tsconfig.json`: TypeScript config
- `package.json`: scripts and dev dependencies

## Contract Design

### `TodoFactory`

Responsibilities:

- Deploys new `TodoList` contracts with `owner = msg.sender`
- Stores per-user list addresses
- Stores global list addresses

Main functions:

- `createTodoList() returns (address)`
- `getMyTodoLists() returns (address[])`
- `getTodoListsByUser(address user) returns (address[])`
- `getAllTodoLists() returns (address[])`
- `totalTodoLists() returns (uint256)`

Event:

- `TodoListCreated(address creator, address todoList, uint256 userListCount)`

### `TodoList`

Todo item fields:

- `text`
- `completed`
- `deleted` (soft delete)
- `createdAt`
- `updatedAt`

Main functions:

- `addTodo(string text) returns (uint256 todoId)`
- `updateTodoText(uint256 todoId, string newText)`
- `setTodoCompleted(uint256 todoId, bool completed)`
- `deleteTodo(uint256 todoId)`
- `getTodo(uint256 todoId) returns (Todo)`
- `totalTodos() returns (uint256)`

Access control:

- write operations are protected by `onlyOwner`

Events:

- `TodoAdded`
- `TodoUpdated`
- `TodoCompleted`
- `TodoDeleted`

## Local Development (Hardhat + TypeScript)

### Prerequisites

- Node.js 18+ (recommended)
- npm

### Install

```bash
cd kelechi/assingmet
npm install
```

### Compile

```bash
npm run compile
```

### Run Unit Tests

```bash
npm test
```

Current test coverage includes:

- factory deployment and address tracking
- ownership assignment
- todo CRUD lifecycle
- non-owner revert checks
- invalid input/state revert checks

## Remix Quick Test

1. Open Remix and create `TodoFactory.sol`
2. Paste the contract code
3. Compile using Solidity `0.8.24`
4. Deploy `TodoFactory`
5. Call `createTodoList()`
6. Call `getMyTodoLists()` and copy the list address
7. Load `TodoList` at that address using `At Address`
8. Call:
- `addTodo("Learn Solidity")`
- `updateTodoText(0, "Learn Solidity deeply")`
- `setTodoCompleted(0, true)`
- `deleteTodo(0)`
- `getTodo(0)`

## Security Notes

- Factory is intentionally public.
- Each list is isolated by owner account.
- Deleted todos are soft-deleted (`deleted = true`) and remain queryable.
Loading