Skip to content

Commit

Permalink
Merge pull request #32 from Abidoyesimze/canvasOnchain
Browse files Browse the repository at this point in the history
Change the branch name to canvasOnchain and also write a readme file in the hardhat folder for description
  • Loading branch information
phipsae authored Dec 17, 2024
2 parents a5befac + 0fad4a1 commit 270c25a
Show file tree
Hide file tree
Showing 6 changed files with 1,005 additions and 2 deletions.
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";

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 {}
}
15 changes: 14 additions & 1 deletion packages/hardhat/deploy/00_deploy_your_contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
// Contract constructor arguments
args: [deployer, BATCH_NUMBER],
log: true,
});

await deploy("PixelCanvas", {
from: deployer,
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,
Expand All @@ -40,13 +45,21 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn
console.log("\nBatchRegistry deployed to:", await batchRegistry.getAddress());
console.log("Remember to update the allow list!\n");

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");

// 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 = ["BatchRegistry", "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

0 comments on commit 270c25a

Please sign in to comment.