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
1 change: 1 addition & 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 @@ -45,6 +45,7 @@
"node": ">=18.18.0"
},
"dependencies": {
"hardhat": "^2.22.17",
"react-icons": "^5.3.0"
}
}
51 changes: 51 additions & 0 deletions packages/hardhat/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
## PixelCanvas: Decentralized Collaborative Pixel Art Smart Contract

### Contract Overview
PixelCanvas is an Ethereum smart contract that enables collaborative pixel art creation on a fixed 64x64 canvas with a predefined color palette.

### 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
initializeBuidlGuidlLogo(): Sets initial canvas state with Batch11 logo

3. Contract Management
withdraw(): Allows owner to withdraw contract balance
Accepts Ether via fallback() and receive() functions

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

Current Limitations
Fixed canvas size (64x64)
No pixel modification after placement
Limited to 8 colors
Only contract owner can withdraw funds



129 changes: 129 additions & 0 deletions packages/hardhat/contracts/PixelCanvas.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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");


(bool success, ) = payable(owner()).call{value: balance}("");
require(success, "Transfer failed");


emit Withdrawal(owner(), balance);
}

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

receive() external payable {}
}
31 changes: 21 additions & 10 deletions packages/hardhat/deploy/00_deploy_your_contract.ts
Copy link
Collaborator

Choose a reason for hiding this comment

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

See my comment in the conversation.
Leave the script as it is, just add the deploy contract block to it (like it is done for batch registry).

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";

// Update with your Batch number
const BATCH_NUMBER = "11";
// const BATCH_NUMBER = "11";

/**
* Deploys a contract named "deployYourContract" using the deployer account and
Expand All @@ -25,28 +25,39 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
const { deployer } = await hre.getNamedAccounts();
const { deploy } = hre.deployments;

await deploy("BatchRegistry", {
// await deploy("BatchRegistry", {
// from: deployer,
// // Contract constructor arguments
// args: [deployer, BATCH_NUMBER],
await deploy("PixelCanvas", {
from: deployer,
// Contract constructor arguments
args: [deployer, BATCH_NUMBER],
args: [],
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 batchRegistry = await hre.ethers.getContract<Contract>("BatchRegistry", deployer);
console.log("\nBatchRegistry deployed to:", await batchRegistry.getAddress());
console.log("Remember to update the allow list!\n");
// const batchRegistry = await hre.ethers.getContract<Contract>("BatchRegistry", deployer);
// console.log("\nBatchRegistry deployed to:", await batchRegistry.getAddress());
// console.log("Remember to update the allow list!\n");
Copy link
Contributor

Choose a reason for hiding this comment

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

we still need to deploy the BatchRegistry, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually don't have idea about it that's why i commented it out till @phipsae replied

Copy link
Collaborator

Choose a reason for hiding this comment

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

As a reviewer of the PR, I would first test it in my local environment, which involves deploying all the contracts (including batchRegistry). Therefore, you should add the deployment of your new contract to the existing script (so you leave the script as it is and add the additional deploy code).


const pixelCanvas = await hre.ethers.getContract<Contract>("PixelCanvas", deployer);
console.log("\nPixelCanvas deployed to:", await pixelCanvas.getAddress());

// The GraduationNFT contract is deployed on the BatchRegistry constructor.
const batchGraduationNFTAddress = await batchRegistry.batchGraduationNFT();
console.log("BatchGraduation NFT deployed to:", batchGraduationNFTAddress, "\n");
// const batchGraduationNFTAddress = await batchRegistry.batchGraduationNFT();
// console.log("BatchGraduation NFT deployed to:", batchGraduationNFTAddress, "\n");

// Verify initial canvas state
const canvasWidth = await pixelCanvas.CANVAS_WIDTH();
const canvasHeight = await pixelCanvas.CANVAS_HEIGHT();
console.log(`Canvas dimensions: ${canvasWidth}x${canvasHeight}`);
};

export default deployYourContract;

// 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 = ["BatchRegistry"];
deployYourContract.tags = ["PixelCanvas"];
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