// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
library Lib {
/* NFT 구조체 */
struct NFT {
string tokenName;
string imageCID;
string metaDataCID;
address publisher;
uint256 tokenId;
}
}
Lib.sol
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract TestToken is ERC20, Ownable {
using SafeMath for uint256;
bool public paused = false;
address private nftAddress;
uint256 private _totalSupply;
address private immutable _owner;
uint256 private initialSupply = 1000000000 * (10**uint256(decimals()));
constructor() ERC20("TestToken", "TST") {
_owner = msg.sender;
_mint(msg.sender, initialSupply);
}
function setNFTAddress(address _target) external {
require(msg.sender == _owner, "not owner");
require(_target != address(0), "0 address");
nftAddress = _target;
}
function approveAllNFT(bool approve) external {
require(address(nftAddress) != address(0), "NFT address not set");
uint256 amount = approve == true ? type(uint256).max : 0;
_approve(msg.sender, nftAddress, amount);
}
function mint(address _to, uint256 _value) public onlyOwner returns (bool) {
_mint(_to, _value);
return true;
}
function burn(address _address, uint256 _value) public onlyOwner {
require(_value <= balanceOf(_address), "Balance is too small.");
_burn(_address, _value);
}
function currentTime() public view returns (uint256) {
return block.timestamp;
}
function afterTime(uint256 _value) public view returns (uint256) {
return block.timestamp + _value;
}
fallback() external payable {}
receive() external payable {}
}
TestToken.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Lib.sol";
contract TestNFT is ERC721URIStorage, ERC721Enumerable, Ownable {
using Counters for Counters.Counter;
Counters.Counter public _tokenIds;
uint256 public maxSupply = 100;
address private immutable _owner;
uint256 public total = totalSupply();
uint256 public price = 0.0000000000000001 * 10**18;
ERC20 public immutable tokenAddress;
mapping(uint256 => Lib.NFT) public nftStorage;
constructor(address _tokenAddress) ERC721("TestNFT", "TNT") {
_owner = msg.sender;
tokenAddress = ERC20(_tokenAddress);
}
/* URIStorage & Enumerable 충돌 override */
function _burn(uint256 _tokenId)
internal
override(ERC721, ERC721URIStorage)
{
super._burn(_tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721Enumerable)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId,
uint256 batchSize
) internal override(ERC721, ERC721Enumerable) {
super._beforeTokenTransfer(from, to, tokenId, batchSize);
}
function _baseURI() internal pure override(ERC721) returns (string memory) {
return "https://ipfsuploadapp.infura-ipfs.io/ipfs/";
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return string(abi.encodePacked(super.tokenURI(tokenId)));
}
/* URIStorage & Enumerable 충돌 override */
function mintNFT(
string memory _tokenName,
string memory _imageCID,
string memory _metaDataCID
) external {
require(total < maxSupply, "supply reached");
require(tokenAddress.balanceOf(msg.sender) > price);
tokenAddress.transferFrom(msg.sender, address(this), price);
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
Lib.NFT memory newNFT = Lib.NFT(
_tokenName,
_imageCID,
_metaDataCID,
msg.sender,
newTokenId
);
_safeMint(msg.sender, newTokenId);
_setTokenURI(newTokenId, _metaDataCID);
total++;
nftStorage[newTokenId] = newNFT;
}
function burnNFT(uint256 _tokenId) external {
_burn(_tokenId);
delete nftStorage[_tokenId];
}
function viewBalance() external view returns (uint256) {
uint256 balance = o2TokenAddress.balanceOf(address(this));
return balance;
}
// o2token owner한테 반환
function withdrawTokens() external onlyOwner {
uint256 currentBalance = o2TokenAddress.balanceOf(address(this));
require(currentBalance > 0, "no tokens");
o2TokenAddress.transfer(_owner, currentBalance);
}
// ether owner한테 반환
function withdrawEther() external onlyOwner {
payable(msg.sender).transfer(address(this).balance);
}
function getTokenIds(address _address)
external
view
returns (uint256[] memory)
{
uint256 balanceLength = balanceOf(_address);
uint256[] memory tokenIds = new uint256[](balanceLength);
for (uint256 i = 0; i < balanceLength; i++) {
uint256 tokenId = tokenOfOwnerByIndex(_address, i);
tokenIds[i] = tokenId;
}
return tokenIds;
}
function getNfts(uint256[] memory tokenIds)
external
view
returns (Lib.NFT[] memory)
{
Lib.NFT[] memory nfts = new Lib.NFT[](tokenIds.length);
for (uint256 i = 0; i < tokenIds.length; i++) {
nfts[i] = nftStorage[tokenIds[i]];
}
return nfts;
}
fallback() external payable {}
receive() external payable {}
}
TestNFT.sol
이렇게 3개를 remixIDE나 klaytnIDE에서 테스트할때는 정상적으로 TestToken이 NFT컨트랙트에 전송되는데
truffle을 통해 baobab 테스트넷에 배포후 민팅을 진행하면 자꾸 revert가 납니다.
approve랑 increaseAllowance를 다 해봤는데도 불구하고 되지 않는데 혹시 제가 놓친게 있을까요??
에러 내용
Error: evm: execution reverted
{
"blockHash": "0xb43cf45b27ac22f918e5397d4f22faf03f511f782d6b7e8fe7b78c9ac724b9f9",
"blockNumber": 106978939,
"contractAddress": null,
"effectiveGasPrice": "0x5d21dba00",
"feePayer": "0xadc565bb88aa72aa14b98cb6196f216900614b3c",
"feePayerSignatures": [
{
"V": "0x7f6",
"R": "0x2c34fd032497c9666fa52ddb0f4ec689bdf5065546ad3df85de42954dc063752",
"S": "0x48a1ec6e88c67ef76a57e2cb9ac8207311dd94512dca2c8df459965e2a45d2e4"
}
],
"from": "0xadc565bb88aa72aa14b98cb6196f216900614b3c",
"gas": "0x927c0",
"gasPrice": "0xba43b7400",
"gasUsed": 72390,
"input": "0x0c3b7b72000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000474657374000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004746573740000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000047465737400000000000000000000000000000000000000000000000000000000",
"logs": [],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"nonce": "0x194",
"senderTxHash": "0xd80de585917ef8d9df33c63902d95e82f083a0296cd937a2f430b08930ec21d9",
"signatures": [
{
"V": "0x7f5",
"R": "0x149253a2640f06f118f94b631dd2cf047f0dfc9121a9e0c1190c85bb7623a723",
"S": "0x2778d2d73b83f8d1d4270bf9db7c309cd6a6c55a3a55e575e0d0af68b020ae97"
}
],
"status": false,
"to": "0x40708de71dd9ea9f491db70f7b56742357da2c2e",
"transactionHash": "0xed7c674d5f23be9c06f134e7bb91f1c5b00e07ea65d958607f680d2c1b7330fc",
"transactionIndex": 3,
"txError": "0x9",
"type": "TxTypeFeeDelegatedSmartContractExecution",
"typeInt": 49,
"value": "0x0"
}
at checkForNormalTx
caver.js를 사용하였으며 node express 서버를 통해 개발하고 있었습니다.
권한 부여 로직
const approveInstanceData = {
method: baobabTokenInstance.methods.approve(
baobabNftAddress,
price
),
contractAddress: baobabTokenAddress,
};
const feeDelegateTx = async (abi, address) => {
const { rawTransaction: senderRawTransaction } =
await caver.klay.accounts.signTransaction(
{
type: "FEE_DELEGATED_SMART_CONTRACT_EXECUTION",
from: testnetAccount.address,
to: approveInstanceData.contractAddress,
gas: "600000",
value: caver.utils.toPeb("0", "KLAY"),
data: approveInstanceData.method.encodeABI(),
},
testnetAccount.privateKey
);
const receipt = await caver.klay.sendTransaction({
senderRawTransaction,
feePayer: testnetAccount.address,
});
console.log(receipt);
};
await feeDelegateTx(
approveInstanceData.method.encodeABI,
testnetAccount.address
);
NFT 민팅 로직
const mintInstanceData = {
method: baobabNftInstance.methods.mintNFT(
nftName,
imageCID,
metaDataCID
),
contractAddress: baobabNftAddress,
};
const feeDelegateTx = async (abi, address) => {
const { rawTransaction: senderRawTransaction } =
await caver.klay.accounts.signTransaction(
{
type: "FEE_DELEGATED_SMART_CONTRACT_EXECUTION",
from: testnetAccount.address,
to: mintInstanceData.contractAddress,
gas: "600000",
value: caver.utils.toPeb("0", "KLAY"),
data: mintInstanceData.method.encodeABI(),
},
testnetAccount.privateKey
);
const receipt = await caver.klay.sendTransaction({
senderRawTransaction,
feePayer: testnetAccount.address,
});
console.log(receipt);
};
await feeDelegateTx(
mintInstanceData.method.encodeABI,
testnetAccount.address
);
대납 서명을 하려고 했는데
컨트랙트에서 function mintNFT에서 transferFrom을 빼면 민팅이 너무 잘됩니다…
누락된 부분이나 보시고 이해 안가시는 부분에 대해서 가감없이 지적해주시면 감사하겠습니다