Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change the branch name to canvasOnchain and also write a readme file in the hardhat folder for description #32

Merged
merged 13 commits into from
Dec 17, 2024
Merged
2 changes: 2 additions & 0 deletions package.json
phipsae marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No changes to this file, pls.

Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
},
"packageManager": "[email protected]",
"devDependencies": {
"@nomicfoundation/hardhat-network-helpers": "^1.0.12",
phipsae marked this conversation as resolved.
Show resolved Hide resolved
"husky": "^9.1.6",
"lint-staged": "^15.2.10"
},
"engines": {
"node": ">=18.18.0"
},
"dependencies": {
"hardhat": "^2.22.17",
"react-icons": "^5.3.0"
}
}
48 changes: 48 additions & 0 deletions packages/hardhat/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## PixelCanvas: Decentralized Collaborative Pixel Art Smart Contract

### Contract Overview
A collaborative on-chain pixel art platform implemented as an Ethereum smart contract, enabling users to create and modify a shared 64x64 pixel canvas.

### Technical Specifications
- **Blockchain**: Ethereum
- **Solidity Version**: ^0.8.20
- **Dependencies**: OpenZeppelin Ownable
- **Canvas Dimensions**: 64x64 pixels
- **Color Palette**: 8 predefined colors

### Key Components

#### Pixel Structure
```solidity
struct Pixel {
address author; // Address of pixel creator
Color color; // Chosen color from enum
uint256 timestamp;// Block timestamp of pixel placement
}

Core Functionalities
1. Pixel Placement

Function: placePixel(uint256 x, uint256 y, Color color)
Validates coordinate boundaries
Allows user to place a single pixel
Emits PixelPlaced event for tracking

2. Canvas Retrieval

getPixel(x, y): Retrieves individual pixel information
getCanvasSnapshot(): Returns entire canvas state

3. Initialization
Test the PixelCanvas contract
Includes default Buidlguidl Batch11 logo on contract deployment
Demonstrates initial canvas state

Known Limitations

Fixed 64x64 canvas size
Limited 8-color palette
No pixel modification after placement



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

import "@openzeppelin/contracts/access/Ownable.sol";
phipsae marked this conversation as resolved.
Show resolved Hide resolved

contract PixelCanvas is Ownable {
// Canvas Dimensions
uint256 public constant CANVAS_WIDTH = 64;
uint256 public constant CANVAS_HEIGHT = 64;

// Color Palette (Limited Options)
enum Color {
WHITE,
BLACK,
RED,
GREEN,
BLUE,
YELLOW,
PURPLE,
ORANGE
}

// Pixel Structure
struct Pixel {
address author;
Color color;
uint256 timestamp;
}

// Mapping to store modified pixels
mapping(uint256 => mapping(uint256 => Pixel)) public canvas;


// Events
event PixelPlaced(address indexed author, uint256 x, uint256 y, Color color);
event Withdrawal(address indexed owner, uint256 amount);

// Constructor to initialize default Buidlguidl Batch11 drawing
constructor() Ownable(msg.sender){

// Initial drawing representing Buidlguidl Batch11 logo
initializeBuidlGuidlLogo();
}

/**
* @dev Initialize a default Buidlguidl Batch11 inspired pixel art
* This is a simplified representation and can be customized
*/
function initializeBuidlGuidlLogo() private {
for (uint256 x = 10; x < 20; x++) {
for (uint256 y = 10; y < 50; y++) {
canvas[x][y] = Pixel({
author: msg.sender,
color: Color.BLUE,
timestamp: block.timestamp
});
}
}

// 11 representation with some pixels
for (uint256 x = 30; x < 40; x++) {
for (uint256 y = 20; y < 30; y++) {
canvas[x][y] = Pixel({
author: msg.sender,
color: Color.GREEN,
timestamp: block.timestamp
});
}
}

// Add some distinctive pixels to represent Buidlguidl spirit
canvas[32][25] = Pixel({
author: msg.sender,
color: Color.RED,
timestamp: block.timestamp
});
}

/**
* @dev Place a pixel on the canvas
* @param x X-coordinate of the pixel
* @param y Y-coordinate of the pixel
* @param color Color of the pixel
*/
function placePixel(uint256 x, uint256 y, Color color) external {
require(x < CANVAS_WIDTH, "X coordinate out of bounds");
require(y < CANVAS_HEIGHT, "Y coordinate out of bounds");

canvas[x][y] = Pixel({
author: msg.sender,
color: color,
timestamp: block.timestamp
});

emit PixelPlaced(msg.sender, x, y, color);
}


/**
* @dev Get pixel information
* @param x X-coordinate of the pixel
* @param y Y-coordinate of the pixel
* @return Pixel details
*/
function getPixel(uint256 x, uint256 y) external view returns (Pixel memory) {
require(x < CANVAS_WIDTH, "X coordinate out of bounds");
require(y < CANVAS_HEIGHT, "Y coordinate out of bounds");
return canvas[x][y];
}



function withdraw() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No funds to withdraw");

// Transfer the balance to the owner
payable(owner()).transfer(balance);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


// Emit withdrawal event
emit Withdrawal(owner(), balance);
}

// Fallback and receive functions to accept Ether
fallback() external payable {}

receive() external payable {}
}
2 changes: 1 addition & 1 deletion packages/hardhat/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const config: HardhatUserConfig = {
accounts: [deployerPrivateKey],
},
sepolia: {
url: `https://rpc2.sepolia.org`,
url: `https://ethereum-sepolia.blockpi.network/v1/rpc/public`,
accounts: [deployerPrivateKey],
},
arbitrum: {
Expand Down
74 changes: 74 additions & 0 deletions packages/hardhat/test/PixelCanvas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { loadFixture } from "@nomicfoundation/hardhat-network-helpers";
import { expect } from "chai";
import hre from "hardhat";

describe("PixelCanvas", function () {
async function deployPixelCanvas() {
const [owner, user1, user2] = await hre.ethers.getSigners();
const pixel = await hre.ethers.getContractFactory("PixelCanvas");
const Pixel = await pixel.deploy();
return { owner, user1, user2, Pixel };
}

describe("Deployment", function () {
it("Should set the correct owner", async function () {
const { owner, Pixel } = await loadFixture(deployPixelCanvas);
expect(await Pixel.owner()).to.equal(owner.address);
});

it("Should initialize canvas with predefined pixels", async function () {
// Check a few predefined pixels from initializeBuidlGuidlLogo()
const { owner, Pixel } = await loadFixture(deployPixelCanvas);
const bluePixel = await Pixel.canvas(15, 25);
expect(bluePixel.color).to.equal(4); // Blue is enum index 1
expect(bluePixel.author).to.equal(owner.address);
});
});

describe("Pixel Placement", function () {
it("Should allow placing a pixel", async function () {
const x = 10;
const y = 20;
const color = 2; // Red from enum
const { user1, Pixel } = await loadFixture(deployPixelCanvas);
// Place pixel from user1
await Pixel.connect(user1).placePixel(x, y, color);

const pixel = await Pixel.canvas(x, y);
expect(pixel.author).to.equal(user1.address);
expect(pixel.color).to.equal(color);
});

it("Should reject out-of-bounds pixel placement", async function () {
const { Pixel } = await loadFixture(deployPixelCanvas);
await expect(Pixel.placePixel(64, 10, 0)).to.be.revertedWith("X coordinate out of bounds");

await expect(Pixel.placePixel(10, 64, 0)).to.be.revertedWith("Y coordinate out of bounds");
});

it("Should emit PixelPlaced event", async function () {
const x = 30;
const y = 40;

const color = 3; // Green from enum
const { user1, Pixel } = await loadFixture(deployPixelCanvas);
await expect(Pixel.connect(user1).placePixel(x, y, color))
.to.emit(Pixel, "PixelPlaced")
.withArgs(user1.address, x, y, color);
});
});

describe("Pixel Retrieval", function () {
it("Should retrieve pixel information", async function () {
const x = 12;
const y = 15;
const color = 4; // Blue from enum
const { user1, Pixel } = await loadFixture(deployPixelCanvas);
await Pixel.connect(user1).placePixel(x, y, color);

const pixel = await Pixel.getPixel(x, y);
expect(pixel.author).to.equal(user1.address);
expect(pixel.color).to.equal(color);
});
});
});
Loading
Loading