Skip to content

Commit

Permalink
implement OwnerData contract supporting user adding custom data
Browse files Browse the repository at this point in the history
  • Loading branch information
tienngovan committed Feb 2, 2024
1 parent 9a07563 commit 7995df8
Show file tree
Hide file tree
Showing 3 changed files with 358 additions and 0 deletions.
77 changes: 77 additions & 0 deletions contracts/OwnerData.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/Context.sol";

contract FFV4 {
function ownerOf(uint256 tokenId) public view returns (address) {}
}

contract OwnerData is Context {
struct Data {
address owner; // Address of the owner who submitted the data
bytes dataHash; // Hash of the actual data stored off-chain (e.g., IPFS CID)
string metadata; // Any additional metadata associated with the data
}

// Mapping to store contract => tokenID => data[]
mapping(address => mapping(uint256 => Data[])) private tokenData;

constructor() {}

function add(address contractAddress, uint256 tokenID, Data calldata data) external {
// check ownership
address tokenOwner = FFV4(contractAddress).ownerOf(tokenID);

require(data.owner == _msgSender() && tokenOwner == _msgSender(), "OwnerData: owner mismatch");

bool exists = false;

for (uint i = 0; i < tokenData[contractAddress][tokenID].length; i++) {
if (tokenData[contractAddress][tokenID][i].owner == data.owner) {
// update existing data
tokenData[contractAddress][tokenID][i] = data;
exists = true;
break;
}
}

if (!exists) {
tokenData[contractAddress][tokenID].push(data);
}

emit DataAdded(contractAddress, tokenID, data);
}

function remove(address contractAddress, uint256 tokenID) external {
// check ownership
address tokenOwner = FFV4(contractAddress).ownerOf(tokenID);

require(tokenOwner == _msgSender(), "OwnerData: owner mismatch");

uint index;
for (uint i = 0; i < tokenData[contractAddress][tokenID].length; i++) {
if (tokenData[contractAddress][tokenID][i].owner == _msgSender()) {
index = i;
break;
}
}

require(index >= 0 && index < tokenData[contractAddress][tokenID].length, "OwnerData: data not found");

// remove data from array
for (uint j = index; j < tokenData[contractAddress][tokenID].length - 1; j++) {
tokenData[contractAddress][tokenID][j] = tokenData[contractAddress][tokenID][j + 1];
}
tokenData[contractAddress][tokenID].pop();

// emit event
emit DataRemoved(contractAddress, tokenID);
}

function get(address contractAddress, uint256 tokenID) external view returns (Data[] memory) {
return tokenData[contractAddress][tokenID];
}

event DataAdded(address contractAddress, uint256 tokenID, Data data);
event DataRemoved(address contractAddress, uint256 tokenID);
}
5 changes: 5 additions & 0 deletions migrations/252_owner_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
var OwnerData = artifacts.require("OwnerData");

module.exports = function (deployer) {
deployer.deploy(OwnerData);
};
276 changes: 276 additions & 0 deletions test/owner_data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
const OwnerData = artifacts.require("OwnerData");
const FeralfileExhibitionV4 = artifacts.require("FeralfileExhibitionV4");
const FeralfileVault = artifacts.require("FeralfileVault");

const CONTRACT_URI = "ipfs://QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";

const bytesToString = (bytes) => {
return web3.utils.toAscii(bytes).replace(/\u0000/g, "");
};

contract("OwnerData", async (accounts) => {
before(async function () {
this.signer = accounts[0];
this.vault = await FeralfileVault.new(this.signer);
this.ownerDataContract = await OwnerData.new();
this.exhibitionContract = await FeralfileExhibitionV4.new(
"Feral File V4 Test",
"FFv4",
true,
true,
this.signer,
this.vault.address,
this.signer,
CONTRACT_URI,
[1],
[1000]
);

await this.exhibitionContract.mintArtworks([
[1, 1, accounts[0]],
[1, 2, accounts[1]],
[1, 3, accounts[2]],
[1, 4, accounts[0]],
[1, 5, accounts[1]],
[1, 6, accounts[2]],
]);
});

it("test adding data", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
const tx = await this.ownerDataContract.add(
this.exhibitionContract.address,
1,
[accounts[0], cidBytes, "{duration: 1000}"]
);
const { logs } = tx;
assert.equal(logs[0].event, "DataAdded");

assert.equal(bytesToString(logs[0].args.data.dataHash), cid);
});

it("test getting data", async function () {
const data = await this.ownerDataContract.get(
this.exhibitionContract.address,
1
);
assert.equal(data[0].owner, accounts[0]);
assert.equal(
bytesToString(data[0].dataHash),
"QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc"
);
assert.equal(data[0].metadata, "{duration: 1000}");
});

it("test remove data successfully", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
const tx = await this.ownerDataContract.add(
this.exhibitionContract.address,
1,
[accounts[0], cidBytes, "{duration: 1000}"]
);
assert.equal(tx.logs[0].event, "DataAdded");

const tx2 = await this.ownerDataContract.remove(
this.exhibitionContract.address,
1
);
assert.equal(tx2.logs[0].event, "DataRemoved");

const data = await this.ownerDataContract.get(
this.exhibitionContract.address,
1
);
assert.equal(data.length, 0);
});

