KIP17의 Factory pattern 구현시 instance의 저장 관련

안녕하세요, 먼저 질문이 solidity와 관련이 높기 때문에 포럼 취지에 맞지 않는 질문이라면 죄송합니다.

KIP17을 Factory pattern과 비슷하게 구현 중이고, Contract의 대략적인 코드는 아래와 같습니다.

  1. 먼저 Factory에서 각각 deploy될 KIP17의 상속 컨트랙트입니다.
    contract NFTToken is KIP17Full {

address payable owner;

constructor(string memory _name, string memory _symbol) KIP17Full(_name, _symbol) public {

owner = msg.sender;

}
…그외 함수들)

  1. Factory가 될 컨트랙트입니다.
    contract NFTFactory{

    NFTToken public minter;

    address[] public minters;

    address payable owner;

    mapping (string => NFTMinter[]) public tokenManager;

    constructor() public {

     owner = msg.sender;
    

    }

    function mintNewToken(string memory _tokenHash, string memory _name, string memory _symbol) public returns (NFTToken){

     minter = new NFTToken(_name, _symbol);
    
     minters.push(address(minter));
    
     tokenManager[_tokenHash].push(minter);
    
     return tokenManager[_tokenHash][0];
    

    }

    function isMintExist(string memory _tokenHash) public view returns(uint){

    return minters.length;

    }
    …그 외 함수들
    }

각 NFTToken 컨트랜트들은 tokenHash라는 임의의 해쉬값에 의해 구분되고, 이를 tokenManager라는 mapping으로 관리하려고 합니다. Factory의 isMintExist는 최초로 mintNewToken이 호출된 다음 잘 저장이 되었는지 확인하고자 만든 함수입니다.

비슷한 패턴과 예제들을 솔리디티 예제들에서 보고 만든 것인데, 만들어진 NFTToken들이 각 mapping이나 배열에 전혀 저장이 되지 않습니다. isMintExist에서 minters.length를 찍어보아도 0으로 출력됩니다. 다만, 최초로 mintNewToken을 호출하면 리턴값으로 나오는 minter instance의 주소(address형과는 다르지만 형식이 주소이기에)는 잘 리턴됩니다만, 이후 isMintExist를 호출시에 모든 배열들이 초기화된 것처럼 어떤 값도 저장되어있지 않습니다.

혹시 ERC721과 KIP17이 관련 부분에서 다른 점이 있을까요?

안녕하세요

다음과같이 테스트 해보았는데 정상적으로 작동하는것을 확인했습니다.

pragma solidity 0.5.6;

import "@openzeppelin/contracts/ownership/Ownable.sol";

contract Item {
  uint256 public id;

  constructor() public {

  }

  function setId(uint256 newId) public {
    id = newId;
  }
}

contract Game is Ownable {
  mapping(address => Item[]) items;

  constructor() public {
    Item item = new Item();
    item.setId(100);

    items[msg.sender].push(item);
    items[msg.sender].length; // 1
    items[msg.sender][0].id(); // 100
  }
}

contract를 추가하는 방법도 있지만

struct를 사용하는 방법을 추천드립니다. struct를 사용하는 방법은 다음과 같습니다.

pragma solidity 0.5.6;

import "@openzeppelin/contracts/ownership/Ownable.sol";

contract Game is Ownable {
  struct Item {
    uint256 id;
  }

  mapping(address => Item[]) items;

  constructor() public {
    Item memory item;
    item.id = 100;

    items[msg.sender].push(item);
    items[msg.sender].length; // 1
    items[msg.sender][0].id; // 100
  }
}
3 Likes

감사합니다! 말씀하신대로 적용해보았는데요,

  1. 만들어질 컨트랙트에 따로 setInit()라는 function을 만들어서 넘어온 argument를 저장하는 함수를 만들었습니다.
    contract NFTToken is KIP17Full {

address payable owner;

constructor(string memory _name, string memory _symbol) KIP17Full(_name, _symbol) public {

owner = msg.sender;

}

function setInit(string memory _name, string memory _symbol) public{
** name = _name;**
** symbol = _symbol;**
** }**
…그외 함수들)

  1. 팩토리 컨트랙트에는 새로운 컨트랙트를 선언하고 setInit()를 호출했습니다.
    contract NFTFactory{NFTToken public minter;address[] public minters;address payable owner;mapping (string => NFTMinter[]) public tokenManager;constructor() public {
 owner = msg.sender;

}function mintNewToken(string memory _tokenHash, string memory _name, string memory _symbol) public returns (NFTToken){

 minter = new NFTToken(_name, _symbol);
**minter.setInit(_name, _symbol);**

 minters.push(address(minter));

 tokenManager[_tokenHash].push(minter);

 return tokenManager[_tokenHash][0];

}function isMintExist(string memory _tokenHash) public view returns(uint){return minters.length;}
…그 외 함수들
}

deploy를 했더니 다음과 같은 오류가 발생했습니다.

“NFTFactory” received a generic error from Geth that
can be caused by hitting revert in a contract constructor or running out of gas.

아무래도 가스비 문제는 아니고 setInit()를 호출했을 때 revert된 것 같습니다.

앞서 제시해주신 예제에서는 Factory를 deploy하면서 constructor에서 곧바로 Item의 constructor를 호출하시는데, 저의 경우엔 프론트엔드에서 Factory의 함수를 호출할 때 각 NFTToken을 생성하도록 만들고 싶어서 따로 함수를 만들어주었는데요,

덕분에 문제의 답에 접근해가는 느낌입니다만, 아무래도 new NFTToken(_name, _symbol)로 컨트랙트를 생성할 때 문제가 생기는 것 같습니다…

안녕하세요

아래와 같이 다시 바오밥 네트워크에서 테스트 해보았습니다.

// Token.sol
pragma solidity 0.5.6;

import "hardhat/console.sol";
import "@klaytn/contracts/token/KIP17/KIP17Token.sol";

contract Token is KIP17Token {
  constructor(address owner, string memory name, string memory symbol) public KIP17Token(name, symbol) {
    /*
      TokenFactory 안에서 실행하면 TokenFactory address만
      MinterRole, PauserRole권한을 가지기 때문에 다음과같이 추가해주었습니다.
    */
    addMinter(owner);
    addPauser(owner);
  }
}
pragma solidity 0.5.6;

import "hardhat/console.sol";
import "@openzeppelin/contracts/ownership/Ownable.sol";
import "./Token.sol";

contract TokenFactory is Ownable {
  mapping(address => Token[]) tokens;

  constructor() public {

  }

  function createToken(string memory name, string memory symbol) public {
    Token token = new Token(msg.sender, name, symbol);
    tokens[msg.sender].push(token);
  }

  function getMyTokens() public view returns (Token[] memory) {
    return tokens[msg.sender];
  }
}

이렇게 하면 잘 되는것 같습니다.

아래 createToken함수 호출 트랜잭션 데이터입니다.

주의사항

  • A Contract에서 B Contract함수를 호출할때 B함수 안의 msg.sender값은 A Contract Address가 됩니다.
2 Likes

감사합니다.

revert된 이유는 mapping(string => 컨트랙트)를 선언하고 argument로 넘어온 string이 memory여서 생기는 문제 같습니다. string 대신 address로 설정하니 잘 실행되었고, 제시해주신 예시 참고하여 잘 완성했습니다!

2 Likes