From 5b1efca741add858b0d9f344695a19ab722d2897 Mon Sep 17 00:00:00 2001 From: Tien Ngo Date: Tue, 5 Mar 2024 14:07:07 +0700 Subject: [PATCH] added blocknumber support sorting in get function --- contracts/OwnerData.sol | 112 ++++++++++++++++++++++------------- migrations/252_owner_data.js | 2 +- test/owner_data.js | 66 +++++++++++---------- 3 files changed, 105 insertions(+), 75 deletions(-) diff --git a/contracts/OwnerData.sol b/contracts/OwnerData.sol index a44d582..05d2241 100644 --- a/contracts/OwnerData.sol +++ b/contracts/OwnerData.sol @@ -17,11 +17,12 @@ contract OwnerData is Context, Ownable { string private constant SIGNED_MESSAGE = "Authorize to write your data to the contract"; address private immutable _signer; address private immutable _costReceiver; - uint256 private immutable _cost; + uint256 public cost; struct Data { address owner; bytes dataHash; + uint256 blockNumber; string metadata; } @@ -33,13 +34,6 @@ contract OwnerData is Context, Ownable { uint8 v; } - struct SignedAddParams { - address contractAddress; - uint256 tokenID; - Data data; - Signature signature; - } - // contractAddress => tokenID => Data[] mapping(address => mapping(uint256 => Data[])) private _tokenData; // contractAddress => tokenID => owner => bool @@ -49,17 +43,37 @@ contract OwnerData is Context, Ownable { event DataAdded(address indexed contractAddress, uint256 indexed tokenID, Data data); + error TrusteeIsZeroAddress(); + error CostReceiverIsZeroAddress(); + error CostIsZero(); + error PaymentRequiredForPublicToken(); + error IndexOutOfBounds(); + error InvalidParameters(); + error OwnerAndSenderMismatch(); + error SenderIsNotTheOwner(); + error OwnerDataAlreadyAdded(); + error InvalidSignature(); + + constructor(address signer_, address costReceiver_, uint256 cost_) { - require(signer_ != address(0), "OwnerData: Trustee is the zero address"); - require(costReceiver_ != address(0), "OwnerData: Cost receiver is the zero address"); - require(cost_ > 0, "OwnerData: Cost is zero"); + if (signer_ == address(0)) { + revert TrusteeIsZeroAddress(); + } + if (costReceiver_ == address(0)) { + revert CostReceiverIsZeroAddress(); + } + if (cost_ == 0) { + revert CostIsZero(); + } _signer = signer_; _costReceiver = costReceiver_; - _cost = cost_; + cost = cost_; } function add(address contractAddress_, uint256 tokenID_, Data calldata data_) external payable { - require(!_publicTokens[contractAddress_][tokenID_] || msg.value == _cost, "OwnerData: Payment required for public token"); + if (_publicTokens[contractAddress_][tokenID_] && msg.value != cost) { + revert PaymentRequiredForPublicToken(); + } _addData(_msgSender(), contractAddress_, tokenID_, data_); if (msg.value > 0) { payable(_costReceiver).transfer(msg.value); @@ -68,8 +82,10 @@ contract OwnerData is Context, Ownable { function get(address contractAddress_, uint256 tokenID_, uint256 startIndex, uint256 count) public view returns (Data[] memory) { Data[] memory data = _tokenData[contractAddress_][tokenID_]; - require(startIndex >= 0 && count > 0 && startIndex < data.length, "OwnerData: Invalid parameters"); - if (count > data.length - startIndex) { + if (startIndex > data.length) { + return new Data[](0); + } + if (startIndex + count > data.length) { count = data.length - startIndex; } Data[] memory result = new Data[](count); @@ -82,7 +98,9 @@ contract OwnerData is Context, Ownable { function remove(address contractAddress_, uint256 tokenID_, uint256[] calldata indexes_) external { Data[] storage data = _tokenData[contractAddress_][tokenID_]; for (uint256 i = 0; i < indexes_.length; i++) { - require(indexes_[i] < data.length, "Index out of bounds"); + if (indexes_[i] >= data.length) { + revert IndexOutOfBounds(); + } if (indexes_[i] != data.length - 1) { data[indexes_[i]] = data[data.length - 1]; } @@ -90,52 +108,60 @@ contract OwnerData is Context, Ownable { } } + function setCost(uint256 cost_) external onlyOwner { + if (cost_ == 0) { + revert CostIsZero(); + } + cost = cost_; + } + function setPublicTokens(address[] memory contractAddresses_, uint256[] memory tokenIDs_, bool isPublic_) external onlyOwner { - require(contractAddresses_.length == tokenIDs_.length, "OwnerData: Arrays length mismatch"); + if (contractAddresses_.length == 0 || contractAddresses_.length != tokenIDs_.length) { + revert InvalidParameters(); + } for (uint256 i = 0; i < contractAddresses_.length; i++) { _publicTokens[contractAddresses_[i]][tokenIDs_[i]] = isPublic_; } } - function signedAdd(SignedAddParams[] calldata params_) external { - for (uint256 i = 0; i < params_.length; i++) { - _signedAdd(params_[i]); + function signedAdd(address contractAddress_, uint256 tokenID_, Data calldata data_, Signature calldata signature_) external { + _validateSignature(signature_); + address account = data_.owner; + if (!_publicTokens[contractAddress_][tokenID_]) { + account = _recoverOwnerSignature(signature_.ownerSign); } + _addData(account, contractAddress_, tokenID_, data_); } - - function _signedAdd(SignedAddParams calldata params_) private { - _validateSignature(params_.signature); - if (_publicTokens[params_.contractAddress][params_.tokenID]) { - _addData(params_.data.owner, params_.contractAddress, params_.tokenID, params_.data); - } else { - address account = _recoverOwnerSignature(params_.signature.ownerSign); - _addData(account, params_.contractAddress, params_.tokenID, params_.data); + function _addData(address sender_, address contractAddress_, uint256 tokenID_, Data memory data_) private { + if (data_.owner != sender_) { + revert OwnerAndSenderMismatch(); + } + if (data_.dataHash.length == 0) { + revert InvalidParameters(); } - } - - function _addData( - address sender_, - address contractAddress_, - uint256 tokenID_, - Data calldata data_ - ) private { - require(data_.owner == sender_, "OwnerData: data owner and sender mismatch"); - require(data_.dataHash.length > 0, "OwnerData: dataHash is empty"); if (!_publicTokens[contractAddress_][tokenID_]) { - require(_isOwner(contractAddress_, tokenID_, data_.owner), "OwnerData: sender is not the owner"); - require(!_tokenDataOwner[contractAddress_][tokenID_][data_.owner], "OwnerData: data already added"); + if (_tokenDataOwner[contractAddress_][tokenID_][data_.owner]) { + revert OwnerDataAlreadyAdded(); + } + if (!_isOwner(contractAddress_, tokenID_, data_.owner)) { + revert SenderIsNotTheOwner(); + } _tokenDataOwner[contractAddress_][tokenID_][data_.owner] = true; } + data_.blockNumber = block.number; + _tokenData[contractAddress_][tokenID_].push(data_); emit DataAdded(contractAddress_, tokenID_, data_); } function _validateSignature(Signature calldata signature_) private view { - require(block.number < signature_.expiryBlock, "OwnerData: signature expired"); + if (block.number > signature_.expiryBlock) { + revert InvalidSignature(); + } bytes32 message = keccak256( abi.encode(block.chainid, address(this), signature_.ownerSign, signature_.expiryBlock) ); @@ -145,7 +171,9 @@ contract OwnerData is Context, Ownable { signature_.r, signature_.s ); - require(reqSigner == _signer, "OwnerData: Invalid signature"); + if (reqSigner != _signer) { + revert InvalidSignature(); + } } function _recoverOwnerSignature(bytes memory signature_) private view returns (address) { diff --git a/migrations/252_owner_data.js b/migrations/252_owner_data.js index e2ff33b..0b27838 100644 --- a/migrations/252_owner_data.js +++ b/migrations/252_owner_data.js @@ -8,6 +8,6 @@ module.exports = function (deployer) { argv.trustee || "0xdB33365a8730de2F7574ff1189fB9D337bF4c36d"; const costReceiver = argv.costReceiver || "0xdB33365a8730de2F7574ff1189fB9D337bF4c36d"; - const cost = argv.cost || "1000000000000000000"; + const cost = argv.cost || "1000000000000000"; deployer.deploy(OwnerData, trustee, costReceiver, cost); }; diff --git a/test/owner_data.js b/test/owner_data.js index 81b9118..be68a89 100644 --- a/test/owner_data.js +++ b/test/owner_data.js @@ -82,7 +82,7 @@ contract("OwnerData", async (accounts) => { const tx = await this.ownerDataContract.add( this.exhibitionContract.address, 100001, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: 0 }, ); const { logs } = tx; @@ -112,7 +112,7 @@ contract("OwnerData", async (accounts) => { const tx = await this.ownerDataContract.add( this.exhibitionContract.address, 100003, - [accounts[2], cidBytes, "{duration: 1000}"], + [accounts[2], cidBytes, 0, "{duration: 1000}"], { from: accounts[2], value: 0 }, ); assert.equal(bytesToString(tx.logs[0].args.data.dataHash), cid); @@ -123,11 +123,11 @@ contract("OwnerData", async (accounts) => { await this.ownerDataContract.add( this.exhibitionContract.address, 100003, - [accounts[2], updatedCidBytes, "{duration: 2000}"], + [accounts[2], updatedCidBytes, 0, "{duration: 2000}"], { from: accounts[2], value: 0 }, ); } catch (error) { - assert.equal(error.reason, "OwnerData: data already added"); + assert.equal(error.reason, "Custom error (could not decode)"); } }); @@ -142,7 +142,7 @@ contract("OwnerData", async (accounts) => { const tx1 = await this.ownerDataContract.add( this.exhibitionContract.address, 100002, - [accounts[1], cidBytes1, "{duration: 1000}"], + [accounts[1], cidBytes1, 0, "{duration: 1000}"], { from: accounts[1], value: 0 }, ); assert.equal(bytesToString(tx1.logs[0].args.data.dataHash), cid1); @@ -160,7 +160,7 @@ contract("OwnerData", async (accounts) => { const tx2 = await this.ownerDataContract.add( this.exhibitionContract.address, 100002, - [accounts[2], cidBytes2, "{duration: 2000}"], + [accounts[2], cidBytes2, 0, "{duration: 2000}"], { from: accounts[2], value: 0 }, ); assert.equal(bytesToString(tx2.logs[0].args.data.dataHash), cid2); @@ -178,7 +178,7 @@ contract("OwnerData", async (accounts) => { const tx3 = await this.ownerDataContract.add( this.exhibitionContract.address, 100002, - [accounts[4], cidBytes3, "{duration: 3000}"], + [accounts[4], cidBytes3, 0, "{duration: 3000}"], { from: accounts[4], value: 0 }, ); assert.equal(bytesToString(tx3.logs[0].args.data.dataHash), cid3); @@ -208,14 +208,11 @@ contract("OwnerData", async (accounts) => { await this.ownerDataContract.add( this.exhibitionContract.address, 100001, - [accounts[1], cidBytes, "{duration: 1000}"], + [accounts[1], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: 0 }, ); } catch (error) { - assert.equal( - error.reason, - "OwnerData: data owner and sender mismatch", - ); + assert.equal(error.reason, "Custom error (could not decode)"); } }); @@ -226,7 +223,7 @@ contract("OwnerData", async (accounts) => { await this.ownerDataContract.add( accounts[1], 100001, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: 0 }, ); } catch (error) { @@ -244,31 +241,31 @@ contract("OwnerData", async (accounts) => { await this.ownerDataContract.add( this.exhibitionContract.address, 200002, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: web3.utils.toWei("0.015", "ether") }, ); await this.ownerDataContract.add( this.exhibitionContract.address, 200002, - [accounts[1], cidBytes, "{duration: 1000}"], + [accounts[1], cidBytes, 0, "{duration: 1000}"], { from: accounts[1], value: web3.utils.toWei("0.015", "ether") }, ); await this.ownerDataContract.add( this.exhibitionContract.address, 200002, - [accounts[2], cidBytes, "{duration: 1000}"], + [accounts[2], cidBytes, 0, "{duration: 1000}"], { from: accounts[2], value: web3.utils.toWei("0.015", "ether") }, ); await this.ownerDataContract.add( this.exhibitionContract.address, 200002, - [accounts[1], cidBytes, "{duration: 1000}"], + [accounts[1], cidBytes, 0, "{duration: 1000}"], { from: accounts[1], value: web3.utils.toWei("0.015", "ether") }, ); await this.ownerDataContract.add( this.exhibitionContract.address, 200002, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: web3.utils.toWei("0.015", "ether") }, ); const res = await this.ownerDataContract.get( @@ -300,6 +297,7 @@ contract("OwnerData", async (accounts) => { const data = [ "0x23221e5403511CeC833294D2B1B006e9D639A61b", cidBytes, + 0, "{duration: 1000}", ]; @@ -337,9 +335,12 @@ contract("OwnerData", async (accounts) => { web3.utils.toDecimal(v) + 27, ]; - const tx = await this.ownerDataContract.signedAdd([ - [this.exhibitionContract.address, 100007, data, signs], - ]); + const tx = await this.ownerDataContract.signedAdd( + this.exhibitionContract.address, + 100007, + data, + signs, + ); assert.equal(tx.logs[0].event, "DataAdded"); }); @@ -350,7 +351,7 @@ contract("OwnerData", async (accounts) => { const tx1 = await this.ownerDataContract.add( this.exhibitionContract.address, 200001, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: web3.utils.toWei("0.015", "ether") }, ); assert.equal(tx1.logs[0].event, "DataAdded"); @@ -358,7 +359,7 @@ contract("OwnerData", async (accounts) => { const tx2 = await this.ownerDataContract.add( this.exhibitionContract.address, 200001, - [accounts[2], cidBytes, "{duration: 1000}"], + [accounts[2], cidBytes, 0, "{duration: 1000}"], { from: accounts[2], value: web3.utils.toWei("0.015", "ether") }, ); assert.equal(tx2.logs[0].event, "DataAdded"); @@ -370,6 +371,7 @@ contract("OwnerData", async (accounts) => { const data = [ "0x23221e5403511CeC833294D2B1B006e9D639A61b", cidBytes, + 0, "{duration: 1000}", ]; @@ -401,9 +403,12 @@ contract("OwnerData", async (accounts) => { web3.utils.toDecimal(v) + 27, ]; - const tx = await this.ownerDataContract.signedAdd([ - [this.exhibitionContract.address, 200001, data, signs], - ]); + const tx = await this.ownerDataContract.signedAdd( + this.exhibitionContract.address, + 200001, + data, + signs, + ); assert.equal(tx.logs[0].event, "DataAdded"); }); @@ -413,7 +418,7 @@ contract("OwnerData", async (accounts) => { const tx = await this.ownerDataContract.add( this.fungibleContract.address, 999, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[0], value: 0 }, ); assert.equal(tx.logs[0].event, "DataAdded"); @@ -426,14 +431,11 @@ contract("OwnerData", async (accounts) => { const tx = await this.ownerDataContract.add( this.fungibleContract.address, 999, - [accounts[0], cidBytes, "{duration: 1000}"], + [accounts[0], cidBytes, 0, "{duration: 1000}"], { from: accounts[2], value: 0 }, ); } catch (error) { - assert.equal( - error.reason, - "OwnerData: data owner and sender mismatch", - ); + assert.equal(error.reason, "Custom error (could not decode)"); } }); });