Skip to content

Commit

Permalink
Refactored contract using string as key
Browse files Browse the repository at this point in the history
  • Loading branch information
tienngovan committed Mar 11, 2024
1 parent c9c915e commit 3dba2eb
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 126 deletions.
176 changes: 127 additions & 49 deletions contracts/OwnerData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

/**
* @title OwnerData
* @dev Manages addition data for ERC721 or ERC1155 token.
*/
contract OwnerData is Context, Ownable {
string private constant SIGNED_MESSAGE = "Authorize to write your data to the contract";
using EnumerableSet for EnumerableSet.AddressSet;

string private constant SIGNED_MESSAGE =
"Authorize to write your data to the contract";
uint256 public immutable publicToken;
address public signer;
address public serviceFeeReceiver;
uint256 public serviceFee;
Expand All @@ -33,15 +38,21 @@ contract OwnerData is Context, Ownable {
uint8 v;
}

// contractAddress => tokenID => Data[]
mapping(address => mapping(uint256 => Data[])) private _tokenData;
// contractAddress => tokenID => owner => bool
mapping(address => mapping(uint256 => mapping(address => bool))) private _tokenDataOwner;
// contractAddress => tokenID => bool
mapping(address => mapping(uint256 => bool)) private _publicTokens;
// contractAddress|tokenID => bytes[]
mapping(string => Data[]) private _tokenData;
// contractAddress|tokenID => addresses
mapping(string => EnumerableSet.AddressSet) private _tokenDataOwner;

event DataAdded(address indexed contractAddress, uint256 indexed tokenID, Data data);
event DataRemoved(address indexed contractAddress, uint256 indexed tokenID, uint256[] indexes);
event DataAdded(
address indexed contractAddress,
uint256 indexed tokenID,
Data data
);
event DataRemoved(
address indexed contractAddress,
uint256 indexed tokenID,
uint256[] indexes
);

error TrusteeIsZeroAddress();
error EmptyServiceFeeReceiver();
Expand All @@ -52,8 +63,12 @@ contract OwnerData is Context, Ownable {
error OwnerDataAlreadyAdded();
error InvalidSignature();


constructor(address signer_, address serviceFeeReceiver_, uint256 serviceFee_) {
constructor(
address signer_,
address serviceFeeReceiver_,
uint256 serviceFee_,
uint256 publicToken_
) {
if (signer_ == address(0)) {
revert TrusteeIsZeroAddress();
}
Expand All @@ -63,17 +78,29 @@ contract OwnerData is Context, Ownable {
signer = signer_;
serviceFeeReceiver = serviceFeeReceiver_;
serviceFee = serviceFee_;
publicToken = publicToken_;
}

/// @notice add data to the token
/// @param contractAddress_ - the address of the contract
/// @param tokenID_ - the token ID
/// @param data_ - the data to add
function add(address contractAddress_, uint256 tokenID_, Data calldata data_) external payable {
if (_publicTokens[contractAddress_][tokenID_] && msg.value < serviceFee) {
function add(
address contractAddress_,
uint256 tokenID_,
Data calldata data_
) external payable {
bool isPublicToken_ = publicToken == tokenID_;
if (isPublicToken_ && msg.value < serviceFee) {
revert PaymentRequiredForPublicToken();
}
_addData(_msgSender(), contractAddress_, tokenID_, data_);
_addData(
_msgSender(),
contractAddress_,
tokenID_,
data_,
isPublicToken_
);
if (msg.value > 0) {
payable(serviceFeeReceiver).transfer(msg.value);
}
Expand All @@ -84,8 +111,16 @@ contract OwnerData is Context, Ownable {
/// @param tokenID_ - the token ID
/// @param startIndex - the start index
/// @param count - the count of data
function get(address contractAddress_, uint256 tokenID_, uint256 startIndex, uint256 count) public view returns (Data[] memory) {
Data[] memory data = _tokenData[contractAddress_][tokenID_];
function get(
address contractAddress_,
uint256 tokenID_,
uint256 startIndex,
uint256 count
) public view returns (Data[] memory) {
string memory key = string(
abi.encodePacked(contractAddress_, "|", tokenID_)
);
Data[] memory data = _tokenData[key];
if (startIndex > data.length) {
return new Data[](0);
}
Expand All @@ -103,8 +138,15 @@ contract OwnerData is Context, Ownable {
/// @param contractAddress_ - the address of the contract
/// @param tokenID_ - the token ID
/// @param owner_ - the owner address
function getByOwner(address contractAddress_, uint256 tokenID_, address owner_) public view returns (Data[] memory) {
Data[] memory data = _tokenData[contractAddress_][tokenID_];
function getByOwner(
address contractAddress_,
uint256 tokenID_,
address owner_
) public view returns (Data[] memory) {
string memory key = string(
abi.encodePacked(contractAddress_, "|", tokenID_)
);
Data[] memory data = _tokenData[key];
Data[] memory temp = new Data[](data.length);
uint256 count = 0;
for (uint256 i = 0; i < data.length; i++) {
Expand All @@ -124,8 +166,15 @@ contract OwnerData is Context, Ownable {
/// @param contractAddress_ - the address of the contract
/// @param tokenID_ - the token ID
/// @param indexes_ - the indexes of the data to remove
function remove(address contractAddress_, uint256 tokenID_, uint256[] calldata indexes_) external onlyOwner {
Data[] storage data = _tokenData[contractAddress_][tokenID_];
function remove(
address contractAddress_,
uint256 tokenID_,
uint256[] calldata indexes_
) external onlyOwner {
string memory key = string(
abi.encodePacked(contractAddress_, "|", tokenID_)
);
Data[] storage data = _tokenData[key];
for (uint256 i = 0; i < indexes_.length; i++) {
data[indexes_[i]].dataHash = new bytes(0);
}
Expand All @@ -140,7 +189,9 @@ contract OwnerData is Context, Ownable {

/// @notice the service fee receiver address
/// @param serviceFeeReceiver_ - the address of service fee receiver
function setServiceFeeReceiver(address serviceFeeReceiver_) external onlyOwner {
function setServiceFeeReceiver(
address serviceFeeReceiver_
) external onlyOwner {
if (serviceFeeReceiver_ == address(0)) {
revert EmptyServiceFeeReceiver();
}
Expand All @@ -156,52 +207,55 @@ contract OwnerData is Context, Ownable {
signer = signer_;
}

/// @notice set public tokens
/// @param contractAddresses_ - the addresses of the contracts
/// @param tokenIDs_ - the token IDs
/// @param isPublic_ - the flag of public token
function setPublicTokens(address[] memory contractAddresses_, uint256[] memory tokenIDs_, bool isPublic_) external onlyOwner {
if (contractAddresses_.length == 0 || contractAddresses_.length != tokenIDs_.length) {
revert InvalidParameters();
}
for (uint256 i = 0; i < contractAddresses_.length; i++) {
_publicTokens[contractAddresses_[i]][tokenIDs_[i]] = isPublic_;
}
}

/// @notice add data with signature
/// @param contractAddress_ - the address of the contract
/// @param tokenID_ - the token ID
/// @param data_ - the data to add
/// @param signature_ - the signature
function signedAdd(address contractAddress_, uint256 tokenID_, Data calldata data_, Signature calldata signature_) external {
function signedAdd(
address contractAddress_,
uint256 tokenID_,
Data calldata data_,
Signature calldata signature_
) external {
_validateSignature(signature_);
address account = data_.owner;
if (!_publicTokens[contractAddress_][tokenID_]) {
bool isPublicToken_ = publicToken == tokenID_;
if (!isPublicToken_) {
account = _recoverOwnerSignature(signature_.ownerSign);
}
_addData(account, contractAddress_, tokenID_, data_);
_addData(account, contractAddress_, tokenID_, data_, isPublicToken_);
}

function _addData(address sender_, address contractAddress_, uint256 tokenID_, Data memory data_) private {
function _addData(
address sender_,
address contractAddress_,
uint256 tokenID_,
Data calldata data_,
bool isPublicToken_
) private {
if (data_.owner != sender_) {
revert OwnerAndSenderMismatch();
}
if (data_.dataHash.length == 0) {
revert InvalidParameters();
}

if (!_publicTokens[contractAddress_][tokenID_]) {
if (_tokenDataOwner[contractAddress_][tokenID_][data_.owner]) {
string memory key = string(
abi.encodePacked(contractAddress_, "|", tokenID_)
);

if (!isPublicToken_) {
if (EnumerableSet.contains(_tokenDataOwner[key], data_.owner)) {
revert OwnerDataAlreadyAdded();
}
if (!_isOwner(contractAddress_, tokenID_, data_.owner)) {
revert SenderIsNotTheOwner();
}
_tokenDataOwner[contractAddress_][tokenID_][data_.owner] = true;
_tokenDataOwner[key].add(data_.owner);
}

_tokenData[contractAddress_][tokenID_].push(data_);
_tokenData[key].push(data_);

emit DataAdded(contractAddress_, tokenID_, data_);
}
Expand All @@ -211,7 +265,12 @@ contract OwnerData is Context, Ownable {
revert InvalidSignature();
}
bytes32 message = keccak256(
abi.encode(block.chainid, address(this), signature_.ownerSign, signature_.expiryBlock)
abi.encode(
block.chainid,
address(this),
signature_.ownerSign,
signature_.expiryBlock
)
);
address reqSigner = ECDSA.recover(
ECDSA.toEthSignedMessageHash(message),
Expand All @@ -224,18 +283,37 @@ contract OwnerData is Context, Ownable {
}
}

function _recoverOwnerSignature(bytes memory signature_) private view returns (address) {
bytes memory message = abi.encodePacked(SIGNED_MESSAGE, " ", Strings.toHexString(address(this)), ".");
function _recoverOwnerSignature(
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_);
}

function _isOwner(address contractAddress, uint256 tokenID, address account) private view returns (bool) {
if (IERC165(contractAddress).supportsInterface(type(IERC1155).interfaceId)) {
function _isOwner(
address contractAddress,
uint256 tokenID,
address account
) private view returns (bool) {
if (
IERC165(contractAddress).supportsInterface(
type(IERC1155).interfaceId
)
) {
return IERC1155(contractAddress).balanceOf(account, tokenID) > 0;
}
if (IERC165(contractAddress).supportsInterface(type(IERC721).interfaceId)) {
if (
IERC165(contractAddress).supportsInterface(
type(IERC721).interfaceId
)
) {
return IERC721(contractAddress).ownerOf(tokenID) == account;
}
return false;
}
}
}
3 changes: 2 additions & 1 deletion migrations/252_owner_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ module.exports = function (deployer) {
const costReceiver =
argv.costReceiver || "0xdB33365a8730de2F7574ff1189fB9D337bF4c36d";
const cost = argv.cost || "1000000000000000";
deployer.deploy(OwnerData, trustee, costReceiver, cost);
const publicToken = argv.publicToken || "10000";
deployer.deploy(OwnerData, trustee, costReceiver, cost, publicToken);
};
2 changes: 2 additions & 0 deletions package-lock.json

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

Loading

0 comments on commit 3dba2eb

Please sign in to comment.