Skip to content

Commit

Permalink
added signedAdd for OwnerData and refactored contract
Browse files Browse the repository at this point in the history
  • Loading branch information
tienngovan committed Feb 21, 2024
1 parent 7995df8 commit ee72761
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 59 deletions.
129 changes: 73 additions & 56 deletions contracts/OwnerData.sol
Original file line number Diff line number Diff line change
@@ -1,77 +1,94 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/utils/Context.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract FFV4 {
function ownerOf(uint256 tokenId) public view returns (address) {}
contract FF {
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() {}
string constant SIGNED_MESSAGE = "Feral File is requesting authorization to write your sound piece to contract";

function add(address contractAddress, uint256 tokenID, Data calldata data) external {
// check ownership
address tokenOwner = FFV4(contractAddress).ownerOf(tokenID);
contract OwnerData is Context {
struct Data {
address owner;
bytes dataHash;
string metadata;
}

require(data.owner == _msgSender() && tokenOwner == _msgSender(), "OwnerData: owner mismatch");
mapping(address => mapping(uint256 => Data[])) private _tokenData;

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;
}
}
function add(address contractAddress, uint256 tokenID, Data calldata data) external {
require(_isOwner(contractAddress, tokenID, _msgSender()), "OwnerData: caller is not the owner");
require(data.owner == _msgSender(), "OwnerData: data owner mismatch");
_updateData(contractAddress, tokenID, data);
}

if (!exists) {
tokenData[contractAddress][tokenID].push(data);
function signedAdd(
address contractAddress,
uint256 tokenID,
bytes memory signature,
Data calldata data
) external {
address signer = _verifySignature(signature);
require(_isOwner(contractAddress, tokenID, signer), "OwnerData: signer is not the owner");
require(data.owner == signer, "OwnerData: data owner mismatch");
_updateData(contractAddress, tokenID, data);
}

emit DataAdded(contractAddress, tokenID, data);
}

function remove(address contractAddress, uint256 tokenID) external {
// check ownership
address tokenOwner = FFV4(contractAddress).ownerOf(tokenID);
function remove(address contractAddress, uint256 tokenID) external {
require(_isOwner(contractAddress, tokenID, _msgSender()), "OwnerData: caller is not the owner");
_removeData(contractAddress, tokenID);
}

require(tokenOwner == _msgSender(), "OwnerData: owner mismatch");
function get(address contractAddress, uint256 tokenID) external view returns (Data[] memory) {
return _tokenData[contractAddress][tokenID];
}

uint index;
for (uint i = 0; i < tokenData[contractAddress][tokenID].length; i++) {
if (tokenData[contractAddress][tokenID][i].owner == _msgSender()) {
index = i;
break;
}
function _updateData(
address contractAddress,
uint256 tokenID,
Data calldata data
) private {
Data[] storage datas = _tokenData[contractAddress][tokenID];
bool exists = false;
for (uint256 i = 0; i < datas.length; ++i) {
if (datas[i].owner == data.owner) {
datas[i] = data;
exists = true;
break;
}
}
if (!exists) {
datas.push(data);
}
emit DataAdded(contractAddress, tokenID, data);
}

require(index >= 0 && index < tokenData[contractAddress][tokenID].length, "OwnerData: data not found");
function _removeData(address contractAddress, uint256 tokenID) private {
Data[] storage datas = _tokenData[contractAddress][tokenID];
for (uint256 i = 0; i < datas.length; ++i) {
if (datas[i].owner == _msgSender()) {
datas[i] = datas[datas.length - 1];
datas.pop();
emit DataRemoved(contractAddress, tokenID);
return;
}
}
revert("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];
function _verifySignature(bytes memory signature) private view returns (address) {
bytes memory message = abi.encodePacked(SIGNED_MESSAGE, " ", Strings.toHexString(address(this)), ".");
return ECDSA.recover(ECDSA.toEthSignedMessageHash(message), signature);
}
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];
}
function _isOwner(address contractAddress, uint256 tokenID, address owner) private view returns (bool) {
return FF(contractAddress).ownerOf(tokenID) == owner;
}

event DataAdded(address contractAddress, uint256 tokenID, Data data);
event DataRemoved(address contractAddress, uint256 tokenID);
event DataAdded(address indexed contractAddress, uint256 indexed tokenID, Data data);
event DataRemoved(address indexed contractAddress, uint256 indexed tokenID);
}
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"dependencies": {
"@openzeppelin/contracts": "4.9.0",
"@truffle/hdwallet-provider": "^2.0.3",
"axios": "^0.27.2"
"axios": "^0.27.2",
"ethereumjs-util": "^7.1.5"
},
"devDependencies": {
"@openzeppelin/test-helpers": "^0.5.16",
Expand Down
29 changes: 28 additions & 1 deletion test/owner_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const FeralfileVault = artifacts.require("FeralfileVault");

const CONTRACT_URI = "ipfs://QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";

const { bufferToHex } = require("ethereumjs-util");

const bytesToString = (bytes) => {
return web3.utils.toAscii(bytes).replace(/\u0000/g, "");
};
Expand Down Expand Up @@ -33,6 +35,7 @@ contract("OwnerData", async (accounts) => {
[1, 4, accounts[0]],
[1, 5, accounts[1]],
[1, 6, accounts[2]],
[1, 7, "0x23221e5403511CeC833294D2B1B006e9D639A61b"],
]);
});

Expand Down Expand Up @@ -252,7 +255,7 @@ contract("OwnerData", async (accounts) => {
[accounts[1], cidBytes, "{duration: 1000}"]
);
} catch (error) {
assert.equal(error.reason, "OwnerData: owner mismatch");
assert.equal(error.reason, "OwnerData: data owner mismatch");
}
});

Expand All @@ -273,4 +276,28 @@ contract("OwnerData", async (accounts) => {
);
}
});

it("test adding with signed add function", async function () {
const cid = "QmQPeNsJPyVWPFDVHb77w8G42Fvo15z4bG2X8D2GhfbSXc";
const cidBytes = web3.utils.fromAscii(cid);
const data = [
"0x23221e5403511CeC833294D2B1B006e9D639A61b",
cidBytes,
"{duration: 1000}",
];

const msg = `Feral File is requesting authorization to write your sound piece to contract ${this.ownerDataContract.address.toLowerCase()}.`;
const msgHash = bufferToHex(Buffer.from(msg, "utf-8"));
const privateKey =
"0x5cd8bcda59dd3a9988bd20bdbdea7225a4a57949d12b9a527caf3ff819941d7f";
const { signature } = await web3.eth.accounts.sign(msgHash, privateKey);

const tx = await this.ownerDataContract.signedAdd(
this.exhibitionContract.address,
1,
signature,
data
);
assert.equal(tx.logs[0].event, "DataAdded");
});
});

0 comments on commit ee72761

Please sign in to comment.