it("test remove data successfully in list data", async function () {
const cid1 = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cid2 = "QmNVdQSp1AvZonLwHzTbbZDPLgbpty15RMQrbPEWd4ooTU";
const cidBytes1 = web3.utils.fromAscii(cid1);
const cidBytes2 = web3.utils.fromAscii(cid2);

const tx1 = await this.ownerDataContract.add(
this.exhibitionContract.address,
4,
[accounts[0], cidBytes1, "{duration: 1000}"],
{ from: accounts[0] }
);
assert.equal(bytesToString(tx1.logs[0].args.data.dataHash), cid1);

// Transfer to account 2
await this.exhibitionContract.transferFrom(
accounts[0],
accounts[2],
4,
{ from: accounts[0] }
);
const owner = await this.exhibitionContract.ownerOf(4);
assert.equal(owner, accounts[2]);

const tx2 = await this.ownerDataContract.add(
this.exhibitionContract.address,
4,
[accounts[2], cidBytes2, "{duration: 2000}"],
{ from: accounts[2] }
);
assert.equal(bytesToString(tx2.logs[0].args.data.dataHash), cid2);

const data = await this.ownerDataContract.get(
this.exhibitionContract.address,
4
);

assert.equal(data.length, 2);
assert.equal(data[0].owner, accounts[0]);
assert.equal(bytesToString(data[0].dataHash), cid1);

assert.equal(data[1].owner, accounts[2]);
assert.equal(bytesToString(data[1].dataHash), cid2);

const tx3 = await this.ownerDataContract.remove(
this.exhibitionContract.address,
4,
{ from: accounts[2] }
);
assert.equal(tx3.logs[0].event, "DataRemoved");

const data2 = await this.ownerDataContract.get(
this.exhibitionContract.address,
4
);
assert.equal(data2.length, 1);
assert.equal(data2[0].owner, accounts[0]);
});

it("test updating data", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
const tx = await this.ownerDataContract.add(
this.exhibitionContract.address,
3,
[accounts[2], cidBytes, "{duration: 1000}"],
{ from: accounts[2] }
);
assert.equal(bytesToString(tx.logs[0].args.data.dataHash), cid);

const updatedCid = "QmNVdQSp1AvZonLwHzTbbZDPLgbpty15RMQrbPEWd4ooTU";
const updatedCidBytes = web3.utils.fromAscii(updatedCid);
const tx2 = await this.ownerDataContract.add(
this.exhibitionContract.address,
3,
[accounts[2], updatedCidBytes, "{duration: 2000}"],
{ from: accounts[2] }
);
assert.equal(bytesToString(tx2.logs[0].args.data.dataHash), updatedCid);

const data = await this.ownerDataContract.get(
this.exhibitionContract.address,
3
);
assert.equal(data[0].owner, accounts[2]);
assert.equal(bytesToString(data[0].dataHash), updatedCid);
});

it("test adding multiple data", async function () {
const cid1 = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cid2 = "QmNVdQSp1AvZonLwHzTbbZDPLgbpty15RMQrbPEWd4ooTU";
const cid3 = "QmR7jnM6jgoAE6VZf6uiRRF3e6JULgJ6GYMh1kRPRtr3CS";
const cidBytes1 = web3.utils.fromAscii(cid1);
const cidBytes2 = web3.utils.fromAscii(cid2);
const cidBytes3 = web3.utils.fromAscii(cid3);

const tx1 = await this.ownerDataContract.add(
this.exhibitionContract.address,
2,
[accounts[1], cidBytes1, "{duration: 1000}"],
{ from: accounts[1] }
);
assert.equal(bytesToString(tx1.logs[0].args.data.dataHash), cid1);

// Transfer to account 3
await this.exhibitionContract.transferFrom(
accounts[1],
accounts[2],
2,
{ from: accounts[1] }
);
const owner = await this.exhibitionContract.ownerOf(2);
assert.equal(owner, accounts[2]);

const tx2 = await this.ownerDataContract.add(
this.exhibitionContract.address,
2,
[accounts[2], cidBytes2, "{duration: 2000}"],
{ from: accounts[2] }
);
assert.equal(bytesToString(tx2.logs[0].args.data.dataHash), cid2);

// Transfer to account 4
await this.exhibitionContract.transferFrom(
accounts[2],
accounts[4],
2,
{ from: accounts[2] }
);
const owner2 = await this.exhibitionContract.ownerOf(2);
assert.equal(owner2, accounts[4]);

const tx3 = await this.ownerDataContract.add(
this.exhibitionContract.address,
2,
[accounts[4], cidBytes3, "{duration: 3000}"],
{ from: accounts[4] }
);
assert.equal(bytesToString(tx3.logs[0].args.data.dataHash), cid3);

const data = await this.ownerDataContract.get(
this.exhibitionContract.address,
2
);

assert.equal(data.length, 3);
assert.equal(data[0].owner, accounts[1]);
assert.equal(bytesToString(data[0].dataHash), cid1);

assert.equal(data[1].owner, accounts[2]);
assert.equal(bytesToString(data[1].dataHash), cid2);

assert.equal(data[2].owner, accounts[4]);
assert.equal(bytesToString(data[2].dataHash), cid3);
});

it("test adding fail with wrong owner address", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
try {
await this.ownerDataContract.add(
this.exhibitionContract.address,
1,
[accounts[1], cidBytes, "{duration: 1000}"]
);
} catch (error) {
assert.equal(error.reason, "OwnerData: owner mismatch");
}
});

it("test adding fail with invalid contract address", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
try {
await this.ownerDataContract.add(accounts[1], 1, [
accounts[0],
cidBytes,
"{duration: 1000}",
]);
} catch (error) {
assert.ok(
error.message.includes(
"VM Exception while processing transaction: revert"
)
);
}
});
});

0 comments on commit 7995df8

Please sign in to comment.