diff --git a/contracts/AINFT721.sol b/contracts/AINFT721.sol index dba304c..9ebad72 100644 --- a/contracts/AINFT721.sol +++ b/contracts/AINFT721.sol @@ -104,10 +104,10 @@ contract AINFT721 is super._requireMinted(tokenId); } - function _isApprovedOrOwner( + function isApprovedOrOwner( address spender, uint256 tokenId - ) internal view virtual override(ERC721) returns (bool) + ) public view virtual returns (bool) { return super._isApprovedOrOwner(spender, tokenId); } @@ -194,7 +194,7 @@ contract AINFT721 is string memory newTokenURI ) external returns (bool) { require( - (_isApprovedOrOwner(_msgSender(), tokenId) || + (isApprovedOrOwner(_msgSender(), tokenId) || hasRole(DEFAULT_ADMIN_ROLE, _msgSender())), "AINFT721::updateTokenURI() - not owner of tokenId or contract owner" ); diff --git a/contracts/AINFT721Upgradeable.sol b/contracts/AINFT721Upgradeable.sol index 100870c..8adfd0a 100644 --- a/contracts/AINFT721Upgradeable.sol +++ b/contracts/AINFT721Upgradeable.sol @@ -126,6 +126,14 @@ contract AINFT721Upgradeable is { return super._isApprovedOrOwner(spender, tokenId); } + + function isApprovedOrOwner( + address spender, + uint256 tokenId + ) public view virtual returns (bool) + { + return _isApprovedOrOwner(spender, tokenId); + } //// diff --git a/contracts/AINPayment.sol b/contracts/AINPayment.sol new file mode 100644 index 0000000..4ae84be --- /dev/null +++ b/contracts/AINPayment.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import "@openzeppelin/contracts/interfaces/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; + +import "./interfaces/IAINFT.sol"; + +contract AINPayment is Ownable, ReentrancyGuard { + IERC20 public _ain; + IAINFT public _ainft; + + uint256[2] public _price; // [update_price, rollback_price] + + constructor(address ainft, address ain) { + require(ain == 0x3A810ff7211b40c4fA76205a14efe161615d0385, "AINPayment: only supports AIN ERC20"); + _ain = IERC20(ain); + _ainft = IAINFT(ainft); + _price = [0, 0]; + } + + function setPrice(uint256[2] calldata price) external onlyOwner { + require(!(_price[0] == price[0] && _price[1] == price[1]), "AINPayment::setPrice, the new price is same as the old one."); + _price[0] = price[0]; + _price[1] = price[1]; + } + + function pay(uint256 amount) public nonReentrant returns(bool) { + require(amount > 0, "Amount must be greater than 0"); + require(_ain.balanceOf(_msgSender()) >= amount, "Insufficient balance"); + + bool success = _ain.transfer(address(this), amount); + return success; + } + + function executeUpdate(uint256 tokenId, string memory newTokenURI) external returns(bool) { + require(_ainft.isApprovedOrOwner(_msgSender(), tokenId), "AINPayment::executeUpdate, owner of AINFT or holder only call this"); + require(pay(_price[0]), "Insufficient AIN"); + + bool success = _ainft.updateTokenURI(tokenId, newTokenURI); + return success; + + } + + function executeRollback(uint256 tokenId) external returns(bool) { + require(_ainft.isApprovedOrOwner(_msgSender(), tokenId), "AINPayment::executeRollback, owner of AINFT or holder only call this"); + require(pay(_price[1]), "Insufficient AIN"); + + bool success = _ainft.rollbackTokenURI(tokenId); + return success; + + } + + function withdraw(uint256 amount) public onlyOwner nonReentrant returns(bool) { + require(owner() != address(0), "Owner should be set"); + require(_ain.balanceOf(address(this)) >= amount, "Insufficient balance"); + + _ain.approve(owner(), amount); + require(_ain.allowance(address(this), owner()) >= amount, "Insufficient amount is allowed"); + bool success = _ain.transferFrom(address(this), owner(), amount); + return success; + } + + function withdrawAll() public onlyOwner nonReentrant returns(bool) { + require(owner() != address(0), "Owner should be set"); + + uint256 stackedAin = _ain.balanceOf(address(this)); + _ain.approve(owner(), stackedAin); + require(_ain.allowance(address(this), owner()) >= stackedAin, "Insufficient amount is allowed"); + bool success = _ain.transferFrom(address(this), owner(), stackedAin); + return success; + } + + function destruct(string memory areYouSure) external payable onlyOwner { + require(owner() != address(0), "Owner should be set"); + require(keccak256(abi.encodePacked(areYouSure)) == keccak256(abi.encodePacked("DELETE")), "Please type DELETE if you really want to destruct"); + + // 1. withdraw all AIN to owner + withdrawAll(); + + // 2. withdraw all ethers stored in this contract to owner + address payable _owner = payable(owner()); + uint256 balance = address(this).balance; + require(balance > 0, "The contract has no funds to withdraw"); + _owner.transfer(balance); + } +} diff --git a/contracts/interfaces/IAINFT.sol b/contracts/interfaces/IAINFT.sol index f300f4e..32001fa 100644 --- a/contracts/interfaces/IAINFT.sol +++ b/contracts/interfaces/IAINFT.sol @@ -1,9 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.9; +// import "@openzeppelin/contracts/interfaces/IERC721Enumerable.sol"; + //TODO: implement the function inside interface IAINFT { + function isApprovedOrOwner(address spender, uint256 tokenId) external view returns (bool); + function tokenURICurrentVersion(uint256 tokenId) external view returns (uint256); + function setBaseURI(string memory newBaseURI) external returns (bool); + ///@dev fetch the tokenURI of tokenId by certain version function tokenURIByVersion(uint256 tokenId, uint256 uriVersion) external view returns (string memory);